From df59c5151629c842817f86bbfa4dadcafadb2e70 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Thu, 10 Sep 2026 15:38:40 -0700 Subject: [PATCH 01/35] chore: introduce non-blocking .NET quality analysis (#16) Enable the analyzers that ship with the pinned .NET SDK and add a root .editorconfig that defines formatting, naming, and IDE style explicitly. Every rule reports as a warning at this layer so the diagnostic backlog is visible without blocking the build. Add scripts/Test-DotNetQuality.ps1 as the single quality entry point shared by local development, CI, and the future pre-push hook, and invoke it from the Windows test job. Add CONTRIBUTING.md and a pull request template, which the repository previously lacked. GenerateDocumentationFile is required for build-time IDE0005, so CS1591 is suppressed rather than satisfied with low-value XML comments. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 331d9f1d-2f78-491c-8347-19969cb5ce11 --- .editorconfig | 265 +++++++++++++++++++++++++++++ .github/copilot-instructions.md | 15 ++ .github/pull_request_template.md | 76 +++++++++ .github/workflows/gateway-msix.yml | 7 + CONTRIBUTING.md | 152 +++++++++++++++++ Directory.Build.props | 31 ++++ scripts/Test-DotNetQuality.ps1 | 80 +++++++++ 7 files changed, 626 insertions(+) create mode 100644 .editorconfig create mode 100644 .github/pull_request_template.md create mode 100644 CONTRIBUTING.md create mode 100644 scripts/Test-DotNetQuality.ps1 diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..1d7a918 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,265 @@ +root = true + +# Repository-wide defaults. `.gitattributes` normalizes tracked text to LF, so +# `end_of_line` must stay `lf`; otherwise `dotnet format` rewrites files with +# CRLF and every formatted line shows up as a spurious diff. +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{json,jsonc,yml,yaml}] +indent_size = 2 + +[*.md] +# Markdown uses trailing double-space for hard line breaks. +trim_trailing_whitespace = false + +[*.{appxmanifest,csproj,props,targets,slnx,xml}] +indent_size = 2 + +[*.ps1] +indent_size = 4 + +[*.cs] +indent_size = 4 +max_line_length = 100 + +#### .NET code style #### + +# `this.` qualification is not used anywhere in this repository. +dotnet_style_qualification_for_field = false:warning +dotnet_style_qualification_for_property = false:warning +dotnet_style_qualification_for_method = false:warning +dotnet_style_qualification_for_event = false:warning + +dotnet_style_predefined_type_for_locals_parameters_members = true:warning +dotnet_style_predefined_type_for_member_access = true:warning + +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_explicit_tuple_names = true:warning +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +# IDE0045/IDE0046: collapsing guard clauses into conditional expressions hurts +# readability in this codebase, which uses early-return validation throughout. +dotnet_style_prefer_conditional_expression_over_assignment = false:silent +dotnet_style_prefer_conditional_expression_over_return = false:silent +dotnet_style_prefer_compound_assignment = true:warning +dotnet_style_prefer_simplified_boolean_expressions = true:warning +dotnet_style_prefer_simplified_interpolation = true:warning +dotnet_style_null_propagation = true:warning +dotnet_style_coalesce_expression = true:warning +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning +dotnet_style_readonly_field = true:warning +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_namespace_match_folder = true:suggestion + +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion + +# Accessibility modifiers are written explicitly, except where C# requires +# them to be omitted (for example interface members). +dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning + +#### C# code style #### + +# `var` versus explicit types is left to the author. Both forms are idiomatic, +# this codebase deliberately mixes them, and enforcing either direction is +# churn that catches no defects. +csharp_style_var_for_built_in_types = false:silent +csharp_style_var_when_type_is_apparent = true:silent +csharp_style_var_elsewhere = false:silent + +csharp_style_expression_bodied_methods = when_on_single_line:suggestion +csharp_style_expression_bodied_constructors = false:suggestion +csharp_style_expression_bodied_operators = when_on_single_line:suggestion +csharp_style_expression_bodied_properties = true:suggestion +csharp_style_expression_bodied_indexers = true:suggestion +csharp_style_expression_bodied_accessors = true:suggestion +csharp_style_expression_bodied_lambdas = true:suggestion +csharp_style_expression_bodied_local_functions = when_on_single_line:suggestion + +csharp_style_pattern_matching_over_is_with_cast_check = true:warning +csharp_style_pattern_matching_over_as_with_null_check = true:warning +csharp_style_prefer_not_pattern = true:warning +csharp_style_prefer_pattern_matching = true:suggestion +csharp_style_prefer_switch_expression = true:suggestion +csharp_style_prefer_extended_property_pattern = true:suggestion + +csharp_style_inlined_variable_declaration = true:warning +csharp_style_deconstructed_variable_declaration = true:suggestion +# Guard clauses that throw read better than ternary `throw` expressions, and +# this codebase already uses them consistently. +csharp_style_throw_expression = false:silent +csharp_style_conditional_delegate_call = true:warning +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_prefer_null_check_over_type_check = true:warning +csharp_style_prefer_tuple_swap = true:suggestion +csharp_style_prefer_utf8_string_literals = true:suggestion +csharp_style_prefer_readonly_struct = true:suggestion +csharp_style_prefer_readonly_struct_member = true:suggestion +csharp_style_prefer_primary_constructors = false:suggestion + +csharp_prefer_simple_using_statement = true:suggestion +csharp_prefer_static_local_function = true:warning +csharp_prefer_static_anonymous_function = true:suggestion +csharp_prefer_braces = true:warning +csharp_style_namespace_declarations = file_scoped:warning +csharp_style_prefer_method_group_conversion = true:suggestion +csharp_style_prefer_top_level_statements = false:suggestion +# IDE0058: requiring `_ =` on every ignored return value is noise. Ignoring the +# result of calls such as `Directory.CreateDirectory` is ordinary C#. +csharp_style_unused_value_expression_statement_preference = discard_variable:silent +# IDE0059 catches dead stores, which are a real defect signal. +csharp_style_unused_value_assignment_preference = discard_variable:warning + +csharp_using_directive_placement = outside_namespace:warning + +#### C# formatting #### + +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = false +csharp_indent_switch_labels = true +csharp_indent_labels = one_less_than_current +csharp_indent_block_contents = true +csharp_indent_braces = false + +csharp_space_after_cast = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_after_comma = true +csharp_space_before_comma = false +csharp_space_after_dot = false +csharp_space_before_dot = false +csharp_space_after_semicolon_in_for_statement = true +csharp_space_before_semicolon_in_for_statement = false +csharp_space_around_declaration_statements = false +csharp_space_before_open_square_brackets = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_square_brackets = false +csharp_space_around_binary_operators = before_and_after + +csharp_preserve_single_line_statements = false +csharp_preserve_single_line_blocks = true + +#### Naming conventions #### + +dotnet_naming_style.pascal_case.capitalization = pascal_case +dotnet_naming_style.camel_case.capitalization = camel_case + +dotnet_naming_style.interface_prefix.capitalization = pascal_case +dotnet_naming_style.interface_prefix.required_prefix = I + +dotnet_naming_style.type_parameter_prefix.capitalization = pascal_case +dotnet_naming_style.type_parameter_prefix.required_prefix = T + +dotnet_naming_style.underscore_camel_case.capitalization = camel_case +dotnet_naming_style.underscore_camel_case.required_prefix = _ + +dotnet_naming_symbols.interfaces.applicable_kinds = interface +dotnet_naming_symbols.interfaces.applicable_accessibilities = * + +dotnet_naming_symbols.type_parameters.applicable_kinds = type_parameter +dotnet_naming_symbols.type_parameters.applicable_accessibilities = * + +dotnet_naming_symbols.types_and_members.applicable_kinds = class, struct, enum, property, method, event, delegate, local_function +dotnet_naming_symbols.types_and_members.applicable_accessibilities = * + +dotnet_naming_symbols.constants.applicable_kinds = field +dotnet_naming_symbols.constants.applicable_accessibilities = * +dotnet_naming_symbols.constants.required_modifiers = const + +dotnet_naming_symbols.static_readonly_fields.applicable_kinds = field +dotnet_naming_symbols.static_readonly_fields.applicable_accessibilities = * +dotnet_naming_symbols.static_readonly_fields.required_modifiers = static, readonly + +dotnet_naming_symbols.instance_fields.applicable_kinds = field +dotnet_naming_symbols.instance_fields.applicable_accessibilities = private, private_protected + +dotnet_naming_symbols.parameters_and_locals.applicable_kinds = parameter, local + +dotnet_naming_rule.interfaces_are_prefixed.symbols = interfaces +dotnet_naming_rule.interfaces_are_prefixed.style = interface_prefix +dotnet_naming_rule.interfaces_are_prefixed.severity = warning + +dotnet_naming_rule.type_parameters_are_prefixed.symbols = type_parameters +dotnet_naming_rule.type_parameters_are_prefixed.style = type_parameter_prefix +dotnet_naming_rule.type_parameters_are_prefixed.severity = warning + +dotnet_naming_rule.constants_are_pascal_case.symbols = constants +dotnet_naming_rule.constants_are_pascal_case.style = pascal_case +dotnet_naming_rule.constants_are_pascal_case.severity = warning + +dotnet_naming_rule.static_readonly_fields_are_pascal_case.symbols = static_readonly_fields +dotnet_naming_rule.static_readonly_fields_are_pascal_case.style = pascal_case +dotnet_naming_rule.static_readonly_fields_are_pascal_case.severity = warning + +dotnet_naming_rule.instance_fields_are_underscore_camel_case.symbols = instance_fields +dotnet_naming_rule.instance_fields_are_underscore_camel_case.style = underscore_camel_case +dotnet_naming_rule.instance_fields_are_underscore_camel_case.severity = warning + +dotnet_naming_rule.parameters_and_locals_are_camel_case.symbols = parameters_and_locals +dotnet_naming_rule.parameters_and_locals_are_camel_case.style = camel_case +dotnet_naming_rule.parameters_and_locals_are_camel_case.severity = warning + +dotnet_naming_rule.types_and_members_are_pascal_case.symbols = types_and_members +dotnet_naming_rule.types_and_members_are_pascal_case.style = pascal_case +dotnet_naming_rule.types_and_members_are_pascal_case.severity = warning + +#### Analyzer severities #### +# +# This layer reports every rule below as a warning so the diagnostic backlog is +# visible without blocking the build. Later PRs in this stack promote one +# cohesive rule family at a time to `error` in the same change that clears it. + +# Unnecessary usings. Requires `GenerateDocumentationFile` to run at build time. +dotnet_diagnostic.IDE0005.severity = warning + +# Style rules with findings in the current tree. +dotnet_diagnostic.IDE0305.severity = warning +dotnet_diagnostic.IDE0330.severity = warning + +# Interop and security. +dotnet_diagnostic.CA5392.severity = warning +dotnet_diagnostic.CA1838.severity = warning + +# Public contracts and globalization. +dotnet_diagnostic.CA1062.severity = warning +dotnet_diagnostic.CA1307.severity = warning +dotnet_diagnostic.CA1308.severity = warning + +# Asynchronous code. +dotnet_diagnostic.CA2007.severity = warning +dotnet_diagnostic.CA1849.severity = warning + +# Lifetime and design. +dotnet_diagnostic.CA2000.severity = warning +dotnet_diagnostic.CA1031.severity = warning +dotnet_diagnostic.CA1515.severity = warning +dotnet_diagnostic.CA1859.severity = warning + +[tests/**/*.cs] +# xUnit theory and member data intentionally expose public members, and test +# names describe scenarios rather than following production naming. +dotnet_diagnostic.CA1515.severity = none diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index e98d94f..731c9cd 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -10,6 +10,10 @@ Run commands from the repository root in PowerShell 7 (`pwsh`). dotnet restore .\OpenClaw.Gateway.MSIX.slnx dotnet build .\OpenClaw.Gateway.MSIX.slnx --configuration Release --no-restore +# Canonical quality gate: restore plus a Release rebuild with static analysis. +# Used by local development, CI, and the optional pre-push hook. +.\scripts\Test-DotNetQuality.ps1 + # Publish the launcher through the NativeAOT toolchain without MSIX content. $vsInstaller = Join-Path ` ([Environment]::GetFolderPath([Environment+SpecialFolder]::ProgramFilesX86)) ` @@ -77,6 +81,17 @@ and ARM64 separately. ## Repository conventions +- Static analysis uses only the analyzers shipped by the pinned SDK. + `Directory.Build.props` sets `AnalysisMode=All`, `EnforceCodeStyleInBuild`, + and `GenerateDocumentationFile` (required for build-time `IDE0005`), and + suppresses `CS1591`. Rule severity belongs in the root `.editorconfig`, not + in the project files. Rules currently reported as warnings are a known + backlog being cleared and promoted to `error` one family at a time. Do not + commit a generated suppression baseline; fix the diagnostic or add a narrow + suppression with a written rationale. +- `.gitattributes` normalizes tracked text to LF and `.editorconfig` sets + `end_of_line = lf`. Avoid whole-file rewrites through `Set-Content` or + `Out-File`, which reintroduce CRLF. - Ordinary builds and tests must leave `IncludePackagingContent` unset. Packaging builds set it to `true`, supply a runtime identifier and platform, and use `obj\packaging` through `Directory.Build.props` to isolate MSIX diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..8e95c4d --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,76 @@ + + +
+Additional instructions + +**MUST:** Keep **Allow edits from maintainers** enabled for this PR so +maintainers can help update the branch when needed. + +See [CONTRIBUTING.md](../CONTRIBUTING.md) for the validation commands this +repository expects and for the packaging, signing, and NativeAOT rules that +apply to launcher changes. + +
+ +## What Problem This Solves + + + +## Why This Change Was Made + + + +## User Impact + + + +## Evidence + + diff --git a/.github/workflows/gateway-msix.yml b/.github/workflows/gateway-msix.yml index 15d1530..58c2e23 100644 --- a/.github/workflows/gateway-msix.yml +++ b/.github/workflows/gateway-msix.yml @@ -51,6 +51,13 @@ jobs: - name: Restore run: dotnet restore .\OpenClaw.Gateway.MSIX.slnx + - name: Static analysis + shell: pwsh + run: > + .\scripts\Test-DotNetQuality.ps1 + -Configuration Release + -SkipRestore + - name: Test run: > dotnet test .\OpenClaw.Gateway.MSIX.slnx diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..68008f9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,152 @@ +# Contributing + +This repository builds the `OpenClaw.Gateway` MSIX package and the +`openclaw.exe` NativeAOT launcher it ships. It is Windows-focused and pins the +.NET SDK through `global.json`. Run every command below from the repository +root in PowerShell 7 (`pwsh`). + +## Prerequisites + +- PowerShell 7 +- The .NET SDK feature band pinned in `global.json` +- Visual Studio Build Tools with the **Desktop development with C++** workload + and the Windows SDK, for NativeAOT publish and MSIX composition + +## Build, analyze, and test + +```powershell +dotnet restore .\OpenClaw.Gateway.MSIX.slnx + +# Canonical quality gate: restore plus a Release rebuild with static analysis. +.\scripts\Test-DotNetQuality.ps1 + +dotnet test .\OpenClaw.Gateway.MSIX.slnx --configuration Release --no-restore +``` + +`Test-DotNetQuality.ps1` is the single entry point used by local development, +continuous integration, and the optional pre-push hook, so all three report the +same result. It always rebuilds: an up-to-date project is skipped and reports no +analyzer diagnostics at all. + +Run one test by fully qualified name: + +```powershell +dotnet test .\tests\OpenClaw.Launcher.Tests\OpenClaw.Launcher.Tests.csproj ` + --configuration Release ` + --filter "FullyQualifiedName=OpenClaw.Launcher.Tests.HostOptionsTests.ParseForwardsAllArgumentsUnchanged" +``` + +Run the PowerShell policy suites when you change the workflow, signing inputs, +or package version logic: + +```powershell +.\scripts\Test-SigningInputs.Tests.ps1 +.\scripts\Test-WorkflowPackageVersion.Tests.ps1 +``` + +Run the NativeAOT publish when you change host JSON, reflection, interop, or +anything else that is trimming-sensitive. A JIT `dotnet build` does not +exercise that path: + +```powershell +$vsInstaller = Join-Path ` + ([Environment]::GetFolderPath([Environment+SpecialFolder]::ProgramFilesX86)) ` + 'Microsoft Visual Studio\Installer' +$env:Path = "$vsInstaller;$env:Path" +dotnet publish .\src\OpenClaw.Launcher\OpenClaw.Launcher.csproj ` + --configuration Release --runtime win-x64 --self-contained +``` + +## Formatting and static analysis + +Formatting and analyzer severity are defined by the root `.editorconfig`. The +analyzer properties themselves live in `Directory.Build.props`, and the +repository uses only the analyzers that ship with the pinned SDK. + +Continuous integration never rewrites source. To apply formatting locally, +review the resulting diff before committing: + +```powershell +dotnet format whitespace .\OpenClaw.Gateway.MSIX.slnx +dotnet format style .\OpenClaw.Gateway.MSIX.slnx +``` + +To check without writing files: + +```powershell +dotnet format whitespace .\OpenClaw.Gateway.MSIX.slnx --verify-no-changes +dotnet format style .\OpenClaw.Gateway.MSIX.slnx --verify-no-changes +``` + +`--verify-no-changes` exits with code 2 when it finds violations. + +The analysis policy is being rolled out in stages. Rules currently reported as +warnings are a known backlog rather than an invitation to add new violations; +each is being cleared and promoted to `error` one family at a time. Do not +commit a generated suppression baseline. Fix the diagnostic, or add a narrow +suppression with a written rationale. + +`.gitattributes` normalizes tracked text to LF and `.editorconfig` sets +`end_of_line = lf`. Avoid whole-file rewrites through `Set-Content` or +`Out-File`, which can reintroduce CRLF. Verify with `git ls-files --eol`. + +## Repository conventions + +- Ordinary builds and tests must leave `IncludePackagingContent` unset. + Packaging builds set it to `true` and supply a runtime identifier. +- Treat launcher arguments as OpenClaw-owned. Do not add host-only switches, + consume `--`, rewrite arguments, or block upstream commands. +- Keep x64 and ARM64 behavior synchronized across the workflow matrix, scripts, + project runtime identifiers, manifest content, and signing validation. +- Metadata files are part of the release trust chain. Coordinate changes across + payload creation, MSIX creation, signing validation, workflow artifacts, and + tests. +- Use source-generated `System.Text.Json` metadata through `OpenClawJsonContext`. + The launcher is NativeAOT and must not introduce reflection-based + serialization. +- PowerShell scripts set `$ErrorActionPreference = 'Stop'` and must also check + `$LASTEXITCODE` after invoking native tools. +- Package versions have four numeric components that each fit in `UInt16`. + Package dependency versions belong in `Directory.Packages.props`. + +## Tests + +- A test earns its place by the realistic defect it would catch. Prefer + functional and integration tests that drive the real path. +- Assert observable behavior: return values, emitted events, persisted state, + exit codes, rendered output. Do not read a source file and assert on string + markers of the implementation. +- Tests must never modify real user state. Use the isolated temporary + directory fixtures rather than touching a real OpenClaw profile, packaged + LocalState, or an installed MSIX. +- Tests must be deterministic: no sleep-based synchronization, hardcoded ports, + or inter-test ordering dependencies. + +## Pull requests + +Use the pull request template. Title the PR +`type: user-facing description`, where `type` is one of `feat`, `fix`, +`improve`, `refactor`, `docs`, or `chore`. Describe the outcome rather than the +mechanism. + +Every PR body should explain the problem it solves, why the change was made, +the user or contributor impact, and the evidence that it works. Name the exact +head SHA your evidence came from and state which validation lanes you did not +run. + +Keep the description current when review feedback changes the implementation; +the body is the durable explanation, not just the comment thread. Keep **Allow +edits from maintainers** enabled so a maintainer can update the branch. Do not +edit `CHANGELOG.md`. + +### Stacked pull requests + +Larger work may land as a linear stack of dependent PRs, each with a single +review boundary. When a change belongs to a stack: + +- State `Layer N of M`, the immediate parent, and the ordered stack in the PR + body. +- Validate each layer against its immediate parent, not only against the top of + the stack. +- Merge bottom-up, and retarget the child PR to the parent's base before the + parent merges. diff --git a/Directory.Build.props b/Directory.Build.props index ec3ccb0..b6b4a91 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,4 +4,35 @@ $(MSBuildProjectDirectory)\obj\packaging\ + + + true + latest + All + true + + + true + $(NoWarn);CS1591 + + diff --git a/scripts/Test-DotNetQuality.ps1 b/scripts/Test-DotNetQuality.ps1 new file mode 100644 index 0000000..9115670 --- /dev/null +++ b/scripts/Test-DotNetQuality.ps1 @@ -0,0 +1,80 @@ +<# +.SYNOPSIS + Runs the repository's canonical .NET quality checks. + +.DESCRIPTION + This is the single entry point used by local development, continuous + integration, and the optional pre-push hook, so every surface reports the + same result. + + The script performs a full restore and a Release rebuild with the analyzer + policy from Directory.Build.props and the root .editorconfig. A rebuild is + required because an up-to-date project is skipped and reports no analyzer + diagnostics at all. + + Analyzer severity is configured in .editorconfig rather than here. While + rules are still being cleared they report as warnings and this script + succeeds; once a rule family is promoted to error the same command fails. + +.PARAMETER Configuration + The MSBuild configuration to rebuild. Defaults to Release, which matches + continuous integration. + +.PARAMETER SkipRestore + Reuse the existing restore output instead of restoring first. Use this only + when a restore has already run against the current dependency manifests. + +.EXAMPLE + .\scripts\Test-DotNetQuality.ps1 + +.EXAMPLE + .\scripts\Test-DotNetQuality.ps1 -SkipRestore +#> +[CmdletBinding()] +param( + [string]$Configuration = 'Release', + + [switch]$SkipRestore +) + +$ErrorActionPreference = 'Stop' +$repositoryRoot = Split-Path $PSScriptRoot -Parent +$solution = Join-Path $repositoryRoot 'OpenClaw.Gateway.MSIX.slnx' + +function Invoke-CheckedCommand { + param( + [Parameter(Mandatory)] + [scriptblock]$Command, + + [Parameter(Mandatory)] + [string]$FailureMessage + ) + + & $Command + if ($LASTEXITCODE -ne 0) { + throw "$FailureMessage Exit code: $LASTEXITCODE." + } +} + +if (-not (Test-Path -LiteralPath $solution)) { + throw "Could not find the solution at '$solution'." +} + +if (-not $SkipRestore) { + Write-Host 'Restoring packages...' + Invoke-CheckedCommand ` + -Command { dotnet restore $solution } ` + -FailureMessage 'Package restore failed.' +} + +Write-Host "Rebuilding with static analysis ($Configuration)..." +Invoke-CheckedCommand ` + -Command { + dotnet build $solution ` + --configuration $Configuration ` + --no-restore ` + -t:Rebuild + } ` + -FailureMessage 'Static analysis build failed.' + +Write-Host 'Static analysis completed successfully.' From 87bb5c8a813ffb190b44f1942926e0613b74dc2a Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Thu, 10 Sep 2026 15:38:40 -0700 Subject: [PATCH 02/35] chore: enforce C# formatting and style (#17) Apply the outstanding whitespace and style fixes, then promote the IDE style and naming rules from warning to error so CI rejects future drift. The code changes are behavior preserving: indentation in ProgramTests, two collection expressions, and the diagnostic log's lock object moving from `object` to `System.Threading.Lock`. The lock field is only ever used in `lock` statements, so the new type introduces no Monitor-based behavior change. Also refine the style policy so it only enforces rules that add value. `var` versus explicit type, throw expressions, conditional expressions over guard clauses, and IDE0058 discards are left to the author, because forcing them produced worse code in this codebase rather than better. Test-DotNetQuality.ps1 now also verifies whitespace and style. It remains check-only and never rewrites source, including in CI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 331d9f1d-2f78-491c-8347-19969cb5ce11 --- .editorconfig | 83 ++++++++++--------- CONTRIBUTING.md | 12 ++- Directory.Build.props | 8 +- scripts/Test-DotNetQuality.ps1 | 25 +++++- src/OpenClaw.Launcher/HostDiagnosticLog.cs | 2 +- src/OpenClaw.Launcher/HostOptions.cs | 2 +- .../PackageManifestTests.cs | 5 +- tests/OpenClaw.Launcher.Tests/ProgramTests.cs | 14 ++-- 8 files changed, 90 insertions(+), 61 deletions(-) diff --git a/.editorconfig b/.editorconfig index 1d7a918..dd1af45 100644 --- a/.editorconfig +++ b/.editorconfig @@ -31,31 +31,31 @@ max_line_length = 100 #### .NET code style #### # `this.` qualification is not used anywhere in this repository. -dotnet_style_qualification_for_field = false:warning -dotnet_style_qualification_for_property = false:warning -dotnet_style_qualification_for_method = false:warning -dotnet_style_qualification_for_event = false:warning +dotnet_style_qualification_for_field = false:error +dotnet_style_qualification_for_property = false:error +dotnet_style_qualification_for_method = false:error +dotnet_style_qualification_for_event = false:error -dotnet_style_predefined_type_for_locals_parameters_members = true:warning -dotnet_style_predefined_type_for_member_access = true:warning +dotnet_style_predefined_type_for_locals_parameters_members = true:error +dotnet_style_predefined_type_for_member_access = true:error dotnet_style_object_initializer = true:suggestion dotnet_style_collection_initializer = true:suggestion dotnet_style_prefer_auto_properties = true:suggestion -dotnet_style_explicit_tuple_names = true:warning +dotnet_style_explicit_tuple_names = true:error dotnet_style_prefer_inferred_tuple_names = true:suggestion dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion # IDE0045/IDE0046: collapsing guard clauses into conditional expressions hurts # readability in this codebase, which uses early-return validation throughout. dotnet_style_prefer_conditional_expression_over_assignment = false:silent dotnet_style_prefer_conditional_expression_over_return = false:silent -dotnet_style_prefer_compound_assignment = true:warning -dotnet_style_prefer_simplified_boolean_expressions = true:warning -dotnet_style_prefer_simplified_interpolation = true:warning -dotnet_style_null_propagation = true:warning -dotnet_style_coalesce_expression = true:warning -dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning -dotnet_style_readonly_field = true:warning +dotnet_style_prefer_compound_assignment = true:error +dotnet_style_prefer_simplified_boolean_expressions = true:error +dotnet_style_prefer_simplified_interpolation = true:error +dotnet_style_null_propagation = true:error +dotnet_style_coalesce_expression = true:error +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:error +dotnet_style_readonly_field = true:error dotnet_style_operator_placement_when_wrapping = beginning_of_line dotnet_style_namespace_match_folder = true:suggestion @@ -66,7 +66,7 @@ dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion # Accessibility modifiers are written explicitly, except where C# requires # them to be omitted (for example interface members). -dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning +dotnet_style_require_accessibility_modifiers = for_non_interface_members:error #### C# code style #### @@ -86,22 +86,22 @@ csharp_style_expression_bodied_accessors = true:suggestion csharp_style_expression_bodied_lambdas = true:suggestion csharp_style_expression_bodied_local_functions = when_on_single_line:suggestion -csharp_style_pattern_matching_over_is_with_cast_check = true:warning -csharp_style_pattern_matching_over_as_with_null_check = true:warning -csharp_style_prefer_not_pattern = true:warning +csharp_style_pattern_matching_over_is_with_cast_check = true:error +csharp_style_pattern_matching_over_as_with_null_check = true:error +csharp_style_prefer_not_pattern = true:error csharp_style_prefer_pattern_matching = true:suggestion csharp_style_prefer_switch_expression = true:suggestion csharp_style_prefer_extended_property_pattern = true:suggestion -csharp_style_inlined_variable_declaration = true:warning +csharp_style_inlined_variable_declaration = true:error csharp_style_deconstructed_variable_declaration = true:suggestion # Guard clauses that throw read better than ternary `throw` expressions, and # this codebase already uses them consistently. csharp_style_throw_expression = false:silent -csharp_style_conditional_delegate_call = true:warning +csharp_style_conditional_delegate_call = true:error csharp_style_prefer_index_operator = true:suggestion csharp_style_prefer_range_operator = true:suggestion -csharp_style_prefer_null_check_over_type_check = true:warning +csharp_style_prefer_null_check_over_type_check = true:error csharp_style_prefer_tuple_swap = true:suggestion csharp_style_prefer_utf8_string_literals = true:suggestion csharp_style_prefer_readonly_struct = true:suggestion @@ -109,19 +109,19 @@ csharp_style_prefer_readonly_struct_member = true:suggestion csharp_style_prefer_primary_constructors = false:suggestion csharp_prefer_simple_using_statement = true:suggestion -csharp_prefer_static_local_function = true:warning +csharp_prefer_static_local_function = true:error csharp_prefer_static_anonymous_function = true:suggestion -csharp_prefer_braces = true:warning -csharp_style_namespace_declarations = file_scoped:warning +csharp_prefer_braces = true:error +csharp_style_namespace_declarations = file_scoped:error csharp_style_prefer_method_group_conversion = true:suggestion csharp_style_prefer_top_level_statements = false:suggestion # IDE0058: requiring `_ =` on every ignored return value is noise. Ignoring the # result of calls such as `Directory.CreateDirectory` is ordinary C#. csharp_style_unused_value_expression_statement_preference = discard_variable:silent # IDE0059 catches dead stores, which are a real defect signal. -csharp_style_unused_value_assignment_preference = discard_variable:warning +csharp_style_unused_value_assignment_preference = discard_variable:error -csharp_using_directive_placement = outside_namespace:warning +csharp_using_directive_placement = outside_namespace:error #### C# formatting #### @@ -201,44 +201,47 @@ dotnet_naming_symbols.parameters_and_locals.applicable_kinds = parameter, local dotnet_naming_rule.interfaces_are_prefixed.symbols = interfaces dotnet_naming_rule.interfaces_are_prefixed.style = interface_prefix -dotnet_naming_rule.interfaces_are_prefixed.severity = warning +dotnet_naming_rule.interfaces_are_prefixed.severity = error dotnet_naming_rule.type_parameters_are_prefixed.symbols = type_parameters dotnet_naming_rule.type_parameters_are_prefixed.style = type_parameter_prefix -dotnet_naming_rule.type_parameters_are_prefixed.severity = warning +dotnet_naming_rule.type_parameters_are_prefixed.severity = error dotnet_naming_rule.constants_are_pascal_case.symbols = constants dotnet_naming_rule.constants_are_pascal_case.style = pascal_case -dotnet_naming_rule.constants_are_pascal_case.severity = warning +dotnet_naming_rule.constants_are_pascal_case.severity = error dotnet_naming_rule.static_readonly_fields_are_pascal_case.symbols = static_readonly_fields dotnet_naming_rule.static_readonly_fields_are_pascal_case.style = pascal_case -dotnet_naming_rule.static_readonly_fields_are_pascal_case.severity = warning +dotnet_naming_rule.static_readonly_fields_are_pascal_case.severity = error dotnet_naming_rule.instance_fields_are_underscore_camel_case.symbols = instance_fields dotnet_naming_rule.instance_fields_are_underscore_camel_case.style = underscore_camel_case -dotnet_naming_rule.instance_fields_are_underscore_camel_case.severity = warning +dotnet_naming_rule.instance_fields_are_underscore_camel_case.severity = error dotnet_naming_rule.parameters_and_locals_are_camel_case.symbols = parameters_and_locals dotnet_naming_rule.parameters_and_locals_are_camel_case.style = camel_case -dotnet_naming_rule.parameters_and_locals_are_camel_case.severity = warning +dotnet_naming_rule.parameters_and_locals_are_camel_case.severity = error dotnet_naming_rule.types_and_members_are_pascal_case.symbols = types_and_members dotnet_naming_rule.types_and_members_are_pascal_case.style = pascal_case -dotnet_naming_rule.types_and_members_are_pascal_case.severity = warning +dotnet_naming_rule.types_and_members_are_pascal_case.severity = error #### Analyzer severities #### # -# This layer reports every rule below as a warning so the diagnostic backlog is -# visible without blocking the build. Later PRs in this stack promote one -# cohesive rule family at a time to `error` in the same change that clears it. +# IDE style and naming rules above are errors: the tree is clean of them and +# CI must reject new drift. +# +# The CA rules below are still a known backlog and report as warnings. Later +# PRs in this stack promote one cohesive family at a time to `error` in the +# same change that clears it. # Unnecessary usings. Requires `GenerateDocumentationFile` to run at build time. -dotnet_diagnostic.IDE0005.severity = warning +dotnet_diagnostic.IDE0005.severity = error -# Style rules with findings in the current tree. -dotnet_diagnostic.IDE0305.severity = warning -dotnet_diagnostic.IDE0330.severity = warning +# Style rules cleared by the formatting layer. +dotnet_diagnostic.IDE0305.severity = error +dotnet_diagnostic.IDE0330.severity = error # Interop and security. dotnet_diagnostic.CA5392.severity = warning diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 68008f9..db0d054 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,8 +25,9 @@ dotnet test .\OpenClaw.Gateway.MSIX.slnx --configuration Release --no-restore `Test-DotNetQuality.ps1` is the single entry point used by local development, continuous integration, and the optional pre-push hook, so all three report the -same result. It always rebuilds: an up-to-date project is skipped and reports no -analyzer diagnostics at all. +same result. It rebuilds with static analysis and then verifies whitespace and +code style. It always rebuilds: an up-to-date project is skipped and reports no +analyzer diagnostics at all. It never rewrites source. Run one test by fully qualified name: @@ -90,6 +91,13 @@ suppression with a written rationale. `end_of_line = lf`. Avoid whole-file rewrites through `Set-Content` or `Out-File`, which can reintroduce CRLF. Verify with `git ls-files --eol`. +A checkout always produces LF, so this only bites on files you have just +created. Many Windows editors write CRLF by default, so a brand-new `.cs` file +can fail the whitespace check before its first commit with +`error ENDOFLINE: Fix end of line marker`. Run +`dotnet format whitespace .\OpenClaw.Gateway.MSIX.slnx` to normalize it, or +configure your editor to write LF for this repository. + ## Repository conventions - Ordinary builds and tests must leave `IncludePackagingContent` unset. diff --git a/Directory.Build.props b/Directory.Build.props index b6b4a91..6361cdc 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -13,10 +13,10 @@ a command-line build instead of only inside an editor. `EnforceCodeStyleInBuild` runs the IDE rules but does not set their - severity. Severity lives in the root `.editorconfig`, which currently - reports every rule as a warning. Later PRs in this stack promote one rule - family at a time to `error` in the same change that clears it, and the - final PR turns on `TreatWarningsAsErrors`. + severity. Severity lives in the root `.editorconfig`. IDE style and naming + rules are errors; the remaining CA rules are a known backlog reported as + warnings. Later PRs in this stack promote one rule family at a time to + `error`, and the final PR turns on `TreatWarningsAsErrors`. --> true diff --git a/scripts/Test-DotNetQuality.ps1 b/scripts/Test-DotNetQuality.ps1 index 9115670..e11b698 100644 --- a/scripts/Test-DotNetQuality.ps1 +++ b/scripts/Test-DotNetQuality.ps1 @@ -7,11 +7,15 @@ integration, and the optional pre-push hook, so every surface reports the same result. - The script performs a full restore and a Release rebuild with the analyzer - policy from Directory.Build.props and the root .editorconfig. A rebuild is - required because an up-to-date project is skipped and reports no analyzer + The script performs a full restore, a Release rebuild with the analyzer + policy from Directory.Build.props and the root .editorconfig, and + check-only whitespace and code-style verification. A rebuild is required + because an up-to-date project is skipped and reports no analyzer diagnostics at all. + Nothing here rewrites source. Run `dotnet format whitespace` and + `dotnet format style` without `--verify-no-changes` to apply fixes locally. + Analyzer severity is configured in .editorconfig rather than here. While rules are still being cleared they report as warnings and this script succeeds; once a rule family is promoted to error the same command fails. @@ -77,4 +81,19 @@ Invoke-CheckedCommand ` } ` -FailureMessage 'Static analysis build failed.' +# Verification only. This never rewrites source, including in CI. Run +# `dotnet format whitespace` and `dotnet format style` without +# `--verify-no-changes` to apply fixes locally, then review the diff. +Write-Host 'Verifying whitespace formatting...' +Invoke-CheckedCommand ` + -Command { dotnet format whitespace $solution --verify-no-changes } ` + -FailureMessage ('Whitespace formatting check failed. Run ' + + '"dotnet format whitespace .\OpenClaw.Gateway.MSIX.slnx" to fix.') + +Write-Host 'Verifying code style...' +Invoke-CheckedCommand ` + -Command { dotnet format style $solution --verify-no-changes } ` + -FailureMessage ('Code style check failed. Run ' + + '"dotnet format style .\OpenClaw.Gateway.MSIX.slnx" to fix.') + Write-Host 'Static analysis completed successfully.' diff --git a/src/OpenClaw.Launcher/HostDiagnosticLog.cs b/src/OpenClaw.Launcher/HostDiagnosticLog.cs index ce9de5d..54e1899 100644 --- a/src/OpenClaw.Launcher/HostDiagnosticLog.cs +++ b/src/OpenClaw.Launcher/HostDiagnosticLog.cs @@ -9,7 +9,7 @@ public sealed class HostDiagnosticLog : IDisposable { private const int ErrorInsufficientBuffer = 122; private const int AppModelErrorNoPackage = 15700; - private readonly object _sync = new(); + private readonly Lock _sync = new(); private readonly Mutex _writeMutex; private bool _disposed; diff --git a/src/OpenClaw.Launcher/HostOptions.cs b/src/OpenClaw.Launcher/HostOptions.cs index 146e152..3155f55 100644 --- a/src/OpenClaw.Launcher/HostOptions.cs +++ b/src/OpenClaw.Launcher/HostOptions.cs @@ -26,6 +26,6 @@ internal static HostOptions Parse( return new HostOptions( directApplicationDirectory, - arguments.ToArray()); + [.. arguments]); } } diff --git a/tests/OpenClaw.Launcher.Tests/PackageManifestTests.cs b/tests/OpenClaw.Launcher.Tests/PackageManifestTests.cs index bef5652..f4619e2 100644 --- a/tests/OpenClaw.Launcher.Tests/PackageManifestTests.cs +++ b/tests/OpenClaw.Launcher.Tests/PackageManifestTests.cs @@ -19,12 +19,11 @@ public void ManifestRegistersBothAliasesToTheSingleExecutable() "openclaw.exe", (string?)extension.Attribute("Executable")); - string[] aliases = extension.Descendants() + string[] aliases = [.. extension.Descendants() .Where(element => element.Name.LocalName == "ExecutionAlias") .Select(element => (string?)element.Attribute("Alias")) .OfType() - .OrderBy(value => value, StringComparer.Ordinal) - .ToArray(); + .OrderBy(value => value, StringComparer.Ordinal)]; Assert.Equal(["clawctl.exe", "openclaw.exe"], aliases); } } diff --git a/tests/OpenClaw.Launcher.Tests/ProgramTests.cs b/tests/OpenClaw.Launcher.Tests/ProgramTests.cs index a32bb2a..df8e09a 100644 --- a/tests/OpenClaw.Launcher.Tests/ProgramTests.cs +++ b/tests/OpenClaw.Launcher.Tests/ProgramTests.cs @@ -26,16 +26,16 @@ await File.WriteAllTextAsync( _ => { }, _ => { - nodeResolutionAttempted = true; - return Task.FromResult(nodeRuntime); + nodeResolutionAttempted = true; + return Task.FromResult(nodeRuntime); }, (nodePath, appDirectory, forwardedArguments, _, _) => { - launchAttempted = true; - Assert.Equal(nodeRuntime.ExecutablePath, nodePath); - Assert.Equal(applicationDirectory, appDirectory); - Assert.Equal(arguments, forwardedArguments); - return Task.FromResult(23); + launchAttempted = true; + Assert.Equal(nodeRuntime.ExecutablePath, nodePath); + Assert.Equal(applicationDirectory, appDirectory); + Assert.Equal(arguments, forwardedArguments); + return Task.FromResult(23); }); Assert.True(nodeResolutionAttempted); From b3d98ae592b490abb681408a6b0d68292dbf8ad8 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Thu, 10 Sep 2026 15:38:41 -0700 Subject: [PATCH 03/35] improve: harden native library loading (#18) * improve: harden native library loading All ten P/Invokes in the launcher target kernel32.dll. Without an explicit search path the loader also probes the application and current directories, so a planted kernel32.dll next to openclaw.exe could be loaded instead of the system one. Restrict DLL resolution to System32 for the whole assembly. Also stop marshalling the CreateProcessW command line through a StringBuilder. CreateProcessW may write to that buffer, so it now receives an explicitly null-terminated char array sized from the built command line. The buffer is never read back. Promote CA5392 and CA1838 to errors now that both are clear, and add a regression test that launches a real child process with an argument containing spaces and embedded quotes to prove the command line still arrives intact. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 331d9f1d-2f78-491c-8347-19969cb5ce11 * fix: keep child command text free of fixture paths The real-child argument test interpolated the fixture output path into a single-quoted PowerShell literal. A temporary root containing an apostrophe (for example C:\Users\O'Connor) terminated that literal early and turned the remaining path into PowerShell syntax, so the test failed for a reason unrelated to argument marshalling. Pass the output path to the child as environment data and keep the command text fixed. The spaced and embedded-quote payload still travels on the command line, so the marshalling assertion is unchanged, and the case is now covered for both a plain fixture subdirectory and one containing spaces and an apostrophe. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 331d9f1d-2f78-491c-8347-19969cb5ce11 --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 331d9f1d-2f78-491c-8347-19969cb5ce11 --- .editorconfig | 4 +- src/OpenClaw.Launcher/AssemblyInfo.cs | 9 +++ .../WindowsKillOnCloseJob.cs | 12 +++- .../WindowsKillOnCloseJobTests.cs | 56 +++++++++++++++++++ 4 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 src/OpenClaw.Launcher/AssemblyInfo.cs diff --git a/.editorconfig b/.editorconfig index dd1af45..6b5bab2 100644 --- a/.editorconfig +++ b/.editorconfig @@ -244,8 +244,8 @@ dotnet_diagnostic.IDE0305.severity = error dotnet_diagnostic.IDE0330.severity = error # Interop and security. -dotnet_diagnostic.CA5392.severity = warning -dotnet_diagnostic.CA1838.severity = warning +dotnet_diagnostic.CA5392.severity = error +dotnet_diagnostic.CA1838.severity = error # Public contracts and globalization. dotnet_diagnostic.CA1062.severity = warning diff --git a/src/OpenClaw.Launcher/AssemblyInfo.cs b/src/OpenClaw.Launcher/AssemblyInfo.cs new file mode 100644 index 0000000..ab1c9a4 --- /dev/null +++ b/src/OpenClaw.Launcher/AssemblyInfo.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +// Every P/Invoke in this assembly targets kernel32.dll, which always lives in +// System32. Restricting the search path to System32 assembly-wide prevents DLL +// planting: without it the loader would also probe the application directory +// and the current directory, where an attacker-supplied kernel32.dll could be +// loaded instead. Individual P/Invokes can override this with their own +// DefaultDllImportSearchPaths attribute if they ever need a different policy. +[assembly: DefaultDllImportSearchPaths(DllImportSearchPath.System32)] diff --git a/src/OpenClaw.Launcher/WindowsKillOnCloseJob.cs b/src/OpenClaw.Launcher/WindowsKillOnCloseJob.cs index 6d6d9a7..17cd7b0 100644 --- a/src/OpenClaw.Launcher/WindowsKillOnCloseJob.cs +++ b/src/OpenClaw.Launcher/WindowsKillOnCloseJob.cs @@ -91,6 +91,14 @@ public Process StartProcess(ProcessStartInfo startInfo) } string commandLine = BuildCommandLine(startInfo); + + // CreateProcessW may write to lpCommandLine, so it needs a writable, + // null-terminated buffer rather than a marshalled immutable string. + // The array is one element longer than the command line, and the extra + // trailing element is already '\0'. The buffer is never read back. + char[] commandLineBuffer = new char[commandLine.Length + 1]; + commandLine.CopyTo(0, commandLineBuffer, 0, commandLine.Length); + IntPtr environment = BuildEnvironmentBlock(startInfo); var startupInfo = new StartupInfo { @@ -105,7 +113,7 @@ public Process StartProcess(ProcessStartInfo startInfo) { if (!CreateProcessW( startInfo.FileName, - new StringBuilder(commandLine), + commandLineBuffer, IntPtr.Zero, IntPtr.Zero, inheritHandles: true, @@ -256,7 +264,7 @@ private static extern bool AssignProcessToJobObject( [return: MarshalAs(UnmanagedType.Bool)] private static extern bool CreateProcessW( string applicationName, - StringBuilder commandLine, + char[] commandLine, IntPtr processAttributes, IntPtr threadAttributes, [MarshalAs(UnmanagedType.Bool)] bool inheritHandles, diff --git a/tests/OpenClaw.Launcher.Tests/WindowsKillOnCloseJobTests.cs b/tests/OpenClaw.Launcher.Tests/WindowsKillOnCloseJobTests.cs index a9531fb..a0fc818 100644 --- a/tests/OpenClaw.Launcher.Tests/WindowsKillOnCloseJobTests.cs +++ b/tests/OpenClaw.Launcher.Tests/WindowsKillOnCloseJobTests.cs @@ -60,4 +60,60 @@ public async Task FastExitRetainsExitCodeAfterDelayedObservation() Assert.Equal(37, process.ExitCode); } + + // The child writes to a directory this test creates. "o'connor dir" + // stands in for a real profile or temporary path that contains spaces and + // an apostrophe, which PowerShell would otherwise read as syntax. + [Theory] + [InlineData("plain")] + [InlineData("o'connor dir")] + public async Task StartProcessDeliversArgumentsContainingSpacesAndQuotes( + string outputDirectoryName) + { + string directory = TestDirectory.Create(); + try + { + string outputDirectory = Path.Combine(directory, outputDirectoryName); + Directory.CreateDirectory(outputDirectory); + string outputPath = Path.Combine(outputDirectory, "argument.txt"); + const string expectedContent = "a b \"quoted\" c"; + + using WindowsKillOnCloseJob job = WindowsKillOnCloseJob.Create(); + using Process process = job.StartProcess(new ProcessStartInfo + { + FileName = Path.Combine( + Environment.SystemDirectory, + "WindowsPowerShell", + "v1.0", + "powershell.exe"), + UseShellExecute = false, + + // The command text is fixed. The output path reaches the child + // as environment data instead, because interpolating it here + // would let an apostrophe in the path terminate the PowerShell + // literal and fail the run for a reason unrelated to argument + // marshalling. The command argument still carries the spaces + // and embedded quotes this test exists to prove survive. + ArgumentList = + { + "-NoLogo", + "-NoProfile", + "-Command", + "Set-Content -LiteralPath $env:OPENCLAW_TEST_OUTPUT_PATH " + + "-Value 'a b \"quoted\" c' -NoNewline" + }, + Environment = { ["OPENCLAW_TEST_OUTPUT_PATH"] = outputPath } + }); + + using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + await process.WaitForExitAsync(timeout.Token); + + Assert.Equal(0, process.ExitCode); + Assert.Equal(expectedContent, File.ReadAllText(outputPath)); + } + finally + { + Directory.Delete(directory, recursive: true); + } + } } From c3e18107d4efc634cf607903a0fa563fd0b0bba6 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Thu, 10 Sep 2026 15:38:41 -0700 Subject: [PATCH 04/35] improve: make validation and comparisons deterministic (#19) Narrow the launcher's implementation types to internal and make every string comparison in the test suite explicit and culture-independent, then promote the contract and globalization analyzer families to errors. Internalizing the seven implementation-only types removes the ambiguity about what this executable's supported surface is. Because these types are no longer externally visible, the six CA1062 public-argument-validation findings disappear without adding guard clauses that would have become dead weight. The test project keeps full access through the existing InternalsVisibleTo entry. The CA1307 findings were all Assert.Contains calls matching literal English fragments of diagnostic and exception messages. StringComparison.Ordinal is the semantically correct choice: these assertions want exact, invariant substring matching, not culture-sensitive collation. CA1062, CA1307, CA1308, and CA1515 are now errors. CA1308 and CA1062 have zero findings and act as forward guards. CA1515 is disabled for the test project because xUnit 2.x only discovers public test classes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 331d9f1d-2f78-491c-8347-19969cb5ce11 --- .editorconfig | 8 ++--- src/OpenClaw.Launcher/ClawCtlCommand.cs | 6 ++-- src/OpenClaw.Launcher/ClawCtlConsole.cs | 2 +- src/OpenClaw.Launcher/GatewayLauncher.cs | 2 +- src/OpenClaw.Launcher/HostDiagnosticLog.cs | 2 +- src/OpenClaw.Launcher/HostOptions.cs | 2 +- .../ClawCtlCommandTests.cs | 32 +++++++++++++------ .../HostDiagnosticLogTests.cs | 2 +- .../NodeRuntimeResolverTests.cs | 18 +++++------ 9 files changed, 44 insertions(+), 30 deletions(-) diff --git a/.editorconfig b/.editorconfig index 6b5bab2..6b87ff5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -248,9 +248,9 @@ dotnet_diagnostic.CA5392.severity = error dotnet_diagnostic.CA1838.severity = error # Public contracts and globalization. -dotnet_diagnostic.CA1062.severity = warning -dotnet_diagnostic.CA1307.severity = warning -dotnet_diagnostic.CA1308.severity = warning +dotnet_diagnostic.CA1062.severity = error +dotnet_diagnostic.CA1307.severity = error +dotnet_diagnostic.CA1308.severity = error # Asynchronous code. dotnet_diagnostic.CA2007.severity = warning @@ -259,7 +259,7 @@ dotnet_diagnostic.CA1849.severity = warning # Lifetime and design. dotnet_diagnostic.CA2000.severity = warning dotnet_diagnostic.CA1031.severity = warning -dotnet_diagnostic.CA1515.severity = warning +dotnet_diagnostic.CA1515.severity = error dotnet_diagnostic.CA1859.severity = warning [tests/**/*.cs] diff --git a/src/OpenClaw.Launcher/ClawCtlCommand.cs b/src/OpenClaw.Launcher/ClawCtlCommand.cs index 2c1b141..60f00c4 100644 --- a/src/OpenClaw.Launcher/ClawCtlCommand.cs +++ b/src/OpenClaw.Launcher/ClawCtlCommand.cs @@ -1,17 +1,17 @@ namespace OpenClaw.Launcher; -public enum ClawCtlCommand +internal enum ClawCtlCommand { Help, Version, Setup } -public sealed record ClawCtlCommandParseResult( +internal sealed record ClawCtlCommandParseResult( ClawCtlCommand Command, string? Error = null); -public static class ClawCtlCommandParser +internal static class ClawCtlCommandParser { public static ClawCtlCommandParseResult Parse(IReadOnlyList args) { diff --git a/src/OpenClaw.Launcher/ClawCtlConsole.cs b/src/OpenClaw.Launcher/ClawCtlConsole.cs index 49d4416..a741cf1 100644 --- a/src/OpenClaw.Launcher/ClawCtlConsole.cs +++ b/src/OpenClaw.Launcher/ClawCtlConsole.cs @@ -1,6 +1,6 @@ namespace OpenClaw.Launcher; -public static class ClawCtlConsole +internal static class ClawCtlConsole { public static void WriteHelp(TextWriter output) { diff --git a/src/OpenClaw.Launcher/GatewayLauncher.cs b/src/OpenClaw.Launcher/GatewayLauncher.cs index 9f4f0fd..908212d 100644 --- a/src/OpenClaw.Launcher/GatewayLauncher.cs +++ b/src/OpenClaw.Launcher/GatewayLauncher.cs @@ -2,7 +2,7 @@ namespace OpenClaw.Launcher; -public static class GatewayLauncher +internal static class GatewayLauncher { public static async Task RunAsync( string nodePath, diff --git a/src/OpenClaw.Launcher/HostDiagnosticLog.cs b/src/OpenClaw.Launcher/HostDiagnosticLog.cs index 54e1899..f6b794b 100644 --- a/src/OpenClaw.Launcher/HostDiagnosticLog.cs +++ b/src/OpenClaw.Launcher/HostDiagnosticLog.cs @@ -5,7 +5,7 @@ namespace OpenClaw.Launcher; -public sealed class HostDiagnosticLog : IDisposable +internal sealed class HostDiagnosticLog : IDisposable { private const int ErrorInsufficientBuffer = 122; private const int AppModelErrorNoPackage = 15700; diff --git a/src/OpenClaw.Launcher/HostOptions.cs b/src/OpenClaw.Launcher/HostOptions.cs index 3155f55..0432db7 100644 --- a/src/OpenClaw.Launcher/HostOptions.cs +++ b/src/OpenClaw.Launcher/HostOptions.cs @@ -1,6 +1,6 @@ namespace OpenClaw.Launcher; -public sealed record HostOptions( +internal sealed record HostOptions( string? PackagedApplicationDirectory, IReadOnlyList OpenClawArguments) { diff --git a/tests/OpenClaw.Launcher.Tests/ClawCtlCommandTests.cs b/tests/OpenClaw.Launcher.Tests/ClawCtlCommandTests.cs index 667509c..a2abc33 100644 --- a/tests/OpenClaw.Launcher.Tests/ClawCtlCommandTests.cs +++ b/tests/OpenClaw.Launcher.Tests/ClawCtlCommandTests.cs @@ -2,18 +2,32 @@ namespace OpenClaw.Launcher.Tests; public sealed class ClawCtlCommandTests { - [Theory] - [InlineData(ClawCtlCommand.Help)] - [InlineData(ClawCtlCommand.Setup, "setup")] - [InlineData(ClawCtlCommand.Version, "--version")] - public void ParseAcceptsOnlyThePublicManagementSurface( - ClawCtlCommand expected, - params string[] args) + [Fact] + public void ParseReturnsHelpWhenNoArgumentsAreProvided() { - ClawCtlCommandParseResult result = ClawCtlCommandParser.Parse(args); + ClawCtlCommandParseResult result = ClawCtlCommandParser.Parse([]); + + Assert.Null(result.Error); + Assert.Equal(ClawCtlCommand.Help, result.Command); + } + + [Fact] + public void ParseReturnsSetupForTheSetupCommand() + { + ClawCtlCommandParseResult result = ClawCtlCommandParser.Parse(["setup"]); + + Assert.Null(result.Error); + Assert.Equal(ClawCtlCommand.Setup, result.Command); + } + + [Fact] + public void ParseReturnsVersionForTheVersionOption() + { + ClawCtlCommandParseResult result = + ClawCtlCommandParser.Parse(["--version"]); Assert.Null(result.Error); - Assert.Equal(expected, result.Command); + Assert.Equal(ClawCtlCommand.Version, result.Command); } [Theory] diff --git a/tests/OpenClaw.Launcher.Tests/HostDiagnosticLogTests.cs b/tests/OpenClaw.Launcher.Tests/HostDiagnosticLogTests.cs index 982e546..5f28c81 100644 --- a/tests/OpenClaw.Launcher.Tests/HostDiagnosticLogTests.cs +++ b/tests/OpenClaw.Launcher.Tests/HostDiagnosticLogTests.cs @@ -44,7 +44,7 @@ public void ConcurrentInvocationsSerializeCompleteLogRecords() string[] lines = File.ReadAllLines(path); Assert.Equal(200, lines.Length); - Assert.All(lines, line => Assert.Contains(" invocation ", line)); + Assert.All(lines, line => Assert.Contains(" invocation ", line, StringComparison.Ordinal)); } public void Dispose() diff --git a/tests/OpenClaw.Launcher.Tests/NodeRuntimeResolverTests.cs b/tests/OpenClaw.Launcher.Tests/NodeRuntimeResolverTests.cs index e3d5555..3c77ea1 100644 --- a/tests/OpenClaw.Launcher.Tests/NodeRuntimeResolverTests.cs +++ b/tests/OpenClaw.Launcher.Tests/NodeRuntimeResolverTests.cs @@ -29,8 +29,8 @@ public async Task ResolveReportsInstallCommandWhenRuntimeIsMissing() Architecture.X64, Architecture.X64)); - Assert.Contains("not found on PATH", exception.Message); - Assert.Contains(NodeRuntimeResolver.InstallCommand, exception.Message); + Assert.Contains("not found on PATH", exception.Message, StringComparison.Ordinal); + Assert.Contains(NodeRuntimeResolver.InstallCommand, exception.Message, StringComparison.Ordinal); } [Fact] @@ -44,8 +44,8 @@ public async Task ResolveRejectsOutdatedRuntime() Architecture.X64, Architecture.X64)); - Assert.Contains("version 24.14.0 is unsupported", exception.Message); - Assert.Contains(NodeRuntimeResolver.SupportedVersions, exception.Message); + Assert.Contains("version 24.14.0 is unsupported", exception.Message, StringComparison.Ordinal); + Assert.Contains(NodeRuntimeResolver.SupportedVersions, exception.Message, StringComparison.Ordinal); } [Fact] @@ -59,7 +59,7 @@ public async Task ResolveRejectsMalformedVersion() Architecture.X64, Architecture.X64)); - Assert.Contains("invalid version", exception.Message); + Assert.Contains("invalid version", exception.Message, StringComparison.Ordinal); } [Fact] @@ -73,7 +73,7 @@ public async Task ResolveRejectsPrereleaseVersion() Architecture.X64, Architecture.X64)); - Assert.Contains("invalid version", exception.Message); + Assert.Contains("invalid version", exception.Message, StringComparison.Ordinal); } [Fact] @@ -87,7 +87,7 @@ public async Task ResolveRejectsIncompatibleArchitecture() Architecture.Arm64, Architecture.X64)); - Assert.Contains("architecture Arm64 does not match X64", exception.Message); + Assert.Contains("architecture Arm64 does not match X64", exception.Message, StringComparison.Ordinal); } [Fact] @@ -102,8 +102,8 @@ public async Task ResolveReportsVersionQueryFailure() Architecture.X64, Architecture.X64)); - Assert.Contains("query failed", exception.Message); - Assert.Contains(NodeRuntimeResolver.InstallCommand, exception.Message); + Assert.Contains("query failed", exception.Message, StringComparison.Ordinal); + Assert.Contains(NodeRuntimeResolver.InstallCommand, exception.Message, StringComparison.Ordinal); } [Theory] From 2904bd8dd641b44839ba0c2bd11e9b0744f353e2 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Thu, 10 Sep 2026 15:38:41 -0700 Subject: [PATCH 05/35] improve: remove blocking work from async paths (#20) Make every await in the launcher explicit about continuation scheduling and replace the remaining synchronous calls inside async methods, then promote the async analyzer family to errors. The launcher is a console executable with no SynchronizationContext, so ConfigureAwait(false) is a no-op at runtime today. It is applied anyway because the rule protects future code: if this assembly is ever consumed from a context-capturing host, or a UI-hosted setup surface is added, the absence of an explicit choice becomes a deadlock risk rather than a style question. Making the intent explicit at every await costs nothing now and removes an entire defect class later. CA1849 flagged two genuine synchronous calls on async paths: the clawctl --version write to the output TextWriter, and File.ReadAllText in the interop argument-delivery test. Both now use their asynchronous overloads, and the test threads its existing timeout token through the read so a hung file operation fails the test rather than hanging the run. CA2007 does not apply to the test project; the SDK excludes it automatically for projects that reference a test framework, so no ConfigureAwait noise is added to test code. Validated on this branch: static analysis gate clean, 53 tests pass, and win-x64 NativeAOT publish succeeds with no IL, trim, or AOT warnings. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 331d9f1d-2f78-491c-8347-19969cb5ce11 --- .editorconfig | 4 ++-- src/OpenClaw.Launcher/GatewayLauncher.cs | 6 ++++-- src/OpenClaw.Launcher/NodeRuntimeResolver.cs | 12 +++++++----- src/OpenClaw.Launcher/Program.cs | 18 ++++++++++-------- .../WindowsKillOnCloseJobTests.cs | 4 +++- 5 files changed, 26 insertions(+), 18 deletions(-) diff --git a/.editorconfig b/.editorconfig index 6b87ff5..4fbf3f3 100644 --- a/.editorconfig +++ b/.editorconfig @@ -253,8 +253,8 @@ dotnet_diagnostic.CA1307.severity = error dotnet_diagnostic.CA1308.severity = error # Asynchronous code. -dotnet_diagnostic.CA2007.severity = warning -dotnet_diagnostic.CA1849.severity = warning +dotnet_diagnostic.CA2007.severity = error +dotnet_diagnostic.CA1849.severity = error # Lifetime and design. dotnet_diagnostic.CA2000.severity = warning diff --git a/src/OpenClaw.Launcher/GatewayLauncher.cs b/src/OpenClaw.Launcher/GatewayLauncher.cs index 908212d..ec27931 100644 --- a/src/OpenClaw.Launcher/GatewayLauncher.cs +++ b/src/OpenClaw.Launcher/GatewayLauncher.cs @@ -22,7 +22,8 @@ public static async Task RunAsync( try { - await process.WaitForExitAsync(cancellationToken); + await process.WaitForExitAsync(cancellationToken) + .ConfigureAwait(false); log?.Invoke($"OpenClaw child process exited with code {process.ExitCode}."); return process.ExitCode; } @@ -31,7 +32,8 @@ public static async Task RunAsync( if (!process.HasExited) { process.Kill(entireProcessTree: true); - await process.WaitForExitAsync(CancellationToken.None); + await process.WaitForExitAsync(CancellationToken.None) + .ConfigureAwait(false); } throw; diff --git a/src/OpenClaw.Launcher/NodeRuntimeResolver.cs b/src/OpenClaw.Launcher/NodeRuntimeResolver.cs index dd93b0c..64bcd57 100644 --- a/src/OpenClaw.Launcher/NodeRuntimeResolver.cs +++ b/src/OpenClaw.Launcher/NodeRuntimeResolver.cs @@ -57,7 +57,8 @@ internal static async Task ResolveAsync( { try { - string output = await queryVersion(candidate, cancellationToken); + string output = await queryVersion(candidate, cancellationToken) + .ConfigureAwait(false); Version version = ParseVersion(output); if (!IsSupported(version)) { @@ -187,9 +188,9 @@ private static async Task QueryVersionAsync( timeout.Token); Task standardError = process.StandardError.ReadToEndAsync( timeout.Token); - await process.WaitForExitAsync(timeout.Token); - string output = await standardOutput; - string error = await standardError; + await process.WaitForExitAsync(timeout.Token).ConfigureAwait(false); + string output = await standardOutput.ConfigureAwait(false); + string error = await standardError.ConfigureAwait(false); if (process.ExitCode != 0) { throw new InvalidOperationException( @@ -204,7 +205,8 @@ private static async Task QueryVersionAsync( if (!process.HasExited) { process.Kill(entireProcessTree: true); - await process.WaitForExitAsync(CancellationToken.None); + await process.WaitForExitAsync(CancellationToken.None) + .ConfigureAwait(false); } throw new TimeoutException("Node.js version query timed out."); } diff --git a/src/OpenClaw.Launcher/Program.cs b/src/OpenClaw.Launcher/Program.cs index a3402c4..4c2003b 100644 --- a/src/OpenClaw.Launcher/Program.cs +++ b/src/OpenClaw.Launcher/Program.cs @@ -92,8 +92,9 @@ PlatformNotSupportedException or args, WriteDiagnostic, WriteConsoleError, - Console.Out) - : await RunAgentAsync(options, WriteDiagnostic); + Console.Out).ConfigureAwait(false) + : await RunAgentAsync(options, WriteDiagnostic) + .ConfigureAwait(false); } catch (Exception exception) { @@ -121,7 +122,7 @@ await RunAgentAsync( options, log, resolveNode ?? NodeRuntimeResolver.ResolveAsync, - GatewayLauncher.RunAsync); + GatewayLauncher.RunAsync).ConfigureAwait(false); // launchOpenClaw is a test seam: tests substitute a fake in place of // GatewayLauncher.RunAsync so they can assert launch behavior without @@ -132,7 +133,8 @@ internal static async Task RunAgentAsync( Func> resolveNode, LaunchOpenClawAsync launchOpenClaw) { - NodeRuntime nodeRuntime = await resolveNode(CancellationToken.None); + NodeRuntime nodeRuntime = await resolveNode(CancellationToken.None) + .ConfigureAwait(false); log( $"Using Node.js {nodeRuntime.Version} from " + $"{nodeRuntime.ExecutablePath}."); @@ -143,7 +145,7 @@ internal static async Task RunAgentAsync( applicationDirectory, options.OpenClawArguments, CancellationToken.None, - log); + log).ConfigureAwait(false); } // output is a required parameter (not a Console.Out default) so tests @@ -171,15 +173,15 @@ internal static async Task RunControlAsync( ClawCtlConsole.WriteHelp(output); return 0; case ClawCtlCommand.Version: - output.WriteLine( + await output.WriteLineAsync( Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? - "unknown"); + "unknown").ConfigureAwait(false); return 0; case ClawCtlCommand.Setup: { NodeRuntime nodeRuntime = await ( resolveNode ?? NodeRuntimeResolver.ResolveAsync)( - CancellationToken.None); + CancellationToken.None).ConfigureAwait(false); ClawCtlConsole.WriteNodeRuntimeSummary(output, nodeRuntime); string applicationDirectory = GetPackagedApplicationDirectory(options); diff --git a/tests/OpenClaw.Launcher.Tests/WindowsKillOnCloseJobTests.cs b/tests/OpenClaw.Launcher.Tests/WindowsKillOnCloseJobTests.cs index a0fc818..eff88d4 100644 --- a/tests/OpenClaw.Launcher.Tests/WindowsKillOnCloseJobTests.cs +++ b/tests/OpenClaw.Launcher.Tests/WindowsKillOnCloseJobTests.cs @@ -109,7 +109,9 @@ public async Task StartProcessDeliversArgumentsContainingSpacesAndQuotes( await process.WaitForExitAsync(timeout.Token); Assert.Equal(0, process.ExitCode); - Assert.Equal(expectedContent, File.ReadAllText(outputPath)); + string actualContent = + await File.ReadAllTextAsync(outputPath, timeout.Token); + Assert.Equal(expectedContent, actualContent); } finally { From 562416853d6835aad7e4cef7ba727210c924bcc8 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Thu, 10 Sep 2026 15:38:42 -0700 Subject: [PATCH 06/35] improve: tighten resource ownership and type boundaries (#21) Give every disposable in the test suite an explicit scope, document the one deliberate broad catch, sharpen a private return type, and promote the remaining lifetime and design analyzer families to errors. After this change the branch builds with zero warnings. The three CA2000 findings were StringWriter instances in tests that were never disposed. They are now scoped with using declarations. This is small but real: undisposed writers accumulate across a run and mask genuine ownership mistakes in the same files later. CA1031 flagged the last-chance catch in Main. That catch is correct and deliberate. Every other catch in this assembly already uses an exception filter to name the failures it expects; Main is the process boundary, and narrowing it would trade a logged diagnostic, a readable error message, and a deterministic exit code 1 for an unhandled-exception crash. The rule is suppressed at that single method with that rationale rather than repository wide, so any new broad catch elsewhere still fails the build. CA1859 flagged FindPathCandidates returning IReadOnlyList from a private helper that always produces a List. The interface added no contract value across a private call and cost an interface dispatch on the PATH scan; the concrete type is now returned directly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 331d9f1d-2f78-491c-8347-19969cb5ce11 --- .editorconfig | 6 +++--- src/OpenClaw.Launcher/NodeRuntimeResolver.cs | 2 +- src/OpenClaw.Launcher/Program.cs | 10 ++++++++++ tests/OpenClaw.Launcher.Tests/ClawCtlConsoleTests.cs | 4 ++-- tests/OpenClaw.Launcher.Tests/ProgramTests.cs | 2 +- 5 files changed, 17 insertions(+), 7 deletions(-) diff --git a/.editorconfig b/.editorconfig index 4fbf3f3..c1c7682 100644 --- a/.editorconfig +++ b/.editorconfig @@ -257,10 +257,10 @@ dotnet_diagnostic.CA2007.severity = error dotnet_diagnostic.CA1849.severity = error # Lifetime and design. -dotnet_diagnostic.CA2000.severity = warning -dotnet_diagnostic.CA1031.severity = warning +dotnet_diagnostic.CA2000.severity = error +dotnet_diagnostic.CA1031.severity = error dotnet_diagnostic.CA1515.severity = error -dotnet_diagnostic.CA1859.severity = warning +dotnet_diagnostic.CA1859.severity = error [tests/**/*.cs] # xUnit theory and member data intentionally expose public members, and test diff --git a/src/OpenClaw.Launcher/NodeRuntimeResolver.cs b/src/OpenClaw.Launcher/NodeRuntimeResolver.cs index 64bcd57..f9fbc58 100644 --- a/src/OpenClaw.Launcher/NodeRuntimeResolver.cs +++ b/src/OpenClaw.Launcher/NodeRuntimeResolver.cs @@ -122,7 +122,7 @@ internal static string CreateFailureMessage(string detail) => $" {InstallCommand}{Environment.NewLine}" + "Then open a new terminal and retry."; - private static IReadOnlyList FindPathCandidates() + private static List FindPathCandidates() { string? pathValue = Environment.GetEnvironmentVariable("PATH"); if (string.IsNullOrWhiteSpace(pathValue)) diff --git a/src/OpenClaw.Launcher/Program.cs b/src/OpenClaw.Launcher/Program.cs index 4c2003b..365f984 100644 --- a/src/OpenClaw.Launcher/Program.cs +++ b/src/OpenClaw.Launcher/Program.cs @@ -1,9 +1,19 @@ +using System.Diagnostics.CodeAnalysis; using System.Reflection; namespace OpenClaw.Launcher; internal static class Program { + [SuppressMessage( + "Design", + "CA1031:Do not catch general exception types", + Justification = + "Main is the process last-chance handler. Every narrower catch in " + + "this assembly uses an exception filter; this one deliberately does " + + "not, because narrowing it would replace the diagnostic log entry, " + + "the user-facing error message, and the deterministic exit code 1 " + + "with an unhandled-exception crash.")] public static async Task Main(string[] args) { HostEntrypoint entrypoint = HostEntrypointResolver.Resolve(); diff --git a/tests/OpenClaw.Launcher.Tests/ClawCtlConsoleTests.cs b/tests/OpenClaw.Launcher.Tests/ClawCtlConsoleTests.cs index d6851c0..f1f0db2 100644 --- a/tests/OpenClaw.Launcher.Tests/ClawCtlConsoleTests.cs +++ b/tests/OpenClaw.Launcher.Tests/ClawCtlConsoleTests.cs @@ -7,7 +7,7 @@ public sealed class ClawCtlConsoleTests : IDisposable [Fact] public void WriteHelpListsOnlyThePublicCommands() { - var output = new StringWriter(); + using var output = new StringWriter(); ClawCtlConsole.WriteHelp(output); @@ -31,7 +31,7 @@ public void WriteHelpListsOnlyThePublicCommands() [Fact] public void WriteReadinessSummaryDescribesPackagedApplication() { - var output = new StringWriter(); + using var output = new StringWriter(); string applicationDirectory = Path.Combine(_testDirectory, "app"); ClawCtlConsole.WriteReadinessSummary( diff --git a/tests/OpenClaw.Launcher.Tests/ProgramTests.cs b/tests/OpenClaw.Launcher.Tests/ProgramTests.cs index df8e09a..1ef0b16 100644 --- a/tests/OpenClaw.Launcher.Tests/ProgramTests.cs +++ b/tests/OpenClaw.Launcher.Tests/ProgramTests.cs @@ -56,7 +56,7 @@ public async Task SetupChecksNodeAndPackagedApplicationWithoutMutation() Path.Combine(_testDirectory, "node.exe"), new Version(24, 15, 0), System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture); - var output = new StringWriter(); + using var output = new StringWriter(); int exitCode = await Program.RunControlAsync( options, From 51969c7b2022e7a2cf61690f853c1b04f868f109 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Thu, 10 Sep 2026 15:38:42 -0700 Subject: [PATCH 07/35] chore: enforce the complete .NET quality gate (#22) * chore: enforce the complete .NET quality gate Turn on TreatWarningsAsErrors now that the branch builds warning-free, and add an opt-in pre-push hook so contributors can get the CI answer before pushing. The per-rule error entries the earlier layers added only cover diagnostics somebody has already looked at. The gap they leave is everything nobody has classified yet: a new compiler warning, a new analyzer from an SDK bump, a rule that changes default severity. Those arrive as warnings and get scrolled past. TreatWarningsAsErrors turns each one into a decision at the moment it appears. The explicit .editorconfig severities stay as readable policy history. NuGet audit advisories are excluded from the gate. NU1901 through NU1904 report published vulnerabilities, so one can appear against a dependency graph nobody touched and break main with no committed change and no fix available inside the failing build. They remain visible as warnings and are triaged as security work rather than treated as build breaks. The hook is deliberately small and deliberately opt in. hooks/pre-push is tracked, carries a marker identifying it as repository-owned, and runs only Test-DotNetQuality.ps1, so it cannot drift from what CI does. Install-GitHooks.ps1 copies it into the current clone and -Remove deletes it. Neither direction touches global Git configuration or core.hooksPath; installation stops if core.hooksPath is set rather than writing a hook Git would silently ignore, refuses to overwrite a pre-push hook it did not write, and removal only deletes a hook carrying the marker. Both are idempotent. The hook is a latency shortcut, not a policy boundary. It is local to one clone and `git push --no-verify` skips it; required CI checks remain authoritative. That is stated in the hook, the installer, and CONTRIBUTING so nobody mistakes it for enforcement. Test-GitHooks.Tests.ps1 covers install, repeated install, removal, repeated removal, unmanaged-hook conflict in both directions, and the core.hooksPath refusal. The pass and fail cases are end to end: they build a throwaway repository with a local bare remote and a stub quality script, then perform a real git push, proving Git invokes the hook and that its exit code decides whether the push lands. Nothing runs against this clone. CI runs the suite alongside the existing policy suites. Validated on this branch: quality gate clean, 53 tests pass, all three PowerShell suites pass, win-x64 NativeAOT publish succeeds with no IL, trim, or AOT warnings, and an injected #warning fails the build as CS1030. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 331d9f1d-2f78-491c-8347-19969cb5ce11 * fix: install the pre-push hook where Git looks for it The installer composed its destination from git rev-parse --absolute-git-dir plus 'hooks'. In a linked worktree that is the worktree's private directory, which Git never consults for hooks, so installation reported success and no push ever ran the quality gate. Ask Git for the hooks directory instead, with rev-parse --git-path hooks, and use that answer for both installation and removal. A clone has one hooks directory shared by all of its worktrees, so say so in the installer output and in CONTRIBUTING. The hook suite now runs every existing case against both an ordinary clone and a linked worktree. In the worktree layout the two work trees carry opposite stub exit codes, so the real pushes prove Git ran the pushing worktree's quality script. Two further cases cover the shared destination and clone-wide removal. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 331d9f1d-2f78-491c-8347-19969cb5ce11 --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 331d9f1d-2f78-491c-8347-19969cb5ce11 --- .github/copilot-instructions.md | 14 +- .github/workflows/gateway-msix.yml | 5 + CONTRIBUTING.md | 48 +++- Directory.Build.props | 23 +- hooks/pre-push | 23 ++ scripts/Install-GitHooks.ps1 | 143 +++++++++++ scripts/Test-DotNetQuality.ps1 | 6 +- scripts/Test-GitHooks.Tests.ps1 | 384 +++++++++++++++++++++++++++++ 8 files changed, 630 insertions(+), 16 deletions(-) create mode 100644 hooks/pre-push create mode 100644 scripts/Install-GitHooks.ps1 create mode 100644 scripts/Test-GitHooks.Tests.ps1 diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 731c9cd..304db33 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -85,10 +85,16 @@ and ARM64 separately. `Directory.Build.props` sets `AnalysisMode=All`, `EnforceCodeStyleInBuild`, and `GenerateDocumentationFile` (required for build-time `IDE0005`), and suppresses `CS1591`. Rule severity belongs in the root `.editorconfig`, not - in the project files. Rules currently reported as warnings are a known - backlog being cleared and promoted to `error` one family at a time. Do not - commit a generated suppression baseline; fix the diagnostic or add a narrow - suppression with a written rationale. + in the project files. `TreatWarningsAsErrors` is on and the build is + warning-free, so any new warning fails the build; only NuGet audit + advisories (`NU1901`-`NU1904`) are excluded, because a new advisory can break + an unchanged dependency graph. Do not commit a generated suppression + baseline; fix the diagnostic or add a narrow suppression with a written + rationale. +- The pre-push hook is opt in. `scripts\Install-GitHooks.ps1` copies the + tracked `hooks\pre-push` into the current clone and `-Remove` deletes it. + Never change `core.hooksPath` or global Git configuration, and never + overwrite a hook the repository did not write. - `.gitattributes` normalizes tracked text to LF and `.editorconfig` sets `end_of_line = lf`. Avoid whole-file rewrites through `Set-Content` or `Out-File`, which reintroduce CRLF. diff --git a/.github/workflows/gateway-msix.yml b/.github/workflows/gateway-msix.yml index 58c2e23..826434f 100644 --- a/.github/workflows/gateway-msix.yml +++ b/.github/workflows/gateway-msix.yml @@ -74,6 +74,11 @@ jobs: run: > .\scripts\Test-WorkflowPackageVersion.Tests.ps1 + - name: Test Git hooks + shell: pwsh + run: > + .\scripts\Test-GitHooks.Tests.ps1 + build-package: name: Build OpenClaw npm package runs-on: ubuntu-latest diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index db0d054..5872973 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,6 +43,7 @@ or package version logic: ```powershell .\scripts\Test-SigningInputs.Tests.ps1 .\scripts\Test-WorkflowPackageVersion.Tests.ps1 +.\scripts\Test-GitHooks.Tests.ps1 ``` Run the NativeAOT publish when you change host JSON, reflection, interop, or @@ -81,11 +82,18 @@ dotnet format style .\OpenClaw.Gateway.MSIX.slnx --verify-no-changes `--verify-no-changes` exits with code 2 when it finds violations. -The analysis policy is being rolled out in stages. Rules currently reported as -warnings are a known backlog rather than an invitation to add new violations; -each is being cleared and promoted to `error` one family at a time. Do not -commit a generated suppression baseline. Fix the diagnostic, or add a narrow -suppression with a written rationale. +The build is warning-free and `TreatWarningsAsErrors` is on, so any new warning +fails the build. That includes compiler diagnostics and analyzers this +repository has never seen, such as those introduced by an SDK upgrade. When one +appears, fix it or add a narrow suppression with a written rationale next to +the code it applies to. Do not commit a generated suppression baseline, and do +not relax a rule repository-wide to get past a single site. + +NuGet audit advisories (`NU1901`-`NU1904`) are deliberately excluded from the +error gate. A newly published advisory can appear against an unchanged +dependency graph, and breaking `main` with no committed change and no in-build +fix helps nobody. They stay visible as warnings and are triaged as security +work. `.gitattributes` normalizes tracked text to LF and `.editorconfig` sets `end_of_line = lf`. Avoid whole-file rewrites through `Set-Content` or @@ -98,6 +106,36 @@ can fail the whitespace check before its first commit with `dotnet format whitespace .\OpenClaw.Gateway.MSIX.slnx` to normalize it, or configure your editor to write LF for this repository. +## Optional pre-push hook + +You can have the quality gate run before every push instead of finding out from +CI. The hook is opt in and local to your clone: + +```powershell +.\scripts\Install-GitHooks.ps1 +``` + +That writes the tracked `hooks\pre-push` into the hooks directory Git consults +for your working tree. It runs `Test-DotNetQuality.ps1` and nothing else, so it +reports exactly what CI reports. To remove it: + +```powershell +.\scripts\Install-GitHooks.ps1 -Remove +``` + +A clone has one hooks directory, shared by every linked worktree +(`git worktree add`). Installing or removing from any worktree therefore +affects all of them, and each push runs the quality script from the worktree +you pushed. Other clones are unaffected. + +Both operations are idempotent. Installation refuses to overwrite a `pre-push` +hook it did not write, removal only deletes a hook carrying its own marker, and +neither touches your global Git configuration or `core.hooksPath`. + +Skip the hook for a single push with `git push --no-verify`. The hook is a +latency shortcut, not a policy boundary: it lives in one clone, it is +bypassable, and required CI checks remain authoritative. + ## Repository conventions - Ordinary builds and tests must leave `IncludePackagingContent` unset. diff --git a/Directory.Build.props b/Directory.Build.props index 6361cdc..079e291 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -13,16 +13,31 @@ a command-line build instead of only inside an editor. `EnforceCodeStyleInBuild` runs the IDE rules but does not set their - severity. Severity lives in the root `.editorconfig`. IDE style and naming - rules are errors; the remaining CA rules are a known backlog reported as - warnings. Later PRs in this stack promote one rule family at a time to - `error`, and the final PR turns on `TreatWarningsAsErrors`. + severity. Severity lives in the root `.editorconfig`, which records the + per-rule policy this repository arrived at one family at a time. + + `TreatWarningsAsErrors` closes the gate. The explicit per-rule `error` + entries in `.editorconfig` remain as readable policy history; this switch + is what catches warnings nobody has classified yet, including new compiler + diagnostics and new analyzers introduced by a future SDK bump. When an SDK + upgrade surfaces something new, that is a deliberate decision to make, not + a warning to scroll past. --> true latest All true + true + + + $(WarningsNotAsErrors);NU1901;NU1902;NU1903;NU1904 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5872973..ca67588 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -142,6 +142,9 @@ bypassable, and required CI checks remain authoritative. Packaging builds set it to `true` and supply a runtime identifier. - Treat launcher arguments as OpenClaw-owned. Do not add host-only switches, consume `--`, rewrite arguments, or block upstream commands. +- Preserve direct execution from the read-only MSIX package. `clawctl setup` + is a readiness check; do not add runtime extraction, copying, repair, or + launcher-managed package state under the user profile. - Keep x64 and ARM64 behavior synchronized across the workflow matrix, scripts, project runtime identifiers, manifest content, and signing validation. - Metadata files are part of the release trust chain. Coordinate changes across diff --git a/README.md b/README.md index f5d4df6..8be44a2 100644 --- a/README.md +++ b/README.md @@ -53,13 +53,15 @@ the read-only application directory the workspace. ### `clawctl` -`clawctl` owns the package readiness check: +`clawctl` exposes package readiness and launcher version information: | Command | Behavior | |---|---| | `clawctl setup` | Verify compatible Node.js is on `PATH` and confirm packaged `app\openclaw.mjs` exists. | +| `clawctl --version` | Print the packaged launcher version. | -Bare `clawctl` and `clawctl --help` print help without changing state. +Bare `clawctl`, `clawctl -h`, and `clawctl --help` print help without changing +state. Commands such as `doctor`, `gateway`, and `uninstall` belong to the OpenClaw CLI and must be invoked through `openclaw`. @@ -73,7 +75,7 @@ walk, or state mutation. The launcher places Node.js in a Windows job configured with `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`. The launcher remains alive while Node.js runs; if the launcher exits or is terminated, Windows terminates Node.js and -its child processes before releasing the payload lease. +its child processes when the job handle closes. Install the current Node.js LTS release, open a new terminal, optionally check readiness, then use `openclaw`: @@ -155,21 +157,21 @@ key is stored in the repository. |---|---| | OpenClaw application files | Read-only MSIX package `app` directory | | OpenClaw configuration and user state | `%USERPROFILE%\.openclaw` | -| Launcher and package-management diagnostics | `%LOCALAPPDATA%\Packages\\LocalState\OpenClawGatewayMSIX\Logs\openclaw.log` | +| Launcher diagnostics | `%LOCALAPPDATA%\Packages\\LocalState\OpenClawGatewayMSIX\Logs\openclaw.log` | OpenClaw application files are owned and serviced by Windows as part of the immutable MSIX installation. OpenClaw user state remains outside the package. Updating or removing the MSIX does not automatically delete that state or stop a running Gateway. Use OpenClaw's documented [`openclaw uninstall`](https://docs.openclaw.ai/install/uninstall) flow before -removing the MSIX. A `%USERPROFILE%\.openclaw-msix` directory left by an older -staged-payload package may be removed manually after OpenClaw is stopped. +removing the MSIX. The current package does not create or use +`%USERPROFILE%\.openclaw-msix`. ## Integrity and isolation boundary The payload build emits an expanded npm-installed application tree. `Build-MSIX.ps1` rejects bundled Node.js, copies the tree into package content, -and records every application's file path, length, and SHA-256 in +and records every application file's path, length, and SHA-256 in `payload-files.json`. Package construction verifies that exact inventory against the generated MSIX. Official signing authorization repeats the inventory validation, including rejecting missing, changed, duplicate, unsafe, diff --git a/src/OpenClaw.Launcher/ClawCtlConsole.cs b/src/OpenClaw.Launcher/ClawCtlConsole.cs index a741cf1..b4bd295 100644 --- a/src/OpenClaw.Launcher/ClawCtlConsole.cs +++ b/src/OpenClaw.Launcher/ClawCtlConsole.cs @@ -4,12 +4,17 @@ internal static class ClawCtlConsole { public static void WriteHelp(TextWriter output) { - output.WriteLine("clawctl - OpenClaw package preparation"); + output.WriteLine("clawctl - OpenClaw package readiness"); output.WriteLine(); WriteUsage(output); output.WriteLine(); output.WriteLine("Commands:"); - output.WriteLine(" setup Prepare the packaged OpenClaw environment."); + output.WriteLine( + " setup Verify Node.js and the packaged OpenClaw application."); + output.WriteLine(); + output.WriteLine("Options:"); + output.WriteLine(" -h, --help Show this help."); + output.WriteLine(" --version Print the packaged launcher version."); output.WriteLine(); WriteNodePrerequisite(output); output.WriteLine(); @@ -17,7 +22,7 @@ public static void WriteHelp(TextWriter output) } public static void WriteUsage(TextWriter output) => - output.WriteLine("Usage: clawctl setup"); + output.WriteLine("Usage: clawctl [setup | --help | --version]"); public static void WriteNodePrerequisite(TextWriter output) { diff --git a/tests/OpenClaw.Launcher.Tests/ClawCtlConsoleTests.cs b/tests/OpenClaw.Launcher.Tests/ClawCtlConsoleTests.cs index f1f0db2..2076d9c 100644 --- a/tests/OpenClaw.Launcher.Tests/ClawCtlConsoleTests.cs +++ b/tests/OpenClaw.Launcher.Tests/ClawCtlConsoleTests.cs @@ -13,9 +13,20 @@ public void WriteHelpListsOnlyThePublicCommands() string help = output.ToString(); Assert.Contains("setup", help, StringComparison.Ordinal); - Assert.DoesNotContain("prepare", help, StringComparison.Ordinal); - Assert.DoesNotContain("verify", help, StringComparison.Ordinal); - Assert.DoesNotContain("repair", help, StringComparison.Ordinal); + Assert.Contains("readiness", help, StringComparison.OrdinalIgnoreCase); + Assert.Contains("--version", help, StringComparison.Ordinal); + Assert.DoesNotContain( + "prepare", + help, + StringComparison.OrdinalIgnoreCase); + Assert.DoesNotContain( + $"{Environment.NewLine} verify", + help, + StringComparison.OrdinalIgnoreCase); + Assert.DoesNotContain( + $"{Environment.NewLine} repair", + help, + StringComparison.OrdinalIgnoreCase); Assert.Contains( NodeRuntimeResolver.SupportedVersions, help, From bf2ce9f7c5184747701b1b7ea432731dc3e9396d Mon Sep 17 00:00:00 2001 From: Linus Huang Date: Thu, 10 Sep 2026 16:36:14 -0700 Subject: [PATCH 09/35] docs: remove obsolete cleanup callout Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7cb16ef4-e347-4342-a67e-385a8721db1c --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 8be44a2..cfbcdbc 100644 --- a/README.md +++ b/README.md @@ -164,8 +164,7 @@ immutable MSIX installation. OpenClaw user state remains outside the package. Updating or removing the MSIX does not automatically delete that state or stop a running Gateway. Use OpenClaw's documented [`openclaw uninstall`](https://docs.openclaw.ai/install/uninstall) flow before -removing the MSIX. The current package does not create or use -`%USERPROFILE%\.openclaw-msix`. +removing the MSIX. ## Integrity and isolation boundary From 27cc6b84ac367786e8df3caa5d263726725bd657 Mon Sep 17 00:00:00 2001 From: Linus Huang Date: Fri, 11 Sep 2026 15:09:18 -0700 Subject: [PATCH 10/35] feat: bundle Node.js runtime in gateway MSIX Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a35bc4fd-e612-4be2-8166-7c100e8cf3ef --- .github/copilot-instructions.md | 24 +- .github/workflows/gateway-msix.yml | 12 + CONTRIBUTING.md | 7 +- README.md | 67 +++--- scripts/Build-LocalMSIX.ps1 | 24 ++ scripts/Build-MSIX.ps1 | 88 +++++++- scripts/Test-SigningInputs.Tests.ps1 | 41 +++- scripts/Test-SigningInputs.ps1 | 68 +++++- src/OpenClaw.Launcher/ClawCtlConsole.cs | 13 +- src/OpenClaw.Launcher/HostDataPaths.cs | 67 ++++++ src/OpenClaw.Launcher/HostDiagnosticLog.cs | 41 +--- src/OpenClaw.Launcher/HostOptions.cs | 10 + src/OpenClaw.Launcher/NodeRuntimeInstaller.cs | 208 ++++++++++++++++++ src/OpenClaw.Launcher/NodeRuntimeResolver.cs | 61 ++--- .../OpenClaw.Launcher.csproj | 5 + src/OpenClaw.Launcher/Program.cs | 35 ++- .../ClawCtlConsoleTests.cs | 9 +- .../HostOptionsTests.cs | 19 ++ .../NodeRuntimeInstallerTests.cs | 101 +++++++++ .../NodeRuntimeResolverTests.cs | 8 +- tests/OpenClaw.Launcher.Tests/ProgramTests.cs | 10 +- 21 files changed, 739 insertions(+), 179 deletions(-) create mode 100644 src/OpenClaw.Launcher/HostDataPaths.cs create mode 100644 src/OpenClaw.Launcher/NodeRuntimeInstaller.cs create mode 100644 tests/OpenClaw.Launcher.Tests/NodeRuntimeInstallerTests.cs diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 304db33..7f3863d 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -57,10 +57,12 @@ and ARM64 separately. app execution alias and declares the `OpenClaw.Gateway` MSIX identity. - The package contains an expanded, read-only OpenClaw application tree. `HostOptions` resolves `app\openclaw.mjs` directly from the package. -- `openclaw` resolves device-installed Node.js, confirms the packaged entry - point exists, and forwards every argument unchanged to `openclaw.mjs`. -- `clawctl setup` is a read-only readiness check for compatible Node.js and the - packaged entry point. Runtime launches do not hash or walk package files. +- `openclaw` resolves the Node.js executable extracted into package LocalState, + confirms the packaged entry point exists, and forwards every argument + unchanged to `openclaw.mjs`. +- `clawctl setup` idempotently extracts the architecture-specific bundled + Node.js archive into versioned package LocalState and verifies the packaged + entry point. Runtime launches do not hash or walk application files. - `GatewayLauncher` starts Node without a shell, uses `ArgumentList`, inherits the console streams, and sets `OPENCLAW_SUPERVISOR_MODE=external` plus `OPENCLAW_NO_AUTO_UPDATE=1`. The child process exit code is the launcher exit @@ -71,9 +73,10 @@ and ARM64 separately. - The GitHub workflow first builds and packs a pinned `openclaw/openclaw` revision on Linux. Windows matrix jobs use `Build-Payload.ps1` to produce x64/ARM64 expanded trees and build metadata, - then `Build-MSIX.ps1` to reject bundled Node.js, build the application - inventory, publish the NativeAOT host, validate package contents, and emit - MSIX metadata. + then downloads the matching official Node.js archive and uses + `Build-MSIX.ps1` to reject Node.js from the application payload, build the + application inventory, publish the NativeAOT host, validate package + contents, and emit MSIX metadata including the runtime hash. - Unsigned artifacts are the normal PR/push output. Test signing uses a temporary runner-local certificate. Official signing is gated to `main` and the immutable upstream commit in `release-policy.json`; signing inputs are @@ -105,9 +108,10 @@ and ARM64 separately. - Treat launcher arguments as OpenClaw-owned. Do not add host-only switches, consume `--`, rewrite arguments, or block upstream commands; tests explicitly protect transparent forwarding. -- Preserve direct execution from the immutable package and the caller's - working directory. Do not add runtime extraction, copying, hashing, or - inventory walks. +- Preserve direct execution of `app\openclaw.mjs` from the immutable package + and the caller's working directory. Node.js extraction belongs only to + `clawctl setup` and targets versioned package LocalState; do not copy the + OpenClaw application payload. - The build-time inventory is a release trust boundary. Keep safe unique paths, lengths, and SHA-256 values synchronized across composition and signing validation. diff --git a/.github/workflows/gateway-msix.yml b/.github/workflows/gateway-msix.yml index 826434f..9788128 100644 --- a/.github/workflows/gateway-msix.yml +++ b/.github/workflows/gateway-msix.yml @@ -227,6 +227,16 @@ jobs: name: openclaw-gateway-payload-${{ matrix.architecture }} path: ${{ runner.temp }}\openclaw-payload + - name: Download bundled Node.js runtime + shell: pwsh + run: | + $archiveName = 'node-v${{ env.NODE_VERSION }}-win-${{ matrix.architecture }}.zip' + $archivePath = Join-Path $env:RUNNER_TEMP $archiveName + $archiveUri = 'https://nodejs.org/dist/v${{ env.NODE_VERSION }}/' + $archiveName + Invoke-WebRequest -Uri $archiveUri -OutFile $archivePath -UseBasicParsing + "NODE_ARCHIVE_PATH=$archivePath" | + Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + - name: Restore NativeAOT and MSIX dependencies shell: pwsh run: | @@ -246,6 +256,8 @@ jobs: .\scripts\Build-MSIX.ps1 ` -PayloadDirectory '${{ runner.temp }}\openclaw-payload' ` + -NodeArchivePath $env:NODE_ARCHIVE_PATH ` + -NodeVersion '${{ env.NODE_VERSION }}' ` -Architecture '${{ matrix.architecture }}' ` -PackageVersion $packageVersion ` -SourceCommit '${{ github.sha }}' ` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ca67588..f201aa4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -142,9 +142,10 @@ bypassable, and required CI checks remain authoritative. Packaging builds set it to `true` and supply a runtime identifier. - Treat launcher arguments as OpenClaw-owned. Do not add host-only switches, consume `--`, rewrite arguments, or block upstream commands. -- Preserve direct execution from the read-only MSIX package. `clawctl setup` - is a readiness check; do not add runtime extraction, copying, repair, or - launcher-managed package state under the user profile. +- Preserve direct execution of `app\openclaw.mjs` from the read-only MSIX + package. `clawctl setup` owns idempotent extraction of the bundled Node.js + archive into versioned package LocalState; do not copy the OpenClaw + application payload or use device-installed Node.js. - Keep x64 and ARM64 behavior synchronized across the workflow matrix, scripts, project runtime identifiers, manifest content, and signing validation. - Metadata files are part of the release trust chain. Coordinate changes across diff --git a/README.md b/README.md index cfbcdbc..3dacfe1 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,8 @@ This repository builds a Windows MSIX package containing: - one .NET 10 NativeAOT launcher exposed through the `openclaw` and `clawctl` app execution aliases; - a pinned, verified build of - [`openclaw/openclaw`](https://github.com/openclaw/openclaw). - -Node.js is a device prerequisite and is never downloaded or included in the -MSIX. + [`openclaw/openclaw`](https://github.com/openclaw/openclaw); +- the official Node.js 24.16.0 archive matching the package architecture. The package is independent from the [OpenClaw Windows Node and Companion](https://github.com/openclaw/openclaw-windows-node) @@ -29,9 +27,10 @@ own package-management commands. Every argument, including an empty argument list, is forwarded unchanged to `node openclaw.mjs`, and the launcher returns the exact child exit code. -Before launching, the host discovers `node.exe` on `PATH` and verifies its -version and executable architecture. It never downloads, installs, or services -Node.js. +Before launching, the host resolves the bundled Node.js executable previously +prepared by `clawctl setup` and verifies its version and executable +architecture. Device-installed Node.js and `PATH` do not affect command +passthrough. The expanded OpenClaw application is installed read-only inside the MSIX. After resolving Node.js, the launcher confirms that packaged @@ -57,7 +56,7 @@ the read-only application directory the workspace. | Command | Behavior | |---|---| -| `clawctl setup` | Verify compatible Node.js is on `PATH` and confirm packaged `app\openclaw.mjs` exists. | +| `clawctl setup` | Extract the bundled Node.js runtime when needed and confirm packaged `app\openclaw.mjs` exists. | | `clawctl --version` | Print the packaged launcher version. | Bare `clawctl`, `clawctl -h`, and `clawctl --help` print help without changing @@ -65,23 +64,21 @@ state. Commands such as `doctor`, `gateway`, and `uninstall` belong to the OpenClaw CLI and must be invoked through `openclaw`. -`setup` requires a compatible device-installed Node.js runtime. Missing, -outdated, malformed, or architecture-incompatible runtimes produce an -actionable error rather than a later process-launch failure. - -`clawctl setup` is read-only. It performs no extraction, hashing, inventory -walk, or state mutation. +`setup` extracts the architecture-specific runtime archive from the immutable +MSIX into the package's writable LocalState: +`%LOCALAPPDATA%\Packages\\LocalState\OpenClaw\NodeJS\node-v24.16.0-win-`. +Extraction is idempotent, versioned, and serialized across concurrent setup +processes. The launcher validates the extracted `node.exe` before reporting +readiness. The launcher places Node.js in a Windows job configured with `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`. The launcher remains alive while Node.js runs; if the launcher exits or is terminated, Windows terminates Node.js and its child processes when the job handle closes. -Install the current Node.js LTS release, open a new terminal, optionally check -readiness, then use `openclaw`: +Prepare the bundled runtime once, then use `openclaw`: ```powershell -winget install --id OpenJS.NodeJS.LTS --exact --source winget clawctl setup openclaw ``` @@ -127,12 +124,14 @@ dotnet test .\OpenClaw.Gateway.MSIX.slnx ` ``` `scripts\Build-Payload.ps1` npm-installs an OpenClaw package into an expanded, -architecture-specific application tree. `scripts\Build-MSIX.ps1` copies that -tree into package content, rejects any Node.js executable or runtime archive, -creates a per-file inventory, and then creates an unsigned NativeAOT MSIX. +architecture-specific application tree. The workflow downloads the official +Node.js archive for the same architecture. `scripts\Build-MSIX.ps1` copies the +application tree and runtime archive into package content, rejects Node.js +inside the application payload, creates a per-file inventory, and then creates +an unsigned NativeAOT MSIX. `scripts\Build-LocalMSIX.ps1` can reuse a successful workflow payload or a -local payload directory. The Node.js used by the payload build jobs is build -infrastructure only and is not copied into the MSIX. +local payload directory; unless `-NodeArchivePath` is supplied, it downloads +the pinned runtime archive used by CI. Normal pull-request and push workflows publish unsigned packages for validation. Manual runs support three signing modes: @@ -156,6 +155,8 @@ key is stored in the repository. | Data | Default path | |---|---| | OpenClaw application files | Read-only MSIX package `app` directory | +| Bundled Node.js archive | Read-only MSIX package `runtime` directory | +| Extracted Node.js runtime | `%LOCALAPPDATA%\Packages\\LocalState\OpenClaw\NodeJS\node-v24.16.0-win-` | | OpenClaw configuration and user state | `%USERPROFILE%\.openclaw` | | Launcher diagnostics | `%LOCALAPPDATA%\Packages\\LocalState\OpenClawGatewayMSIX\Logs\openclaw.log` | @@ -169,18 +170,18 @@ removing the MSIX. ## Integrity and isolation boundary The payload build emits an expanded npm-installed application tree. -`Build-MSIX.ps1` rejects bundled Node.js, copies the tree into package content, +`Build-MSIX.ps1` rejects Node.js from that tree, copies it into package content, and records every application file's path, length, and SHA-256 in -`payload-files.json`. Package construction verifies that exact inventory -against the generated MSIX. Official signing authorization repeats the -inventory validation, including rejecting missing, changed, duplicate, unsafe, -or unlisted application entries, before requesting signing credentials. - -At runtime, Windows' MSIX package integrity and read-only enforcement is the -trust boundary. `openclaw` and `clawctl setup` only check that -`app\openclaw.mjs` exists; neither performs file hashing or an inventory walk. -This avoids redundant startup overhead while keeping package mutation under -Windows servicing control. +`payload-files.json`. It separately validates and hashes the pinned Node.js +archive. Package construction verifies both inputs against the generated MSIX. +Official signing authorization repeats the application inventory and Node.js +archive validation before requesting signing credentials. + +At runtime, Windows' MSIX package integrity and read-only enforcement remains +the trust boundary for the application and archive. `clawctl setup` extracts +the archive into versioned package LocalState; `openclaw` launches the packaged +`app\openclaw.mjs` directly with that extracted executable. Neither command +hashes or walks the expanded application inventory. The longer-term design is to run the Gateway payload in a dedicated isolated agent session rather than the interactive session where the human user is diff --git a/scripts/Build-LocalMSIX.ps1 b/scripts/Build-LocalMSIX.ps1 index 7dc0a76..c0e77db 100644 --- a/scripts/Build-LocalMSIX.ps1 +++ b/scripts/Build-LocalMSIX.ps1 @@ -7,6 +7,11 @@ param( [long]$PayloadRunId, + [ValidatePattern('^\d+\.\d+\.\d+$')] + [string]$NodeVersion = '24.16.0', + + [string]$NodeArchivePath, + [string]$PackageVersion, [string]$OutputDirectory @@ -55,6 +60,23 @@ if (Test-Path -LiteralPath $OutputDirectory) { } New-Item -Path $workDirectory -ItemType Directory -Force | Out-Null +if ($NodeArchivePath) { + $resolvedNodeArchivePath = ( + Resolve-Path -LiteralPath $NodeArchivePath + ).Path +} +else { + $nodeArchiveName = "node-v$NodeVersion-win-$Architecture.zip" + $resolvedNodeArchivePath = Join-Path $workDirectory $nodeArchiveName + $nodeArchiveUri = + "https://nodejs.org/dist/v$NodeVersion/$nodeArchiveName" + Write-Host "Downloading bundled Node.js runtime from $nodeArchiveUri." + Invoke-WebRequest ` + -Uri $nodeArchiveUri ` + -OutFile $resolvedNodeArchivePath ` + -UseBasicParsing +} + if ($PayloadDirectory) { $resolvedPayloadDirectory = (Resolve-Path -LiteralPath $PayloadDirectory).Path } @@ -135,6 +157,8 @@ try { Write-Host "Building unsigned MSIX version $PackageVersion." & .\scripts\Build-MSIX.ps1 ` -PayloadDirectory $resolvedPayloadDirectory ` + -NodeArchivePath $resolvedNodeArchivePath ` + -NodeVersion $NodeVersion ` -Architecture $Architecture ` -PackageVersion $PackageVersion ` -SourceCommit $sourceCommit ` diff --git a/scripts/Build-MSIX.ps1 b/scripts/Build-MSIX.ps1 index d690f86..955d9ff 100644 --- a/scripts/Build-MSIX.ps1 +++ b/scripts/Build-MSIX.ps1 @@ -3,6 +3,13 @@ param( [Parameter(Mandatory)] [string]$PayloadDirectory, + [Parameter(Mandatory)] + [string]$NodeArchivePath, + + [Parameter(Mandatory)] + [ValidatePattern('^\d+\.\d+\.\d+$')] + [string]$NodeVersion, + [Parameter(Mandatory)] [ValidateSet('x64', 'arm64')] [string]$Architecture, @@ -129,6 +136,36 @@ function Assert-ApplicationHasNoReparsePoints { } } +function Assert-NodeArchive { + param( + [Parameter(Mandatory)] + [string]$Path, + + [Parameter(Mandatory)] + [string]$ExpectedRoot + ) + + Add-Type -AssemblyName System.IO.Compression.FileSystem + $archive = [IO.Compression.ZipFile]::OpenRead($Path) + try { + $nodeEntries = @( + $archive.Entries | + Where-Object { + $_.FullName -ieq "$ExpectedRoot/node.exe" + } + ) + if ($nodeEntries.Count -ne 1) { + throw ( + "The Node.js archive must contain exactly one " + + "'$ExpectedRoot/node.exe' entry." + ) + } + } + finally { + $archive.Dispose() + } +} + function Add-VswhereToPath { if (Get-Command vswhere.exe -CommandType Application -ErrorAction SilentlyContinue) { return @@ -154,6 +191,20 @@ Add-VswhereToPath $PayloadDirectory = (Resolve-Path -LiteralPath $PayloadDirectory).Path $payloadApplication = Join-Path $PayloadDirectory 'app' $payloadMetadata = Join-Path $PayloadDirectory 'payload-metadata.json' +$NodeArchivePath = (Resolve-Path -LiteralPath $NodeArchivePath).Path +$expectedNodeArchiveName = "node-v$NodeVersion-win-$Architecture.zip" +if ([IO.Path]::GetFileName($NodeArchivePath) -cne $expectedNodeArchiveName) { + throw ( + "NodeArchivePath must name the expected runtime archive: " + + $expectedNodeArchiveName + ) +} +$expectedNodeArchiveRoot = [IO.Path]::GetFileNameWithoutExtension( + $expectedNodeArchiveName +) +Assert-NodeArchive ` + -Path $NodeArchivePath ` + -ExpectedRoot $expectedNodeArchiveRoot if (-not (Test-Path -LiteralPath $payloadApplication -PathType Container)) { throw "Required MSIX input was not found: $payloadApplication" } @@ -184,6 +235,10 @@ Assert-ApplicationDoesNotBundleNode -Path $payloadApplication $contentRoot = Join-Path $repositoryRoot 'content' $openClawContent = Join-Path $contentRoot 'openclaw' $applicationTarget = Join-Path $openClawContent 'app' +$runtimeTargetDirectory = Join-Path $openClawContent 'runtime' +$nodeArchiveTarget = Join-Path ` + $runtimeTargetDirectory ` + $expectedNodeArchiveName New-Item -Path $openClawContent -ItemType Directory -Force | Out-Null if ( @@ -197,6 +252,15 @@ if ( -Recurse } +Remove-DirectoryIfPresent -Path $runtimeTargetDirectory +New-Item -Path $runtimeTargetDirectory -ItemType Directory -Force | Out-Null +Copy-Item ` + -LiteralPath $NodeArchivePath ` + -Destination $nodeArchiveTarget +$nodeArchiveHash = ( + Get-FileHash -LiteralPath $nodeArchiveTarget -Algorithm SHA256 +).Hash.ToLowerInvariant() + $payloadSymbols = @( # MSBuild's own AppxPackagePayload step strips .pdb files when it later # packages content\openclaw\app; remove them here too so the inventory @@ -328,6 +392,12 @@ try { ).Hash.ToLowerInvariant() } ) + $expectedPackageFiles.Add( + "runtime/$expectedNodeArchiveName", + [pscustomobject]@{ + Hash = $nodeArchiveHash + } + ) $packageEntries = [System.Collections.Generic.HashSet[string]]::new( [System.StringComparer]::OrdinalIgnoreCase ) @@ -417,17 +487,20 @@ try { if (-not $packageEntries.Contains('openclaw.exe')) { throw 'The MSIX does not contain the NativeAOT host executable.' } - $bundledNodeEntries = @( + $unexpectedNodeEntries = @( $packageEntries | Where-Object { - [IO.Path]::GetFileName($_) -ieq 'node.exe' -or - [IO.Path]::GetFileName($_) -match '^node-v\d' + ( + [IO.Path]::GetFileName($_) -ieq 'node.exe' -or + [IO.Path]::GetFileName($_) -match '^node-v\d' + ) -and + $_ -ine "runtime/$expectedNodeArchiveName" } ) - if ($bundledNodeEntries.Count -ne 0) { + if ($unexpectedNodeEntries.Count -ne 0) { throw ( - 'The MSIX must not bundle Node.js: ' + - (($bundledNodeEntries | Sort-Object) -join ', ') + 'The MSIX contains unexpected Node.js content: ' + + (($unexpectedNodeEntries | Sort-Object) -join ', ') ) } foreach ($managedHostArtifact in @( @@ -463,6 +536,9 @@ try { payloadResolvedCommit = $payloadInfo.resolvedCommit.ToLowerInvariant() payloadLayout = 'immutable-package' payloadFileCount = $payloadFiles.Count + nodeRuntimeVersion = $NodeVersion + nodeRuntimeArchive = $expectedNodeArchiveName + nodeRuntimeSha256 = $nodeArchiveHash architecture = $Architecture archive = $msixName sha256 = $msixHash diff --git a/scripts/Test-SigningInputs.Tests.ps1 b/scripts/Test-SigningInputs.Tests.ps1 index e31b3e0..59b61ee 100644 --- a/scripts/Test-SigningInputs.Tests.ps1 +++ b/scripts/Test-SigningInputs.Tests.ps1 @@ -124,6 +124,38 @@ function New-TestArtifact { -LiteralPath (Join-Path $runtimeDirectory 'node.exe') ` -Value 'bundled-node' } + else { + $runtimeDirectory = Join-Path $staging 'runtime' + New-Item -Path $runtimeDirectory -ItemType Directory | Out-Null + } + $nodeRuntimeVersion = '24.16.0' + $nodeRuntimeArchive = + "node-v$nodeRuntimeVersion-win-$Architecture.zip" + $nodeRuntimePath = Join-Path $runtimeDirectory $nodeRuntimeArchive + $nodeRuntimeZip = [IO.Compression.ZipFile]::Open( + $nodeRuntimePath, + [IO.Compression.ZipArchiveMode]::Create) + try { + $nodeRuntimeRoot = [IO.Path]::GetFileNameWithoutExtension( + $nodeRuntimeArchive + ) + $nodeEntry = $nodeRuntimeZip.CreateEntry( + "$nodeRuntimeRoot/node.exe" + ) + $nodeWriter = [IO.StreamWriter]::new($nodeEntry.Open()) + try { + $nodeWriter.Write('bundled-node') + } + finally { + $nodeWriter.Dispose() + } + } + finally { + $nodeRuntimeZip.Dispose() + } + $nodeRuntimeHash = ( + Get-FileHash -LiteralPath $nodeRuntimePath -Algorithm SHA256 + ).Hash.ToLowerInvariant() $msixName = "OpenClawGateway-$Architecture.msix" $msixPath = Join-Path $directory $msixName @@ -143,6 +175,9 @@ function New-TestArtifact { payloadResolvedCommit = $PayloadCommit payloadLayout = 'immutable-package' payloadFileCount = $payloadFiles.Count + nodeRuntimeVersion = $nodeRuntimeVersion + nodeRuntimeArchive = $nodeRuntimeArchive + nodeRuntimeSha256 = $nodeRuntimeHash architecture = $Architecture archive = $msixName sha256 = $msixHash @@ -296,7 +331,7 @@ try { -IncludeBundledNode $true New-TestArtifact -Root $testRoot -Architecture arm64 Assert-Fails ` - -MessagePattern 'x64 MSIX bundles Node.js' ` + -MessagePattern 'x64 MSIX has unexpected Node.js content' ` -Action { Invoke-PolicyValidation -Root $testRoot } @@ -309,7 +344,7 @@ try { -IncludeApplicationBundledNode $true New-TestArtifact -Root $testRoot -Architecture arm64 Assert-Fails ` - -MessagePattern 'x64 MSIX bundles Node.js' ` + -MessagePattern 'x64 MSIX has unexpected Node.js content' ` -Action { Invoke-PolicyValidation -Root $testRoot } @@ -322,7 +357,7 @@ try { -IncludeApplicationNodeArchive $true New-TestArtifact -Root $testRoot -Architecture arm64 Assert-Fails ` - -MessagePattern 'x64 MSIX bundles Node.js' ` + -MessagePattern 'x64 MSIX has unexpected Node.js content' ` -Action { Invoke-PolicyValidation -Root $testRoot } diff --git a/scripts/Test-SigningInputs.ps1 b/scripts/Test-SigningInputs.ps1 index 9b9cd52..9a81655 100644 --- a/scripts/Test-SigningInputs.ps1 +++ b/scripts/Test-SigningInputs.ps1 @@ -108,6 +108,39 @@ function Get-PackageEntrySha256 { } } +function Assert-NodeArchiveEntry { + param( + [Parameter(Mandatory)] + [IO.Compression.ZipArchiveEntry]$Entry, + + [Parameter(Mandatory)] + [string]$ExpectedRoot + ) + + $stream = $Entry.Open() + $archive = [IO.Compression.ZipArchive]::new( + $stream, + [IO.Compression.ZipArchiveMode]::Read) + try { + $nodeEntries = @( + $archive.Entries | + Where-Object { + $_.FullName -ieq "$ExpectedRoot/node.exe" + } + ) + if ($nodeEntries.Count -ne 1) { + throw ( + "The Node.js archive must contain exactly one " + + "'$ExpectedRoot/node.exe' entry." + ) + } + } + finally { + $archive.Dispose() + $stream.Dispose() + } +} + $resolvedArtifactsDirectory = ( Resolve-Path -LiteralPath $ArtifactsDirectory ).Path @@ -166,6 +199,10 @@ foreach ($architecture in @('x64', 'arm64')) { $metadata.payloadLayout -ne 'immutable-package' -or $metadata.payloadFileCount -isnot [int64] -or $metadata.payloadFileCount -le 0 -or + $metadata.nodeRuntimeVersion -notmatch '^\d+\.\d+\.\d+$' -or + $metadata.nodeRuntimeArchive -ne + "node-v$($metadata.nodeRuntimeVersion)-win-$architecture.zip" -or + $metadata.nodeRuntimeSha256 -notmatch '^[0-9a-fA-F]{64}$' -or $metadata.architecture -ne $architecture -or $metadata.archive -ne $msix.Name -or $metadata.sha256 -notmatch '^[0-9a-fA-F]{64}$' -or @@ -193,16 +230,37 @@ foreach ($architecture in @('x64', 'arm64')) { try { # MSIX percent-encodes some names, so index decoded paths once. $entriesByPath = New-PackageEntryIndex -Archive $packageArchive - $bundledNodeEntries = @( + $expectedNodeArchivePath = + "runtime/$($metadata.nodeRuntimeArchive)" + $unexpectedNodeEntries = @( $entriesByPath.Keys | Where-Object { - [IO.Path]::GetFileName($_) -ieq 'node.exe' -or - [IO.Path]::GetFileName($_) -match '^node-v\d' + ( + [IO.Path]::GetFileName($_) -ieq 'node.exe' -or + [IO.Path]::GetFileName($_) -match '^node-v\d' + ) -and + $_ -ine $expectedNodeArchivePath } ) - if ($bundledNodeEntries.Count -ne 0) { - throw "The $architecture MSIX bundles Node.js." + if ($unexpectedNodeEntries.Count -ne 0) { + throw "The $architecture MSIX has unexpected Node.js content." } + $nodeArchiveEntry = Get-PackageEntry ` + -EntriesByPath $entriesByPath ` + -Path $expectedNodeArchivePath + if ( + (Get-PackageEntrySha256 -Entry $nodeArchiveEntry) -ine + $metadata.nodeRuntimeSha256 + ) { + throw "The embedded $architecture Node.js archive is invalid." + } + Assert-NodeArchiveEntry ` + -Entry $nodeArchiveEntry ` + -ExpectedRoot ( + [IO.Path]::GetFileNameWithoutExtension( + [string]$metadata.nodeRuntimeArchive + ) + ) [xml]$manifest = Read-ZipEntryText ` -EntriesByPath $entriesByPath ` diff --git a/src/OpenClaw.Launcher/ClawCtlConsole.cs b/src/OpenClaw.Launcher/ClawCtlConsole.cs index b4bd295..866991f 100644 --- a/src/OpenClaw.Launcher/ClawCtlConsole.cs +++ b/src/OpenClaw.Launcher/ClawCtlConsole.cs @@ -10,32 +10,23 @@ public static void WriteHelp(TextWriter output) output.WriteLine(); output.WriteLine("Commands:"); output.WriteLine( - " setup Verify Node.js and the packaged OpenClaw application."); + " setup Extract Node.js and verify the packaged OpenClaw application."); output.WriteLine(); output.WriteLine("Options:"); output.WriteLine(" -h, --help Show this help."); output.WriteLine(" --version Print the packaged launcher version."); output.WriteLine(); - WriteNodePrerequisite(output); - output.WriteLine(); output.WriteLine("Run `openclaw ` to invoke the OpenClaw CLI."); } public static void WriteUsage(TextWriter output) => output.WriteLine("Usage: clawctl [setup | --help | --version]"); - public static void WriteNodePrerequisite(TextWriter output) - { - output.WriteLine( - $"Prerequisite: install Node.js {NodeRuntimeResolver.SupportedVersions}."); - output.WriteLine($" {NodeRuntimeResolver.InstallCommand}"); - } - internal static void WriteNodeRuntimeSummary( TextWriter output, NodeRuntime runtime) => output.WriteLine( - $"Using Node.js {runtime.Version} from {runtime.ExecutablePath}"); + $"Bundled Node.js {runtime.Version} is ready at {runtime.ExecutablePath}"); public static void WriteReadinessSummary( TextWriter output, diff --git a/src/OpenClaw.Launcher/HostDataPaths.cs b/src/OpenClaw.Launcher/HostDataPaths.cs new file mode 100644 index 0000000..b4a085d --- /dev/null +++ b/src/OpenClaw.Launcher/HostDataPaths.cs @@ -0,0 +1,67 @@ +using System.Runtime.InteropServices; + +namespace OpenClaw.Launcher; + +internal static class HostDataPaths +{ + private const int ErrorInsufficientBuffer = 122; + private const int AppModelErrorNoPackage = 15700; + + public static string GetProductLocalStateRoot() + { + string localAppData = Environment.GetFolderPath( + Environment.SpecialFolder.LocalApplicationData); + if (string.IsNullOrWhiteSpace(localAppData)) + { + throw new InvalidOperationException( + "The local application data directory is unavailable."); + } + + string? packageFamilyName = TryGetPackageFamilyName(); + return packageFamilyName is null + ? Path.Combine(localAppData, "OpenClaw") + : Path.Combine( + localAppData, + "Packages", + packageFamilyName, + "LocalState", + "OpenClaw"); + } + + internal static string? TryGetPackageFamilyName() + { + if (!OperatingSystem.IsWindows()) + { + return null; + } + + uint length = 0; + int result = GetCurrentPackageFamilyName(ref length, null); + if (result == AppModelErrorNoPackage) + { + return null; + } + + if (result != ErrorInsufficientBuffer || length == 0) + { + throw new InvalidOperationException( + $"Unable to determine package identity (error {result})."); + } + + var value = new char[length]; + result = GetCurrentPackageFamilyName(ref length, value); + if (result != 0) + { + throw new InvalidOperationException( + $"Unable to determine package identity (error {result})."); + } + + return new string(value, 0, checked((int)length - 1)); + } + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] + private static extern int GetCurrentPackageFamilyName( + ref uint packageFamilyNameLength, + [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] + char[]? packageFamilyName); +} diff --git a/src/OpenClaw.Launcher/HostDiagnosticLog.cs b/src/OpenClaw.Launcher/HostDiagnosticLog.cs index f6b794b..103866e 100644 --- a/src/OpenClaw.Launcher/HostDiagnosticLog.cs +++ b/src/OpenClaw.Launcher/HostDiagnosticLog.cs @@ -1,5 +1,4 @@ using System.Globalization; -using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; @@ -7,8 +6,6 @@ namespace OpenClaw.Launcher; internal sealed class HostDiagnosticLog : IDisposable { - private const int ErrorInsufficientBuffer = 122; - private const int AppModelErrorNoPackage = 15700; private readonly Lock _sync = new(); private readonly Mutex _writeMutex; private bool _disposed; @@ -42,7 +39,7 @@ public static HostDiagnosticLog Create() "The local application data directory is unavailable."); } - string? packageFamilyName = GetPackageFamilyName(); + string? packageFamilyName = HostDataPaths.TryGetPackageFamilyName(); string logRoot = packageFamilyName is null ? System.IO.Path.Combine(localAppData, "OpenClawGatewayMSIX") : System.IO.Path.Combine( @@ -119,40 +116,4 @@ public void Dispose() GC.SuppressFinalize(this); } - private static string? GetPackageFamilyName() - { - if (!OperatingSystem.IsWindows()) - { - return null; - } - - uint length = 0; - int result = GetCurrentPackageFamilyName(ref length, null); - if (result == AppModelErrorNoPackage) - { - return null; - } - - if (result != ErrorInsufficientBuffer || length == 0) - { - throw new InvalidOperationException( - $"Unable to determine package identity (error {result})."); - } - - var value = new char[length]; - result = GetCurrentPackageFamilyName(ref length, value); - if (result != 0) - { - throw new InvalidOperationException( - $"Unable to determine package identity (error {result})."); - } - - return new string(value, 0, checked((int)length - 1)); - } - - [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] - private static extern int GetCurrentPackageFamilyName( - ref uint packageFamilyNameLength, - [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] - char[]? packageFamilyName); } diff --git a/src/OpenClaw.Launcher/HostOptions.cs b/src/OpenClaw.Launcher/HostOptions.cs index 0432db7..6dace6f 100644 --- a/src/OpenClaw.Launcher/HostOptions.cs +++ b/src/OpenClaw.Launcher/HostOptions.cs @@ -2,6 +2,7 @@ namespace OpenClaw.Launcher; internal sealed record HostOptions( string? PackagedApplicationDirectory, + string? PackagedNodeArchivePath, IReadOnlyList OpenClawArguments) { public static HostOptions Parse(IReadOnlyList arguments) => @@ -23,9 +24,18 @@ internal static HostOptions Parse( "openclaw.mjs")) ? packagedApplicationDirectory : null; + string packagedNodeArchivePath = Path.Combine( + baseDirectory, + "runtime", + NodeRuntimeInstaller.GetArchiveFileName( + System.Runtime.InteropServices.RuntimeInformation + .ProcessArchitecture)); return new HostOptions( directApplicationDirectory, + File.Exists(packagedNodeArchivePath) + ? packagedNodeArchivePath + : null, [.. arguments]); } } diff --git a/src/OpenClaw.Launcher/NodeRuntimeInstaller.cs b/src/OpenClaw.Launcher/NodeRuntimeInstaller.cs new file mode 100644 index 0000000..06475f6 --- /dev/null +++ b/src/OpenClaw.Launcher/NodeRuntimeInstaller.cs @@ -0,0 +1,208 @@ +using System.IO.Compression; +using System.Runtime.InteropServices; + +namespace OpenClaw.Launcher; + +internal static class NodeRuntimeInstaller +{ + public const string Version = "24.16.0"; + + public static string GetArchiveFileName(Architecture architecture) => + $"node-v{Version}-win-{GetArchitectureName(architecture)}.zip"; + + public static string GetInstallDirectory(Architecture architecture) => + Path.Combine( + HostDataPaths.GetProductLocalStateRoot(), + "NodeJS", + $"node-v{Version}-win-{GetArchitectureName(architecture)}"); + + public static string GetExecutablePath(Architecture architecture) => + Path.Combine(GetInstallDirectory(architecture), "node.exe"); + + public static async Task EnsureInstalledAsync( + string archivePath, + CancellationToken cancellationToken) + { + Architecture architecture = RuntimeInformation.ProcessArchitecture; + string executablePath = EnsureInstalled( + archivePath, + GetInstallDirectory(architecture), + architecture); + return await NodeRuntimeResolver.ResolvePathAsync( + executablePath, + cancellationToken).ConfigureAwait(false); + } + + internal static string EnsureInstalled( + string archivePath, + string installDirectory, + Architecture architecture) + { + string executablePath = Path.Combine(installDirectory, "node.exe"); + if (File.Exists(executablePath)) + { + return executablePath; + } + + if (!File.Exists(archivePath)) + { + throw new FileNotFoundException( + "The packaged Node.js runtime archive was not found.", + archivePath); + } + + string mutexName = + $"Local\\OpenClawGatewayMSIX.NodeRuntime.{GetArchitectureName(architecture)}"; + using var mutex = new Mutex(initiallyOwned: false, mutexName); + bool ownsMutex = false; + try + { + try + { + ownsMutex = mutex.WaitOne(); + } + catch (AbandonedMutexException) + { + ownsMutex = true; + } + + if (File.Exists(executablePath)) + { + return executablePath; + } + + string? parentDirectory = Path.GetDirectoryName(installDirectory); + if (string.IsNullOrWhiteSpace(parentDirectory)) + { + throw new InvalidOperationException( + "The Node.js installation path has no parent directory."); + } + + Directory.CreateDirectory(parentDirectory); + string stagingDirectory = + $"{installDirectory}.{Guid.NewGuid():N}.extract"; + try + { + ExtractRuntime( + archivePath, + stagingDirectory, + architecture); + if (!File.Exists(Path.Combine(stagingDirectory, "node.exe"))) + { + throw new InvalidDataException( + "The packaged Node.js archive does not contain node.exe."); + } + + if (Directory.Exists(installDirectory)) + { + Directory.Delete(installDirectory, recursive: true); + } + Directory.Move(stagingDirectory, installDirectory); + } + finally + { + if (Directory.Exists(stagingDirectory)) + { + Directory.Delete(stagingDirectory, recursive: true); + } + } + + return executablePath; + } + finally + { + if (ownsMutex) + { + mutex.ReleaseMutex(); + } + } + } + + private static void ExtractRuntime( + string archivePath, + string stagingDirectory, + Architecture architecture) + { + string archiveRoot = + $"node-v{Version}-win-{GetArchitectureName(architecture)}"; + string archivePrefix = archiveRoot + "/"; + string fullStagingDirectory = + Path.GetFullPath(stagingDirectory) + Path.DirectorySeparatorChar; + Directory.CreateDirectory(stagingDirectory); + + using ZipArchive archive = ZipFile.OpenRead(archivePath); + foreach (ZipArchiveEntry entry in archive.Entries) + { + string archivePathName = entry.FullName.Replace('\\', '/'); + if (string.Equals( + archivePathName, + archiveRoot + "/", + StringComparison.Ordinal)) + { + continue; + } + + if (!archivePathName.StartsWith( + archivePrefix, + StringComparison.Ordinal)) + { + throw new InvalidDataException( + $"Unexpected Node.js archive entry: {entry.FullName}"); + } + + bool isDirectory = string.IsNullOrEmpty(entry.Name); + string relativePath = archivePathName[archivePrefix.Length..]; + if (isDirectory) + { + relativePath = relativePath.TrimEnd('/'); + } + string[] segments = relativePath.Split('/'); + if ( + string.IsNullOrWhiteSpace(relativePath) || + Path.IsPathRooted(relativePath) || + relativePath.Contains(':', StringComparison.Ordinal) || + segments.Contains(string.Empty, StringComparer.Ordinal) || + segments.Contains(".", StringComparer.Ordinal) || + segments.Contains("..", StringComparer.Ordinal)) + { + throw new InvalidDataException( + $"Unsafe Node.js archive entry: {entry.FullName}"); + } + + string destinationPath = Path.GetFullPath( + Path.Combine(stagingDirectory, relativePath)); + if (!destinationPath.StartsWith( + fullStagingDirectory, + StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidDataException( + $"Unsafe Node.js archive entry: {entry.FullName}"); + } + + if (isDirectory) + { + Directory.CreateDirectory(destinationPath); + continue; + } + + string? destinationDirectory = Path.GetDirectoryName(destinationPath); + if (string.IsNullOrWhiteSpace(destinationDirectory)) + { + throw new InvalidDataException( + $"Invalid Node.js archive entry: {entry.FullName}"); + } + + Directory.CreateDirectory(destinationDirectory); + entry.ExtractToFile(destinationPath); + } + } + + private static string GetArchitectureName(Architecture architecture) => + architecture switch + { + Architecture.X64 => "x64", + Architecture.Arm64 => "arm64", + _ => throw new PlatformNotSupportedException( + $"Node.js runtime packaging does not support {architecture}.") + }; +} diff --git a/src/OpenClaw.Launcher/NodeRuntimeResolver.cs b/src/OpenClaw.Launcher/NodeRuntimeResolver.cs index f9fbc58..5f24686 100644 --- a/src/OpenClaw.Launcher/NodeRuntimeResolver.cs +++ b/src/OpenClaw.Launcher/NodeRuntimeResolver.cs @@ -13,9 +13,6 @@ internal sealed record NodeRuntime( internal static partial class NodeRuntimeResolver { - public const string InstallCommand = - "winget install --id OpenJS.NodeJS.LTS --exact --source winget"; - private static readonly TimeSpan VersionQueryTimeout = TimeSpan.FromSeconds(10); private static readonly NodeVersionRange[] SupportedVersionRanges = [ @@ -32,8 +29,16 @@ range.ExclusiveMajor is int exclusiveMajor : $">={range.Minimum}")); public static Task ResolveAsync(CancellationToken cancellationToken) => + ResolvePathAsync( + NodeRuntimeInstaller.GetExecutablePath( + RuntimeInformation.ProcessArchitecture), + cancellationToken); + + public static Task ResolvePathAsync( + string executablePath, + CancellationToken cancellationToken) => ResolveAsync( - FindPathCandidates(), + File.Exists(executablePath) ? [executablePath] : [], QueryVersionAsync, ReadArchitecture, RuntimeInformation.ProcessArchitecture, @@ -49,7 +54,8 @@ internal static async Task ResolveAsync( if (candidates.Count == 0) { throw new InvalidOperationException( - CreateFailureMessage("Node.js was not found on PATH.")); + CreateFailureMessage( + "The bundled Node.js runtime has not been extracted.")); } var failures = new List(); @@ -118,49 +124,8 @@ internal static Version ParseVersion(string output) internal static string CreateFailureMessage(string detail) => $"{detail}{Environment.NewLine}" + - $"Install a supported Node.js runtime ({SupportedVersions}):{Environment.NewLine}" + - $" {InstallCommand}{Environment.NewLine}" + - "Then open a new terminal and retry."; - - private static List FindPathCandidates() - { - string? pathValue = Environment.GetEnvironmentVariable("PATH"); - if (string.IsNullOrWhiteSpace(pathValue)) - { - return []; - } - - var candidates = new List(); - var seen = new HashSet(StringComparer.OrdinalIgnoreCase); - foreach (string entry in pathValue.Split(Path.PathSeparator)) - { - string directory = entry.Trim().Trim('"'); - if (string.IsNullOrWhiteSpace(directory)) - { - continue; - } - - string candidate; - try - { - candidate = Path.GetFullPath(Path.Combine(directory, "node.exe")); - } - catch (Exception exception) when ( - exception is ArgumentException or - NotSupportedException or - PathTooLongException) - { - continue; - } - - if (File.Exists(candidate) && seen.Add(candidate)) - { - candidates.Add(candidate); - } - } - - return candidates; - } + $"Run `clawctl setup` to extract Node.js {NodeRuntimeInstaller.Version} " + + "from the installed OpenClaw package."; private static async Task QueryVersionAsync( string executablePath, diff --git a/src/OpenClaw.Launcher/OpenClaw.Launcher.csproj b/src/OpenClaw.Launcher/OpenClaw.Launcher.csproj index c5aa467..47071d7 100644 --- a/src/OpenClaw.Launcher/OpenClaw.Launcher.csproj +++ b/src/OpenClaw.Launcher/OpenClaw.Launcher.csproj @@ -81,6 +81,9 @@ + + RunControlAsync( Action log, Action writeError, TextWriter output, - Func>? resolveNode = null) + Func>? setupNode = null) { ClawCtlCommandParseResult parsed = ClawCtlCommandParser.Parse(args); if (parsed.Error is not null) @@ -189,9 +189,19 @@ await output.WriteLineAsync( return 0; case ClawCtlCommand.Setup: { - NodeRuntime nodeRuntime = await ( - resolveNode ?? NodeRuntimeResolver.ResolveAsync)( + NodeRuntime nodeRuntime; + if (setupNode is not null) + { + nodeRuntime = await setupNode(CancellationToken.None) + .ConfigureAwait(false); + } + else + { + string archivePath = GetPackagedNodeArchivePath(options); + nodeRuntime = await NodeRuntimeInstaller.EnsureInstalledAsync( + archivePath, CancellationToken.None).ConfigureAwait(false); + } ClawCtlConsole.WriteNodeRuntimeSummary(output, nodeRuntime); string applicationDirectory = GetPackagedApplicationDirectory(options); @@ -231,4 +241,23 @@ private static string GetPackagedApplicationDirectory(HostOptions options) return applicationDirectory; } + + private static string GetPackagedNodeArchivePath(HostOptions options) + { + string? archivePath = options.PackagedNodeArchivePath; + string expectedPath = archivePath ?? Path.Combine( + AppContext.BaseDirectory, + "runtime", + NodeRuntimeInstaller.GetArchiveFileName( + System.Runtime.InteropServices.RuntimeInformation + .ProcessArchitecture)); + if (archivePath is null || !File.Exists(archivePath)) + { + throw new FileNotFoundException( + "The packaged Node.js runtime archive was not found.", + expectedPath); + } + + return archivePath; + } } diff --git a/tests/OpenClaw.Launcher.Tests/ClawCtlConsoleTests.cs b/tests/OpenClaw.Launcher.Tests/ClawCtlConsoleTests.cs index 2076d9c..948999e 100644 --- a/tests/OpenClaw.Launcher.Tests/ClawCtlConsoleTests.cs +++ b/tests/OpenClaw.Launcher.Tests/ClawCtlConsoleTests.cs @@ -27,14 +27,7 @@ public void WriteHelpListsOnlyThePublicCommands() $"{Environment.NewLine} repair", help, StringComparison.OrdinalIgnoreCase); - Assert.Contains( - NodeRuntimeResolver.SupportedVersions, - help, - StringComparison.Ordinal); - Assert.Contains( - NodeRuntimeResolver.InstallCommand, - help, - StringComparison.Ordinal); + Assert.Contains("Extract Node.js", help, StringComparison.Ordinal); Assert.DoesNotContain("update-package", help, StringComparison.Ordinal); Assert.DoesNotContain("gateway-service", help, StringComparison.Ordinal); } diff --git a/tests/OpenClaw.Launcher.Tests/HostOptionsTests.cs b/tests/OpenClaw.Launcher.Tests/HostOptionsTests.cs index 41f8893..48c9ccb 100644 --- a/tests/OpenClaw.Launcher.Tests/HostOptionsTests.cs +++ b/tests/OpenClaw.Launcher.Tests/HostOptionsTests.cs @@ -31,6 +31,7 @@ public void ParseReportsMissingPackagedApplication() HostOptions options = HostOptions.Parse([], _testDirectory); Assert.Null(options.PackagedApplicationDirectory); + Assert.Null(options.PackagedNodeArchivePath); Assert.Empty(options.OpenClawArguments); } @@ -50,9 +51,27 @@ public void ParseResolvesPackagedApplicationWhenEntryPointExists() Assert.Equal( applicationDirectory, options.PackagedApplicationDirectory); + Assert.Null(options.PackagedNodeArchivePath); Assert.Equal(["gateway", "run"], options.OpenClawArguments); } + [Fact] + public void ParseResolvesArchitectureSpecificPackagedNodeArchive() + { + string runtimeDirectory = Path.Combine(_testDirectory, "runtime"); + Directory.CreateDirectory(runtimeDirectory); + string archivePath = Path.Combine( + runtimeDirectory, + NodeRuntimeInstaller.GetArchiveFileName( + System.Runtime.InteropServices.RuntimeInformation + .ProcessArchitecture)); + File.WriteAllText(archivePath, "fixture"); + + HostOptions options = HostOptions.Parse([], _testDirectory); + + Assert.Equal(archivePath, options.PackagedNodeArchivePath); + } + public void Dispose() { Directory.Delete(_testDirectory, recursive: true); diff --git a/tests/OpenClaw.Launcher.Tests/NodeRuntimeInstallerTests.cs b/tests/OpenClaw.Launcher.Tests/NodeRuntimeInstallerTests.cs new file mode 100644 index 0000000..989d944 --- /dev/null +++ b/tests/OpenClaw.Launcher.Tests/NodeRuntimeInstallerTests.cs @@ -0,0 +1,101 @@ +using System.IO.Compression; +using System.Runtime.InteropServices; + +namespace OpenClaw.Launcher.Tests; + +public sealed class NodeRuntimeInstallerTests : IDisposable +{ + private readonly string _testDirectory = TestDirectory.Create(); + + [Fact] + public void EnsureInstalledExtractsArchitectureRuntimeOnce() + { + Architecture architecture = Architecture.X64; + string archivePath = CreateRuntimeArchive(architecture); + string installDirectory = Path.Combine(_testDirectory, "installed"); + + string firstPath = NodeRuntimeInstaller.EnsureInstalled( + archivePath, + installDirectory, + architecture); + DateTime firstWriteTime = File.GetLastWriteTimeUtc(firstPath); + string secondPath = NodeRuntimeInstaller.EnsureInstalled( + archivePath, + installDirectory, + architecture); + + Assert.Equal(Path.Combine(installDirectory, "node.exe"), firstPath); + Assert.Equal(firstPath, secondPath); + Assert.Equal("fixture-node", File.ReadAllText(firstPath)); + Assert.Equal(firstWriteTime, File.GetLastWriteTimeUtc(secondPath)); + } + + [Fact] + public void EnsureInstalledRejectsEntriesOutsideExpectedRuntimeRoot() + { + string archivePath = Path.Combine(_testDirectory, "unsafe.zip"); + using (ZipArchive archive = ZipFile.Open( + archivePath, + ZipArchiveMode.Create)) + { + ZipArchiveEntry entry = archive.CreateEntry("unexpected/node.exe"); + using StreamWriter writer = new(entry.Open()); + writer.Write("fixture-node"); + } + + Assert.Throws(() => + NodeRuntimeInstaller.EnsureInstalled( + archivePath, + Path.Combine(_testDirectory, "unsafe-install"), + Architecture.X64)); + } + + [Fact] + public void EnsureInstalledReplacesIncompleteRuntimeDirectory() + { + Architecture architecture = Architecture.X64; + string archivePath = CreateRuntimeArchive(architecture); + string installDirectory = Path.Combine(_testDirectory, "incomplete"); + Directory.CreateDirectory(installDirectory); + File.WriteAllText( + Path.Combine(installDirectory, "partial.txt"), + "partial"); + + string executablePath = NodeRuntimeInstaller.EnsureInstalled( + archivePath, + installDirectory, + architecture); + + Assert.True(File.Exists(executablePath)); + Assert.False(File.Exists( + Path.Combine(installDirectory, "partial.txt"))); + } + + public void Dispose() + { + Directory.Delete(_testDirectory, recursive: true); + GC.SuppressFinalize(this); + } + + private string CreateRuntimeArchive(Architecture architecture) + { + string archivePath = Path.Combine( + _testDirectory, + NodeRuntimeInstaller.GetArchiveFileName(architecture)); + string rootName = Path.GetFileNameWithoutExtension(archivePath); + using ZipArchive archive = ZipFile.Open( + archivePath, + ZipArchiveMode.Create); + archive.CreateEntry($"{rootName}/node_modules/"); + ZipArchiveEntry nestedEntry = archive.CreateEntry( + $"{rootName}/node_modules/package.json"); + using (StreamWriter nestedWriter = new(nestedEntry.Open())) + { + nestedWriter.Write("{}"); + } + ZipArchiveEntry entry = archive.CreateEntry($"{rootName}/node.exe"); + using StreamWriter writer = new(entry.Open()); + writer.Write("fixture-node"); + return archivePath; + } +} diff --git a/tests/OpenClaw.Launcher.Tests/NodeRuntimeResolverTests.cs b/tests/OpenClaw.Launcher.Tests/NodeRuntimeResolverTests.cs index 3c77ea1..cf92dcd 100644 --- a/tests/OpenClaw.Launcher.Tests/NodeRuntimeResolverTests.cs +++ b/tests/OpenClaw.Launcher.Tests/NodeRuntimeResolverTests.cs @@ -19,7 +19,7 @@ public async Task ResolveAcceptsCompatibleRuntime() } [Fact] - public async Task ResolveReportsInstallCommandWhenRuntimeIsMissing() + public async Task ResolveReportsSetupCommandWhenRuntimeIsMissing() { InvalidOperationException exception = await Assert.ThrowsAsync< InvalidOperationException>( @@ -29,8 +29,8 @@ public async Task ResolveReportsInstallCommandWhenRuntimeIsMissing() Architecture.X64, Architecture.X64)); - Assert.Contains("not found on PATH", exception.Message, StringComparison.Ordinal); - Assert.Contains(NodeRuntimeResolver.InstallCommand, exception.Message, StringComparison.Ordinal); + Assert.Contains("has not been extracted", exception.Message, StringComparison.Ordinal); + Assert.Contains("clawctl setup", exception.Message, StringComparison.Ordinal); } [Fact] @@ -103,7 +103,7 @@ public async Task ResolveReportsVersionQueryFailure() Architecture.X64)); Assert.Contains("query failed", exception.Message, StringComparison.Ordinal); - Assert.Contains(NodeRuntimeResolver.InstallCommand, exception.Message, StringComparison.Ordinal); + Assert.Contains("clawctl setup", exception.Message, StringComparison.Ordinal); } [Theory] diff --git a/tests/OpenClaw.Launcher.Tests/ProgramTests.cs b/tests/OpenClaw.Launcher.Tests/ProgramTests.cs index 1ef0b16..c18f3d7 100644 --- a/tests/OpenClaw.Launcher.Tests/ProgramTests.cs +++ b/tests/OpenClaw.Launcher.Tests/ProgramTests.cs @@ -13,7 +13,7 @@ await File.WriteAllTextAsync( Path.Combine(applicationDirectory, "openclaw.mjs"), "console.log('fixture');"); string[] arguments = ["gateway", "run", "--port", "12345"]; - var options = new HostOptions(applicationDirectory, arguments); + var options = new HostOptions(applicationDirectory, null, arguments); var nodeRuntime = new NodeRuntime( Path.Combine(_testDirectory, "node.exe"), new Version(24, 15, 0), @@ -44,14 +44,14 @@ await File.WriteAllTextAsync( } [Fact] - public async Task SetupChecksNodeAndPackagedApplicationWithoutMutation() + public async Task SetupPreparesNodeAndChecksPackagedApplication() { string applicationDirectory = Path.Combine(_testDirectory, "app"); Directory.CreateDirectory(applicationDirectory); string entryPoint = Path.Combine(applicationDirectory, "openclaw.mjs"); await File.WriteAllTextAsync(entryPoint, "console.log('fixture');"); DateTime lastWriteTime = File.GetLastWriteTimeUtc(entryPoint); - var options = new HostOptions(applicationDirectory, []); + var options = new HostOptions(applicationDirectory, null, []); var nodeRuntime = new NodeRuntime( Path.Combine(_testDirectory, "node.exe"), new Version(24, 15, 0), @@ -86,7 +86,7 @@ public async Task SetupResolvesNodeBeforeReportingMissingApplication() await Assert.ThrowsAsync( () => Program.RunControlAsync( - new HostOptions(null, []), + new HostOptions(null, null, []), ["setup"], _ => { }, _ => { }, @@ -112,7 +112,7 @@ public async Task AgentResolvesNodeBeforeReportingMissingApplication() await Assert.ThrowsAsync( () => Program.RunAgentAsync( - new HostOptions(null, []), + new HostOptions(null, null, []), _ => { }, _ => { From fa303bff28a8906a65566472e7f08259b540acfb Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Fri, 11 Sep 2026 15:48:52 -0700 Subject: [PATCH 11/35] fix: fail loudly when test MSIX signing has no inputs (#31) The signing script previously skipped both architecture directories when the artifact root was not CI-shaped, then reported success after creating and discarding a certificate. Validate the expected architecture directories before creating signing material so a no-op cannot look like a signed result. Add a regression test that runs the script against an empty artifact root and verifies the failure message, non-zero exit, absence of output, and absence of a newly created publisher certificate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 97c31f3f-0c7b-43e8-b979-6418dcb7fedc --- scripts/Sign-TestMSIX.ps1 | 20 ++++++++- scripts/Test-Sign-TestMSIX.Tests.ps1 | 63 ++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 scripts/Test-Sign-TestMSIX.Tests.ps1 diff --git a/scripts/Sign-TestMSIX.ps1 b/scripts/Sign-TestMSIX.ps1 index 6e0d116..fa1d19f 100644 --- a/scripts/Sign-TestMSIX.ps1 +++ b/scripts/Sign-TestMSIX.ps1 @@ -29,6 +29,24 @@ if ([string]::IsNullOrWhiteSpace($publisher)) { throw 'The Gateway MSIX release policy publisher is missing.' } +$architectures = @('x64', 'arm64') +$architectureDirectories = @( + $architectures | + ForEach-Object { + $sourceDirectory = Join-Path $resolvedArtifactsDirectory $_ + if (Test-Path -LiteralPath $sourceDirectory -PathType Container) { + $sourceDirectory + } + } +) +if ($architectureDirectories.Count -eq 0) { + throw ( + "No architecture directories were found under " + + "'$resolvedArtifactsDirectory'. Expected at least one of: " + + ($architectures -join ', ') + '.' + ) +} + $signtool = Get-ChildItem ` -LiteralPath "${env:ProgramFiles(x86)}\Windows Kits\10\bin" ` -Filter 'signtool.exe' ` @@ -75,7 +93,7 @@ try { -Password $password | Out-Null - foreach ($architecture in @('x64', 'arm64')) { + foreach ($architecture in $architectures) { $sourceDirectory = Join-Path $resolvedArtifactsDirectory $architecture if (-not (Test-Path -LiteralPath $sourceDirectory -PathType Container)) { continue diff --git a/scripts/Test-Sign-TestMSIX.Tests.ps1 b/scripts/Test-Sign-TestMSIX.Tests.ps1 new file mode 100644 index 0000000..88338e1 --- /dev/null +++ b/scripts/Test-Sign-TestMSIX.Tests.ps1 @@ -0,0 +1,63 @@ +[CmdletBinding()] +param() + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +if (-not $IsWindows) { + throw 'Test MSIX signing tests require Windows.' +} + +$scriptPath = Join-Path $PSScriptRoot 'Sign-TestMSIX.ps1' +$testRoot = Join-Path $env:TEMP ( + "openclaw-sign-testmsix-$([guid]::NewGuid().ToString('N'))" +) +$artifactsDirectory = Join-Path $testRoot 'artifacts' +$outputDirectory = Join-Path $testRoot 'signed' +$publisher = 'CN=OpenClaw Foundation, O=OpenClaw Foundation, L=Mill Valley, S=California, C=US' +$thumbprintsBefore = @( + Get-ChildItem -Path 'Cert:\CurrentUser\My' | + Where-Object Subject -eq $publisher | + Select-Object -ExpandProperty Thumbprint +) + +try { + New-Item -Path $artifactsDirectory -ItemType Directory -Force | Out-Null + + $output = @( + & pwsh -NoProfile -File $scriptPath ` + -ArtifactsDirectory $artifactsDirectory ` + -OutputDirectory $outputDirectory 2>&1 + ) + $exitCode = $LASTEXITCODE + $message = $output -join [Environment]::NewLine + + if ($exitCode -eq 0) { + throw 'Signing unexpectedly succeeded without an architecture directory.' + } + if ($message -notmatch 'No architecture directories were found') { + throw "Signing failure did not identify the missing architecture directories. Output: $message" + } + if (Test-Path -LiteralPath $outputDirectory) { + throw 'Signing created an output directory despite finding no architecture directory.' + } + + $thumbprintsAfter = @( + Get-ChildItem -Path 'Cert:\CurrentUser\My' | + Where-Object Subject -eq $publisher | + Select-Object -ExpandProperty Thumbprint + ) + $newThumbprints = Compare-Object ` + -ReferenceObject $thumbprintsBefore ` + -DifferenceObject $thumbprintsAfter ` + -PassThru | + Where-Object SideIndicator -eq '=>' + if ($null -ne $newThumbprints) { + throw 'Signing created a publisher certificate on the no-op failure path.' + } + + Write-Host 'Test MSIX no-op signing regression test passed.' +} +finally { + Remove-Item -LiteralPath $testRoot -Recurse -Force -ErrorAction SilentlyContinue +} From 6f89905a5349dcdd9416420fe1fa357afecf17ef Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Fri, 11 Sep 2026 15:49:12 -0700 Subject: [PATCH 12/35] refactor(clawctl): parse management arguments with System.CommandLine (#29) * Parse clawctl arguments with System.CommandLine Replace the hand-written clawctl parser and help renderer with a declarative System.CommandLine tree. The `openclaw` entrypoint is unchanged and still forwards its argument vector to the OpenClaw CLI without parsing it. - Add ClawCtlCommandLine: a root command plus a `setup` subcommand, with Node prerequisite and install guidance in the command descriptions so help is generated from one source instead of hand-formatted. - Delete ClawCtlCommand and its enum/parse-result/exact-token matcher rather than leaving a second dispatch model beside the library. - Rewrite Program.RunControlAsync around the tree. Disable the library's default exception handler so operational failures keep reaching the host's diagnostic boundary, and leave ProcessTerminationTimeout null so it does not compete with the Node job object for process lifetime. The setup action's cancellation token now flows into the Node resolver. - Replace the `Action writeError` seam with an injected TextWriter and drop the direct Console.Error write from the control dispatcher. - Report the launcher assembly version through a custom version action. The built-in one reports the entry assembly, which under a test or scenario host is not the launcher. - Disable response-file expansion. A leading `@` is an unrecognized argument for clawctl and stays uninterpreted through `openclaw`. Completion is kept. Invalid management input now exits 1, the library's parse-error code, instead of the previous 2. Add Test-NativeAotCli.Tests.ps1 and run it in CI. The xUnit suite runs under a JIT test host, so it cannot observe the root command name that System.CommandLine derives from native argv[0], and a successful AOT publish is not execution evidence. The script publishes win-x64 NativeAOT into a temporary directory it owns, runs the binary as clawctl.exe, and removes it afterwards. Node-dependent scenarios stay in xUnit with an injected runtime so the gate does not depend on the agent's installed Node version. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 79e90679-15f4-4fd5-a669-d70d363a9c34 * Cover the completion directive and version precedence The only completion test used the in-process GetCompletions API, which no shell can reach. Cover the `[suggest]` directive through the real control dispatcher instead, since that is the path a completion client actually calls. Also record that `--version` now wins over trailing arguments. The old parser rejected the combination; the library's version action clears parse errors, so `clawctl --version bogus` prints the version and exits 0. That is stock System.CommandLine behavior, not a local choice, but it is a visible change to this CLI's contract and was previously untested. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 79e90679-15f4-4fd5-a669-d70d363a9c34 * fix: exit the NativeAOT clawctl gate with its own status The gate's final assertion runs clawctl with an argument that must be rejected, so $LASTEXITCODE is 1 when the script ends. GitHub Actions exits a pwsh step with that value, so the step failed even though every assertion passed. The job printed "NativeAOT clawctl checks completed successfully." immediately before reporting exit code 1. Exit 0 explicitly after cleanup. Assertion failures throw and never reach that line, so the gate still fails when it should: verified by injecting a pattern that cannot match, which exits 1 with the offending output. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 79e90679-15f4-4fd5-a669-d70d363a9c34 * Isolate the NativeAOT command-line gate from the user profile The native gate published the production launcher and ran its `Main`. `Main` creates the diagnostic log through `HostDiagnosticLog.Create()`, which resolves `%LOCALAPPDATA%\OpenClawGatewayMSIX` rather than anything relative to the publish directory, so every gate run appended startup and exit records to the developer's real log. Publishing into a temporary directory did not redirect that, and green CI never revealed it. Extract the startup path into `Program.RunAsync(args, HostStartup)` and leave `Main` as the production adapter that supplies the real collaborators. `HostStartup` carries the entrypoint, the diagnostic factory, the base directory, the writers, and the existing Node and launch seams, so startup can be driven with fixture-owned storage. Replace the gate's subject with a NativeAOT scenario driver in `tests/OpenClaw.Launcher.AotSmoke`. It runs the same `RunAsync` under the `clawctl.exe` name with an explicit temporary log path, in-memory writers, and delegates that cannot start a real process, so native coverage still includes startup logging and the error boundary. The script now also runs the driver under a wrong executable name and requires a nonzero exit, so the alias check cannot pass vacuously. Add JIT regressions for the same seam: diagnostics routed to a test path, a handled operational failure reporting that path and exiting 1, a failed diagnostic factory warning once and continuing, and `openclaw` forwarding arguments verbatim while returning the child's exit code. A read-only before/after observation of the normal unpackaged log across a full gate run showed an unchanged length, timestamp, and SHA-256. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 79e90679-15f4-4fd5-a669-d70d363a9c34 * Keep the NativeAOT scenario fixtures out of %TEMP% CodeQL treats `Path.GetTempPath()` as a user-controlled source, because `TMP` is an environment variable a caller controls. The scenario driver is a plain console executable rather than a test-framework project, so it is not classified as test code, and its fixture roots flowed into `HostOptions.Parse` and the driver's own file writes as eight new high-severity `cs/path-injection` alerts on the pull request. Build the fixture roots under `AppContext.BaseDirectory` instead. The gate script already publishes into a directory it owns and deletes, so the scenarios stay isolated without reading an environment variable, and the alerts have no source. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 79e90679-15f4-4fd5-a669-d70d363a9c34 --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 79e90679-15f4-4fd5-a669-d70d363a9c34 --- .github/copilot-instructions.md | 6 + .github/workflows/gateway-msix.yml | 15 + CONTRIBUTING.md | 27 +- Directory.Packages.props | 1 + OpenClaw.Gateway.MSIX.slnx | 1 + README.md | 21 +- scripts/Test-NativeAotCli.Tests.ps1 | 159 +++++++ src/OpenClaw.Launcher/ClawCtlCommand.cs | 41 -- src/OpenClaw.Launcher/ClawCtlCommandLine.cs | 98 +++++ src/OpenClaw.Launcher/ClawCtlConsole.cs | 29 -- src/OpenClaw.Launcher/HostStartup.cs | 34 ++ .../OpenClaw.Launcher.csproj | 5 + src/OpenClaw.Launcher/Program.cs | 114 +++-- .../OpenClaw.Launcher.AotSmoke.csproj | 33 ++ .../SmokeProgram.cs | 408 ++++++++++++++++++ .../ClawCtlCommandLineTests.cs | 158 +++++++ .../ClawCtlCommandTests.cs | 48 --- .../ClawCtlConsoleTests.cs | 35 -- .../ClawCtlParserDefaultsTests.cs | 104 +++++ .../GatewayLauncherTests.cs | 14 + .../ProgramStartupTests.cs | 145 +++++++ tests/OpenClaw.Launcher.Tests/ProgramTests.cs | 6 +- 22 files changed, 1297 insertions(+), 205 deletions(-) create mode 100644 scripts/Test-NativeAotCli.Tests.ps1 delete mode 100644 src/OpenClaw.Launcher/ClawCtlCommand.cs create mode 100644 src/OpenClaw.Launcher/ClawCtlCommandLine.cs create mode 100644 src/OpenClaw.Launcher/HostStartup.cs create mode 100644 tests/OpenClaw.Launcher.AotSmoke/OpenClaw.Launcher.AotSmoke.csproj create mode 100644 tests/OpenClaw.Launcher.AotSmoke/SmokeProgram.cs create mode 100644 tests/OpenClaw.Launcher.Tests/ClawCtlCommandLineTests.cs delete mode 100644 tests/OpenClaw.Launcher.Tests/ClawCtlCommandTests.cs create mode 100644 tests/OpenClaw.Launcher.Tests/ClawCtlParserDefaultsTests.cs create mode 100644 tests/OpenClaw.Launcher.Tests/ProgramStartupTests.cs diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 304db33..3790db3 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -61,6 +61,12 @@ and ARM64 separately. point exists, and forwards every argument unchanged to `openclaw.mjs`. - `clawctl setup` is a read-only readiness check for compatible Node.js and the packaged entry point. Runtime launches do not hash or walk package files. +- `clawctl` parses its own arguments with System.CommandLine + (`ClawCtlCommandLine` builds the tree; `Program.RunControlAsync` invokes it). + Help, usage, version, and completion are library behavior; parse errors exit + `1`. Response-file expansion is disabled, so `@file` is an ordinary + unrecognized argument. The library is scoped to `clawctl` only and must never + see `openclaw` arguments. - `GatewayLauncher` starts Node without a shell, uses `ArgumentList`, inherits the console streams, and sets `OPENCLAW_SUPERVISOR_MODE=external` plus `OPENCLAW_NO_AUTO_UPDATE=1`. The child process exit code is the launcher exit diff --git a/.github/workflows/gateway-msix.yml b/.github/workflows/gateway-msix.yml index 826434f..81c0b75 100644 --- a/.github/workflows/gateway-msix.yml +++ b/.github/workflows/gateway-msix.yml @@ -64,6 +64,21 @@ jobs: --configuration Release --no-restore + # The xUnit suite runs under a JIT test host, so it cannot prove the + # native entrypoint alias and root command name, or that + # System.CommandLine survives trimming and ahead-of-time compilation. + # This publishes the scenario driver in tests\OpenClaw.Launcher.AotSmoke, + # which runs the shared startup path with fixture-owned diagnostics + # instead of the production Main, so nothing is written outside the + # temporary directory the script owns. x64 only: windows-latest cannot + # execute an ARM64 binary, and both architectures are still compiled by + # the packaging matrix. + - name: Test NativeAOT clawctl command line + shell: pwsh + run: > + .\scripts\Test-NativeAotCli.Tests.ps1 + -Configuration Release + - name: Test signing policy shell: pwsh run: > diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ca67588..c6549ac 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -59,6 +59,29 @@ dotnet publish .\src\OpenClaw.Launcher\OpenClaw.Launcher.csproj ` --configuration Release --runtime win-x64 --self-contained ``` +Run the native `clawctl` gate when you change command-line parsing, help, +version output, or the host startup path. The xUnit suite runs under a JIT test +host, so it cannot see the entrypoint alias or root command name that the +launcher derives from native `argv[0]`, and a successful publish is not +execution evidence. The script publishes the scenario driver in +`tests\OpenClaw.Launcher.AotSmoke` for win-x64 with NativeAOT into a temporary +directory it owns, runs it as `clawctl.exe`, repeats the run under a wrong +executable name to prove the alias check is real, and removes the directory +afterwards: + +```powershell +.\scripts\Test-NativeAotCli.Tests.ps1 +``` + +The driver calls the same `Program.RunAsync` that the shipped `Main` calls, so +startup diagnostics, argument routing, the error boundary, and disposal are all +covered. It must never call `Main` itself: `Main` resolves the diagnostic log +under the user's profile, so a gate built on it would append to your real +`%LOCALAPPDATA%\OpenClawGatewayMSIX` log. Add scenarios by injecting +fixture-owned collaborators through `HostStartup` — an explicit temporary +diagnostic path, in-memory writers, and Node/launch delegates that cannot start +a real process. + ## Formatting and static analysis Formatting and analyzer severity are defined by the root `.editorconfig`. The @@ -141,7 +164,9 @@ bypassable, and required CI checks remain authoritative. - Ordinary builds and tests must leave `IncludePackagingContent` unset. Packaging builds set it to `true` and supply a runtime identifier. - Treat launcher arguments as OpenClaw-owned. Do not add host-only switches, - consume `--`, rewrite arguments, or block upstream commands. + consume `--`, rewrite arguments, or block upstream commands. The + System.CommandLine tree covers `clawctl` only; the `openclaw` entrypoint must + keep forwarding its argument vector without parsing it. - Preserve direct execution from the read-only MSIX package. `clawctl setup` is a readiness check; do not add runtime extraction, copying, repair, or launcher-managed package state under the user profile. diff --git a/Directory.Packages.props b/Directory.Packages.props index 05461f4..4ea58b0 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -9,6 +9,7 @@ + diff --git a/OpenClaw.Gateway.MSIX.slnx b/OpenClaw.Gateway.MSIX.slnx index e22ddf6..d0f21df 100644 --- a/OpenClaw.Gateway.MSIX.slnx +++ b/OpenClaw.Gateway.MSIX.slnx @@ -3,6 +3,7 @@ + diff --git a/README.md b/README.md index cfbcdbc..064acd2 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,26 @@ the read-only application directory the workspace. | `clawctl --version` | Print the packaged launcher version. | Bare `clawctl`, `clawctl -h`, and `clawctl --help` print help without changing -state. +state. `clawctl setup --help` prints help for that command alone. Help, usage, +and completion come from +[System.CommandLine](https://learn.microsoft.com/en-us/dotnet/standard/commandline/). +Invalid management input is rejected with exit code `1` and a parse diagnostic +on standard error; no readiness check runs. + +Help and version requests take precedence over the rest of the command line. +`clawctl --version bogus` prints the launcher version and exits `0` rather than +reporting `bogus`, because the version request is satisfied before the +remaining arguments are validated. The version printed is always the packaged +launcher's assembly version, including when the launcher is hosted by another +process. + +Response-file expansion is disabled. A leading `@` has no meaning to `clawctl` +and is reported as an unrecognized argument rather than read from disk. + +These parser conveniences belong to `clawctl` only. `openclaw` forwards every +argument to the OpenClaw CLI verbatim, so a leading `@` or a directive-shaped +token reaches that CLI uninterpreted. + Commands such as `doctor`, `gateway`, and `uninstall` belong to the OpenClaw CLI and must be invoked through `openclaw`. diff --git a/scripts/Test-NativeAotCli.Tests.ps1 b/scripts/Test-NativeAotCli.Tests.ps1 new file mode 100644 index 0000000..e57bc7a --- /dev/null +++ b/scripts/Test-NativeAotCli.Tests.ps1 @@ -0,0 +1,159 @@ +<# +.SYNOPSIS + Verifies the launcher's startup path inside a published NativeAOT binary. + +.DESCRIPTION + clawctl parses its arguments with System.CommandLine. Two of its behaviors + cannot be proven by the xUnit suite, because that suite runs under a JIT + test host: + + - The entrypoint alias and the root command name in usage and error output + are derived from native argv[0]. Under `dotnet test` the entry process is + the test host, so the name is never `clawctl`. + - Command-line parsing, help rendering, and completion must survive + trimming and ahead-of-time compilation. A successful publish is not + evidence; the binary has to run. + + This script publishes the NativeAOT scenario driver in + `tests\OpenClaw.Launcher.AotSmoke` for win-x64, copies it to `clawctl.exe` + in an isolated directory so the native alias resolver selects the + management entrypoint, and runs it once. + + The driver calls the same `Program.RunAsync` that the shipped `Main` calls, + so startup diagnostics, argument routing, the operational error boundary, + and disposal are all covered. It never runs the production `Main` adapter, + because that adapter resolves the diagnostic log under the user's profile: + a gate that ran it would append to the developer's real + `%LOCALAPPDATA%\OpenClawGatewayMSIX` log. Every collaborator the driver + injects is fixture-owned, including an explicit temporary diagnostic path + and Node/launch delegates that cannot start a real process. + + Scenarios that require a compatible device-installed Node.js runtime are + deliberately excluded. Those stay in the xUnit suite, where the runtime is + injected, so this gate does not depend on the agent's installed Node + version. The publish output lives under a temporary directory that this + script owns and removes. + +.PARAMETER Configuration + The MSBuild configuration to publish. Defaults to Release, which matches + continuous integration. + +.EXAMPLE + .\scripts\Test-NativeAotCli.Tests.ps1 +#> +[CmdletBinding()] +param( + [string]$Configuration = 'Release' +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$repositoryRoot = Split-Path $PSScriptRoot -Parent +$project = Join-Path $repositoryRoot ( + 'tests\OpenClaw.Launcher.AotSmoke\OpenClaw.Launcher.AotSmoke.csproj') + +if (-not (Test-Path -LiteralPath $project)) { + throw "Could not find the NativeAOT scenario driver at '$project'." +} + +$publishRoot = Join-Path ([System.IO.Path]::GetTempPath()) ( + 'clawctl-aot-' + [guid]::NewGuid().ToString('n')) + +function Assert-Invocation { + param( + [Parameter(Mandatory)] + [string]$Executable, + + [Parameter(Mandatory)] + [AllowEmptyCollection()] + [string[]]$Arguments, + + [Parameter(Mandatory)] + [int]$ExpectedExitCode, + + [string[]]$ExpectedPatterns = @(), + + [Parameter(Mandatory)] + [string]$Description + ) + + $output = & $Executable @Arguments 2>&1 | Out-String + $exitCode = $LASTEXITCODE + + if ($exitCode -ne $ExpectedExitCode) { + throw ( + "$Description expected exit code $ExpectedExitCode but received " + + "$exitCode. Output:`n$output" + ) + } + + foreach ($pattern in $ExpectedPatterns) { + if ($output -notmatch $pattern) { + throw ( + "$Description expected output matching '$pattern'. " + + "Output:`n$output" + ) + } + } + + return $output +} + +try { + Write-Host "Publishing the NativeAOT scenario driver ($Configuration)..." + dotnet publish $project ` + --configuration $Configuration ` + --runtime win-x64 ` + -p:Platform=x64 ` + --self-contained ` + --output $publishRoot ` + --nologo ` + --verbosity minimal + if ($LASTEXITCODE -ne 0) { + throw "NativeAOT publish failed. Exit code: $LASTEXITCODE." + } + + $driver = Join-Path $publishRoot 'OpenClaw.Launcher.AotSmoke.exe' + if (-not (Test-Path -LiteralPath $driver)) { + throw "The NativeAOT publish did not produce '$driver'." + } + + # The management entrypoint is selected by executable name, so the copy is + # how this gate reaches clawctl. It is test infrastructure and never a + # runtime extraction step. + $clawctl = Join-Path $publishRoot 'clawctl.exe' + Copy-Item -LiteralPath $driver -Destination $clawctl -Force + + $output = Assert-Invocation ` + -Executable $clawctl ` + -Arguments @() ` + -ExpectedExitCode 0 ` + -ExpectedPatterns @('NativeAOT scenarios passed\.') ` + -Description 'NativeAOT scenario driver running as clawctl.exe' + Write-Host $output + + # A gate that passes under any executable name would not be testing the + # alias at all. Under the wrong name the driver's first scenario must fail. + $wrongName = Join-Path $publishRoot 'notclawctl.exe' + Copy-Item -LiteralPath $driver -Destination $wrongName -Force + Assert-Invocation ` + -Executable $wrongName ` + -Arguments @() ` + -ExpectedExitCode 1 ` + -ExpectedPatterns @('native alias resolves to clawctl') ` + -Description 'NativeAOT scenario driver running under the wrong name' + + Write-Host 'NativeAOT clawctl checks completed successfully.' +} +finally { + if (Test-Path -LiteralPath $publishRoot) { + Remove-Item -LiteralPath $publishRoot -Recurse -Force + } +} + +# The negative check deliberately runs a native command that exits non-zero, and +# $LASTEXITCODE is still set when the script ends. GitHub Actions exits a pwsh +# step with that value, so a fully successful run would otherwise report +# failure. Any assertion failure throws instead and never reaches this line. +exit 0 diff --git a/src/OpenClaw.Launcher/ClawCtlCommand.cs b/src/OpenClaw.Launcher/ClawCtlCommand.cs deleted file mode 100644 index 60f00c4..0000000 --- a/src/OpenClaw.Launcher/ClawCtlCommand.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace OpenClaw.Launcher; - -internal enum ClawCtlCommand -{ - Help, - Version, - Setup -} - -internal sealed record ClawCtlCommandParseResult( - ClawCtlCommand Command, - string? Error = null); - -internal static class ClawCtlCommandParser -{ - public static ClawCtlCommandParseResult Parse(IReadOnlyList args) - { - if (args.Count == 0 || IsSingle(args, "--help") || IsSingle(args, "-h")) - { - return new ClawCtlCommandParseResult(ClawCtlCommand.Help); - } - - if (IsSingle(args, "--version")) - { - return new ClawCtlCommandParseResult(ClawCtlCommand.Version); - } - - if (IsSingle(args, "setup")) - { - return new ClawCtlCommandParseResult(ClawCtlCommand.Setup); - } - - return new ClawCtlCommandParseResult( - ClawCtlCommand.Help, - $"Unknown command or option: {string.Join(' ', args)}"); - } - - private static bool IsSingle(IReadOnlyList args, string value) => - args.Count == 1 && - string.Equals(args[0], value, StringComparison.Ordinal); -} diff --git a/src/OpenClaw.Launcher/ClawCtlCommandLine.cs b/src/OpenClaw.Launcher/ClawCtlCommandLine.cs new file mode 100644 index 0000000..de8b20a --- /dev/null +++ b/src/OpenClaw.Launcher/ClawCtlCommandLine.cs @@ -0,0 +1,98 @@ +using System.CommandLine; +using System.CommandLine.Help; +using System.CommandLine.Invocation; + +namespace OpenClaw.Launcher; + +// The clawctl command tree. Only the package-readiness surface belongs here: +// doctor, gateway, uninstall, and every other OpenClaw command is owned by the +// bundled CLI and reached through `openclaw`, which forwards its arguments +// without parsing them. +internal static class ClawCtlCommandLine +{ + public const string SetupCommandName = "setup"; + + // Response-file expansion is off. A leading `@` means nothing to clawctl, + // so it is reported as an unrecognized argument instead of silently reading + // a file from disk. `openclaw` already forwards such a token to the + // OpenClaw CLI untouched, and leaving the two entrypoints consistent + // matters more than the convenience. Completion stays enabled. + public static ParserConfiguration CreateParserConfiguration() => new() + { + ResponseFileTokenReplacer = null + }; + + // Node guidance is part of help rather than only a launch-time failure so + // that a user can discover the prerequisite before running anything. + public static string RootDescription => + "Verify that this device can run the packaged OpenClaw application." + + Environment.NewLine + + Environment.NewLine + + $"Prerequisite: install Node.js {NodeRuntimeResolver.SupportedVersions}." + + Environment.NewLine + + $" {NodeRuntimeResolver.InstallCommand}" + + Environment.NewLine + + Environment.NewLine + + "Run `openclaw ` to invoke the OpenClaw CLI."; + + public static string SetupDescription => + "Check for a compatible Node.js runtime and confirm the packaged " + + "OpenClaw application is present. Reads only; changes nothing." + + Environment.NewLine + + $"Requires Node.js {NodeRuntimeResolver.SupportedVersions}."; + + // runSetup stays a delegate so the command tree owns parsing and help while + // Program keeps the readiness operation and its test seams. + public static RootCommand Create(Func> runSetup) + { + Command setup = new(SetupCommandName, SetupDescription); + setup.SetAction((_, cancellationToken) => runSetup(cancellationToken)); + + RootCommand root = new(RootDescription) + { + setup + }; + + // Bare `clawctl` is a discovery request, not a usage error, so the root + // prints help and succeeds instead of reporting a missing command. + root.SetAction((parseResult, _) => Task.FromResult(WriteHelp(parseResult))); + UseLauncherVersion(root); + + return root; + } + + private static int WriteHelp(ParseResult parseResult) + { + HelpAction help = new(); + return help.Invoke(parseResult); + } + + // The built-in version action reports the entry assembly, which is the test + // or scenario host rather than the launcher. Report the launcher assembly so + // the value identifies the shipped package binary in every host. + private static void UseLauncherVersion(RootCommand root) + { + foreach (Option option in root.Options) + { + if (option is VersionOption versionOption) + { + versionOption.Action = new LauncherVersionAction(); + } + } + } + + private sealed class LauncherVersionAction : SynchronousCommandLineAction + { + public override bool ClearsParseErrors => true; + + public override int Invoke(ParseResult parseResult) + { + string version = typeof(LauncherVersionAction).Assembly + .GetName() + .Version? + .ToString() ?? "unknown"; + parseResult.InvocationConfiguration.Output.WriteLine(version); + return 0; + } + } +} diff --git a/src/OpenClaw.Launcher/ClawCtlConsole.cs b/src/OpenClaw.Launcher/ClawCtlConsole.cs index b4bd295..648c6e4 100644 --- a/src/OpenClaw.Launcher/ClawCtlConsole.cs +++ b/src/OpenClaw.Launcher/ClawCtlConsole.cs @@ -2,35 +2,6 @@ namespace OpenClaw.Launcher; internal static class ClawCtlConsole { - public static void WriteHelp(TextWriter output) - { - output.WriteLine("clawctl - OpenClaw package readiness"); - output.WriteLine(); - WriteUsage(output); - output.WriteLine(); - output.WriteLine("Commands:"); - output.WriteLine( - " setup Verify Node.js and the packaged OpenClaw application."); - output.WriteLine(); - output.WriteLine("Options:"); - output.WriteLine(" -h, --help Show this help."); - output.WriteLine(" --version Print the packaged launcher version."); - output.WriteLine(); - WriteNodePrerequisite(output); - output.WriteLine(); - output.WriteLine("Run `openclaw ` to invoke the OpenClaw CLI."); - } - - public static void WriteUsage(TextWriter output) => - output.WriteLine("Usage: clawctl [setup | --help | --version]"); - - public static void WriteNodePrerequisite(TextWriter output) - { - output.WriteLine( - $"Prerequisite: install Node.js {NodeRuntimeResolver.SupportedVersions}."); - output.WriteLine($" {NodeRuntimeResolver.InstallCommand}"); - } - internal static void WriteNodeRuntimeSummary( TextWriter output, NodeRuntime runtime) => diff --git a/src/OpenClaw.Launcher/HostStartup.cs b/src/OpenClaw.Launcher/HostStartup.cs new file mode 100644 index 0000000..050f1d7 --- /dev/null +++ b/src/OpenClaw.Launcher/HostStartup.cs @@ -0,0 +1,34 @@ +namespace OpenClaw.Launcher; + +// The collaborators the host startup path needs, so that startup itself can be +// exercised outside the production process. Main supplies the real ones; the +// NativeAOT scenario driver and the xUnit suite supply fixture-owned storage +// and dependencies that cannot reach the user's profile or start a real child +// process. +internal sealed class HostStartup +{ + public required HostEntrypoint Entrypoint { get; init; } + + // Returns a log the caller owns. Production resolves the packaged + // LocalState or %LOCALAPPDATA% location; tests pass an explicit path. + public required Func CreateDiagnostics { get; init; } + + public required string BaseDirectory { get; init; } + + public required TextWriter Output { get; init; } + + public required TextWriter Error { get; init; } + + public Func>? ResolveNode { get; init; } + + public Program.LaunchOpenClawAsync? LaunchOpenClaw { get; init; } + + public static HostStartup CreateProduction() => new() + { + Entrypoint = HostEntrypointResolver.Resolve(), + CreateDiagnostics = HostDiagnosticLog.Create, + BaseDirectory = AppContext.BaseDirectory, + Output = Console.Out, + Error = Console.Error + }; +} diff --git a/src/OpenClaw.Launcher/OpenClaw.Launcher.csproj b/src/OpenClaw.Launcher/OpenClaw.Launcher.csproj index c5aa467..b37cf8e 100644 --- a/src/OpenClaw.Launcher/OpenClaw.Launcher.csproj +++ b/src/OpenClaw.Launcher/OpenClaw.Launcher.csproj @@ -32,6 +32,10 @@ true + + + + @@ -52,6 +56,7 @@ + Main(string[] args) => + await RunAsync(args, HostStartup.CreateProduction()).ConfigureAwait(false); + + // The whole startup path lives here rather than in Main so that tests can + // drive it with fixture-owned diagnostics and writers. Main is only the + // production adapter that supplies the real collaborators. [SuppressMessage( "Design", "CA1031:Do not catch general exception types", Justification = - "Main is the process last-chance handler. Every narrower catch in " + + "This is the process last-chance handler. Every narrower catch in " + "this assembly uses an exception filter; this one deliberately does " + "not, because narrowing it would replace the diagnostic log entry, " + "the user-facing error message, and the deterministic exit code 1 " + "with an unhandled-exception crash.")] - public static async Task Main(string[] args) + internal static async Task RunAsync(string[] args, HostStartup startup) { - HostEntrypoint entrypoint = HostEntrypointResolver.Resolve(); - string commandName = entrypoint == HostEntrypoint.Control + string commandName = startup.Entrypoint == HostEntrypoint.Control ? HostEntrypointResolver.ControlCommandName : HostEntrypointResolver.AgentCommandName; HostDiagnosticLog? diagnostics = null; @@ -28,7 +33,7 @@ void WriteConsoleError(string message) { try { - Console.Error.WriteLine(message); + startup.Error.WriteLine(message); } catch (Exception exception) when ( exception is IOException or ObjectDisposedException) @@ -44,7 +49,7 @@ void WriteConsoleError(string message) try { - diagnostics = HostDiagnosticLog.Create(); + diagnostics = startup.CreateDiagnostics(); } catch (Exception exception) when ( exception is IOException or @@ -95,15 +100,20 @@ PlatformNotSupportedException or try { WriteDiagnostic($"Host started through the {commandName} entrypoint."); - HostOptions options = HostOptions.Parse(args); - return entrypoint == HostEntrypoint.Control + HostOptions options = HostOptions.Parse(args, startup.BaseDirectory); + return startup.Entrypoint == HostEntrypoint.Control ? await RunControlAsync( options, args, WriteDiagnostic, - WriteConsoleError, - Console.Out).ConfigureAwait(false) - : await RunAgentAsync(options, WriteDiagnostic) + startup.Output, + startup.Error, + startup.ResolveNode).ConfigureAwait(false) + : await RunAgentAsync( + options, + WriteDiagnostic, + startup.ResolveNode ?? NodeRuntimeResolver.ResolveAsync, + startup.LaunchOpenClaw ?? GatewayLauncher.RunAsync) .ConfigureAwait(false); } catch (Exception exception) @@ -158,52 +168,62 @@ internal static async Task RunAgentAsync( log).ConfigureAwait(false); } - // output is a required parameter (not a Console.Out default) so tests + // output and error are required parameters (not Console defaults) so tests // can capture clawctl output without mutating global console state, // which would be unsafe across parallel test runs. internal static async Task RunControlAsync( HostOptions options, IReadOnlyList args, Action log, - Action writeError, TextWriter output, + TextWriter error, Func>? resolveNode = null) { - ClawCtlCommandParseResult parsed = ClawCtlCommandParser.Parse(args); - if (parsed.Error is not null) - { - writeError($"clawctl: {parsed.Error}"); - ClawCtlConsole.WriteUsage(Console.Error); - return 2; - } + RootCommand command = ClawCtlCommandLine.Create( + cancellationToken => RunSetupAsync( + options, + log, + output, + resolveNode, + cancellationToken)); - switch (parsed.Command) + InvocationConfiguration configuration = new() { - case ClawCtlCommand.Help: - ClawCtlConsole.WriteHelp(output); - return 0; - case ClawCtlCommand.Version: - await output.WriteLineAsync( - Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? - "unknown").ConfigureAwait(false); - return 0; - case ClawCtlCommand.Setup: - { - NodeRuntime nodeRuntime = await ( - resolveNode ?? NodeRuntimeResolver.ResolveAsync)( - CancellationToken.None).ConfigureAwait(false); - ClawCtlConsole.WriteNodeRuntimeSummary(output, nodeRuntime); - string applicationDirectory = - GetPackagedApplicationDirectory(options); - log("Confirmed the packaged OpenClaw application is present."); - ClawCtlConsole.WriteReadinessSummary( - output, - applicationDirectory); - return 0; - } - default: - throw new InvalidOperationException("Unknown clawctl command."); - } + Output = output, + Error = error, + + // Operational failures stay the host's responsibility. The default + // handler would print its own message and return its own exit code, + // losing the diagnostic log entry and the log path Main reports. + EnableDefaultExceptionHandler = false, + + // Node lifetime is owned by the job object in GatewayLauncher. The + // library's termination timeout would add a second, conflicting + // forced-exit policy and process-wide signal handlers. + ProcessTerminationTimeout = null + }; + + return await command + .Parse(args, ClawCtlCommandLine.CreateParserConfiguration()) + .InvokeAsync(configuration) + .ConfigureAwait(false); + } + + private static async Task RunSetupAsync( + HostOptions options, + Action log, + TextWriter output, + Func>? resolveNode, + CancellationToken cancellationToken) + { + NodeRuntime nodeRuntime = await ( + resolveNode ?? NodeRuntimeResolver.ResolveAsync)( + cancellationToken).ConfigureAwait(false); + ClawCtlConsole.WriteNodeRuntimeSummary(output, nodeRuntime); + string applicationDirectory = GetPackagedApplicationDirectory(options); + log("Confirmed the packaged OpenClaw application is present."); + ClawCtlConsole.WriteReadinessSummary(output, applicationDirectory); + return 0; } internal delegate Task LaunchOpenClawAsync( diff --git a/tests/OpenClaw.Launcher.AotSmoke/OpenClaw.Launcher.AotSmoke.csproj b/tests/OpenClaw.Launcher.AotSmoke/OpenClaw.Launcher.AotSmoke.csproj new file mode 100644 index 0000000..aef6976 --- /dev/null +++ b/tests/OpenClaw.Launcher.AotSmoke/OpenClaw.Launcher.AotSmoke.csproj @@ -0,0 +1,33 @@ + + + + Exe + net10.0-windows10.0.19041.0 + 10.0.19041.0 + enable + enable + all + OpenClaw.Launcher.AotSmoke + OpenClaw.Launcher.AotSmoke + x64;arm64 + win-x64;win-arm64 + false + true + true + false + + + 9.9.9.9 + 9.9.9.9 + 9.9.9.9 + + + + + + + diff --git a/tests/OpenClaw.Launcher.AotSmoke/SmokeProgram.cs b/tests/OpenClaw.Launcher.AotSmoke/SmokeProgram.cs new file mode 100644 index 0000000..898e0fb --- /dev/null +++ b/tests/OpenClaw.Launcher.AotSmoke/SmokeProgram.cs @@ -0,0 +1,408 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using LauncherProgram = OpenClaw.Launcher.Program; + +namespace OpenClaw.Launcher.AotSmoke; + +// A NativeAOT scenario driver for the launcher's startup path. +// +// Two things cannot be proven by the xUnit suite, because it runs under a JIT +// test host: the alias and root-command names that come from native argv[0], +// and that command-line parsing, help rendering, and completion survive +// trimming and ahead-of-time compilation. +// +// This driver runs the same Program.RunAsync that Main runs, so startup +// diagnostics, argument routing, the operational error boundary, and disposal +// are all exercised. Every collaborator it injects is fixture-owned: the +// diagnostic log is created at an explicit path the driver owns, the writers are +// in-memory, and the Node and launch delegates cannot start a real process. +// Nothing here reads or writes the user's profile. +internal static class SmokeProgram +{ + [SuppressMessage( + "Design", + "CA1031:Do not catch general exception types", + Justification = + "This is the scenario harness boundary. A scenario that fails with " + + "any exception type must be reported as a failed check with a " + + "nonzero exit code, not crash the driver and hide the remaining " + + "scenarios.")] + private static async Task Main() + { + (string Name, Func RunAsync)[] scenarios = + [ + ("native alias resolves to clawctl", NativeAliasResolvesToControlAsync), + ("bare clawctl prints help", BareInvocationPrintsHelpAsync), + ("--help prints help", HelpOptionPrintsHelpAsync), + ("setup --help prints command help", SetupHelpPrintsCommandHelpAsync), + ("--version reports the launcher", VersionReportsLauncherAssemblyAsync), + ("--version wins over trailing arguments", VersionWinsOverTrailingAsync), + ("unknown command fails", UnknownCommandFailsAsync), + ("response-file token is not expanded", ResponseFileTokenIsNotExpandedAsync), + ("completion directive suggests commands", CompletionDirectiveSuggestsAsync), + ("setup reports readiness and logs", SetupReportsReadinessAsync), + ("missing application reports diagnostics", MissingApplicationReportsAsync), + ("openclaw forwards arguments verbatim", AgentForwardsArgumentsAsync) + ]; + + int failures = 0; + + // Console writes go through non-async helpers so this harness reports + // results the same way in the middle of an async scenario loop. + static void WriteLine(string message) => Console.Out.WriteLine(message); + static void WriteError(string message) => Console.Error.WriteLine(message); + + foreach ((string name, Func runAsync) in scenarios) + { + try + { + await runAsync().ConfigureAwait(false); + WriteLine($" ok {name}"); + } + catch (Exception exception) + { + failures++; + WriteError($" FAIL {name}"); + WriteError($" {exception.Message}"); + } + } + + if (failures != 0) + { + WriteError($"{failures} NativeAOT scenario(s) failed."); + return 1; + } + + WriteLine($"{scenarios.Length} NativeAOT scenarios passed."); + return 0; + } + + // The management entrypoint is selected from the native command line. This + // is the check the JIT suite cannot make, because there the first argument + // is the test host rather than clawctl. + private static Task NativeAliasResolvesToControlAsync() + { + HostEntrypoint resolved = HostEntrypointResolver.Resolve(); + Assert( + resolved == HostEntrypoint.Control, + $"Expected the native alias to resolve to Control but got {resolved}. " + + "This driver must run as clawctl.exe."); + return Task.CompletedTask; + } + + private static async Task BareInvocationPrintsHelpAsync() + { + using Fixture fixture = Fixture.CreateWithoutApplication(); + + int exitCode = await fixture.RunAsync([]).ConfigureAwait(false); + + AssertExitCode(0, exitCode, fixture); + + // The root command name comes from native argv[0]; a JIT host would + // render the test runner's name here instead. + AssertContains(fixture.Output.ToString(), "clawctl", fixture); + AssertContains(fixture.Output.ToString(), "setup", fixture); + AssertContains(fixture.Output.ToString(), "Node.js", fixture); + fixture.AssertNodeWasNotResolved(); + fixture.AssertLogRecordsStartupAndExit(); + } + + private static async Task HelpOptionPrintsHelpAsync() + { + using Fixture fixture = Fixture.CreateWithoutApplication(); + + int exitCode = await fixture.RunAsync(["--help"]).ConfigureAwait(false); + + AssertExitCode(0, exitCode, fixture); + AssertContains(fixture.Output.ToString(), "setup", fixture); + AssertContains(fixture.Output.ToString(), "--version", fixture); + fixture.AssertNodeWasNotResolved(); + } + + private static async Task SetupHelpPrintsCommandHelpAsync() + { + using Fixture fixture = Fixture.CreateWithoutApplication(); + + int exitCode = await fixture.RunAsync(["setup", "--help"]).ConfigureAwait(false); + + AssertExitCode(0, exitCode, fixture); + AssertContains(fixture.Output.ToString(), "clawctl setup", fixture); + fixture.AssertNodeWasNotResolved(); + } + + // This driver's assembly version is 9.9.9.9. The library's built-in action + // reports the entry assembly, so if the custom action were ever dropped + // this scenario would print 9.9.9.9 instead of the launcher's version. + private static async Task VersionReportsLauncherAssemblyAsync() + { + using Fixture fixture = Fixture.CreateWithoutApplication(); + string launcherVersion = LauncherVersion(); + string driverVersion = DriverVersion(); + + int exitCode = await fixture.RunAsync(["--version"]).ConfigureAwait(false); + + AssertExitCode(0, exitCode, fixture); + string reported = fixture.Output.ToString().Trim(); + Assert( + string.Equals(reported, launcherVersion, StringComparison.Ordinal), + $"Expected the launcher version '{launcherVersion}' but got '{reported}'."); + Assert( + !string.Equals(reported, driverVersion, StringComparison.Ordinal), + $"Reported this driver's version '{driverVersion}' instead of the launcher's."); + } + + private static async Task VersionWinsOverTrailingAsync() + { + using Fixture fixture = Fixture.CreateWithoutApplication(); + + int exitCode = await fixture.RunAsync(["--version", "bogus"]).ConfigureAwait(false); + + AssertExitCode(0, exitCode, fixture); + Assert( + string.Equals( + fixture.Output.ToString().Trim(), + LauncherVersion(), + StringComparison.Ordinal), + "Expected the launcher version with a trailing argument present."); + } + + private static async Task UnknownCommandFailsAsync() + { + using Fixture fixture = Fixture.CreateWithoutApplication(); + + int exitCode = await fixture.RunAsync(["bogus"]).ConfigureAwait(false); + + AssertExitCode(1, exitCode, fixture); + AssertContains(fixture.Error.ToString(), "bogus", fixture); + fixture.AssertNodeWasNotResolved(); + } + + // Response-file expansion is disabled, so a readable file behind an `@` + // token must still be rejected as an unrecognized argument. + private static async Task ResponseFileTokenIsNotExpandedAsync() + { + using Fixture fixture = Fixture.CreateWithoutApplication(); + string responseFile = Path.Combine(fixture.Root, "help.rsp"); + await File.WriteAllTextAsync(responseFile, "--help").ConfigureAwait(false); + + int exitCode = await fixture.RunAsync([$"@{responseFile}"]).ConfigureAwait(false); + + AssertExitCode(1, exitCode, fixture); + fixture.AssertNodeWasNotResolved(); + } + + private static async Task CompletionDirectiveSuggestsAsync() + { + using Fixture fixture = Fixture.CreateWithoutApplication(); + + int exitCode = await fixture.RunAsync(["[suggest:2]", "se"]).ConfigureAwait(false); + + AssertExitCode(0, exitCode, fixture); + AssertContains(fixture.Output.ToString(), "setup", fixture); + fixture.AssertNodeWasNotResolved(); + } + + private static async Task SetupReportsReadinessAsync() + { + using Fixture fixture = await Fixture.CreateWithApplicationAsync().ConfigureAwait(false); + + int exitCode = await fixture.RunAsync(["setup"]).ConfigureAwait(false); + + AssertExitCode(0, exitCode, fixture); + AssertContains(fixture.Output.ToString(), fixture.ApplicationDirectory, fixture); + AssertContains(fixture.Output.ToString(), Fixture.NodePath, fixture); + fixture.AssertLogRecordsStartupAndExit(); + Assert( + File.Exists(fixture.EntryPoint), + "The readiness check removed or replaced the fixture entry point."); + } + + // The operational error boundary is part of startup, not of the parser. + // It must still name the diagnostic log, which here is the fixture's. + private static async Task MissingApplicationReportsAsync() + { + using Fixture fixture = Fixture.CreateWithoutApplication(); + + int exitCode = await fixture.RunAsync(["setup"]).ConfigureAwait(false); + + AssertExitCode(1, exitCode, fixture); + AssertContains(fixture.Error.ToString(), fixture.LogPath, fixture); + Assert( + !fixture.Output.ToString().Contains("package is ready", StringComparison.Ordinal), + "A failed readiness check still reported success."); + fixture.AssertLogRecordsStartupAndExit(); + } + + // The agent entrypoint must never consult System.CommandLine. These tokens + // are all meaningful to the clawctl parser and must survive untouched. + private static async Task AgentForwardsArgumentsAsync() + { + string[] arguments = + ["--help", "--version", "--", "@response.rsp", "[suggest:1]", "", "a b"]; + using Fixture fixture = await Fixture + .CreateWithApplicationAsync(HostEntrypoint.Agent) + .ConfigureAwait(false); + + int exitCode = await fixture.RunAsync(arguments).ConfigureAwait(false); + + AssertExitCode(23, exitCode, fixture); + string[]? forwarded = fixture.ForwardedArguments; + Assert(forwarded is not null, "The agent path never reached the launch delegate."); + Assert( + forwarded!.Length == arguments.Length, + $"Expected {arguments.Length} forwarded arguments but got {forwarded.Length}."); + for (int index = 0; index < arguments.Length; index++) + { + Assert( + string.Equals(forwarded[index], arguments[index], StringComparison.Ordinal), + $"Argument {index} was rewritten from '{arguments[index]}' to " + + $"'{forwarded[index]}'."); + } + } + + private static string LauncherVersion() => + typeof(HostStartup).Assembly.GetName().Version?.ToString() ?? "unknown"; + + private static string DriverVersion() => + typeof(SmokeProgram).Assembly.GetName().Version?.ToString() ?? "unknown"; + + private static void AssertExitCode(int expected, int actual, Fixture fixture) + { + Assert( + expected == actual, + $"Expected exit code {expected} but got {actual}." + + $"{Environment.NewLine}stdout: {fixture.Output}" + + $"{Environment.NewLine}stderr: {fixture.Error}"); + } + + private static void AssertContains(string haystack, string needle, Fixture fixture) + { + Assert( + haystack.Contains(needle, StringComparison.Ordinal), + $"Expected to find '{needle}'." + + $"{Environment.NewLine}stdout: {fixture.Output}" + + $"{Environment.NewLine}stderr: {fixture.Error}"); + } + + private static void Assert(bool condition, string message) + { + if (!condition) + { + throw new InvalidOperationException(message); + } + } + + private sealed class Fixture : IDisposable + { + public const string NodePath = @"C:\fixture\node.exe"; + + private readonly HostEntrypoint _entrypoint; + private bool _nodeResolved; + + private Fixture(string root, HostEntrypoint entrypoint) + { + Root = root; + _entrypoint = entrypoint; + LogPath = Path.Combine(root, "diagnostics", "openclaw.log"); + ApplicationDirectory = Path.Combine(root, "app"); + EntryPoint = Path.Combine(ApplicationDirectory, "openclaw.mjs"); + } + + public string Root { get; } + + public string LogPath { get; } + + public string ApplicationDirectory { get; } + + public string EntryPoint { get; } + + public StringWriter Output { get; } = new(); + + public StringWriter Error { get; } = new(); + + public string[]? ForwardedArguments { get; private set; } + + public static Fixture CreateWithoutApplication( + HostEntrypoint entrypoint = HostEntrypoint.Control) => + new(CreateRoot(), entrypoint); + + public static async Task CreateWithApplicationAsync( + HostEntrypoint entrypoint = HostEntrypoint.Control) + { + Fixture fixture = new(CreateRoot(), entrypoint); + Directory.CreateDirectory(fixture.ApplicationDirectory); + await File.WriteAllTextAsync(fixture.EntryPoint, "console.log('fixture');") + .ConfigureAwait(false); + return fixture; + } + + public async Task RunAsync(string[] args) + { + HostStartup startup = new() + { + Entrypoint = _entrypoint, + CreateDiagnostics = () => HostDiagnosticLog.Create(LogPath), + BaseDirectory = Root, + Output = Output, + Error = Error, + ResolveNode = _ => + { + _nodeResolved = true; + return Task.FromResult( + new NodeRuntime( + NodePath, + new Version(24, 15, 0), + RuntimeInformation.ProcessArchitecture)); + }, + LaunchOpenClaw = (_, _, forwarded, _, _) => + { + ForwardedArguments = [.. forwarded]; + return Task.FromResult(23); + } + }; + + return await LauncherProgram.RunAsync(args, startup).ConfigureAwait(false); + } + + public void AssertNodeWasNotResolved() => + Assert( + !_nodeResolved, + "Node resolution ran for an invocation that must not need a runtime."); + + public void AssertLogRecordsStartupAndExit() + { + Assert(File.Exists(LogPath), $"No diagnostic log was written at '{LogPath}'."); + string log = File.ReadAllText(LogPath); + Assert( + log.Contains("Host started", StringComparison.Ordinal), + "The diagnostic log has no startup record."); + Assert( + log.Contains("Host exiting", StringComparison.Ordinal), + "The diagnostic log has no exit record."); + } + + public void Dispose() + { + Output.Dispose(); + Error.Dispose(); + if (Directory.Exists(Root)) + { + Directory.Delete(Root, recursive: true); + } + } + + private static string CreateRoot() + { + // Scenario state lives under the driver's own output directory + // rather than %TEMP%. The gate script publishes into a directory it + // owns and deletes, so this stays isolated without reading an + // environment variable a caller could point somewhere else. + string path = Path.Combine( + AppContext.BaseDirectory, + "scenarios", + $"clawctl-aot-scenario-{Guid.NewGuid():N}"); + Directory.CreateDirectory(path); + return path; + } + } +} diff --git a/tests/OpenClaw.Launcher.Tests/ClawCtlCommandLineTests.cs b/tests/OpenClaw.Launcher.Tests/ClawCtlCommandLineTests.cs new file mode 100644 index 0000000..8592ad6 --- /dev/null +++ b/tests/OpenClaw.Launcher.Tests/ClawCtlCommandLineTests.cs @@ -0,0 +1,158 @@ +using System.CommandLine; +using System.Reflection; + +namespace OpenClaw.Launcher.Tests; + +public sealed class ClawCtlCommandLineTests +{ + // Any invocation that reaches this resolver has started the readiness + // operation, which help, version, and rejected input must never do. + private static Task FailIfSetupRuns(CancellationToken _) => + throw new InvalidOperationException( + "Setup ran for an invocation that should not have started it."); + + private static async Task<(int ExitCode, string Output, string Error)> RunAsync( + params string[] args) + { + using var output = new StringWriter(); + using var error = new StringWriter(); + + int exitCode = await Program.RunControlAsync( + new HostOptions(null, []), + args, + _ => { }, + output, + error, + FailIfSetupRuns).ConfigureAwait(false); + + return (exitCode, output.ToString(), error.ToString()); + } + + // Generated help wraps to the terminal width, so assert on content rather + // than layout. Pinning a width would make the tests pass while real users + // saw different output. + private static string Normalize(string text) => + string.Join( + ' ', + text.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries)); + + [Theory] + [InlineData()] + [InlineData("--help")] + [InlineData("-h")] + [InlineData("-?")] + public async Task DiscoveryInputPrintsHelpAndSucceeds(params string[] args) + { + (int exitCode, string output, string error) = await RunAsync(args).ConfigureAwait(true); + + Assert.Equal(0, exitCode); + Assert.Empty(error); + Assert.Contains("setup", Normalize(output), StringComparison.Ordinal); + } + + [Fact] + public async Task HelpDescribesReadinessAndTheNodePrerequisite() + { + (_, string output, _) = await RunAsync("--help").ConfigureAwait(true); + string help = Normalize(output); + + Assert.Contains("setup", help, StringComparison.Ordinal); + Assert.Contains("--version", help, StringComparison.Ordinal); + Assert.Contains( + Normalize(NodeRuntimeResolver.SupportedVersions), + help, + StringComparison.Ordinal); + Assert.Contains( + Normalize(NodeRuntimeResolver.InstallCommand), + help, + StringComparison.Ordinal); + Assert.Contains("openclaw ", help, StringComparison.Ordinal); + } + + // clawctl owns package readiness only. If an upstream command name ever + // becomes a clawctl subcommand, the host has started shadowing the bundled + // CLI instead of forwarding through `openclaw`. + [Fact] + public void OnlyTheReadinessCommandIsExposed() + { + RootCommand root = ClawCtlCommandLine.Create(_ => Task.FromResult(0)); + + Assert.Equal( + [ClawCtlCommandLine.SetupCommandName], + root.Subcommands.Select(command => command.Name)); + } + + [Fact] + public async Task SetupHelpDescribesTheReadOnlyCheckWithoutRunningIt() + { + (int exitCode, string output, string error) = + await RunAsync("setup", "--help").ConfigureAwait(true); + string help = Normalize(output); + + Assert.Equal(0, exitCode); + Assert.Empty(error); + Assert.Contains("setup", help, StringComparison.Ordinal); + Assert.Contains( + Normalize(ClawCtlCommandLine.SetupDescription), + help, + StringComparison.Ordinal); + } + + // The built-in version action reports the entry assembly, which under a test + // host or scenario runner is not the launcher. Assert the launcher's own + // version so that substitution is caught. + [Fact] + public async Task VersionReportsTheLauncherAssemblyVersion() + { + string expected = typeof(Program).Assembly.GetName().Version?.ToString() + ?? "unknown"; + + (int exitCode, string output, string error) = await RunAsync("--version").ConfigureAwait(true); + + Assert.Equal(0, exitCode); + Assert.Empty(error); + Assert.Equal(expected, output.Trim()); + Assert.NotEqual( + Assembly.GetEntryAssembly()?.GetName().Version?.ToString(), + output.Trim()); + } + + // The old parser rejected `--version` combined with anything else. The + // library's version action clears parse errors instead, so trailing + // garbage is ignored. This is a deliberate consequence of adopting the + // standard UX, recorded here so the change is visible rather than + // discovered by a user. + [Fact] + public async Task VersionWinsOverTrailingArguments() + { + (int exitCode, string output, _) = + await RunAsync("--version", "bogus").ConfigureAwait(true); + + Assert.Equal(0, exitCode); + Assert.Equal( + typeof(Program).Assembly.GetName().Version?.ToString(), + output.Trim()); + } + + [Theory] + [InlineData("bogus")] + [InlineData("--bogus")] + [InlineData("prepare")] + [InlineData("verify")] + [InlineData("repair")] + [InlineData("update-package")] + [InlineData("gateway-service")] + [InlineData("setup", "extra")] + [InlineData("setup", "--bogus")] + public async Task RejectedInputFailsWithoutStartingSetup(params string[] args) + { + (int exitCode, string output, string error) = await RunAsync(args).ConfigureAwait(true); + + Assert.Equal(1, exitCode); + Assert.NotEmpty(error); + Assert.DoesNotContain( + "package is ready", + output, + StringComparison.Ordinal); + } +} diff --git a/tests/OpenClaw.Launcher.Tests/ClawCtlCommandTests.cs b/tests/OpenClaw.Launcher.Tests/ClawCtlCommandTests.cs deleted file mode 100644 index a2abc33..0000000 --- a/tests/OpenClaw.Launcher.Tests/ClawCtlCommandTests.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace OpenClaw.Launcher.Tests; - -public sealed class ClawCtlCommandTests -{ - [Fact] - public void ParseReturnsHelpWhenNoArgumentsAreProvided() - { - ClawCtlCommandParseResult result = ClawCtlCommandParser.Parse([]); - - Assert.Null(result.Error); - Assert.Equal(ClawCtlCommand.Help, result.Command); - } - - [Fact] - public void ParseReturnsSetupForTheSetupCommand() - { - ClawCtlCommandParseResult result = ClawCtlCommandParser.Parse(["setup"]); - - Assert.Null(result.Error); - Assert.Equal(ClawCtlCommand.Setup, result.Command); - } - - [Fact] - public void ParseReturnsVersionForTheVersionOption() - { - ClawCtlCommandParseResult result = - ClawCtlCommandParser.Parse(["--version"]); - - Assert.Null(result.Error); - Assert.Equal(ClawCtlCommand.Version, result.Command); - } - - [Theory] - [InlineData("prepare")] - [InlineData("doctor")] - [InlineData("gateway", "status")] - [InlineData("update-package")] - [InlineData("verify")] - [InlineData("repair")] - [InlineData("setup", "--force")] - public void ParseRejectsCommandsOutsideThePublicManagementSurface( - params string[] args) - { - ClawCtlCommandParseResult result = ClawCtlCommandParser.Parse(args); - - Assert.NotNull(result.Error); - } -} diff --git a/tests/OpenClaw.Launcher.Tests/ClawCtlConsoleTests.cs b/tests/OpenClaw.Launcher.Tests/ClawCtlConsoleTests.cs index 2076d9c..1e0745a 100644 --- a/tests/OpenClaw.Launcher.Tests/ClawCtlConsoleTests.cs +++ b/tests/OpenClaw.Launcher.Tests/ClawCtlConsoleTests.cs @@ -4,41 +4,6 @@ public sealed class ClawCtlConsoleTests : IDisposable { private readonly string _testDirectory = TestDirectory.Create(); - [Fact] - public void WriteHelpListsOnlyThePublicCommands() - { - using var output = new StringWriter(); - - ClawCtlConsole.WriteHelp(output); - - string help = output.ToString(); - Assert.Contains("setup", help, StringComparison.Ordinal); - Assert.Contains("readiness", help, StringComparison.OrdinalIgnoreCase); - Assert.Contains("--version", help, StringComparison.Ordinal); - Assert.DoesNotContain( - "prepare", - help, - StringComparison.OrdinalIgnoreCase); - Assert.DoesNotContain( - $"{Environment.NewLine} verify", - help, - StringComparison.OrdinalIgnoreCase); - Assert.DoesNotContain( - $"{Environment.NewLine} repair", - help, - StringComparison.OrdinalIgnoreCase); - Assert.Contains( - NodeRuntimeResolver.SupportedVersions, - help, - StringComparison.Ordinal); - Assert.Contains( - NodeRuntimeResolver.InstallCommand, - help, - StringComparison.Ordinal); - Assert.DoesNotContain("update-package", help, StringComparison.Ordinal); - Assert.DoesNotContain("gateway-service", help, StringComparison.Ordinal); - } - [Fact] public void WriteReadinessSummaryDescribesPackagedApplication() { diff --git a/tests/OpenClaw.Launcher.Tests/ClawCtlParserDefaultsTests.cs b/tests/OpenClaw.Launcher.Tests/ClawCtlParserDefaultsTests.cs new file mode 100644 index 0000000..2b4cbc4 --- /dev/null +++ b/tests/OpenClaw.Launcher.Tests/ClawCtlParserDefaultsTests.cs @@ -0,0 +1,104 @@ +using System.CommandLine; + +namespace OpenClaw.Launcher.Tests; + +// clawctl keeps System.CommandLine's completion support but deliberately +// disables response-file expansion. These cases exist so a later "restore the +// library defaults" change cannot silently reintroduce file reads driven by an +// argument, or drop completion. +public sealed class ClawCtlParserDefaultsTests : IDisposable +{ + private readonly string _testDirectory = TestDirectory.Create(); + + private static Task FailIfSetupRuns(CancellationToken _) => + throw new InvalidOperationException( + "Setup ran for an invocation that should not have started it."); + + private static async Task<(int ExitCode, string Output)> RunAsync( + params string[] args) + { + using var output = new StringWriter(); + + int exitCode = await Program.RunControlAsync( + new HostOptions(null, []), + args, + _ => { }, + output, + TextWriter.Null, + FailIfSetupRuns).ConfigureAwait(false); + + return (exitCode, output.ToString()); + } + + private string CreateResponseFile(string name, params string[] lines) + { + string path = Path.Combine(_testDirectory, name); + File.WriteAllLines(path, lines); + return path; + } + + // A readable file holding a valid command is the case that would succeed if + // expansion were ever re-enabled, so it is the one worth asserting on. + [Fact] + public async Task ResponseFileTokenIsRejectedRatherThanExpanded() + { + string responseFile = CreateResponseFile("commands.rsp", "setup"); + + (int exitCode, string output) = + await RunAsync($"@{responseFile}").ConfigureAwait(true); + + Assert.Equal(1, exitCode); + Assert.DoesNotContain( + "package is ready", + output, + StringComparison.Ordinal); + } + + [Fact] + public async Task ResponseFileTokenForAMissingFileIsAlsoJustAnArgument() + { + string missing = Path.Combine(_testDirectory, "absent.rsp"); + + (int exitCode, _) = await RunAsync($"@{missing}").ConfigureAwait(true); + + Assert.Equal(1, exitCode); + } + + // Completion is a parse-time query. It must never start the readiness + // operation, because a shell issues it on every keystroke. + [Fact] + public void CompletionSuggestsCommandsWithoutResolvingNode() + { + RootCommand root = ClawCtlCommandLine.Create( + _ => throw new InvalidOperationException( + "Completion started the readiness operation.")); + + IEnumerable completions = root + .Parse("se", ClawCtlCommandLine.CreateParserConfiguration()) + .GetCompletions() + .Select(completion => completion.Label); + + Assert.Contains( + ClawCtlCommandLine.SetupCommandName, + completions, + StringComparer.Ordinal); + } + + // The directive is the only completion path a shell can actually reach, so + // it is covered through the real dispatcher rather than the in-process + // completion API alone. + [Fact] + public async Task CompletionDirectiveWritesSuggestionsWithoutRunningSetup() + { + (int exitCode, string output) = + await RunAsync("[suggest:2]", "se").ConfigureAwait(true); + + Assert.Equal(0, exitCode); + Assert.Contains( + ClawCtlCommandLine.SetupCommandName, + output, + StringComparison.Ordinal); + } + + public void Dispose() => Directory.Delete(_testDirectory, recursive: true); +} diff --git a/tests/OpenClaw.Launcher.Tests/GatewayLauncherTests.cs b/tests/OpenClaw.Launcher.Tests/GatewayLauncherTests.cs index 34bd1db..cc7c894 100644 --- a/tests/OpenClaw.Launcher.Tests/GatewayLauncherTests.cs +++ b/tests/OpenClaw.Launcher.Tests/GatewayLauncherTests.cs @@ -79,6 +79,20 @@ public void CreateStartInfoPreservesExplicitArguments() [InlineData("gateway", "install")] [InlineData("setup", "--install-daemon")] [InlineData("onboard", "--mode", "local")] + + // clawctl now parses its own arguments with System.CommandLine. These + // tokens are the ones that library would claim, so they prove the openclaw + // entrypoint still forwards instead of parsing. + [InlineData("--help")] + [InlineData("-h")] + [InlineData("-?")] + [InlineData("--version")] + [InlineData("--")] + [InlineData("--", "--version")] + [InlineData("@arguments.rsp")] + [InlineData("[suggest:3]", "se")] + [InlineData("")] + [InlineData("chat", "", "--prompt", "value with spaces")] public void CreateStartInfoForwardsCommandsWithoutInterpretation( params string[] arguments) { diff --git a/tests/OpenClaw.Launcher.Tests/ProgramStartupTests.cs b/tests/OpenClaw.Launcher.Tests/ProgramStartupTests.cs new file mode 100644 index 0000000..f874189 --- /dev/null +++ b/tests/OpenClaw.Launcher.Tests/ProgramStartupTests.cs @@ -0,0 +1,145 @@ +using System.Runtime.InteropServices; + +namespace OpenClaw.Launcher.Tests; + +// Startup is the part of the host that owns diagnostics, argument routing, the +// operational error boundary, and disposal. These scenarios drive the real +// Program.RunAsync with fixture-owned storage, so a regression there is caught +// without a test ever writing to the developer's profile. +public sealed class ProgramStartupTests : IDisposable +{ + private readonly string _testDirectory = TestDirectory.Create(); + + [Fact] + public async Task StartupWritesRecordsToTheSuppliedDiagnosticLog() + { + using var output = new StringWriter(); + using var error = new StringWriter(); + string logPath = Path.Combine(_testDirectory, "logs", "openclaw.log"); + + int exitCode = await Program.RunAsync( + ["--version"], + CreateStartup(logPath, output, error)); + + Assert.Equal(0, exitCode); + Assert.True(File.Exists(logPath)); + string log = await File.ReadAllTextAsync(logPath); + Assert.Contains("Host started", log, StringComparison.Ordinal); + Assert.Contains("Host exiting", log, StringComparison.Ordinal); + } + + // The library's default exception handler is disabled so this boundary, + // not System.CommandLine, reports operational failures. + [Fact] + public async Task OperationalFailureReportsTheDiagnosticPathAndExitsNonZero() + { + using var output = new StringWriter(); + using var error = new StringWriter(); + string logPath = Path.Combine(_testDirectory, "logs", "openclaw.log"); + + int exitCode = await Program.RunAsync( + ["setup"], + CreateStartup(logPath, output, error)); + + Assert.Equal(1, exitCode); + Assert.Contains(logPath, error.ToString(), StringComparison.Ordinal); + Assert.DoesNotContain("package is ready", output.ToString(), StringComparison.Ordinal); + Assert.Contains( + "Unhandled failure", + await File.ReadAllTextAsync(logPath), + StringComparison.Ordinal); + } + + // Losing the log must not take the command down with it: the host warns + // once and keeps running. + [Fact] + public async Task UnavailableDiagnosticsWarnsOnceAndStillRunsTheCommand() + { + using var output = new StringWriter(); + using var error = new StringWriter(); + HostStartup startup = new() + { + Entrypoint = HostEntrypoint.Control, + CreateDiagnostics = () => throw new IOException("The log is unavailable."), + BaseDirectory = _testDirectory, + Output = output, + Error = error + }; + + int exitCode = await Program.RunAsync(["--version"], startup); + + Assert.Equal(0, exitCode); + Assert.Contains( + "Unable to create diagnostics", + error.ToString(), + StringComparison.Ordinal); + Assert.Equal( + typeof(Program).Assembly.GetName().Version?.ToString(), + output.ToString().Trim()); + } + + // Startup routes the agent alias without letting the clawctl parser see + // the arguments, and the child's exit code is the host's exit code. + [Fact] + public async Task AgentStartupForwardsArgumentsAndChildExitCode() + { + string applicationDirectory = Path.Combine(_testDirectory, "app"); + Directory.CreateDirectory(applicationDirectory); + await File.WriteAllTextAsync( + Path.Combine(applicationDirectory, "openclaw.mjs"), + "console.log('fixture');"); + string[] arguments = ["--help", "--", "@file.rsp", "[suggest:1]", "", "a b"]; + string[]? forwarded = null; + using var output = new StringWriter(); + using var error = new StringWriter(); + + HostStartup startup = new() + { + Entrypoint = HostEntrypoint.Agent, + CreateDiagnostics = () => HostDiagnosticLog.Create( + Path.Combine(_testDirectory, "logs", "openclaw.log")), + BaseDirectory = _testDirectory, + Output = output, + Error = error, + ResolveNode = _ => Task.FromResult( + new NodeRuntime( + Path.Combine(_testDirectory, "node.exe"), + new Version(24, 15, 0), + RuntimeInformation.ProcessArchitecture)), + LaunchOpenClaw = (_, _, launchArguments, _, _) => + { + forwarded = [.. launchArguments]; + return Task.FromResult(23); + } + }; + + int exitCode = await Program.RunAsync(arguments, startup); + + Assert.Equal(23, exitCode); + Assert.Equal(arguments, forwarded); + Assert.Empty(output.ToString()); + } + + private HostStartup CreateStartup( + string logPath, + TextWriter output, + TextWriter error) => new() + { + Entrypoint = HostEntrypoint.Control, + CreateDiagnostics = () => HostDiagnosticLog.Create(logPath), + BaseDirectory = _testDirectory, + Output = output, + Error = error, + ResolveNode = _ => Task.FromResult( + new NodeRuntime( + Path.Combine(_testDirectory, "node.exe"), + new Version(24, 15, 0), + RuntimeInformation.ProcessArchitecture)) + }; + + public void Dispose() + { + Directory.Delete(_testDirectory, recursive: true); + GC.SuppressFinalize(this); + } +} diff --git a/tests/OpenClaw.Launcher.Tests/ProgramTests.cs b/tests/OpenClaw.Launcher.Tests/ProgramTests.cs index 1ef0b16..59479b4 100644 --- a/tests/OpenClaw.Launcher.Tests/ProgramTests.cs +++ b/tests/OpenClaw.Launcher.Tests/ProgramTests.cs @@ -62,8 +62,8 @@ public async Task SetupChecksNodeAndPackagedApplicationWithoutMutation() options, ["setup"], _ => { }, - _ => { }, output, + TextWriter.Null, _ => Task.FromResult(nodeRuntime)); Assert.Equal(0, exitCode); @@ -89,8 +89,8 @@ await Assert.ThrowsAsync( new HostOptions(null, []), ["setup"], _ => { }, - _ => { }, - new StringWriter(), + TextWriter.Null, + TextWriter.Null, _ => { nodeResolutionAttempted = true; From 5208c1d4f3f308487a05990d159837c486c99dec Mon Sep 17 00:00:00 2001 From: Linus Huang Date: Fri, 11 Sep 2026 16:23:14 -0700 Subject: [PATCH 13/35] fix: follow upstream Node selection and repair bundled runtimes Carry the upstream toolchain version through payload metadata and MSIX composition. Validate bundled executables without launching staged images, repair invalid caches under a cross-session installation lock, reclaim interrupted staging, and expose bundled tools only to the child PATH. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/copilot-instructions.md | 25 +- .github/workflows/gateway-msix.yml | 42 ++-- CONTRIBUTING.md | 4 + README.md | 44 ++-- scripts/Build-LocalMSIX.ps1 | 21 +- scripts/Build-MSIX.ps1 | 53 ++-- scripts/Build-Payload.ps1 | 23 +- scripts/Test-NodeRuntimeInputs.Tests.ps1 | 103 ++++++++ scripts/Test-SigningInputs.Tests.ps1 | 18 +- scripts/Test-SigningInputs.ps1 | 8 + src/OpenClaw.Launcher/GatewayLauncher.cs | 8 + src/OpenClaw.Launcher/HostOptions.cs | 13 +- src/OpenClaw.Launcher/NodeRuntimeInstaller.cs | 136 +++++++---- src/OpenClaw.Launcher/NodeRuntimeResolver.cs | 190 +++++---------- .../OpenClaw.Launcher.csproj | 5 +- src/OpenClaw.Launcher/Program.cs | 12 +- .../GatewayLauncherTests.cs | 18 ++ .../HostOptionsTests.cs | 26 +- .../NodeRuntimeInstallerTests.cs | 227 +++++++++++++++--- .../NodeRuntimeResolverTests.cs | 133 +++++----- 20 files changed, 691 insertions(+), 418 deletions(-) create mode 100644 scripts/Test-NodeRuntimeInputs.Tests.ps1 diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 7f3863d..7829147 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -60,23 +60,23 @@ and ARM64 separately. - `openclaw` resolves the Node.js executable extracted into package LocalState, confirms the packaged entry point exists, and forwards every argument unchanged to `openclaw.mjs`. -- `clawctl setup` idempotently extracts the architecture-specific bundled - Node.js archive into versioned package LocalState and verifies the packaged - entry point. Runtime launches do not hash or walk application files. +- `clawctl setup` validates and reuses or repairs the architecture-specific + bundled Node.js runtime in versioned package LocalState and verifies the + packaged entry point. Runtime launches do not hash or walk application files. - `GatewayLauncher` starts Node without a shell, uses `ArgumentList`, inherits the console streams, and sets `OPENCLAW_SUPERVISOR_MODE=external` plus `OPENCLAW_NO_AUTO_UPDATE=1`. The child process exit code is the launcher exit - code. + code. Only the child environment prepends the bundled runtime to `PATH`. - Diagnostics are written to packaged LocalState (or `%LOCALAPPDATA%\OpenClawGatewayMSIX` outside an MSIX context) with a named mutex so concurrent processes append complete records. -- The GitHub workflow first builds and packs a pinned - `openclaw/openclaw` revision on Linux. Windows matrix jobs use - `Build-Payload.ps1` to produce x64/ARM64 expanded trees and build metadata, - then downloads the matching official Node.js archive and uses - `Build-MSIX.ps1` to reject Node.js from the application payload, build the - application inventory, publish the NativeAOT host, validate package - contents, and emit MSIX metadata including the runtime hash. +- The GitHub workflow first builds and packs a pinned `openclaw/openclaw` + revision on Linux using that revision's `setup-node-env` action. The resolved + Node.js version flows through `source.json` and `payload-metadata.json`; + Windows payload builds use the same version. `Build-MSIX.ps1` downloads its + matching official archive, rejects Node.js from the application payload, + builds the application inventory, publishes the NativeAOT host, validates + package contents, and emits MSIX metadata including the runtime hash. - Unsigned artifacts are the normal PR/push output. Test signing uses a temporary runner-local certificate. Official signing is gated to `main` and the immutable upstream commit in `release-policy.json`; signing inputs are @@ -118,6 +118,9 @@ and ARM64 separately. - Keep x64 and ARM64 behavior synchronized across the workflow matrix, scripts, project runtime identifiers, manifest content, payload metadata, and signing validation. +- Do not add a packaging-side Node.js version pin or support-range policy. + The selected upstream toolchain owns version selection; package composition + supplies `NodeRuntimeArchiveFileName`, and the host reads the archive name. - Metadata files are part of the release trust chain, not incidental build output. Changes to their fields must be coordinated across payload creation, MSIX creation, signing validation, workflow artifacts, and tests. diff --git a/.github/workflows/gateway-msix.yml b/.github/workflows/gateway-msix.yml index 9788128..f089454 100644 --- a/.github/workflows/gateway-msix.yml +++ b/.github/workflows/gateway-msix.yml @@ -26,8 +26,6 @@ permissions: contents: read env: - NODE_VERSION: 24.16.0 - PNPM_VERSION: 11.15.1 OPENCLAW_REF: ${{ github.event_name == 'workflow_dispatch' && inputs.openclaw_ref || '0965053fe6b9341776df147a6934b7485c60b5ca' }} PACKAGING_ROOT: . @@ -69,6 +67,10 @@ jobs: run: > .\scripts\Test-SigningInputs.Tests.ps1 + - name: Test Node.js packaging inputs + shell: pwsh + run: .\scripts\Test-NodeRuntimeInputs.Tests.ps1 + - name: Test workflow package version shell: pwsh run: > @@ -85,32 +87,26 @@ jobs: outputs: source_sha: ${{ steps.source.outputs.sha }} package_version: ${{ steps.source.outputs.version }} + node_version: ${{ steps.source.outputs.node_version }} steps: - name: Check out OpenClaw source uses: actions/checkout@v7 with: repository: openclaw/openclaw ref: ${{ env.OPENCLAW_REF }} - path: openclaw-source persist-credentials: false fetch-depth: 1 - - name: Set up Node.js - uses: actions/setup-node@v6 + - name: Set up upstream Node.js and pnpm + uses: ./.github/actions/setup-node-env with: - node-version: ${{ env.NODE_VERSION }} - - - name: Enable pnpm - run: | - corepack enable - corepack prepare "pnpm@${PNPM_VERSION}" --activate + install-bun: "false" + install-deps: "false" - name: Install dependencies - working-directory: openclaw-source run: pnpm install --frozen-lockfile - name: Build OpenClaw - working-directory: openclaw-source env: OPENCLAW_CONTROL_UI_RELEASE_BUILD: "1" run: | @@ -119,7 +115,6 @@ jobs: - name: Pack npm package id: source - working-directory: openclaw-source run: | set -euo pipefail artifact_dir="${RUNNER_TEMP}/openclaw-package" @@ -133,15 +128,18 @@ jobs: source_sha="$(git rev-parse HEAD)" package_version="$(node -p "require('./package.json').version")" + node_version="$(node -p 'process.versions.node')" echo "sha=${source_sha}" >> "${GITHUB_OUTPUT}" echo "version=${package_version}" >> "${GITHUB_OUTPUT}" + echo "node_version=${node_version}" >> "${GITHUB_OUTPUT}" cat > "${artifact_dir}/source.json" <\LocalState\OpenClaw\NodeJS\node-v24.16.0-win-`. +`%LOCALAPPDATA%\Packages\\LocalState\OpenClaw\NodeJS\node-v-win-`. Extraction is idempotent, versioned, and serialized across concurrent setup -processes. The launcher validates the extracted `node.exe` before reporting -readiness. +processes, including different Windows sessions. Setup validates existing +runtimes before reuse, replaces invalid runtimes, and validates extraction +before publishing it. The launcher places Node.js in a Windows job configured with `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`. The launcher remains alive while Node.js @@ -83,9 +87,9 @@ clawctl setup openclaw ``` -The packaged OpenClaw revision accepts Node.js -`>=22.22.3 <23 || >=24.15.0 <25 || >=25.9.0`. The launcher keeps this -requirement in one shared validator used by `clawctl` and `openclaw`. +When an MSIX update changes the bundled Node.js version, run `clawctl setup` +again before launching OpenClaw. Previously extracted versions are left in +place so an update does not remove a running process's runtime. ## Selecting the OpenClaw revision @@ -101,6 +105,14 @@ For a one-time override, run **Build OpenClaw Gateway MSIX** manually and provide a tag, branch, or preferably a full 40-character commit SHA in `openclaw_ref`. +The source build uses that revision's `.github/actions/setup-node-env` action +to select Node.js and pnpm. Its resolved Node.js version is recorded in +`source.json`, reused for both Windows payload builds, and carried in +`payload-metadata.json`. Package composition downloads that exact version; +the launcher derives its runtime version and LocalState path from the bundled +archive name. There is no separate packaging-side Node.js version pin or +runtime-support policy. + The payload artifact records the requested ref and resolved upstream commit in `payload-metadata.json`. That build-only file is not embedded in the MSIX. `msix-metadata.json` records both the packaging repository commit and bundled @@ -124,14 +136,14 @@ dotnet test .\OpenClaw.Gateway.MSIX.slnx ` ``` `scripts\Build-Payload.ps1` npm-installs an OpenClaw package into an expanded, -architecture-specific application tree. The workflow downloads the official -Node.js archive for the same architecture. `scripts\Build-MSIX.ps1` copies the -application tree and runtime archive into package content, rejects Node.js +architecture-specific application tree. `scripts\Build-MSIX.ps1` downloads +the official Node.js archive matching the payload's recorded build version +and architecture, copies both inputs into package content, rejects Node.js inside the application payload, creates a per-file inventory, and then creates an unsigned NativeAOT MSIX. `scripts\Build-LocalMSIX.ps1` can reuse a successful workflow payload or a -local payload directory; unless `-NodeArchivePath` is supplied, it downloads -the pinned runtime archive used by CI. +local payload directory. `-NodeArchivePath` can supply an already-downloaded +archive, but its version and architecture must match the payload metadata. Normal pull-request and push workflows publish unsigned packages for validation. Manual runs support three signing modes: @@ -156,7 +168,7 @@ key is stored in the repository. |---|---| | OpenClaw application files | Read-only MSIX package `app` directory | | Bundled Node.js archive | Read-only MSIX package `runtime` directory | -| Extracted Node.js runtime | `%LOCALAPPDATA%\Packages\\LocalState\OpenClaw\NodeJS\node-v24.16.0-win-` | +| Extracted Node.js runtime | `%LOCALAPPDATA%\Packages\\LocalState\OpenClaw\NodeJS\node-v-win-` | | OpenClaw configuration and user state | `%USERPROFILE%\.openclaw` | | Launcher diagnostics | `%LOCALAPPDATA%\Packages\\LocalState\OpenClawGatewayMSIX\Logs\openclaw.log` | diff --git a/scripts/Build-LocalMSIX.ps1 b/scripts/Build-LocalMSIX.ps1 index c0e77db..57d3664 100644 --- a/scripts/Build-LocalMSIX.ps1 +++ b/scripts/Build-LocalMSIX.ps1 @@ -7,9 +7,6 @@ param( [long]$PayloadRunId, - [ValidatePattern('^\d+\.\d+\.\d+$')] - [string]$NodeVersion = '24.16.0', - [string]$NodeArchivePath, [string]$PackageVersion, @@ -61,20 +58,7 @@ if (Test-Path -LiteralPath $OutputDirectory) { New-Item -Path $workDirectory -ItemType Directory -Force | Out-Null if ($NodeArchivePath) { - $resolvedNodeArchivePath = ( - Resolve-Path -LiteralPath $NodeArchivePath - ).Path -} -else { - $nodeArchiveName = "node-v$NodeVersion-win-$Architecture.zip" - $resolvedNodeArchivePath = Join-Path $workDirectory $nodeArchiveName - $nodeArchiveUri = - "https://nodejs.org/dist/v$NodeVersion/$nodeArchiveName" - Write-Host "Downloading bundled Node.js runtime from $nodeArchiveUri." - Invoke-WebRequest ` - -Uri $nodeArchiveUri ` - -OutFile $resolvedNodeArchivePath ` - -UseBasicParsing + $NodeArchivePath = (Resolve-Path -LiteralPath $NodeArchivePath).Path } if ($PayloadDirectory) { @@ -157,8 +141,7 @@ try { Write-Host "Building unsigned MSIX version $PackageVersion." & .\scripts\Build-MSIX.ps1 ` -PayloadDirectory $resolvedPayloadDirectory ` - -NodeArchivePath $resolvedNodeArchivePath ` - -NodeVersion $NodeVersion ` + -NodeArchivePath $NodeArchivePath ` -Architecture $Architecture ` -PackageVersion $PackageVersion ` -SourceCommit $sourceCommit ` diff --git a/scripts/Build-MSIX.ps1 b/scripts/Build-MSIX.ps1 index 955d9ff..0159d7c 100644 --- a/scripts/Build-MSIX.ps1 +++ b/scripts/Build-MSIX.ps1 @@ -3,13 +3,8 @@ param( [Parameter(Mandatory)] [string]$PayloadDirectory, - [Parameter(Mandatory)] [string]$NodeArchivePath, - [Parameter(Mandatory)] - [ValidatePattern('^\d+\.\d+\.\d+$')] - [string]$NodeVersion, - [Parameter(Mandatory)] [ValidateSet('x64', 'arm64')] [string]$Architecture, @@ -186,25 +181,10 @@ function Add-VswhereToPath { } Test-PackageVersion -Add-VswhereToPath $PayloadDirectory = (Resolve-Path -LiteralPath $PayloadDirectory).Path $payloadApplication = Join-Path $PayloadDirectory 'app' $payloadMetadata = Join-Path $PayloadDirectory 'payload-metadata.json' -$NodeArchivePath = (Resolve-Path -LiteralPath $NodeArchivePath).Path -$expectedNodeArchiveName = "node-v$NodeVersion-win-$Architecture.zip" -if ([IO.Path]::GetFileName($NodeArchivePath) -cne $expectedNodeArchiveName) { - throw ( - "NodeArchivePath must name the expected runtime archive: " + - $expectedNodeArchiveName - ) -} -$expectedNodeArchiveRoot = [IO.Path]::GetFileNameWithoutExtension( - $expectedNodeArchiveName -) -Assert-NodeArchive ` - -Path $NodeArchivePath ` - -ExpectedRoot $expectedNodeArchiveRoot if (-not (Test-Path -LiteralPath $payloadApplication -PathType Container)) { throw "Required MSIX input was not found: $payloadApplication" } @@ -217,6 +197,8 @@ if ( $payloadInfo.repository -ne 'https://github.com/openclaw/openclaw' -or $payloadInfo.architecture -ne $Architecture -or $payloadInfo.layout -ne 'expanded-directory' -or + $payloadInfo.nodeVersion -isnot [string] -or + $payloadInfo.nodeVersion -notmatch '^v?\d+\.\d+\.\d+$' -or $payloadInfo.requestedRef -isnot [string] -or [string]::IsNullOrWhiteSpace($payloadInfo.requestedRef) -or $payloadInfo.resolvedCommit -notmatch '^[0-9a-fA-F]{40}$' @@ -224,6 +206,18 @@ if ( throw 'Payload metadata is not valid for this MSIX package.' } +$nodeVersion = $payloadInfo.nodeVersion.TrimStart('v') +$expectedNodeArchiveName = "node-v$nodeVersion-win-$Architecture.zip" +if ($NodeArchivePath) { + $NodeArchivePath = (Resolve-Path -LiteralPath $NodeArchivePath).Path + if ([IO.Path]::GetFileName($NodeArchivePath) -cne $expectedNodeArchiveName) { + throw ( + 'NodeArchivePath must match the payload build runtime: ' + + $expectedNodeArchiveName + ) + } +} + if (-not (Test-Path ` -LiteralPath (Join-Path $payloadApplication 'openclaw.mjs') ` -PathType Leaf)) { @@ -252,11 +246,18 @@ if ( -Recurse } -Remove-DirectoryIfPresent -Path $runtimeTargetDirectory New-Item -Path $runtimeTargetDirectory -ItemType Directory -Force | Out-Null -Copy-Item ` - -LiteralPath $NodeArchivePath ` - -Destination $nodeArchiveTarget +if (-not $NodeArchivePath) { + $archiveUri = "https://nodejs.org/dist/v$nodeVersion/$expectedNodeArchiveName" + Write-Host "Downloading bundled Node.js runtime from $archiveUri." + Invoke-WebRequest -Uri $archiveUri -OutFile $nodeArchiveTarget +} +elseif ($NodeArchivePath -ne $nodeArchiveTarget) { + Copy-Item -LiteralPath $NodeArchivePath -Destination $nodeArchiveTarget -Force +} +Assert-NodeArchive ` + -Path $nodeArchiveTarget ` + -ExpectedRoot ([IO.Path]::GetFileNameWithoutExtension($expectedNodeArchiveName)) $nodeArchiveHash = ( Get-FileHash -LiteralPath $nodeArchiveTarget -Algorithm SHA256 ).Hash.ToLowerInvariant() @@ -327,6 +328,7 @@ New-Item ` Out-Null try { + Add-VswhereToPath $appxOutput = $msixBuildDirectory.TrimEnd('\') + '\' Write-Host "Building unsigned NativeAOT win-$Architecture MSIX with MSBuild." Invoke-CheckedCommand ` @@ -341,6 +343,7 @@ try { -p:PublishAot=true ` -p:SelfContained=true ` -p:IncludePackagingContent=true ` + "-p:NodeRuntimeArchiveFileName=$expectedNodeArchiveName" ` -p:GenerateAppxPackageOnBuild=true ` "-p:AssemblyVersion=$PackageVersion" ` "-p:FileVersion=$PackageVersion" ` @@ -536,7 +539,7 @@ try { payloadResolvedCommit = $payloadInfo.resolvedCommit.ToLowerInvariant() payloadLayout = 'immutable-package' payloadFileCount = $payloadFiles.Count - nodeRuntimeVersion = $NodeVersion + nodeRuntimeVersion = $nodeVersion nodeRuntimeArchive = $expectedNodeArchiveName nodeRuntimeSha256 = $nodeArchiveHash architecture = $Architecture diff --git a/scripts/Build-Payload.ps1 b/scripts/Build-Payload.ps1 index a823b9d..4ce97e3 100644 --- a/scripts/Build-Payload.ps1 +++ b/scripts/Build-Payload.ps1 @@ -23,6 +23,22 @@ if (-not (Test-Path $sourceMetadataPath -PathType Leaf)) { throw "Missing source metadata: $sourceMetadataPath" } +$sourceMetadata = Get-Content $sourceMetadataPath -Raw | ConvertFrom-Json +$nodeVersion = & node -p 'process.versions.node' +if ($LASTEXITCODE -ne 0 -or $nodeVersion -notmatch '^\d+\.\d+\.\d+$') { + throw 'Unable to determine the payload build Node.js version.' +} +if ($sourceMetadata.nodeVersion -cne $nodeVersion) { + throw ( + "Payload Node.js $nodeVersion does not match the source build " + + "version '$($sourceMetadata.nodeVersion)'." + ) +} +$npmVersion = & npm --version +if ($LASTEXITCODE -ne 0) { + throw 'Unable to determine the payload build npm version.' +} + $stagingDirectory = Join-Path $env:RUNNER_TEMP "openclaw-stage-$Architecture" Remove-Item $stagingDirectory -Recurse -Force -ErrorAction SilentlyContinue New-Item $stagingDirectory -ItemType Directory | Out-Null @@ -105,16 +121,15 @@ Copy-Item ` -Destination $applicationDirectory ` -Recurse -$sourceMetadata = Get-Content $sourceMetadataPath -Raw | ConvertFrom-Json [ordered]@{ repository = $sourceMetadata.repository requestedRef = $sourceMetadata.requestedRef resolvedCommit = $sourceMetadata.resolvedCommit packageVersion = $sourceMetadata.packageVersion architecture = $Architecture - layout = 'expanded-directory' - nodeVersion = (& node --version) - npmVersion = (& npm --version) + layout = 'expanded-directory' + nodeVersion = $nodeVersion + npmVersion = $npmVersion } | ConvertTo-Json | Set-Content (Join-Path $OutputDirectory 'payload-metadata.json') -Encoding utf8 $files = @(Get-ChildItem -LiteralPath $applicationDirectory -File -Recurse) diff --git a/scripts/Test-NodeRuntimeInputs.Tests.ps1 b/scripts/Test-NodeRuntimeInputs.Tests.ps1 new file mode 100644 index 0000000..751efe8 --- /dev/null +++ b/scripts/Test-NodeRuntimeInputs.Tests.ps1 @@ -0,0 +1,103 @@ +[CmdletBinding()] +param() + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' +$testRoot = Join-Path ([IO.Path]::GetTempPath()) ( + "openclaw-node-inputs-$([guid]::NewGuid().ToString('N'))" +) +$previousRunnerTemp = $env:RUNNER_TEMP +$previousNpmCache = $env:npm_config_cache + +function Assert-Fails { + param([scriptblock]$Action, [string]$MessagePattern) + + try { + & $Action + } + catch { + if ($_.Exception.Message -notmatch $MessagePattern) { + throw + } + return + } + throw "Expected failure matching '$MessagePattern'." +} + +try { + $env:RUNNER_TEMP = $testRoot + $env:npm_config_cache = Join-Path $testRoot 'npm-cache' + $source = Join-Path $testRoot 'source' + $package = Join-Path $testRoot 'package' + $payload = Join-Path $testRoot 'payload' + New-Item -ItemType Directory -Path "$source\dist", $package -Force | Out-Null + '{"name":"openclaw","version":"0.0.0","type":"module"}' | + Set-Content -LiteralPath "$source\package.json" + 'console.log("fixture");' | Set-Content -LiteralPath "$source\openclaw.mjs" + 'export {};' | Set-Content -LiteralPath "$source\dist\index.js" + & npm pack $source --ignore-scripts --offline --silent --pack-destination $package + if ($LASTEXITCODE -ne 0) { + throw 'Unable to pack the local Node.js input fixture.' + } + $nodeVersion = & node -p 'process.versions.node' + if ($LASTEXITCODE -ne 0) { + throw 'Unable to determine the fixture Node.js version.' + } + $sourceMetadata = @{ + repository = 'https://github.com/openclaw/openclaw' + requestedRef = '1' * 40 + resolvedCommit = '1' * 40 + packageVersion = '0.0.0' + nodeVersion = $nodeVersion + } + $sourceMetadata | ConvertTo-Json | Set-Content -LiteralPath "$package\source.json" + + & "$PSScriptRoot\Build-Payload.ps1" ` + -PackageDirectory $package -Architecture x64 -OutputDirectory $payload + $metadataPath = Join-Path $payload 'payload-metadata.json' + $metadata = Get-Content -LiteralPath $metadataPath -Raw | ConvertFrom-Json + if ($metadata.nodeVersion -cne $nodeVersion) { + throw 'The payload did not preserve the exact source build Node.js version.' + } + + $sourceMetadata.nodeVersion = '0.0.0' + $sourceMetadata | ConvertTo-Json | Set-Content -LiteralPath "$package\source.json" + Assert-Fails -MessagePattern 'does not match the source build' -Action { + & "$PSScriptRoot\Build-Payload.ps1" ` + -PackageDirectory $package -Architecture x64 -OutputDirectory $payload + } + + foreach ($architecture in @('x64', 'arm64')) { + $wrongArchive = Join-Path $testRoot "node-v0.0.0-win-$architecture.zip" + Set-Content -LiteralPath $wrongArchive -Value 'wrong runtime' + $metadata.architecture = $architecture + foreach ($version in @('v24.15.0', '26.1.0')) { + $metadata.nodeVersion = $version + $metadata | ConvertTo-Json | Set-Content -LiteralPath $metadataPath + $expectedName = "node-v$($version.TrimStart('v'))-win-$architecture.zip" + Assert-Fails -MessagePattern ([regex]::Escape($expectedName)) -Action { + & "$PSScriptRoot\Build-MSIX.ps1" ` + -PayloadDirectory $payload -NodeArchivePath $wrongArchive ` + -Architecture $architecture -PackageVersion '0.1.1.0' ` + -SourceCommit ('1' * 40) -OutputDirectory "$testRoot\msix" + } + } + } + $metadata.nodeVersion = '>=24' + $metadata | ConvertTo-Json | Set-Content -LiteralPath $metadataPath + Assert-Fails -MessagePattern 'Payload metadata is not valid' -Action { + & "$PSScriptRoot\Build-MSIX.ps1" ` + -PayloadDirectory $payload -Architecture arm64 ` + -PackageVersion '0.1.1.0' -SourceCommit ('1' * 40) ` + -OutputDirectory "$testRoot\msix" + } + + Write-Host 'Node.js source and packaging input tests passed.' +} +finally { + $env:RUNNER_TEMP = $previousRunnerTemp + $env:npm_config_cache = $previousNpmCache + if (Test-Path -LiteralPath $testRoot) { + Remove-Item -LiteralPath $testRoot -Recurse -Force + } +} diff --git a/scripts/Test-SigningInputs.Tests.ps1 b/scripts/Test-SigningInputs.Tests.ps1 index 59b61ee..1a83dec 100644 --- a/scripts/Test-SigningInputs.Tests.ps1 +++ b/scripts/Test-SigningInputs.Tests.ps1 @@ -26,6 +26,8 @@ function New-TestArtifact { [bool]$SourceTreeDirty = $false, + [string]$NodeRuntimeVersion = '24.16.0', + [bool]$IncludeBundledNode = $false, [bool]$IncludeApplicationBundledNode = $false, @@ -128,7 +130,6 @@ function New-TestArtifact { $runtimeDirectory = Join-Path $staging 'runtime' New-Item -Path $runtimeDirectory -ItemType Directory | Out-Null } - $nodeRuntimeVersion = '24.16.0' $nodeRuntimeArchive = "node-v$nodeRuntimeVersion-win-$Architecture.zip" $nodeRuntimePath = Join-Path $runtimeDirectory $nodeRuntimeArchive @@ -289,6 +290,21 @@ try { Reset-TestArtifacts Invoke-PolicyValidation -Root $testRoot + Remove-Item -LiteralPath $testRoot -Recurse -Force + New-Item -Path $testRoot -ItemType Directory | Out-Null + New-TestArtifact -Root $testRoot -Architecture x64 -NodeRuntimeVersion '26.1.0' + New-TestArtifact -Root $testRoot -Architecture arm64 -NodeRuntimeVersion '26.1.0' + Invoke-PolicyValidation -Root $testRoot + + Remove-Item -LiteralPath $testRoot -Recurse -Force + New-Item -Path $testRoot -ItemType Directory | Out-Null + New-TestArtifact -Root $testRoot -Architecture x64 + New-TestArtifact -Root $testRoot -Architecture arm64 -NodeRuntimeVersion '26.1.0' + Assert-Fails ` + -MessagePattern 'Node.js runtime versions do not match' ` + -Action { Invoke-PolicyValidation -Root $testRoot } + + Reset-TestArtifacts Assert-Fails ` -MessagePattern 'approved immutable OpenClaw commit' ` -Action { diff --git a/scripts/Test-SigningInputs.ps1 b/scripts/Test-SigningInputs.ps1 index 9a81655..bc31d64 100644 --- a/scripts/Test-SigningInputs.ps1 +++ b/scripts/Test-SigningInputs.ps1 @@ -170,6 +170,7 @@ if ( $expectedPackagingCommit = $PackagingCommit.ToLowerInvariant() $expectedPackageVersion = $null +$expectedNodeRuntimeVersion = $null foreach ($architecture in @('x64', 'arm64')) { $directory = Join-Path $resolvedArtifactsDirectory $architecture $metadataPath = Join-Path $directory 'msix-metadata.json' @@ -219,6 +220,13 @@ foreach ($architecture in @('x64', 'arm64')) { throw 'The x64 and ARM64 package versions do not match.' } + if ($null -eq $expectedNodeRuntimeVersion) { + $expectedNodeRuntimeVersion = $metadata.nodeRuntimeVersion + } + elseif ($metadata.nodeRuntimeVersion -ne $expectedNodeRuntimeVersion) { + throw 'The x64 and ARM64 Node.js runtime versions do not match.' + } + $actualMsixHash = ( Get-FileHash -LiteralPath $msix.FullName -Algorithm SHA256 ).Hash.ToLowerInvariant() diff --git a/src/OpenClaw.Launcher/GatewayLauncher.cs b/src/OpenClaw.Launcher/GatewayLauncher.cs index ec27931..531a98a 100644 --- a/src/OpenClaw.Launcher/GatewayLauncher.cs +++ b/src/OpenClaw.Launcher/GatewayLauncher.cs @@ -69,6 +69,14 @@ public static ProcessStartInfo CreateStartInfo( startInfo.Environment["OPENCLAW_SUPERVISOR_MODE"] = "external"; startInfo.Environment["OPENCLAW_SERVICE_REPAIR_POLICY"] = "external"; startInfo.Environment["OPENCLAW_NO_AUTO_UPDATE"] = "1"; + string? nodeDirectory = Path.GetDirectoryName(nodePath); + if (!string.IsNullOrEmpty(nodeDirectory)) + { + startInfo.Environment.TryGetValue("PATH", out string? inheritedPath); + startInfo.Environment["PATH"] = string.IsNullOrEmpty(inheritedPath) + ? nodeDirectory + : $"{nodeDirectory}{Path.PathSeparator}{inheritedPath}"; + } startInfo.ArgumentList.Add(entryPoint); foreach (string argument in openClawArguments) diff --git a/src/OpenClaw.Launcher/HostOptions.cs b/src/OpenClaw.Launcher/HostOptions.cs index 6dace6f..5c14526 100644 --- a/src/OpenClaw.Launcher/HostOptions.cs +++ b/src/OpenClaw.Launcher/HostOptions.cs @@ -24,18 +24,13 @@ internal static HostOptions Parse( "openclaw.mjs")) ? packagedApplicationDirectory : null; - string packagedNodeArchivePath = Path.Combine( - baseDirectory, - "runtime", - NodeRuntimeInstaller.GetArchiveFileName( - System.Runtime.InteropServices.RuntimeInformation - .ProcessArchitecture)); + string? packagedNodeArchivePath = NodeRuntimeInstaller.FindArchivePath( + Path.Combine(baseDirectory, "runtime"), + System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture); return new HostOptions( directApplicationDirectory, - File.Exists(packagedNodeArchivePath) - ? packagedNodeArchivePath - : null, + packagedNodeArchivePath, [.. arguments]); } } diff --git a/src/OpenClaw.Launcher/NodeRuntimeInstaller.cs b/src/OpenClaw.Launcher/NodeRuntimeInstaller.cs index 06475f6..92451d9 100644 --- a/src/OpenClaw.Launcher/NodeRuntimeInstaller.cs +++ b/src/OpenClaw.Launcher/NodeRuntimeInstaller.cs @@ -1,49 +1,77 @@ using System.IO.Compression; using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; namespace OpenClaw.Launcher; -internal static class NodeRuntimeInstaller +internal static partial class NodeRuntimeInstaller { - public const string Version = "24.16.0"; + public static string? FindArchivePath( + string runtimeDirectory, + Architecture architecture) + { + if (!Directory.Exists(runtimeDirectory)) + { + return null; + } - public static string GetArchiveFileName(Architecture architecture) => - $"node-v{Version}-win-{GetArchitectureName(architecture)}.zip"; + string[] archives = Directory.GetFiles( + runtimeDirectory, + $"node-v*-win-{GetArchitectureName(architecture)}.zip"); + return archives.Length switch + { + 0 => null, + 1 => archives[0], + _ => throw new InvalidDataException( + "The package contains multiple Node.js runtime archives.") + }; + } - public static string GetInstallDirectory(Architecture architecture) => + public static Version GetArchiveVersion( + string archivePath, + Architecture architecture) + { + Match match = ArchiveNameRegex().Match(Path.GetFileName(archivePath)); + if (!match.Success || + match.Groups["architecture"].Value != GetArchitectureName(architecture)) + { + throw new InvalidDataException( + $"Unexpected Node.js runtime archive: {Path.GetFileName(archivePath)}"); + } + + return NodeRuntimeResolver.ParseVersion(match.Groups["version"].Value); + } + + public static string GetInstallDirectory(string archivePath) => Path.Combine( HostDataPaths.GetProductLocalStateRoot(), "NodeJS", - $"node-v{Version}-win-{GetArchitectureName(architecture)}"); + Path.GetFileNameWithoutExtension(archivePath)); - public static string GetExecutablePath(Architecture architecture) => - Path.Combine(GetInstallDirectory(architecture), "node.exe"); - - public static async Task EnsureInstalledAsync( + public static NodeRuntime EnsureInstalled( string archivePath, - CancellationToken cancellationToken) + Action log) { Architecture architecture = RuntimeInformation.ProcessArchitecture; - string executablePath = EnsureInstalled( + Version version = GetArchiveVersion(archivePath, architecture); + return EnsureInstalled( archivePath, - GetInstallDirectory(architecture), - architecture); - return await NodeRuntimeResolver.ResolvePathAsync( - executablePath, - cancellationToken).ConfigureAwait(false); + GetInstallDirectory(archivePath), + path => NodeRuntimeResolver.ResolvePath( + path, + version), + log); } - internal static string EnsureInstalled( + internal static NodeRuntime EnsureInstalled( string archivePath, string installDirectory, - Architecture architecture) + Func resolveNode, + Action log) { string executablePath = Path.Combine(installDirectory, "node.exe"); - if (File.Exists(executablePath)) - { - return executablePath; - } - if (!File.Exists(archivePath)) { throw new FileNotFoundException( @@ -51,9 +79,11 @@ internal static string EnsureInstalled( archivePath); } - string mutexName = - $"Local\\OpenClawGatewayMSIX.NodeRuntime.{GetArchitectureName(architecture)}"; - using var mutex = new Mutex(initiallyOwned: false, mutexName); + // LocalState is shared across Windows sessions. Keep validation and + // publication on this thread because mutex ownership is thread-affine. + using var mutex = new Mutex( + initiallyOwned: false, + GetInstallMutexName(installDirectory)); bool ownsMutex = false; try { @@ -66,38 +96,38 @@ internal static string EnsureInstalled( ownsMutex = true; } - if (File.Exists(executablePath)) + string stagingDirectory = $"{installDirectory}.extract"; + if (Directory.Exists(stagingDirectory)) { - return executablePath; + Directory.Delete(stagingDirectory, recursive: true); } - string? parentDirectory = Path.GetDirectoryName(installDirectory); - if (string.IsNullOrWhiteSpace(parentDirectory)) + if (File.Exists(executablePath)) { - throw new InvalidOperationException( - "The Node.js installation path has no parent directory."); + try + { + return resolveNode(executablePath); + } + catch (InvalidOperationException exception) + { + log($"Reinstalling invalid bundled Node.js: {exception.Message}"); + } } - Directory.CreateDirectory(parentDirectory); - string stagingDirectory = - $"{installDirectory}.{Guid.NewGuid():N}.extract"; try { ExtractRuntime( archivePath, - stagingDirectory, - architecture); - if (!File.Exists(Path.Combine(stagingDirectory, "node.exe"))) - { - throw new InvalidDataException( - "The packaged Node.js archive does not contain node.exe."); - } + stagingDirectory); + NodeRuntime runtime = resolveNode( + Path.Combine(stagingDirectory, "node.exe")); if (Directory.Exists(installDirectory)) { Directory.Delete(installDirectory, recursive: true); } Directory.Move(stagingDirectory, installDirectory); + return runtime with { ExecutablePath = executablePath }; } finally { @@ -106,8 +136,6 @@ internal static string EnsureInstalled( Directory.Delete(stagingDirectory, recursive: true); } } - - return executablePath; } finally { @@ -118,13 +146,18 @@ internal static string EnsureInstalled( } } + internal static string GetInstallMutexName(string installDirectory) + { + string identity = Path.GetFullPath(installDirectory).ToUpperInvariant(); + string hash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(identity))); + return $"Global\\OpenClawGatewayMSIX.NodeRuntime.{hash}"; + } + private static void ExtractRuntime( string archivePath, - string stagingDirectory, - Architecture architecture) + string stagingDirectory) { - string archiveRoot = - $"node-v{Version}-win-{GetArchitectureName(architecture)}"; + string archiveRoot = Path.GetFileNameWithoutExtension(archivePath); string archivePrefix = archiveRoot + "/"; string fullStagingDirectory = Path.GetFullPath(stagingDirectory) + Path.DirectorySeparatorChar; @@ -197,6 +230,11 @@ private static void ExtractRuntime( } } + [GeneratedRegex( + @"^node-v(?\d+\.\d+\.\d+)-win-(?x64|arm64)\.zip$", + RegexOptions.CultureInvariant)] + private static partial Regex ArchiveNameRegex(); + private static string GetArchitectureName(Architecture architecture) => architecture switch { diff --git a/src/OpenClaw.Launcher/NodeRuntimeResolver.cs b/src/OpenClaw.Launcher/NodeRuntimeResolver.cs index 5f24686..6f1e999 100644 --- a/src/OpenClaw.Launcher/NodeRuntimeResolver.cs +++ b/src/OpenClaw.Launcher/NodeRuntimeResolver.cs @@ -13,101 +13,71 @@ internal sealed record NodeRuntime( internal static partial class NodeRuntimeResolver { - private static readonly TimeSpan VersionQueryTimeout = TimeSpan.FromSeconds(10); - private static readonly NodeVersionRange[] SupportedVersionRanges = - [ - new(new Version(22, 22, 3), 23), - new(new Version(24, 15, 0), 25), - new(new Version(25, 9, 0), null) - ]; - - public static string SupportedVersions { get; } = string.Join( - " || ", - SupportedVersionRanges.Select(range => - range.ExclusiveMajor is int exclusiveMajor - ? $">={range.Minimum} <{exclusiveMajor}" - : $">={range.Minimum}")); - - public static Task ResolveAsync(CancellationToken cancellationToken) => - ResolvePathAsync( - NodeRuntimeInstaller.GetExecutablePath( - RuntimeInformation.ProcessArchitecture), - cancellationToken); - - public static Task ResolvePathAsync( + public static NodeRuntime Resolve(string archivePath) => + ResolvePath( + Path.Combine(NodeRuntimeInstaller.GetInstallDirectory(archivePath), "node.exe"), + NodeRuntimeInstaller.GetArchiveVersion( + archivePath, + RuntimeInformation.ProcessArchitecture)); + + public static NodeRuntime ResolvePath( string executablePath, - CancellationToken cancellationToken) => - ResolveAsync( - File.Exists(executablePath) ? [executablePath] : [], - QueryVersionAsync, - ReadArchitecture, - RuntimeInformation.ProcessArchitecture, - cancellationToken); - - internal static async Task ResolveAsync( - IReadOnlyList candidates, - Func> queryVersion, - Func readArchitecture, - Architecture requiredArchitecture, - CancellationToken cancellationToken) + Version expectedVersion) { - if (candidates.Count == 0) + if (!File.Exists(executablePath)) { throw new InvalidOperationException( CreateFailureMessage( "The bundled Node.js runtime has not been extracted.")); } - var failures = new List(); - foreach (string candidate in candidates) + return Resolve( + executablePath, + expectedVersion, + ReadVersion, + ReadArchitecture, + RuntimeInformation.ProcessArchitecture); + } + + internal static NodeRuntime Resolve( + string executablePath, + Version expectedVersion, + Func readVersion, + Func readArchitecture, + Architecture requiredArchitecture) + { + try { - try + Version version = ParseVersion(readVersion(executablePath)); + if (version != expectedVersion) { - string output = await queryVersion(candidate, cancellationToken) - .ConfigureAwait(false); - Version version = ParseVersion(output); - if (!IsSupported(version)) - { - throw new InvalidDataException( - $"version {version} is unsupported; required {SupportedVersions}"); - } - - Architecture architecture = readArchitecture(candidate); - if (architecture != requiredArchitecture) - { - throw new InvalidDataException( - $"architecture {architecture} does not match {requiredArchitecture}"); - } - - return new NodeRuntime(candidate, version, architecture); + throw new InvalidDataException( + $"version {version} does not match bundled version {expectedVersion}"); } - catch (Exception exception) when ( - exception is IOException or - UnauthorizedAccessException or - BadImageFormatException or - InvalidDataException or - InvalidOperationException or - TimeoutException or - Win32Exception) + + Architecture architecture = readArchitecture(executablePath); + if (architecture != requiredArchitecture) { - failures.Add($"{candidate}: {exception.Message}"); + throw new InvalidDataException( + $"architecture {architecture} does not match {requiredArchitecture}"); } - } - throw new InvalidOperationException( - CreateFailureMessage( - "No compatible Node.js runtime was found. " + - string.Join(" ", failures))); + return new NodeRuntime(executablePath, version, architecture); + } + catch (Exception exception) when ( + exception is IOException or + UnauthorizedAccessException or + BadImageFormatException or + InvalidDataException or + InvalidOperationException or + Win32Exception) + { + throw new InvalidOperationException( + CreateFailureMessage($"{executablePath}: {exception.Message}"), + exception); + } } - internal static bool IsSupported(Version version) => - SupportedVersionRanges.Any(range => - version >= range.Minimum && - ( - range.ExclusiveMajor is null || - version.Major < range.ExclusiveMajor - )); - internal static Version ParseVersion(string output) { Match match = NodeVersionRegex().Match(output.Trim()); @@ -116,7 +86,7 @@ internal static Version ParseVersion(string output) !Version.TryParse(match.Groups["version"].Value, out Version? version)) { throw new InvalidDataException( - $"Node.js returned an invalid version: {output.Trim()}"); + $"Node.js has an invalid product version: {output.Trim()}"); } return version; @@ -124,57 +94,19 @@ internal static Version ParseVersion(string output) internal static string CreateFailureMessage(string detail) => $"{detail}{Environment.NewLine}" + - $"Run `clawctl setup` to extract Node.js {NodeRuntimeInstaller.Version} " + - "from the installed OpenClaw package."; + "Run `clawctl setup` to prepare the Node.js runtime bundled " + + "with the installed OpenClaw package."; - private static async Task QueryVersionAsync( - string executablePath, - CancellationToken cancellationToken) + private static string ReadVersion(string executablePath) { - var startInfo = new ProcessStartInfo - { - FileName = executablePath, - UseShellExecute = false, - CreateNoWindow = true, - RedirectStandardOutput = true, - RedirectStandardError = true - }; - startInfo.ArgumentList.Add("--version"); - - using Process process = Process.Start(startInfo) ?? - throw new InvalidOperationException("Unable to start Node.js."); - using var timeout = CancellationTokenSource.CreateLinkedTokenSource( - cancellationToken); - timeout.CancelAfter(VersionQueryTimeout); - - try + // Executing a staged image can keep it locked after process exit, + // preventing Windows from publishing the extracted directory. + string? version = FileVersionInfo.GetVersionInfo(executablePath).ProductVersion; + if (string.IsNullOrWhiteSpace(version)) { - Task standardOutput = process.StandardOutput.ReadToEndAsync( - timeout.Token); - Task standardError = process.StandardError.ReadToEndAsync( - timeout.Token); - await process.WaitForExitAsync(timeout.Token).ConfigureAwait(false); - string output = await standardOutput.ConfigureAwait(false); - string error = await standardError.ConfigureAwait(false); - if (process.ExitCode != 0) - { - throw new InvalidOperationException( - $"Node.js version query exited with code {process.ExitCode}: " + - error.Trim()); - } - - return output; - } - catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) - { - if (!process.HasExited) - { - process.Kill(entireProcessTree: true); - await process.WaitForExitAsync(CancellationToken.None) - .ConfigureAwait(false); - } - throw new TimeoutException("Node.js version query timed out."); + throw new InvalidDataException("Node.js has no product version."); } + return version; } private static Architecture ReadArchitecture(string executablePath) @@ -196,8 +128,4 @@ private static Architecture ReadArchitecture(string executablePath) @"^v?(?\d+\.\d+\.\d+)(?:\+[0-9A-Za-z.-]+)?$", RegexOptions.CultureInvariant)] private static partial Regex NodeVersionRegex(); - - private sealed record NodeVersionRange( - Version Minimum, - int? ExclusiveMajor); } diff --git a/src/OpenClaw.Launcher/OpenClaw.Launcher.csproj b/src/OpenClaw.Launcher/OpenClaw.Launcher.csproj index 47071d7..ddbe9d9 100644 --- a/src/OpenClaw.Launcher/OpenClaw.Launcher.csproj +++ b/src/OpenClaw.Launcher/OpenClaw.Launcher.csproj @@ -81,7 +81,8 @@ - @@ -93,7 +94,7 @@ Text="Missing expanded OpenClaw application content." /> - diff --git a/src/OpenClaw.Launcher/Program.cs b/src/OpenClaw.Launcher/Program.cs index 3602448..05454cf 100644 --- a/src/OpenClaw.Launcher/Program.cs +++ b/src/OpenClaw.Launcher/Program.cs @@ -131,7 +131,8 @@ internal static async Task RunAgentAsync( await RunAgentAsync( options, log, - resolveNode ?? NodeRuntimeResolver.ResolveAsync, + resolveNode ?? (_ => Task.FromResult(NodeRuntimeResolver.Resolve( + GetPackagedNodeArchivePath(options)))), GatewayLauncher.RunAsync).ConfigureAwait(false); // launchOpenClaw is a test seam: tests substitute a fake in place of @@ -198,9 +199,9 @@ await output.WriteLineAsync( else { string archivePath = GetPackagedNodeArchivePath(options); - nodeRuntime = await NodeRuntimeInstaller.EnsureInstalledAsync( + nodeRuntime = NodeRuntimeInstaller.EnsureInstalled( archivePath, - CancellationToken.None).ConfigureAwait(false); + log); } ClawCtlConsole.WriteNodeRuntimeSummary(output, nodeRuntime); string applicationDirectory = @@ -247,10 +248,7 @@ private static string GetPackagedNodeArchivePath(HostOptions options) string? archivePath = options.PackagedNodeArchivePath; string expectedPath = archivePath ?? Path.Combine( AppContext.BaseDirectory, - "runtime", - NodeRuntimeInstaller.GetArchiveFileName( - System.Runtime.InteropServices.RuntimeInformation - .ProcessArchitecture)); + "runtime"); if (archivePath is null || !File.Exists(archivePath)) { throw new FileNotFoundException( diff --git a/tests/OpenClaw.Launcher.Tests/GatewayLauncherTests.cs b/tests/OpenClaw.Launcher.Tests/GatewayLauncherTests.cs index 34bd1db..6c3b574 100644 --- a/tests/OpenClaw.Launcher.Tests/GatewayLauncherTests.cs +++ b/tests/OpenClaw.Launcher.Tests/GatewayLauncherTests.cs @@ -48,6 +48,24 @@ public void CreateStartInfoDefaultsToCurrentWorkingDirectory() Assert.NotEqual(_payloadDirectory, startInfo.WorkingDirectory); } + [Fact] + public void CreateStartInfoPrependsBundledRuntimeOnlyToChildPath() + { + string? inheritedPath = Environment.GetEnvironmentVariable("PATH"); + string nodeDirectory = Path.Combine(_payloadDirectory, "runtime"); + + var startInfo = GatewayLauncher.CreateStartInfo( + Path.Combine(nodeDirectory, "node.exe"), + _payloadDirectory, + []); + + string expectedPath = string.IsNullOrEmpty(inheritedPath) + ? nodeDirectory + : $"{nodeDirectory}{Path.PathSeparator}{inheritedPath}"; + Assert.Equal(expectedPath, startInfo.Environment["PATH"]); + Assert.Equal(inheritedPath, Environment.GetEnvironmentVariable("PATH")); + } + [Fact] public void CreateStartInfoPreservesExplicitArguments() { diff --git a/tests/OpenClaw.Launcher.Tests/HostOptionsTests.cs b/tests/OpenClaw.Launcher.Tests/HostOptionsTests.cs index 48c9ccb..a165df8 100644 --- a/tests/OpenClaw.Launcher.Tests/HostOptionsTests.cs +++ b/tests/OpenClaw.Launcher.Tests/HostOptionsTests.cs @@ -55,16 +55,20 @@ public void ParseResolvesPackagedApplicationWhenEntryPointExists() Assert.Equal(["gateway", "run"], options.OpenClawArguments); } - [Fact] - public void ParseResolvesArchitectureSpecificPackagedNodeArchive() + [Theory] + [InlineData("24.16.0")] + [InlineData("26.1.0")] + public void ParseResolvesArchitectureSpecificPackagedNodeArchive(string version) { string runtimeDirectory = Path.Combine(_testDirectory, "runtime"); Directory.CreateDirectory(runtimeDirectory); + string architecture = System.Runtime.InteropServices.RuntimeInformation + .ProcessArchitecture == System.Runtime.InteropServices.Architecture.X64 + ? "x64" + : "arm64"; string archivePath = Path.Combine( runtimeDirectory, - NodeRuntimeInstaller.GetArchiveFileName( - System.Runtime.InteropServices.RuntimeInformation - .ProcessArchitecture)); + $"node-v{version}-win-{architecture}.zip"); File.WriteAllText(archivePath, "fixture"); HostOptions options = HostOptions.Parse([], _testDirectory); @@ -72,6 +76,18 @@ public void ParseResolvesArchitectureSpecificPackagedNodeArchive() Assert.Equal(archivePath, options.PackagedNodeArchivePath); } + [Fact] + public void ArchiveDiscoveryRejectsAmbiguousVersions() + { + File.WriteAllText(Path.Combine(_testDirectory, "node-v24.16.0-win-x64.zip"), "fixture"); + File.WriteAllText(Path.Combine(_testDirectory, "node-v26.1.0-win-x64.zip"), "fixture"); + + Assert.Throws(() => + NodeRuntimeInstaller.FindArchivePath( + _testDirectory, + System.Runtime.InteropServices.Architecture.X64)); + } + public void Dispose() { Directory.Delete(_testDirectory, recursive: true); diff --git a/tests/OpenClaw.Launcher.Tests/NodeRuntimeInstallerTests.cs b/tests/OpenClaw.Launcher.Tests/NodeRuntimeInstallerTests.cs index 989d944..a397a11 100644 --- a/tests/OpenClaw.Launcher.Tests/NodeRuntimeInstallerTests.cs +++ b/tests/OpenClaw.Launcher.Tests/NodeRuntimeInstallerTests.cs @@ -7,81 +7,240 @@ public sealed class NodeRuntimeInstallerTests : IDisposable { private readonly string _testDirectory = TestDirectory.Create(); - [Fact] - public void EnsureInstalledExtractsArchitectureRuntimeOnce() + [Theory] + [InlineData("24.16.0", Architecture.X64)] + [InlineData("26.1.0", Architecture.X64)] + [InlineData("26.1.0", Architecture.Arm64)] + public void EnsureInstalledExtractsSelectedRuntimeOnce( + string version, + Architecture architecture) { - Architecture architecture = Architecture.X64; - string archivePath = CreateRuntimeArchive(architecture); + string archivePath = CreateRuntimeArchive(version, architecture); string installDirectory = Path.Combine(_testDirectory, "installed"); - string firstPath = NodeRuntimeInstaller.EnsureInstalled( + NodeRuntime first = EnsureInstalled( archivePath, - installDirectory, - architecture); - DateTime firstWriteTime = File.GetLastWriteTimeUtc(firstPath); - string secondPath = NodeRuntimeInstaller.EnsureInstalled( + installDirectory); + DateTime firstWriteTime = File.GetLastWriteTimeUtc(first.ExecutablePath); + NodeRuntime second = EnsureInstalled( archivePath, - installDirectory, - architecture); + installDirectory); - Assert.Equal(Path.Combine(installDirectory, "node.exe"), firstPath); - Assert.Equal(firstPath, secondPath); - Assert.Equal("fixture-node", File.ReadAllText(firstPath)); - Assert.Equal(firstWriteTime, File.GetLastWriteTimeUtc(secondPath)); + Assert.Equal(Path.Combine(installDirectory, "node.exe"), first.ExecutablePath); + Assert.Equal(Version.Parse(version), first.Version); + Assert.Equal(architecture, first.Architecture); + Assert.Equal(first, second); + Assert.Equal("fixture-node", File.ReadAllText(first.ExecutablePath)); + Assert.Equal(firstWriteTime, File.GetLastWriteTimeUtc(second.ExecutablePath)); + Assert.Equal( + Version.Parse(version), + NodeRuntimeInstaller.GetArchiveVersion(archivePath, architecture)); + Assert.EndsWith( + Path.GetFileNameWithoutExtension(archivePath), + NodeRuntimeInstaller.GetInstallDirectory(archivePath), + StringComparison.Ordinal); } - [Fact] - public void EnsureInstalledRejectsEntriesOutsideExpectedRuntimeRoot() + [Theory] + [InlineData("unexpected/node.exe")] + [InlineData("{root}/../outside.txt")] + [InlineData("{root}/nested/../../outside.txt")] + public void EnsureInstalledRejectsUnsafeArchiveEntries(string entryName) { - string archivePath = Path.Combine(_testDirectory, "unsafe.zip"); + ArgumentNullException.ThrowIfNull(entryName); + string archivePath = CreateRuntimeArchive("26.1.0", Architecture.X64); using (ZipArchive archive = ZipFile.Open( archivePath, - ZipArchiveMode.Create)) + ZipArchiveMode.Update)) { - ZipArchiveEntry entry = archive.CreateEntry("unexpected/node.exe"); + ZipArchiveEntry entry = archive.CreateEntry(entryName.Replace( + "{root}", + Path.GetFileNameWithoutExtension(archivePath), + StringComparison.Ordinal)); using StreamWriter writer = new(entry.Open()); writer.Write("fixture-node"); } + string installDirectory = Path.Combine(_testDirectory, "unsafe-install"); Assert.Throws(() => - NodeRuntimeInstaller.EnsureInstalled( - archivePath, - Path.Combine(_testDirectory, "unsafe-install"), - Architecture.X64)); + EnsureInstalled(archivePath, installDirectory)); + Assert.False(Directory.Exists(installDirectory)); + Assert.False(Directory.Exists($"{installDirectory}.extract")); } [Fact] public void EnsureInstalledReplacesIncompleteRuntimeDirectory() { - Architecture architecture = Architecture.X64; - string archivePath = CreateRuntimeArchive(architecture); + string archivePath = CreateRuntimeArchive("26.1.0", Architecture.X64); string installDirectory = Path.Combine(_testDirectory, "incomplete"); Directory.CreateDirectory(installDirectory); File.WriteAllText( Path.Combine(installDirectory, "partial.txt"), "partial"); - string executablePath = NodeRuntimeInstaller.EnsureInstalled( - archivePath, - installDirectory, - architecture); + NodeRuntime runtime = EnsureInstalled(archivePath, installDirectory); - Assert.True(File.Exists(executablePath)); + Assert.True(File.Exists(runtime.ExecutablePath)); Assert.False(File.Exists( Path.Combine(installDirectory, "partial.txt"))); } + [Fact] + public void EnsureInstalledRepairsInvalidExecutableAndThenReusesIt() + { + string archivePath = CreateRuntimeArchive("26.1.0", Architecture.X64); + string installDirectory = Path.Combine(_testDirectory, "invalid"); + Directory.CreateDirectory(installDirectory); + File.WriteAllText(Path.Combine(installDirectory, "node.exe"), "invalid-node"); + var messages = new List(); + + NodeRuntime runtime = EnsureInstalled(archivePath, installDirectory, messages.Add); + DateTime writeTime = File.GetLastWriteTimeUtc(runtime.ExecutablePath); + NodeRuntime reused = EnsureInstalled(archivePath, installDirectory, messages.Add); + + Assert.Equal("fixture-node", File.ReadAllText(runtime.ExecutablePath)); + Assert.Equal(runtime, reused); + Assert.Equal(writeTime, File.GetLastWriteTimeUtc(reused.ExecutablePath)); + Assert.Contains("Reinstalling invalid bundled Node.js", Assert.Single(messages), + StringComparison.Ordinal); + } + + [Fact] + public void EnsureInstalledDoesNotPublishInvalidRuntimeAndCanRetry() + { + string archivePath = CreateRuntimeArchive( + "26.1.0", + Architecture.X64, + "invalid-node"); + string installDirectory = Path.Combine(_testDirectory, "retry"); + + Assert.Throws(() => + EnsureInstalled(archivePath, installDirectory)); + Assert.False(Directory.Exists(installDirectory)); + Assert.False(Directory.Exists($"{installDirectory}.extract")); + + File.Delete(archivePath); + CreateRuntimeArchive("26.1.0", Architecture.X64); + NodeRuntime runtime = EnsureInstalled(archivePath, installDirectory); + + Assert.Equal("fixture-node", File.ReadAllText(runtime.ExecutablePath)); + } + + [Theory] + [InlineData(Architecture.X64)] + [InlineData(Architecture.Arm64)] + public void EnsureInstalledClearsInterruptedExtraction(Architecture architecture) + { + string archivePath = CreateRuntimeArchive("26.1.0", architecture); + string installDirectory = Path.Combine(_testDirectory, "interrupted"); + string stagingDirectory = $"{installDirectory}.extract"; + Directory.CreateDirectory(stagingDirectory); + File.WriteAllText(Path.Combine(stagingDirectory, "node.exe"), "partial-node"); + + NodeRuntime runtime = EnsureInstalled(archivePath, installDirectory); + + Assert.Equal("fixture-node", File.ReadAllText(runtime.ExecutablePath)); + Assert.False(Directory.Exists(stagingDirectory)); + } + + [Fact] + public async Task ConcurrentSetupPublishesOnlyOnce() + { + string archivePath = CreateRuntimeArchive("26.1.0", Architecture.X64); + string installDirectory = Path.Combine(_testDirectory, "concurrent"); + int publications = 0; + using var start = new Barrier(3); + + NodeRuntime Install() + { + start.SignalAndWait(); + return NodeRuntimeInstaller.EnsureInstalled( + archivePath, + installDirectory, + path => + { + if (path == Path.Combine($"{installDirectory}.extract", "node.exe")) + { + Interlocked.Increment(ref publications); + } + return ResolveFixture(path, archivePath); + }, + _ => { }); + } + + Task first = Task.Run(Install); + Task second = Task.Run(Install); + start.SignalAndWait(); + NodeRuntime[] runtimes = await Task.WhenAll(first, second); + + Assert.Equal(1, publications); + Assert.Equal(runtimes[0], runtimes[1]); + Assert.False(Directory.Exists($"{installDirectory}.extract")); + } + + [Fact] + public void InstallationLockIsGlobalAndScopedToItsDirectory() + { + string first = Path.Combine(_testDirectory, "first"); + string second = Path.Combine(_testDirectory, "second"); + string name = NodeRuntimeInstaller.GetInstallMutexName(first); + + Assert.StartsWith("Global\\", name, StringComparison.Ordinal); + Assert.Equal(name, NodeRuntimeInstaller.GetInstallMutexName(first.ToUpperInvariant())); + Assert.NotEqual(name, NodeRuntimeInstaller.GetInstallMutexName(second)); + } + + [Theory] + [InlineData("node-v26-win-x64.zip")] + [InlineData("node-v26.1.0-rc.1-win-x64.zip")] + [InlineData("node-v26.1.0-win-arm64.zip")] + public void ArchiveVersionRejectsMalformedOrWrongArchitectureNames(string name) + { + Assert.Throws(() => + NodeRuntimeInstaller.GetArchiveVersion(name, Architecture.X64)); + } + public void Dispose() { Directory.Delete(_testDirectory, recursive: true); GC.SuppressFinalize(this); } - private string CreateRuntimeArchive(Architecture architecture) + private static NodeRuntime EnsureInstalled( + string archivePath, + string installDirectory, + Action? log = null) => + NodeRuntimeInstaller.EnsureInstalled( + archivePath, + installDirectory, + path => ResolveFixture(path, archivePath), + log ?? (_ => { })); + + private static NodeRuntime ResolveFixture(string path, string archivePath) + { + if (!File.Exists(path) || File.ReadAllText(path) != "fixture-node") + { + throw new InvalidOperationException("Invalid fixture runtime."); + } + + Architecture architecture = archivePath.EndsWith("-win-x64.zip", StringComparison.Ordinal) + ? Architecture.X64 + : Architecture.Arm64; + return new NodeRuntime( + path, + NodeRuntimeInstaller.GetArchiveVersion(archivePath, architecture), + architecture); + } + + private string CreateRuntimeArchive( + string version, + Architecture architecture, + string nodeContent = "fixture-node") { + string architectureName = architecture == Architecture.X64 ? "x64" : "arm64"; string archivePath = Path.Combine( _testDirectory, - NodeRuntimeInstaller.GetArchiveFileName(architecture)); + $"node-v{version}-win-{architectureName}.zip"); string rootName = Path.GetFileNameWithoutExtension(archivePath); using ZipArchive archive = ZipFile.Open( archivePath, @@ -95,7 +254,7 @@ private string CreateRuntimeArchive(Architecture architecture) } ZipArchiveEntry entry = archive.CreateEntry($"{rootName}/node.exe"); using StreamWriter writer = new(entry.Open()); - writer.Write("fixture-node"); + writer.Write(nodeContent); return archivePath; } } diff --git a/tests/OpenClaw.Launcher.Tests/NodeRuntimeResolverTests.cs b/tests/OpenClaw.Launcher.Tests/NodeRuntimeResolverTests.cs index cf92dcd..8ccbc2b 100644 --- a/tests/OpenClaw.Launcher.Tests/NodeRuntimeResolverTests.cs +++ b/tests/OpenClaw.Launcher.Tests/NodeRuntimeResolverTests.cs @@ -4,86 +4,87 @@ namespace OpenClaw.Launcher.Tests; public sealed class NodeRuntimeResolverTests { - [Fact] - public async Task ResolveAcceptsCompatibleRuntime() + [Theory] + [InlineData("24.16.0", Architecture.X64)] + [InlineData("26.1.0", Architecture.X64)] + [InlineData("26.1.0", Architecture.Arm64)] + public void ResolveAcceptsSelectedRuntime(string version, Architecture architecture) { - NodeRuntime runtime = await ResolveAsync( - ["C:\\Node\\node.exe"], - _ => Task.FromResult("v24.16.0"), - Architecture.X64, - Architecture.X64); + NodeRuntime runtime = Resolve( + _ => $"v{version}", + architecture, + architecture, + version); Assert.Equal("C:\\Node\\node.exe", runtime.ExecutablePath); - Assert.Equal(new Version(24, 16, 0), runtime.Version); - Assert.Equal(Architecture.X64, runtime.Architecture); + Assert.Equal(Version.Parse(version), runtime.Version); + Assert.Equal(architecture, runtime.Architecture); } [Fact] - public async Task ResolveReportsSetupCommandWhenRuntimeIsMissing() + public void ResolveReportsSetupCommandWhenRuntimeIsMissing() { - InvalidOperationException exception = await Assert.ThrowsAsync< + InvalidOperationException exception = Assert.Throws< InvalidOperationException>( - () => ResolveAsync( - [], - _ => Task.FromResult("v24.16.0"), - Architecture.X64, - Architecture.X64)); + () => NodeRuntimeResolver.ResolvePath( + Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"), "node.exe"), + new Version(26, 1, 0))); Assert.Contains("has not been extracted", exception.Message, StringComparison.Ordinal); Assert.Contains("clawctl setup", exception.Message, StringComparison.Ordinal); } - [Fact] - public async Task ResolveRejectsOutdatedRuntime() + [Theory] + [InlineData("24.14.0")] + [InlineData("24.17.0")] + public void ResolveRejectsRuntimeDifferentFromArchive(string version) { - InvalidOperationException exception = await Assert.ThrowsAsync< + InvalidOperationException exception = Assert.Throws< InvalidOperationException>( - () => ResolveAsync( - ["C:\\Node\\node.exe"], - _ => Task.FromResult("v24.14.0"), + () => Resolve( + _ => $"v{version}", Architecture.X64, Architecture.X64)); - Assert.Contains("version 24.14.0 is unsupported", exception.Message, StringComparison.Ordinal); - Assert.Contains(NodeRuntimeResolver.SupportedVersions, exception.Message, StringComparison.Ordinal); + Assert.Contains( + $"version {version} does not match bundled version 24.16.0", + exception.Message, + StringComparison.Ordinal); } [Fact] - public async Task ResolveRejectsMalformedVersion() + public void ResolveRejectsMalformedVersion() { - InvalidOperationException exception = await Assert.ThrowsAsync< + InvalidOperationException exception = Assert.Throws< InvalidOperationException>( - () => ResolveAsync( - ["C:\\Node\\node.exe"], - _ => Task.FromResult("not-node"), + () => Resolve( + _ => "not-node", Architecture.X64, Architecture.X64)); - Assert.Contains("invalid version", exception.Message, StringComparison.Ordinal); + Assert.Contains("invalid product version", exception.Message, StringComparison.Ordinal); } [Fact] - public async Task ResolveRejectsPrereleaseVersion() + public void ResolveRejectsPrereleaseVersion() { - InvalidOperationException exception = await Assert.ThrowsAsync< + InvalidOperationException exception = Assert.Throws< InvalidOperationException>( - () => ResolveAsync( - ["C:\\Node\\node.exe"], - _ => Task.FromResult("v24.15.0-rc.1"), + () => Resolve( + _ => "v24.15.0-rc.1", Architecture.X64, Architecture.X64)); - Assert.Contains("invalid version", exception.Message, StringComparison.Ordinal); + Assert.Contains("invalid product version", exception.Message, StringComparison.Ordinal); } [Fact] - public async Task ResolveRejectsIncompatibleArchitecture() + public void ResolveRejectsIncompatibleArchitecture() { - InvalidOperationException exception = await Assert.ThrowsAsync< + InvalidOperationException exception = Assert.Throws< InvalidOperationException>( - () => ResolveAsync( - ["C:\\Node\\node.exe"], - _ => Task.FromResult("v24.16.0"), + () => Resolve( + _ => "v24.16.0", Architecture.Arm64, Architecture.X64)); @@ -91,50 +92,28 @@ public async Task ResolveRejectsIncompatibleArchitecture() } [Fact] - public async Task ResolveReportsVersionQueryFailure() + public void ResolveReportsVersionReadFailure() { - InvalidOperationException exception = await Assert.ThrowsAsync< + InvalidOperationException exception = Assert.Throws< InvalidOperationException>( - () => ResolveAsync( - ["C:\\Node\\node.exe"], - _ => Task.FromException( - new InvalidOperationException("query failed")), + () => Resolve( + _ => throw new IOException("version read failed"), Architecture.X64, Architecture.X64)); - Assert.Contains("query failed", exception.Message, StringComparison.Ordinal); + Assert.Contains("version read failed", exception.Message, StringComparison.Ordinal); Assert.Contains("clawctl setup", exception.Message, StringComparison.Ordinal); } - [Theory] - [InlineData(22, 22, 2, false)] - [InlineData(22, 22, 3, true)] - [InlineData(23, 99, 0, false)] - [InlineData(24, 14, 99, false)] - [InlineData(24, 15, 0, true)] - [InlineData(25, 8, 99, false)] - [InlineData(25, 9, 0, true)] - [InlineData(26, 0, 0, true)] - public void SupportedVersionsMatchPackagedOpenClawRequirement( - int major, - int minor, - int build, - bool expected) - { - Assert.Equal( - expected, - NodeRuntimeResolver.IsSupported(new Version(major, minor, build))); - } - - private static Task ResolveAsync( - IReadOnlyList candidates, - Func> queryVersion, + private static NodeRuntime Resolve( + Func readVersion, Architecture candidateArchitecture, - Architecture requiredArchitecture) => - NodeRuntimeResolver.ResolveAsync( - candidates, - (path, _) => queryVersion(path), + Architecture requiredArchitecture, + string expectedVersion = "24.16.0") => + NodeRuntimeResolver.Resolve( + "C:\\Node\\node.exe", + Version.Parse(expectedVersion), + readVersion, _ => candidateArchitecture, - requiredArchitecture, - CancellationToken.None); + requiredArchitecture); } From cc1d833182813a3ee9aebd7fe9a4e7a16c7ec052 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Sat, 12 Sep 2026 00:30:21 -0700 Subject: [PATCH 14/35] chore(ci): finalize Artifact Signing setup (#24) * chore(ci): finalize Artifact Signing setup * docs: name the dedicated packaging signer * docs: use the MSIX signing identity name * fix(ci): recurse into signing artifact folders --- .github/workflows/gateway-msix.yml | 12 ++++-- README.md | 29 +++++++++++++++ scripts/Test-WorkflowSigningConfiguration.ps1 | 37 +++++++++++++++++++ 3 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 scripts/Test-WorkflowSigningConfiguration.ps1 diff --git a/.github/workflows/gateway-msix.yml b/.github/workflows/gateway-msix.yml index 81c0b75..2aff1cb 100644 --- a/.github/workflows/gateway-msix.yml +++ b/.github/workflows/gateway-msix.yml @@ -84,6 +84,11 @@ jobs: run: > .\scripts\Test-SigningInputs.Tests.ps1 + - name: Test signing workflow configuration + shell: pwsh + run: > + .\scripts\Test-WorkflowSigningConfiguration.ps1 + - name: Test workflow package version shell: pwsh run: > @@ -404,9 +409,9 @@ jobs: - name: Azure login uses: azure/login@v3 with: - client-id: ${{ secrets.AZURE_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_TENANT_ID }} - subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + client-id: ${{ vars.AZURE_CLIENT_ID }} + tenant-id: ${{ vars.AZURE_TENANT_ID }} + subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }} - name: Sign MSIX packages uses: azure/artifact-signing-action@v2 @@ -416,6 +421,7 @@ jobs: certificate-profile-name: openclaw files-folder: artifacts files-folder-filter: msix + files-folder-recurse: true files-folder-depth: 2 file-digest: SHA256 timestamp-rfc3161: http://timestamp.acs.microsoft.com diff --git a/README.md b/README.md index 064acd2..b874ae8 100644 --- a/README.md +++ b/README.md @@ -170,6 +170,35 @@ Test-signing private keys are generated only on the temporary GitHub runner and are deleted before artifacts are uploaded. No signing secret or private key is stored in the repository. +### Official signing setup + +The `release-signing` GitHub environment must define these environment +variables (they are identifiers, not credentials): + +- `AZURE_CLIENT_ID`: application (client) ID of the dedicated + `openclaw-windows-msix-signing` Entra application; +- `AZURE_TENANT_ID`: Entra tenant ID; +- `AZURE_SUBSCRIPTION_ID`: Azure subscription containing the signing resource. + +Do not create an `AZURE_CLIENT_SECRET`. The `sign-msix` job requests a +short-lived Azure token with GitHub OIDC. The Entra application must have a +federated identity credential with: + +- issuer: `https://token.actions.githubusercontent.com`; +- subject: + `repo:openclaw@252820863/openclaw-windows-packaging@1347889239:environment:release-signing`; +- audience: `api://AzureADTokenExchange`. + +This repository was created after GitHub's immutable OIDC subject rollout, so +the subject includes the organization and repository IDs. The older mutable +`repo:openclaw/openclaw-windows-packaging:...` form will not match its tokens. + +The service principal must have `Artifact Signing Certificate Profile Signer` +on the `openclaw` certificate profile (or a containing scope). The workflow +uses account `openclaw`, certificate profile `openclaw`, and endpoint +`https://eus.codesigning.azure.net/`. The expected public certificate subject +is recorded in `release-policy.json`. + ## Installed data | Data | Default path | diff --git a/scripts/Test-WorkflowSigningConfiguration.ps1 b/scripts/Test-WorkflowSigningConfiguration.ps1 new file mode 100644 index 0000000..b47146f --- /dev/null +++ b/scripts/Test-WorkflowSigningConfiguration.ps1 @@ -0,0 +1,37 @@ +[CmdletBinding()] +param() + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$repositoryRoot = Split-Path $PSScriptRoot -Parent +$workflowPath = Join-Path ` + $repositoryRoot ` + '.github\workflows\gateway-msix.yml' +$workflow = Get-Content -LiteralPath $workflowPath -Raw + +$requiredFragments = @( + 'environment: release-signing' + 'id-token: write' + 'uses: azure/login@v3' + 'client-id: ${{ vars.AZURE_CLIENT_ID }}' + 'tenant-id: ${{ vars.AZURE_TENANT_ID }}' + 'subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}' + 'uses: azure/artifact-signing-action@v2' + 'files-folder-recurse: true' + 'endpoint: https://eus.codesigning.azure.net/' + 'signing-account-name: openclaw' + 'certificate-profile-name: openclaw' +) + +foreach ($fragment in $requiredFragments) { + if (-not $workflow.Contains($fragment, [StringComparison]::Ordinal)) { + throw "Signing workflow is missing required configuration: $fragment" + } +} + +if ($workflow.Contains('AZURE_CLIENT_SECRET', [StringComparison]::Ordinal)) { + throw 'Signing workflow must use OIDC, not an Azure client secret.' +} + +Write-Host 'Gateway MSIX signing workflow configuration passed.' From 54d4c43adfd8d2ef5f8c8d5a52482e988125780b Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Sat, 12 Sep 2026 00:54:57 -0700 Subject: [PATCH 15/35] feat(ci): publish signed Gateway MSIX releases (#33) * feat(ci): publish durable signed MSIX releases * feat(ci): publish multi-architecture MSIX bundle * fix(ci): authorize exact MSIX bundle contents * fix(ci): use explicit zero-version signing proof --- .github/copilot-instructions.md | 8 +- .github/workflows/gateway-msix.yml | 216 +++++++++++++++++- README.md | 34 ++- release-policy.json | 4 +- scripts/Build-MSIX.ps1 | 2 + scripts/Build-MSIXBundle.ps1 | 126 ++++++++++ scripts/Get-WorkflowPackageVersion.ps1 | 34 ++- scripts/Test-Build-MSIXBundle.Tests.ps1 | 110 +++++++++ scripts/Test-SigningInputs.Tests.ps1 | 114 ++++++++- scripts/Test-SigningInputs.ps1 | 99 ++++++++ scripts/Test-WorkflowPackageVersion.Tests.ps1 | 34 +++ scripts/Test-WorkflowSigningConfiguration.ps1 | 14 ++ 12 files changed, 779 insertions(+), 16 deletions(-) create mode 100644 scripts/Build-MSIXBundle.ps1 create mode 100644 scripts/Test-Build-MSIXBundle.Tests.ps1 diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 3790db3..851251e 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -120,12 +120,18 @@ and ARM64 separately. - Keep x64 and ARM64 behavior synchronized across the workflow matrix, scripts, project runtime identifiers, manifest content, payload metadata, and signing validation. +- Official releases combine the x64 and ARM64 packages into one signed + `.msixbundle` while retaining signed standalone packages for explicit + architecture-specific deployment. Compose the bundle before signing; bundle + signing recursively covers its contained packages. - Metadata files are part of the release trust chain, not incidental build output. Changes to their fields must be coordinated across payload creation, MSIX creation, signing validation, workflow artifacts, and tests. - Keep the workflow's manual `openclaw_ref` default and automatic `env.OPENCLAW_REF` fallback identical. Official-release changes also update - the reviewed immutable commit in `release-policy.json`. + the reviewed immutable commit and stable or correction tag in + `release-policy.json`. The tag determines the four-part MSIX identity + version and the permanent GitHub Release tag. - The launcher is NativeAOT. `dotnet build` and the xUnit suite exercise a JIT build, so run the NativeAOT publish path when changing reflection, interop, or trimming-sensitive code. diff --git a/.github/workflows/gateway-msix.yml b/.github/workflows/gateway-msix.yml index 2aff1cb..3b6e176 100644 --- a/.github/workflows/gateway-msix.yml +++ b/.github/workflows/gateway-msix.yml @@ -94,6 +94,11 @@ jobs: run: > .\scripts\Test-WorkflowPackageVersion.Tests.ps1 + - name: Test MSIX bundle build + shell: pwsh + run: > + .\scripts\Test-Build-MSIXBundle.Tests.ps1 + - name: Test Git hooks shell: pwsh run: > @@ -259,10 +264,20 @@ jobs: - name: Compose unsigned MSIX shell: pwsh + env: + SIGNING_MODE: ${{ github.event_name == 'workflow_dispatch' && inputs.signing_mode || 'unsigned' }} run: | + $versionParameters = @{ + RunNumber = '${{ github.run_number }}' + RunAttempt = '${{ github.run_attempt }}' + } + if ($env:SIGNING_MODE -eq 'official') { + $policy = Get-Content -LiteralPath .\release-policy.json -Raw | + ConvertFrom-Json + $versionParameters.ReleaseVersion = [string]$policy.packageVersion + } $packageVersion = .\scripts\Get-WorkflowPackageVersion.ps1 ` - -RunNumber '${{ github.run_number }}' ` - -RunAttempt '${{ github.run_attempt }}' + @versionParameters .\scripts\Build-MSIX.ps1 ` -PayloadDirectory '${{ runner.temp }}\openclaw-payload' ` @@ -330,6 +345,59 @@ jobs: if-no-files-found: error retention-days: 7 + build-msix-bundle: + name: Build unsigned multi-architecture Gateway MSIX bundle + needs: build-msix + runs-on: windows-latest + steps: + - name: Check out repository + uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Download unsigned x64 package + uses: actions/download-artifact@v8 + with: + name: openclaw-gateway-msix-unsigned-x64 + path: artifacts\x64 + + - name: Download unsigned ARM64 package + uses: actions/download-artifact@v8 + with: + name: openclaw-gateway-msix-unsigned-arm64 + path: artifacts\arm64 + + - name: Compose unsigned multi-architecture MSIX bundle + shell: pwsh + env: + SIGNING_MODE: ${{ github.event_name == 'workflow_dispatch' && inputs.signing_mode || 'unsigned' }} + run: | + $versionParameters = @{ + RunNumber = '${{ github.run_number }}' + RunAttempt = '${{ github.run_attempt }}' + } + if ($env:SIGNING_MODE -eq 'official') { + $policy = Get-Content -LiteralPath .\release-policy.json -Raw | + ConvertFrom-Json + $versionParameters.ReleaseVersion = [string]$policy.packageVersion + } + $packageVersion = .\scripts\Get-WorkflowPackageVersion.ps1 ` + @versionParameters + + .\scripts\Build-MSIXBundle.ps1 ` + -X64Package artifacts\x64\OpenClawGateway-x64.msix ` + -Arm64Package artifacts\arm64\OpenClawGateway-arm64.msix ` + -PackageVersion $packageVersion ` + -OutputPath artifacts\bundle\OpenClawGateway.msixbundle + + - name: Upload unsigned multi-architecture MSIX bundle + uses: actions/upload-artifact@v7 + with: + name: openclaw-gateway-msix-unsigned-bundle + path: artifacts\bundle\OpenClawGateway.msixbundle + if-no-files-found: error + retention-days: 7 + reject-untrusted-official-signing: name: Reject official signing outside main if: ${{ github.event_name == 'workflow_dispatch' && inputs.signing_mode == 'official' && github.ref != 'refs/heads/main' }} @@ -346,8 +414,14 @@ jobs: authorize-signing: name: Authorize official Gateway MSIX signing if: ${{ github.event_name == 'workflow_dispatch' && inputs.signing_mode == 'official' && github.ref == 'refs/heads/main' }} - needs: build-msix + needs: + - build-msix + - build-msix-bundle runs-on: windows-latest + outputs: + package_version: ${{ steps.release.outputs.package_version }} + release_tag: ${{ steps.release.outputs.release_tag }} + release_version: ${{ steps.release.outputs.release_version }} permissions: actions: read contents: read @@ -369,6 +443,28 @@ jobs: name: openclaw-gateway-msix-unsigned-arm64 path: artifacts\arm64 + - name: Download unsigned multi-architecture bundle + uses: actions/download-artifact@v8 + with: + name: openclaw-gateway-msix-unsigned-bundle + path: artifacts\bundle + + - name: Resolve official release metadata + id: release + shell: pwsh + run: | + $policy = Get-Content -LiteralPath .\release-policy.json -Raw | + ConvertFrom-Json + $releaseTag = ([string]$policy.releaseTag).Trim() + $packageVersion = .\scripts\Get-WorkflowPackageVersion.ps1 ` + -RunNumber '${{ github.run_number }}' ` + -RunAttempt '${{ github.run_attempt }}' ` + -ReleaseVersion ([string]$policy.packageVersion) + + "package_version=$packageVersion" >> $env:GITHUB_OUTPUT + "release_tag=$releaseTag" >> $env:GITHUB_OUTPUT + "release_version=$($releaseTag.Substring(1))" >> $env:GITHUB_OUTPUT + - name: Enforce official signing policy shell: pwsh env: @@ -378,6 +474,7 @@ jobs: .\scripts\Test-SigningInputs.ps1 ` -ArtifactsDirectory artifacts ` -PolicyPath .\release-policy.json ` + -BundlePath artifacts\bundle\OpenClawGateway.msixbundle ` -RequestedRef $env:OPENCLAW_REF ` -PackagingCommit $env:PACKAGING_COMMIT @@ -386,6 +483,7 @@ jobs: if: ${{ needs.authorize-signing.result == 'success' }} needs: - build-msix + - build-msix-bundle - authorize-signing runs-on: windows-latest environment: release-signing @@ -406,6 +504,12 @@ jobs: name: openclaw-gateway-msix-unsigned-arm64 path: artifacts\arm64 + - name: Download unsigned multi-architecture bundle + uses: actions/download-artifact@v8 + with: + name: openclaw-gateway-msix-unsigned-bundle + path: artifacts\bundle + - name: Azure login uses: azure/login@v3 with: @@ -413,7 +517,7 @@ jobs: tenant-id: ${{ vars.AZURE_TENANT_ID }} subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }} - - name: Sign MSIX packages + - name: Sign standalone MSIX packages uses: azure/artifact-signing-action@v2 with: endpoint: https://eus.codesigning.azure.net/ @@ -427,6 +531,17 @@ jobs: timestamp-rfc3161: http://timestamp.acs.microsoft.com timestamp-digest: SHA256 + - name: Sign multi-architecture MSIX bundle + uses: azure/artifact-signing-action@v2 + with: + endpoint: https://eus.codesigning.azure.net/ + signing-account-name: openclaw + certificate-profile-name: openclaw + files: ${{ github.workspace }}\artifacts\bundle\OpenClawGateway.msixbundle + file-digest: SHA256 + timestamp-rfc3161: http://timestamp.acs.microsoft.com + timestamp-digest: SHA256 + - name: Verify signatures and refresh metadata shell: pwsh run: | @@ -460,6 +575,24 @@ jobs: Set-Content -LiteralPath $metadataPath -Encoding utf8 } + $bundle = Get-Item ` + -LiteralPath artifacts\bundle\OpenClawGateway.msixbundle + $bundleSignature = Get-AuthenticodeSignature ` + -LiteralPath $bundle.FullName + if ($bundleSignature.Status -ne 'Valid') { + throw "$($bundle.Name) signature status was $($bundleSignature.Status)." + } + if (-not [string]::Equals( + $bundleSignature.SignerCertificate.Subject, + $expectedSubject, + [StringComparison]::OrdinalIgnoreCase + )) { + throw ( + "$($bundle.Name) signer was unexpected: " + + $bundleSignature.SignerCertificate.Subject + ) + } + - name: Upload signed x64 MSIX uses: actions/upload-artifact@v7 with: @@ -475,3 +608,78 @@ jobs: path: artifacts\arm64\ if-no-files-found: error retention-days: 7 + + - name: Upload signed multi-architecture MSIX bundle + uses: actions/upload-artifact@v7 + with: + name: openclaw-gateway-msix-bundle + path: artifacts\bundle\OpenClawGateway.msixbundle + if-no-files-found: error + retention-days: 7 + + publish-release: + name: Publish signed Gateway MSIX release + if: ${{ needs.sign-msix.result == 'success' }} + needs: + - authorize-signing + - sign-msix + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Download signed x64 package + uses: actions/download-artifact@v8 + with: + name: openclaw-gateway-msix-x64 + path: signed/x64 + + - name: Download signed ARM64 package + uses: actions/download-artifact@v8 + with: + name: openclaw-gateway-msix-arm64 + path: signed/arm64 + + - name: Download signed multi-architecture bundle + uses: actions/download-artifact@v8 + with: + name: openclaw-gateway-msix-bundle + path: signed/bundle + + - name: Stage versioned release assets + shell: bash + env: + RELEASE_VERSION: ${{ needs.authorize-signing.outputs.release_version }} + run: | + set -euo pipefail + mkdir release-assets + cp signed/x64/OpenClawGateway-x64.msix \ + "release-assets/OpenClawGateway-${RELEASE_VERSION}-x64.msix" + cp signed/arm64/OpenClawGateway-arm64.msix \ + "release-assets/OpenClawGateway-${RELEASE_VERSION}-arm64.msix" + cp signed/bundle/OpenClawGateway.msixbundle \ + "release-assets/OpenClawGateway-${RELEASE_VERSION}.msixbundle" + + - name: Create permanent GitHub release + uses: softprops/action-gh-release@v3 + with: + tag_name: ${{ needs.authorize-signing.outputs.release_tag }} + target_commitish: ${{ github.sha }} + name: OpenClaw Gateway MSIX ${{ needs.authorize-signing.outputs.release_tag }} + generate_release_notes: true + prerelease: false + make_latest: true + overwrite_files: false + fail_on_unmatched_files: true + files: | + release-assets/*.msix + release-assets/*.msixbundle + body: | + Packages OpenClaw Gateway `${{ needs.authorize-signing.outputs.release_tag }}` + from [`openclaw/openclaw@${{ inputs.openclaw_ref }}`](https://github.com/openclaw/openclaw/commit/${{ inputs.openclaw_ref }}). + + ### Downloads + - **Recommended:** `OpenClawGateway-${{ needs.authorize-signing.outputs.release_version }}.msixbundle` + - **x64:** `OpenClawGateway-${{ needs.authorize-signing.outputs.release_version }}-x64.msix` + - **ARM64:** `OpenClawGateway-${{ needs.authorize-signing.outputs.release_version }}-arm64.msix` + + The packages are signed by OpenClaw Foundation through Azure Artifact Signing. diff --git a/README.md b/README.md index b874ae8..2020591 100644 --- a/README.md +++ b/README.md @@ -129,12 +129,15 @@ The payload artifact records the requested ref and resolved upstream commit in OpenClaw commit, while embedded `payload-files.json` records every packaged application file's path, length, and SHA-256. -`release-policy.json` records the immutable OpenClaw commit approved for -official signing. Updating that policy requires a reviewed repository change. -Official signing runs only from `main` and verifies the workflow input, both +`release-policy.json` records the immutable OpenClaw commit and payload version +approved for official signing, plus the independent MSIX package version and +release tag. Updating that +policy requires a reviewed repository change. Official signing runs only from +`main` and verifies the workflow input, policy-approved package version, both architecture metadata files, both MSIX hashes, the embedded manifests, and -every file against the embedded application inventory before requesting Azure -credentials. +every file against the embedded application inventory. It also byte-compares +the bundle's embedded packages with those authorized standalone packages before +requesting Azure credentials. ## Build and test @@ -162,7 +165,8 @@ validation. Manual runs support three signing modes: temporary self-signed certificate plus the public `.cer` needed for local installation; - `official` requires the approved immutable commit from - `release-policy.json` and may run only from `main`. + `release-policy.json`, may run only from `main`, and publishes the signed + packages as permanent assets on a GitHub Release named by the policy. Official signing uses the protected `release-signing` environment, Azure OIDC, and the existing OpenClaw Artifact Signing account and certificate profile. @@ -170,6 +174,24 @@ Test-signing private keys are generated only on the temporary GitHub runner and are deleted before artifacts are uploaded. No signing secret or private key is stored in the repository. +Official releases use the independent four-part numeric `packageVersion` and +`releaseTag` from `release-policy.json`. The initial signing proof uses package +version `0.0.0.0` and tag `v0.0.0.0`; a later policy change can establish the +long-term Gateway-to-MSIX version mapping. The workflow creates the tag in this +repository and a GitHub Release with generated release notes. Each release +contains a signed, multi-architecture +`OpenClawGateway-.msixbundle` as the recommended download, plus signed +`OpenClawGateway--x64.msix` and +`OpenClawGateway--arm64.msix` packages for architecture-specific +deployment. The duplicate GitHub Actions artifacts remain short-lived transport +and diagnostic copies. + +An `.msixbundle` is a single installable container for the x64 and ARM64 MSIX +packages; Windows selects the package appropriate for the device. An +`.appinstaller` file is separate update-channel metadata rather than an +alternative package format. This repository does not publish one yet, so GitHub +Release installs do not opt devices into automatic update checks. + ### Official signing setup The `release-signing` GitHub environment must define these environment diff --git a/release-policy.json b/release-policy.json index daef22a..6bc2f23 100644 --- a/release-policy.json +++ b/release-policy.json @@ -1,6 +1,8 @@ { "repository": "https://github.com/openclaw/openclaw", - "releaseTag": "v2026.8.2", + "releaseTag": "v0.0.0.0", + "packageVersion": "0.0.0.0", + "payloadPackageVersion": "2026.8.2", "approvedCommit": "0965053fe6b9341776df147a6934b7485c60b5ca", "publisher": "CN=OpenClaw Foundation, O=OpenClaw Foundation, L=Mill Valley, S=California, C=US" } diff --git a/scripts/Build-MSIX.ps1 b/scripts/Build-MSIX.ps1 index d690f86..6bab046 100644 --- a/scripts/Build-MSIX.ps1 +++ b/scripts/Build-MSIX.ps1 @@ -166,6 +166,7 @@ if ( $payloadInfo.repository -ne 'https://github.com/openclaw/openclaw' -or $payloadInfo.architecture -ne $Architecture -or $payloadInfo.layout -ne 'expanded-directory' -or + [string]::IsNullOrWhiteSpace([string]$payloadInfo.packageVersion) -or $payloadInfo.requestedRef -isnot [string] -or [string]::IsNullOrWhiteSpace($payloadInfo.requestedRef) -or $payloadInfo.resolvedCommit -notmatch '^[0-9a-fA-F]{40}$' @@ -461,6 +462,7 @@ try { payloadRepository = $payloadInfo.repository payloadRequestedRef = $payloadInfo.requestedRef payloadResolvedCommit = $payloadInfo.resolvedCommit.ToLowerInvariant() + payloadPackageVersion = [string]$payloadInfo.packageVersion payloadLayout = 'immutable-package' payloadFileCount = $payloadFiles.Count architecture = $Architecture diff --git a/scripts/Build-MSIXBundle.ps1 b/scripts/Build-MSIXBundle.ps1 new file mode 100644 index 0000000..a1a9727 --- /dev/null +++ b/scripts/Build-MSIXBundle.ps1 @@ -0,0 +1,126 @@ +[CmdletBinding()] +param( + [Parameter(Mandatory)] + [string]$X64Package, + + [Parameter(Mandatory)] + [string]$Arm64Package, + + [Parameter(Mandatory)] + [string]$PackageVersion, + + [Parameter(Mandatory)] + [string]$OutputPath, + + [string]$MakeAppxPath +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +function Resolve-MakeAppx { + if (-not [string]::IsNullOrWhiteSpace($MakeAppxPath)) { + return (Resolve-Path -LiteralPath $MakeAppxPath).Path + } + + $command = Get-Command ` + MakeAppx.exe ` + -CommandType Application ` + -ErrorAction SilentlyContinue + if ($null -ne $command) { + return $command.Source + } + + $windowsKits = Join-Path ` + ${env:ProgramFiles(x86)} ` + 'Windows Kits\10\bin' + $candidate = Get-ChildItem ` + -LiteralPath $windowsKits ` + -Filter MakeAppx.exe ` + -File ` + -Recurse ` + -ErrorAction SilentlyContinue | + Where-Object { $_.Directory.Name -eq 'x64' } | + Sort-Object FullName -Descending | + Select-Object -First 1 + if ($null -eq $candidate) { + throw 'MakeAppx.exe was not found in PATH or the Windows 10 SDK.' + } + + $candidate.FullName +} + +foreach ($package in @($X64Package, $Arm64Package)) { + if (-not (Test-Path -LiteralPath $package -PathType Leaf)) { + throw "Required MSIX package was not found: $package" + } + if ([IO.Path]::GetExtension($package) -ine '.msix') { + throw "Bundle input must be an MSIX package: $package" + } +} + +$version = $null +if (-not [version]::TryParse($PackageVersion, [ref]$version)) { + throw "PackageVersion is not a valid four-part version: $PackageVersion" +} +$versionSegments = @($PackageVersion.Split('.')) +if ($versionSegments.Count -ne 4) { + throw 'PackageVersion must contain four numeric components.' +} +foreach ($segment in $versionSegments) { + [uint16]$value = 0 + if (-not [uint16]::TryParse($segment, [ref]$value) -or $value -gt 65534) { + throw "Invalid MSIX bundle version component: $segment" + } +} + +$resolvedX64Package = (Resolve-Path -LiteralPath $X64Package).Path +$resolvedArm64Package = (Resolve-Path -LiteralPath $Arm64Package).Path +if ($resolvedX64Package -eq $resolvedArm64Package) { + throw 'The x64 and ARM64 bundle inputs must be different packages.' +} + +$resolvedOutputPath = [IO.Path]::GetFullPath($OutputPath) +if ([IO.Path]::GetExtension($resolvedOutputPath) -ine '.msixbundle') { + throw 'OutputPath must use the .msixbundle extension.' +} +if (Test-Path -LiteralPath $resolvedOutputPath) { + throw "MSIX bundle output already exists: $resolvedOutputPath" +} + +$outputDirectory = Split-Path $resolvedOutputPath -Parent +New-Item -Path $outputDirectory -ItemType Directory -Force | Out-Null +$workRoot = Join-Path ` + ([IO.Path]::GetTempPath()) ` + "openclaw-msixbundle-$([guid]::NewGuid().ToString('N'))" +$bundleInput = Join-Path $workRoot 'packages' +New-Item -Path $bundleInput -ItemType Directory -Force | Out-Null + +try { + Copy-Item ` + -LiteralPath $resolvedX64Package ` + -Destination (Join-Path $bundleInput 'OpenClawGateway-x64.msix') + Copy-Item ` + -LiteralPath $resolvedArm64Package ` + -Destination (Join-Path $bundleInput 'OpenClawGateway-arm64.msix') + + $resolvedMakeAppx = Resolve-MakeAppx + & $resolvedMakeAppx bundle ` + /v ` + /bv $PackageVersion ` + /d $bundleInput ` + /p $resolvedOutputPath + if ($LASTEXITCODE -ne 0) { + throw "MakeAppx.exe failed to build the MSIX bundle. Exit code: $LASTEXITCODE." + } + if (-not (Test-Path -LiteralPath $resolvedOutputPath -PathType Leaf)) { + throw 'MakeAppx.exe completed without producing the requested bundle.' + } + + Write-Host "Unsigned MSIX bundle is ready: $resolvedOutputPath" +} +finally { + if ([IO.Directory]::Exists($workRoot)) { + [IO.Directory]::Delete($workRoot, $true) + } +} diff --git a/scripts/Get-WorkflowPackageVersion.ps1 b/scripts/Get-WorkflowPackageVersion.ps1 index 1137607..9fb31fa 100644 --- a/scripts/Get-WorkflowPackageVersion.ps1 +++ b/scripts/Get-WorkflowPackageVersion.ps1 @@ -4,7 +4,9 @@ param( [long]$RunNumber, [Parameter(Mandatory)] - [long]$RunAttempt + [long]$RunAttempt, + + [string]$ReleaseVersion ) Set-StrictMode -Version Latest @@ -13,6 +15,36 @@ $ErrorActionPreference = 'Stop' $maximumComponent = 65534L $componentBase = $maximumComponent + 1L +if (-not [string]::IsNullOrWhiteSpace($ReleaseVersion)) { + $releaseMatch = [regex]::Match( + $ReleaseVersion.Trim(), + '^(?\d+)\.(?\d+)\.(?\d+)\.(?\d+)$' + ) + if (-not $releaseMatch.Success) { + throw ( + "ReleaseVersion '$ReleaseVersion' must contain exactly four " + + 'numeric components, such as 0.0.0.0.' + ) + } + + $components = @( + [long]::Parse($releaseMatch.Groups['major'].Value), + [long]::Parse($releaseMatch.Groups['minor'].Value), + [long]::Parse($releaseMatch.Groups['build'].Value), + [long]::Parse($releaseMatch.Groups['revision'].Value) + ) + foreach ($component in $components) { + if ($component -gt $maximumComponent) { + throw ( + "ReleaseVersion '$ReleaseVersion' contains a component greater than " + + "$maximumComponent." + ) + } + } + + return ($components -join '.') +} + if ($RunNumber -lt 1) { throw 'RunNumber must be greater than zero.' } diff --git a/scripts/Test-Build-MSIXBundle.Tests.ps1 b/scripts/Test-Build-MSIXBundle.Tests.ps1 new file mode 100644 index 0000000..4cc5169 --- /dev/null +++ b/scripts/Test-Build-MSIXBundle.Tests.ps1 @@ -0,0 +1,110 @@ +[CmdletBinding()] +param() + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$scriptPath = Join-Path $PSScriptRoot 'Build-MSIXBundle.ps1' +$testRoot = Join-Path $env:TEMP ` + "openclaw-bundle-tests-$([guid]::NewGuid().ToString('N'))" +New-Item -Path $testRoot -ItemType Directory -Force | Out-Null + +function Assert-Fails { + param( + [Parameter(Mandatory)] + [scriptblock]$Action, + + [Parameter(Mandatory)] + [string]$MessagePattern + ) + + try { + & $Action + } + catch { + if ($_.Exception.Message -notmatch $MessagePattern) { + throw ( + "Expected failure matching '$MessagePattern'; received: " + + $_.Exception.Message + ) + } + return + } + + throw "Expected failure matching '$MessagePattern', but the action succeeded." +} + +try { + $x64Package = Join-Path $testRoot 'x64.msix' + $arm64Package = Join-Path $testRoot 'arm64.msix' + Set-Content -LiteralPath $x64Package -Value 'x64' -Encoding ascii + Set-Content -LiteralPath $arm64Package -Value 'arm64' -Encoding ascii + + $fakeMakeAppx = Join-Path $testRoot 'MakeAppx.cmd' + $makeAppxArguments = Join-Path $testRoot 'makeappx-arguments.txt' + $env:OPENCLAW_BUNDLE_TEST_ARGUMENTS = $makeAppxArguments + @' +@echo off +echo %* > "%OPENCLAW_BUNDLE_TEST_ARGUMENTS%" +set output= +:parse +if "%~1"=="" goto done +if /I "%~1"=="/p" ( + set output=%~2 + shift +) +shift +goto parse +:done +if "%output%"=="" exit /b 2 +type nul > "%output%" +'@ | Set-Content -LiteralPath $fakeMakeAppx -Encoding ascii + + $bundle = Join-Path $testRoot 'OpenClawGateway.msixbundle' + & $scriptPath ` + -X64Package $x64Package ` + -Arm64Package $arm64Package ` + -PackageVersion '2026.9.4.0' ` + -OutputPath $bundle ` + -MakeAppxPath $fakeMakeAppx + if (-not (Test-Path -LiteralPath $bundle -PathType Leaf)) { + throw 'The bundle builder did not preserve the MakeAppx output.' + } + $arguments = Get-Content -LiteralPath $makeAppxArguments -Raw + if ($arguments -notmatch '/bv 2026\.9\.4\.0') { + throw "MakeAppx did not receive the expected bundle version: $arguments" + } + + Assert-Fails -MessagePattern 'must be different' -Action { + & $scriptPath ` + -X64Package $x64Package ` + -Arm64Package $x64Package ` + -PackageVersion '2026.9.4.0' ` + -OutputPath (Join-Path $testRoot 'duplicate.msixbundle') ` + -MakeAppxPath $fakeMakeAppx + } + Assert-Fails -MessagePattern 'four numeric components' -Action { + & $scriptPath ` + -X64Package $x64Package ` + -Arm64Package $arm64Package ` + -PackageVersion '2026.9.4' ` + -OutputPath (Join-Path $testRoot 'bad-version.msixbundle') ` + -MakeAppxPath $fakeMakeAppx + } + Assert-Fails -MessagePattern 'already exists' -Action { + & $scriptPath ` + -X64Package $x64Package ` + -Arm64Package $arm64Package ` + -PackageVersion '2026.9.4.0' ` + -OutputPath $bundle ` + -MakeAppxPath $fakeMakeAppx + } + + Write-Host 'MSIX bundle build tests passed.' +} +finally { + Remove-Item Env:OPENCLAW_BUNDLE_TEST_ARGUMENTS -ErrorAction SilentlyContinue + if ([IO.Directory]::Exists($testRoot)) { + [IO.Directory]::Delete($testRoot, $true) + } +} diff --git a/scripts/Test-SigningInputs.Tests.ps1 b/scripts/Test-SigningInputs.Tests.ps1 index e31b3e0..4a8f09f 100644 --- a/scripts/Test-SigningInputs.Tests.ps1 +++ b/scripts/Test-SigningInputs.Tests.ps1 @@ -8,6 +8,13 @@ $repositoryRoot = Split-Path $PSScriptRoot -Parent $policyPath = Join-Path $repositoryRoot 'release-policy.json' $policy = Get-Content -LiteralPath $policyPath -Raw | ConvertFrom-Json $approvedCommit = [string]$policy.approvedCommit +$approvedPackageVersion = & ( + Join-Path $PSScriptRoot 'Get-WorkflowPackageVersion.ps1' +) ` + -RunNumber 1 ` + -RunAttempt 1 ` + -ReleaseVersion ([string]$policy.packageVersion) +$approvedPayloadVersion = [string]$policy.payloadPackageVersion $packagingCommit = '1111111111111111111111111111111111111111' $testRoot = Join-Path $env:TEMP ( "openclaw-signing-policy-$([guid]::NewGuid().ToString('N'))" @@ -24,6 +31,8 @@ function New-TestArtifact { [string]$PayloadCommit = $approvedCommit, + [string]$PayloadPackageVersion = $approvedPayloadVersion, + [bool]$SourceTreeDirty = $false, [bool]$IncludeBundledNode = $false, @@ -110,7 +119,7 @@ function New-TestArtifact { "@ | Set-Content ` @@ -141,13 +150,14 @@ function New-TestArtifact { payloadRepository = $policy.repository payloadRequestedRef = $PayloadCommit payloadResolvedCommit = $PayloadCommit + payloadPackageVersion = $PayloadPackageVersion payloadLayout = 'immutable-package' payloadFileCount = $payloadFiles.Count architecture = $Architecture archive = $msixName sha256 = $msixHash signed = $false - packageVersion = '0.1.1.0' + packageVersion = $approvedPackageVersion publisher = $policy.publisher } | ConvertTo-Json | @@ -161,16 +171,86 @@ function Invoke-PolicyValidation { [Parameter(Mandatory)] [string]$Root, - [string]$RequestedRef = $approvedCommit + [string]$RequestedRef = $approvedCommit, + + [switch]$PreserveBundle ) + if (-not $PreserveBundle) { + New-TestBundle -Root $Root + } + & (Join-Path $PSScriptRoot 'Test-SigningInputs.ps1') ` -ArtifactsDirectory $Root ` -PolicyPath $policyPath ` + -BundlePath (Join-Path $Root 'bundle\OpenClawGateway.msixbundle') ` -RequestedRef $RequestedRef ` -PackagingCommit $packagingCommit } +function New-TestBundle { + param( + [Parameter(Mandatory)] + [string]$Root, + + [string]$X64Package = (Join-Path ` + $Root ` + 'x64\OpenClawGateway-x64.msix') + ) + + $bundleDirectory = Join-Path $Root 'bundle' + $bundleStaging = Join-Path $Root '.bundle-package' + Remove-Item ` + -LiteralPath $bundleDirectory, $bundleStaging ` + -Recurse ` + -Force ` + -ErrorAction SilentlyContinue + $bundleMetadata = Join-Path $bundleStaging 'AppxMetadata' + New-Item ` + -Path $bundleDirectory, $bundleMetadata ` + -ItemType Directory ` + -Force | + Out-Null + + Copy-Item ` + -LiteralPath $X64Package ` + -Destination (Join-Path $bundleStaging 'OpenClawGateway-x64.msix') + Copy-Item ` + -LiteralPath (Join-Path ` + $Root ` + 'arm64\OpenClawGateway-arm64.msix') ` + -Destination (Join-Path $bundleStaging 'OpenClawGateway-arm64.msix') + + @" + + + + + + + + +"@ | Set-Content ` + -LiteralPath (Join-Path ` + $bundleMetadata ` + 'AppxBundleManifest.xml') ` + -Encoding utf8 + + [IO.Compression.ZipFile]::CreateFromDirectory( + $bundleStaging, + (Join-Path $bundleDirectory 'OpenClawGateway.msixbundle') + ) + Remove-Item -LiteralPath $bundleStaging -Recurse -Force +} + function Assert-Fails { param( [Parameter(Mandatory)] @@ -275,6 +355,19 @@ try { Invoke-PolicyValidation -Root $testRoot } + Remove-Item -LiteralPath $testRoot -Recurse -Force + New-Item -Path $testRoot -ItemType Directory | Out-Null + New-TestArtifact ` + -Root $testRoot ` + -Architecture x64 ` + -PayloadPackageVersion '2026.9.4' + New-TestArtifact -Root $testRoot -Architecture arm64 + Assert-Fails ` + -MessagePattern 'metadata is not eligible' ` + -Action { + Invoke-PolicyValidation -Root $testRoot + } + Remove-Item -LiteralPath $testRoot -Recurse -Force New-Item -Path $testRoot -ItemType Directory | Out-Null New-TestArtifact -Root $testRoot -Architecture x64 @@ -452,6 +545,21 @@ try { Invoke-PolicyValidation -Root $testRoot } + Reset-TestArtifacts + $substitutedX64 = Join-Path $testRoot 'substituted-x64.msix' + Copy-Item ` + -LiteralPath (Join-Path ` + $testRoot ` + 'x64\OpenClawGateway-x64.msix') ` + -Destination $substitutedX64 + Add-Content -LiteralPath $substitutedX64 -Value 'substituted' + New-TestBundle -Root $testRoot -X64Package $substitutedX64 + Assert-Fails ` + -MessagePattern 'does not match the authorized standalone package' ` + -Action { + Invoke-PolicyValidation -Root $testRoot -PreserveBundle + } + Write-Host 'Gateway MSIX signing policy tests passed.' } finally { diff --git a/scripts/Test-SigningInputs.ps1 b/scripts/Test-SigningInputs.ps1 index 9b9cd52..3ca2f04 100644 --- a/scripts/Test-SigningInputs.ps1 +++ b/scripts/Test-SigningInputs.ps1 @@ -6,6 +6,9 @@ param( [Parameter(Mandatory)] [string]$PolicyPath, + [Parameter(Mandatory)] + [string]$BundlePath, + [Parameter(Mandatory)] [string]$RequestedRef, @@ -117,12 +120,23 @@ $policy = Get-Content -LiteralPath $resolvedPolicyPath -Raw | if ( $policy.repository -ne 'https://github.com/openclaw/openclaw' -or + [string]::IsNullOrWhiteSpace([string]$policy.releaseTag) -or + $policy.packageVersion -notmatch '^\d+\.\d+\.\d+\.\d+$' -or + $policy.releaseTag -ne "v$($policy.packageVersion)" -or + [string]::IsNullOrWhiteSpace([string]$policy.payloadPackageVersion) -or $policy.approvedCommit -notmatch '^[0-9a-fA-F]{40}$' -or [string]::IsNullOrWhiteSpace([string]$policy.publisher) ) { throw 'The Gateway MSIX release policy is invalid.' } +$approvedPackageVersion = & ( + Join-Path $PSScriptRoot 'Get-WorkflowPackageVersion.ps1' +) ` + -RunNumber 1 ` + -RunAttempt 1 ` + -ReleaseVersion ([string]$policy.packageVersion) +$approvedPayloadVersion = [string]$policy.payloadPackageVersion $approvedCommit = ([string]$policy.approvedCommit).ToLowerInvariant() $normalizedRequestedRef = $RequestedRef.Trim().ToLowerInvariant() if ( @@ -137,6 +151,7 @@ if ( $expectedPackagingCommit = $PackagingCommit.ToLowerInvariant() $expectedPackageVersion = $null +$expectedPackages = @{} foreach ($architecture in @('x64', 'arm64')) { $directory = Join-Path $resolvedArtifactsDirectory $architecture $metadataPath = Join-Path $directory 'msix-metadata.json' @@ -163,6 +178,7 @@ foreach ($architecture in @('x64', 'arm64')) { $metadata.payloadRepository -ne $policy.repository -or $metadata.payloadRequestedRef -ine $approvedCommit -or $metadata.payloadResolvedCommit -ine $approvedCommit -or + $metadata.payloadPackageVersion -ne $approvedPayloadVersion -or $metadata.payloadLayout -ne 'immutable-package' -or $metadata.payloadFileCount -isnot [int64] -or $metadata.payloadFileCount -le 0 -or @@ -170,6 +186,7 @@ foreach ($architecture in @('x64', 'arm64')) { $metadata.archive -ne $msix.Name -or $metadata.sha256 -notmatch '^[0-9a-fA-F]{64}$' -or $metadata.signed -ne $false -or + $metadata.packageVersion -ne $approvedPackageVersion -or $metadata.publisher -ne $policy.publisher ) { throw "The $architecture MSIX metadata is not eligible for signing." @@ -188,6 +205,11 @@ foreach ($architecture in @('x64', 'arm64')) { if ($actualMsixHash -ne ([string]$metadata.sha256).ToLowerInvariant()) { throw "The $architecture MSIX hash does not match its metadata." } + $expectedPackages[$architecture] = @{ + Name = $msix.Name + Path = $msix.FullName + Sha256 = $actualMsixHash + } $packageArchive = [IO.Compression.ZipFile]::OpenRead($msix.FullName) try { @@ -321,6 +343,83 @@ foreach ($architecture in @('x64', 'arm64')) { } } +$resolvedBundlePath = (Resolve-Path -LiteralPath $BundlePath).Path +if ([IO.Path]::GetExtension($resolvedBundlePath) -ine '.msixbundle') { + throw 'The official signing bundle must use the .msixbundle extension.' +} + +$bundleArchive = [IO.Compression.ZipFile]::OpenRead($resolvedBundlePath) +try { + $bundleEntries = New-PackageEntryIndex -Archive $bundleArchive + [xml]$bundleManifest = Read-ZipEntryText ` + -EntriesByPath $bundleEntries ` + -Path 'AppxMetadata/AppxBundleManifest.xml' + $bundleIdentity = $bundleManifest.SelectSingleNode( + "/*[local-name()='Bundle']/*[local-name()='Identity']" + ) + if ( + $null -eq $bundleIdentity -or + $bundleIdentity.Name -ne 'OpenClaw.Gateway' -or + $bundleIdentity.Publisher -ne $policy.publisher -or + $bundleIdentity.Version -ne $expectedPackageVersion + ) { + throw 'The MSIX bundle manifest identity is unexpected.' + } + + $bundlePackages = @( + $bundleManifest.SelectNodes( + "/*[local-name()='Bundle']/*[local-name()='Packages']/*[local-name()='Package']" + ) + ) + if ($bundlePackages.Count -ne 2) { + throw 'The MSIX bundle must contain exactly two application packages.' + } + + $seenArchitectures = + [System.Collections.Generic.HashSet[string]]::new( + [System.StringComparer]::OrdinalIgnoreCase + ) + foreach ($bundlePackage in $bundlePackages) { + $architecture = [string]$bundlePackage.Architecture + if ( + -not $expectedPackages.ContainsKey($architecture) -or + -not $seenArchitectures.Add($architecture) -or + $bundlePackage.Type -ne 'application' -or + $bundlePackage.Version -ne $expectedPackageVersion + ) { + throw 'The MSIX bundle package manifest is unexpected.' + } + + $expectedPackage = $expectedPackages[$architecture] + $fileName = [string]$bundlePackage.FileName + if ($fileName -ne $expectedPackage.Name) { + throw "The bundled $architecture MSIX filename is unexpected." + } + + $bundleEntry = Get-PackageEntry ` + -EntriesByPath $bundleEntries ` + -Path $fileName + $bundledHash = Get-PackageEntrySha256 -Entry $bundleEntry + if ($bundledHash -ne $expectedPackage.Sha256) { + throw ( + "The bundled $architecture MSIX does not match the " + + 'authorized standalone package.' + ) + } + } + + $embeddedMsixEntries = @( + $bundleEntries.Keys | + Where-Object { [IO.Path]::GetExtension($_) -ieq '.msix' } + ) + if ($embeddedMsixEntries.Count -ne 2) { + throw 'The MSIX bundle contains an unexpected package file set.' + } +} +finally { + $bundleArchive.Dispose() +} + Write-Host ( "Authorized official signing for OpenClaw commit $approvedCommit " + "and Gateway MSIX version $expectedPackageVersion." diff --git a/scripts/Test-WorkflowPackageVersion.Tests.ps1 b/scripts/Test-WorkflowPackageVersion.Tests.ps1 index 3609127..d939376 100644 --- a/scripts/Test-WorkflowPackageVersion.Tests.ps1 +++ b/scripts/Test-WorkflowPackageVersion.Tests.ps1 @@ -54,6 +54,24 @@ function Assert-Fails { throw "Expected failure matching '$MessagePattern', but the action succeeded." } +function Assert-ReleaseVersion { + param( + [Parameter(Mandatory)] + [string]$ReleaseVersion, + + [Parameter(Mandatory)] + [string]$Expected + ) + + $actual = & $scriptPath ` + -RunNumber 1 ` + -RunAttempt 1 ` + -ReleaseVersion $ReleaseVersion + if ($actual -ne $Expected) { + throw "Expected $ReleaseVersion to produce $Expected; received $actual." + } +} + Assert-Version -RunNumber 1 -RunAttempt 1 -Expected '0.1.1.1' Assert-Version -RunNumber 65534 -RunAttempt 1 -Expected '0.1.65534.1' Assert-Version -RunNumber 65535 -RunAttempt 1 -Expected '0.2.0.1' @@ -63,6 +81,22 @@ Assert-Version -RunNumber 131069 -RunAttempt 1 -Expected '0.2.65534.1' Assert-Version -RunNumber 131070 -RunAttempt 1 -Expected '0.3.0.1' Assert-Version -RunNumber 1 -RunAttempt 65534 -Expected '0.1.1.65534' +Assert-ReleaseVersion -ReleaseVersion '0.0.0.0' -Expected '0.0.0.0' +Assert-ReleaseVersion -ReleaseVersion '2026.9.4.0' -Expected '2026.9.4.0' + +Assert-Fails -MessagePattern 'exactly four numeric components' -Action { + & $scriptPath ` + -RunNumber 1 ` + -RunAttempt 1 ` + -ReleaseVersion '2026.9.1-beta.1' +} +Assert-Fails -MessagePattern 'greater than 65534' -Action { + & $scriptPath ` + -RunNumber 1 ` + -RunAttempt 1 ` + -ReleaseVersion '2026.9.4.65535' +} + $maximumRunNumber = (65534L * 65535L) - 1L Assert-Version ` -RunNumber $maximumRunNumber ` diff --git a/scripts/Test-WorkflowSigningConfiguration.ps1 b/scripts/Test-WorkflowSigningConfiguration.ps1 index b47146f..d1f553d 100644 --- a/scripts/Test-WorkflowSigningConfiguration.ps1 +++ b/scripts/Test-WorkflowSigningConfiguration.ps1 @@ -18,10 +18,24 @@ $requiredFragments = @( 'tenant-id: ${{ vars.AZURE_TENANT_ID }}' 'subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}' 'uses: azure/artifact-signing-action@v2' + 'name: Compose unsigned multi-architecture MSIX bundle' + 'name: Upload unsigned multi-architecture MSIX bundle' + '-BundlePath artifacts\bundle\OpenClawGateway.msixbundle' 'files-folder-recurse: true' + 'files: ${{ github.workspace }}\artifacts\bundle\OpenClawGateway.msixbundle' + 'name: Upload signed multi-architecture MSIX bundle' 'endpoint: https://eus.codesigning.azure.net/' 'signing-account-name: openclaw' 'certificate-profile-name: openclaw' + 'name: Publish signed Gateway MSIX release' + 'contents: write' + 'uses: softprops/action-gh-release@v3' + 'tag_name: ${{ needs.authorize-signing.outputs.release_tag }}' + 'target_commitish: ${{ github.sha }}' + 'generate_release_notes: true' + 'overwrite_files: false' + 'fail_on_unmatched_files: true' + 'release-assets/*.msixbundle' ) foreach ($fragment in $requiredFragments) { From 735bd4e27ff5efbbce88296ebc2e68587d266cc8 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Sat, 12 Sep 2026 01:40:34 -0700 Subject: [PATCH 16/35] fix(ci): authorize MakeAppx zero-version bundles (#34) --- README.md | 6 ++++++ scripts/Build-MSIXBundle.ps1 | 9 ++++++++- scripts/Test-Build-MSIXBundle.Tests.ps1 | 15 +++++++++++++++ scripts/Test-SigningInputs.Tests.ps1 | 6 ++++-- scripts/Test-SigningInputs.ps1 | 24 ++++++++++++++++++++++-- 5 files changed, 55 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2020591..d5645c2 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,12 @@ contains a signed, multi-architecture deployment. The duplicate GitHub Actions artifacts remain short-lived transport and diagnostic copies. +For the all-zero proof only, MakeAppx assigns the outer bundle identity its +date/time-based version because it does not preserve `0.0.0.0` as a bundle +version. The two embedded architecture packages retain identity version +`0.0.0.0`; signing authorization verifies those versions and byte-compares both +embedded packages with the approved standalone inputs. + An `.msixbundle` is a single installable container for the x64 and ARM64 MSIX packages; Windows selects the package appropriate for the device. An `.appinstaller` file is separate update-channel metadata rather than an diff --git a/scripts/Build-MSIXBundle.ps1 b/scripts/Build-MSIXBundle.ps1 index a1a9727..96d06f7 100644 --- a/scripts/Build-MSIXBundle.ps1 +++ b/scripts/Build-MSIXBundle.ps1 @@ -105,9 +105,16 @@ try { -Destination (Join-Path $bundleInput 'OpenClawGateway-arm64.msix') $resolvedMakeAppx = Resolve-MakeAppx + $bundleVersionArguments = @('/bv', $PackageVersion) + if ($PackageVersion -eq '0.0.0.0') { + # MakeAppx does not preserve an all-zero bundle identity version. Let + # it assign its date/time-based version while the embedded + # architecture packages retain the requested 0.0.0.0 identity. + $bundleVersionArguments = @() + } & $resolvedMakeAppx bundle ` /v ` - /bv $PackageVersion ` + @bundleVersionArguments ` /d $bundleInput ` /p $resolvedOutputPath if ($LASTEXITCODE -ne 0) { diff --git a/scripts/Test-Build-MSIXBundle.Tests.ps1 b/scripts/Test-Build-MSIXBundle.Tests.ps1 index 4cc5169..70c390e 100644 --- a/scripts/Test-Build-MSIXBundle.Tests.ps1 +++ b/scripts/Test-Build-MSIXBundle.Tests.ps1 @@ -75,6 +75,21 @@ type nul > "%output%" throw "MakeAppx did not receive the expected bundle version: $arguments" } + $zeroBundle = Join-Path $testRoot 'OpenClawGateway-zero.msixbundle' + & $scriptPath ` + -X64Package $x64Package ` + -Arm64Package $arm64Package ` + -PackageVersion '0.0.0.0' ` + -OutputPath $zeroBundle ` + -MakeAppxPath $fakeMakeAppx + $arguments = Get-Content -LiteralPath $makeAppxArguments -Raw + if ($arguments -match '/bv') { + throw "MakeAppx received an invalid all-zero bundle version: $arguments" + } + if (-not (Test-Path -LiteralPath $zeroBundle -PathType Leaf)) { + throw 'The zero-version bundle builder did not preserve MakeAppx output.' + } + Assert-Fails -MessagePattern 'must be different' -Action { & $scriptPath ` -X64Package $x64Package ` diff --git a/scripts/Test-SigningInputs.Tests.ps1 b/scripts/Test-SigningInputs.Tests.ps1 index 4a8f09f..15d58e5 100644 --- a/scripts/Test-SigningInputs.Tests.ps1 +++ b/scripts/Test-SigningInputs.Tests.ps1 @@ -195,7 +195,9 @@ function New-TestBundle { [string]$X64Package = (Join-Path ` $Root ` - 'x64\OpenClawGateway-x64.msix') + 'x64\OpenClawGateway-x64.msix'), + + [string]$BundleVersion = '2026.912.815.0' ) $bundleDirectory = Join-Path $Root 'bundle' @@ -226,7 +228,7 @@ function New-TestBundle { + Version="$BundleVersion" /> \d+)\.(?\d+)\.(?\d+)\.(?\d+)$' + ) + $bundleVersionIsValid = $bundleVersionMatch.Success + if ($bundleVersionIsValid) { + foreach ($groupName in @('major', 'minor', 'build', 'revision')) { + if ([long]::Parse($bundleVersionMatch.Groups[$groupName].Value) -gt 65534) { + $bundleVersionIsValid = $false + break + } + } + } if ( $null -eq $bundleIdentity -or $bundleIdentity.Name -ne 'OpenClaw.Gateway' -or $bundleIdentity.Publisher -ne $policy.publisher -or - $bundleIdentity.Version -ne $expectedPackageVersion + -not $bundleVersionIsValid ) { throw 'The MSIX bundle manifest identity is unexpected.' } @@ -422,5 +441,6 @@ finally { Write-Host ( "Authorized official signing for OpenClaw commit $approvedCommit " + - "and Gateway MSIX version $expectedPackageVersion." + "and Gateway MSIX version $expectedPackageVersion " + + "(bundle version $bundleVersion)." ) From df917ac47314ecd964f2cae48cf6c3572a1952d3 Mon Sep 17 00:00:00 2001 From: RoboClaw Date: Mon, 14 Sep 2026 11:33:34 -0700 Subject: [PATCH 17/35] docs: make PR descriptions plain-language first (#35) Co-authored-by: roboclaw-bot <309084314+roboclaw-bot@users.noreply.github.com> Co-authored-by: hannesrudolph <49103247+hannesrudolph@users.noreply.github.com> --- .github/pull_request_template.md | 36 ++++++++++++++++---------------- CONTRIBUTING.md | 5 +++-- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index b79ac3e..08e9180 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -13,10 +13,6 @@ For fixes, describe the user-visible symptom and trigger: fix: launcher exits without a diagnostic when Node is missing Avoid implementation details such as: fix: add null check to payload resolver ---> - -
-Additional instructions **MUST:** Keep **Allow edits from maintainers** enabled for this PR so maintainers can help update the branch when needed. @@ -24,37 +20,39 @@ maintainers can help update the branch when needed. See [CONTRIBUTING.md](../CONTRIBUTING.md) for the validation commands this repository expects and for the packaging, signing, and NativeAOT rules that apply to launcher changes. - -
+--> ## What Problem This Solves -## Why This Change Was Made +## User Impact -## User Impact +## Why This Change Was Made ## Evidence @@ -73,4 +71,6 @@ Include CI links, artifact links, redacted logs, or terminal output. State which lanes you did not run and why. Reviewers will inspect the code, tests, and CI; use this section to make validation easy to understand rather than to restate the diff. +Summarize what was checked and the result; note meaningful gaps. Link long output +or put it in optional
, keeping the useful evidence summary visible. --> diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c6549ac..52b218b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -203,8 +203,9 @@ Use the pull request template. Title the PR `improve`, `refactor`, `docs`, or `chore`. Describe the outcome rather than the mechanism. -Every PR body should explain the problem it solves, why the change was made, -the user or contributor impact, and the evidence that it works. Name the exact +Lead with the problem and user or contributor impact in short, plain-language +sentences, followed by a brief explanation and useful evidence. Keep technical +details optional, but important risks and required actions visible. Name the exact head SHA your evidence came from and state which validation lanes you did not run. From ba1a1e32bdf0b61f0332b774238c8b2274b97f40 Mon Sep 17 00:00:00 2001 From: "Anna Koenig (Dingler)" Date: Mon, 14 Sep 2026 11:40:38 -0700 Subject: [PATCH 18/35] fix: keep dashboard and gateway builds aligned Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/gateway-msix.yml | 9 ++++-- scripts/Test-WorkflowBuildConfiguration.ps1 | 32 +++++++++++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 scripts/Test-WorkflowBuildConfiguration.ps1 diff --git a/.github/workflows/gateway-msix.yml b/.github/workflows/gateway-msix.yml index 3b6e176..a794186 100644 --- a/.github/workflows/gateway-msix.yml +++ b/.github/workflows/gateway-msix.yml @@ -89,6 +89,11 @@ jobs: run: > .\scripts\Test-WorkflowSigningConfiguration.ps1 + - name: Test build workflow configuration + shell: pwsh + run: > + .\scripts\Test-WorkflowBuildConfiguration.ps1 + - name: Test workflow package version shell: pwsh run: > @@ -138,9 +143,7 @@ jobs: working-directory: openclaw-source env: OPENCLAW_CONTROL_UI_RELEASE_BUILD: "1" - run: | - pnpm build - pnpm ui:build + run: pnpm build - name: Pack npm package id: source diff --git a/scripts/Test-WorkflowBuildConfiguration.ps1 b/scripts/Test-WorkflowBuildConfiguration.ps1 new file mode 100644 index 0000000..c0c0a03 --- /dev/null +++ b/scripts/Test-WorkflowBuildConfiguration.ps1 @@ -0,0 +1,32 @@ +[CmdletBinding()] +param() + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$repositoryRoot = Split-Path $PSScriptRoot -Parent +$workflowPath = Join-Path ` + $repositoryRoot ` + '.github\workflows\gateway-msix.yml' +$workflow = Get-Content -LiteralPath $workflowPath -Raw + +$requiredFragments = @( + 'name: Build OpenClaw' + 'OPENCLAW_CONTROL_UI_RELEASE_BUILD: "1"' + 'run: pnpm build' +) + +foreach ($fragment in $requiredFragments) { + if (-not $workflow.Contains($fragment, [StringComparison]::Ordinal)) { + throw "Build workflow is missing required configuration: $fragment" + } +} + +if ($workflow.Contains('pnpm ui:build', [StringComparison]::Ordinal)) { + throw ( + 'Build workflow must not rebuild the Control UI after pnpm build; ' + + 'a second build creates a different release build identity.' + ) +} + +Write-Host 'Gateway MSIX build workflow configuration passed.' From bf02d020add675eedc8cb77c6f0624f127c2e354 Mon Sep 17 00:00:00 2001 From: "Anna Koenig (Dingler)" Date: Mon, 14 Sep 2026 13:23:44 -0700 Subject: [PATCH 19/35] test: validate emitted dashboard build identity Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/gateway-msix.yml | 16 ++- scripts/Test-OpenClawBuildIdentity.Tests.ps1 | 111 +++++++++++++++++++ scripts/Test-OpenClawBuildIdentity.ps1 | 73 ++++++++++++ scripts/Test-WorkflowBuildConfiguration.ps1 | 32 ------ 4 files changed, 198 insertions(+), 34 deletions(-) create mode 100644 scripts/Test-OpenClawBuildIdentity.Tests.ps1 create mode 100644 scripts/Test-OpenClawBuildIdentity.ps1 delete mode 100644 scripts/Test-WorkflowBuildConfiguration.ps1 diff --git a/.github/workflows/gateway-msix.yml b/.github/workflows/gateway-msix.yml index a794186..5b9f94f 100644 --- a/.github/workflows/gateway-msix.yml +++ b/.github/workflows/gateway-msix.yml @@ -89,10 +89,10 @@ jobs: run: > .\scripts\Test-WorkflowSigningConfiguration.ps1 - - name: Test build workflow configuration + - name: Test OpenClaw build identity validation shell: pwsh run: > - .\scripts\Test-WorkflowBuildConfiguration.ps1 + .\scripts\Test-OpenClawBuildIdentity.Tests.ps1 - name: Test workflow package version shell: pwsh @@ -116,6 +116,12 @@ jobs: source_sha: ${{ steps.source.outputs.sha }} package_version: ${{ steps.source.outputs.version }} steps: + - name: Check out repository + uses: actions/checkout@v7 + with: + path: packaging-source + persist-credentials: false + - name: Check out OpenClaw source uses: actions/checkout@v7 with: @@ -145,6 +151,12 @@ jobs: OPENCLAW_CONTROL_UI_RELEASE_BUILD: "1" run: pnpm build + - name: Validate OpenClaw build identities + shell: pwsh + run: > + ./packaging-source/scripts/Test-OpenClawBuildIdentity.ps1 + -OpenClawDirectory ./openclaw-source + - name: Pack npm package id: source working-directory: openclaw-source diff --git a/scripts/Test-OpenClawBuildIdentity.Tests.ps1 b/scripts/Test-OpenClawBuildIdentity.Tests.ps1 new file mode 100644 index 0000000..4b2a28c --- /dev/null +++ b/scripts/Test-OpenClawBuildIdentity.Tests.ps1 @@ -0,0 +1,111 @@ +[CmdletBinding()] +param() + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$scriptPath = Join-Path $PSScriptRoot 'Test-OpenClawBuildIdentity.ps1' +$testRoot = Join-Path ` + ([IO.Path]::GetTempPath()) ` + "openclaw-build-identity-$([guid]::NewGuid().ToString('N'))" + +function New-BuildFixture { + param( + [Parameter(Mandatory)] + [string]$Name, + + [Parameter(Mandatory)] + [string]$GatewayBuildId, + + [string]$ControlUiBuildId, + + [switch]$OmitControlUiBuildId + ) + + $root = Join-Path $testRoot $Name + $distDirectory = Join-Path $root 'dist' + $controlUiDirectory = Join-Path $distDirectory 'control-ui' + $assetsDirectory = Join-Path $controlUiDirectory 'assets' + New-Item -Path $assetsDirectory -ItemType Directory -Force | Out-Null + + @{ buildId = $GatewayBuildId } | + ConvertTo-Json | + Set-Content (Join-Path $distDirectory 'build-info.json') -Encoding utf8 + + if ($OmitControlUiBuildId) { + 'const CACHE_NAME = "openclaw-control-ui";' | + Set-Content (Join-Path $controlUiDirectory 'sw.js') -Encoding utf8 + } + else { + $encodedBuildId = $ControlUiBuildId | ConvertTo-Json -Compress + "const EMBEDDED_CACHE_VERSION = $encodedBuildId;" | + Set-Content (Join-Path $controlUiDirectory 'sw.js') -Encoding utf8 + } + + $clientBuildId = if ($null -eq $ControlUiBuildId) { + '' + } + else { + $ControlUiBuildId + } + "const buildInfo = { buildId: ""$clientBuildId"" };" | + Set-Content (Join-Path $assetsDirectory 'app.js') -Encoding utf8 + + return $root +} + +function Assert-Fails { + param( + [Parameter(Mandatory)] + [scriptblock]$Action, + + [Parameter(Mandatory)] + [string]$MessagePattern + ) + + try { + & $Action + } + catch { + if ($_.Exception.Message -notmatch $MessagePattern) { + throw ( + "Expected failure matching '$MessagePattern'; received: " + + $_.Exception.Message + ) + } + return + } + + throw "Expected failure matching '$MessagePattern', but the action succeeded." +} + +try { + $matching = New-BuildFixture ` + -Name 'matching' ` + -GatewayBuildId 'release-build-a' ` + -ControlUiBuildId 'release-build-a' + & $scriptPath -OpenClawDirectory $matching + + $missing = New-BuildFixture ` + -Name 'missing' ` + -GatewayBuildId 'release-build-a' ` + -OmitControlUiBuildId + Assert-Fails -MessagePattern 'Control UI build identity is missing' -Action { + & $scriptPath -OpenClawDirectory $missing + } + + $mismatched = New-BuildFixture ` + -Name 'mismatched' ` + -GatewayBuildId 'release-build-a' ` + -ControlUiBuildId 'release-build-b' + Assert-Fails -MessagePattern 'OpenClaw build identity mismatch' -Action { + & $scriptPath -OpenClawDirectory $mismatched + } +} +finally { + if (Test-Path -LiteralPath $testRoot) { + Remove-Item -LiteralPath $testRoot -Recurse -Force + } +} + +Write-Host 'OpenClaw build identity validation tests passed.' diff --git a/scripts/Test-OpenClawBuildIdentity.ps1 b/scripts/Test-OpenClawBuildIdentity.ps1 new file mode 100644 index 0000000..f67e1f1 --- /dev/null +++ b/scripts/Test-OpenClawBuildIdentity.ps1 @@ -0,0 +1,73 @@ +[CmdletBinding()] +param( + [Parameter(Mandatory)] + [string]$OpenClawDirectory +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$distDirectory = Join-Path $OpenClawDirectory 'dist' +$buildInfoPath = Join-Path $distDirectory 'build-info.json' +$controlUiDirectory = Join-Path $distDirectory 'control-ui' +$serviceWorkerPath = Join-Path $controlUiDirectory 'sw.js' +$assetsDirectory = Join-Path $controlUiDirectory 'assets' + +foreach ($requiredPath in @( + $buildInfoPath + $serviceWorkerPath + $assetsDirectory +)) { + if (-not (Test-Path -LiteralPath $requiredPath)) { + throw "OpenClaw build identity validation is missing: $requiredPath" + } +} + +$buildInfo = Get-Content -LiteralPath $buildInfoPath -Raw | + ConvertFrom-Json +$gatewayBuildId = [string]$buildInfo.buildId +if ([string]::IsNullOrWhiteSpace($gatewayBuildId)) { + throw "Gateway build identity is missing from '$buildInfoPath'." +} + +$serviceWorker = Get-Content -LiteralPath $serviceWorkerPath -Raw +$serviceWorkerMatch = [regex]::Match( + $serviceWorker, + '(?m)^\s*const\s+EMBEDDED_CACHE_VERSION\s*=\s*(?"[^"\r\n]+")\s*;') +if (-not $serviceWorkerMatch.Success) { + throw "Control UI build identity is missing from '$serviceWorkerPath'." +} + +$controlUiBuildId = [string]( + $serviceWorkerMatch.Groups['value'].Value | + ConvertFrom-Json +) +if ([string]::IsNullOrWhiteSpace($controlUiBuildId)) { + throw "Control UI build identity is empty in '$serviceWorkerPath'." +} +if (-not [string]::Equals( + $gatewayBuildId, + $controlUiBuildId, + [StringComparison]::Ordinal)) { + throw ( + "OpenClaw build identity mismatch: Gateway '$gatewayBuildId'; " + + "Control UI '$controlUiBuildId'." + ) +} + +$clientBundleContainsBuildId = @( + Get-ChildItem -LiteralPath $assetsDirectory -Filter '*.js' -File -Recurse | + Where-Object { + (Get-Content -LiteralPath $_.FullName -Raw).Contains( + $gatewayBuildId, + [StringComparison]::Ordinal) + } +).Count -gt 0 +if (-not $clientBundleContainsBuildId) { + throw ( + "Control UI client bundle does not contain Gateway build identity " + + "'$gatewayBuildId'." + ) +} + +Write-Host "OpenClaw Gateway and Control UI build identity match: $gatewayBuildId" diff --git a/scripts/Test-WorkflowBuildConfiguration.ps1 b/scripts/Test-WorkflowBuildConfiguration.ps1 deleted file mode 100644 index c0c0a03..0000000 --- a/scripts/Test-WorkflowBuildConfiguration.ps1 +++ /dev/null @@ -1,32 +0,0 @@ -[CmdletBinding()] -param() - -Set-StrictMode -Version Latest -$ErrorActionPreference = 'Stop' - -$repositoryRoot = Split-Path $PSScriptRoot -Parent -$workflowPath = Join-Path ` - $repositoryRoot ` - '.github\workflows\gateway-msix.yml' -$workflow = Get-Content -LiteralPath $workflowPath -Raw - -$requiredFragments = @( - 'name: Build OpenClaw' - 'OPENCLAW_CONTROL_UI_RELEASE_BUILD: "1"' - 'run: pnpm build' -) - -foreach ($fragment in $requiredFragments) { - if (-not $workflow.Contains($fragment, [StringComparison]::Ordinal)) { - throw "Build workflow is missing required configuration: $fragment" - } -} - -if ($workflow.Contains('pnpm ui:build', [StringComparison]::Ordinal)) { - throw ( - 'Build workflow must not rebuild the Control UI after pnpm build; ' + - 'a second build creates a different release build identity.' - ) -} - -Write-Host 'Gateway MSIX build workflow configuration passed.' From e4fee8a3e95acf4c5442a60a8676e8d25bcb5da5 Mon Sep 17 00:00:00 2001 From: "Anna Koenig (Dingler)" Date: Mon, 14 Sep 2026 13:39:26 -0700 Subject: [PATCH 20/35] docs: clarify build identity validation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- scripts/Test-OpenClawBuildIdentity.Tests.ps1 | 6 ++++++ scripts/Test-OpenClawBuildIdentity.ps1 | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/scripts/Test-OpenClawBuildIdentity.Tests.ps1 b/scripts/Test-OpenClawBuildIdentity.Tests.ps1 index 4b2a28c..ede7de6 100644 --- a/scripts/Test-OpenClawBuildIdentity.Tests.ps1 +++ b/scripts/Test-OpenClawBuildIdentity.Tests.ps1 @@ -80,12 +80,16 @@ function Assert-Fails { } try { + # A single OpenClaw build must emit one identity across all runtime and UI + # artifacts consumed by the packaged dashboard handshake. $matching = New-BuildFixture ` -Name 'matching' ` -GatewayBuildId 'release-build-a' ` -ControlUiBuildId 'release-build-a' & $scriptPath -OpenClawDirectory $matching + # Missing UI identity is unsafe because packaging could not prove which + # dashboard build will connect to the Gateway. $missing = New-BuildFixture ` -Name 'missing' ` -GatewayBuildId 'release-build-a' ` @@ -94,6 +98,8 @@ try { & $scriptPath -OpenClawDirectory $missing } + # A separately rebuilt dashboard must fail even when both artifacts are + # otherwise complete, reproducing the release-only regression. $mismatched = New-BuildFixture ` -Name 'mismatched' ` -GatewayBuildId 'release-build-a' ` diff --git a/scripts/Test-OpenClawBuildIdentity.ps1 b/scripts/Test-OpenClawBuildIdentity.ps1 index f67e1f1..db56d5f 100644 --- a/scripts/Test-OpenClawBuildIdentity.ps1 +++ b/scripts/Test-OpenClawBuildIdentity.ps1 @@ -7,6 +7,8 @@ param( Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' +# A release is safe to package only when the Gateway and its same-origin +# dashboard were emitted by the same OpenClaw build lifecycle. $distDirectory = Join-Path $OpenClawDirectory 'dist' $buildInfoPath = Join-Path $distDirectory 'build-info.json' $controlUiDirectory = Join-Path $distDirectory 'control-ui' @@ -30,6 +32,8 @@ if ([string]::IsNullOrWhiteSpace($gatewayBuildId)) { throw "Gateway build identity is missing from '$buildInfoPath'." } +# Vite writes the dashboard identity into the service worker so stale browser +# assets can retire themselves when a new Gateway build is installed. $serviceWorker = Get-Content -LiteralPath $serviceWorkerPath -Raw $serviceWorkerMatch = [regex]::Match( $serviceWorker, @@ -55,6 +59,8 @@ if (-not [string]::Equals( ) } +# The browser sends its embedded identity during the Gateway handshake. Check +# the JavaScript payload as well as the service worker before packing the app. $clientBundleContainsBuildId = @( Get-ChildItem -LiteralPath $assetsDirectory -Filter '*.js' -File -Recurse | Where-Object { From 9a8cd4af139513c21d290a01a8a1f2be19b602bc Mon Sep 17 00:00:00 2001 From: Anna Koenig <98650930+anna-dingler@users.noreply.github.com> Date: Mon, 14 Sep 2026 16:01:41 -0700 Subject: [PATCH 21/35] fix: preserve build identity validator in pipeline (#39) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/gateway-msix.yml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gateway-msix.yml b/.github/workflows/gateway-msix.yml index f1b169c..c0e563c 100644 --- a/.github/workflows/gateway-msix.yml +++ b/.github/workflows/gateway-msix.yml @@ -122,9 +122,19 @@ jobs: - name: Check out repository uses: actions/checkout@v7 with: - path: packaging-source persist-credentials: false + # The OpenClaw checkout below must own GITHUB_WORKSPACE because its + # composite setup action resolves workspace files from that root. Keep + # the packaging-owned validator outside the workspace before replacing + # this checkout with the OpenClaw source tree. + - name: Stage build identity validator + shell: pwsh + run: > + Copy-Item + ./scripts/Test-OpenClawBuildIdentity.ps1 + $env:RUNNER_TEMP/Test-OpenClawBuildIdentity.ps1 + - name: Check out OpenClaw source uses: actions/checkout@v7 with: @@ -150,8 +160,8 @@ jobs: - name: Validate OpenClaw build identities shell: pwsh run: > - ./packaging-source/scripts/Test-OpenClawBuildIdentity.ps1 - -OpenClawDirectory ./openclaw-source + & "$env:RUNNER_TEMP/Test-OpenClawBuildIdentity.ps1" + -OpenClawDirectory $env:GITHUB_WORKSPACE - name: Pack npm package id: source From bfc266429dc60d49486bcaad1e05be0552593b3d Mon Sep 17 00:00:00 2001 From: Chaz Gordish Date: Thu, 10 Sep 2026 18:04:34 -0700 Subject: [PATCH 22/35] Add Gateway isolation status plugin Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/gateway-msix.yml | 10 + README.md | 39 ++- plugins/gateway-isolation/index.js | 265 ++++++++++++++++++ plugins/gateway-isolation/index.test.js | 142 ++++++++++ .../gateway-isolation/openclaw.plugin.json | 17 ++ plugins/gateway-isolation/package.json | 23 ++ scripts/Build-Payload.ps1 | 96 +++++++ scripts/Test-GatewayIsolationPlugin.Tests.ps1 | 161 +++++++++++ src/OpenClaw.Launcher/GatewayIsolationMode.cs | 18 ++ src/OpenClaw.Launcher/GatewayLauncher.cs | 10 +- src/OpenClaw.Launcher/Program.cs | 2 + .../GatewayLauncherTests.cs | 24 ++ tests/OpenClaw.Launcher.Tests/ProgramTests.cs | 11 +- 13 files changed, 802 insertions(+), 16 deletions(-) create mode 100644 plugins/gateway-isolation/index.js create mode 100644 plugins/gateway-isolation/index.test.js create mode 100644 plugins/gateway-isolation/openclaw.plugin.json create mode 100644 plugins/gateway-isolation/package.json create mode 100644 scripts/Test-GatewayIsolationPlugin.Tests.ps1 create mode 100644 src/OpenClaw.Launcher/GatewayIsolationMode.cs diff --git a/.github/workflows/gateway-msix.yml b/.github/workflows/gateway-msix.yml index c0e563c..c9e71c9 100644 --- a/.github/workflows/gateway-msix.yml +++ b/.github/workflows/gateway-msix.yml @@ -46,6 +46,11 @@ jobs: cache: true cache-dependency-path: Directory.Packages.props + - name: Set up Node.js + uses: actions/setup-node@v6 + with: + node-version: ${{ env.NODE_VERSION }} + - name: Restore run: dotnet restore .\OpenClaw.Gateway.MSIX.slnx @@ -111,6 +116,11 @@ jobs: run: > .\scripts\Test-GitHooks.Tests.ps1 + - name: Test Gateway isolation plugin + shell: pwsh + run: > + .\scripts\Test-GatewayIsolationPlugin.Tests.ps1 + build-package: name: Build OpenClaw npm package runs-on: ubuntu-latest diff --git a/README.md b/README.md index 8299cfb..fb6d8e6 100644 --- a/README.md +++ b/README.md @@ -43,13 +43,18 @@ copy, repair, or otherwise change package files at runtime. Every OpenClaw child process runs with `OPENCLAW_SUPERVISOR_MODE=external`, `OPENCLAW_SERVICE_REPAIR_POLICY=external`, and -`OPENCLAW_NO_AUTO_UPDATE=1`. These declare external lifecycle ownership, -prevent doctor-owned service repair, and disable configured background -auto-updates. The pinned OpenClaw `v2026.8.2` release honors external supervisor -mode by refusing native service mutation and OpenClaw self-update with guidance -to use the external supervisor's workflow. This behavior belongs to upstream -OpenClaw; the launcher does not reserve, reject, or rewrite upstream command -arguments. +`OPENCLAW_NO_AUTO_UPDATE=1`. It also reports the selected Windows Gateway +session mode through the process-stable +`CLAWCTL_GATEWAY_ISOLATION=enabled|disabled` environment variable. The current +interactive-session launch path reports `disabled`; the future isolated-session +launch path will select `enabled` when that session switch is implemented. +These values declare external lifecycle ownership, prevent doctor-owned service +repair, disable configured background auto-updates, and expose diagnostic +isolation status without claiming independent attestation. The pinned OpenClaw +`v2026.8.2` release honors external supervisor mode by refusing native service +mutation and OpenClaw self-update with guidance to use the external supervisor's +workflow. This behavior belongs to upstream OpenClaw; the launcher does not +reserve, reject, or rewrite upstream command arguments. OpenClaw inherits the terminal's working directory; the launcher does not make the read-only application directory the workspace. @@ -122,7 +127,10 @@ both: Changing only the workflow-dispatch default does not change automatic builds. For a one-time override, run **Build OpenClaw Gateway MSIX** manually and provide a tag, branch, or preferably a full 40-character commit SHA in -`openclaw_ref`. +`openclaw_ref`. Payload composition validates that the selected OpenClaw +runtime can discover and load the packaging-owned Gateway Isolation plugin +with its required read-only route shape; incompatible older refs fail instead +of producing a package without status UI. The source build uses that revision's `.github/actions/setup-node-env` action to select Node.js and pnpm. Its resolved Node.js version is recorded in @@ -158,11 +166,16 @@ dotnet test .\OpenClaw.Gateway.MSIX.slnx ` ``` `scripts\Build-Payload.ps1` npm-installs an OpenClaw package into an expanded, -architecture-specific application tree. `scripts\Build-MSIX.ps1` downloads -the official Node.js archive matching the payload's recorded build version -and architecture, copies both inputs into package content, rejects Node.js -inside the application payload, creates a per-file inventory, and then creates -an unsigned NativeAOT MSIX. +architecture-specific application tree and provisions the packaging-owned, +enabled-by-default Gateway Isolation plugin into OpenClaw's bundled plugin +directory. The plugin adds a read-only **Gateway Isolation** tab to the Control +group and serves it through an authenticated, sandboxed plugin route. It reads +only the launch-time `CLAWCTL_GATEWAY_ISOLATION` value and registers no mutation +RPC or process control. `scripts\Build-MSIX.ps1` downloads the official Node.js +archive matching the payload's recorded build version and architecture, copies +both inputs into package content, rejects Node.js inside the application +payload, creates a per-file inventory, and then creates an unsigned NativeAOT +MSIX. `scripts\Build-LocalMSIX.ps1` can reuse a successful workflow payload or a local payload directory. `-NodeArchivePath` can supply an already-downloaded archive, but its version and architecture must match the payload metadata. diff --git a/plugins/gateway-isolation/index.js b/plugins/gateway-isolation/index.js new file mode 100644 index 0000000..7690e82 --- /dev/null +++ b/plugins/gateway-isolation/index.js @@ -0,0 +1,265 @@ +const ISOLATION_ENVIRONMENT_VARIABLE = "CLAWCTL_GATEWAY_ISOLATION"; +const STATUS_PATH = "/plugins/gateway-isolation/status"; + +export function readGatewayIsolationMode(env) { + const value = env[ISOLATION_ENVIRONMENT_VARIABLE]; + return value === "enabled" || value === "disabled" ? value : null; +} + +export function renderGatewayIsolationPage(mode) { + if (mode !== "enabled" && mode !== "disabled") { + throw new TypeError("Gateway isolation mode must be enabled or disabled."); + } + + const enabled = mode === "enabled"; + const status = enabled ? "Enabled" : "Disabled"; + const command = `clawctl gateway-isolation ${enabled ? "disable" : "enable"}`; + const tone = enabled ? "ok" : "warn"; + + return ` + + + + + Gateway Isolation + + + +
+

Gateway Isolation

+

Diagnostic launch mode reported by the Windows launcher.

+
+
+
Reported Gateway Isolation
+
+ ${status} +
+
+
+
+
Change with CLI
+
Run from the signed-in user session on the Gateway host.
+
+
+ ${command} + +
+
+
+
+
+ + +`; +} + +function writeHtmlResponse(response, statusCode, html) { + response.writeHead(statusCode, { + "Cache-Control": "no-store", + "Content-Security-Policy": + "default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; frame-ancestors 'self'", + "Content-Type": "text/html; charset=utf-8", + "Referrer-Policy": "no-referrer", + "X-Content-Type-Options": "nosniff", + }); + response.end(html); +} + +export function createGatewayIsolationPlugin(env = process.env) { + const launchMode = readGatewayIsolationMode(env); + + return { + id: "gateway-isolation", + name: "Gateway Isolation", + description: "Reports the Windows launch mode selected for the running Gateway.", + register(api) { + api.session.controls.registerControlUiDescriptor({ + surface: "tab", + id: "gateway-isolation", + label: "Gateway Isolation", + description: "Read-only Windows Gateway isolation status.", + icon: "shield-check", + group: "control", + order: 20, + path: STATUS_PATH, + requiredScopes: ["operator.read"], + }); + api.registerHttpRoute({ + path: STATUS_PATH, + auth: "gateway", + match: "exact", + handler(_request, response) { + if (!launchMode) { + writeHtmlResponse( + response, + 503, + "Gateway Isolation unavailable

The Windows launcher did not provide a valid Gateway isolation mode.

", + ); + return true; + } + writeHtmlResponse(response, 200, renderGatewayIsolationPage(launchMode)); + return true; + }, + }); + }, + }; +} + +export default createGatewayIsolationPlugin(); diff --git a/plugins/gateway-isolation/index.test.js b/plugins/gateway-isolation/index.test.js new file mode 100644 index 0000000..2e5c09d --- /dev/null +++ b/plugins/gateway-isolation/index.test.js @@ -0,0 +1,142 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { + createGatewayIsolationPlugin, + readGatewayIsolationMode, + renderGatewayIsolationPage, +} from "./index.js"; + +function registerPlugin(mode) { + const descriptors = []; + const routes = []; + const plugin = createGatewayIsolationPlugin({ + CLAWCTL_GATEWAY_ISOLATION: mode, + }); + plugin.register({ + session: { + controls: { + registerControlUiDescriptor(descriptor) { + descriptors.push(descriptor); + }, + }, + }, + registerHttpRoute(route) { + routes.push(route); + }, + }); + assert.equal(descriptors.length, 1); + assert.equal(routes.length, 1); + return { descriptors, routes }; +} + +function invokeRoute(route) { + const result = { + body: "", + headers: {}, + statusCode: 0, + }; + const handled = route.handler( + {}, + { + writeHead(statusCode, headers) { + result.statusCode = statusCode; + result.headers = headers; + }, + end(body) { + result.body = body; + }, + }, + ); + assert.equal(handled, true); + return result; +} + +test("accepts only the exact launcher isolation values", () => { + assert.equal(readGatewayIsolationMode({ CLAWCTL_GATEWAY_ISOLATION: "enabled" }), "enabled"); + assert.equal(readGatewayIsolationMode({ CLAWCTL_GATEWAY_ISOLATION: "disabled" }), "disabled"); + for (const value of [undefined, "", "ENABLED", "unknown"]) { + assert.equal(readGatewayIsolationMode({ CLAWCTL_GATEWAY_ISOLATION: value }), null); + } +}); + +for (const expected of [ + { + mode: "enabled", + status: "Enabled", + command: "clawctl gateway-isolation disable", + tone: "status--ok", + }, + { + mode: "disabled", + status: "Disabled", + command: "clawctl gateway-isolation enable", + tone: "status--warn", + }, +]) { + test(`renders the exact ${expected.mode} read-only status`, () => { + const html = renderGatewayIsolationPage(expected.mode); + assert.match(html, /Gateway Isolation/); + assert.match(html, /Reported Gateway Isolation/); + assert.match(html, new RegExp(`>${expected.status}<`)); + assert.match(html, /Change with CLI/); + assert.match(html, /Run from the signed-in user session on the Gateway host\./); + assert.match(html, new RegExp(expected.command)); + assert.match(html, new RegExp(expected.tone)); + assert.match(html, /aria-label="Copy command"/); + assert.match(html, /Copy the selected command manually\./); + assert.match(html, /copied = document\.execCommand\("copy"\)/); + assert.doesNotMatch(html, /next manual Gateway restart/i); + }); +} + +test("registers one read-only Control tab and one authenticated sandbox route", () => { + const { descriptors, routes } = registerPlugin("enabled"); + assert.deepEqual(descriptors[0], { + surface: "tab", + id: "gateway-isolation", + label: "Gateway Isolation", + description: "Read-only Windows Gateway isolation status.", + icon: "shield-check", + group: "control", + order: 20, + path: "/plugins/gateway-isolation/status", + requiredScopes: ["operator.read"], + }); + assert.equal(routes[0].path, descriptors[0].path); + assert.equal(routes[0].auth, "gateway"); + assert.equal(routes[0].match, "exact"); + + const response = invokeRoute(routes[0]); + assert.equal(response.statusCode, 200); + assert.equal(response.headers["Cache-Control"], "no-store"); + assert.match(response.headers["Content-Security-Policy"], /frame-ancestors 'self'/); + assert.match(response.body, />Enabled { + const env = { CLAWCTL_GATEWAY_ISOLATION: "enabled" }; + const plugin = createGatewayIsolationPlugin(env); + env.CLAWCTL_GATEWAY_ISOLATION = "disabled"; + const routes = []; + plugin.register({ + session: { + controls: { + registerControlUiDescriptor() {}, + }, + }, + registerHttpRoute(route) { + routes.push(route); + }, + }); + + const response = invokeRoute(routes[0]); + assert.match(response.body, />EnabledDisabled { + const { routes } = registerPlugin("invalid"); + const response = invokeRoute(routes[0]); + assert.equal(response.statusCode, 503); + assert.match(response.body, /did not provide a valid Gateway isolation mode/); +}); diff --git a/plugins/gateway-isolation/openclaw.plugin.json b/plugins/gateway-isolation/openclaw.plugin.json new file mode 100644 index 0000000..b07da30 --- /dev/null +++ b/plugins/gateway-isolation/openclaw.plugin.json @@ -0,0 +1,17 @@ +{ + "id": "gateway-isolation", + "name": "Gateway Isolation", + "description": "Reports the Windows launch mode selected for the running Gateway.", + "enabledByDefault": true, + "enabledByDefaultOnPlatforms": [ + "win32" + ], + "activation": { + "onStartup": true + }, + "configSchema": { + "type": "object", + "additionalProperties": false, + "properties": {} + } +} diff --git a/plugins/gateway-isolation/package.json b/plugins/gateway-isolation/package.json new file mode 100644 index 0000000..ced9c93 --- /dev/null +++ b/plugins/gateway-isolation/package.json @@ -0,0 +1,23 @@ +{ + "name": "@openclaw/gateway-isolation", + "version": "2026.8.2", + "private": true, + "description": "Read-only Windows Gateway isolation status", + "type": "module", + "scripts": { + "test": "node --test index.test.js" + }, + "peerDependencies": { + "openclaw": ">=2026.8.2" + }, + "peerDependenciesMeta": { + "openclaw": { + "optional": true + } + }, + "openclaw": { + "extensions": [ + "./index.js" + ] + } +} diff --git a/scripts/Build-Payload.ps1 b/scripts/Build-Payload.ps1 index 4ce97e3..fc093c6 100644 --- a/scripts/Build-Payload.ps1 +++ b/scripts/Build-Payload.ps1 @@ -12,6 +12,7 @@ param( ) $ErrorActionPreference = 'Stop' +$repositoryRoot = Split-Path $PSScriptRoot -Parent $package = @(Get-ChildItem -Path $PackageDirectory -Filter '*.tgz' -File) if ($package.Count -ne 1) { @@ -77,6 +78,101 @@ foreach ($requiredPath in @('package.json', 'openclaw.mjs', 'dist')) { } } +$pluginSource = Join-Path $repositoryRoot 'plugins\gateway-isolation' +$pluginFiles = @('package.json', 'openclaw.plugin.json', 'index.js') +foreach ($pluginFile in $pluginFiles) { + $sourcePath = Join-Path $pluginSource $pluginFile + if (-not (Test-Path -LiteralPath $sourcePath -PathType Leaf)) { + throw "Gateway isolation plugin is missing required file: $sourcePath" + } +} + +$pluginManifest = Get-Content ` + -LiteralPath (Join-Path $pluginSource 'openclaw.plugin.json') ` + -Raw | + ConvertFrom-Json +if ( + $pluginManifest.id -ne 'gateway-isolation' -or + $pluginManifest.enabledByDefault -ne $true -or + $pluginManifest.activation.onStartup -ne $true +) { + throw 'Gateway isolation plugin manifest is not enabled for Gateway startup.' +} + +$bundledPluginsDirectory = Join-Path $installedPackage 'dist\extensions' +if (-not (Test-Path -LiteralPath $bundledPluginsDirectory -PathType Container)) { + throw ( + 'The pinned OpenClaw payload does not expose the supported bundled ' + + "plugin directory: $bundledPluginsDirectory" + ) +} +$pluginTarget = Join-Path $bundledPluginsDirectory 'gateway-isolation' +if (Test-Path -LiteralPath $pluginTarget) { + throw ( + 'The OpenClaw payload already contains a gateway-isolation plugin; ' + + 'refusing to replace upstream content.' + ) +} +New-Item -Path $pluginTarget -ItemType Directory | Out-Null +foreach ($pluginFile in $pluginFiles) { + Copy-Item ` + -LiteralPath (Join-Path $pluginSource $pluginFile) ` + -Destination (Join-Path $pluginTarget $pluginFile) +} + +$previousStateDirectory = $env:OPENCLAW_STATE_DIR +$previousIsolationMode = $env:CLAWCTL_GATEWAY_ISOLATION +try { + $env:OPENCLAW_STATE_DIR = Join-Path ` + $stagingDirectory ` + 'gateway-isolation-validation' + $env:CLAWCTL_GATEWAY_ISOLATION = 'disabled' + Push-Location $installedPackage + try { + $inspectionOutput = ( + & node ` + .\openclaw.mjs ` + plugins inspect gateway-isolation ` + --runtime ` + --json + ) | Out-String + if ($LASTEXITCODE -ne 0) { + throw ( + 'The selected OpenClaw payload cannot load the Gateway ' + + "isolation plugin. Exit code: $LASTEXITCODE." + ) + } + } + finally { + Pop-Location + } + + $inspection = $inspectionOutput | ConvertFrom-Json + if ( + $inspection.plugin.id -ne 'gateway-isolation' -or + $inspection.plugin.origin -ne 'bundled' -or + $inspection.plugin.enabled -ne $true -or + $inspection.plugin.activated -ne $true -or + $inspection.plugin.status -ne 'loaded' -or + $inspection.plugin.imported -ne $true -or + $inspection.plugin.httpRoutes -ne 1 -or + $inspection.httpRouteCount -ne 1 -or + @($inspection.gatewayMethods).Count -ne 0 -or + @($inspection.tools).Count -ne 0 -or + @($inspection.services).Count -ne 0 -or + @($inspection.diagnostics).Count -ne 0 + ) { + throw ( + 'The selected OpenClaw payload did not load the Gateway isolation ' + + 'plugin with the required read-only runtime shape.' + ) + } +} +finally { + $env:OPENCLAW_STATE_DIR = $previousStateDirectory + $env:CLAWCTL_GATEWAY_ISOLATION = $previousIsolationMode +} + $bundledNodeFiles = @( Get-ChildItem -LiteralPath $installedPackage -File -Recurse | Where-Object { diff --git a/scripts/Test-GatewayIsolationPlugin.Tests.ps1 b/scripts/Test-GatewayIsolationPlugin.Tests.ps1 new file mode 100644 index 0000000..5b3b3ee --- /dev/null +++ b/scripts/Test-GatewayIsolationPlugin.Tests.ps1 @@ -0,0 +1,161 @@ +[CmdletBinding()] +param() + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$repositoryRoot = Split-Path $PSScriptRoot -Parent +$pluginDirectory = Join-Path $repositoryRoot 'plugins\gateway-isolation' +$testRoot = Join-Path $env:TEMP ( + "openclaw-gateway-isolation-$([guid]::NewGuid().ToString('N'))" +) +$packageSource = Join-Path $testRoot 'package-source' +$packageDirectory = Join-Path $testRoot 'package' +$payloadDirectory = Join-Path $testRoot 'payload' + +function Assert-Path { + param( + [Parameter(Mandatory)] + [string]$Path + ) + + if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { + throw "Expected file was not found: $Path" + } +} + +try { + $manifest = Get-Content ` + -LiteralPath (Join-Path $pluginDirectory 'openclaw.plugin.json') ` + -Raw | + ConvertFrom-Json + if ( + $manifest.id -ne 'gateway-isolation' -or + $manifest.enabledByDefault -ne $true -or + $manifest.activation.onStartup -ne $true -or + $manifest.configSchema.additionalProperties -ne $false + ) { + throw 'Gateway isolation plugin manifest is not valid for automatic startup.' + } + + $package = Get-Content ` + -LiteralPath (Join-Path $pluginDirectory 'package.json') ` + -Raw | + ConvertFrom-Json + if ($package.openclaw.extensions.Count -ne 1 -or + $package.openclaw.extensions[0] -ne './index.js') { + throw 'Gateway isolation plugin package has an invalid runtime entry.' + } + + & node --test (Join-Path $pluginDirectory 'index.test.js') + if ($LASTEXITCODE -ne 0) { + throw "Gateway isolation plugin tests failed with exit code $LASTEXITCODE." + } + + New-Item ` + -Path ( + Join-Path $packageSource 'dist\extensions\fixture' + ), $packageDirectory ` + -ItemType Directory ` + -Force | + Out-Null + Set-Content ` + -LiteralPath (Join-Path $packageSource 'openclaw.mjs') ` + -Value @' +const inspection = { + plugin: { + id: "gateway-isolation", + origin: "bundled", + enabled: true, + activated: true, + status: "loaded", + imported: true, + httpRoutes: 1 + }, + httpRouteCount: 1, + gatewayMethods: [], + tools: [], + services: [], + diagnostics: [] +}; +console.log(JSON.stringify(inspection)); +'@ ` + -Encoding utf8 + Set-Content ` + -LiteralPath ( + Join-Path $packageSource 'dist\extensions\fixture\package.json' + ) ` + -Value '{"name":"@openclaw/fixture","version":"1.0.0"}' ` + -Encoding utf8 + [ordered]@{ + name = 'openclaw' + version = '2026.8.2' + type = 'module' + } | + ConvertTo-Json | + Set-Content ` + -LiteralPath (Join-Path $packageSource 'package.json') ` + -Encoding utf8 + + Push-Location $packageSource + try { + & npm pack --pack-destination $packageDirectory --silent + if ($LASTEXITCODE -ne 0) { + throw "npm pack failed with exit code $LASTEXITCODE." + } + } + finally { + Pop-Location + } + + [ordered]@{ + repository = 'https://github.com/openclaw/openclaw' + requestedRef = '0965053fe6b9341776df147a6934b7485c60b5ca' + resolvedCommit = '0965053fe6b9341776df147a6934b7485c60b5ca' + packageVersion = '2026.8.2' + } | + ConvertTo-Json | + Set-Content ` + -LiteralPath (Join-Path $packageDirectory 'source.json') ` + -Encoding utf8 + + $previousRunnerTemp = $env:RUNNER_TEMP + try { + $env:RUNNER_TEMP = $testRoot + & (Join-Path $PSScriptRoot 'Build-Payload.ps1') ` + -PackageDirectory $packageDirectory ` + -Architecture arm64 ` + -OutputDirectory $payloadDirectory + } + finally { + $env:RUNNER_TEMP = $previousRunnerTemp + } + + $packagedPlugin = Join-Path ` + $payloadDirectory ` + 'app\dist\extensions\gateway-isolation' + foreach ($pluginFile in @('package.json', 'openclaw.plugin.json', 'index.js')) { + Assert-Path -Path (Join-Path $packagedPlugin $pluginFile) + } + if (Test-Path -LiteralPath (Join-Path $packagedPlugin 'index.test.js')) { + throw 'Plugin test sources must not be shipped in the MSIX payload.' + } + + $packagedManifest = Get-Content ` + -LiteralPath (Join-Path $packagedPlugin 'openclaw.plugin.json') ` + -Raw | + ConvertFrom-Json + if ( + $packagedManifest.id -ne 'gateway-isolation' -or + $packagedManifest.enabledByDefault -ne $true + ) { + throw 'Packaged Gateway isolation plugin manifest changed during provisioning.' + } + + Write-Host 'Gateway isolation plugin tests passed.' +} +finally { + if (Test-Path -LiteralPath $testRoot) { + Remove-Item -LiteralPath $testRoot -Recurse -Force + } +} diff --git a/src/OpenClaw.Launcher/GatewayIsolationMode.cs b/src/OpenClaw.Launcher/GatewayIsolationMode.cs new file mode 100644 index 0000000..ff8e2c3 --- /dev/null +++ b/src/OpenClaw.Launcher/GatewayIsolationMode.cs @@ -0,0 +1,18 @@ +namespace OpenClaw.Launcher; + +public enum GatewayIsolationMode +{ + Disabled, + Enabled +} + +internal static class GatewayIsolationModeExtensions +{ + public static string ToEnvironmentValue(this GatewayIsolationMode mode) => + mode switch + { + GatewayIsolationMode.Disabled => "disabled", + GatewayIsolationMode.Enabled => "enabled", + _ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null) + }; +} diff --git a/src/OpenClaw.Launcher/GatewayLauncher.cs b/src/OpenClaw.Launcher/GatewayLauncher.cs index 531a98a..e75d23e 100644 --- a/src/OpenClaw.Launcher/GatewayLauncher.cs +++ b/src/OpenClaw.Launcher/GatewayLauncher.cs @@ -8,13 +8,15 @@ public static async Task RunAsync( string nodePath, string payloadDirectory, IReadOnlyList openClawArguments, + GatewayIsolationMode gatewayIsolationMode, CancellationToken cancellationToken, Action? log = null) { ProcessStartInfo startInfo = CreateStartInfo( nodePath, payloadDirectory, - openClawArguments); + openClawArguments, + gatewayIsolationMode: gatewayIsolationMode); log?.Invoke("Launching OpenClaw with forwarded command arguments."); using WindowsKillOnCloseJob job = WindowsKillOnCloseJob.Create(); using Process process = job.StartProcess(startInfo); @@ -44,7 +46,9 @@ public static ProcessStartInfo CreateStartInfo( string nodePath, string applicationDirectory, IReadOnlyList openClawArguments, - string? workingDirectory = null) + string? workingDirectory = null, + GatewayIsolationMode gatewayIsolationMode = + GatewayIsolationMode.Disabled) { string entryPoint = Path.Combine(applicationDirectory, "openclaw.mjs"); if (!File.Exists(entryPoint)) @@ -77,6 +81,8 @@ public static ProcessStartInfo CreateStartInfo( ? nodeDirectory : $"{nodeDirectory}{Path.PathSeparator}{inheritedPath}"; } + startInfo.Environment["CLAWCTL_GATEWAY_ISOLATION"] = + gatewayIsolationMode.ToEnvironmentValue(); startInfo.ArgumentList.Add(entryPoint); foreach (string argument in openClawArguments) diff --git a/src/OpenClaw.Launcher/Program.cs b/src/OpenClaw.Launcher/Program.cs index b294df8..be74d3a 100644 --- a/src/OpenClaw.Launcher/Program.cs +++ b/src/OpenClaw.Launcher/Program.cs @@ -166,6 +166,7 @@ internal static async Task RunAgentAsync( nodeRuntime.ExecutablePath, applicationDirectory, options.OpenClawArguments, + GatewayIsolationMode.Disabled, CancellationToken.None, log).ConfigureAwait(false); } @@ -240,6 +241,7 @@ internal delegate Task LaunchOpenClawAsync( string nodePath, string applicationDirectory, IReadOnlyList openClawArguments, + GatewayIsolationMode gatewayIsolationMode, CancellationToken cancellationToken, Action? log); diff --git a/tests/OpenClaw.Launcher.Tests/GatewayLauncherTests.cs b/tests/OpenClaw.Launcher.Tests/GatewayLauncherTests.cs index 3be9a21..ccd66eb 100644 --- a/tests/OpenClaw.Launcher.Tests/GatewayLauncherTests.cs +++ b/tests/OpenClaw.Launcher.Tests/GatewayLauncherTests.cs @@ -31,6 +31,9 @@ public void CreateStartInfoForwardsNoArgumentsUnchanged() "external", startInfo.Environment["OPENCLAW_SERVICE_REPAIR_POLICY"]); Assert.Equal("1", startInfo.Environment["OPENCLAW_NO_AUTO_UPDATE"]); + Assert.Equal( + "disabled", + startInfo.Environment["CLAWCTL_GATEWAY_ISOLATION"]); Assert.Equal( [Path.Combine(_payloadDirectory, "openclaw.mjs")], startInfo.ArgumentList); @@ -85,11 +88,32 @@ public void CreateStartInfoPreservesExplicitArguments() "external", startInfo.Environment["OPENCLAW_SERVICE_REPAIR_POLICY"]); Assert.Equal("1", startInfo.Environment["OPENCLAW_NO_AUTO_UPDATE"]); + Assert.Equal( + "disabled", + startInfo.Environment["CLAWCTL_GATEWAY_ISOLATION"]); Assert.Equal( [Path.Combine(_payloadDirectory, "openclaw.mjs"), .. arguments], startInfo.ArgumentList); } + [Theory] + [InlineData(GatewayIsolationMode.Disabled, "disabled")] + [InlineData(GatewayIsolationMode.Enabled, "enabled")] + public void CreateStartInfoReportsExactGatewayIsolationMode( + GatewayIsolationMode mode, + string expected) + { + var startInfo = GatewayLauncher.CreateStartInfo( + "node", + _payloadDirectory, + [], + gatewayIsolationMode: mode); + + Assert.Equal( + expected, + startInfo.Environment["CLAWCTL_GATEWAY_ISOLATION"]); + } + [Theory] [InlineData("update", "--yes")] [InlineData("--update")] diff --git a/tests/OpenClaw.Launcher.Tests/ProgramTests.cs b/tests/OpenClaw.Launcher.Tests/ProgramTests.cs index 4d8adde..fd2b614 100644 --- a/tests/OpenClaw.Launcher.Tests/ProgramTests.cs +++ b/tests/OpenClaw.Launcher.Tests/ProgramTests.cs @@ -29,12 +29,21 @@ await File.WriteAllTextAsync( nodeResolutionAttempted = true; return Task.FromResult(nodeRuntime); }, - (nodePath, appDirectory, forwardedArguments, _, _) => + ( + nodePath, + appDirectory, + forwardedArguments, + gatewayIsolationMode, + _, + _) => { launchAttempted = true; Assert.Equal(nodeRuntime.ExecutablePath, nodePath); Assert.Equal(applicationDirectory, appDirectory); Assert.Equal(arguments, forwardedArguments); + Assert.Equal( + GatewayIsolationMode.Disabled, + gatewayIsolationMode); return Task.FromResult(23); }); From d6855d0cea76ecf28fa813c22ec3ce04bbdeacfb Mon Sep 17 00:00:00 2001 From: Chaz Gordish Date: Thu, 10 Sep 2026 19:48:13 -0700 Subject: [PATCH 23/35] docs: attach Gateway isolation local E2E evidence Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- docs/validation/pr-28/README.md | 129 ++++++++++++++++++ docs/validation/pr-28/e2e-results.json | 33 +++++ .../pr-28/gateway-isolation-e2e.png | Bin 0 -> 41807 bytes docs/validation/pr-28/hashes.json | 16 +++ 4 files changed, 178 insertions(+) create mode 100644 docs/validation/pr-28/README.md create mode 100644 docs/validation/pr-28/e2e-results.json create mode 100644 docs/validation/pr-28/gateway-isolation-e2e.png create mode 100644 docs/validation/pr-28/hashes.json diff --git a/docs/validation/pr-28/README.md b/docs/validation/pr-28/README.md new file mode 100644 index 0000000..fafc9db --- /dev/null +++ b/docs/validation/pr-28/README.md @@ -0,0 +1,129 @@ +# Gateway Isolation local end-to-end evidence + +## Why is this change being made? + +PR #28 needs evidence of the real Windows launcher starting a Gateway whose +authenticated Control UI exposes the Gateway Isolation tab. This records the +local run against packaging commit +`9aa1df286c2b6fd59b4c101201ddae0ae6000324`. + +## What changed? + +No launcher, plugin, or upstream runtime source was changed for this validation. +These files add the full-shell screenshot and sanitized machine-readable results. +The GitHub user-attachments endpoint returned HTTP 404 for the target repository, +so the evidence is stored in the PR branch instead. + +![Authenticated Control UI showing the real launcher-reported Disabled state](gateway-isolation-e2e.png) + +## How was the change tested? + +The run completed at `2026-09-11T02:45:20.860Z` on Windows x64 build +`10.0.26687.0`, using .NET SDK `10.0.401`, Node.js `24.16.0`, and Microsoft Edge +`152.0.4191.66` through Playwright in headless Chromium mode. + +### Runtime composition + +The launcher was published as NativeAOT from the exact PR commit. The expanded +x64 application came from the successful +[packaging workflow run 34546297901](https://github.com/openclaw/openclaw-windows-packaging/actions/runs/34546297901), +artifact `openclaw-gateway-payload-x64` (artifact ID `10179486438`). +That workflow's packaging commit was +`52f2a53fb62b13499ba2692d81870413fcaf943a`, not this PR. +Its recorded upstream source is OpenClaw `2026.8.2`, +commit `0965053fe6b9341776df147a6934b7485c60b5ca`. + +The PR's three shipping plugin files were copied into +`app\dist\extensions\gateway-isolation`; the plugin test file was not included. +The source and runtime `index.js` SHA-256 values matched. + +To resolve the previously reported UI/Gateway build-identity mismatch, the +Control UI was rebuilt from unmodified source at the same upstream commit, +using its frozen dependency lockfile and declared pnpm `12.1.0`. Only the +generated `dist\control-ui` tree was replaced in this validation payload. +The supported build inputs were: + +```powershell +$env:GIT_COMMIT = '0965053fe6b9341776df147a6934b7485c60b5ca' +$env:OPENCLAW_BUILD_TIMESTAMP = '2026-09-11T00:24:39.157Z' +$env:OPENCLAW_CONTROL_UI_RELEASE_BUILD = '1' +pnpm install --frozen-lockfile --ignore-scripts +pnpm --dir ui build +``` + +The timestamp came from the Gateway's canonical `dist\build-info.json`. +The authenticated WebSocket response reported: + +```text +type: hello-ok +version: 2026.8.2 +buildId: 2026.8.2-release-0965053fe6b9-2026-09-11T00-24-39.157Z +controlUiBuildSource: bundled +``` + +The JavaScript served by the Gateway matched the rebuilt JavaScript SHA-256. +There was no custom Control UI root, cross-origin UI, edited build metadata, +mock Gateway, injected status HTML, disabled authentication, or build-admission +bypass. + +### Launcher-to-browser flow + +```powershell +dotnet restore .\OpenClaw.Gateway.MSIX.slnx +dotnet test .\OpenClaw.Gateway.MSIX.slnx --configuration Release --no-restore +dotnet publish .\src\OpenClaw.Launcher\OpenClaw.Launcher.csproj ` + --configuration Release --runtime win-x64 --self-contained +.\scripts\Test-GatewayIsolationPlugin.Tests.ps1 +.\scripts\Test-SigningInputs.Tests.ps1 +.\scripts\Test-WorkflowPackageVersion.Tests.ps1 + +# Run the published executable beside the expanded app directory. +# OPENCLAW_STATE_DIR and OPENCLAW_CONFIG_PATH point to a new test-only profile. +# OPENCLAW_GATEWAY_TOKEN contains a newly generated random token, not a real credential. +# CLAWCTL_GATEWAY_ISOLATION is absent from the parent environment. +.\openclaw.exe gateway run --port 19428 --bind loopback --auth token --ws-log compact +``` + +The profile used local Gateway mode, disabled mDNS and browser automation, and +contained no model credentials. Process inspection confirmed that the listening +`node.exe` process was a child of the published `openclaw.exe`. + +| Check | Observed result | +|---|---| +| .NET launcher tests | 54 passed | +| Node plugin tests | 6 passed | +| Synthetic payload provisioning | Passed; this is a fixture test, not the real Gateway | +| Signing-input and workflow-version tests | Passed | +| NativeAOT publish | Passed | +| Actual launcher `--version` | OpenClaw 2026.8.2 (0965053) | +| Real runtime plugin inspection | Bundled, enabled, activated, loaded, imported; one HTTP route; zero Gateway methods, tools, services, or diagnostics | +| Anonymous plugin route request | HTTP 401 | +| Authenticated plugin route request | HTTP 200; `Cache-Control: no-store`; restrictive CSP | +| Full Control UI connection | Token-authenticated `hello-ok`; bundled matching build identity | +| Normal UI navigation | Opened the Control UI, clicked Back to app from the fresh-profile model setup screen, then clicked Gateway Isolation in the sidebar | +| Rendered plugin iframe | `sandbox="allow-scripts"` without `allow-same-origin` | +| Real launcher state | **Disabled**, with `clawctl gateway-isolation enable` guidance | +| Copy affordance | Clicked Copy; button reported **Copied**. Clipboard contents were not independently read back | +| RPC errors during browser run | None observed | + +The screenshot was captured from the real rendered page after the sidebar click. +The model setup screen was exited through its normal Back to app button; no model +was configured or queried. See [e2e-results.json](e2e-results.json) for the sanitized +browser assertions and [hashes.json](hashes.json) for exact file hashes. + +### Scope and limitations + +This validates the local runtime path in an **expanded, unpackaged application +layout**, not an installed MSIX. It does not validate Store installation, +package identity, execution aliases, MSIX read-only enforcement, signing, ARM64 +execution, or an unchanged workflow payload. The UI rebuild described above is +part of the tested composition, not a workflow/source fix. + +**Disabled is the real interactive-session launcher's report.** Enabled, +missing, and invalid values are covered by unit tests only. No isolated-session +launch or isolation attestation is claimed. The CLI guidance was displayed and +copied, not executed; changing isolation remains outside this PR's scope. + +Only the validation-owned Gateway process tree was stopped after capture. The +test port was checked to be free afterward. No existing Gateway service, user +profile, installed package, or system Node installation was changed. diff --git a/docs/validation/pr-28/e2e-results.json b/docs/validation/pr-28/e2e-results.json new file mode 100644 index 0000000..c52a82f --- /dev/null +++ b/docs/validation/pr-28/e2e-results.json @@ -0,0 +1,33 @@ +{ + "timestamp": "2026-09-11T02:45:20.860Z", + "browser": "152.0.4191.66", + "browserEngine": "Microsoft Edge (headless Chromium)", + "launcher": "NativeAOT win-x64, unpackaged app layout", + "admissionBypasses": false, + "events": [ + { + "event": "route-auth-check", + "anonymous": 401, + "authenticated": 200, + "cacheControl": "no-store", + "csp": "default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; frame-ancestors 'self'" + }, + { + "event": "authenticated-hello-ok", + "version": "2026.8.2", + "buildId": "2026.8.2-release-0965053fe6b9-2026-09-11T00-24-39.157Z", + "controlUiBuildSource": "bundled" + }, + { + "event": "control-ui-plugin-tab", + "state": "Disabled", + "sandbox": "allow-scripts", + "command": "clawctl gateway-isolation enable", + "realLauncherMode": true + }, + { + "event": "copy-affordance", + "result": "Copied" + } + ] +} \ No newline at end of file diff --git a/docs/validation/pr-28/gateway-isolation-e2e.png b/docs/validation/pr-28/gateway-isolation-e2e.png new file mode 100644 index 0000000000000000000000000000000000000000..e6c27a9bb2dd3bed080d698ed67d065506dfc3e4 GIT binary patch literal 41807 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 OG10MKJ^J$0#lHbY-#;_} literal 0 HcmV?d00001 diff --git a/docs/validation/pr-28/hashes.json b/docs/validation/pr-28/hashes.json new file mode 100644 index 0000000..e96d740 --- /dev/null +++ b/docs/validation/pr-28/hashes.json @@ -0,0 +1,16 @@ +{ + "algorithm": "SHA-256", + "packagingSource": "9aa1df286c2b6fd59b4c101201ddae0ae6000324", + "upstreamSource": "0965053fe6b9341776df147a6934b7485c60b5ca", + "files": { + "launcher": "a85063b9caedf346444db4da72fa3ab62a0bf913b6a4b6056b8d0b6d4a476def", + "node": "b3094d0b49f9ad602262a9921551737bb97637c05dd357a06ae98188d7290aa3", + "upstreamNpmArtifact": "d0d5b0a0a2ace6c0a957b785929af6c0c0d69fe5dc5fb47e995db25ec700548f", + "runtimeEntry": "4f4d29770da4f86dbd0e07cbd4d46deab785905dd89ac719033fcfd866fb5d17", + "gatewayBuildInfo": "b5bbc98a27a81d41261ca7984a36126ccad982e3fb1ea46e67234cc007038648", + "plugin": "7f98aed21bf7773f78a25c94c47d4f0f4efd6998c314201869307b798f25d1cd", + "controlUiIndex": "73ac0e6f38896c9d491b45a6c3d23b4990dae139d8ff3cb93da7a33e640bb1e7", + "controlUiEntry": "1430574e4d89d8c43b303057d918fdba41a4a940d436d7feaf8e0122a533e4f6", + "screenshot": "1dd7153742614febf8d00aa08c50f3c3465cdd8a7a3b4cf87e8fa585177a0142" + } +} From db2b16c579410d1e8895e1f491d920d2a024b89e Mon Sep 17 00:00:00 2001 From: Chaz Gordish Date: Thu, 10 Sep 2026 21:15:15 -0700 Subject: [PATCH 24/35] test: expand Gateway isolation contract proof Attach runtime and payload matrices, clipboard checks, regression results, and authenticated Control UI screenshots. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- docs/validation/pr-28/README.md | 183 +- docs/validation/pr-28/copy-checks.txt | 6 + docs/validation/pr-28/copy-fixtures.json | 104 + docs/validation/pr-28/fixture-disabled.png | Bin 0 -> 41835 bytes docs/validation/pr-28/fixture-empty.png | Bin 0 -> 28633 bytes docs/validation/pr-28/fixture-enabled.png | Bin 0 -> 41850 bytes docs/validation/pr-28/fixture-invalid.png | Bin 0 -> 28633 bytes docs/validation/pr-28/fixture-missing.png | Bin 0 -> 28633 bytes docs/validation/pr-28/fixture-uppercase.png | Bin 0 -> 28633 bytes docs/validation/pr-28/fixture-whitespace.png | Bin 0 -> 28633 bytes docs/validation/pr-28/hashes.json | 11 + docs/validation/pr-28/launcher-disabled.png | Bin 0 -> 41835 bytes docs/validation/pr-28/launcher-tests.json | 234 ++ docs/validation/pr-28/payload-matrix.json | 3656 +++++++++++++++++ docs/validation/pr-28/payload-matrix.txt | 625 +++ docs/validation/pr-28/plugin-tests.tap | 136 + docs/validation/pr-28/policy-checks.txt | 4 + docs/validation/pr-28/runtime-checks.txt | 8 + docs/validation/pr-28/runtime-matrix.json | 686 ++++ .../pr-28/runtime-registration.json | 28 + plugins/gateway-isolation/index.test.js | 68 +- .../Test-GatewayIsolationPayloadMatrix.ps1 | 377 ++ scripts/validation/gateway-isolation-copy.mjs | 72 + .../validation/gateway-isolation-runtime.mjs | 231 ++ .../GatewayLauncherTests.cs | 14 + 25 files changed, 6321 insertions(+), 122 deletions(-) create mode 100644 docs/validation/pr-28/copy-checks.txt create mode 100644 docs/validation/pr-28/copy-fixtures.json create mode 100644 docs/validation/pr-28/fixture-disabled.png create mode 100644 docs/validation/pr-28/fixture-empty.png create mode 100644 docs/validation/pr-28/fixture-enabled.png create mode 100644 docs/validation/pr-28/fixture-invalid.png create mode 100644 docs/validation/pr-28/fixture-missing.png create mode 100644 docs/validation/pr-28/fixture-uppercase.png create mode 100644 docs/validation/pr-28/fixture-whitespace.png create mode 100644 docs/validation/pr-28/launcher-disabled.png create mode 100644 docs/validation/pr-28/launcher-tests.json create mode 100644 docs/validation/pr-28/payload-matrix.json create mode 100644 docs/validation/pr-28/payload-matrix.txt create mode 100644 docs/validation/pr-28/plugin-tests.tap create mode 100644 docs/validation/pr-28/policy-checks.txt create mode 100644 docs/validation/pr-28/runtime-checks.txt create mode 100644 docs/validation/pr-28/runtime-matrix.json create mode 100644 docs/validation/pr-28/runtime-registration.json create mode 100644 scripts/validation/Test-GatewayIsolationPayloadMatrix.ps1 create mode 100644 scripts/validation/gateway-isolation-copy.mjs create mode 100644 scripts/validation/gateway-isolation-runtime.mjs diff --git a/docs/validation/pr-28/README.md b/docs/validation/pr-28/README.md index fafc9db..f9815d0 100644 --- a/docs/validation/pr-28/README.md +++ b/docs/validation/pr-28/README.md @@ -1,47 +1,79 @@ -# Gateway Isolation local end-to-end evidence +# Gateway Isolation validation ## Why is this change being made? -PR #28 needs evidence of the real Windows launcher starting a Gateway whose -authenticated Control UI exposes the Gateway Isolation tab. This records the -local run against packaging commit -`9aa1df286c2b6fd59b4c101201ddae0ae6000324`. +PR #28 adds launcher-reported Gateway isolation status, a read-only authenticated +Control UI tab, and payload provisioning. This evidence covers those implemented +contracts against production source `9aa1df286c2b6fd59b4c101201ddae0ae6000324`. ## What changed? -No launcher, plugin, or upstream runtime source was changed for this validation. -These files add the full-shell screenshot and sanitized machine-readable results. -The GitHub user-attachments endpoint returned HTTP 404 for the target repository, -so the evidence is stored in the PR branch instead. - -![Authenticated Control UI showing the real launcher-reported Disabled state](gateway-isolation-e2e.png) +Added reusable validation harnesses, expanded regression tests, named test results, +HTTP/browser assertions, runtime registration output, and authentic screenshots. +Launcher and plugin production code are unchanged. ## How was the change tested? -The run completed at `2026-09-11T02:45:20.860Z` on Windows x64 build -`10.0.26687.0`, using .NET SDK `10.0.401`, Node.js `24.16.0`, and Microsoft Edge -`152.0.4191.66` through Playwright in headless Chromium mode. +### Evidence matrix + +| Implemented contract | Executed check and result | Proof | +|---|---|---| +| Interactive launcher selects `disabled` | Real NativeAOT `openclaw.exe` started the Gateway with inherited `CLAWCTL_GATEWAY_ISOLATION=enabled`. The child Gateway and authenticated Control UI reported **Disabled**, proving the launcher overrides inherited input | [Runtime matrix: launcher-disabled](runtime-matrix.json), [screenshot](launcher-disabled.png) | +| Both exact values are supported | Real Gateway processes with explicit `enabled` and `disabled` launcher-input fixtures rendered the correct state, green/warning tone, and inverse CLI command | [Runtime matrix](runtime-matrix.json), [Enabled](fixture-enabled.png), [Disabled](fixture-disabled.png) | +| Invalid or absent input fails closed | Missing, `invalid`, `ENABLED`, empty, and whitespace-padded input each returned **HTTP 503** and rendered the invalid-launcher-mode diagnostic, with no status badge, CLI command, or Copy control | [Runtime matrix](runtime-matrix.json), [missing](fixture-missing.png), [invalid](fixture-invalid.png), [uppercase](fixture-uppercase.png), [empty](fixture-empty.png), [whitespace](fixture-whitespace.png) | +| Route authentication | Each of eight Gateway runs checked GET and HEAD with no token, a wrong token, and the correct token. Unauthorized requests returned **401**; authorized requests returned **200** or **503** according to input. HEAD had an empty body | [80 HTTP case results](runtime-matrix.json) | +| Read-only route | Authenticated POST, PUT, PATCH, and DELETE returned the same read-only page bytes as GET. A subsequent GET remained identical. This handler serves status for these methods; it does not implement a mutation action | [Runtime matrix](runtime-matrix.json), [command output](runtime-checks.txt) | +| Response hardening | Successful and fail-closed responses had `Cache-Control: no-store`, `X-Content-Type-Options: nosniff`, `Referrer-Policy: no-referrer`, and CSP restricting frame ancestors to self | [Runtime assertions](../../../scripts/validation/gateway-isolation-runtime.mjs) | +| Full authenticated Control UI integration | All eight runs completed normal `hello-ok` handshakes with the matching bundled build, exposed the sidebar tab, and rendered the plugin in `sandbox="allow-scripts"` without `allow-same-origin` | [Runtime matrix](runtime-matrix.json), screenshots below | +| Copy behavior | Clicked Copy for the real launcher and both valid input fixtures. The button reported **Copied**, and browser clipboard readback equaled the exact inverse CLI command | [Runtime matrix](runtime-matrix.json) | +| Clipboard capability branches | Six browser capability fixtures exercised Clipboard API success, API denial with legacy-copy success, and both methods unavailable, for both valid states. Manual fallback kept the exact command selected and showed the manual-copy instruction | [Fixture results](copy-fixtures.json), [output](copy-checks.txt), [harness](../../../scripts/validation/gateway-isolation-copy.mjs) | +| Process-stable reporting | Seven initial inputs were each read exactly once. After plugin construction, repeated changes to the input source did not change any response, including fail-closed responses | [21 passing Node tests](plugin-tests.tap), [test source](../../../plugins/gateway-isolation/index.test.js) | +| Typed launcher selection | Enabled/Disabled mappings and invalid enum rejection passed, alongside argument forwarding, working-directory, process, and entrypoint regressions | [57 named passing .NET results](launcher-tests.json), [test source](../../../tests/OpenClaw.Launcher.Tests/GatewayLauncherTests.cs) | +| Minimal runtime surface | Real launcher-driven runtime inspection reported bundled, enabled, activated, loaded, imported; **one HTTP route**, **zero Gateway methods, tools, services, diagnostics, hooks, commands, discovery services, MCP servers, and LSP servers** | [Sanitized runtime inspection](runtime-registration.json) | +| Control descriptor | Node tests verify the exact label, Control group, order, icon, route, `operator.read` scope, gateway authentication, and exact path matching | [Node results](plugin-tests.tap) | +| Payload acceptance and rejection | **66 passed:** 33 per architecture. Six accepted cases verified exact three-file shipping content, matching hashes, metadata, excluded tests, and environment restoration. Sixty cases rejected missing/conflicting plugin directories, failed runtime inspection, and invalid/missing runtime shapes | [Payload matrix](payload-matrix.json), [transcript](payload-matrix.txt), [reusable harness](../../../scripts/validation/Test-GatewayIsolationPayloadMatrix.ps1) | +| Signing and workflow regressions | Existing signing-input and workflow package-version checks passed | [Command output](policy-checks.txt) | + +### Screenshots + +**Real NativeAOT launcher, inherited input `enabled`, actual reported state Disabled:** + +![Real launcher reports Disabled and copies the enable command](launcher-disabled.png) + +**Real Gateway with explicit `enabled` launcher-input fixture:** + +![Enabled input fixture displays Enabled and copies the disable command](fixture-enabled.png) + +**Real Gateway with missing launcher-input fixture:** -### Runtime composition +![Missing input fails closed in the authenticated Control UI](fixture-missing.png) -The launcher was published as NativeAOT from the exact PR commit. The expanded -x64 application came from the successful -[packaging workflow run 34546297901](https://github.com/openclaw/openclaw-windows-packaging/actions/runs/34546297901), -artifact `openclaw-gateway-payload-x64` (artifact ID `10179486438`). -That workflow's packaging commit was -`52f2a53fb62b13499ba2692d81870413fcaf943a`, not this PR. -Its recorded upstream source is OpenClaw `2026.8.2`, -commit `0965053fe6b9341776df147a6934b7485c60b5ca`. +**Real Gateway with invalid launcher-input fixture:** -The PR's three shipping plugin files were copied into -`app\dist\extensions\gateway-isolation`; the plugin test file was not included. -The source and runtime `index.js` SHA-256 values matched. +![Invalid input fails closed in the authenticated Control UI](fixture-invalid.png) -To resolve the previously reported UI/Gateway build-identity mismatch, the -Control UI was rebuilt from unmodified source at the same upstream commit, -using its frozen dependency lockfile and declared pnpm `12.1.0`. Only the -generated `dist\control-ui` tree was replaced in this validation payload. -The supported build inputs were: +The valid-state fixtures set the process input directly while running the real +Gateway, plugin, authentication, and Control UI. The launcher case runs the actual +published executable. All screenshots are captured after clicking Gateway +Isolation through the normal Control UI. The same browser context is reused +across the matrix, including valid-to-invalid transitions. + +### Test environment and provenance + +Windows x64 build `10.0.26687.0`, .NET SDK `10.0.401`, Node.js `24.16.0`, and +Microsoft Edge `152.0.4191.66` through Playwright. The application uses an expanded +package layout: the NativeAOT launcher beside `app\openclaw.mjs`. + +The pinned OpenClaw `2026.8.2` runtime, commit +`0965053fe6b9341776df147a6934b7485c60b5ca`, came from +[workflow run 34546297901](https://github.com/openclaw/openclaw-windows-packaging/actions/runs/34546297901), +artifact `openclaw-gateway-payload-x64` (`10179486438`). That artifact's packaging +commit was `52f2a53fb62b13499ba2692d81870413fcaf943a`. The three PR-owned plugin +files were provisioned into `app\dist\extensions\gateway-isolation`. + +The bundled Control UI was built from unmodified source at the same pinned +OpenClaw commit, with its frozen lockfile and declared pnpm `12.1.0`. +The supported build inputs used the Gateway's canonical build timestamp: ```powershell $env:GIT_COMMIT = '0965053fe6b9341776df147a6934b7485c60b5ca' @@ -51,79 +83,34 @@ pnpm install --frozen-lockfile --ignore-scripts pnpm --dir ui build ``` -The timestamp came from the Gateway's canonical `dist\build-info.json`. -The authenticated WebSocket response reported: +Gateway and UI reported +`2026.8.2-release-0965053fe6b9-2026-09-11T00-24-39.157Z`. +Served JavaScript matched the rebuilt output hash. Authentication and bundled +build-admission checks ran normally. [SHA-256 fingerprints](hashes.json) identify +the exact launcher, Node runtime, plugin, runtime entrypoint, build metadata, +and UI assets. The runtime matrix also records the tested executable hashes. -```text -type: hello-ok -version: 2026.8.2 -buildId: 2026.8.2-release-0965053fe6b9-2026-09-11T00-24-39.157Z -controlUiBuildSource: bundled -``` - -The JavaScript served by the Gateway matched the rebuilt JavaScript SHA-256. -There was no custom Control UI root, cross-origin UI, edited build metadata, -mock Gateway, injected status HTML, disabled authentication, or build-admission -bypass. +### Reproduction -### Launcher-to-browser flow +From the repository root, with a published launcher/application layout and the +compatible Node executable available: ```powershell -dotnet restore .\OpenClaw.Gateway.MSIX.slnx dotnet test .\OpenClaw.Gateway.MSIX.slnx --configuration Release --no-restore -dotnet publish .\src\OpenClaw.Launcher\OpenClaw.Launcher.csproj ` - --configuration Release --runtime win-x64 --self-contained -.\scripts\Test-GatewayIsolationPlugin.Tests.ps1 +node --test --test-reporter=tap .\plugins\gateway-isolation\index.test.js +node .\scripts\validation\gateway-isolation-runtime.mjs ` + +node .\scripts\validation\gateway-isolation-copy.mjs ` + +pwsh -NoProfile -File .\scripts\validation\Test-GatewayIsolationPayloadMatrix.ps1 ` + -TestRoot .\.validation-payload-matrix ` + -EvidenceDirectory .\docs\validation\pr-28 ` + -ProductionSourceCommit 9aa1df286c2b6fd59b4c101201ddae0ae6000324 .\scripts\Test-SigningInputs.Tests.ps1 .\scripts\Test-WorkflowPackageVersion.Tests.ps1 - -# Run the published executable beside the expanded app directory. -# OPENCLAW_STATE_DIR and OPENCLAW_CONFIG_PATH point to a new test-only profile. -# OPENCLAW_GATEWAY_TOKEN contains a newly generated random token, not a real credential. -# CLAWCTL_GATEWAY_ISOLATION is absent from the parent environment. -.\openclaw.exe gateway run --port 19428 --bind loopback --auth token --ws-log compact ``` -The profile used local Gateway mode, disabled mDNS and browser automation, and -contained no model credentials. Process inspection confirmed that the listening -`node.exe` process was a child of the published `openclaw.exe`. - -| Check | Observed result | -|---|---| -| .NET launcher tests | 54 passed | -| Node plugin tests | 6 passed | -| Synthetic payload provisioning | Passed; this is a fixture test, not the real Gateway | -| Signing-input and workflow-version tests | Passed | -| NativeAOT publish | Passed | -| Actual launcher `--version` | OpenClaw 2026.8.2 (0965053) | -| Real runtime plugin inspection | Bundled, enabled, activated, loaded, imported; one HTTP route; zero Gateway methods, tools, services, or diagnostics | -| Anonymous plugin route request | HTTP 401 | -| Authenticated plugin route request | HTTP 200; `Cache-Control: no-store`; restrictive CSP | -| Full Control UI connection | Token-authenticated `hello-ok`; bundled matching build identity | -| Normal UI navigation | Opened the Control UI, clicked Back to app from the fresh-profile model setup screen, then clicked Gateway Isolation in the sidebar | -| Rendered plugin iframe | `sandbox="allow-scripts"` without `allow-same-origin` | -| Real launcher state | **Disabled**, with `clawctl gateway-isolation enable` guidance | -| Copy affordance | Clicked Copy; button reported **Copied**. Clipboard contents were not independently read back | -| RPC errors during browser run | None observed | - -The screenshot was captured from the real rendered page after the sidebar click. -The model setup screen was exited through its normal Back to app button; no model -was configured or queried. See [e2e-results.json](e2e-results.json) for the sanitized -browser assertions and [hashes.json](hashes.json) for exact file hashes. - -### Scope and limitations - -This validates the local runtime path in an **expanded, unpackaged application -layout**, not an installed MSIX. It does not validate Store installation, -package identity, execution aliases, MSIX read-only enforcement, signing, ARM64 -execution, or an unchanged workflow payload. The UI rebuild described above is -part of the tested composition, not a workflow/source fix. - -**Disabled is the real interactive-session launcher's report.** Enabled, -missing, and invalid values are covered by unit tests only. No isolated-session -launch or isolation attestation is claimed. The CLI guidance was displayed and -copied, not executed; changing isolation remains outside this PR's scope. - -Only the validation-owned Gateway process tree was stopped after capture. The -test port was checked to be free afterward. No existing Gateway service, user -profile, installed package, or system Node installation was changed. +The runtime harness creates isolated profiles and random test credentials, checks +that its loopback port is free before starting, and stops only the process trees +it started. After each case it verifies the port is free. Public results contain +assertions, versions, and hashes rather than tokens or machine-local paths. diff --git a/docs/validation/pr-28/copy-checks.txt b/docs/validation/pr-28/copy-checks.txt new file mode 100644 index 0000000..7f2fa31 --- /dev/null +++ b/docs/validation/pr-28/copy-checks.txt @@ -0,0 +1,6 @@ +PASS enabled/clipboard-api: copy success +PASS enabled/legacy-fallback: copy success +PASS enabled/manual-selection: manual text remains selected +PASS disabled/clipboard-api: copy success +PASS disabled/legacy-fallback: copy success +PASS disabled/manual-selection: manual text remains selected diff --git a/docs/validation/pr-28/copy-fixtures.json b/docs/validation/pr-28/copy-fixtures.json new file mode 100644 index 0000000..c9f65a5 --- /dev/null +++ b/docs/validation/pr-28/copy-fixtures.json @@ -0,0 +1,104 @@ +{ + "kind": "Browser capability fixtures against the actual rendered plugin page", + "completedAt": "2026-09-11T03:55:15.070Z", + "cases": [ + { + "mode": "enabled", + "capabilityFixture": "clipboard-api", + "command": "clawctl gateway-isolation disable", + "label": "Copied", + "calls": [ + { + "method": "clipboard.writeText", + "value": "clawctl gateway-isolation disable" + } + ], + "passed": true + }, + { + "mode": "enabled", + "capabilityFixture": "legacy-fallback", + "command": "clawctl gateway-isolation disable", + "label": "Copied", + "calls": [ + { + "method": "clipboard.writeText", + "value": "clawctl gateway-isolation disable" + }, + { + "method": "execCommand", + "command": "copy", + "selection": "clawctl gateway-isolation disable" + } + ], + "passed": true + }, + { + "mode": "enabled", + "capabilityFixture": "manual-selection", + "command": "clawctl gateway-isolation disable", + "label": "Selected", + "calls": [ + { + "method": "clipboard.writeText", + "value": "clawctl gateway-isolation disable" + }, + { + "method": "execCommand", + "command": "copy", + "selection": "clawctl gateway-isolation disable" + } + ], + "passed": true + }, + { + "mode": "disabled", + "capabilityFixture": "clipboard-api", + "command": "clawctl gateway-isolation enable", + "label": "Copied", + "calls": [ + { + "method": "clipboard.writeText", + "value": "clawctl gateway-isolation enable" + } + ], + "passed": true + }, + { + "mode": "disabled", + "capabilityFixture": "legacy-fallback", + "command": "clawctl gateway-isolation enable", + "label": "Copied", + "calls": [ + { + "method": "clipboard.writeText", + "value": "clawctl gateway-isolation enable" + }, + { + "method": "execCommand", + "command": "copy", + "selection": "clawctl gateway-isolation enable" + } + ], + "passed": true + }, + { + "mode": "disabled", + "capabilityFixture": "manual-selection", + "command": "clawctl gateway-isolation enable", + "label": "Selected", + "calls": [ + { + "method": "clipboard.writeText", + "value": "clawctl gateway-isolation enable" + }, + { + "method": "execCommand", + "command": "copy", + "selection": "clawctl gateway-isolation enable" + } + ], + "passed": true + } + ] +} diff --git a/docs/validation/pr-28/fixture-disabled.png b/docs/validation/pr-28/fixture-disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..e3be96a07d9bab4293ef4d378cc98e101de84d44 GIT binary patch literal 41835 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≶6q`BwXQ%68lG*A*A?_B<5^2z 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 literal 0 HcmV?d00001 diff --git a/docs/validation/pr-28/fixture-empty.png b/docs/validation/pr-28/fixture-empty.png new file mode 100644 index 0000000000000000000000000000000000000000..0b4559fd397ae5bef26581b2deec5c38437bb009 GIT binary patch literal 28633 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 literal 0 HcmV?d00001 diff --git a/docs/validation/pr-28/fixture-enabled.png b/docs/validation/pr-28/fixture-enabled.png new file mode 100644 index 0000000000000000000000000000000000000000..d0518d6f37f93c39f98f645fc2e5803719e70553 GIT binary patch literal 41850 zcma%iWk6Kl^EZf!1*jlMOE)MDODQES-6hhpG)OF{bc2+1cXzW&!_wVdOD(W0OYQP3 zexKj_|9!*R%f0tZoij6^nG^C!Sr+dJ*%K@*EIhf7QfgRO4>GZ^?h8J;hq>eM{onx> z)^jX5srMS+QV*6MOK423_nzPk($ePK%f6S8$1{nvuRpuxC-KFYR9!=nDTwRN(B z7H5(e+?w-MyTL#oN220cRswmE)f26s428@J_^jNWr(k#D*I#DF8ZS_-9-cGKBW*nN zNP(Ty67DY+k5;MN7&zqe#tWy&urV}XePWmW_ZKVE;XcLP4J?V1M}q$b-`8I_{2O3( z)2;qD2$lWw-_t}<-*NukVy42z{Wtg|au5ICAoCk5c4lgIb$FAIkkI&TO^vg&v%=Ey z-@kwR`{k}W|Bk)SQwuw7OzFLo0oE4nD%JoEfC&JJhOX}JZsv~JOpa&Z4d`at>g?yb1U?>NkCIe(*L?M5{m$Fz#H$|>_qKI9eCw&g+x z2W^va6f{F~*w{3_=|iY6yIFLj=43Xh)2**kL1f^Hb<&U|S`?!Af`V`jkD z9WDp57A))`v!=PayhwWhpZ zzK{Zml7K{2ZQhpdlJnB@s#vf5;{KSEa@P${9sCmfEhlYVq5f6ow|BnvWz~1 zeHM-SlZ$6tlpg@9mR8H>fg zlmKMv_|wJxLGf?Nti$F@St1*KR<*<9J0caP0gK*}ku=4jfYp4nJ8SqOE=?6OA(uC% zkVGyI&*>&> zoCvLnQfW2uIl4o zQU65z%3oex`l?=#XQQ}L)D2&7bB!llz~4~L7*Ikc2ur zY)3@I%ip7$6;bAGXMFWq+6o06K3OWamn-f=aobNz8-Q;{7@5ci**J}dSIQjL@)`0y z;LYRmm99sN8JwI`i;IhPB>DOD#$cbJf@Rb__kf}>`ADMbS55q)hT_gWEn&_y(@@n9)~KWE=L!J2-z|Yt9z2;tZKl0+z5jnSPkf zNfQr-r!-#oW;ORUP*c{6%~k)9?VYxy*lHmMgO*iv z21vP!rlyltk|4*sx0mI*>xUf-L{l1@L3S$&0wYQ(b#F(|p6fcFKXl}XQV~{WuvyEo z#>Z~L0}lj7JeBi_X~(P7xYA12jI@bhL3SxfDH#{oa)FYAequ4Fnh8xFE1({T-mj?P zYQOO`rXPw=Q% zORXoCL?vk-=+ZtQr4$$Tyh8E>fjv1TZczm*X)K;u5&p34QkC=9)kl-i6%}bkUnz1P zFKGHht{cmf=!Dbx1W6LEtC8@?x~?jrk&5sMf1g^jm5~@o`YFE@_}1v#@-CSp+ie=U zLmN-ob>SyTKIC`k*0urEadUdM*&u%oUu%fPG`B)v^2aV zx7WAp$ccC2hhY@A!;_J>d$f7v9lZQeQ6r#Eqt_^h-JDxI{q!_feGjHPBe`*=z-&{E zc;^S%YUe2>wZ>w%+}ujZ4$zyMrv%OM=837L?6OL_?H-W^Ez@sWsrMP&iL7R7z!rz| z#|YdNdj^nu`9$-RD)%F}eS!E2&qdztQ3ktllbJLJ(WPIQjiKv@uF|yQU*}B)fQKW? z^(%{sT?HG&Ror^t-1nyIVy#<9-xXN?zDl7_<+jReq*cvGcb6>jusQjab()CeLLl|w z?-#eu2KBI`Tw0p^KYtAAAy{a?-5;rmjEsyr-{o+qXm+GC8!G0y#`{+7i69G@al6vC zI;$$QTi_R+ZUNshiS1>jCdB)|ur-x%n$UdA}#4;y=`3iKofI~&?+;2-0z z(0L9~L*ue!=CDaJOBIwCSn zQo#Nq;h$Uo{y}JYnTO$US~`y+O?{o#T^<@3X5H02q3_NZ?+RXARP!qOa&&VfOJksa zzPC~VzC;9rWpXP}PL}z7la`*yQ0=FsKGoo0j`MhW8)%G7A5?XDQV77N5nw>&M|#YGWbvWUh+dij^=mjf>J zd#gb(mB_Gb$yt@2i16$E>x9a`Amr3YUnA$80wj_K;4U$=vmC2^{qGVSA%yx3XpSxN zw4P^noI~Aq3!eFdLChSIO*g3dwhtF={PyV7D2pUx1;+D4rytbd>vudyHQ1C?Hh}dS zoJ5|waO)pBV#p?X#|4fXq_nv(Z8?LkfYK|%9~ zZ(dRL8!gnL93-#_-=62x{Z zh;F<+$XLEbp*oyL!Qj{1mcgmxupGJ$hOMObFf!^9zc69zaEC%S)!xdoy1u zZEJm1Qn`gk#{b0HxXYn>%Tw{|EJ?;xxnYCXh**F&>9ZuB0wqtMO$cKD`c(rUD&)>F zfBI`UQ4p+eKJs1$QW?MYXBahZoSr5O5im`TRzB>C!I{B75aQH}OLvGne4aPHXEVjR z&@nE++^CVv=B})|_X9?m!F7G0F<9YIpP2>+{P@{i-dQ_)prz~(# zXb)zx014Ku;Y8#m9-gMA9^A60YQx&E6+Q~wNi5T;MxW#r4TbzlSEcU`w&{St;0Vi@ z%N`Le%GGWm0hP@@;P8l9+uGi~Xk@hLtazqS=Y4ny14+_1iIq|)@ zj-A5hk3pn}(i-u@q-d(|KM z^3Y4E@6p_6tuUoCs!eONHPiqG$>i|8#>)d*ynUU^5g^KYZhv~-WqrGV+PH$XD~lkt z`4Vl!kSi-WGaKWl>P#x$?cQURFLKDmMnB%M^hdM@h#ho;@M5`?*-KBWI$h~8ZU@~a zrc6`LHLW}!Z>sf|#B$GIX@xjCFlRT#`btdLgLM%ZDm!u$GJZ50SFZ3grI*8~%Sj%| zCtQi4t}g6vX#@YoB87a(vQ&qNk6Bwv$^5rq8ai!uL&2-M25;{pHpG8{ND^kAO$I2}%KHtbvl7dej$>y%FsZE6K{~1^ z3hBbO^*k?F@7!}Xqg+#|!I5=ldnDX5yQqe_AQ!M8sw{K#Lc`1zyLTXhuN=7Se)*q7Nqw?AjXm$eLQ!dEAI63oI z)xw*caYK`keY1MISU6rrIl)wmb;NcSRephG`ZAfvQ9Zut`t;TmY0Yx1ajbBD`Nip| ztEs!(zGX!)v3p-kKuv=&(en-nq+}U{40|H+_CNQ|sXXF+9w}Py!rV!Y> ze7>}4B$B?3*Hm#H?1(Dofi^!(!xg16kmZot3qb&Ab8d;+-!I|1TI*=e&({v2A=%0% zmApIbYB}ww{w8gEXPoG;Ann_2gd&r@_376xk`>tkCKS3mwD4gTmus;vYH-*UfZrW< zMMEHdUI-rEzUT=HnyFa04r;e>Siv?-NuK^H`~6P`P7T=B_5 z&C1HvDj&$clI<-aCDl$VX?}9e;>~`yI10ZUT>REq+ts*vlf}{F-DbXk)E8s%jElfs zxim5&*K#F3Sf7Ky+h)#UuyBpC#wSufHlgvdxf?i}VZ+M3eO)Hw zZtr}uOoP)b;8eB*{F7PW*DC#Wwq+&Xo#LJ2Uht=#C3xaZa*TFFoG9Fqy5x57cgTiU zk+05;Qfe!^>#YxHb+sWs(h9vmVygPVK)m&Hf6{jvGp0xwWBV=lY3ML`;rr@*IUY6n z+g;)okv~U@{b@w_f@DAg$nfw>H`-V69vj6qwshArwD+dFO)&7bGktw(%z+Wq{N@?MGpqCDzaii{)mKaQLu zawA-k*Mi2v?s6JLA9->7A|+=YDHxmDaC1d$$b$3piO|2 zs2k!SqA23$Pf(iyM3k8Ihzu7lkrSyb)Fd=hkzZWWs8Qs!57OkeH{knXFrzgk_byQ5 zKuL*puyHZ*Bn%9e6`X)9x2mFDfIt=oO9pepU@-4 z*m(2j=WkwZ?dqU`T+Puj>Mq8hPX4cS^>i{lU#OW(>{u{1Q7Sk0Yx=o*5zk1asg4A`Mtf%4&j9k&FUf>eT57Z)|5BHmZ4 z%Y%h8PtziAvVO5?KaK1#a5?%HVVMxTs(KKVB(hzomr|mZ@;UNAt-;;JiCdcd)t6SR zI^}<9FP22NyCn`j4@>2zqLN3NKxO56BInKR?cYHp&l4EW@^Q@uR6HDB))xMUpKiI6 zEXaR+7kK0Ss!VUcbXsdl!!gEa+@F_?BTg#@h$X=gf8hc9eucL+^ILS?E%h-qDSiAz z?#@|G(j85ee>>^0Ghbi*w%=(TuW_!t95(Q&J9^D~9N+E5879xk6}5}>_*wYzB^`K? z$rvPgvtvy467$}7JPd5htmu0d7W3ZjD(aejUE{MSdi`t@(o5Ses(x;1V!1a$p~sL% ze;BAynJaGd(D*Gu$p_bQj&QJwc zAX59}b4tPejmJ?BCdUULh{uk&J_hx=`sdmBN}gu{L#O^=2_eegi?+MO9@+0JZUV$` z5-6$O+)>tn!J#w14~nV>>5b#vvWS;bPrtE`(Aps!J-qZ-x_|e`lORr3ER+4yhTG*# z0j0~ z>VsK&!Lz(5g5R>|A0Iuf3rD^~6q--a%Iq$lBzyEn+i$gT6Q27=c-32e+E2(2;Lvv= z=SImwBxo$R{q-I`hf*f2C@?zjf7(~Y53Y`374i94xb=KIQZz%4(yWGmdja{h9OP^H zL9X+Af`(<4Wohjw20RrDNHW~AGg`21D?Vl81+U0MWgF($jubCu)3-TN?juuMYsRAr zFj{qhy;;S=B2Nl(r{GcXKpegLG|=;SzgJO*%H!b19P*YDniyr5!dp0#o0E6IH0+PC zMV{qyj3rRwK`q@leOYr?b}Vys7#dR=nksj5wk6h2vnZ$O*Z#bRYYeJ7GvIt!=%-Vo zNaj1pu_x#;Ru&+@|2*1DiT2hgDCpQqK|UOb_-eN-#JDc8yma$7EhZma7lp868jt|H zD>f7`f!r??n_X{W&Mt6pW9^g+ZX3CtmZO{$gNEtAqUr`A3{y?s1I}muBY}a+vu*>e zc{HfW>m5SP! zUF#NgIqlfg%&;3G%7)*wUK?Ld7{4jFQAv)_os~mtu-Jy-dt1yL}kPg zm~tplTp$P3$BpYgWZ2m*UmtJ#C1~N{`Yc*rE!fUZlu41&`;RPqRpt?HcfZJmeh~n@ zefvk`wcIi^n31boh|@4M)@!=mip-hbW2KR0KTLsLzSz0IW4z<-{wO&f<6)e|2ok;o^~=MGqZ%1%9OpuYqF0;AT_`RHn+iW2h> zDSRqcX!_;uOh$^AwhPGei0*VZZ=Mkpl*AL9rcW1Oh5CpK55$e^f86_nnl4vdzrn_u z)uUojn0`B&<+RV%Y}|2sy9g)c+kUMYAEz-f72cqo#LFC&ek8KaQ=G(x^X;iDJV!Ii z4!u0-($%u_hcVmxGc7-LODaokUGiDVwVK0^i4&@K$TBUbuAH)|qq3@mFFUIPL(<}6 z`J2LtxI$9dU65nCWp zb?@ZsO^q&m_4cu=ZkoLCcb~W5!F>21mjzIB$`Rxbk=}M?9+fdS6V|W3#wJ|U)t|va zM9H+j#N~+%f85et;=-%^q9N4W~W`zM#ZaK--A(ICHZ6JP`Vd3dKoADA`&vhK9-ip2w+j{zXU}2?oV`AD&v#^L4JsA) z*mc;Pu6r6-OPmL{HX%j5cR|haehWIZMZFcGE1D`V@krUF%#covc^Rdgr6lFBQ+}Gw zgR3o*Ec5#CCb%dlIC!D8C(b<>-#JQ(Ix2j&`O8<>hriM-1sS{nTu^}9k!a;aEog=Y z2a;;vXY)Wsry6la-AgM*{n>7)$`t-SWkSfU&q8seF(;u4;3rJ-PQx~7&w#kW#pP}r zP{wQnpCtUW3-Hh0DlyoT&)aS8;Buv;uzWF6W`FK(pxnTFJZj8umnAN2Ikuhh)Okvm zjIm3Gs`hB9-qIf4kwzW)IEn|^*EiWKJxnUJbVa0-QBSFeygpg+;ljmUqOxYlqbOTQ z25y_357-GyeS+0*ZxdbJR}lTBgy>Uzl+n=WP!Y{2DU%dP*yimp#;WfA8;J9vF1z%l zRum-(m6@rT~E}&xl7yZ&nd!;o- zEj+jcugH0q2F5Uzu}?yn0;N(5)`Vlsx2(>vH$u635>l+_}xpHc#?USx>fSU7^)hjQQ;rO@qYnAow z9*{b!_QUk+Rm6>Def{2azcA_Z`QcP^-p9ji;P(}gbx$ZdRw%Tsa6AVM8jca!J@<O%PxIe?qh4M|NgaTdhV@waI#B>!4yT7xxi~ZJ&2Wy zE55H|)k;Z_k3!D70%LD;f9Nm&+7DsMo>-y|ixDrz;eChAR!3)F!c` z@TiE?wxX!pjllb@$Iy$eV@^CJkp3Byd)Y$eZK;uFi+SREzn?ZG4~V4Y1|j-Met1eQjgwl-Y$*m)NKj>A7bLQ5Ccuv;?TubXOR$^T!9 zY1D*ldE_L+Op}Pyn0&5OmDV~f^2E<>ln{r8=>yMV52DF%(G=QWWY4ZHF3+99!ZWvr z!&Y=_partBb&1<&d#fiEZt=aIE-16xQ^Iz?9ehNh#S!@1L1*FFvy+{3*MhW`2x@d; zLG9Z=0{i1BYYR~bvSoRR$GI*WnJi1Q4VLbpm4}#mGvPmih`#K6m&B9^w?B2(v&c5_ z?h06cK|5=60+YlJf1fpoF=#f{*;s)RLY;H27rUx7Agej$zyv1U#wMu$@KTw{kD_sl zB7gl`Dw%G%eDBBEMfMb3w@~LLDo)11snekWJflnLGo^1zPr*H=QE?yp6 zKVa1=%~c{a(*O2xQO1+GHcFdWAS)-1bn%#Ra>UV6?^fWtCyldoA-}P zzIx@Z##TUWGf2z`ZIscxPEEtWWgp0tRM7UYwZT)1n(K#e`r&o<Y95q$0V*wt*@NTCDBvzE8yyt-Y5@aIDn*`E6;Vw)uRGWm^De6m#e>>D>DvRu4}l*||+ zSa|{WDTX!EDh%87PWgp|<~TXavt54B?W~-UYor;e6CK~gL!a1fB#%mS9W~s@%XHHCMf2VwDceUS0fk_O0LpO%tlRq)L3%SwZg1sl zAIB{$*th!zUXp*c7XAwMc#0ftnm#Am$>aT!QY2tFk!2=5n|#gaBa&Ia==f&Q>+A8D z$Fyb%eEp^M)VL0$cE|~8d+EUhe_~J0x8_&J@*(wRHOUUBBKJy=iNs3OYMc>1|C5iU z%YtW3-0iYZ!rAV2&rYFuy%sF0N8&P-qhxeB$0j<%uGMn0RoCZXW z=BKO<6Xq;ev07bTM6=7r{!FPPcAGe93UTYjU)5jed04i61@?i|z)^4pKja zkGE#XrU=h|O>$3Dg3YMA(oUz>PGdaWqO{v1K{CE47mmHt)V{OH3CW%tfj51zJia%a zs3d{I?d^p4v?<0><=f$sl06___NV>gr&!Dt&0>h|F*phPiiJLfF!NGKS;B26OH!p!9Mp-L3$&Gz-CImrLgHCa$!b>&k?^HJ)15gIlr!D@h2?ImR>r_QtiJ8`ICSLPt!9s+8uZrwyWFbPtTZSwMc;G&ZmXHt4v&%UN)$N zyhcupPC;aoAk@f^CAPop3pYq)DRjGyfj8V{_rb4tw?d)@6b{zQ8)aOg*Fj(;unmL^x$5smX^ z%!Os>0tyjY>ad1v>q6S5?96V#bZf#1h^J#|zt^WNiAR?g+yFYV{G6gq zFl@FxRU4)aO(a4j4yBN{TT3|fgBnYzKdJ}p=!!k-zTr)Kw!np|1e>;pEAWec>365K z^T}kN(e0Zt9x&gGz{dJxXc_cQP{YPG$|y?w_2hZXk*F5}I#c3qY~A73tYorqBzi3< z#8L=&h84&7Ee^3i$4{fwJ?AVp&DC7ip!qkahre!aS@cW^&SAAArlMajuu{?f1-0Db zmVu2X!KbVZKluI2D*gBgP<%Zhg~GU?ec5M0NE3imcUlRf#VK2wZ}zblFP4p2J^1 zcXEGxTL4!+j>w`(xcAh+&f_{3H|Gg;y)~@W;z2i%3#4kVB(r&yl1QRW4ZkFIPE)eC z@>Rfkv3Sd(ddjlMzWKnG?$Kp(fef165YCU|8C+*Ob~DZ0nJoeNEWE~(aPgc5Jb*{mMhzv2*$4TYUY z((VLHS>f(X z6QJC6?E|Sy3?W%;3ln}1wFyHh7%zZ%?L!t&0(I3akaK|;{eAibjf`JCGjSCo51JZ2 zqO+raDQD3eA$5g!w~*1lV3%G|P4U=bVMRJiDN)R_kH-s^+7MVC@kAc}WtT!MpA3r0 z7r~_naqW(4&DDa=lJtVN4bO{qJZNrKFR|@%@I3+tZM<%?7*t8Fz9s3!JeqhTXtsq* z9uPl^8tAHTNIWLl(vHkvgz`!sh+HyF4lFH<|HS~aH_ld?*jRaQ`SgG5IC$eiu(0zv zS)Cjzd#V@DsdU;)h!{kdblRt);@Vs4QAWbB?N0-O?1mrmL)LjBZ(5{O=XHMzbC>#b z&abU=srpE3&MJorzks7E`Oe;P#db29?w!72zy~71M(0ePL^Y_UpEdcXlJdnWk2m)T zxid%mo|1$D0)D05vQ3??71=_&XiAYcTiS}*_QheI!e;QZw21L_E`Kgkq?-MY? z0Y(-6$dXSVG@J9KGW+s1Y*8#;DS2Kvw;!9l-xc4~$S zKD;)XfrFK~ZsIHp`_5(odSgLQ%X@qLdioO8QDub~=!;V#Fi2ji(cA%dLf_elde=;KW;WgHGFFX!GtW^t=`YnNNf~s(O&sfYN|4JorSyy*j>l<9xBD+dX zn2~(^xADb`Q$Ch|~0ab!&dH?UN z&ufot15)q}4bNgWQ#LKxDR~z^BsXtqnjEN9BuFx&7XTIo)&L(PU^V&s;x1A5F?SD`x{bT?FYOR7E zQ}H1eYaKmAN!Yz!c|8$`aq^FnZMA3?N>qT@^U^msJ_0P{*g;=C?`CfPED%HP`Dj6~ z(}f&d?@bWQ0YxRVo$i}lN55e4O={2(u}hEdMF&@=f3@R!VG4rW^m205cl(Qc90-2f z#1y6>p?=%!5$~~K_@-RmZscWm6E}cxj@WS&~;+jdY5l zVgnQP)XXxIOs3axYFtde`uFTH=?7Q`GE}(lz!~7%*{z&^PjuaUdEO)|M}C)uzr;L& z6mBlO%UmQLyvKa$?s8C}Tm3h{`oDF+|6f|^El0l72?)%Xyq)EYvB(SfFQ;8quD1LW zlaSEth!Ut*;P+Rj*`WC!7T^mb)IJ1Jhez!)!E|~0MR>LapP&(`k%;Og^TvCj%pRH z@63$4g=DPY=#^ls_iC=X$k{ia4Gx8mD$ z8pM;JEaK{^q$`5VlfY;gbFsb#xTwl0DYZF>tqoFx6((F;TZLVZXq9sJ5sr>vW94Rz zy5ulpzd+Z$Z}A0PII1CHWr4_&q}q*rNhWCtb&o9cY>z8g;5Bby(lD?4XTu9>f@fA~ zFR4x-9@b(`{Uox|exr%PQvyc9wLr7Yez<3yC0rKw?CFU{3R1}$q1eV#GIJdU$lWj64CdD*bLaq#%V#CW300hNZ;Y`MU2c1g(Q&h!%f`OA8 z0U2)}V)vI}_5wT&<w8QM;cUn613}G+2HZs_A*Y2AKzkI&XZhkU~qrkANxBui9Q+pr_{;^s?*0P#=i9BO9S zDm{I6(xGCZA4Ng5HjrNdE!=u;(4}r3>zTw1)EhgHOLy(Df)Z{!_eoPwIqd4|c>?`^ z7H|N;=MdCjWr`nYvQQF$e{D9p-LL4yB_L%~B7YWWF{ICDKjvw_S^is{DK~^Wj>P@K zzx_Q-!qS@iO>WZOL?Pjf-_I~oo5?cOPnGD|J)914`oajJF9*65F_R{vy(0~Y4Fonv ztruy0DL$Sqaf|jcFw|_vU+i(I*zlaO6sdv`E>YGgL8(`uuX#pX{;D(9lV`GNu9{OH zcpi8Q|}E&cCvrG9PZ`6#Dgo=QXJUct76o0}=MV zp>9y98rFuvL|L=`brp;c{A=r9(-GEp&>W`5EoZO@!z1VCR+FVk+cR&r6W332J?Gq>7?HdCitjZ>twbJS{%WN z4{;|j`|jNZsPX;dc&Rq-_rq>z!lrJ%e@rg9P`y_{KQ9zLu+qH8C%-Iu{>df5y_X4~ zFH1iqYljls)7i^0d;(0c!6O1@V=x{K8k>|Iwed4^QAyAyQ;k5$RqKgSu)8KACSXG= zq1N#-a-H&OFDOd})x;B2@7?A(?{sofxEcyS(EmpRE6z18g{EOK1O1KW#AI}qY=xNZ zOsO|;HVoucC4-)-gI|e*apji;HoDL}#R+uxp2U$%8n~}^E%T^6J0xtjHSJ1u_aSD5MMmMa z1l*b~XF@yEY`MaJ^K*XSrKi@AAtjnmc?Sx>5%NW%vND z5tM4hW4yKIN-uoHR~jg1X3VKT`3y4(oz5d~P9 zDX7oMp`=RuG0^)rI*A52QWVQnZq!D$$WL$IU(Z2aqUj9Etny70Hrn3lPLGK4YJ||` zHLNnnpEMh3OcjOcWQ{x*wsb{X=1)#ha z$)0o4OR2BP>Z(_K^JvNkC&R_TAy~+j_GplLo>fy#{TJ$n%4)neiE zemQ0`7Wr7E7ot>Hr-oqDQ5&G6#9a^6qLL7)4-uquvk@<7LsZZXy@fXzO`2fB7 zyE%LznHbm3wLspIp{dEiGUdS^0Dpr2TD=6DXa$x|^qM6-%L_$va~b>Bs%@3|W(>4( zv1dvDJN>A^E=0@J&BCG1T(>6kIJZ>>_H8OUWh+5_Dds(mskZwj+y${WWjj;8H6%Zw zU6KEJc&2~7e-W5<;P0os7dhaPH{Ll-c6B73n89^;v=I`V4ZY%s36k1wrGR#^#qk^lNWh3{>e z+wST}mSIcDK%rC}t08t?SK+^7Nc8R?R7;o_pwL5%JY5N!dUg8aq`I^)Tv$ zxFOdKEC;`Se8))PY&B)Iz2M?=Bo(wY&)Iz-?*%R}kup8|C9)SL>mziqGh@BLi;x zEB_?t4tyq*g&~zCPu0=PWSp@8S1O-JN2$sTs#pMK-s*z!NCb?EDPr)jG$zD7ETGb7 zom*mTrLqb)kddxO>pe7+IZUv~a|21-r0nZ`*k}o;=gIhBa!fbE8TRUx`uf0oY$e

Fn_|leVo~h`4*^uWJJmjqn{71)04i^!;SNYkHNJIPNNnB#GnR>S4Y2JQTKD?O zzEZz{*m8w(&)?b?;Zq-RIg$g?0Y~Ny3qLpJ>q-h#GWN#y9j%)^5y|4WM7j$#%KNx} z!1+=*_(KB-T%~LXE7pYY_3a{N`*vNXaPVw)xa1>3#8~c0TZ+RMp@d`Eb-NnIU4csa zJ{n7BRqd3nSH|M{-?;fos#-^wGBC*38Sh z&aIE5-U&j2oKnZJkbPQ_$+QCE`Y7xN4{ESeWIvfkpSF{7DTc#Zw&7U~pyN=w?R`^w zpY`kEAN`bhNoO@xCzjIOV9XAD^ecQk*}n98B_H8cPtRyvx^XmD076=};4?eI=cCR* zCoy~Zayy={Zd4ga@sl{4M569B?4P$33Vs|^uM@F#hm8U3yF)g5e{mXFp-eJcyf`KT zJI1mU8PatHOy@nJ_GU7kG6@454i`M=4+g9r8R4QWQ5K5=yQrN7i1@4bc-^f~@+Wq= z+PG1^btN}Bl=MF24Ob1~-8scBP;#xbnv|B|vWY?{2SWGe6LuqBIfrndTg(x%CD4X2 zrM=zGxO8{n{i0BkB>|<%3(+n+fEc1_ZmfJM(MP-`XSll1cH}Pu%q$o{l|sv!wArb8 z0-aVI@H)PV^~1z;?qOqDxj_Y`Av)r|OP1Tv8j@ku^@6#suC8Q@xb6)V=a8Q0q%O_M za)S4f1nClx2X%;TF}LlbmyjqJ%IXs&pW~jMdjoVl zTl^ex;d6LztDLrM0hv<2U+qDl4h|;(r%%PjOx`fKS@AncXv*6S!-+2iA?WCPpUZecf@hS(%aqr64 zT=?Ro@*QZ?*?L~gWl#pXnxt?1)M_4O2B(P?z_+$3u7_%D9*LZ~SPVYgPvW;*f#r01 zHgS&|9TrekQw1JR`TP$HxET#ksILiNqec#~Mb!x;J{N|4drVUK$Eb?DSIlm?L>hvw z!H06xd77s1R-qm^Yd&?cX31BCTz~tv-*}W?F6SpEB4sIy^@n312CdzSvE0Sl&6YNO zX^-D63I3GVD=iyd4WJ^gUSz_J(^4y=tMBYZd&O_znpWF=V_MRP&*XSL zBkaOv!*g)ngHCvW()8h38gfGyGF}2M7l2K*^tMv_5}G?!;FX60I`GXLE-v|8kjEmP zo0W$hxnU$^*8+|enkD*Cy~=3M>_bUPZxmTvl|;O=YufNBD}(*0ag~T2PB@!$6@?fxz}q8hnhrlNuq?;&g6#&2t}XvG{Dmi_YyZeD?h0_uMt_VL>F2h9gX0 z=mx6Wi?h7kXxSS|le|Qjk>WVm?6H384n8LX`@lmR0AFMV$@pB2^UbfJL53N>QA?2# z3bz%~>jD+XgJvUg??LG>#edmhcESi1MzC*_tk=PNp%F;6Ztb$7l z^4!^Ku?DXsR~vM` z%rBi>{0JlkZ=9|p1MP)0Yer4mIvb&%Va7&tq6;IO19RW6mY}EO);LIa;ANGr3ElD5 z_tk(!1kHz8)xGU9$En|ANz4|Y>B-tS&*9{KqS2h+mp=!-Dfry>&WITR zGZSrVyVCJJeVrZz2gqF^kHdV`x;#fiicj^49)Kh0B2a%UM@P4>Z9K8H4H_4yVsP^1 zacybnW8=0sz(HMM)ww2wOntlGZRYxDmnvCMt0=ccI?yw&X?|o#iqpf+ll60Ocs%i* zdY@EL;6z(ncAVbnj$NsIkGai- z%GK-f$(ifma;YuCW8vF4YAVF7Ern+gUS?Tl)mnW)j^FE>OdJ#)MHo2*Yh_@P@!ath zhx;a~KRI<9?l2Dc^U(8CPz3*+DqeW%>sL8x$-b#ivgx*8VIXI=##F?p7FQYZ(E=6d zuSbr=Bf*jp7{ll0!pH$;c}|Q>BkF^=AULmWp4hFPvf3Rcci}wboat-Ys*6b#oO-ht+)gj$lxxlO_^hoC7C;6i5{FA|I1M#t9=bc(})`9Ebczr;{ z=Zo#)VxbT!8KZ>*S#dn9-RfNS1PcD)H}#v;wNynnU@qvJ7!K{K9tJoD;QK|xwCABU zwcvPjg>AO3=DYOvbnM?9)y(X_*bkc2grYWA4Q4ZPrRi8CkX2;acP>_GWDMF%=Q*fC z9K&V1ewJR1rd)S}hW1&Bxj^pPRp!vEKa(L8_J3%S6Vg&N-0r13Wu|$F*pqk0CMmQHbQ#era1UYtQ;>6hPVc zuB34HcCxzxi88T(-So>O7=?c8n-Y#rpn!z;EIKn)mjE1yxU@rRPDgQNoc6xhyEusk zGh_`hE8@r5On->Ui4kPF28`QO-Nx}ZSpBulLjBXe```nlw6FGeh0$oec@T?yq?yxr zspDp~<7LDI+@uy%ZFhZzIMLPQEhYbBs5>*=151MF2YuaL8R}9rVK*^=m0ez-%||7R z*MW+Uv*bfA;+QBWd1l>2)*d@G6FXoTKYDtwH~h8Dpj62XLwAjWk@LmU(Vfi%O(Hrg ztwA?NrxkQFgRU1cO?XnwztDBznC>p+>Ew^nqS-IGnC!gV&Ie|@oLqxj=6hpNa+I0o}de#AuI}=fX zGryuT3WzmzeiNNK!5u+GD7(JWt8cP3XXrNj%dgS3A)LpTv1iXIjrv;BuY`nn9hQ@a zl+%z)OABf7sLx|?Erg+5${;ndmdXAVB@@Tdmm z&Aj*uh;aPsVyQjD~ zL^uBv9grh}{;`0wv$0@O`{#NNY(VGH#WEdd1M8d+%+X{gGYAm;cJ8tv-w!i_1{_Ig z^G2ge5^9Is;;!Z{eR4sV9^DIK4J!uyA2k#sArv&o`$TcaaVzgN*>y{Fq7xfWqsaxd zkM99^hRf`a#*IoS(`=V9?8NC~cH-SM;ooTCEa;2T`A?Qkn9;dz&rSO`y%N~TL zj7@;AOhHlV4UCGxR~G8y{3QjG_44+SM^k63lGh%4N-IYZ6h>sZZ0ECIcq*i%o(l@1 z21z`3M~YR$DEV{sbajn37{#UNHXXX01$#4WDi`;|HeQ9>Bg)*5uc)quLP5nQg$U2v z&8b-`ok)ox&23?|~|N3Y{B5OIkU$-@@Xrs;FzkgSNMx%JfRV#hzHz-tPa0@*YJ$%-Jd z_=hTA_~rgK2EVbX#dch@L#$e7@)L4!fcL_u3!KUsL4Pz-dOU9w063`dn+<2Q`0mpI z_G^;Orx3)GoD#oY0CbTY4$KN-f-|qhxl;E75d-_%4ZchMw5cK}FPn4gZM};?4=3`w z_AuH!&3is|ZRf{5#&fB9h!3<%*KYSFEEw9!`>&(-750J*VMtm&Ti| zFJcp>{G=)`>5w2H(T~=5$vCqhqmO`2$$Rc?|B&+KM3*bet>4~Gdf=-7JLoCQw#4LZ zo@_8{zPmqBX0s6EnSbJP<=xqsDbDSNcA6DRv~_%QVV zZlN8(UY)G)Qyy=-dwbS@)5Xfhs>MexBlUz@3?t=-VSa#iqhDv*Lk^W4-|YWsX^(a5c5 zOe9?Qu_ZCtgWB~gA||eNf%;RB6`j~YeH`Q)3i8?QjjY=Sp5PE{#`}%B%lkp|F0yQ? zxH^2f5t$Nd&#sw`4{s=9dvD6!utV*coaZI2utRvOyp~6iVeF<-J@b#bgHJcfVgLK> z@r3#6$zojMogSp{gT&I`B6= z!^XgRuV71!X<$(y@7r6g`_qQX1G8IfGUOepE%^V?9I&tw7Q}9+t^RVC`aJLS`g6!{ z>cAAcNJg?&c_EQC5fK9ISi>+KEANl@_gEy1Z~jj$Q=VIvKeH%qn*oOmJbNo}b@7|5Is+3gyA0#$rcxPYW3Yb&E$7OkxQ$mYm(3+}s|w_Q)%U z(&$WQ&UawUh_V@usF*a7?l%qsiej20n0G(U{;n=^tcC%CpBtkr; z+qt1qB^rcpgjZDU92`3Np*JstGmEQ0Amrleimi&)jdXbPB`1{exRXDi^=1wIec!U) zl8vJu8yN{sV^w3=blu+Au;QEji@W#lEsgPSv+Bpz7Pkq}y!r8>!&R?S>F*mC>?ja$ z-Ox`swnz(`=e?vF9X+xj0_8u`h{AK46c!O#TUoiu`QW@U*X=bkty+U)>tX-l4s>;? zfUB7M#Mq@#QE;_$87(|_0mwoNOIE!xvs0CQ6vEmg6PK_^ZNMHyv(d_PYL|+zzpJ1& z75mtDCGL1;KPDZ8#`JyhXQh)LvyA8HcYY{5S<0U%Ae$sE?i#h9P|MdqdArVlIwvRW zMzFDwLfVf*hx{%T6&k?s6Thlj@0zP_?ouMk%P`=WSTbO_;oCROXCXW17oU{rln?xj>=X3=* z(*(H+Pn`s1V@0~G(13X*gt}vqkx`$TQfp12)ZOs32CvmTqvW==a5|BL)$5cx{Z?c^ zRb3FS^Ae}LXa%}$6~8w@=R>>w#lbh_!;aE8)rFwbJ4>-HnTCp#DemdVIfFju!IOu*58y6)pg`@cJj(>rK=wBo7w0l>Iiq>%fB zNnr}c?2RJf$DLvNP2_4l{=!`KdqW^3E_? zxnn!j8)d1{FW@!Kvy)>Y{42L3>)GKp;ZH+ot}LTzzS7+nvDbYSx$2bLZhuD8E0g&T z8$6a~atWW5emGtRTWetU!Eue2=SMVI*z+z`nO`o-f}}W^zpx~7y)<>cLU&Uh@Za5V$XQ^D@+8v>21iOItP zR<&R(%VB#-!`u_7)-|+}={OfHEbG$24TfHe03&Qe!bn^~sqZFjI9}LjRr!;bA{r zb6m{&SOCiJ;@s$%J~>YbAF10ciC;&%z%|iO+W})ulF08+cENppTA&*3QtQDs^1uWP zA4l`7pUTfjcRk9MIXx8PLH!W+eNYV;hdCHG_KaGuygyqSAfQaQ5!B_k9&10G>F}2) zoTu9YoP3+4Kr@j0oOY+gcY);(_IORt#s6!1l4<#REF-vJr%BCroTT$Z#T;5yGG|m_nI0;m{oPMS_4mUwQ65 z=x_R~Ssd&a;m@IP#mHtzDK>0{9&en=v8NhSO+VT~X~8c693cuJnvXe|?nNS^SDRBL z=yrHjFNpOTG9nba3Qatm3-y$HMQHi>OwJc`75OnD)rz7l0*Zxl`2reW%PhBko`vso zJY`c3SsUmwqCD#CHIiC$vlA8Ec{AG=W=lKeTd21!Ba5WvcC+(b>TaQ=NkfvRNv(lP zt=TlaIzbTc{an70!XbYdM9Hk1>Bjaz$Cuq1aCPb*sME_c&sM~{@!yMjl{G`%Q^oj{ ze3XwUYP`UT*XMK(PNOG0ZPv}6o?n1k@vEqkYE_rtbrnq&8bPX{M+9y*sEo}bJ%KPn zlSWW0?2;;42KottUaASh+A`D@YNZ~YKDh_yItNMmcVt6a4voPYs;JhP`QDMj><94|^GUfd_G$)8u}PVLY0fmrd2?S_ z2NJ+(H1DYUp7~d&)Jr7Lc8dzP4bC}lp9VaKOYrBmnLG|lzSOHdu$`B)Yx+Fa^iH!R z$c%h>G*>Z8oj19HGGsqT`ej<$#2YE)eX`~~V2JA9^DSl#Y2b9Pwua_`bzv}76LRnJ z^L#5dIW!SF<8UT^FcBlU2J*T-=CYj=SKunvg9t2rQ-(bDdGO2eiXU#%D|m3XZ`V)1 z9iLIRKz1OFZc9_y^|MO4wKQ4Aqco_bp2R44VYF&k=32t(<9>BJv8o_=lXTjyrDZp4 zxzs)8h|t+*s&5&zdv=wwI$&!PB@pIA-)g9oKR-tSyWBmBT?(>9oezZkwyj>nl7*1e zC&gg3F!nm}L}nUoK9G|u?|m35r+8^_pV1DfWpNqHV; z{{A!8x}cBGZ0^_W9FWCsgD~EwwVLj9R39P3#fUM2;->B%9+9GPl2Aac(q_Q>F^6_1 zM0+eLvx7*a8=Oz06aHP?wwCERw4eb`Z)Iey^0*pN92Bii$&!zw(Ob_qV5tP0%-zAK~isN;Y(9%(8`N$lO&&iKf1F z_QTuVso$4%xl*EVvaF#Xi|8kzetz{2`9+BLH#mUM7rD2qsZicUBev`^NIiOyrrOOC z&o#pfr>*X{geyk2RnPcc3&cbU3+75g_It@CXjaRG-w)JehCR8~5j}Z3b}}~Cmajrp zr6}@i{~&+nXY8TI!E?#nYKyL6nLbj?-75A53b}zjaD)iYX~|n#K7PK*D;+`KQ{4Kx zcnT#l()GsIPG1U_Xu3FlQ3bE;YV?;Up}eP2r>jTa?p}^#54R`6Owz8y6GS{$nnB6E zTdy6O?DcLO37qDDZ<+0~Wrp(50(Dy&YU-ae&A+0kVQ#=>&-wT~P&%rqv(jM@?uLjr zj<>i@xX$#w1i>HNe^lG)#J`*LnuJx~=}=p}Bq{G;q9!C4K$MgO?-h9`t!|!Y?_;}> zXK@@)a>|Y>?SPlJyRRCBS71G0{-?af5IF0nmPNHAn=nWh;2PDsNisjs-~o3$F&H$SPwa^|%?pdyIfwEz{TA2! z$vG`{TXwMn{=P6IWUBT|ikywtVJr_pd78N4NeFpj(DisS)>F8*x8?2Mw`YTB+$NS~ z&Nqx)ex9Q6r*mpu0_&;?*;KJnoa-2FFHS9-dQyM?*5E^w`9VJZ!9YO7&fu~xxyr?f zi15^>7e+S^d4s3h3r%-(O-PQ`*5@TY|5T1=QOqv!v|!F*W&8He_~hj64Y_~lr(;c6 z+dxI|>68@eTP&JbsDi$x^#3)FSQ+*?1_hIEgm0{KhDykW3jcGdxN)(CsHv&>r;GIT zWNxv9arYItdpLjmrGE3)%gexj4si|pFi?{CFe7r!b=F?1{ zOT{UI&3)Lurraq0`jbmguF6?=OeT9=W+s^Y`rRXxlLTXOkz1f~BG&B0^Xe zYz0s(6ZNgVIBM@^qRs0mjpoe4r6ohS_RWKF_MWYJea)*3QSo^emZ;a+ZydC)~<`s_b^9AgO{;fHir)hg| zc|m`LH@!kO^_!z7x_C}A*f})=c}P8he7`Lr&HmU_ZPMMlk^({ zUi}xn@W>=>hlV+|`wW^(5t;au+qT`W)` zNj!2pYAQ5)*@Zl_*?qXrvkyjy9v)H%3hPZ7q+ZHTmXo3Od# zuaId&u56K~j#C?kC281s2Xl{#ap0qcoEJx8Gd@wAM(Jd|3*ugwo|d@H{>s4c{2b@SG_%*Ay_Qx)6s^wSG zFZNRe3UX)ha{#c2rt{&NY}uny&olvd5^IhUk>6ZS3;iW#`gg>5Bs=+Bz1v<}SC(J) zyI&Qhdy9Zc2ma>66;FfG;fxuZ8Lw>xqdqH|>sR^h71zD9YyzG;Eb{M|**4rrCYsR- z%Ljyf2r}%&^SN_L0lA7kG{}E7&$D#aNHLFi?a&g9|FZUR`Q?(nl9Bj~2^Gwx|6nz` zGZ3$Y%4UnRhJI-F6n$ZEUC79y$yw7KQHLHw2r(>Dzlemq%B(m@2Ll_vsLx?$e zvPrsr2Fl)*zZKvJZd0w@{rMp38GO z@6XCjI=Od(t#4l7l!vMmki?Z96IAJ-;rHhPP-Sf0`m&(bAW zJLs;mFM+h>OI~hSR;zQ1skt;t+hX(7Gl27une5!q_Qy_qbeNWfkE}9YzFHC2|3EX? zo336;yCuTx?C?t-0_pn0MbYgpV|l?;PwW(}ujW0$eN_&1Z~hw#nDJEwTDI*4v)3{s zX;K8g%ajn$8T>?{q%Db0B^q6Ij~}&F_n~N1$vBpfyHDQoezlu1vU0B$Q(F8H=*vNp z>TbPUyRNdqLXPSzBfd^8Q!Q1($8O>k{W|B1^_3MZPxnJCLeZtD4p5D?kG#BLx}#td z2(T?bJP>E^rUO73J_*+*V)r<>XH+Y48(?sj#Oy{}I$?fcX2NA$^~uxD)W@Y)+04bq z*WhP4ab~=ifDYS>f^zKJuu>A2VxXpN1^vNm92kZ6rHpvd^>1sjsm>-CZDJj3PTIh7|;{aiKgQgTGs zgQSL`1`*7e@up3JNqG-8Y&2Um&aPXm?f&GxR*8*v;n9CyL86L-*=~*-B=^gEa?GVuERlGJg0?)^gCZOyfiL^!<^|4&@frxQVY)R=$JitzFLPAiXwUabD4y{he}e zj9MdpUx;H&k!2a&C5hj3#tg#FJq7gT%)D!|Qgx zX0LRdJ!x16bJYooyjfg^8&*J;yFxc(Ai;lSt9EX9!!F$MJR2wDL9j{0wbseA(H4{B zI@_re6p8@+Na`Q@UqY;=sND?mHh3g-r%LRpeBPfI>tgfm1Z~AWA9f48`;4wJ@AwG? zZR8GUmy)vq72%rl4xlB?Nkm~*x7+7uP>YvTgDX_v$A3sxxJ{ah_&a~h^`7b>w=s<%*(y% z*o-v)&i1I8E~g1*%4HwbcK32wj=tDGU)ppd%PXy zRsVL|qpJ?CT4#s86cUg8%Ni=4asw%6i_4^gAXN)vALr2n9q%E--k(M=c2+fFe!G~= z72j4a^}s>qH1-SldrSA)IGUXD)=BsEJQHc3{cRle=wiFYE=uB?f z)i!xaX#W%`YN*$qEG(9k@C%)!n>6Ch+5Ksd2D9wUbcb*i>O+NqAN|E!T4Yd;BDK?<#U(B|GC)>RKwsENyd8^N)55#58)4 z&~%XV*SeMl5fk}$`9KNMrMAa3W{|9Whdt#6{^^PQhMP3%sJD<4gh$!t=pUXc2B zg(28QVy+oH5nM|UFYJljMHpr1HMl$MjHE4`~AU)A6BD$&p|zyU0mC%#_fK+ zPeei~xOCfI)>i=s2`b-?YuTVTx9bcSUUR{vbhE*Gd-u~bAAuf_D?~k=xL*B3Fk>39 zA}G8mC?48ohW- zJ9bzVfdeeo-t`4rsNH2pF(lx<&9iVXM+Mc%^l)+avF$DRi3@pDR7y&%qe8Fwra5o& zO$m*I_KNMTQ>r8rGe*Tfob<1%GTr;;KM|}ktq~+=b?$Jhz68!gnmhBd8_V?CRjzfP zg=_OA^BgPwYFgp4=_#{kSj#3}TLtfO_}$O*QNTl9&LZz1L-`$34y0j?xIciAR&#6zC37!>q2SG}uJlO7rVpUe zYGqiyapJ|*Y%tAS`zq-rm!8g65|Pj5`Qn=H24)lstqfz2+nE%+?`_)u=NX};*x38n zM}V`4rT7`EVe~C}py)F**Nyb7(eJ9?E@}+4?J@0U#=%srB+obqd)0VcT>Gl)yUFXB zeq)m^b^J#{kDML%FB#W99wXI0@4jcVTgh)@b6K)<2}=OSYT+9Yd>T9Es;*8C7n=>f$IFFA%;X2ex&@kiE@4<9Ma9c1 za+g$uex>*1;o;zNx`i9?GuM04rhwm6i1(QBuzu5ix7z-KBDjOrSVhqIwWGBU;VY&6 zok7-Hc(rrK&R6))_o1BVM-^HNKe8PYZJF|hrVKRj9Dw+a+UUcxP7d@>LnbCNu1*+q z*{ffEvwC;8F5b7aAM!F^a;v5gAwO0$1TfMtH-mvvMR^2OO^4;J|0B z%Fp5A&H=0^T3>ie;%h-;AndEyFAcp~N=zk;$NUD5HtGf$wA`;3pRTOFaNF6Bo8pP_ zQ+0NS^CKNy#`>yLblcmqb>^F}D5@OYtKWfvSmmlC$8u&ZNt!!9!!PP~WaoAZ5ptJl z@I1Dqc?f#>Hsw*1a`>*&LuXktKPowMrX54_F~*%iVuDZiUw%I4z!T{Y6?c7A`bQl3 z8}X`!Xu#3``DEi;cF_6zrT3(;4Vuwl>Jaq$F)p`oj|Lm~fE-laVG0ht&+mi2-`9Fms%>caj)0uygm`*xo@fTZ+)rm? zJ+mn8W`{Z>f|QWkqmjhh3mG)VzK}!Y>E-?XOZ}_6rpqeULxAKOe%fmbeA_RCU6=P4 z(sjRdE;tbbt+hs@z!RX?fK;*KBRfG;uORn3L*uyVO@QWx+)eIsATHu*RMY3vBNAnT zY=y}T1Uv4wqGkt2tu}a)$}xzX9SIb$ueGKE2O3i!zAtXg{&68WQrj}ZA9wveuJFdt zvdFL4a@o~LXfYjC`)?lc?B4}yG?b%e%rGyj^kHTZLgd30U+OePNH*vLplXPd?%2E0 z9x@3I!K6)I(m=ItIu{wui-Lk5boa-abw$2(A8dSatVScdpsk~~&G!>N&HPq%FMqbK zasT=INj={H6`{(vC)eXi_j`zBp8Tx93ckW;Q-{Su?vC*y(~8hudS*(g64iICTZ~#N zNT1&kj)aJ@uH0AoYIL#fbwZ1XNyq~OpUd;$jdz!wj@yr3vxC$lysWJo54JV;^80W+ zKS)!4*3z;c_TBM?5^^~3S~w(U33w8E15M=;<0m)yu`uLD;dL3PaUxvtDVEH>()CN^ zJ8|Eklw@7}b(QQjPc}>tsw|p0Yk6rUT!e^HVmQZ|5PPZ~3~dXQO%h72@6hO6D4Q)M z3XNpI3e`dH{zP_*$6#KS!%|Wh{bU1{ z+1_mB$CBx!lB*zYL2>Sr{_+ot>z4X8o)i5aB2fmvO%z=Tf1n4H?eBL!V0p@ZR!z1@ z2V%eAIC+Es)?O7H(6BHwIp=@Bu3U4)n9f_!>l@S{X^}b+7mH>cPY|r2*&~`_F{EWJ z*9DPyG>eG$CEtNK@6K}fx^k)z(oal0Pxgazp=^2eV-5NxNZp;0^7d^uR9BBLm~q|D zEpJi-TL))_OK&ntB>Fgj_&Aa$i9&$zdLnn9(NYB0gz!uUI2LksZz{@$>0P<{&!E5{wXpEDsEM#F~YGmmf>xC!=aN24A_PsCyx3%?KsACUotDl1)s}&J6|F zvqkM&=saL6d6XzdcWjNrS5o4Q&m6E}QN3@htxXdvlCw2~gdQrvzL^Kj(Wmx-r7`$L zG5fni{X1+!SL_#h$t^Sjj=}K;;%-}$pZf$xdi|JL=GwUIP3P<0bP?768fdeNFyH$) z>h2Tznu`m#B4=%4vW=5_o^9;`+}^z#cgVk9o%We`?T6UsfNIfi-WkeR2P1u*W-d?1 zpe8k{c+NQkQ8Umwe!GKjHsJgNP53Ft>xTFFKxnPq%xtj7cC`~xFZPut<{4wVPrOY5 z=iKF4W{ADRO5-8AH*WoQBdAZd)7YW{)mNz+k9C`_BD=S}OzC$ayB*i0eORi%e{ccs za9z9Y-&la`=D6(UJ#J~?V95=A&)z=wfA~LADXep<1;6j=6g+~)axJ`&m7u9*0i7%* z;l5Pe(vzK)P%OVSRO*%cz=Ulv?+_LqRJ-L3%IBF zC99&q%F@kxdu0kF4x~B_))zpBjGb3{ZZC)RuGF4=kGqP^?t_UrZI0CbM$8*@uDwFZ z<<<`H4Cm2MK3Y&=o8e+RI!K@S&D%s;W6v+CD^1Jpb~!+}8b(o-vT!-_>bPi6Q8lS+ zS?<9$f^mAr5x&YsLRl2F!UQ(onjh?-k+7jadTA0++M^TgZf6%cU=@wW*zUMHAIXSot@qv?j zsu0Mx*jpd>&XO{h9t%^r2lrZD34-kp7`h%!%_-0M!n#8{WXMGN{V!VT9oaq;%3TV8T$A}j8 z*cd8pF{2M!31|C6IMI!=m`}HoIV#0lub%pZa1c0#&02h(k#sV|sl&yF7#0Sjn!v=U z@~WLO4CO2udSX@HJ6p~KJ`LOr&(kgrh%hEUL1WuyRV-3L?~c#Y&@z5if|9R11}gD7 zroen-8mBh!BRHaT6McGLe1PDR?cH;7?#p=w$)#R92z6d`Ac09{>$XVEOepNdK1^f# z$=Q|1A`d)0YVsP|j#@yL84C8fjKRH{;HoAGo-s{uy2R^}s}1cy>2mjiDN>neVJJn0QUo6Z)Bdo-*VXG)SaJqVc!t;8F{g7f4sva zF+sI3KDTI!{wbZ7m&UW)ntRzV&8UnjFsL=QGs*~gn`+EU;XO6KXu{HbALFJ?QP0+zxtZWjy($dCl9$^&rVt$!m z!!OyeoZxXYSuSLefvf|L_IgWK?r1o584$g~UUn#lh_t_r`>pAZ&Ad`KRZ84f5^T{j zDhI179uMD`J4Z;~(6wwXMN|ex&@w}btCJ1#=y7u%w)2f#1m<=ZyEtk? zj=~IwkG)NVYJup;q(BntftelhF(cbKkCH7pL@5a$JV!0$wsGC0O>5X^1TE1D~le zW&0tGrq&wRh1GN{g;~dEm1f51d=OAUD~gFdZ$@(}N-|~vMC*C%jmxicc4-MJjlr0%$HX(s^)_a!^q-q<5&nSi{>Zo8|S1AwIMB$>R zbx-O|WVkdXgPaxHXXAg?-=pYNf2Y3A#O}7~FwHHweDGV-0R7pO+uXt258-Hus9?po zE=^suBrR%OJp3>o5#VhYDxLR8#7>ih$~~k$V9oSfM=laqTspNmgDWfiMNr7vEgxlR zqB5~6Y1+c}iip`f>l(654mx%<2a+BGv%(pyZW;?rCi- z#bI2Bdf#vIUjnanGsZ*I)Uf>{#lWrcr3c{x(^a;9hh63d9L!u12zuHFY-V!KqL*>< z&p6fRni%n4lu$}|D=vQQKIwn1xZtwbT%LtMWPvjag=I!34svu#R=dIOPR+RoVH4^1 zW@;qW)deK|^Hlh#ECZNVPrYyX|11qCO;T8mN%4_p>0KR%@!MztDIM5#;(oVy#BJ$kobTRB z$EdA%lQ+0w!+WdqL)w3Qpg0a!aej7?=^V~dS8BQKeN%2ZP}*yK9+^})U!ntbbB|BIo&>SseRph>AE$t<6!F@M-v-M9@E88f zq6BA~rb?8#m7!7h>9v-7DFZh7`xjO4qfl>-UsyTa`uQ>rnd*IZ&9PBi9m{^LKezsJ zJj8V6hj-Nz_I#%H&ywGi!RC_o8)$Er;op6RP0afr6=|dq7n~>>DObT5*Z_S zeJ@6$8t-{5)Q zod2Kgy0ft`@;@UZOxe0Lam+Tv31WqoG{^7iQ3_r~-qRN69URdXJ55$e4}5?Q2Va~+ zOmlj)+hoB}7Moi)4~ikYF_i<0Tv;+)NtaO%Z|($EBXJYFK5+hgs_Jwj?(FJD>G6t1 z-Z;1(Uqx7z-^iZN&`>HX{xjK#_rW(l5vZ--lhg-$Z>YvT51TJ=Q@@1+A@DI);Qaje ztUE>HS#K>ZrfX0|4EDXcO-HxjaiJDtzGv9VG=?ad88*HFVb_Uc7r=>`DFwE%rY#7X zuT-&jEnURw=NS^#k5x-*+>o2;*50%t-+MvFrKm|vb?PUk3{rg7HA^0{7XGBT`=*n* z|IGWFB&4o46?CwR@(YY*#5v*0y;?tN^T!b!2N=%Fc=g9H0-FEB+D3r!8-#KgDDZr}rg z#X9hMl@dx=x^fQqrat6{%dSr+7TV=*1OKg4{S)WD1_90+dX3JJoiCS5q*0}F-A*oU zMOj2u9CQDT1&m-#U~KOVf6>BTZ*T7)-&2RDp~6#BdV0bGRFnC;cYogKh_tJlo1K%% z9PD(G$KkWFJj{cUmzO;m|2Ot`v-WJvvX&(i^*{5HiWP};Wyqz=u{X<%7yNFVp@Uc! zAsj8go6P#nAiGh?o54c+*LGq}zzsIwW@LE%Zz6FwFZ-{0|GWJ+a)8ruofllKB$DaT(XNPf2~^Y7rz|It^}FVU}OxQQFTpD2>*8YGbD)OlqPeKV&)-J2}Q|MFR-{~2>} ziY4&&e`34X#r_3X|1-c$3Y+T5=+HkqEYhAnjMIV-`k=$SuXUlOBSq7>wiS`B&>7FY ziN%Z|57EzRD1a~7%X`I{pX`AxvWK472!5GwpM-JllkQ@$L8?cBG#HLvM6O?pnSJ7Q zC8izT%?Y{eq277L9tVXNX(vyV%;fA=%PvaX%puM@e{FaCwkVs^@1nwFm!aS5v`I66 z%1*(on7uW%geqboy{*-xg2oim=?YCZ2H|C`t-&hs={w_aXF8x66kb zkxNk|?(i!>$Y3L~K;^PZ_27|>_d<7-Eut{P{HA63*&mw^1~3bEdz-{rE}n*((#)NU zd`Y{xZnXK@5`L+#2t@2H-&FciL$&+O)JRxEs5rn>*0OiP@w=2kW=Id9{mVWEl6fKV z@{mGsaUbyUK#>8|<+PXFBMh4``6DxhUjS#1p9Fg|uf7ZS1SRm6*i4KIY7qAOEQRg7 z1cY1M?z<3r=<81WYXBT+8MBXsj|AhirN@CK4wl& zN{1;r+Rgr{bI{%$MSs;PF{teWuI^4>-y{-6J1#mq1KhDR`X#n@Q#Gvn(aX);t7&?L z9kdWnk9{+Rqoc>7bCs&97>pFDBX77mIZsoCihpCH#k$LD1qA~v9sYEfQ=Inn zx)yo0-&pCQ(2-ton+H7}X&oPr$(`W;t%Ut2zwa*3gys{VIX?48YwK=QW!Fuqls1-7 z37Aia>yxI6-!_9$s)??z=t+Sp7N@kKE8p z3Om`+yEsmt7e>X#51~he?Wb3~-VTuW#VaRYcp-qYz||BmVB!1aK4x>gFoMo?_Sax4 z>Sh)PwJzuPFe%oAH3GTUiIPHleP)3v7x3_bq03`exHXlQa+7Os6cb^bQj%8Fm~K!( zlHXJiBiM^)_sJ6upKagTP@@{ZDM2onjnrKCXZ)!?yVs(z%5uV|XE8_EEqIil)|5-Y zOmh9sOsb7kv-hT3FFMumZupPmIDW}bU~VHw{sp|c&==O^%P)EyCOlI?(AQXTcsy3- z3e!)?6FWFqs&XADPB7Gi7`$rN6SkZ{IXv@}153f2djtuukK29QMT45n)*`Fxv}4>3p; z!s0$lu!=Y&Z5+&0>ob`W9Pvs{Dlp=iaHEj8VCE9|N4CY<8DSu`TQfaA%|YQ%Xx%59 zfzHK_2xCv1eW|2Q-*m5$@8?uz?Mpw|WvJ_|kK9+asRArRsa}5`{_~6qoC0jixFt21 zu2yaBx78S&jn8l7(w&*Tml_`yPQ$Y?`qOS{MKSO*Z&%0sy|vX|VOi=a(28VAY`Vz- z%CfRW-9+q?SM~%{@8Z!L*+o~}-Pb3%c-)qpF0k`Gnu>~wF8#;~Yis;rv3>kPak_$# zN<|o>f^XjRCdj*}alsWg1SJZ!^=a}Ebv!LiB$=&zPINsOR0H*mU}?H6t?1FVOrNPj zDXf+-4mSF>TrqadOZ;|PJF^8IRkxhN61@SNZP^hGZVLIguF)9R%ZWII$8N2v2y9mq zCU*5BsAd;i%*h`KI7a8BF@O&A!_u=Vv;;h8^~Ve$l(E}!0+1w4eSe^%^jvkBAS z!9GYlU1ObGj8UWDDgxSUnf*u=MSu{hf}tl{fIq)7neEbE=PpHX!Kx0eBgI8L{EmXV z?h=dS^);o-VK=-?o)fog`Xaz$FfPq_{N(f-uxoaht9p=_cv~~6Xgz;d08<;iHFlt` zY~VwEkn5_j-{6e?-s{?RqM|wvKz(&P`*Z1drJ;^=yy}X7V&Xi6?$Fley>?Zwah;$c zZQ_3pS@_--64E4C@6dt{mMoG>9lThj>-s-hyY{Cfuqf=TWA1L5v%9I2hU{!PYLr%* z$@_FWBGi+&Ob*?wK>^e)pU2oO{q0Lgo!yZqL^6iseHx7}rfT_nmmL1h5u#Dpi@i zN~I#=Mb$HA{(dtEo5QZMt(n2t+a2uwM)h`86Mp?;4hR&<3T@hcn~(4XuV2SvN4z%{ zsyj&(P;``~Y7fmzw3iBYCeM{REEyYnyj3*AT}%_aA;hwP-V76gV84NmsS$xF#Ihmx zI95m)?`+Cek~JO#W8Vs96v zY2@fGIq=li1=;MH)IU+)IB%tKqQiOgrm*>D<-7}8qbBKELvOM}nC}KKtPPlJqOt-X z@55mu8WrQ#)*(9*0zWvUWIR!;;4?*Y{8x!(%rC@`l@P`Aqx&|26Kf7JNn$&s*O-2P z#iK#Gn+L-*CgV_MWRb_OSU(6-98(virlUfm=G=m=xHmD%5v&5!o~rS9A+E5{mScM& zMZ;UR%uMgddywVOx7(Fr(hb}jlE^9|+LaD{>P{o9ju5LoM)AKR`&{7@I4cCE$n%wN za${7pVrfE)sJD~}q7JVK9!00_%Ar0Y&K8kD6hCpqbc|x=p9-Y;H85r)XNwW%Gbc_E zwUeX#G-FvMB*dtObUGXwlDd^^zl?yXOEWtlp!$d|#igf1X%mIvW;t?)X-3#koJYf2 zvsLA1f!PKG8ByV6Td0vRrL?xiE+)Xm3Ke(-Ly={ykmuH=LZ`x_Lsqp=mV3HrZP<@3Eu%{m$5Fmat%gsGejM-FK z1F)^_eiv8yYq7NH63IbKC=U;?Kj{8 zslg0p1O_gA(ON5X4kmx^ansp?pqWZ&M4m9oRZk1}JuV8W-EkY6EyY8A-=*eLi&?VG zxfv3PMAb+-X@-`L@uLqb+s!R3Segx!>?I2x)?TzId4RV2(BI9EmG!Dr;OMyjFxW-y z^>(l2?h)rX5B@?V#jPlKZb@_YRcD2}Uw4xH7Ng2cy(AMw+xG!^kL`!{G!CFN{_qwf|Ii8W^Z{)<^lzp8dRR`mX~!v%`~9 z?+4QP;6!PaeGG~vFdkg}`a-6B-*^2JL!hSBr+DiWN(nDR?9McJdRFbSSswV@rg6gP z{>|MB>9SUtQWx;AzsvH9KVeu26iT*p(s?e5FL|bh+w&BX9H4uq)<92^YLwGE!tyWl z4}^fKQV1i$USFZ2nver#L#bgNPbieBvPh$Uj@5GTz>$)Z_}3AAZy7iZ49@-o+dD|- zf+l!``B=a~*BTf#ER$Cd?_It6txl7~Bj)~cr-kV|rCpC2-5Ah;ivRI((Wue3duueA j0mr7bj`!(OSYOZF5dc5`X(4K|rZ0y+&mO5do1721IGn6-W$_#D5j=7J z1Ro!tpy};@?(^{-`NYTfyY{i)fKP&6(2wx(o#He7=Z4MmEc)c}2+_fBT2md*4!a#c zb;c&h?ev3B0sr0;xTMlxKT$Fyyc}KUQyb_%SyE=}TssIn-$K_lkAE)v`~8g2S??pF zr{9Y!O#L?W>Ea(L*Z)XlToikB^5XCRcJ@(stt?31T?^4mWU$mW{-U5rz0 zQ;oahEr^9ea&=@4pw~6oHJzgp$+lXf_7qQ7D-COSHQdL}VN9Ty!+)r-jS2Q(6)6CriTB(&V>?B|$Jd)qc(!akE* zu(Y!GI#u`6SBty^tdqNwQ|tUD4HePH3D+D)i_a>LtGHsdAFM4Kf;M1v2ci_Lj}mUl z{v+;DAUj{6IGh|4bD zh>#xf78+y&s7uv3*{=!E@Tp+;!VC}3bAP{4B`*!4)vi@#X!wqf2wodvW85yZUQ%>U z9%!<)s9w+zFtd+*feCrsHaSvL2W+e%a=REWGgv%{Zn4 zHXyb1!$E`5D38iv`#f#72M>B*6-5VUP&Fa1m)tzgJ8Bj>1~l~g29y?I;du&gy%Qpi zF8!zL$oBU3?oNw>jCsw#gw*jW^T#e{M{lD{zgkJQe3Z*nwz*&VLoc)Qj7RzN#(+6s=J9)0>nRBFTbeEaETxQ|B!?7jPYMq); z)Y37KUg5QCYS+Y()g5LmGX4r}3t!D_;>4~a8=j?mVyoeZFr+F}F5LJ?pv|je?L7QCXfnU3bjHR3ZWkRVJ4{I@Ug$--&8- z+3up$csoMR#05D5w-v+r_fFd6l%SI;k6I9J9CvaMGT}KsGb1ajXcxjDZ`KTu{06bP z?)etZz>=L~J$szXRa6DDG45*aAIlX?Gb|jRba<}*42z_Ys;y(BB-R^IOr4dSCWfKT zQ(|VgkTivc6Si#fu3r)jHpvNEtgRmU*h$;V)?@6CB4EqlhKFwc!$#FCvvag) z{TFy@%sh=Fb?1XPJPr))4B?_oKu=V`eq$Q)i11%cwcGUdfv8nbo^REc9Jf@)?xKcp z{_@CD1IUsc`izFvC}r3s$V-J^49S5qwrjWOUDaMH4=NtEPn*c9XUpC?GCHbLY||Sy zT<(Go8|)PI->i3JjL}r@W@>D7OvTl+nVqiWihxSJ0GtQI5YD1iQ?O`5 z7dC}0H11_fx$wuB!hLC9q@#9*wU&n@Qho^VvRqbXB33UsbU;|?I?})V-vf9%MObcaVms3va zZ_j19PfzA~eX+kwQ2zbgVZ?TSmKK$;Hq4+qvD?dpyTQC>r?dJ&EhBMt=OGkyFgYT$ zc`!;nd}R-0zRH{g#r|1mGB{>nA-x_2;y(I1)X^>8`=^E~^^>c?F2+lK^69g}en%pE zH!Y)2VCmGVuf5!8~ zVbrCKjo$&-znShhnz^em_$A_>g7s|lXE9y`EC2JAzGpwrQ6}YyS2Jxsrg|-?FQigU zS!Cyo5Z$Hr68;#slh_9s+XCyCiq4d3jG)`;LA1!g4e&x}!?*0z_eWn}jQ zO7mROw_BODy=IBStN1y(Cq>8OI%QcpGXs&5zxiUlqsY;;@MV;geg$?y)g^N?dWmX& zw#jKs>AR>vbm~xucd&)j({Y+Dh>U_i7!EJbW=;3oIy1uBb3=WLOm3^^Jf|l>DKI@X zwP4^_$w%0hJ#c(RqO3}VvFpcbp5Ru;Uh+$*`z~y?Nr^*$^|i?Ycjt6xXJ@$so<%aQ}u3>+z~lJz2}mYJ2V#*kXd(4v86&vvF}uul}}v+TP%qV1+^Ba+#a zc9565XEu{(VL4-CXUAL+$E?)t4=2iP{8+{$r=~J$S%hVWMVqxOK8_5jjZO-KLBSYN_-4|++88zfcR7s`w=0{Cx2AfX2m$C*9%8F?~o*m)22%0t6C-s@k?GU;(fC{-uAg8 zX+a~POOSkVV|pMDWtj-J(8*^Cm@@U2c1}6peLj_7A;y`0R6K?9lD6Wr;9W&i;xU)lh4ynGtd)aTnwNSHo_I;BnXUU>r+(yGD(xF^`IiL0)>I zo2~25qwol>F1*`X3_Od;)T74(_a;JCW7PLimt9#==|s>EE8Xp-S%i)TEog?p+W>*h zcLi>19KpI=^eV5cgl*2nVVx>ctV|h8h5!}4;RYl zL-Mvy%Hyms?<99q$Mql+%nF%UN2u)+T8VEtmi?i+ck)SL`L_S6MkOEL*Ruhz^%-|- zjrqUwVlLV0uF#67Q9_6LZ9|)%?FrLUyVL3-#^!hzUB`o(`x@jliyX;A&RPM}Zv`U4 zo}`U0W<7c>hzw=##*VwfSLQQD0j$yb$4QL4$j*?(G06KLrYYusC)_FMrx}arLKaPl zgw@VfJ=RI2k1D3ffz#%UsOJdjX~qW}=&w;@R7Sxxv2yzF0&7%GiVanq#`-vSQep(e zu4Y%RTPOYS{zv4yuaPw4C?$8ZVEBtaZh*8yHbCO`JKCf#?@SRrJPH7SAR6$>D3s@L zJG%uoED*oFAbuQJsr~(=$#181QQ}Q4pBHKTUT={1$K{WZEG&rn1*IO;i;nSk5q?W? z`z+GvLy_aa<_0gYd`O6ZxFWyUtSrw0j{d*r+WpVxhQFm<^u} zXVm$OmTboANg?L6S~l!m(>YW<-WtdYLEP^(_yYhQOq!g4b_oJOxlXXs-lYwrxys?| zTvvjF*W`!7!W5=-P3{|+mn7HF=k}H4=?Lg(C~Kd$UJoMg;jVyz*H%5c6^4yH!oUY_ z6t@h4rAm6b*}wk%)2)tB=ac{Jnb5gsa~GDCDE=%mFL1;W%Aut&c#K;Bd8znzKmiGN zGnA&c3$4tF& zyc*rrRb`#|9M@KkW9^};36!c^T|fRK>K^?eaYb*WqwG^P?mZ;un?^T-z!ptAiMiv$ zE>?SfeLr2hZ@ZxMYu7SO<^n&x6HG(237ZfGu(v|BRDh#xP11S7nsAp9{eWL!&Xp>@ zg%lA1+UuxLn*eV$w!zhHTr7~rwzeNzH}=FIGN}N_vXDfCca^Ea&D!Fu3@=RcKWSw? zinTOSHGSf7>i@_jJK+BM!&{e!ua{lJ>6IlBd7Ix{DTLmPER3`ZhuN(YXAu@yol%qm z+TkbeY?IL32Zo>4%5f;y1og&OmBSPb#oZ6x4fA_#pxd_-SL((C_9Y>A>-a95aj?Cg z=r?B;amUOIRAS;_Yh`8S(9q!CT2)$V8Tr8tV%zb4Z%jc|6;td^>o2Iybf|JS4Lg-p zp^wkc&%gLlRY~b2Qr>jFBw2B(rna2NWfApO8)jRCL0J(ghA6ufWoO9OL;P8YJ(_7! z*m|oNbi^%c4QehzTb?`SL#`%XtjiQ#j%t+_~AN4-3I zp5SOEX2I?P*PA2sx-n_{TkWTZU34C8iD1N#SWcC&%TAjz5y#9R>T^2BH0Gl{<2YS{ z_T1HS`GqfN6i^kYMpv$C@4{KwzUyVaRzYSO}*H&O>M{IqD|NNegS*~K$KX4s(sIIA8YPHyrSy4p~tK3eYS^D<(GrGI=~blJ^Ur54n`tx z@3n2Q(b||yTuLqDV>!W^H!Hl{hkZ<(wbb{ZSadAQAj5e5hrC=M^XdHahQR~=s;R%| zKV~|#z@XqlYZ8$@BV%p(VaFu&nYDJP&r{TZ3U;HL9LV;V+d3876g2D~43%W&b*UJt z!{$~^%{Cx9TRQ|vXDDcCM?`Bqfj{Fx=rghAm)-rzT>zp5vftAFd^9d4p?5&`0hM&sTcp$KG zbJ1sSX1Q{f>^sDA6U6QwmNwUpl1D{68{uVKQpmo`Vb&A<8+w)J<^n$&BkqItM@e-f zIQxvXgL`>ItLyqI7Bc;njy^u5tvf82amaxF@5Tnm((U&jE^+gC~yd??6! ztG^1Vwymc`78r)!ERF3#r@7i*^D5`L`PvTio~%7PR8o|kV0y-q_F5X5VX(_i)Mz~+ zXW3)GtRbuz#Yi5_=DltyL3MR7kl(rEQOz(tNH2Jc{5ijsKm`YnshF-eYn@H5zjRr} z^uGVbGi%cTmm+<)Xe&54O7V(sYyW2;OcXPr@XyI*ytmOsAdvS_X@4gLG{Jpp^)&mM zn`W}lP}42)`vX&SJs z!HWD?+Y8RTJXh-t<^%>%F6i~w`Xe6%NG) zW_0$LX<1WMK8j-E2phxoaVSDXL+Emhlt41c+&Ju0Uw7##@lpr7hG+Ahf<_g#6|mF59#%dGUx+6Brs3-wTeHwv+*q{q;B%-Z2JeHHHeU&rD&JKBV`~B?b&FlI zql57B#NL3wU(nnIn_t{d1NIi!uULNPaPJRWcDcZb2ztX4 zNX)LLslIT)+;jW!;{zdKDdeZH%J~iM3W&4pYdh;3vPkyuc-oH+fGTp`LDig@*?w*+Bm=N@sc91*<9j*c_mi7e8Ui92I4A#@0JN9a?Uhke#fj_p7mz zX}(9P4(z>U)B1aEPHj?pDp}l{*dNWuWsG|tPy*L67O=9m4r;b;Lm*Fb)7K#G=VJKp zI}J>-rWnLq5zk(D|=8`x-%=>)pf^tt`<|& zU353GO9iJ?EOj=70%r*l)#L|xf!uKlmsxFHNZzI?*1MzSgzGWMh}l2&o0X1@)@T_^0XnhB4r|FX6$NR4VKtxn2yxFiv)CE*n2mR)(_ zW^G~CvpZoQqttq}W#9q6zH{6ty9E+Tnpx;Y%9(b5yLl?sdv{~LmZ5P>`X6lEd8Q`) z{!U1<+n3;^doTxbV=(oet#;@({9m^Ysc`!G*Tceo&MUM+6k;|;OGtn1tM1{{Hv%$S z$}M%dxk%2ktA>da_4gqfYG|O{?$fm&M9o9QRX#| zJK7^b?bELsXWlC|zpYj68(aEtwUQj?`PzpyZ3GRQj1HPiLd8Dmr!)jvG^anAgXZ=Y zA2TDvsM&6V{i%*=J2Xq=dB|T`@5kCi4QDb&fyp+GnZJRXxjtLQdoH<4&@%5WWs!nx z-xwT^I$L*QAhg#j4Nk9YYi>;^Pge-LS^AWtbB&_jGCUjNo1rBZGj##!LFRAcFVTa- zSpCu-a2%L2EdNos%P5j&bgU3AVhldQN4TFs*QnGe34f(CKVM*KeazOwGl2bA6~-Z9 z|7Ku@xgp=(#88dA;=&%n_MUIyYP>x=z%g#1H-M5m@O37$T;ghYW>xdV1iLEv!rPJU zUypO}OBud}kS@i`OFt_Y1|@g*g}qrj;`~hi_2N!FCd2hPN2A_?fgIU=9VJYd{b7a} zE)K2=y21QL#Xh5M@nw{@Y$RTcs+YH{u6(5XM7`uj^>UOAuiG-7Ak#O!_Q z>Uu5Ssm3QWIs3#7(=hE18BY)0OuY=gwsqpBuv*Byq^+`h=Q@;$#9h6Xw*cNNI9Aqt z9{MVAq1WNQLuk(%aM2w;MTPg#B>^+X1pOy-A4BXcx`tK~OBZr!KZmr4yFEYWwRh)| zy@}#COg=2>TQ6-;_jQoT*h#% z@9^FR_2%mYOftN?ztV;dMeDs4Krl$ z#BDPKx}NAmGrC<89!I;N1|9dW|5BAr zKebRM_o(6bii=y~zJ}i;1;pOMErcKZ(QAZF{+X1e;cIsZWfK>4npsB>`P1U|%I!-A zx}H^#n)YoW)alMQFJwX-2gKh>j)XJtE&nW1<(ESk!v@SPmqAva-@T%Xz==oQY7ukW zA-t(}60tZk{i(TG5Fz$zJLi4;yEhCc?H0F|pg;DQE6#z+BKpoU7>Bd#f@^loo^Ps=p&Uh z-q;&k3}MelyJ!!hXE3&mbwzC3x9zSmD^ zTfJYO?0?jiiTmdJg+FII`elc%xSd8n=a)3tKD_5=7sdfsKAS-kYC%q-d+Qp*bJn8` zMCxu|m@Vr3~z`9Hrm8yl;F8*`3wZ*c8N5kxIB-XS?IGj zM?*yq_!lSyqm4zu`+Flly^c**FyM@E9Q}SM>}~I~eD%3=CZRNd06I?E*?E+$YY!z8 zUV=in1J$`Q2||X=mJQQwgJZHr>G!TF2~Q>hUb6mVf&hH-C#Z21yWITGh$dbwcmFgN zkkz)QQvI(j&P{oRi>kTHiL2iJr9Yu#MH|x(B6A{Eq`SqR7kVzj2N6s+JRX;iO>$L} z^eI2CdOok0TZjwJ?Xq|MT3Md4^QG?KKt=ION35+)w+vX__C_;DZwpA9SXuRjWje5a zA4=dFfI|9OlJDVCU7Q_~S8Jlw9&EC2?|EEuvmGhOSSvD&+4XLoKmRCtjrNPi9+t2P zd-Ph&4F_3~dOr0s)Yp3lcaKthZ2Qe&;n5js?phQzCp_Ra!{GSK+uF+0O10j7e`i|_ zL3@6Uw#4Y7a_{yCKIjroLLto^1DQ~q&^{eQ&*RKK0Qm~Czo^D;s5jj*ab z?lf|!=`UG5J-<{T?epHBznJu0cOGX{%znMWufINLEh1+)03{-7y;$QI*Gm)abpSZA zKVbu|$Gb>{1=Q|qY8*cIhOpiQ+q@ZfmA!@l?B-y|UoFC8w8UJaL_(_po6ni@;>?rW zeX-bjfW!tPlFeCoaf+b8BzH{aj=GZMp5GOtgF~a5A-`IVmpcBTyA)%omAHg> zyeKkcDsi5BswHX=aKhIPMGC?8#I{3O3L((p{l5p_#?>rTCg@u3{W^^(vII4riZ!?^ zKlpSU-~&(~imO>=G~OO37s9YFby%O3<~0x$^RJ4zwTBIbE1c)5s0S(ZvohJ+805-C zWAI@sE2ojl`L5Kcu;z6jh4%eyOrxjfO2km3fam10Pw=n$alr_9hNjygKEjq^OTEII z9LEyJ=$~ja&HE$yVJ)P&NF{L@42h*8m4&)4Nh@Rl5rpC2Pl^9=g2>%lrj?y9*I_?6 z$Bjem*{$U7@sF4$$(7ZYC0XcRW~YdAG04Tg26I+^6$cHmDW%W0XJLz%uUUd zGQM!PX@3>hCJ}sh5%N~Z&0CkB|_;StE9A#)Q`CzB1IgTBA;!WHafYReNCG30n?PcKqTnH%|MQ_#RmgL3i zmIP7=;`%Q&?7@dgAKn;~04BFp;DQs#4ls~#>e-R2O)V_IqzfX{pImrwY}jpN7vSi4 z0yJ+3dRlH!9wcElGMHp(*!X2(JOHN*-w{NLo!1tfmLUT%7z}O|8;IDVEBfs4gW#Sc zVh`eTgoukdP%=!TEb7m{4X$5L6FgwP_SU8=*}`4Eyc3d$t#TNdU@?#@lDKL=Y&fTj z0}q5Pj^qn#(nl^tNo@Qy5-!j>&Z!I;m_AwKKYkFX$_<*e96Rub1L4cgH25)i{?9x! zR(5}K)Vqw5=EQd*@rWTna$h8HZ$iQA_|Zb)_GywA`&vLwvoEHF(dh$el?a zrJ*aCmFn!%z-8hN>pNIczrWoxQjeD^&SkEDu{2v8eS4?*IFHENCuVG>*u*tB4g~yr zVTj{B6fR?&eZ$Syu-1JgSYp}bO>XX(JkgV~l+|&RARvf(zt;EbLy(opifZ5`Bx@2H zg+H-wzA{;E4a4T>4qx9p<%1$+sDd5R5El!3HLR_Tddi2&)eL-$_oD5n7fb8A|2-{p z^Iz+I-zxHCIlMiTV5otz*r2JJwb$umf8%qt>xveW&s^F(rae@2-uz+czdFrC+Vqjc zCD-QRiFdA{dT_g{tAS=O<<`@WN~}2+O%_}HYrSXu?ydCPA-|3Ic=GCtQyGI@O|}C$_ueI?f-|X=KBN4KT%R-cs(l*5emV zt=y*7EJ0RIP8one*Okphg4q7QP&f#K%v(M@1QtCDStbNuivPOcxbd{_1C6v&2}Q|E z!L@QBS7~d!6`n=q?KD!0AcjArhkt5P?9_`a{u2qhmXAO`Pqa+s?QF}0#6+aur zWW=8kpqdkwTiLm<&2pr4o-${Cm78Qk9`P0e4c1weA{0wqVeKb7Q>%ldx--OU>jeHn z0yt~(Y1}vYoQA+Qo0~Q_u~)Z8k&=+_eY~ulUsJE64od6pnGH1El9i_>tqa_Yw^9Qq zdC&QE^YNRxqV{gWdLFFrd-#4iYZ%cJBXS1A7tE{?xZ9<+Q!_A_{T)b^s)f%P)SmV?Ew!H zH8{<)c+IDtru((wb)T;oobc(Gdn$4Qo4qrx#>8M{9#hDTGKgoS2Bff#7w?LF)>8Po zuu%E=<;rbmYev@Od<`XwV3szz{yKfw+|?$i2{7e}Cd?#MWZMq!XDaLu;J*yTc zLGZeH@+NluK`3?|(?O#DDsTo0lx_oJO$}sdPaV67O%T%Xp>$SSVFmH*QcE&!f!Pz_ur^lnZl^&?=crUXx0;}3dB(se%DiYh=e6Mt^V;Q+KCm+2* zRlUQc$5#|6Dk}@pew?Bm1zsZPv-e`QIw|{%n5dj-+t$R)=b0a?K+56bq~&fa>~OHi z-6Xav67wD&mIk}t5J*W;MbLa_$ykOK<~;qgr!ELYj0oMmU=-Wl`Tj|} zFyZgtC4l8Q|EsD!bz4#CK~_QnkcCUIu>->8k+K>C$)d@-E1|!Q-uw?M0rV6JQXf?o z-QC5)4zwaBlT%*=Jn;Q8`U(iZ_mAcOR|Dqq;aH#yr@y~{%&ghZA?-kjh?IFV)++D}89|Xm_*9N7Hmi29-Hdj}uY`MDugZm^z*Oxw*MFyPnkL zTfL$=zB0~OUb&f5$s{2-L_$y9_*+{bPkrjW2B9}`lr?D(_G_}W4E9_ ziDW$5d@jjV)xuKj0Gb(=BuilC7s_|7W54y4!ZypnxM7$6+U4GZWSt05Fyj%Gja9Gv z?A{@eM4kxvnLC+ck-af?RDg6oc-de;>u>$hKaa#s7%aVO^p%HYWrGc>CN}je+=cU+PB#a4%mUje1wm+6olq?7DsXe74L9vN8nE^sgIFJ6~w- zIszk28;y(V@3N?_rjSXzkK1~8)M|M3_&a%KCpsq+PD!hW7$PvOkL8+n5NW88aaWf+ zSjiCP*X;i{J8&lu_nc&1kJ8&C6bGh;{uwe;FSJ^@vD=4)vj-C=CWRysdxTxv&=jRC zeFXix{fSN|hWgsSB2j*%6YJNy_puuZV|GJ_V*kZ%tfXVQbBNah_g(OMbry+3lB~tS@Hh)tM{^XW z5wTTD$H(f)JBjbs|81NWXPI&`*Jee_}l6;1sxtu&)vMTC` zR5YE=8Xah(K)Svs8XT22XfXQr9T$lF`y+x@j{c!E#(A@;vfn#wf47OVw|?Z{c>Ju% zm=cPm(@W{}3CPOEK($$Pv@2p4R4BNQx&n4`8bh;k@Wi^|kESB7;h~T;?| z`rUH%9E)**YGa^fJwYpSwiwsAOSjwTFMZnawX0X^4`u_BS(={rJ~}C1DR4^1Ubv&j zH{njAi>!6dIs9{2p#iHWhc_o9?Kh0$5hT%0&L0D6+&3Dw>#C5pip2ym-qciy)oqqJ zxglsvoNq~cRQQcvH%2o#Y2Pmh2cu>R`R(EqoAFQLn8(0yIeFV8mLd&Utq*X;$Oqr1=+W^v{|vQM&! zOi4vw3;)cQg*jI?hr-M8i#ha(Sc9k>xRYz#0X$z9@db=&%dI2(TcfH5*>?)6u2naG z*Btj72EbuPR+?J-WPEJ_0|KF&23`aTI_drWWPQs-I^D23sC~*(O!0yErC$mE4@)fN zww^DJL2B=(2_VQs>*!w}oK++S?@h<}77W|~HLQNBnqe&%g1C%43|4J-MF(D=1*HI$ zZC_JwSOcZ@Ww*NQ1kN^P<`}ZuA4~li9C)8Iac6oaPBSgwqO3Ivc6oHO$eOh>j_g6b zj+?-5zq)MsA8(M4RFm&f6ma|Qncuw zKmRltxsT6(oC*f`iV_UQd{rAtotJBkKAQ|LS^QDE(dnwH&x@s|=5N$d=BJFHB+K^# z??wLSYI7tdz_?Hh?0f+%V3nr_e|{k5aJgP!(0JmgHPw^xbfFvtc;95$fTv}8ct5R8 zLVZ2uxE6bE^db(>B(q0)(+>M}m0xJ-dDk1XN>==qgyu}?riw$@xIfh8YJrbAgo>bo zuUr9{p1)d7RRJhtOBu_R*CKCJQn`O4yvA&S=o(KStmM7KxA9iWr2%3ky_4iSL^%G1 z^A$L!2%I#iiNs)Fx0;c2nwEDuH}~MwDdQt52fIG|KSmGW7u_y(evTB*-mntPk8M=u zR7-B8s*L+pgZJQ-fg}`kYRX76g1_x3Kr}3z<_d-P*yWNqf}B{8Z4#)MSJ0 zxxCTQ;?K<`_C-Pl)6VD4ox|XD!^so%85)5u4Lf<|U?|z2gv3sc1?5VpYEAP64A;* zZ5SI&S{wVhixJd*UkFm)>kw-Bk65mG45=S#Ym*i;;7wHXOGY?A6l#IYqidF-|Jt{x z!BP&XG*vfb1nWNzi771`e@8U*WoQftw7DQ=7yDGV4T#oNrn*9S_LtlP>#|lb^!uT< zrh0P3e)cZF*1|X5t?wgeU%-MX-s4PbmKhx=X-?wmxREEcaFB7G(c9U~G5mJEJz4d( z&u_6%xk=5dI$IxZfCpkM3CkUGpQ`y+&wNR3;%*tl8R`))LhFs9wpC&7kG3~5esh~S z@HCTx9a;SRn=9~ zs~7!RShr@s7qNAE zt98UQ*{R(%0Ss>Sf(;38xKW}folaR@Zj;jW&P)v?dQ4=@YaD6*zO`OId9Bht?wg75 z>b)!LtK+C5C;i#FW#0M?Qj$u;!<`q&A_4gMY2gEU@|R{NlHbHQK4%PT)Yc*d!g{vS zdEH7tA!zu@Kp}j;3@sp+elDiu=_)++frcjA*{-T4Utf<%TC;|lCf*VZw7#~Ro_YtO zy`Jhtcim1&f#c+fxV>+oCqxVPT-dK)qg`xqfg3*}^C-{d&n4n(v`sgjZH`mth4raR z2JgG@bAWZ^o2hO*WCBz-htsRZ^SyG;hfH0lO^pi4Yk1z_621*XELG!KBf-7CnAIv@ z%tB1K2|d;X%v>S+ehJQ-2at$;Gh3{7jU(J=A9Eye+)B6aLi^rleXDh<7JO=$xcoVZI736s`bfMPhhi~ z=E{my&&ZpO@>`q4AVGNbK$2m0qR!($nA>#X%na*GY(!ANtWP+6Za0;zS*tzxWuV;X zD67}odP)RsgkX@;Rp~uGeKp}(6cQGztL$6+6+=|`ybJKmMGi&TI$&{oI|2k!K zO)c{;m0 z0h(5uF!(<%t)@?dFv0zQ?QZqpU+3APhd2bosXh~_6OBJ!@=NyD`Qr-AqNNdw+*8>S z&wu8c@3`NQEN;@BO-|{-GfHrj)u-S4!fPymKn*(qs&~?mkU%rG4OQNFBMvO)R2eX- zj6YC902IddWNKNG${i~IHdS!0@lKB@Px(K=0Qtl*Ac|}hD?LVh-Tlfq=)htBdiGK9 z>XdiJi^DUrQ@~6kH~lQW_I+$Y{)eL%{sX-0TmRcbRZ@(N9k!$?VsNRdY7pI}pr|M^ z@|!#G<5Ad*vMF(dlsE!!_-fy6{JR8DCJA<|(pOWHLErI&*lqq^_oryG=mn^vb1e{I z_`47mJ#iLxfR(Q2Voqo)DA=hgs@zpj%$!#j042*;%y)FRcX!`OEG>->d6Smc6k^-e z#9q1u{C~u*#-o#fOFiEjd10{KbpE#1$VOzo%mcLBXN6=Sx-x}!YdK`n@qQ-$X?~UU z@$Y#{@5@S+99^=2@2-hfynK;YRrLsH!W!)t{QJzcSX;$Z@e^VPUwe4?kO_=d(%r3O z`Q=nz(4?oQXG(JUUEpgwRpZycTG`bpD56i#hI~DH`FrrZ2l@cF2z1R;E~PGDDZ~*= z&Hj?+xI5ABeLT%TDhgn>)o&G+7pgeHCb_!Lk3tpDR+z-H%8l2&Ufw{@ zIa80+V8n#qGEgrRzQ3Z!iyv$nW%3xMXS1Pqr-DBwxC0HF2e5r0!*i>~1R_Ee0oiC% zwHV)i?;1&~XEYT2EmQ13h^R*g9IFm4Z*1z?6`P-{HJ{o{Je{cd`U9{d5l7VLcg~B> z&qt%Of4Q~y(MGd-b%~+%Ibb4hc8k80ta$BO)p3~7b9hW{ST2{;B9aGf`L8@)In*t^nHf_qeW3KE47sUGFyyyPBFAHX zX)s-S>}@#*G1^lMAkO-*nMm{&;8sQ zEwF&sFDo)4W{)SIy}Q307(m!230s8AqXDwyJg<@#G2rT1JAw0RE4{~yhO(c8^^VaJ zkYSKe%w(Oj;XW5M8PNr1>2E)?8sSMg!_{|t^-y=iR;&F3wx!Oz85O&ahb*UAYt%>d z?nF_c277ChPO`UxrEr`d6wW#s*{jew9$Md9R)QiP7vOCajt3Txwv@zh_v!eg#KRV& z$ngrSg)>7Rve{nP&wmBUteKbGtIf2pwKsr{tEdzFdK;|jc~8F=Jtg&^7bnnEJKNBn z%%FjWxbT4eK5H%B!dpF520jdphmLE?=OX6OT|n>a;!rMdAH6(1k95d&JQ?~MV9TE_ ze=Jil)f`+Pf3tG3`SXvMatg76!{rarovwj0q!I?3V1=_5#nz==EH10XE9a7PmHXkr z9@uV-I|G3S;VYJJfY_9l@V71z-x|7zpl23^kGZTYwS8qGbwj|o*LT>1!NJy;C)R=Y zR`9OG@|DCRvnsm2-`Of{Y{o6>$xGtUzDBL4hEk)A_3zYU=om*dfmDO9e zwCV$hqJq%Z;cjFiVPS=-<(%mr)wg$2dm5%Eq8W}qX=!RvsD9jHP8Si`QZptz{IPu8 z4p$B|<4Ip1;o)&LW_QFaGPOMrwqRBA8Yg*FI)TAO?{vPzWVVD5CZt0SOA|ZOq_LMWP5i`R8$$aGBG=&=<6vU z74A*Tvac5pdloH;D|M{N)%N)I`H1O8N0QP&fcC-|vOpepa(|$wtwthfe)6?*ov&zB zEc{M*95$fuGX%`fR18yrY3Kiam?d<)c}|g$0h*#}p`^Yfk@2bz)akFRM4~3IJS0*+ zWN9)ca#lR=I{xy-NU444z9jiGkokc+hM4_WZW`sL6%$|&_Ud=za)d1%Z9bmVU)Yfn z{%~Gve@mKa@x_3DWJm*^;)Q@GRZM1w~=ZSRSH-{%*MU_^3JVfLIYaHMOSNp9~|=hlSF$W`2}Y zAKVMy<&QI{*?7&W7vC#m8S@%v6O2H6RBK5gL;XRlc}s2Rs>J8`TeoichS9@OYR~4< z-42dJj@6(YI_1`Yv5VSLVTgiT)7cldxB@VO1rq=k_5580d4`i!m z1g$ti3wj&5Uta6hccwpWxoTfl_$n*ErB90tvm3TlRn{$PV-%WaRz*x}SJ_c5hvy#s;e*U|&@O8>*qWtXEH|5zw31jF%?TnlptgijG2h-*iQ07zXO< zyloi#J#1Gx(V1I=A5fl$ z2AV9P*eY3RBD{?O-ER$S3AyDsYLb+*8nia;hHZ@n>q_W^aQ_v@A$ZLnx?}#e(v=SW zKia#_uqLx@&Bz&1Q8*r~6f=r~ij)D74k{`Fq9O*Qgouw>;5?Rxqs#!etplEC;QoZuf4y$*1Psv?_x0_D|I6ZTM0>jx zvH8i))tIf#&PScK*qJF23xz@k)D-FpeIbCDuF9XxJJj&P(QsTyqtFR(D6Jhm58-ri z^eDY7S^U8s#Li-phE!GQqpAwRklw*^1jQS&%@uXVA$Sfw?-b;$GIAr1a91ZB4U5e+ z7>Ve5bvNsj9yHkA;KL(m`b|JZh1g(H7q#mdw-8pNx>r8I?UQ;=BqSu{ITjC3A$qPR z|H(UGB1Ostf>ro7X#U4K|7Y03EB*k~ZZ7s3MpQQQwinY}kyZ`CmIF-ayIM#A7$hIK zP!!c%gr-4824&6qp+13si}5**GrIEWQ-(VO;gYUNi3H4ox+M2n-pwc#uM|g|P0vw9$OFKj%^j{V$p$Me!~`8X zq|JVx0Z)Hrb#s~?*`5myQ?vXO2H5bAh08SjJluX6dsH?**&ex4(jntNbRc-g+~OUO zDUNgQyWcM}1_=2Fuqpd(T+gh~>FCj;`@cM0qxTi2&HTU>z7qS?v0f>^D?D*@blg+6 zf580}4QodHPqWdana0AwBaj9CG=113xx zYP6+QRB$(Kf1sPjaKV^2@QMxs=FUZ9f{{*fQz8b7&EB#yv8sY60i#;#`~G1d$HB>| z5?<@mwlV>hvjAQkfKh}a#l>P8X?fW$jnM$R&1pb&P?d2v8T`e;ypHzgxxxu`O9drv z;DDi`K1au53yinEko7AdQlw=5q4~u=?Y{O8*+3W2YZ8Y&a`PS#A87IumKzhl*#GeA z|LdUcUq*rdaszCtYv=Fnj5Hb~EwkkHOlinLb$=ou5cWdjgcCIQG3^5Ls2iKoT~ z)DN`8oY0YDe;}Y|Q*oetG8f$*@7ml7Du)p|k%Gr6|v z#wNpF_SkCS0acf$H;B;vig%Ga+~1k1_cP6CQtMRMsDpIKYFG9Wj!%~K+~niApCzLX zM8?gF1YAq4YiK5Qq_2f8%F20J)Rg?1xcH zKo#~_;#jk9s-{c3{BaZa47>@hK3p>35lRG1it5F2#JphmL44GdXh-x2)1T9|BS zh?b><$JDxpOJjEGDM{9FV~_5I%JCGy%73o-<(TT;)^!8h^=%!Pw(|6lr6OL(vBShn z2DHjDj-lo^bXNzVc&v2l`<+!WGZ=ok{bj9ffbW(cTD{f+UQbXnTEE+Rs+`b^*HIpS z6?SE0!lI`;?<-T$=041>pp%Sq>C=2#@f8&EE>tL~ zi2nHV&yvhLHSSOB@RJcDIswcq!xgH&1ysv%z4xYpXz8f{Y0SVMFr>?k*AvO26tz;y zHJGS&p#d*GE{=w%c3CYsPBjW#ctl#EqTGf{*`0>B&)XWWTLp==YVk5`-+3?M%@!z6 z%LWV$biCHW>@%3y-;M>z_5wTuA0`YP9}G2W0>g05l|Mf=`=)jx!7#_%*O|A_K%P>N zSJ)r5QdL-eGSgu$ZlaQHl&L+T)H5fXC2YKCXYFXWBwte z8?vC1gGQ*i4L-ly7C{0MXJJvPY~4KMPJ!QfW-C5HBAnpajlU zRGuq_ijmh|3Wv+ryRW7Xm=r3c54g_;$j%?hr&|N0H3qgG*E|OxW^g<`nXXI@;ReAn zDrhyNlzGqYGKy^tY%!&}rS~8(|KOjLjVuz*LLrXl&KbhdZ?CIS%KOb{go`{WW&*hq<*p1o$eJx|%YyEJ;h}C%n=<1546My0V zuJIG#HWT=}&hBGZ1pijM!0h=4>VNqH{;g)d`?c>Lc)Mr*uG-sG`MWBASMlv?!(DB- zs||Ox;jT8^)rNlwZOB2cFKX8mZCxGPP~2a8N#V;L!=J*wWy{}}k2;XC$I9}{9 zv#!pEUt=_TA`L1YNqCQFE>zfqYhDdsA3h*uomFah6)Dv5|u!1w?p_GIth(c#R-7Aq3BlJ?RqrGZ&MejReF{0l~fkzXb02=fUwC z;J*bp@Bd6K8UtPgvSmH^kH18-wB&Mc0Rhol{5k@Ei2?5p64+Wd?v3$x*k5U*{eM|E z+bDSif?tzE;8)i!bVz{!vh|AlAW)hDsDFX_k#6|EpJ}G0xVSjqH%;vCAj|zGm~sw` z*8yI?yAkW^|Dm2~$v=9YS2)isfTKK?N)<7dWwVu7Uhzl;+?Z%hj(ZzpfLrLTgfL0I z3o0e#G9Asmugou=5ouMGk1`k@=u-s}zAaNc8x)r!|p2Jh5%A&03;A~&=^qW_YTjOs`F>F7oQGSmJpsc)1x|0SZTs{+6D%j8 zp1DNW5AWUBr>f_1)~d>-r-*o%e<{SZz`3)ue8|^XxyZL&el84Qc%Ubeace@6P&`B3 zaPSOxK6sXQ#1-c*@;MRUKo;1}%kpKAi5Q9T$A627a8d|ypH3Zre%5j-%Q|tdt`ptG zHQplAkj$uZFC2OV<=q9EJO-UryWi&CyYAQRg-@P=JT#ZT05x5)at=Qli%$%`%^R=& zEjFUPeSpaust2y|q4iVU083GLm`s8Z_vtv(Heh=Cp*}sN04Tl?v^u-FSth2eqSr4W zp>{d9${pPaM6tHJB===6C%m3qG=R%eNixnKx{f|(h?2d)_>-K{S&Ak{hK!bo)} z-fBjlV!i)Pl7dTLk)^R`==c0+iNtr8#MNitVQf|_MbnqYxO(yzEWNi*N^ALGq5PY8 zW>P1OW+7iVNPhR3#v6OBNQ+Hd>RUG_G1nKNa|Mw<6bAwBx5%c=C!+kF^am zG(=KFn9*OqTu)}Hxs5ClUGteAB>tHCB_Q>nOhMfK)1}9b9?ySJxGW~K__S&88T5AF zfS#?*3823*!S0jl{fX|*fla18HQvHL)~?ra*92o-JZD^s*B!+Yu`h;Pn`SRSiTl5L zfeH^qm|J$e=uT;#Fxa10Y#z*9PY&72*ocNh`z9uW`p zJ*o0R1GI()vyPd+G@Z;h5l<4!NH$v?Xfmx2UU!rZV$*wS2MTmmc4p$}>CNC+s(Ka_ z)v_E4gX+SFohMAe-U}OMz9tQ2AA?($u*v$jLql!COLWSb9vj?l%nlMa=Z}! zQH^qLTXt)tva&L1i}NYJAX?Ye?P7w}Lyj{rrRVsBPF3|Q=5RQDH8Y+S7@m@c49Heu zcf$+33m98Yq5QqkK{R(f!czcP;f6M$8k)os!5&ndwp0 zn#q+RAn(jonjTut$*(iW&j^QovT8{`I?uW1ClyyT8r$X&G zwPyCJR^pARU*=m-po5y+nQK{&o*oZ)b@!`fBtPci)1k*(OyDEkIRi-w?X~R0AupqX zJB;JUNGagMd#CjL$w}CF9!pO}5ny2Hf0&S@$RkaBr@yGB1-st%{A?-ciN)hGtDX?+ zKu3QUInG&$-PG5goazKhlT?pUTS!k=imhYo-8*r6WrVnLPKuu#iRG?X#Zw_peJ_-@ zt49v!khG;GZzbw*djY<~5g@*3>QjaHbbqNeDSh?p8*{`%bD7eqSH1@G{@cDjYfYoP zDr?+G1DDF}2FvX%eQRz#Ma~Op70LS5!}|lZq7ds@$~SNugYr~7yw&rGE$#9_>O<43 z@xu`*IP*)C3Z2dVQyQ}AZ@AT6b|zh?bm_BKvYwvvIzYdEKJALwX`A4dy z&{oyxuCr>gLA1J^<5XXR$<^Zc@w&RS^qe8-d`e30+c-JszAOZ01OggHHom$4F*g`N z2=MNpL>#gek)YJFwOuj-bskkWRdlmW9BzwcO^|F+@OVo_QzS6B|alI!?uUZCXp9yM3to`hTI#eAH z9TfV`Vm|T+m>>+2w*;rFDVy|^O*wd z4}o<{P1M-N>W6#bmp41!XPBgwrXZouoK@kR@9fu(u^HM*y+rx*`vqCT%#?TaZvE%% z@9HojH!E}C>3)n?{ilOgAoyxK{{;@CQ^&s7!byRYzS5|u(0OPmelgT}YqsCmDNTj$ zMYB=E`);g>>TE0;fE{8B$*nR>XBeTif@a@q^;9A0 zqhbA}@rg(1^mV(@G)RxH4d2kJb?ohfADUBPNZiC|<8?3JW5>r-ucB4-z52{8`l>n? z2LnL{#rmzHC-2@G)$ye{VXJeOG2(p z)=dV*iih|*S3SJw(}Tm9sOHxA5{4K6{VEU?=0JUqw03eD9v;a*gs5=tDsnXL0#wK^ zPo9cMx@JJw<;5UjB5RE&PMI`A-Xr*jc04pkbGt<07)t*}$qoG#3x|P?pjdL1J6n;b zxiEOy-SI)yRvs^4W)4Rk4iYCU2G6&lq*4YL8*g;^Ahpr%7vgWyWn&Q}T~+2+kIcL2 zBe0Q#ussnEx$6r65us;DNKt;i%pc+lT9@HnHQVZPa^*p>lR(B!lijs3KuGEbbG_bn zNp)suj;-|}>Nz9Sbt|rtj*x?Fl8{Jc$uapDf-%jEA7kfnKac-Utd)bUf6r3`$v!8f zqV|f|LY#0sfsd0Lcht|TW&vLu`oalh(;M`5s3G$+1ReU=ZJSHtpREFO&@g*kc=-LT z8=G-2x-Bf~hQ3rR%1U`{ZH8=%@*Hm6dLEmh%7^1XnTuaN=BpMH=wRb^!VV4_Na?5J z#PR6KE_W>-cR$Vr$hIhHE{%M;F-w|n0ZZ=Hjcoi01pB8*8Y44+-~La*Je3Dbk;glh i_#@hEFCS403aA`>BSHW@c?5JOV0iQH4fOTLFa86{C;Nv0 literal 0 HcmV?d00001 diff --git a/docs/validation/pr-28/fixture-missing.png b/docs/validation/pr-28/fixture-missing.png new file mode 100644 index 0000000000000000000000000000000000000000..0b4559fd397ae5bef26581b2deec5c38437bb009 GIT binary patch literal 28633 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 literal 0 HcmV?d00001 diff --git a/docs/validation/pr-28/fixture-uppercase.png b/docs/validation/pr-28/fixture-uppercase.png new file mode 100644 index 0000000000000000000000000000000000000000..0b4559fd397ae5bef26581b2deec5c38437bb009 GIT binary patch literal 28633 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 literal 0 HcmV?d00001 diff --git a/docs/validation/pr-28/fixture-whitespace.png b/docs/validation/pr-28/fixture-whitespace.png new file mode 100644 index 0000000000000000000000000000000000000000..0b4559fd397ae5bef26581b2deec5c38437bb009 GIT binary patch literal 28633 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 literal 0 HcmV?d00001 diff --git a/docs/validation/pr-28/hashes.json b/docs/validation/pr-28/hashes.json index e96d740..886d2af 100644 --- a/docs/validation/pr-28/hashes.json +++ b/docs/validation/pr-28/hashes.json @@ -12,5 +12,16 @@ "controlUiIndex": "73ac0e6f38896c9d491b45a6c3d23b4990dae139d8ff3cb93da7a33e640bb1e7", "controlUiEntry": "1430574e4d89d8c43b303057d918fdba41a4a940d436d7feaf8e0122a533e4f6", "screenshot": "1dd7153742614febf8d00aa08c50f3c3465cdd8a7a3b4cf87e8fa585177a0142" + }, + "evidenceFiles": { + "fixture-disabled.png": "9445d0a982b921b77113fa3889278c129b31ba2de50ea591caf2e5af40cd13b4", + "fixture-empty.png": "5b64cdc293b6fa1e6be4d04d5c37c7d0403293e9e8aaaf1576c3b55dcfc2a940", + "fixture-enabled.png": "6912ea4e768ae4a2f8fceff4a64c4f371eb44184b99b061ec6a9fe5599aa4fea", + "fixture-invalid.png": "5b64cdc293b6fa1e6be4d04d5c37c7d0403293e9e8aaaf1576c3b55dcfc2a940", + "fixture-missing.png": "5b64cdc293b6fa1e6be4d04d5c37c7d0403293e9e8aaaf1576c3b55dcfc2a940", + "fixture-uppercase.png": "5b64cdc293b6fa1e6be4d04d5c37c7d0403293e9e8aaaf1576c3b55dcfc2a940", + "fixture-whitespace.png": "5b64cdc293b6fa1e6be4d04d5c37c7d0403293e9e8aaaf1576c3b55dcfc2a940", + "gateway-isolation-e2e.png": "1dd7153742614febf8d00aa08c50f3c3465cdd8a7a3b4cf87e8fa585177a0142", + "launcher-disabled.png": "9445d0a982b921b77113fa3889278c129b31ba2de50ea591caf2e5af40cd13b4" } } diff --git a/docs/validation/pr-28/launcher-disabled.png b/docs/validation/pr-28/launcher-disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..e3be96a07d9bab4293ef4d378cc98e101de84d44 GIT binary patch literal 41835 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≶6q`BwXQ%68lG*A*A?_B<5^2z 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 literal 0 HcmV?d00001 diff --git a/docs/validation/pr-28/launcher-tests.json b/docs/validation/pr-28/launcher-tests.json new file mode 100644 index 0000000..de169c2 --- /dev/null +++ b/docs/validation/pr-28/launcher-tests.json @@ -0,0 +1,234 @@ +{ + "command": "dotnet test .\\OpenClaw.Gateway.MSIX.slnx --configuration Release --no-restore", + "total": 57, + "results": [ + { + "name": "OpenClaw.Launcher.Tests.HostEntrypointResolverTests.OnlyArgvZeroSelectsTheEntrypoint", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.ProgramTests.SetupChecksNodeAndPackagedApplicationWithoutMutation", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.HostDiagnosticLogTests.WritePersistsTimestampedProcessDiagnostics", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.GatewayLauncherTests.CreateStartInfoRejectsInvalidGatewayIsolationMode(mode: -1)", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.ProgramTests.AgentResolvesNodeBeforeReportingMissingApplication", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.NodeRuntimeResolverTests.SupportedVersionsMatchPackagedOpenClawRequirement(major: 25, minor: 8, build: 99, expected: False)", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.ClawCtlCommandTests.ParseRejectsCommandsOutsideThePublicManagementSurface(args: [\"doctor\"])", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.WindowsKillOnCloseJobTests.FastExitRetainsExitCodeAfterDelayedObservation", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.ClawCtlCommandTests.ParseAcceptsOnlyThePublicManagementSurface(expected: Help, args: [])", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.HostOptionsTests.ParseForwardsAllArgumentsUnchanged", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.GatewayLauncherTests.CreateStartInfoPreservesExplicitArguments", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.ProgramTests.AgentLaunchResolvesNodeAndRunsPackagedApplication", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.NodeRuntimeResolverTests.SupportedVersionsMatchPackagedOpenClawRequirement(major: 23, minor: 99, build: 0, expected: False)", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.HostOptionsTests.ParseReportsMissingPackagedApplication", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.GatewayLauncherTests.CreateStartInfoForwardsCommandsWithoutInterpretation(arguments: [\"--update\"])", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.ClawCtlCommandTests.ParseAcceptsOnlyThePublicManagementSurface(expected: Setup, args: [\"setup\"])", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.ClawCtlConsoleTests.WriteReadinessSummaryDescribesPackagedApplication", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.GatewayLauncherTests.CreateStartInfoForwardsCommandsWithoutInterpretation(arguments: [\"update\", \"--yes\"])", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.NodeRuntimeResolverTests.SupportedVersionsMatchPackagedOpenClawRequirement(major: 26, minor: 0, build: 0, expected: True)", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.GatewayLauncherTests.CreateStartInfoForwardsNoArgumentsUnchanged", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.ClawCtlCommandTests.ParseRejectsCommandsOutsideThePublicManagementSurface(args: [\"verify\"])", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.NodeRuntimeResolverTests.SupportedVersionsMatchPackagedOpenClawRequirement(major: 24, minor: 14, build: 99, expected: False)", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.ClawCtlCommandTests.ParseAcceptsOnlyThePublicManagementSurface(expected: Version, args: [\"--version\"])", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.GatewayLauncherTests.CreateStartInfoForwardsCommandsWithoutInterpretation(arguments: [\"onboard\", \"--mode\", \"local\"])", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.GatewayLauncherTests.CreateStartInfoRejectsInvalidGatewayIsolationMode(mode: 2147483647)", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.HostEntrypointResolverTests.UnknownOrMalformedInvocationDefaultsToAgent", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.GatewayLauncherTests.CreateStartInfoForwardsCommandsWithoutInterpretation(arguments: [\"setup\", \"--install-daemon\"])", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.ClawCtlCommandTests.ParseRejectsCommandsOutsideThePublicManagementSurface(args: [\"gateway\", \"status\"])", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.NodeRuntimeResolverTests.ResolveAcceptsCompatibleRuntime", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.NodeRuntimeResolverTests.ResolveRejectsPrereleaseVersion", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.ClawCtlCommandTests.ParseRejectsCommandsOutsideThePublicManagementSurface(args: [\"update-package\"])", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.GatewayLauncherTests.CreateStartInfoDefaultsToCurrentWorkingDirectory", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.ProgramTests.SetupResolvesNodeBeforeReportingMissingApplication", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.GatewayLauncherTests.CreateStartInfoRejectsInvalidGatewayIsolationMode(mode: 2)", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.ClawCtlCommandTests.ParseRejectsCommandsOutsideThePublicManagementSurface(args: [\"setup\", \"--force\"])", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.NodeRuntimeResolverTests.ResolveRejectsIncompatibleArchitecture", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.WindowsKillOnCloseJobTests.DisposingJobTerminatesAssignedProcess", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.NodeRuntimeResolverTests.ResolveRejectsMalformedVersion", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.ClawCtlConsoleTests.WriteHelpListsOnlyThePublicCommands", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.ClawCtlCommandTests.ParseRejectsCommandsOutsideThePublicManagementSurface(args: [\"prepare\"])", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.NodeRuntimeResolverTests.ResolveReportsVersionQueryFailure", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.HostOptionsTests.ParseResolvesPackagedApplicationWhenEntryPointExists", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.PackageManifestTests.ManifestRegistersBothAliasesToTheSingleExecutable", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.GatewayLauncherTests.CreateStartInfoReportsExactGatewayIsolationMode(mode: Disabled, expected: \"disabled\")", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.HostEntrypointResolverTests.AgentAliasSelectsThePassthroughEntrypoint", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.NodeRuntimeResolverTests.SupportedVersionsMatchPackagedOpenClawRequirement(major: 22, minor: 22, build: 2, expected: False)", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.ClawCtlCommandTests.ParseRejectsCommandsOutsideThePublicManagementSurface(args: [\"repair\"])", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.NodeRuntimeResolverTests.SupportedVersionsMatchPackagedOpenClawRequirement(major: 25, minor: 9, build: 0, expected: True)", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.NodeRuntimeResolverTests.ResolveRejectsOutdatedRuntime", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.NodeRuntimeResolverTests.SupportedVersionsMatchPackagedOpenClawRequirement(major: 24, minor: 15, build: 0, expected: True)", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.GatewayLauncherTests.CreateStartInfoForwardsCommandsWithoutInterpretation(arguments: [\"gateway\", \"call\", \"update.run\"])", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.NodeRuntimeResolverTests.ResolveReportsInstallCommandWhenRuntimeIsMissing", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.GatewayLauncherTests.CreateStartInfoForwardsCommandsWithoutInterpretation(arguments: [\"gateway\", \"install\"])", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.NodeRuntimeResolverTests.SupportedVersionsMatchPackagedOpenClawRequirement(major: 22, minor: 22, build: 3, expected: True)", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.HostDiagnosticLogTests.ConcurrentInvocationsSerializeCompleteLogRecords", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.HostEntrypointResolverTests.ControlAliasSelectsTheManagementEntrypoint", + "outcome": "Passed" + }, + { + "name": "OpenClaw.Launcher.Tests.GatewayLauncherTests.CreateStartInfoReportsExactGatewayIsolationMode(mode: Enabled, expected: \"enabled\")", + "outcome": "Passed" + } + ] +} diff --git a/docs/validation/pr-28/payload-matrix.json b/docs/validation/pr-28/payload-matrix.json new file mode 100644 index 0000000..7f14d09 --- /dev/null +++ b/docs/validation/pr-28/payload-matrix.json @@ -0,0 +1,3656 @@ +{ + "schemaVersion": 1, + "fixtureType": "synthetic-local-npm-packages-and-synthetic-runtime-inspection-responses", + "scope": "Actual Build-Payload.ps1 x64 and ARM64 composition contract with controlled runtime-inspection fixtures.", + "productionSourceCommit": "9aa1df286c2b6fd59b4c101201ddae0ae6000324", + "evidenceCheckoutHead": "2ce86d5bf4b54809ab1b4dcf5930db74d36b6d84", + "verifiedSourceFiles": [ + { + "path": "scripts/Build-Payload.ps1", + "sha256": "7dbdc5b23b0b325a8338de26c8e00cb9b7c4475404c78090884c93ba7ae60691" + }, + { + "path": "plugins/gateway-isolation/package.json", + "sha256": "5a4c1c3966c782455e6505dedc9427c8096917f520ca6486f169fc4db5e50142" + }, + { + "path": "plugins/gateway-isolation/openclaw.plugin.json", + "sha256": "a1359e966e39d799f400a7250a4366cd1ef0b0bb8be954100285a2c1da298651" + }, + { + "path": "plugins/gateway-isolation/index.js", + "sha256": "7f98aed21bf7773f78a25c94c47d4f0f4efd6998c314201869307b798f25d1cd" + } + ], + "harness": "scripts/validation/Test-GatewayIsolationPayloadMatrix.ps1", + "harnessSha256": "a538e627a1eda0737557e052a68b44d5e591c1923fb4223b7eb52084481f86d9", + "environment": { + "powershell": "7.6.6", + "node": "v24.16.0", + "npm": "11.13.0", + "host": "Windows x64", + "npmOffline": true + }, + "command": "pwsh -NoProfile -File .\\scripts\\validation\\Test-GatewayIsolationPayloadMatrix.ps1", + "summary": { + "total": 66, + "passed": 66, + "failed": 0, + "accepted": 6, + "rejected": 60, + "architectures": [ + "x64", + "arm64" + ] + }, + "harnessEnvironmentRestored": true, + "harnessWorkingDirectoryRestored": true, + "generatedFixtureDirectoryRemoved": true, + "cases": [ + { + "id": "x64-accepted-existing-environment", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "accepted", + "field": null, + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "accepted", + "actual": "accepted", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-accepted-existing-environment\\package -Architecture x64 -OutputDirectory \\x64-accepted-existing-environment\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + }, + { + "command": "version-smoke", + "args": [ + "--version" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "matrix-original-CLAWCTL_GATEWAY_ISOLATION", + "stateDirectoryMatchesExpected": false + } + ], + "content": { + "shippingFileCount": 3, + "files": [ + { + "file": "index.js", + "sourceSha256": "7f98aed21bf7773f78a25c94c47d4f0f4efd6998c314201869307b798f25d1cd", + "payloadSha256": "7f98aed21bf7773f78a25c94c47d4f0f4efd6998c314201869307b798f25d1cd", + "identical": true + }, + { + "file": "openclaw.plugin.json", + "sourceSha256": "a1359e966e39d799f400a7250a4366cd1ef0b0bb8be954100285a2c1da298651", + "payloadSha256": "a1359e966e39d799f400a7250a4366cd1ef0b0bb8be954100285a2c1da298651", + "identical": true + }, + { + "file": "package.json", + "sourceSha256": "5a4c1c3966c782455e6505dedc9427c8096917f520ca6486f169fc4db5e50142", + "payloadSha256": "5a4c1c3966c782455e6505dedc9427c8096917f520ca6486f169fc4db5e50142", + "identical": true + } + ], + "noTestFile": true, + "metadataArchitecture": "x64", + "metadataLayout": "expanded-directory", + "metadataNodeVersion": "v24.16.0", + "metadataNpmVersion": "11.13.0" + }, + "rejectedOutputAbsent": false + }, + { + "id": "x64-accepted-absent-environment", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "accepted", + "field": null, + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "accepted", + "actual": "accepted", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-accepted-absent-environment\\package -Architecture x64 -OutputDirectory \\x64-accepted-absent-environment\\payload", + "environmentBefore": "absent", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + }, + { + "command": "version-smoke", + "args": [ + "--version" + ], + "nodeVersion": "v24.16.0", + "isolationMode": null, + "stateDirectoryMatchesExpected": false + } + ], + "content": { + "shippingFileCount": 3, + "files": [ + { + "file": "index.js", + "sourceSha256": "7f98aed21bf7773f78a25c94c47d4f0f4efd6998c314201869307b798f25d1cd", + "payloadSha256": "7f98aed21bf7773f78a25c94c47d4f0f4efd6998c314201869307b798f25d1cd", + "identical": true + }, + { + "file": "openclaw.plugin.json", + "sourceSha256": "a1359e966e39d799f400a7250a4366cd1ef0b0bb8be954100285a2c1da298651", + "payloadSha256": "a1359e966e39d799f400a7250a4366cd1ef0b0bb8be954100285a2c1da298651", + "identical": true + }, + { + "file": "package.json", + "sourceSha256": "5a4c1c3966c782455e6505dedc9427c8096917f520ca6486f169fc4db5e50142", + "payloadSha256": "5a4c1c3966c782455e6505dedc9427c8096917f520ca6486f169fc4db5e50142", + "identical": true + } + ], + "noTestFile": true, + "metadataArchitecture": "x64", + "metadataLayout": "expanded-directory", + "metadataNodeVersion": "v24.16.0", + "metadataNpmVersion": "11.13.0" + }, + "rejectedOutputAbsent": false + }, + { + "id": "x64-accepted-empty-environment", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "accepted", + "field": null, + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "accepted", + "actual": "accepted", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-accepted-empty-environment\\package -Architecture x64 -OutputDirectory \\x64-accepted-empty-environment\\payload", + "environmentBefore": "empty", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + }, + { + "command": "version-smoke", + "args": [ + "--version" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "", + "stateDirectoryMatchesExpected": false + } + ], + "content": { + "shippingFileCount": 3, + "files": [ + { + "file": "index.js", + "sourceSha256": "7f98aed21bf7773f78a25c94c47d4f0f4efd6998c314201869307b798f25d1cd", + "payloadSha256": "7f98aed21bf7773f78a25c94c47d4f0f4efd6998c314201869307b798f25d1cd", + "identical": true + }, + { + "file": "openclaw.plugin.json", + "sourceSha256": "a1359e966e39d799f400a7250a4366cd1ef0b0bb8be954100285a2c1da298651", + "payloadSha256": "a1359e966e39d799f400a7250a4366cd1ef0b0bb8be954100285a2c1da298651", + "identical": true + }, + { + "file": "package.json", + "sourceSha256": "5a4c1c3966c782455e6505dedc9427c8096917f520ca6486f169fc4db5e50142", + "payloadSha256": "5a4c1c3966c782455e6505dedc9427c8096917f520ca6486f169fc4db5e50142", + "identical": true + } + ], + "noTestFile": true, + "metadataArchitecture": "x64", + "metadataLayout": "expanded-directory", + "metadataNodeVersion": "v24.16.0", + "metadataNpmVersion": "11.13.0" + }, + "rejectedOutputAbsent": false + }, + { + "id": "x64-missing-extensions", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "missing-extensions", + "field": null, + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The pinned OpenClaw payload does not expose the supported bundled plugin directory: \\x64-missing-extensions\\runner\\openclaw-stage-x64\\node_modules\\openclaw\\dist\\extensions", + "actual": "The pinned OpenClaw payload does not expose the supported bundled plugin directory: \\x64-missing-extensions\\runner\\openclaw-stage-x64\\node_modules\\openclaw\\dist\\extensions", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-missing-extensions\\package -Architecture x64 -OutputDirectory \\x64-missing-extensions\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "x64-preexisting-plugin", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "preexisting-plugin", + "field": null, + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The OpenClaw payload already contains a gateway-isolation plugin; refusing to replace upstream content.", + "actual": "The OpenClaw payload already contains a gateway-isolation plugin; refusing to replace upstream content.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-preexisting-plugin\\package -Architecture x64 -OutputDirectory \\x64-preexisting-plugin\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "x64-inspection-exit-23", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "exit", + "field": null, + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload cannot load the Gateway isolation plugin. Exit code: 23.", + "actual": "The selected OpenClaw payload cannot load the Gateway isolation plugin. Exit code: 23.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-inspection-exit-23\\package -Architecture x64 -OutputDirectory \\x64-inspection-exit-23\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "x64-wrong-plugin-id", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "wrong", + "field": "plugin.id", + "fixtureInspection": { + "plugin": { + "enabled": true, + "activated": true, + "id": "unexpected-plugin", + "imported": true, + "status": "loaded", + "httpRoutes": 1, + "origin": "bundled" + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-wrong-plugin-id\\package -Architecture x64 -OutputDirectory \\x64-wrong-plugin-id\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "x64-missing-plugin-id", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "missing", + "field": "plugin.id", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-missing-plugin-id\\package -Architecture x64 -OutputDirectory \\x64-missing-plugin-id\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "x64-wrong-plugin-origin", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "wrong", + "field": "plugin.origin", + "fixtureInspection": { + "plugin": { + "enabled": true, + "activated": true, + "id": "gateway-isolation", + "imported": true, + "status": "loaded", + "httpRoutes": 1, + "origin": "global" + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-wrong-plugin-origin\\package -Architecture x64 -OutputDirectory \\x64-wrong-plugin-origin\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "x64-missing-plugin-origin", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "missing", + "field": "plugin.origin", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-missing-plugin-origin\\package -Architecture x64 -OutputDirectory \\x64-missing-plugin-origin\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "x64-wrong-plugin-enabled", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "wrong", + "field": "plugin.enabled", + "fixtureInspection": { + "plugin": { + "enabled": false, + "activated": true, + "id": "gateway-isolation", + "imported": true, + "status": "loaded", + "httpRoutes": 1, + "origin": "bundled" + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-wrong-plugin-enabled\\package -Architecture x64 -OutputDirectory \\x64-wrong-plugin-enabled\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "x64-missing-plugin-enabled", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "missing", + "field": "plugin.enabled", + "fixtureInspection": { + "plugin": { + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-missing-plugin-enabled\\package -Architecture x64 -OutputDirectory \\x64-missing-plugin-enabled\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "x64-wrong-plugin-activated", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "wrong", + "field": "plugin.activated", + "fixtureInspection": { + "plugin": { + "enabled": true, + "activated": false, + "id": "gateway-isolation", + "imported": true, + "status": "loaded", + "httpRoutes": 1, + "origin": "bundled" + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-wrong-plugin-activated\\package -Architecture x64 -OutputDirectory \\x64-wrong-plugin-activated\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "x64-missing-plugin-activated", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "missing", + "field": "plugin.activated", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1 + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-missing-plugin-activated\\package -Architecture x64 -OutputDirectory \\x64-missing-plugin-activated\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "x64-wrong-plugin-status", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "wrong", + "field": "plugin.status", + "fixtureInspection": { + "plugin": { + "enabled": true, + "activated": true, + "id": "gateway-isolation", + "imported": true, + "status": "error", + "httpRoutes": 1, + "origin": "bundled" + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-wrong-plugin-status\\package -Architecture x64 -OutputDirectory \\x64-wrong-plugin-status\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "x64-missing-plugin-status", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "missing", + "field": "plugin.status", + "fixtureInspection": { + "plugin": { + "enabled": true, + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-missing-plugin-status\\package -Architecture x64 -OutputDirectory \\x64-missing-plugin-status\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "x64-wrong-plugin-imported", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "wrong", + "field": "plugin.imported", + "fixtureInspection": { + "plugin": { + "enabled": true, + "activated": true, + "id": "gateway-isolation", + "imported": false, + "status": "loaded", + "httpRoutes": 1, + "origin": "bundled" + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-wrong-plugin-imported\\package -Architecture x64 -OutputDirectory \\x64-wrong-plugin-imported\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "x64-missing-plugin-imported", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "missing", + "field": "plugin.imported", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-missing-plugin-imported\\package -Architecture x64 -OutputDirectory \\x64-missing-plugin-imported\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "x64-wrong-plugin-httpRoutes", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "wrong", + "field": "plugin.httpRoutes", + "fixtureInspection": { + "plugin": { + "enabled": true, + "activated": true, + "id": "gateway-isolation", + "imported": true, + "status": "loaded", + "httpRoutes": 0, + "origin": "bundled" + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-wrong-plugin-httpRoutes\\package -Architecture x64 -OutputDirectory \\x64-wrong-plugin-httpRoutes\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "x64-missing-plugin-httpRoutes", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "missing", + "field": "plugin.httpRoutes", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-missing-plugin-httpRoutes\\package -Architecture x64 -OutputDirectory \\x64-missing-plugin-httpRoutes\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "x64-wrong-httpRouteCount", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "wrong", + "field": "httpRouteCount", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 0, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-wrong-httpRouteCount\\package -Architecture x64 -OutputDirectory \\x64-wrong-httpRouteCount\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "x64-missing-httpRouteCount", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "missing", + "field": "httpRouteCount", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-missing-httpRouteCount\\package -Architecture x64 -OutputDirectory \\x64-missing-httpRouteCount\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "x64-wrong-gatewayMethods", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "wrong", + "field": "gatewayMethods", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [ + "fixture.method" + ], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-wrong-gatewayMethods\\package -Architecture x64 -OutputDirectory \\x64-wrong-gatewayMethods\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "x64-missing-gatewayMethods", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "missing", + "field": "gatewayMethods", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-missing-gatewayMethods\\package -Architecture x64 -OutputDirectory \\x64-missing-gatewayMethods\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "x64-wrong-tools", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "wrong", + "field": "tools", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [ + "fixture.tool" + ], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-wrong-tools\\package -Architecture x64 -OutputDirectory \\x64-wrong-tools\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "x64-missing-tools", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "missing", + "field": "tools", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-missing-tools\\package -Architecture x64 -OutputDirectory \\x64-missing-tools\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "x64-wrong-services", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "wrong", + "field": "services", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [ + "fixture.service" + ], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-wrong-services\\package -Architecture x64 -OutputDirectory \\x64-wrong-services\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "x64-missing-services", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "missing", + "field": "services", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-missing-services\\package -Architecture x64 -OutputDirectory \\x64-missing-services\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "x64-wrong-diagnostics", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "wrong", + "field": "diagnostics", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [ + "fixture diagnostic" + ] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-wrong-diagnostics\\package -Architecture x64 -OutputDirectory \\x64-wrong-diagnostics\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "x64-missing-diagnostics", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "missing", + "field": "diagnostics", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-missing-diagnostics\\package -Architecture x64 -OutputDirectory \\x64-missing-diagnostics\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "x64-excess-plugin-httpRoutes", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "wrong", + "field": "plugin.httpRoutes", + "fixtureInspection": { + "plugin": { + "enabled": true, + "activated": true, + "id": "gateway-isolation", + "imported": true, + "status": "loaded", + "httpRoutes": 2, + "origin": "bundled" + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-excess-plugin-httpRoutes\\package -Architecture x64 -OutputDirectory \\x64-excess-plugin-httpRoutes\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "x64-excess-httpRouteCount", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "wrong", + "field": "httpRouteCount", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 2, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-excess-httpRouteCount\\package -Architecture x64 -OutputDirectory \\x64-excess-httpRouteCount\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "x64-missing-plugin-object", + "architecture": "x64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "missing", + "field": "plugin", + "fixtureInspection": { + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\x64-missing-plugin-object\\package -Architecture x64 -OutputDirectory \\x64-missing-plugin-object\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "arm64-accepted-existing-environment", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "accepted", + "field": null, + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "accepted", + "actual": "accepted", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-accepted-existing-environment\\package -Architecture arm64 -OutputDirectory \\arm64-accepted-existing-environment\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": { + "shippingFileCount": 3, + "files": [ + { + "file": "index.js", + "sourceSha256": "7f98aed21bf7773f78a25c94c47d4f0f4efd6998c314201869307b798f25d1cd", + "payloadSha256": "7f98aed21bf7773f78a25c94c47d4f0f4efd6998c314201869307b798f25d1cd", + "identical": true + }, + { + "file": "openclaw.plugin.json", + "sourceSha256": "a1359e966e39d799f400a7250a4366cd1ef0b0bb8be954100285a2c1da298651", + "payloadSha256": "a1359e966e39d799f400a7250a4366cd1ef0b0bb8be954100285a2c1da298651", + "identical": true + }, + { + "file": "package.json", + "sourceSha256": "5a4c1c3966c782455e6505dedc9427c8096917f520ca6486f169fc4db5e50142", + "payloadSha256": "5a4c1c3966c782455e6505dedc9427c8096917f520ca6486f169fc4db5e50142", + "identical": true + } + ], + "noTestFile": true, + "metadataArchitecture": "arm64", + "metadataLayout": "expanded-directory", + "metadataNodeVersion": "v24.16.0", + "metadataNpmVersion": "11.13.0" + }, + "rejectedOutputAbsent": false + }, + { + "id": "arm64-accepted-absent-environment", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "accepted", + "field": null, + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "accepted", + "actual": "accepted", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-accepted-absent-environment\\package -Architecture arm64 -OutputDirectory \\arm64-accepted-absent-environment\\payload", + "environmentBefore": "absent", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": { + "shippingFileCount": 3, + "files": [ + { + "file": "index.js", + "sourceSha256": "7f98aed21bf7773f78a25c94c47d4f0f4efd6998c314201869307b798f25d1cd", + "payloadSha256": "7f98aed21bf7773f78a25c94c47d4f0f4efd6998c314201869307b798f25d1cd", + "identical": true + }, + { + "file": "openclaw.plugin.json", + "sourceSha256": "a1359e966e39d799f400a7250a4366cd1ef0b0bb8be954100285a2c1da298651", + "payloadSha256": "a1359e966e39d799f400a7250a4366cd1ef0b0bb8be954100285a2c1da298651", + "identical": true + }, + { + "file": "package.json", + "sourceSha256": "5a4c1c3966c782455e6505dedc9427c8096917f520ca6486f169fc4db5e50142", + "payloadSha256": "5a4c1c3966c782455e6505dedc9427c8096917f520ca6486f169fc4db5e50142", + "identical": true + } + ], + "noTestFile": true, + "metadataArchitecture": "arm64", + "metadataLayout": "expanded-directory", + "metadataNodeVersion": "v24.16.0", + "metadataNpmVersion": "11.13.0" + }, + "rejectedOutputAbsent": false + }, + { + "id": "arm64-accepted-empty-environment", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "accepted", + "field": null, + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "accepted", + "actual": "accepted", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-accepted-empty-environment\\package -Architecture arm64 -OutputDirectory \\arm64-accepted-empty-environment\\payload", + "environmentBefore": "empty", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": { + "shippingFileCount": 3, + "files": [ + { + "file": "index.js", + "sourceSha256": "7f98aed21bf7773f78a25c94c47d4f0f4efd6998c314201869307b798f25d1cd", + "payloadSha256": "7f98aed21bf7773f78a25c94c47d4f0f4efd6998c314201869307b798f25d1cd", + "identical": true + }, + { + "file": "openclaw.plugin.json", + "sourceSha256": "a1359e966e39d799f400a7250a4366cd1ef0b0bb8be954100285a2c1da298651", + "payloadSha256": "a1359e966e39d799f400a7250a4366cd1ef0b0bb8be954100285a2c1da298651", + "identical": true + }, + { + "file": "package.json", + "sourceSha256": "5a4c1c3966c782455e6505dedc9427c8096917f520ca6486f169fc4db5e50142", + "payloadSha256": "5a4c1c3966c782455e6505dedc9427c8096917f520ca6486f169fc4db5e50142", + "identical": true + } + ], + "noTestFile": true, + "metadataArchitecture": "arm64", + "metadataLayout": "expanded-directory", + "metadataNodeVersion": "v24.16.0", + "metadataNpmVersion": "11.13.0" + }, + "rejectedOutputAbsent": false + }, + { + "id": "arm64-missing-extensions", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "missing-extensions", + "field": null, + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The pinned OpenClaw payload does not expose the supported bundled plugin directory: \\arm64-missing-extensions\\runner\\openclaw-stage-arm64\\node_modules\\openclaw\\dist\\extensions", + "actual": "The pinned OpenClaw payload does not expose the supported bundled plugin directory: \\arm64-missing-extensions\\runner\\openclaw-stage-arm64\\node_modules\\openclaw\\dist\\extensions", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-missing-extensions\\package -Architecture arm64 -OutputDirectory \\arm64-missing-extensions\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "arm64-preexisting-plugin", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "preexisting-plugin", + "field": null, + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The OpenClaw payload already contains a gateway-isolation plugin; refusing to replace upstream content.", + "actual": "The OpenClaw payload already contains a gateway-isolation plugin; refusing to replace upstream content.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-preexisting-plugin\\package -Architecture arm64 -OutputDirectory \\arm64-preexisting-plugin\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "arm64-inspection-exit-23", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "exit", + "field": null, + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload cannot load the Gateway isolation plugin. Exit code: 23.", + "actual": "The selected OpenClaw payload cannot load the Gateway isolation plugin. Exit code: 23.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-inspection-exit-23\\package -Architecture arm64 -OutputDirectory \\arm64-inspection-exit-23\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "arm64-wrong-plugin-id", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "wrong", + "field": "plugin.id", + "fixtureInspection": { + "plugin": { + "enabled": true, + "activated": true, + "id": "unexpected-plugin", + "imported": true, + "status": "loaded", + "httpRoutes": 1, + "origin": "bundled" + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-wrong-plugin-id\\package -Architecture arm64 -OutputDirectory \\arm64-wrong-plugin-id\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "arm64-missing-plugin-id", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "missing", + "field": "plugin.id", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-missing-plugin-id\\package -Architecture arm64 -OutputDirectory \\arm64-missing-plugin-id\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "arm64-wrong-plugin-origin", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "wrong", + "field": "plugin.origin", + "fixtureInspection": { + "plugin": { + "enabled": true, + "activated": true, + "id": "gateway-isolation", + "imported": true, + "status": "loaded", + "httpRoutes": 1, + "origin": "global" + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-wrong-plugin-origin\\package -Architecture arm64 -OutputDirectory \\arm64-wrong-plugin-origin\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "arm64-missing-plugin-origin", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "missing", + "field": "plugin.origin", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-missing-plugin-origin\\package -Architecture arm64 -OutputDirectory \\arm64-missing-plugin-origin\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "arm64-wrong-plugin-enabled", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "wrong", + "field": "plugin.enabled", + "fixtureInspection": { + "plugin": { + "enabled": false, + "activated": true, + "id": "gateway-isolation", + "imported": true, + "status": "loaded", + "httpRoutes": 1, + "origin": "bundled" + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-wrong-plugin-enabled\\package -Architecture arm64 -OutputDirectory \\arm64-wrong-plugin-enabled\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "arm64-missing-plugin-enabled", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "missing", + "field": "plugin.enabled", + "fixtureInspection": { + "plugin": { + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-missing-plugin-enabled\\package -Architecture arm64 -OutputDirectory \\arm64-missing-plugin-enabled\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "arm64-wrong-plugin-activated", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "wrong", + "field": "plugin.activated", + "fixtureInspection": { + "plugin": { + "enabled": true, + "activated": false, + "id": "gateway-isolation", + "imported": true, + "status": "loaded", + "httpRoutes": 1, + "origin": "bundled" + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-wrong-plugin-activated\\package -Architecture arm64 -OutputDirectory \\arm64-wrong-plugin-activated\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "arm64-missing-plugin-activated", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "missing", + "field": "plugin.activated", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1 + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-missing-plugin-activated\\package -Architecture arm64 -OutputDirectory \\arm64-missing-plugin-activated\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "arm64-wrong-plugin-status", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "wrong", + "field": "plugin.status", + "fixtureInspection": { + "plugin": { + "enabled": true, + "activated": true, + "id": "gateway-isolation", + "imported": true, + "status": "error", + "httpRoutes": 1, + "origin": "bundled" + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-wrong-plugin-status\\package -Architecture arm64 -OutputDirectory \\arm64-wrong-plugin-status\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "arm64-missing-plugin-status", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "missing", + "field": "plugin.status", + "fixtureInspection": { + "plugin": { + "enabled": true, + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-missing-plugin-status\\package -Architecture arm64 -OutputDirectory \\arm64-missing-plugin-status\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "arm64-wrong-plugin-imported", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "wrong", + "field": "plugin.imported", + "fixtureInspection": { + "plugin": { + "enabled": true, + "activated": true, + "id": "gateway-isolation", + "imported": false, + "status": "loaded", + "httpRoutes": 1, + "origin": "bundled" + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-wrong-plugin-imported\\package -Architecture arm64 -OutputDirectory \\arm64-wrong-plugin-imported\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "arm64-missing-plugin-imported", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "missing", + "field": "plugin.imported", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-missing-plugin-imported\\package -Architecture arm64 -OutputDirectory \\arm64-missing-plugin-imported\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "arm64-wrong-plugin-httpRoutes", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "wrong", + "field": "plugin.httpRoutes", + "fixtureInspection": { + "plugin": { + "enabled": true, + "activated": true, + "id": "gateway-isolation", + "imported": true, + "status": "loaded", + "httpRoutes": 0, + "origin": "bundled" + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-wrong-plugin-httpRoutes\\package -Architecture arm64 -OutputDirectory \\arm64-wrong-plugin-httpRoutes\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "arm64-missing-plugin-httpRoutes", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "missing", + "field": "plugin.httpRoutes", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-missing-plugin-httpRoutes\\package -Architecture arm64 -OutputDirectory \\arm64-missing-plugin-httpRoutes\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "arm64-wrong-httpRouteCount", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "wrong", + "field": "httpRouteCount", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 0, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-wrong-httpRouteCount\\package -Architecture arm64 -OutputDirectory \\arm64-wrong-httpRouteCount\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "arm64-missing-httpRouteCount", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "missing", + "field": "httpRouteCount", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-missing-httpRouteCount\\package -Architecture arm64 -OutputDirectory \\arm64-missing-httpRouteCount\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "arm64-wrong-gatewayMethods", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "wrong", + "field": "gatewayMethods", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [ + "fixture.method" + ], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-wrong-gatewayMethods\\package -Architecture arm64 -OutputDirectory \\arm64-wrong-gatewayMethods\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "arm64-missing-gatewayMethods", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "missing", + "field": "gatewayMethods", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-missing-gatewayMethods\\package -Architecture arm64 -OutputDirectory \\arm64-missing-gatewayMethods\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "arm64-wrong-tools", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "wrong", + "field": "tools", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [ + "fixture.tool" + ], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-wrong-tools\\package -Architecture arm64 -OutputDirectory \\arm64-wrong-tools\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "arm64-missing-tools", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "missing", + "field": "tools", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-missing-tools\\package -Architecture arm64 -OutputDirectory \\arm64-missing-tools\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "arm64-wrong-services", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "wrong", + "field": "services", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [ + "fixture.service" + ], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-wrong-services\\package -Architecture arm64 -OutputDirectory \\arm64-wrong-services\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "arm64-missing-services", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "missing", + "field": "services", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-missing-services\\package -Architecture arm64 -OutputDirectory \\arm64-missing-services\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "arm64-wrong-diagnostics", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "wrong", + "field": "diagnostics", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [ + "fixture diagnostic" + ] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-wrong-diagnostics\\package -Architecture arm64 -OutputDirectory \\arm64-wrong-diagnostics\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "arm64-missing-diagnostics", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "missing", + "field": "diagnostics", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-missing-diagnostics\\package -Architecture arm64 -OutputDirectory \\arm64-missing-diagnostics\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "arm64-excess-plugin-httpRoutes", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "wrong", + "field": "plugin.httpRoutes", + "fixtureInspection": { + "plugin": { + "enabled": true, + "activated": true, + "id": "gateway-isolation", + "imported": true, + "status": "loaded", + "httpRoutes": 2, + "origin": "bundled" + }, + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-excess-plugin-httpRoutes\\package -Architecture arm64 -OutputDirectory \\arm64-excess-plugin-httpRoutes\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "arm64-excess-httpRouteCount", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "wrong", + "field": "httpRouteCount", + "fixtureInspection": { + "plugin": { + "enabled": true, + "status": "loaded", + "imported": true, + "origin": "bundled", + "id": "gateway-isolation", + "httpRoutes": 1, + "activated": true + }, + "httpRouteCount": 2, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-excess-httpRouteCount\\package -Architecture arm64 -OutputDirectory \\arm64-excess-httpRouteCount\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + }, + { + "id": "arm64-missing-plugin-object", + "architecture": "arm64", + "outcome": "PASS", + "fixtureType": "synthetic-local-npm-package-and-runtime-inspection", + "mutation": "missing", + "field": "plugin", + "fixtureInspection": { + "httpRouteCount": 1, + "gatewayMethods": [], + "tools": [], + "services": [], + "diagnostics": [] + }, + "expected": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "actual": "The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape.", + "command": "& .\\scripts\\Build-Payload.ps1 -PackageDirectory \\arm64-missing-plugin-object\\package -Architecture arm64 -OutputDirectory \\arm64-missing-plugin-object\\payload", + "environmentBefore": "existing sentinels", + "environmentRestored": { + "npm_config_arch": true, + "npm_config_target_arch": true, + "OPENCLAW_STATE_DIR": true, + "CLAWCTL_GATEWAY_ISOLATION": true, + "RUNNER_TEMP": true + }, + "workingDirectoryRestored": true, + "runtimeObservations": [ + { + "command": "runtime-inspection", + "args": [ + "plugins", + "inspect", + "gateway-isolation", + "--runtime", + "--json" + ], + "nodeVersion": "v24.16.0", + "isolationMode": "disabled", + "stateDirectoryMatchesExpected": true + } + ], + "content": null, + "rejectedOutputAbsent": true + } + ] +} diff --git a/docs/validation/pr-28/payload-matrix.txt b/docs/validation/pr-28/payload-matrix.txt new file mode 100644 index 0000000..12a960c --- /dev/null +++ b/docs/validation/pr-28/payload-matrix.txt @@ -0,0 +1,625 @@ +Gateway isolation payload composition matrix +Fixture type: synthetic local npm packages and synthetic runtime-inspection responses. +Real operations: npm pack, npm install, scripts\Build-Payload.ps1, file copy, runtime command invocation, payload metadata. +This matrix tests the x64 and ARM64 composition contract with controlled runtime-inspection fixtures. +Production source: 9aa1df286c2b6fd59b4c101201ddae0ae6000324 +Evidence checkout HEAD: 2ce86d5bf4b54809ab1b4dcf5930db74d36b6d84 +Runtime: pwsh 7.6.6; Node.js v24.16.0; npm 11.13.0; Windows x64 host +Command: $env:PATH = ";" + $env:PATH +Command: pwsh -NoProfile -File .\scripts\validation\Test-GatewayIsolationPayloadMatrix.ps1 +All npm operations use an isolated generated cache and npm_config_offline=true; packages have no dependencies or lifecycle scripts. + +CASE x64-accepted-existing-environment PASS +Command (from \x64-accepted-existing-environment\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-accepted-existing-environment\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-accepted-existing-environment\package -Architecture x64 -OutputDirectory \x64-accepted-existing-environment\payload +Expected: accepted +Actual: accepted +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection, version-smoke +Shipping plugin files: 3; all source/payload SHA-256 hashes identical; index.test.js absent; metadata architecture: x64 +SHA256 index.js 7f98aed21bf7773f78a25c94c47d4f0f4efd6998c314201869307b798f25d1cd +SHA256 openclaw.plugin.json a1359e966e39d799f400a7250a4366cd1ef0b0bb8be954100285a2c1da298651 +SHA256 package.json 5a4c1c3966c782455e6505dedc9427c8096917f520ca6486f169fc4db5e50142 + +CASE x64-accepted-absent-environment PASS +Command (from \x64-accepted-absent-environment\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-accepted-absent-environment\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-accepted-absent-environment\package -Architecture x64 -OutputDirectory \x64-accepted-absent-environment\payload +Expected: accepted +Actual: accepted +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection, version-smoke +Shipping plugin files: 3; all source/payload SHA-256 hashes identical; index.test.js absent; metadata architecture: x64 +SHA256 index.js 7f98aed21bf7773f78a25c94c47d4f0f4efd6998c314201869307b798f25d1cd +SHA256 openclaw.plugin.json a1359e966e39d799f400a7250a4366cd1ef0b0bb8be954100285a2c1da298651 +SHA256 package.json 5a4c1c3966c782455e6505dedc9427c8096917f520ca6486f169fc4db5e50142 + +CASE x64-accepted-empty-environment PASS +Command (from \x64-accepted-empty-environment\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-accepted-empty-environment\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-accepted-empty-environment\package -Architecture x64 -OutputDirectory \x64-accepted-empty-environment\payload +Expected: accepted +Actual: accepted +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection, version-smoke +Shipping plugin files: 3; all source/payload SHA-256 hashes identical; index.test.js absent; metadata architecture: x64 +SHA256 index.js 7f98aed21bf7773f78a25c94c47d4f0f4efd6998c314201869307b798f25d1cd +SHA256 openclaw.plugin.json a1359e966e39d799f400a7250a4366cd1ef0b0bb8be954100285a2c1da298651 +SHA256 package.json 5a4c1c3966c782455e6505dedc9427c8096917f520ca6486f169fc4db5e50142 + +CASE x64-missing-extensions PASS +Command (from \x64-missing-extensions\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-missing-extensions\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-missing-extensions\package -Architecture x64 -OutputDirectory \x64-missing-extensions\payload +Expected: The pinned OpenClaw payload does not expose the supported bundled plugin directory: \x64-missing-extensions\runner\openclaw-stage-x64\node_modules\openclaw\dist\extensions +Actual: The pinned OpenClaw payload does not expose the supported bundled plugin directory: \x64-missing-extensions\runner\openclaw-stage-x64\node_modules\openclaw\dist\extensions +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: +Rejected application and payload metadata absent. + +CASE x64-preexisting-plugin PASS +Command (from \x64-preexisting-plugin\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-preexisting-plugin\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-preexisting-plugin\package -Architecture x64 -OutputDirectory \x64-preexisting-plugin\payload +Expected: The OpenClaw payload already contains a gateway-isolation plugin; refusing to replace upstream content. +Actual: The OpenClaw payload already contains a gateway-isolation plugin; refusing to replace upstream content. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: +Rejected application and payload metadata absent. + +CASE x64-inspection-exit-23 PASS +Command (from \x64-inspection-exit-23\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-inspection-exit-23\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-inspection-exit-23\package -Architecture x64 -OutputDirectory \x64-inspection-exit-23\payload +Expected: The selected OpenClaw payload cannot load the Gateway isolation plugin. Exit code: 23. +Actual: The selected OpenClaw payload cannot load the Gateway isolation plugin. Exit code: 23. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE x64-wrong-plugin-id PASS +Command (from \x64-wrong-plugin-id\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-wrong-plugin-id\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-wrong-plugin-id\package -Architecture x64 -OutputDirectory \x64-wrong-plugin-id\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE x64-missing-plugin-id PASS +Command (from \x64-missing-plugin-id\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-missing-plugin-id\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-missing-plugin-id\package -Architecture x64 -OutputDirectory \x64-missing-plugin-id\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE x64-wrong-plugin-origin PASS +Command (from \x64-wrong-plugin-origin\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-wrong-plugin-origin\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-wrong-plugin-origin\package -Architecture x64 -OutputDirectory \x64-wrong-plugin-origin\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE x64-missing-plugin-origin PASS +Command (from \x64-missing-plugin-origin\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-missing-plugin-origin\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-missing-plugin-origin\package -Architecture x64 -OutputDirectory \x64-missing-plugin-origin\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE x64-wrong-plugin-enabled PASS +Command (from \x64-wrong-plugin-enabled\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-wrong-plugin-enabled\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-wrong-plugin-enabled\package -Architecture x64 -OutputDirectory \x64-wrong-plugin-enabled\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE x64-missing-plugin-enabled PASS +Command (from \x64-missing-plugin-enabled\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-missing-plugin-enabled\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-missing-plugin-enabled\package -Architecture x64 -OutputDirectory \x64-missing-plugin-enabled\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE x64-wrong-plugin-activated PASS +Command (from \x64-wrong-plugin-activated\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-wrong-plugin-activated\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-wrong-plugin-activated\package -Architecture x64 -OutputDirectory \x64-wrong-plugin-activated\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE x64-missing-plugin-activated PASS +Command (from \x64-missing-plugin-activated\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-missing-plugin-activated\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-missing-plugin-activated\package -Architecture x64 -OutputDirectory \x64-missing-plugin-activated\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE x64-wrong-plugin-status PASS +Command (from \x64-wrong-plugin-status\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-wrong-plugin-status\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-wrong-plugin-status\package -Architecture x64 -OutputDirectory \x64-wrong-plugin-status\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE x64-missing-plugin-status PASS +Command (from \x64-missing-plugin-status\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-missing-plugin-status\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-missing-plugin-status\package -Architecture x64 -OutputDirectory \x64-missing-plugin-status\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE x64-wrong-plugin-imported PASS +Command (from \x64-wrong-plugin-imported\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-wrong-plugin-imported\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-wrong-plugin-imported\package -Architecture x64 -OutputDirectory \x64-wrong-plugin-imported\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE x64-missing-plugin-imported PASS +Command (from \x64-missing-plugin-imported\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-missing-plugin-imported\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-missing-plugin-imported\package -Architecture x64 -OutputDirectory \x64-missing-plugin-imported\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE x64-wrong-plugin-httpRoutes PASS +Command (from \x64-wrong-plugin-httpRoutes\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-wrong-plugin-httpRoutes\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-wrong-plugin-httpRoutes\package -Architecture x64 -OutputDirectory \x64-wrong-plugin-httpRoutes\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE x64-missing-plugin-httpRoutes PASS +Command (from \x64-missing-plugin-httpRoutes\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-missing-plugin-httpRoutes\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-missing-plugin-httpRoutes\package -Architecture x64 -OutputDirectory \x64-missing-plugin-httpRoutes\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE x64-wrong-httpRouteCount PASS +Command (from \x64-wrong-httpRouteCount\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-wrong-httpRouteCount\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-wrong-httpRouteCount\package -Architecture x64 -OutputDirectory \x64-wrong-httpRouteCount\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE x64-missing-httpRouteCount PASS +Command (from \x64-missing-httpRouteCount\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-missing-httpRouteCount\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-missing-httpRouteCount\package -Architecture x64 -OutputDirectory \x64-missing-httpRouteCount\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE x64-wrong-gatewayMethods PASS +Command (from \x64-wrong-gatewayMethods\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-wrong-gatewayMethods\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-wrong-gatewayMethods\package -Architecture x64 -OutputDirectory \x64-wrong-gatewayMethods\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE x64-missing-gatewayMethods PASS +Command (from \x64-missing-gatewayMethods\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-missing-gatewayMethods\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-missing-gatewayMethods\package -Architecture x64 -OutputDirectory \x64-missing-gatewayMethods\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE x64-wrong-tools PASS +Command (from \x64-wrong-tools\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-wrong-tools\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-wrong-tools\package -Architecture x64 -OutputDirectory \x64-wrong-tools\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE x64-missing-tools PASS +Command (from \x64-missing-tools\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-missing-tools\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-missing-tools\package -Architecture x64 -OutputDirectory \x64-missing-tools\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE x64-wrong-services PASS +Command (from \x64-wrong-services\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-wrong-services\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-wrong-services\package -Architecture x64 -OutputDirectory \x64-wrong-services\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE x64-missing-services PASS +Command (from \x64-missing-services\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-missing-services\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-missing-services\package -Architecture x64 -OutputDirectory \x64-missing-services\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE x64-wrong-diagnostics PASS +Command (from \x64-wrong-diagnostics\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-wrong-diagnostics\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-wrong-diagnostics\package -Architecture x64 -OutputDirectory \x64-wrong-diagnostics\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE x64-missing-diagnostics PASS +Command (from \x64-missing-diagnostics\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-missing-diagnostics\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-missing-diagnostics\package -Architecture x64 -OutputDirectory \x64-missing-diagnostics\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE x64-excess-plugin-httpRoutes PASS +Command (from \x64-excess-plugin-httpRoutes\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-excess-plugin-httpRoutes\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-excess-plugin-httpRoutes\package -Architecture x64 -OutputDirectory \x64-excess-plugin-httpRoutes\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE x64-excess-httpRouteCount PASS +Command (from \x64-excess-httpRouteCount\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-excess-httpRouteCount\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-excess-httpRouteCount\package -Architecture x64 -OutputDirectory \x64-excess-httpRouteCount\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE x64-missing-plugin-object PASS +Command (from \x64-missing-plugin-object\package-source): npm pack --offline --ignore-scripts --pack-destination \x64-missing-plugin-object\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \x64-missing-plugin-object\package -Architecture x64 -OutputDirectory \x64-missing-plugin-object\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE arm64-accepted-existing-environment PASS +Command (from \arm64-accepted-existing-environment\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-accepted-existing-environment\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-accepted-existing-environment\package -Architecture arm64 -OutputDirectory \arm64-accepted-existing-environment\payload +Expected: accepted +Actual: accepted +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Shipping plugin files: 3; all source/payload SHA-256 hashes identical; index.test.js absent; metadata architecture: arm64 +SHA256 index.js 7f98aed21bf7773f78a25c94c47d4f0f4efd6998c314201869307b798f25d1cd +SHA256 openclaw.plugin.json a1359e966e39d799f400a7250a4366cd1ef0b0bb8be954100285a2c1da298651 +SHA256 package.json 5a4c1c3966c782455e6505dedc9427c8096917f520ca6486f169fc4db5e50142 + +CASE arm64-accepted-absent-environment PASS +Command (from \arm64-accepted-absent-environment\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-accepted-absent-environment\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-accepted-absent-environment\package -Architecture arm64 -OutputDirectory \arm64-accepted-absent-environment\payload +Expected: accepted +Actual: accepted +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Shipping plugin files: 3; all source/payload SHA-256 hashes identical; index.test.js absent; metadata architecture: arm64 +SHA256 index.js 7f98aed21bf7773f78a25c94c47d4f0f4efd6998c314201869307b798f25d1cd +SHA256 openclaw.plugin.json a1359e966e39d799f400a7250a4366cd1ef0b0bb8be954100285a2c1da298651 +SHA256 package.json 5a4c1c3966c782455e6505dedc9427c8096917f520ca6486f169fc4db5e50142 + +CASE arm64-accepted-empty-environment PASS +Command (from \arm64-accepted-empty-environment\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-accepted-empty-environment\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-accepted-empty-environment\package -Architecture arm64 -OutputDirectory \arm64-accepted-empty-environment\payload +Expected: accepted +Actual: accepted +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Shipping plugin files: 3; all source/payload SHA-256 hashes identical; index.test.js absent; metadata architecture: arm64 +SHA256 index.js 7f98aed21bf7773f78a25c94c47d4f0f4efd6998c314201869307b798f25d1cd +SHA256 openclaw.plugin.json a1359e966e39d799f400a7250a4366cd1ef0b0bb8be954100285a2c1da298651 +SHA256 package.json 5a4c1c3966c782455e6505dedc9427c8096917f520ca6486f169fc4db5e50142 + +CASE arm64-missing-extensions PASS +Command (from \arm64-missing-extensions\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-missing-extensions\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-missing-extensions\package -Architecture arm64 -OutputDirectory \arm64-missing-extensions\payload +Expected: The pinned OpenClaw payload does not expose the supported bundled plugin directory: \arm64-missing-extensions\runner\openclaw-stage-arm64\node_modules\openclaw\dist\extensions +Actual: The pinned OpenClaw payload does not expose the supported bundled plugin directory: \arm64-missing-extensions\runner\openclaw-stage-arm64\node_modules\openclaw\dist\extensions +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: +Rejected application and payload metadata absent. + +CASE arm64-preexisting-plugin PASS +Command (from \arm64-preexisting-plugin\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-preexisting-plugin\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-preexisting-plugin\package -Architecture arm64 -OutputDirectory \arm64-preexisting-plugin\payload +Expected: The OpenClaw payload already contains a gateway-isolation plugin; refusing to replace upstream content. +Actual: The OpenClaw payload already contains a gateway-isolation plugin; refusing to replace upstream content. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: +Rejected application and payload metadata absent. + +CASE arm64-inspection-exit-23 PASS +Command (from \arm64-inspection-exit-23\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-inspection-exit-23\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-inspection-exit-23\package -Architecture arm64 -OutputDirectory \arm64-inspection-exit-23\payload +Expected: The selected OpenClaw payload cannot load the Gateway isolation plugin. Exit code: 23. +Actual: The selected OpenClaw payload cannot load the Gateway isolation plugin. Exit code: 23. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE arm64-wrong-plugin-id PASS +Command (from \arm64-wrong-plugin-id\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-wrong-plugin-id\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-wrong-plugin-id\package -Architecture arm64 -OutputDirectory \arm64-wrong-plugin-id\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE arm64-missing-plugin-id PASS +Command (from \arm64-missing-plugin-id\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-missing-plugin-id\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-missing-plugin-id\package -Architecture arm64 -OutputDirectory \arm64-missing-plugin-id\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE arm64-wrong-plugin-origin PASS +Command (from \arm64-wrong-plugin-origin\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-wrong-plugin-origin\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-wrong-plugin-origin\package -Architecture arm64 -OutputDirectory \arm64-wrong-plugin-origin\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE arm64-missing-plugin-origin PASS +Command (from \arm64-missing-plugin-origin\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-missing-plugin-origin\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-missing-plugin-origin\package -Architecture arm64 -OutputDirectory \arm64-missing-plugin-origin\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE arm64-wrong-plugin-enabled PASS +Command (from \arm64-wrong-plugin-enabled\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-wrong-plugin-enabled\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-wrong-plugin-enabled\package -Architecture arm64 -OutputDirectory \arm64-wrong-plugin-enabled\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE arm64-missing-plugin-enabled PASS +Command (from \arm64-missing-plugin-enabled\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-missing-plugin-enabled\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-missing-plugin-enabled\package -Architecture arm64 -OutputDirectory \arm64-missing-plugin-enabled\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE arm64-wrong-plugin-activated PASS +Command (from \arm64-wrong-plugin-activated\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-wrong-plugin-activated\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-wrong-plugin-activated\package -Architecture arm64 -OutputDirectory \arm64-wrong-plugin-activated\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE arm64-missing-plugin-activated PASS +Command (from \arm64-missing-plugin-activated\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-missing-plugin-activated\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-missing-plugin-activated\package -Architecture arm64 -OutputDirectory \arm64-missing-plugin-activated\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE arm64-wrong-plugin-status PASS +Command (from \arm64-wrong-plugin-status\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-wrong-plugin-status\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-wrong-plugin-status\package -Architecture arm64 -OutputDirectory \arm64-wrong-plugin-status\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE arm64-missing-plugin-status PASS +Command (from \arm64-missing-plugin-status\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-missing-plugin-status\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-missing-plugin-status\package -Architecture arm64 -OutputDirectory \arm64-missing-plugin-status\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE arm64-wrong-plugin-imported PASS +Command (from \arm64-wrong-plugin-imported\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-wrong-plugin-imported\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-wrong-plugin-imported\package -Architecture arm64 -OutputDirectory \arm64-wrong-plugin-imported\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE arm64-missing-plugin-imported PASS +Command (from \arm64-missing-plugin-imported\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-missing-plugin-imported\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-missing-plugin-imported\package -Architecture arm64 -OutputDirectory \arm64-missing-plugin-imported\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE arm64-wrong-plugin-httpRoutes PASS +Command (from \arm64-wrong-plugin-httpRoutes\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-wrong-plugin-httpRoutes\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-wrong-plugin-httpRoutes\package -Architecture arm64 -OutputDirectory \arm64-wrong-plugin-httpRoutes\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE arm64-missing-plugin-httpRoutes PASS +Command (from \arm64-missing-plugin-httpRoutes\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-missing-plugin-httpRoutes\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-missing-plugin-httpRoutes\package -Architecture arm64 -OutputDirectory \arm64-missing-plugin-httpRoutes\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE arm64-wrong-httpRouteCount PASS +Command (from \arm64-wrong-httpRouteCount\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-wrong-httpRouteCount\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-wrong-httpRouteCount\package -Architecture arm64 -OutputDirectory \arm64-wrong-httpRouteCount\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE arm64-missing-httpRouteCount PASS +Command (from \arm64-missing-httpRouteCount\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-missing-httpRouteCount\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-missing-httpRouteCount\package -Architecture arm64 -OutputDirectory \arm64-missing-httpRouteCount\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE arm64-wrong-gatewayMethods PASS +Command (from \arm64-wrong-gatewayMethods\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-wrong-gatewayMethods\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-wrong-gatewayMethods\package -Architecture arm64 -OutputDirectory \arm64-wrong-gatewayMethods\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE arm64-missing-gatewayMethods PASS +Command (from \arm64-missing-gatewayMethods\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-missing-gatewayMethods\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-missing-gatewayMethods\package -Architecture arm64 -OutputDirectory \arm64-missing-gatewayMethods\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE arm64-wrong-tools PASS +Command (from \arm64-wrong-tools\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-wrong-tools\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-wrong-tools\package -Architecture arm64 -OutputDirectory \arm64-wrong-tools\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE arm64-missing-tools PASS +Command (from \arm64-missing-tools\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-missing-tools\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-missing-tools\package -Architecture arm64 -OutputDirectory \arm64-missing-tools\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE arm64-wrong-services PASS +Command (from \arm64-wrong-services\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-wrong-services\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-wrong-services\package -Architecture arm64 -OutputDirectory \arm64-wrong-services\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE arm64-missing-services PASS +Command (from \arm64-missing-services\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-missing-services\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-missing-services\package -Architecture arm64 -OutputDirectory \arm64-missing-services\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE arm64-wrong-diagnostics PASS +Command (from \arm64-wrong-diagnostics\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-wrong-diagnostics\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-wrong-diagnostics\package -Architecture arm64 -OutputDirectory \arm64-wrong-diagnostics\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE arm64-missing-diagnostics PASS +Command (from \arm64-missing-diagnostics\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-missing-diagnostics\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-missing-diagnostics\package -Architecture arm64 -OutputDirectory \arm64-missing-diagnostics\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE arm64-excess-plugin-httpRoutes PASS +Command (from \arm64-excess-plugin-httpRoutes\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-excess-plugin-httpRoutes\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-excess-plugin-httpRoutes\package -Architecture arm64 -OutputDirectory \arm64-excess-plugin-httpRoutes\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE arm64-excess-httpRouteCount PASS +Command (from \arm64-excess-httpRouteCount\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-excess-httpRouteCount\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-excess-httpRouteCount\package -Architecture arm64 -OutputDirectory \arm64-excess-httpRouteCount\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +CASE arm64-missing-plugin-object PASS +Command (from \arm64-missing-plugin-object\package-source): npm pack --offline --ignore-scripts --pack-destination \arm64-missing-plugin-object\package --silent +Command: & .\scripts\Build-Payload.ps1 -PackageDirectory \arm64-missing-plugin-object\package -Architecture arm64 -OutputDirectory \arm64-missing-plugin-object\payload +Expected: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Actual: The selected OpenClaw payload did not load the Gateway isolation plugin with the required read-only runtime shape. +Environment restored: npm_config_arch, npm_config_target_arch, OPENCLAW_STATE_DIR, CLAWCTL_GATEWAY_ISOLATION, RUNNER_TEMP; working directory restored: true +Runtime calls: runtime-inspection +Rejected application and payload metadata absent. + +RESULT: 66 passed; 6 accepted; 60 rejected as expected; 0 failed. +Harness environment and working directory restored. Generated TestRoot removed. No preexisting directories were removed. diff --git a/docs/validation/pr-28/plugin-tests.tap b/docs/validation/pr-28/plugin-tests.tap new file mode 100644 index 0000000..1d0ce22 --- /dev/null +++ b/docs/validation/pr-28/plugin-tests.tap @@ -0,0 +1,136 @@ +TAP version 13 +# Subtest: accepts only the exact launcher isolation values +ok 1 - accepts only the exact launcher isolation values + --- + duration_ms: 1.4354 + type: 'test' + ... +# Subtest: renders the exact enabled read-only status +ok 2 - renders the exact enabled read-only status + --- + duration_ms: 0.4472 + type: 'test' + ... +# Subtest: renders the exact disabled read-only status +ok 3 - renders the exact disabled read-only status + --- + duration_ms: 0.1477 + type: 'test' + ... +# Subtest: registers one read-only Control tab and one authenticated sandbox route +ok 4 - registers one read-only Control tab and one authenticated sandbox route + --- + duration_ms: 1.2608 + type: 'test' + ... +# Subtest: reads "enabled" exactly once and keeps every response stable +ok 5 - reads "enabled" exactly once and keeps every response stable + --- + duration_ms: 0.4925 + type: 'test' + ... +# Subtest: reads "disabled" exactly once and keeps every response stable +ok 6 - reads "disabled" exactly once and keeps every response stable + --- + duration_ms: 0.6573 + type: 'test' + ... +# Subtest: reads missing exactly once and keeps every response stable +ok 7 - reads missing exactly once and keeps every response stable + --- + duration_ms: 0.4355 + type: 'test' + ... +# Subtest: reads "" exactly once and keeps every response stable +ok 8 - reads "" exactly once and keeps every response stable + --- + duration_ms: 0.2525 + type: 'test' + ... +# Subtest: reads "invalid" exactly once and keeps every response stable +ok 9 - reads "invalid" exactly once and keeps every response stable + --- + duration_ms: 0.3347 + type: 'test' + ... +# Subtest: reads "ENABLED" exactly once and keeps every response stable +ok 10 - reads "ENABLED" exactly once and keeps every response stable + --- + duration_ms: 0.3578 + type: 'test' + ... +# Subtest: reads " enabled " exactly once and keeps every response stable +ok 11 - reads " enabled " exactly once and keeps every response stable + --- + duration_ms: 0.2516 + type: 'test' + ... +# Subtest: fails closed for missing with no status, command, or copy control +ok 12 - fails closed for missing with no status, command, or copy control + --- + duration_ms: 1.206 + type: 'test' + ... +# Subtest: renderer rejects missing +ok 13 - renderer rejects missing + --- + duration_ms: 0.7585 + type: 'test' + ... +# Subtest: fails closed for "" with no status, command, or copy control +ok 14 - fails closed for "" with no status, command, or copy control + --- + duration_ms: 0.2382 + type: 'test' + ... +# Subtest: renderer rejects "" +ok 15 - renderer rejects "" + --- + duration_ms: 0.08 + type: 'test' + ... +# Subtest: fails closed for "invalid" with no status, command, or copy control +ok 16 - fails closed for "invalid" with no status, command, or copy control + --- + duration_ms: 0.1374 + type: 'test' + ... +# Subtest: renderer rejects "invalid" +ok 17 - renderer rejects "invalid" + --- + duration_ms: 0.0748 + type: 'test' + ... +# Subtest: fails closed for "ENABLED" with no status, command, or copy control +ok 18 - fails closed for "ENABLED" with no status, command, or copy control + --- + duration_ms: 0.1684 + type: 'test' + ... +# Subtest: renderer rejects "ENABLED" +ok 19 - renderer rejects "ENABLED" + --- + duration_ms: 0.1728 + type: 'test' + ... +# Subtest: fails closed for " enabled " with no status, command, or copy control +ok 20 - fails closed for " enabled " with no status, command, or copy control + --- + duration_ms: 0.0809 + type: 'test' + ... +# Subtest: renderer rejects " enabled " +ok 21 - renderer rejects " enabled " + --- + duration_ms: 0.0741 + type: 'test' + ... +1..21 +# tests 21 +# suites 0 +# pass 21 +# fail 0 +# cancelled 0 +# skipped 0 +# todo 0 +# duration_ms 172.9231 diff --git a/docs/validation/pr-28/policy-checks.txt b/docs/validation/pr-28/policy-checks.txt new file mode 100644 index 0000000..7a9a338 --- /dev/null +++ b/docs/validation/pr-28/policy-checks.txt @@ -0,0 +1,4 @@ +Authorized official signing for OpenClaw commit 0965053fe6b9341776df147a6934b7485c60b5ca and Gateway MSIX version 0.1.1.0. +Authorized official signing for OpenClaw commit 0965053fe6b9341776df147a6934b7485c60b5ca and Gateway MSIX version 0.1.1.0. +Gateway MSIX signing policy tests passed. +Workflow package-version tests passed. diff --git a/docs/validation/pr-28/runtime-checks.txt b/docs/validation/pr-28/runtime-checks.txt new file mode 100644 index 0000000..313b639 --- /dev/null +++ b/docs/validation/pr-28/runtime-checks.txt @@ -0,0 +1,8 @@ +PASS launcher-disabled: 10 HTTP assertions; GET/HEAD auth denial and valid auth; response hardening headers; write-method probes leave status unchanged; visible sidebar tab; sandboxed iframe; exact status/tone/inverse CLI; Copy writes exact command to clipboard +PASS fixture-enabled: 10 HTTP assertions; GET/HEAD auth denial and valid auth; response hardening headers; write-method probes leave status unchanged; visible sidebar tab; sandboxed iframe; exact status/tone/inverse CLI; Copy writes exact command to clipboard +PASS fixture-disabled: 10 HTTP assertions; GET/HEAD auth denial and valid auth; response hardening headers; write-method probes leave status unchanged; visible sidebar tab; sandboxed iframe; exact status/tone/inverse CLI; Copy writes exact command to clipboard +PASS fixture-missing: 10 HTTP assertions; GET/HEAD auth denial and valid auth; response hardening headers; write-method probes leave status unchanged; HTTP 503 fails closed; Control UI error without stale status or command +PASS fixture-invalid: 10 HTTP assertions; GET/HEAD auth denial and valid auth; response hardening headers; write-method probes leave status unchanged; HTTP 503 fails closed; Control UI error without stale status or command +PASS fixture-uppercase: 10 HTTP assertions; GET/HEAD auth denial and valid auth; response hardening headers; write-method probes leave status unchanged; HTTP 503 fails closed; Control UI error without stale status or command +PASS fixture-empty: 10 HTTP assertions; GET/HEAD auth denial and valid auth; response hardening headers; write-method probes leave status unchanged; HTTP 503 fails closed; Control UI error without stale status or command +PASS fixture-whitespace: 10 HTTP assertions; GET/HEAD auth denial and valid auth; response hardening headers; write-method probes leave status unchanged; HTTP 503 fails closed; Control UI error without stale status or command diff --git a/docs/validation/pr-28/runtime-matrix.json b/docs/validation/pr-28/runtime-matrix.json new file mode 100644 index 0000000..e97d3f0 --- /dev/null +++ b/docs/validation/pr-28/runtime-matrix.json @@ -0,0 +1,686 @@ +{ + "source": "9aa1df286c2b6fd59b4c101201ddae0ae6000324", + "build": { + "version": "2026.8.2", + "commit": "0965053fe6b9341776df147a6934b7485c60b5ca", + "builtAt": "2026-09-11T00:24:39.157Z", + "buildId": "2026.8.2-release-0965053fe6b9-2026-09-11T00-24-39.157Z" + }, + "environment": { + "os": "Windows x64", + "node": "v24.16.0", + "layout": "expanded application", + "port": 19428, + "browser": "152.0.4191.66" + }, + "cases": [ + { + "id": "launcher-disabled", + "launch": "NativeAOT launcher", + "input": "enabled", + "expectedMode": "disabled", + "http": [ + { + "method": "GET", + "access": "anonymous", + "status": 401 + }, + { + "method": "GET", + "access": "wrong-token", + "status": 401 + }, + { + "method": "GET", + "access": "authenticated", + "status": 200 + }, + { + "method": "HEAD", + "access": "anonymous", + "status": 401 + }, + { + "method": "HEAD", + "access": "wrong-token", + "status": 401 + }, + { + "method": "HEAD", + "access": "authenticated", + "status": 200 + }, + { + "method": "POST", + "access": "authenticated", + "status": 200, + "effect": "same read-only response" + }, + { + "method": "PUT", + "access": "authenticated", + "status": 200, + "effect": "same read-only response" + }, + { + "method": "PATCH", + "access": "authenticated", + "status": 200, + "effect": "same read-only response" + }, + { + "method": "DELETE", + "access": "authenticated", + "status": 200, + "effect": "same read-only response" + } + ], + "assertions": [ + "GET/HEAD auth denial and valid auth", + "response hardening headers", + "write-method probes leave status unchanged", + "visible sidebar tab", + "sandboxed iframe", + "exact status/tone/inverse CLI", + "Copy writes exact command to clipboard" + ], + "ui": { + "state": "Disabled", + "tone": "warn", + "command": "clawctl gateway-isolation enable", + "sandbox": "allow-scripts", + "copy": "exact clipboard text verified" + }, + "hello": { + "version": "2026.8.2", + "buildId": "2026.8.2-release-0965053fe6b9-2026-09-11T00-24-39.157Z", + "controlUiBuildSource": "bundled" + }, + "screenshot": "launcher-disabled.png", + "passed": true + }, + { + "id": "fixture-enabled", + "launch": "Node with launcher-input fixture", + "input": "enabled", + "expectedMode": "enabled", + "http": [ + { + "method": "GET", + "access": "anonymous", + "status": 401 + }, + { + "method": "GET", + "access": "wrong-token", + "status": 401 + }, + { + "method": "GET", + "access": "authenticated", + "status": 200 + }, + { + "method": "HEAD", + "access": "anonymous", + "status": 401 + }, + { + "method": "HEAD", + "access": "wrong-token", + "status": 401 + }, + { + "method": "HEAD", + "access": "authenticated", + "status": 200 + }, + { + "method": "POST", + "access": "authenticated", + "status": 200, + "effect": "same read-only response" + }, + { + "method": "PUT", + "access": "authenticated", + "status": 200, + "effect": "same read-only response" + }, + { + "method": "PATCH", + "access": "authenticated", + "status": 200, + "effect": "same read-only response" + }, + { + "method": "DELETE", + "access": "authenticated", + "status": 200, + "effect": "same read-only response" + } + ], + "assertions": [ + "GET/HEAD auth denial and valid auth", + "response hardening headers", + "write-method probes leave status unchanged", + "visible sidebar tab", + "sandboxed iframe", + "exact status/tone/inverse CLI", + "Copy writes exact command to clipboard" + ], + "ui": { + "state": "Enabled", + "tone": "ok", + "command": "clawctl gateway-isolation disable", + "sandbox": "allow-scripts", + "copy": "exact clipboard text verified" + }, + "hello": { + "version": "2026.8.2", + "buildId": "2026.8.2-release-0965053fe6b9-2026-09-11T00-24-39.157Z", + "controlUiBuildSource": "bundled" + }, + "screenshot": "fixture-enabled.png", + "passed": true + }, + { + "id": "fixture-disabled", + "launch": "Node with launcher-input fixture", + "input": "disabled", + "expectedMode": "disabled", + "http": [ + { + "method": "GET", + "access": "anonymous", + "status": 401 + }, + { + "method": "GET", + "access": "wrong-token", + "status": 401 + }, + { + "method": "GET", + "access": "authenticated", + "status": 200 + }, + { + "method": "HEAD", + "access": "anonymous", + "status": 401 + }, + { + "method": "HEAD", + "access": "wrong-token", + "status": 401 + }, + { + "method": "HEAD", + "access": "authenticated", + "status": 200 + }, + { + "method": "POST", + "access": "authenticated", + "status": 200, + "effect": "same read-only response" + }, + { + "method": "PUT", + "access": "authenticated", + "status": 200, + "effect": "same read-only response" + }, + { + "method": "PATCH", + "access": "authenticated", + "status": 200, + "effect": "same read-only response" + }, + { + "method": "DELETE", + "access": "authenticated", + "status": 200, + "effect": "same read-only response" + } + ], + "assertions": [ + "GET/HEAD auth denial and valid auth", + "response hardening headers", + "write-method probes leave status unchanged", + "visible sidebar tab", + "sandboxed iframe", + "exact status/tone/inverse CLI", + "Copy writes exact command to clipboard" + ], + "ui": { + "state": "Disabled", + "tone": "warn", + "command": "clawctl gateway-isolation enable", + "sandbox": "allow-scripts", + "copy": "exact clipboard text verified" + }, + "hello": { + "version": "2026.8.2", + "buildId": "2026.8.2-release-0965053fe6b9-2026-09-11T00-24-39.157Z", + "controlUiBuildSource": "bundled" + }, + "screenshot": "fixture-disabled.png", + "passed": true + }, + { + "id": "fixture-missing", + "launch": "Node with launcher-input fixture", + "input": "(missing)", + "expectedMode": null, + "http": [ + { + "method": "GET", + "access": "anonymous", + "status": 401 + }, + { + "method": "GET", + "access": "wrong-token", + "status": 401 + }, + { + "method": "GET", + "access": "authenticated", + "status": 503 + }, + { + "method": "HEAD", + "access": "anonymous", + "status": 401 + }, + { + "method": "HEAD", + "access": "wrong-token", + "status": 401 + }, + { + "method": "HEAD", + "access": "authenticated", + "status": 503 + }, + { + "method": "POST", + "access": "authenticated", + "status": 503, + "effect": "same read-only response" + }, + { + "method": "PUT", + "access": "authenticated", + "status": 503, + "effect": "same read-only response" + }, + { + "method": "PATCH", + "access": "authenticated", + "status": 503, + "effect": "same read-only response" + }, + { + "method": "DELETE", + "access": "authenticated", + "status": 503, + "effect": "same read-only response" + } + ], + "assertions": [ + "GET/HEAD auth denial and valid auth", + "response hardening headers", + "write-method probes leave status unchanged", + "HTTP 503 fails closed", + "Control UI error without stale status or command" + ], + "ui": { + "status": "invalid launcher mode message", + "staleStatus": false, + "sandbox": "allow-scripts" + }, + "hello": { + "version": "2026.8.2", + "buildId": "2026.8.2-release-0965053fe6b9-2026-09-11T00-24-39.157Z", + "controlUiBuildSource": "bundled" + }, + "screenshot": "fixture-missing.png", + "passed": true + }, + { + "id": "fixture-invalid", + "launch": "Node with launcher-input fixture", + "input": "invalid", + "expectedMode": null, + "http": [ + { + "method": "GET", + "access": "anonymous", + "status": 401 + }, + { + "method": "GET", + "access": "wrong-token", + "status": 401 + }, + { + "method": "GET", + "access": "authenticated", + "status": 503 + }, + { + "method": "HEAD", + "access": "anonymous", + "status": 401 + }, + { + "method": "HEAD", + "access": "wrong-token", + "status": 401 + }, + { + "method": "HEAD", + "access": "authenticated", + "status": 503 + }, + { + "method": "POST", + "access": "authenticated", + "status": 503, + "effect": "same read-only response" + }, + { + "method": "PUT", + "access": "authenticated", + "status": 503, + "effect": "same read-only response" + }, + { + "method": "PATCH", + "access": "authenticated", + "status": 503, + "effect": "same read-only response" + }, + { + "method": "DELETE", + "access": "authenticated", + "status": 503, + "effect": "same read-only response" + } + ], + "assertions": [ + "GET/HEAD auth denial and valid auth", + "response hardening headers", + "write-method probes leave status unchanged", + "HTTP 503 fails closed", + "Control UI error without stale status or command" + ], + "ui": { + "status": "invalid launcher mode message", + "staleStatus": false, + "sandbox": "allow-scripts" + }, + "hello": { + "version": "2026.8.2", + "buildId": "2026.8.2-release-0965053fe6b9-2026-09-11T00-24-39.157Z", + "controlUiBuildSource": "bundled" + }, + "screenshot": "fixture-invalid.png", + "passed": true + }, + { + "id": "fixture-uppercase", + "launch": "Node with launcher-input fixture", + "input": "ENABLED", + "expectedMode": null, + "http": [ + { + "method": "GET", + "access": "anonymous", + "status": 401 + }, + { + "method": "GET", + "access": "wrong-token", + "status": 401 + }, + { + "method": "GET", + "access": "authenticated", + "status": 503 + }, + { + "method": "HEAD", + "access": "anonymous", + "status": 401 + }, + { + "method": "HEAD", + "access": "wrong-token", + "status": 401 + }, + { + "method": "HEAD", + "access": "authenticated", + "status": 503 + }, + { + "method": "POST", + "access": "authenticated", + "status": 503, + "effect": "same read-only response" + }, + { + "method": "PUT", + "access": "authenticated", + "status": 503, + "effect": "same read-only response" + }, + { + "method": "PATCH", + "access": "authenticated", + "status": 503, + "effect": "same read-only response" + }, + { + "method": "DELETE", + "access": "authenticated", + "status": 503, + "effect": "same read-only response" + } + ], + "assertions": [ + "GET/HEAD auth denial and valid auth", + "response hardening headers", + "write-method probes leave status unchanged", + "HTTP 503 fails closed", + "Control UI error without stale status or command" + ], + "ui": { + "status": "invalid launcher mode message", + "staleStatus": false, + "sandbox": "allow-scripts" + }, + "hello": { + "version": "2026.8.2", + "buildId": "2026.8.2-release-0965053fe6b9-2026-09-11T00-24-39.157Z", + "controlUiBuildSource": "bundled" + }, + "screenshot": "fixture-uppercase.png", + "passed": true + }, + { + "id": "fixture-empty", + "launch": "Node with launcher-input fixture", + "input": "", + "expectedMode": null, + "http": [ + { + "method": "GET", + "access": "anonymous", + "status": 401 + }, + { + "method": "GET", + "access": "wrong-token", + "status": 401 + }, + { + "method": "GET", + "access": "authenticated", + "status": 503 + }, + { + "method": "HEAD", + "access": "anonymous", + "status": 401 + }, + { + "method": "HEAD", + "access": "wrong-token", + "status": 401 + }, + { + "method": "HEAD", + "access": "authenticated", + "status": 503 + }, + { + "method": "POST", + "access": "authenticated", + "status": 503, + "effect": "same read-only response" + }, + { + "method": "PUT", + "access": "authenticated", + "status": 503, + "effect": "same read-only response" + }, + { + "method": "PATCH", + "access": "authenticated", + "status": 503, + "effect": "same read-only response" + }, + { + "method": "DELETE", + "access": "authenticated", + "status": 503, + "effect": "same read-only response" + } + ], + "assertions": [ + "GET/HEAD auth denial and valid auth", + "response hardening headers", + "write-method probes leave status unchanged", + "HTTP 503 fails closed", + "Control UI error without stale status or command" + ], + "ui": { + "status": "invalid launcher mode message", + "staleStatus": false, + "sandbox": "allow-scripts" + }, + "hello": { + "version": "2026.8.2", + "buildId": "2026.8.2-release-0965053fe6b9-2026-09-11T00-24-39.157Z", + "controlUiBuildSource": "bundled" + }, + "screenshot": "fixture-empty.png", + "passed": true + }, + { + "id": "fixture-whitespace", + "launch": "Node with launcher-input fixture", + "input": " enabled ", + "expectedMode": null, + "http": [ + { + "method": "GET", + "access": "anonymous", + "status": 401 + }, + { + "method": "GET", + "access": "wrong-token", + "status": 401 + }, + { + "method": "GET", + "access": "authenticated", + "status": 503 + }, + { + "method": "HEAD", + "access": "anonymous", + "status": 401 + }, + { + "method": "HEAD", + "access": "wrong-token", + "status": 401 + }, + { + "method": "HEAD", + "access": "authenticated", + "status": 503 + }, + { + "method": "POST", + "access": "authenticated", + "status": 503, + "effect": "same read-only response" + }, + { + "method": "PUT", + "access": "authenticated", + "status": 503, + "effect": "same read-only response" + }, + { + "method": "PATCH", + "access": "authenticated", + "status": 503, + "effect": "same read-only response" + }, + { + "method": "DELETE", + "access": "authenticated", + "status": 503, + "effect": "same read-only response" + } + ], + "assertions": [ + "GET/HEAD auth denial and valid auth", + "response hardening headers", + "write-method probes leave status unchanged", + "HTTP 503 fails closed", + "Control UI error without stale status or command" + ], + "ui": { + "status": "invalid launcher mode message", + "staleStatus": false, + "sandbox": "allow-scripts" + }, + "hello": { + "version": "2026.8.2", + "buildId": "2026.8.2-release-0965053fe6b9-2026-09-11T00-24-39.157Z", + "controlUiBuildSource": "bundled" + }, + "screenshot": "fixture-whitespace.png", + "passed": true + } + ], + "sha256": { + "launcher": "a85063b9caedf346444db4da72fa3ab62a0bf913b6a4b6056b8d0b6d4a476def", + "node": "b3094d0b49f9ad602262a9921551737bb97637c05dd357a06ae98188d7290aa3", + "runtimeEntry": "4f4d29770da4f86dbd0e07cbd4d46deab785905dd89ac719033fcfd866fb5d17", + "plugin": "7f98aed21bf7773f78a25c94c47d4f0f4efd6998c314201869307b798f25d1cd" + }, + "completedAt": "2026-09-11T03:52:53.074Z", + "cleanup": "All validation-owned process trees stopped; port verified free" +} diff --git a/docs/validation/pr-28/runtime-registration.json b/docs/validation/pr-28/runtime-registration.json new file mode 100644 index 0000000..dd92ab4 --- /dev/null +++ b/docs/validation/pr-28/runtime-registration.json @@ -0,0 +1,28 @@ +{ + "command": "openclaw.exe plugins inspect gateway-isolation --runtime --json", + "plugin": { + "id": "gateway-isolation", + "origin": "bundled", + "enabled": true, + "activated": true, + "status": "loaded", + "imported": true, + "httpRoutes": 1 + }, + "capabilities": { + "httpRouteCount": 1, + "gatewayMethods": 0, + "tools": 0, + "services": 0, + "diagnostics": 0, + "typedHooks": 0, + "customHooks": 0, + "commands": 0, + "cliCommands": 0, + "gatewayDiscoveryServices": 0, + "mcpServers": 0, + "lspServers": 0 + }, + "shape": "non-capability", + "capabilityMode": "none" +} diff --git a/plugins/gateway-isolation/index.test.js b/plugins/gateway-isolation/index.test.js index 2e5c09d..c8f3583 100644 --- a/plugins/gateway-isolation/index.test.js +++ b/plugins/gateway-isolation/index.test.js @@ -113,30 +113,50 @@ test("registers one read-only Control tab and one authenticated sandbox route", assert.match(response.body, />Enabled { - const env = { CLAWCTL_GATEWAY_ISOLATION: "enabled" }; - const plugin = createGatewayIsolationPlugin(env); - env.CLAWCTL_GATEWAY_ISOLATION = "disabled"; - const routes = []; - plugin.register({ - session: { - controls: { - registerControlUiDescriptor() {}, +for (const initial of ["enabled", "disabled", undefined, "", "invalid", "ENABLED", " enabled "]) { + test(`reads ${JSON.stringify(initial) ?? "missing"} exactly once and keeps every response stable`, () => { + let reads = 0; + let value = initial; + const plugin = createGatewayIsolationPlugin({ + get CLAWCTL_GATEWAY_ISOLATION() { + reads++; + return value; }, - }, - registerHttpRoute(route) { - routes.push(route); - }, - }); + }); + assert.equal(reads, 1); + value = initial === "enabled" ? "disabled" : "enabled"; + const routes = []; + plugin.register({ + session: { controls: { registerControlUiDescriptor() {} } }, + registerHttpRoute(route) { + routes.push(route); + }, + }); - const response = invokeRoute(routes[0]); - assert.match(response.body, />EnabledDisabledEnabledDisabled { - const { routes } = registerPlugin("invalid"); - const response = invokeRoute(routes[0]); - assert.equal(response.statusCode, 503); - assert.match(response.body, /did not provide a valid Gateway isolation mode/); -}); +for (const mode of [undefined, "", "invalid", "ENABLED", " enabled "]) { + const label = JSON.stringify(mode) ?? "missing"; + test(`fails closed for ${label} with no status, command, or copy control`, () => { + const { routes } = registerPlugin(mode); + const response = invokeRoute(routes[0]); + assert.equal(response.statusCode, 503); + assert.match(response.body, /did not provide a valid Gateway isolation mode/); + assert.doesNotMatch(response.body, /status--(?:ok|warn)|isolation-command| + +

+ ${THEME_BRIDGE_SCRIPT} + `; } diff --git a/plugins/gateway-isolation/index.test.js b/plugins/gateway-isolation/index.test.js index 2907b4d..0475aac 100644 --- a/plugins/gateway-isolation/index.test.js +++ b/plugins/gateway-isolation/index.test.js @@ -101,11 +101,13 @@ for (const expected of [ mode: "enabled", status: "Enabled", tone: "status--ok", + command: "clawctl gateway-isolation disable", }, { mode: "disabled", status: "Disabled", tone: "status--warn", + command: "clawctl gateway-isolation enable", }, ]) { test(`renders the exact ${expected.mode} read-only status`, () => { @@ -114,8 +116,13 @@ for (const expected of [ assert.match(html, /Gateway Isolation/); assert.match(html, new RegExp(`>${expected.status}<`)); assert.match(html, new RegExp(expected.tone)); + assert.match(html, /Change with CLI/); + assert.match(html, /Run from the signed-in user session on the Gateway host\./); + assert.match(html, new RegExp(expected.command)); + assert.match(html, /aria-label="Copy command"/); + assert.match(html, /Copy the selected command manually\./); + assert.match(html, /copied = document\.execCommand\("copy"\)/); assert.match(html, /openclaw:widget-theme/); - assert.doesNotMatch(html, /Change with CLI|clipboard|copy-command|clawctl gateway-isolation/); assert.doesNotMatch(html, /next manual Gateway restart/i); }); } diff --git a/scripts/validation/gateway-isolation-runtime.mjs b/scripts/validation/gateway-isolation-runtime.mjs index 27d2444..65185db 100644 --- a/scripts/validation/gateway-isolation-runtime.mjs +++ b/scripts/validation/gateway-isolation-runtime.mjs @@ -286,7 +286,7 @@ async function themeSnapshot(page, frame, expectedTheme, expectedMode) { assert.equal(plugin.h1, "Windows Launcher"); assert.equal(plugin.status, "Disabled"); assert.equal(plugin.row, "Gateway Isolation"); - assert.equal(plugin.mutationGuidance, false); + assert.equal(plugin.mutationGuidance, true); return { hostTheme: expectedTheme, mode: expectedMode, tokens: plugin.values }; } @@ -351,6 +351,15 @@ const context = await browser.newContext({ const page = await context.newPage(); let hello; const rpcErrors = []; +const browserErrors = []; +page.on("console", (message) => { + if (message.type() === "error") { + browserErrors.push(`console: ${message.text()}`); + } +}); +page.on("pageerror", (error) => { + browserErrors.push(`page: ${error.message}`); +}); page.on("websocket", (socket) => socket.on("framereceived", ({ payload }) => { const frame = JSON.parse(payload.toString()); @@ -418,10 +427,9 @@ try { if (method === "GET") { canonicalBody = body; assert.match(body, />Disabled Boolean(hello), "authenticated hello-ok"); + try { + await until(() => Boolean(hello), "authenticated hello-ok"); + } catch (error) { + throw new Error( + `${error.message}\nGateway logs:\n${logs}\nBrowser errors:\n${browserErrors.join("\n")}`, + ); + } assert.equal(hello.buildId, build.buildId); assert.equal(hello.controlUiBuildSource, "bundled"); await pause(2_000); @@ -581,7 +595,7 @@ try { sameFrameAcrossLiveThemeSwitches: true, }, themes: themeResults, - unsupportedCliGuidanceAbsent: true, + cliControlPresent: true, passed: true, }; const json = `${JSON.stringify(result, null, 2)}\n`; From dbb141fd6c65c1915d3327f95d75659f4ea91edf Mon Sep 17 00:00:00 2001 From: Chaz Gordish Date: Mon, 14 Sep 2026 16:23:57 -0700 Subject: [PATCH 33/35] Add final themed launcher proof Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../themed-windows-launcher/README.md | 23 ++ .../themed-windows-launcher/claw-dark.png | Bin 0 -> 95738 bytes .../themed-windows-launcher/claw-light.png | Bin 0 -> 98912 bytes .../themed-windows-launcher/custom-dark.png | Bin 0 -> 88416 bytes .../themed-windows-launcher/custom-light.png | Bin 0 -> 91593 bytes .../runtime-results.json | 201 ++++++++++++++++++ .../validation/gateway-isolation-runtime.mjs | 28 +-- 7 files changed, 240 insertions(+), 12 deletions(-) create mode 100644 docs/validation/themed-windows-launcher/README.md create mode 100644 docs/validation/themed-windows-launcher/claw-dark.png create mode 100644 docs/validation/themed-windows-launcher/claw-light.png create mode 100644 docs/validation/themed-windows-launcher/custom-dark.png create mode 100644 docs/validation/themed-windows-launcher/custom-light.png create mode 100644 docs/validation/themed-windows-launcher/runtime-results.json diff --git a/docs/validation/themed-windows-launcher/README.md b/docs/validation/themed-windows-launcher/README.md new file mode 100644 index 0000000..91db6ba --- /dev/null +++ b/docs/validation/themed-windows-launcher/README.md @@ -0,0 +1,23 @@ +# Themed Windows Launcher validation + +This proof was generated from packaging implementation commit +`5ef558a28f182fd002478db89b8844c8ac4747fc` against the merged generic theme +forwarding in `openclaw/openclaw` commit +`f65ecca89667b8a55d9f88d76c487f0a0ab11da8`. + +The four screenshots cover the default Claw dark and light themes plus +distinctive imported custom dark and light themes. Each run uses the same +authenticated Gateway process and iframe. The result metadata verifies: + +- `Windows Launcher` in the Control UI sidebar, tab, and page heading. +- `Gateway Isolation` as the launcher status row. +- The CLI command and Copy control remain present. +- Exact semantic color, font, and radius forwarding. +- One authenticated iframe request with no navigation or reload during live + theme changes. +- `sandbox="allow-scripts"` without `allow-same-origin`. +- Read-only HTTP behavior and fail-closed launcher mode handling. + +`runtime-results.json` contains sanitized environment details, source commits, +artifact hashes, route checks, frame checks, and semantic token results. This +is expanded-layout browser proof, not installed MSIX proof. diff --git a/docs/validation/themed-windows-launcher/claw-dark.png b/docs/validation/themed-windows-launcher/claw-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..b62b10a1f135772d4104dc7640a06845ccda039b GIT binary patch literal 95738 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 literal 0 HcmV?d00001 diff --git a/docs/validation/themed-windows-launcher/custom-light.png b/docs/validation/themed-windows-launcher/custom-light.png new file mode 100644 index 0000000000000000000000000000000000000000..00d0f38c7f06b36f33915aafb411a87e5fc6a146 GIT binary patch literal 91593 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%0Dtw6_GM!O^5UUYEFv?T1)y{}wE2YfVp);H6|p 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 { }); try { - await until(async () => { - if (child.exitCode !== null) { - throw new Error(`Gateway exited with ${child.exitCode}: ${logs}`); - } - try { - return (await fetch(`${base}/healthz`)).ok; - } catch (error) { - if (error.cause?.code !== "ECONNREFUSED") { - throw error; + try { + await until(async () => { + if (child.exitCode !== null) { + throw new Error(`Gateway exited with ${child.exitCode}: ${logs}`); } - return false; - } - }, "Gateway readiness"); + try { + return (await fetch(`${base}/healthz`)).ok; + } catch (error) { + if (error.cause?.code !== "ECONNREFUSED") { + throw error; + } + return false; + } + }, "Gateway readiness"); + } catch (error) { + throw new Error(`${error.message}\nGateway logs:\n${logs}`); + } let canonicalBody; const httpResults = []; From 13cded52d4ba7685894fa68d401956481b7e5bc9 Mon Sep 17 00:00:00 2001 From: Chaz Gordish Date: Mon, 14 Sep 2026 16:27:21 -0700 Subject: [PATCH 34/35] Revert "Restore launcher CLI control" This reverts commit 5ef558a28f182fd002478db89b8844c8ac4747fc. --- plugins/gateway-isolation/index.js | 81 ------------------- plugins/gateway-isolation/index.test.js | 9 +-- .../validation/gateway-isolation-runtime.mjs | 28 ++----- 3 files changed, 8 insertions(+), 110 deletions(-) diff --git a/plugins/gateway-isolation/index.js b/plugins/gateway-isolation/index.js index 6f457d0..e1d9586 100644 --- a/plugins/gateway-isolation/index.js +++ b/plugins/gateway-isolation/index.js @@ -64,7 +64,6 @@ export function renderGatewayIsolationPage(mode) { const enabled = mode === "enabled"; const status = enabled ? "Enabled" : "Disabled"; - const command = `clawctl gateway-isolation ${enabled ? "disable" : "enable"}`; const tone = enabled ? "ok" : "warn"; return ` @@ -148,14 +147,7 @@ export function renderGatewayIsolationPage(mode) { align-items: center; padding: 18px; } - .settings-row + .settings-row { border-top: 1px solid var(--border); } - .settings-row--stacked { align-items: start; } .settings-row__title { color: var(--text-strong); font-weight: 600; } - .settings-row__description { - margin-top: 5px; - color: var(--muted); - line-height: 1.45; - } .settings-row__control { justify-self: end; min-width: 0; } .status { display: inline-flex; @@ -174,38 +166,6 @@ export function renderGatewayIsolationPage(mode) { } .status--ok { color: var(--ok-text); background: var(--ok-bg); } .status--warn { color: var(--warn-text); background: var(--warn-bg); } - .command { - display: flex; - min-width: 0; - align-items: center; - gap: 8px; - border: 1px solid var(--border); - border-radius: var(--radius); - padding: 8px 9px; - background: var(--bg); - } - code { - min-width: 0; - overflow-wrap: anywhere; - font-family: var(--font-mono); - font-size: 13px; - } - button { - flex: none; - border: 1px solid var(--border); - border-radius: var(--radius); - padding: 5px 9px; - background: var(--button-bg); - color: var(--text); - cursor: pointer; - font: inherit; - } - .copy-status { - margin-top: 6px; - color: var(--muted); - font-size: 12px; - } - button:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; } @media (max-width: 620px) { main { padding: 16px; } .settings-row { grid-template-columns: 1fr; gap: 12px; } @@ -225,50 +185,9 @@ export function renderGatewayIsolationPage(mode) { ${status} -

-
-
Change with CLI
-
Run from the signed-in user session on the Gateway host.
-
-
- ${command} - -
-
-
${THEME_BRIDGE_SCRIPT} - `; } diff --git a/plugins/gateway-isolation/index.test.js b/plugins/gateway-isolation/index.test.js index 0475aac..2907b4d 100644 --- a/plugins/gateway-isolation/index.test.js +++ b/plugins/gateway-isolation/index.test.js @@ -101,13 +101,11 @@ for (const expected of [ mode: "enabled", status: "Enabled", tone: "status--ok", - command: "clawctl gateway-isolation disable", }, { mode: "disabled", status: "Disabled", tone: "status--warn", - command: "clawctl gateway-isolation enable", }, ]) { test(`renders the exact ${expected.mode} read-only status`, () => { @@ -116,13 +114,8 @@ for (const expected of [ assert.match(html, /Gateway Isolation/); assert.match(html, new RegExp(`>${expected.status}<`)); assert.match(html, new RegExp(expected.tone)); - assert.match(html, /Change with CLI/); - assert.match(html, /Run from the signed-in user session on the Gateway host\./); - assert.match(html, new RegExp(expected.command)); - assert.match(html, /aria-label="Copy command"/); - assert.match(html, /Copy the selected command manually\./); - assert.match(html, /copied = document\.execCommand\("copy"\)/); assert.match(html, /openclaw:widget-theme/); + assert.doesNotMatch(html, /Change with CLI|clipboard|copy-command|clawctl gateway-isolation/); assert.doesNotMatch(html, /next manual Gateway restart/i); }); } diff --git a/scripts/validation/gateway-isolation-runtime.mjs b/scripts/validation/gateway-isolation-runtime.mjs index 218cd1d..47e3ead 100644 --- a/scripts/validation/gateway-isolation-runtime.mjs +++ b/scripts/validation/gateway-isolation-runtime.mjs @@ -286,7 +286,7 @@ async function themeSnapshot(page, frame, expectedTheme, expectedMode) { assert.equal(plugin.h1, "Windows Launcher"); assert.equal(plugin.status, "Disabled"); assert.equal(plugin.row, "Gateway Isolation"); - assert.equal(plugin.mutationGuidance, true); + assert.equal(plugin.mutationGuidance, false); return { hostTheme: expectedTheme, mode: expectedMode, tokens: plugin.values }; } @@ -351,15 +351,6 @@ const context = await browser.newContext({ const page = await context.newPage(); let hello; const rpcErrors = []; -const browserErrors = []; -page.on("console", (message) => { - if (message.type() === "error") { - browserErrors.push(`console: ${message.text()}`); - } -}); -page.on("pageerror", (error) => { - browserErrors.push(`page: ${error.message}`); -}); page.on("websocket", (socket) => socket.on("framereceived", ({ payload }) => { const frame = JSON.parse(payload.toString()); @@ -431,9 +422,10 @@ try { if (method === "GET") { canonicalBody = body; assert.match(body, />Disabled Boolean(hello), "authenticated hello-ok"); - } catch (error) { - throw new Error( - `${error.message}\nGateway logs:\n${logs}\nBrowser errors:\n${browserErrors.join("\n")}`, - ); - } + await until(() => Boolean(hello), "authenticated hello-ok"); assert.equal(hello.buildId, build.buildId); assert.equal(hello.controlUiBuildSource, "bundled"); await pause(2_000); @@ -599,7 +585,7 @@ try { sameFrameAcrossLiveThemeSwitches: true, }, themes: themeResults, - cliControlPresent: true, + unsupportedCliGuidanceAbsent: true, passed: true, }; const json = `${JSON.stringify(result, null, 2)}\n`; From 1d4489591af611cb31a782600610e22173028842 Mon Sep 17 00:00:00 2001 From: Chaz Gordish Date: Mon, 14 Sep 2026 16:27:54 -0700 Subject: [PATCH 35/35] Reapply "Restore launcher CLI control" This reverts commit e2789b1fae4b3c14e8860e5d470a93569cd82240. --- plugins/gateway-isolation/index.js | 81 +++++++++++++++++++ plugins/gateway-isolation/index.test.js | 9 ++- .../validation/gateway-isolation-runtime.mjs | 28 +++++-- 3 files changed, 110 insertions(+), 8 deletions(-) diff --git a/plugins/gateway-isolation/index.js b/plugins/gateway-isolation/index.js index e1d9586..6f457d0 100644 --- a/plugins/gateway-isolation/index.js +++ b/plugins/gateway-isolation/index.js @@ -64,6 +64,7 @@ export function renderGatewayIsolationPage(mode) { const enabled = mode === "enabled"; const status = enabled ? "Enabled" : "Disabled"; + const command = `clawctl gateway-isolation ${enabled ? "disable" : "enable"}`; const tone = enabled ? "ok" : "warn"; return ` @@ -147,7 +148,14 @@ export function renderGatewayIsolationPage(mode) { align-items: center; padding: 18px; } + .settings-row + .settings-row { border-top: 1px solid var(--border); } + .settings-row--stacked { align-items: start; } .settings-row__title { color: var(--text-strong); font-weight: 600; } + .settings-row__description { + margin-top: 5px; + color: var(--muted); + line-height: 1.45; + } .settings-row__control { justify-self: end; min-width: 0; } .status { display: inline-flex; @@ -166,6 +174,38 @@ export function renderGatewayIsolationPage(mode) { } .status--ok { color: var(--ok-text); background: var(--ok-bg); } .status--warn { color: var(--warn-text); background: var(--warn-bg); } + .command { + display: flex; + min-width: 0; + align-items: center; + gap: 8px; + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 8px 9px; + background: var(--bg); + } + code { + min-width: 0; + overflow-wrap: anywhere; + font-family: var(--font-mono); + font-size: 13px; + } + button { + flex: none; + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 5px 9px; + background: var(--button-bg); + color: var(--text); + cursor: pointer; + font: inherit; + } + .copy-status { + margin-top: 6px; + color: var(--muted); + font-size: 12px; + } + button:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; } @media (max-width: 620px) { main { padding: 16px; } .settings-row { grid-template-columns: 1fr; gap: 12px; } @@ -185,9 +225,50 @@ export function renderGatewayIsolationPage(mode) { ${status} +
+
+
Change with CLI
+
Run from the signed-in user session on the Gateway host.
+
+
+ ${command} + +
+
+
${THEME_BRIDGE_SCRIPT} + `; } diff --git a/plugins/gateway-isolation/index.test.js b/plugins/gateway-isolation/index.test.js index 2907b4d..0475aac 100644 --- a/plugins/gateway-isolation/index.test.js +++ b/plugins/gateway-isolation/index.test.js @@ -101,11 +101,13 @@ for (const expected of [ mode: "enabled", status: "Enabled", tone: "status--ok", + command: "clawctl gateway-isolation disable", }, { mode: "disabled", status: "Disabled", tone: "status--warn", + command: "clawctl gateway-isolation enable", }, ]) { test(`renders the exact ${expected.mode} read-only status`, () => { @@ -114,8 +116,13 @@ for (const expected of [ assert.match(html, /Gateway Isolation/); assert.match(html, new RegExp(`>${expected.status}<`)); assert.match(html, new RegExp(expected.tone)); + assert.match(html, /Change with CLI/); + assert.match(html, /Run from the signed-in user session on the Gateway host\./); + assert.match(html, new RegExp(expected.command)); + assert.match(html, /aria-label="Copy command"/); + assert.match(html, /Copy the selected command manually\./); + assert.match(html, /copied = document\.execCommand\("copy"\)/); assert.match(html, /openclaw:widget-theme/); - assert.doesNotMatch(html, /Change with CLI|clipboard|copy-command|clawctl gateway-isolation/); assert.doesNotMatch(html, /next manual Gateway restart/i); }); } diff --git a/scripts/validation/gateway-isolation-runtime.mjs b/scripts/validation/gateway-isolation-runtime.mjs index 47e3ead..218cd1d 100644 --- a/scripts/validation/gateway-isolation-runtime.mjs +++ b/scripts/validation/gateway-isolation-runtime.mjs @@ -286,7 +286,7 @@ async function themeSnapshot(page, frame, expectedTheme, expectedMode) { assert.equal(plugin.h1, "Windows Launcher"); assert.equal(plugin.status, "Disabled"); assert.equal(plugin.row, "Gateway Isolation"); - assert.equal(plugin.mutationGuidance, false); + assert.equal(plugin.mutationGuidance, true); return { hostTheme: expectedTheme, mode: expectedMode, tokens: plugin.values }; } @@ -351,6 +351,15 @@ const context = await browser.newContext({ const page = await context.newPage(); let hello; const rpcErrors = []; +const browserErrors = []; +page.on("console", (message) => { + if (message.type() === "error") { + browserErrors.push(`console: ${message.text()}`); + } +}); +page.on("pageerror", (error) => { + browserErrors.push(`page: ${error.message}`); +}); page.on("websocket", (socket) => socket.on("framereceived", ({ payload }) => { const frame = JSON.parse(payload.toString()); @@ -422,10 +431,9 @@ try { if (method === "GET") { canonicalBody = body; assert.match(body, />Disabled Boolean(hello), "authenticated hello-ok"); + try { + await until(() => Boolean(hello), "authenticated hello-ok"); + } catch (error) { + throw new Error( + `${error.message}\nGateway logs:\n${logs}\nBrowser errors:\n${browserErrors.join("\n")}`, + ); + } assert.equal(hello.buildId, build.buildId); assert.equal(hello.controlUiBuildSource, "bundled"); await pause(2_000); @@ -585,7 +599,7 @@ try { sameFrameAcrossLiveThemeSwitches: true, }, themes: themeResults, - unsupportedCliGuidanceAbsent: true, + cliControlPresent: true, passed: true, }; const json = `${JSON.stringify(result, null, 2)}\n`;