Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 107 additions & 4 deletions crates/openshell-driver-mxc/examples/run-mxc-e2e.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,105 @@ $script:registered = $false
$tomlBase = $null
$gwLog = $null
$gwErrLog = $null
$cliStateRoot = $null
$cliEnvironmentSnapshot = @{}
$cliEnvironmentNames = @(
"APPDATA",
"LOCALAPPDATA",
"XDG_CONFIG_HOME",
"XDG_STATE_HOME",
"XDG_DATA_HOME",
"OPENSHELL_GATEWAY",
"OPENSHELL_GATEWAY_ENDPOINT",
"OPENSHELL_GATEWAY_INSECURE",
"OPENSHELL_GATEWAY_CONFIG",
"OPENSHELL_GATEWAY_NAME"
)

# --- Helpers ------------------------------------------------------------------

function Set-ProcessEnvironmentVariableExact {
param(
[Parameter(Mandatory = $true)]
[string] $Name,
[Parameter(Mandatory = $true)]
[bool] $Exists,
[AllowNull()]
[string] $Value
)

if (-not $Exists) {
Remove-Item "Env:$Name" -ErrorAction SilentlyContinue
return
}

if ($Value.Length -eq 0) {
# Windows PowerShell 5.1 maps an empty value passed through
# Environment.SetEnvironmentVariable to deletion. Call Win32 directly
# so an inherited empty entry remains distinguishable from absence.
if (-not ("OpenShellMxcProcessEnvironmentNative" -as [type])) {
Add-Type -TypeDefinition @'
using System.Runtime.InteropServices;

public static class OpenShellMxcProcessEnvironmentNative
{
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetEnvironmentVariable(string name, string value);
}
'@
}
if (-not [OpenShellMxcProcessEnvironmentNative]::SetEnvironmentVariable($Name, [string]::Empty)) {
$errorCode = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
throw "failed to restore empty process environment variable '$Name' (Win32 error $errorCode)"
}
return
}

[Environment]::SetEnvironmentVariable($Name, $Value, "Process")
}

function Enter-IsolatedCliEnvironment {
$processEnvironment = [Environment]::GetEnvironmentVariables("Process")
foreach ($name in $cliEnvironmentNames) {
$exists = $processEnvironment.Contains($name)
$script:cliEnvironmentSnapshot[$name] = [pscustomobject]@{
Exists = $exists
Value = if ($exists) { [string] $processEnvironment[$name] } else { $null }
}
}
$script:cliStateRoot = Join-Path ([IO.Path]::GetTempPath()) "openshell-mxc-e2e-cli-$PID-$([Guid]::NewGuid().ToString('N'))"
$isolatedPaths = @{
APPDATA = Join-Path $script:cliStateRoot "appdata"
LOCALAPPDATA = Join-Path $script:cliStateRoot "localappdata"
XDG_CONFIG_HOME = Join-Path $script:cliStateRoot "xdg-config"
XDG_STATE_HOME = Join-Path $script:cliStateRoot "xdg-state"
XDG_DATA_HOME = Join-Path $script:cliStateRoot "xdg-data"
}
try {
New-Item -ItemType Directory -Force -Path @($isolatedPaths.Values) | Out-Null
foreach ($entry in $isolatedPaths.GetEnumerator()) {
[Environment]::SetEnvironmentVariable($entry.Key, $entry.Value, "Process")
}
foreach ($name in $cliEnvironmentNames | Where-Object { -not $isolatedPaths.ContainsKey($_) }) {
Remove-Item "Env:$name" -ErrorAction SilentlyContinue
}
} catch {
Exit-IsolatedCliEnvironment
throw
}
}

function Exit-IsolatedCliEnvironment {
foreach ($name in $cliEnvironmentNames) {
$snapshot = $script:cliEnvironmentSnapshot[$name]
Set-ProcessEnvironmentVariableExact -Name $name -Exists $snapshot.Exists -Value $snapshot.Value
}
if ($script:cliStateRoot -and (Test-Path -LiteralPath $script:cliStateRoot)) {
Remove-Item -LiteralPath $script:cliStateRoot -Recurse -Force -ErrorAction SilentlyContinue
}
}

# Render host-runtime settings from the pristine base. Sandbox workload
# settings are create-time driver config, not gateway-wide TOML.
function Render-Toml {
Expand Down Expand Up @@ -233,14 +329,14 @@ function Stop-Gw($p) {

function Register-Cli {
if ($script:registered) { return }
$env:OPENSHELL_GATEWAY = ""

$expectedEndpoint = "http://127.0.0.1:$Port"
$addResult = Invoke-NativeCaptured $cli @(
"gateway", "add", "http://127.0.0.1:$Port", "--local", "--name", $GatewayName
"gateway", "add", $expectedEndpoint, "--local", "--name", $GatewayName
)
$addText = ($addResult.Output -join "`n")
if ($addText) { $addResult.Output | ForEach-Object { Info $_ } }
if ($addResult.ExitCode -ne 0 -and $addText -notmatch '(?i)already exists') {
if ($addResult.ExitCode -ne 0) {
throw "gateway add failed (exit $($addResult.ExitCode)): $addText"
}

Expand Down Expand Up @@ -379,6 +475,8 @@ $backendProbe = @{ Live = $false; Reason = "not probed" }
$runId = Get-Date -Format 'MMddHHmmss'

try {
Enter-IsolatedCliEnvironment

# Start the transcript inside the guarded region so a Start-Transcript failure
# is caught and the results bundle is still produced. Pre-flight runs
# immediately below, so the transcript still captures the whole run.
Expand Down Expand Up @@ -750,14 +848,19 @@ try {
}
} finally {
if ($gw -and -not $KeepRunning) { Stop-Gw $gw }
if ($KeepRunning -and $gw) { Info "gateway pid $($gw.Id) left running (-KeepRunning)" }
if ($KeepRunning -and $gw) {
Info "gateway pid $($gw.Id) left running (-KeepRunning)"
Info "CLI inspection endpoint: `$env:OPENSHELL_GATEWAY_ENDPOINT='http://127.0.0.1:$Port'"
}
}
}
catch {
$harnessError = $_.Exception.Message
Bad "harness error: $harnessError"
}
finally {
Exit-IsolatedCliEnvironment

# --- Summary + results bundle ---------------------------------------------
Step "Summary"
$results | Format-Table -AutoSize
Expand Down
112 changes: 108 additions & 4 deletions crates/openshell-driver-mxc/examples/run-ocsf-audit.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,10 @@ function Invoke-Cli([string[]]$CommandArgs, [switch]$AllowFailure) {
if (-not $AllowFailure -and $process.ExitCode -ne 0) {
throw "openshell $($CommandArgs -join ' ') failed (exit $($process.ExitCode)): $text"
}
return @{ ExitCode = $process.ExitCode; Text = $text }
return @{
ExitCode = $process.ExitCode
Text = $text
}
}

function Resolve-Artifact([string]$explicit, [string]$leaf) {
Expand All @@ -152,6 +155,103 @@ function Get-MxcEtwSessions {
}
}

$cliStateRoot = $null
$cliEnvironmentSnapshot = @{}
$cliEnvironmentNames = @(
"APPDATA",
"LOCALAPPDATA",
"XDG_CONFIG_HOME",
"XDG_STATE_HOME",
"XDG_DATA_HOME",
"OPENSHELL_GATEWAY",
"OPENSHELL_GATEWAY_ENDPOINT",
"OPENSHELL_GATEWAY_INSECURE",
"OPENSHELL_GATEWAY_CONFIG",
"OPENSHELL_GATEWAY_NAME"
)

function Set-ProcessEnvironmentVariableExact {
param(
[Parameter(Mandatory = $true)]
[string] $Name,
[Parameter(Mandatory = $true)]
[bool] $Exists,
[AllowNull()]
[string] $Value
)

if (-not $Exists) {
Remove-Item "Env:$Name" -ErrorAction SilentlyContinue
return
}

if ($Value.Length -eq 0) {
# Windows PowerShell 5.1 maps an empty value passed through
# Environment.SetEnvironmentVariable to deletion. Call Win32 directly
# so an inherited empty entry remains distinguishable from absence.
if (-not ("OpenShellMxcProcessEnvironmentNative" -as [type])) {
Add-Type -TypeDefinition @'
using System.Runtime.InteropServices;

public static class OpenShellMxcProcessEnvironmentNative
{
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetEnvironmentVariable(string name, string value);
}
'@
}
if (-not [OpenShellMxcProcessEnvironmentNative]::SetEnvironmentVariable($Name, [string]::Empty)) {
$errorCode = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
throw "failed to restore empty process environment variable '$Name' (Win32 error $errorCode)"
}
return
}

[Environment]::SetEnvironmentVariable($Name, $Value, "Process")
}

function Enter-IsolatedCliEnvironment {
$processEnvironment = [Environment]::GetEnvironmentVariables("Process")
foreach ($name in $cliEnvironmentNames) {
$exists = $processEnvironment.Contains($name)
$script:cliEnvironmentSnapshot[$name] = [pscustomobject]@{
Exists = $exists
Value = if ($exists) { [string] $processEnvironment[$name] } else { $null }
}
}
$script:cliStateRoot = Join-Path ([IO.Path]::GetTempPath()) "openshell-mxc-ocsf-cli-$PID-$([Guid]::NewGuid().ToString('N'))"
$isolatedPaths = @{
APPDATA = Join-Path $script:cliStateRoot "appdata"
LOCALAPPDATA = Join-Path $script:cliStateRoot "localappdata"
XDG_CONFIG_HOME = Join-Path $script:cliStateRoot "xdg-config"
XDG_STATE_HOME = Join-Path $script:cliStateRoot "xdg-state"
XDG_DATA_HOME = Join-Path $script:cliStateRoot "xdg-data"
}
try {
New-Item -ItemType Directory -Force -Path @($isolatedPaths.Values) | Out-Null
foreach ($entry in $isolatedPaths.GetEnumerator()) {
[Environment]::SetEnvironmentVariable($entry.Key, $entry.Value, "Process")
}
foreach ($name in $cliEnvironmentNames | Where-Object { -not $isolatedPaths.ContainsKey($_) }) {
Remove-Item "Env:$name" -ErrorAction SilentlyContinue
}
} catch {
Exit-IsolatedCliEnvironment
throw
}
}

function Exit-IsolatedCliEnvironment {
foreach ($name in $cliEnvironmentNames) {
$snapshot = $script:cliEnvironmentSnapshot[$name]
Set-ProcessEnvironmentVariableExact -Name $name -Exists $snapshot.Exists -Value $snapshot.Value
}
if ($script:cliStateRoot -and (Test-Path -LiteralPath $script:cliStateRoot)) {
Remove-Item -LiteralPath $script:cliStateRoot -Recurse -Force -ErrorAction SilentlyContinue
}
}

$gateway = Resolve-Artifact $GatewayPath "openshell-gateway.exe"
$cli = Resolve-Artifact $CliPath "openshell.exe"
$policySrc = Join-Path $here "ocsf-audit.yaml"
Expand All @@ -167,6 +267,8 @@ $proxyOn = -not $NoProxy
$oldMockWxc = $env:OPENSHELL_MXC_MOCK_WXC

try {
Enter-IsolatedCliEnvironment

# 1. Validate artifacts + privilege.
Step "Validate package artifacts"
foreach ($f in @($gateway, $cli, $policySrc, $tomlSrc)) {
Expand Down Expand Up @@ -319,10 +421,10 @@ try {

# 9. Register CLI -> gateway.
Step "Register CLI -> gateway"
$env:OPENSHELL_GATEWAY = ""
$gatewayAdd = Invoke-Cli @("gateway", "add", "http://127.0.0.1:$Port", "--local", "--name", $GatewayName) -AllowFailure
$expectedEndpoint = "http://127.0.0.1:$Port"
$gatewayAdd = Invoke-Cli @("gateway", "add", $expectedEndpoint, "--local", "--name", $GatewayName) -AllowFailure
if ($gatewayAdd.Text) { Info $gatewayAdd.Text }
if ($gatewayAdd.ExitCode -ne 0 -and $gatewayAdd.Text -notmatch '(?i)already exists') {
if ($gatewayAdd.ExitCode -ne 0) {
throw "gateway registration failed (exit $($gatewayAdd.ExitCode)): $($gatewayAdd.Text)"
}
$gatewaySelect = Invoke-Cli @("gateway", "select", $GatewayName)
Expand Down Expand Up @@ -364,6 +466,7 @@ finally {
# Stop the gateway FIRST so it releases its log + JSONL file handles.
if ($KeepRunning -and $gw -and -not $gw.HasExited) {
Info "leaving gateway pid $($gw.Id) running (-KeepRunning); stop it with: Stop-Process -Id $($gw.Id) -Force"
Info "CLI inspection endpoint: `$env:OPENSHELL_GATEWAY_ENDPOINT='http://127.0.0.1:$Port'"
} elseif ($gw -and -not $gw.HasExited) {
Step "Cleanup"
Stop-Process -Id $gw.Id -Force -ErrorAction SilentlyContinue
Expand All @@ -382,6 +485,7 @@ finally {
} else {
$env:OPENSHELL_MXC_MOCK_WXC = $oldMockWxc
}
Exit-IsolatedCliEnvironment

# ---- summarise the OCSF audit trail --------------------------------------
$logText = @()
Expand Down
19 changes: 19 additions & 0 deletions crates/openshell-driver-mxc/tests/demo_examples.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,25 @@ fn shipped_aggregate_e2e_assets_support_mock_wiring_validation() {
assert!(runner.contains("not evidence of native MXC or OS enforcement"));
}

#[test]
fn shipped_runners_isolate_cli_state_and_gateway_overrides() {
for name in ["run-mxc-e2e.ps1", "run-ocsf-audit.ps1"] {
let runner = read_example(name);
for required in [
"Enter-IsolatedCliEnvironment",
"Exit-IsolatedCliEnvironment",
"APPDATA",
"LOCALAPPDATA",
"OPENSHELL_GATEWAY",
"OPENSHELL_GATEWAY_ENDPOINT",
"Remove-Item \"Env:$name\"",
"CLI inspection endpoint:",
] {
assert!(runner.contains(required), "{name} is missing {required}");
}
}
}

#[test]
fn shipped_audit_and_websocket_configs_use_current_schema() {
for name in ["mxc-ocsf-audit.toml", "mxc-ws-gateway.toml"] {
Expand Down
Loading
Loading