diff options
Diffstat (limited to 'scripts')
| -rw-r--r-- | scripts/E2EClient/E2EClient.csproj | 9 | ||||
| -rw-r--r-- | scripts/E2EClient/Program.cs | 67 | ||||
| -rw-r--r-- | scripts/Operational-E2E.ps1 | 264 | ||||
| -rw-r--r-- | scripts/Publish-Release.ps1 | 76 | ||||
| -rw-r--r-- | scripts/Run-ElevatedE2E.ps1 | 18 | ||||
| -rw-r--r-- | scripts/Run-Final-ElevatedVerification.ps1 | 59 |
6 files changed, 493 insertions, 0 deletions
diff --git a/scripts/E2EClient/E2EClient.csproj b/scripts/E2EClient/E2EClient.csproj new file mode 100644 index 0000000..50fabca --- /dev/null +++ b/scripts/E2EClient/E2EClient.csproj @@ -0,0 +1,9 @@ +<Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <OutputType>Exe</OutputType> + <TargetFramework>net10.0</TargetFramework> + </PropertyGroup> + <ItemGroup> + <PackageReference Include="ModelContextProtocol" /> + </ItemGroup> +</Project> diff --git a/scripts/E2EClient/Program.cs b/scripts/E2EClient/Program.cs new file mode 100644 index 0000000..ee7bb75 --- /dev/null +++ b/scripts/E2EClient/Program.cs @@ -0,0 +1,67 @@ +using ModelContextProtocol; +using ModelContextProtocol.Client; +using ModelContextProtocol.Protocol; + +if (args.Length < 4 || args[0] is not ("stdio" or "http")) +{ + Console.Error.WriteLine("Usage: E2EClient stdio <agent-exe> <config> <request> | http <endpoint> <unused> <request>"); + return 2; +} + +IClientTransport transport = args[0] == "stdio" + ? new StdioClientTransport(new StdioClientTransportOptions + { + Name = "agent-mcp-operational-e2e", + Command = Path.GetFullPath(args[1]), + Arguments = ["serve", "--config", Path.GetFullPath(args[2]), "--lang", "ja", "--transport", "stdio"], + WorkingDirectory = Path.GetDirectoryName(Path.GetFullPath(args[1]))! + }) + : new HttpClientTransport(new HttpClientTransportOptions + { + Name = "agent-mcp-operational-e2e-http", + Endpoint = new Uri(args[1], UriKind.Absolute), + TransportMode = HttpTransportMode.StreamableHttp + }); + +await using McpClient client = await McpClient.CreateAsync(transport); +IList<McpClientTool> tools = await client.ListToolsAsync(); +if (!tools.Select(static tool => tool.Name).Order(StringComparer.Ordinal).SequenceEqual(["agent_run", "agent_status"])) + throw new InvalidOperationException("Unexpected MCP public tool surface."); + +_ = await ExecuteTask(client, args[3], null); +if (args.Length >= 5) _ = await ExecuteTask(client, args[4], null); +return 0; + +static async Task<(Guid TaskId, Guid SessionId)> ExecuteTask(McpClient client, string request, Guid? sessionId) +{ + var runArguments = new Dictionary<string, object?> { ["request"] = request, ["temperature"] = 0.0 }; + if (sessionId is not null) runArguments["session_id"] = sessionId.Value; + CallToolResult run = await client.CallToolAsync("agent_run", runArguments); + if (run.IsError is true) throw new InvalidOperationException(Text(run)); + string accepted = Text(run); + Guid taskId = ReadGuid(accepted, "task_id"); + Guid acceptedSessionId = ReadGuid(accepted, "session_id"); + Console.WriteLine(accepted); + for (int poll = 0; poll < 600; poll++) + { + await Task.Delay(500); + CallToolResult status = await client.CallToolAsync("agent_status", new Dictionary<string, object?> { ["task_id"] = taskId, ["session_id"] = acceptedSessionId }); + string text = Text(status); + string state = ReadValue(text, "status"); + if (state is "completed" or "failed" or "cancelled") + { + Console.WriteLine(text); + if (state != "completed") throw new InvalidOperationException($"Task {taskId} ended in state {state}."); + return (taskId, acceptedSessionId); + } + } + throw new TimeoutException($"Task {taskId} did not reach a terminal state."); +} + +static string Text(CallToolResult result) => string.Join('\n', result.Content.OfType<TextContentBlock>().Select(static block => block.Text)); +static Guid ReadGuid(string text, string key) => Guid.Parse(ReadValue(text, key)); +static string ReadValue(string text, string key) +{ + string prefix = key + ":"; + return text.Split('\n').First(line => line.StartsWith(prefix, StringComparison.Ordinal))[prefix.Length..].Trim(); +} 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 } +} diff --git a/scripts/Publish-Release.ps1 b/scripts/Publish-Release.ps1 new file mode 100644 index 0000000..8ada466 --- /dev/null +++ b/scripts/Publish-Release.ps1 @@ -0,0 +1,76 @@ +param( + [string]$Version = '1.0.0', + [string]$OutputRoot = 'build\Release\publish', + [switch]$SkipRuntimeCheck +) + +$ErrorActionPreference = 'Stop' +$repository = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path +$outputRootPath = [IO.Path]::GetFullPath((Join-Path $repository $OutputRoot)) +$buildRootPath = [IO.Path]::GetFullPath((Join-Path $repository 'build')) +if (-not $outputRootPath.StartsWith($buildRootPath + [IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase)) { + throw 'Release output must remain inside the repository build directory.' +} +$bundleName = "agent-sandbox-mcp-$Version-win-x64" +$bundle = Join-Path $outputRootPath $bundleName +$zip = Join-Path $outputRootPath "$bundleName.zip" +$checksum = "$zip.sha256" + +New-Item -ItemType Directory -Force -Path $outputRootPath | Out-Null +if (Test-Path -LiteralPath $bundle) { Remove-Item -LiteralPath $bundle -Recurse -Force } +if (Test-Path -LiteralPath $zip) { Remove-Item -LiteralPath $zip -Force } +if (Test-Path -LiteralPath $checksum) { Remove-Item -LiteralPath $checksum -Force } + +$appProject = Join-Path $repository 'src\Agent.App\Agent.App.csproj' +dotnet restore $appProject -r win-x64 --locked-mode +if ($LASTEXITCODE -ne 0) { throw "win-x64 locked restore failed with exit code $LASTEXITCODE" } +dotnet publish $appProject -c Release -r win-x64 --self-contained true --no-restore --output $bundle -p:Version=$Version +if ($LASTEXITCODE -ne 0) { throw "dotnet publish failed with exit code $LASTEXITCODE" } + +$required = @( + 'agent-mcp.exe', 'agent.yaml', 'config\locales\en.json', 'config\locales\ja.json', + 'prompts\user-system.md', 'skills', 'README.md' +) +foreach ($relative in $required) { + if (-not (Test-Path -LiteralPath (Join-Path $bundle $relative))) { throw "Release Bundle is missing $relative" } +} + +$manifestPath = Join-Path $bundle 'publish-manifest.sha256' +Get-ChildItem -LiteralPath $bundle -File -Recurse | + Where-Object FullName -ne $manifestPath | + Sort-Object FullName | + ForEach-Object { + $relative = [IO.Path]::GetRelativePath($bundle, $_.FullName).Replace('\', '/') + $hash = (Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash.ToLowerInvariant() + "$hash $relative" + } | Set-Content -LiteralPath $manifestPath -Encoding utf8NoBOM + +Compress-Archive -LiteralPath $bundle -DestinationPath $zip -CompressionLevel Optimal +$zipHash = (Get-FileHash -LiteralPath $zip -Algorithm SHA256).Hash.ToLowerInvariant() +"$zipHash $([IO.Path]::GetFileName($zip))" | Set-Content -LiteralPath $checksum -Encoding ascii + +$freshRoot = Join-Path ([IO.Path]::GetTempPath()) ("agent-mcp-release-check-" + [Guid]::NewGuid().ToString('N')) +New-Item -ItemType Directory -Path $freshRoot | Out-Null +try { + Expand-Archive -LiteralPath $zip -DestinationPath $freshRoot + $freshBundle = Join-Path $freshRoot $bundleName + $sourceReferences = Get-ChildItem -LiteralPath $freshBundle -File -Recurse | + Where-Object Extension -in @('.yaml', '.json', '.md', '.config') | + Select-String -SimpleMatch -Pattern $repository + if ($sourceReferences) { throw 'Release Bundle contains a source-tree reference.' } + if (-not $SkipRuntimeCheck) { + Push-Location $freshBundle + try { + & .\agent-mcp.exe check --config .\agent.yaml --lang en + if ($LASTEXITCODE -ne 0) { throw "Bundle check failed with exit code $LASTEXITCODE. Run this script in an elevated terminal." } + } + finally { Pop-Location } + } +} +finally { + if (Test-Path -LiteralPath $freshRoot) { Remove-Item -LiteralPath $freshRoot -Recurse -Force } +} + +Write-Output "Bundle: $bundle" +Write-Output "ZIP: $zip" +Write-Output "SHA-256: $checksum" diff --git a/scripts/Run-ElevatedE2E.ps1 b/scripts/Run-ElevatedE2E.ps1 new file mode 100644 index 0000000..7a87a51 --- /dev/null +++ b/scripts/Run-ElevatedE2E.ps1 @@ -0,0 +1,18 @@ +param( + [ValidateSet('stdio', 'http')] + [string]$Transport, + [ValidateSet('127.0.0.1', '::1')] + [string]$HttpHost = '127.0.0.1', + [string]$EvidencePath +) + +$ErrorActionPreference = 'Continue' +try { + & (Join-Path $PSScriptRoot 'Operational-E2E.ps1') -Transport $Transport -HttpHost $HttpHost *>&1 | Tee-Object -FilePath $EvidencePath +} +catch { + $_ | Out-String | Tee-Object -FilePath $EvidencePath -Append + exit 1 +} +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } +exit 0 diff --git a/scripts/Run-Final-ElevatedVerification.ps1 b/scripts/Run-Final-ElevatedVerification.ps1 new file mode 100644 index 0000000..9039c77 --- /dev/null +++ b/scripts/Run-Final-ElevatedVerification.ps1 @@ -0,0 +1,59 @@ +param( + [string]$EvidenceRoot = 'artifacts\evidence\final-elevated' +) + +$ErrorActionPreference = 'Continue' +$OutputEncoding = [Text.UTF8Encoding]::new($false) +[Console]::OutputEncoding = $OutputEncoding +$repository = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path +$evidence = [IO.Path]::GetFullPath((Join-Path $repository $EvidenceRoot)) +if (-not $evidence.StartsWith($repository + [IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase)) { + throw 'Evidence output must remain inside the repository.' +} +New-Item -ItemType Directory -Force -Path $evidence | Out-Null +$failed = $false + +function Invoke-VerificationStep { + param([string]$Name, [scriptblock]$Action) + $path = Join-Path $evidence "$Name.log" + "START $Name $(Get-Date -Format o)" | Tee-Object -FilePath $path + $exitCode = 0 + try { + & $Action *>&1 | Tee-Object -FilePath $path -Append + if ($LASTEXITCODE) { $exitCode = $LASTEXITCODE } + } + catch { + $_ | Out-String | Tee-Object -FilePath $path -Append + $exitCode = 1 + } + "END $Name exit=$exitCode $(Get-Date -Format o)" | Tee-Object -FilePath $path -Append + if ($exitCode -ne 0) { $script:failed = $true } +} + +$bundle = Join-Path $repository 'build\Release\publish\agent-sandbox-mcp-1.0.0-win-x64' +$windowsTests = Join-Path $repository 'build\Release\Agent.Windows.Tests\net10.0-windows10.0.26100.0\Agent.Windows.Tests.exe' +$coreTests = Join-Path $repository 'build\Release\Agent.Core.Tests\net10.0-windows10.0.26100.0\Agent.Core.Tests.exe' +$plannerTests = Join-Path $repository 'build\Release\Agent.Planner.Tests\net10.0\Agent.Planner.Tests.exe' + +Invoke-VerificationStep 'bundle-check' { + & (Join-Path $bundle 'agent-mcp.exe') check --config (Join-Path $bundle 'agent.yaml') --lang en +} +Invoke-VerificationStep 'windows-security-tests' { & $windowsTests } +Invoke-VerificationStep 'core-unit-integration-tests' { & $coreTests } +Invoke-VerificationStep 'planner-unit-tests' { & $plannerTests } +Invoke-VerificationStep 'e2e-01-stdio' { + & (Join-Path $PSScriptRoot 'Operational-E2E.ps1') -Transport stdio +} +Invoke-VerificationStep 'e2e-02-http-ipv4' { + & (Join-Path $PSScriptRoot 'Operational-E2E.ps1') -Transport http -HttpHost 127.0.0.1 +} +Invoke-VerificationStep 'e2e-02-http-ipv6' { + & (Join-Path $PSScriptRoot 'Operational-E2E.ps1') -Transport http -HttpHost ::1 +} + +if ($failed) { + Write-Error "Final elevated verification FAILED. Evidence: $evidence" + exit 1 +} +Write-Output "Final elevated verification PASS. Evidence: $evidence" +exit 0 |
