diff options
Diffstat (limited to 'scripts/Operational-E2E.ps1')
| -rw-r--r-- | scripts/Operational-E2E.ps1 | 264 |
1 files changed, 264 insertions, 0 deletions
diff --git a/scripts/Operational-E2E.ps1 b/scripts/Operational-E2E.ps1 new file mode 100644 index 0000000..221e62d --- /dev/null +++ b/scripts/Operational-E2E.ps1 @@ -0,0 +1,264 @@ +param( + [ValidateSet('stdio', 'http')] + [string]$Transport = 'stdio', + [ValidateSet('127.0.0.1', '::1')] + [string]$HttpHost = '127.0.0.1', + [string]$ReleaseZip = 'build\Release\publish\agent-sandbox-mcp-1.0.0-win-x64.zip' +) + +$ErrorActionPreference = 'Stop' +$OutputEncoding = [Text.UTF8Encoding]::new($false) +[Console]::OutputEncoding = $OutputEncoding +$originalTemp = $env:TEMP +$originalTmp = $env:TMP +$originalDotnetCliHome = $env:DOTNET_CLI_HOME +$originalFirstTimeExperience = $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE +$originalTelemetry = $env:DOTNET_CLI_TELEMETRY_OPTOUT +$repository = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path +$zip = (Resolve-Path (Join-Path $repository $ReleaseZip)).Path +$root = Join-Path ([IO.Path]::GetTempPath()) ("agent-mcp-operational-e2e-" + [Guid]::NewGuid().ToString('N')) +$bundleRoot = Join-Path $root 'bundle' +$fixture = Join-Path $root 'fixture' +New-Item -ItemType Directory -Path $bundleRoot,$fixture | Out-Null +$passed = $false +$fixtureAclBefore = (Get-Acl -LiteralPath $fixture).Sddl + +try { + Expand-Archive -LiteralPath $zip -DestinationPath $bundleRoot + $bundle = (Get-ChildItem -LiteralPath $bundleRoot -Directory | Select-Object -First 1).FullName + $program = @' +namespace OperationalFixture; + +public static class Program +{ + private const string Value = "BUG"; + public static int Main(string[] args) + { + if (args.Length == 1 && args[0] == "--self-test") return Value == "FIXED" ? 0 : 1; + System.Console.WriteLine(Value); + return 0; + } +} +'@ + Set-Content -LiteralPath (Join-Path $fixture 'Program.cs') -Value $program -Encoding utf8NoBOM + Set-Content -LiteralPath (Join-Path $fixture 'OperationalFixture.csproj') -Encoding utf8NoBOM -Value @' +<Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <OutputType>Exe</OutputType> + <TargetFramework>net10.0</TargetFramework> + <UseAppHost>false</UseAppHost> + <UseSharedCompilation>false</UseSharedCompilation> + </PropertyGroup> +</Project> +'@ + $sandboxTemp = Join-Path $fixture '.sandbox-temp' + $sandboxCliHome = Join-Path $fixture '.dotnet-home' + New-Item -ItemType Directory -Path $sandboxTemp,$sandboxCliHome | Out-Null + $env:TEMP = $sandboxTemp + $env:TMP = $sandboxTemp + $env:DOTNET_CLI_HOME = $sandboxCliHome + $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = '1' + $env:DOTNET_CLI_TELEMETRY_OPTOUT = '1' + + $dotnet = (Get-Command dotnet).Source + $dotnetRoot = Split-Path $dotnet + $sdkVersion = (& $dotnet --version).Trim() + $csc = Join-Path $dotnetRoot "sdk\$sdkVersion\Roslyn\bincore\csc.dll" + $referenceDirectory = (Get-ChildItem (Join-Path $dotnetRoot 'packs\Microsoft.NETCore.App.Ref') -Directory | + Sort-Object { [Version]$_.Name } -Descending | Select-Object -First 1).FullName + '\ref\net10.0' + $outputDirectory = Join-Path $fixture 'build' + New-Item -ItemType Directory -Path $outputDirectory | Out-Null + $dll = Join-Path $outputDirectory 'OperationalFixture.dll' + Set-Content -LiteralPath (Join-Path $outputDirectory 'OperationalFixture.runtimeconfig.json') -Encoding utf8NoBOM -Value @' +{"runtimeOptions":{"tfm":"net10.0","framework":{"name":"Microsoft.NETCore.App","version":"10.0.0"},"rollForward":"LatestPatch"}} +'@ + $rsp = Join-Path $fixture 'build.rsp' + Set-Content -LiteralPath $rsp -Encoding utf8NoBOM -Value @" +-nostdlib+ +-target:exe +-deterministic+ +-debug- +-out:"$dll" +-r:"$(Join-Path $referenceDirectory 'System.Runtime.dll')" +-r:"$(Join-Path $referenceDirectory 'System.Console.dll')" +"$(Join-Path $fixture 'Program.cs')" +"@ + & $dotnet exec $csc "@$rsp" + if ($LASTEXITCODE -ne 0) { throw 'Initial in-process compiler fixture build failed.' } + + $precompute = Join-Path $root 'precompute' + New-Item -ItemType Directory -Path $precompute | Out-Null + $expectedSource = Join-Path $precompute 'Program.cs' + $expectedDll = Join-Path $precompute 'OperationalFixture.dll' + $expectedRsp = Join-Path $precompute 'expected.rsp' + Set-Content -LiteralPath $expectedSource -Value $program.Replace('"BUG"','"FIXED"') -Encoding utf8NoBOM + Set-Content -LiteralPath $expectedRsp -Encoding utf8NoBOM -Value @" +-nostdlib+ +-target:exe +-deterministic+ +-debug- +-out:"$expectedDll" +-r:"$(Join-Path $referenceDirectory 'System.Runtime.dll')" +-r:"$(Join-Path $referenceDirectory 'System.Console.dll')" +"$expectedSource" +"@ + & $dotnet exec $csc "@$expectedRsp" + if ($LASTEXITCODE -ne 0) { throw 'Expected fixture hash build failed.' } + $expectedDllHash = (Get-FileHash -LiteralPath $expectedDll -Algorithm SHA256).Hash + $fixtureAclBaseline = @{} + @($fixture) + @(Get-ChildItem -LiteralPath $fixture -Recurse -Force | Select-Object -ExpandProperty FullName) | ForEach-Object { + $fixtureAclBaseline[$_] = (Get-Acl -LiteralPath $_).Sddl + } + + $dotnetHash = (Get-FileHash -LiteralPath $dotnet -Algorithm SHA256).Hash + $cscHash = (Get-FileHash -LiteralPath $csc -Algorithm SHA256).Hash + $fixtureYaml = $fixture.Replace('\','/') + $dotnetYaml = $dotnet.Replace('\','/') + $config = @" +schema_version: 1 +application: + language: ja +mcp: + transport: $Transport + http: + host: $HttpHost + port: 18080 + images: + max_count: 4 + max_bytes_each: 10485760 + max_total_bytes: 20971520 +agent: + user_system_prompt: prompts/user-system.md + max_iterations: 32 + same_failure_limit: 5 +ollama: + endpoint: http://127.0.0.1:11434 + model: hf.co/unsloth/gemma-4-E2B-it-qat-GGUF:UD-Q4_K_XL + think: false + keep_alive: 5m + options: + num_ctx: 32768 + temperature: 0 + extra_options: {} +planner: + max_tasks: 32 +sandbox: + filesystem: + read: [$fixtureYaml] + write: [$fixtureYaml] + execute: [$dotnetYaml, $($csc.Replace('\','/')), $($dll.Replace('\','/'))] + delete: [$fixtureYaml] + process: + allow_shell: false + approvals: + - path: $dotnetYaml + sha256: $dotnetHash + - path: $($csc.Replace('\','/')) + sha256: $cscHash + - path: $($dll.Replace('\','/')) + sha256: $expectedDllHash + max_processes: 8 + network: + mode: deny + allow: [] + resources: + memory_mb: 2048 + process_timeout_seconds: 300 +file_edit: + memory_backup_limit_mb: 16 +limits: + whole_read_bytes: 1048576 + search_max_results: 1000 + tail_max_lines: 10000 + context_summary_ratio: 0.8 +localization: + directory: config/locales + fallback: en +skills: + directory: skills +logging: + directory: logs + max_file_bytes: 10485760 + retained_files: 5 +"@ + Set-Content -LiteralPath (Join-Path $bundle 'agent.yaml') -Value $config -Encoding utf8NoBOM + $programPath = (Join-Path $fixture 'Program.cs').Replace('\','/') + $cscPath = $csc.Replace('\','/') + $rspPath = $rsp.Replace('\','/') + $dllPath = $dll.Replace('\','/') + $buildRequest = @" +実作業を行ってください。最終応答は日本語にしてください。 +1. file_readで $programPath を読む。 +2. file_editのoverwriteで、他の内容を維持したまま文字列 \"BUG\" を \"FIXED\" に変更する。 +3. process_runで executable=$dotnetYaml, cwd=$fixtureYaml, args=[\"exec\",\"$cscPath\",\"@$rspPath\"] を実行し、descendantを生成しないin-process C# buildのexit code 0を確認する。 +すべてのtool結果を確認してから完了すること。 +"@ + $testRequest = @" +直前のbuild成果物を独立Taskで実テストしてください。最終応答は日本語にしてください。 +process_runで executable=$dotnetYaml, cwd=$fixtureYaml, args=[\"$dllPath\",\"--self-test\"] を1回実行し、exit code 0を確認してから完了すること。 +"@ + + $clientProject = Join-Path $repository 'scripts\E2EClient\E2EClient.csproj' + if ($Transport -eq 'stdio') { + dotnet run --project $clientProject -c Release --no-build -- stdio (Join-Path $bundle 'agent-mcp.exe') (Join-Path $bundle 'agent.yaml') $buildRequest $testRequest + if ($LASTEXITCODE -ne 0) { throw 'E2E-01 stdio client failed.' } + } + else { + $server = Start-Process -FilePath (Join-Path $bundle 'agent-mcp.exe') -ArgumentList @('serve','--config',(Join-Path $bundle 'agent.yaml'),'--lang','ja','--transport','http') -WorkingDirectory $bundle -WindowStyle Hidden -PassThru + try { + $httpAuthority = if ($HttpHost -eq '::1') { '[::1]' } else { $HttpHost } + $endpoint = "http://${httpAuthority}:18080/mcp" + $ready = $false + for ($attempt = 0; $attempt -lt 100; $attempt++) { + try { $response = Invoke-WebRequest -Uri $endpoint -Method Get -SkipHttpErrorCheck; if ($response.StatusCode -eq 405) { $ready=$true; break } } catch { } + Start-Sleep -Milliseconds 100 + } + if (-not $ready) { throw 'HTTP server did not become ready.' } + dotnet run --project $clientProject -c Release --no-build -- http $endpoint unused $buildRequest $testRequest + if ($LASTEXITCODE -ne 0) { throw 'E2E-02 HTTP client failed.' } + } + finally { if (-not $server.HasExited) { $server.Kill($true); $server.WaitForExit() } } + } + + & $dotnet exec $csc "@$rsp" + if ($LASTEXITCODE -ne 0) { throw 'Post-task fixture build failed.' } + & $dotnet $dll --self-test + if ($LASTEXITCODE -ne 0) { throw 'Post-task fixture self-test failed.' } + if ((Get-ChildItem -LiteralPath (Join-Path $bundle 'state\journals') -File -ErrorAction SilentlyContinue | Measure-Object).Count -ne 0) { throw 'Journal residue detected.' } + if ((Get-ChildItem -LiteralPath (Join-Path $bundle 'state\backups') -Recurse -File -ErrorAction SilentlyContinue | Measure-Object).Count -ne 0) { throw 'Temporary backup residue detected.' } + if ((Get-Acl -LiteralPath $fixture).Sddl -ne $fixtureAclBefore) { throw 'Fixture ACL was not restored byte-for-byte.' } + $bundleExe = (Join-Path $bundle 'agent-mcp.exe') + $orphan = Get-Process -Name 'agent-mcp' -ErrorAction SilentlyContinue | Where-Object { try { $_.Path -eq $bundleExe } catch { $false } } + if ($orphan) { throw 'Orphan agent-mcp process detected.' } + foreach ($entry in $fixtureAclBaseline.GetEnumerator()) { + if ((Get-Acl -LiteralPath $entry.Key).Sddl -ne $entry.Value) { throw "Existing resource ACL was not restored byte-for-byte: $($entry.Key)" } + } + $audit = Get-Content -LiteralPath (Join-Path $bundle 'logs\audit.jsonl') + $auditEvents = $audit | ForEach-Object { $_ | ConvertFrom-Json } + $appContainerSid = ($auditEvents | Where-Object Event -eq 'acl.apply' | Select-Object -First 1).Data.sid + if (-not $appContainerSid) { throw 'Audit did not contain the AppContainer SID.' } + $processStarts = @($auditEvents | Where-Object Event -eq 'process.start') + if (-not ($processStarts | Where-Object { $_.Data.managed_entry_path -eq $csc })) { throw 'Audit is missing the approved in-process compiler execution.' } + if (-not ($processStarts | Where-Object { $_.Data.managed_entry_path -eq $dll })) { throw 'Audit is missing the approved fixture self-test execution.' } + foreach ($path in @($fixture) + @(Get-ChildItem -LiteralPath $fixture -Recurse -Force | Select-Object -ExpandProperty FullName)) { + if ((Get-Acl -LiteralPath $path).Sddl.Contains($appContainerSid, [StringComparison]::OrdinalIgnoreCase)) { + throw "AppContainer ACL residue detected: $path" + } + } + $auditText = $audit -join "`n" + if ($auditText -notmatch 'file.overwrite' -or $auditText -notmatch 'process.start' -or $auditText -notmatch 'wfp.remove' -or $auditText -notmatch 'acl.restore') { throw 'Audit trace is incomplete.' } + $passed = $true + Write-Output "Operational E2E $Transport/$HttpHost PASS: $root" +} +catch { + Write-Error "Operational E2E $Transport FAILED. Evidence retained at $root. $($_.Exception.Message)" + throw +} +finally { + $env:TEMP = $originalTemp + $env:TMP = $originalTmp + $env:DOTNET_CLI_HOME = $originalDotnetCliHome + $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = $originalFirstTimeExperience + $env:DOTNET_CLI_TELEMETRY_OPTOUT = $originalTelemetry + if ($passed) { Remove-Item -LiteralPath $root -Recurse -Force } +} |
