summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rw-r--r--scripts/E2EClient/E2EClient.csproj9
-rw-r--r--scripts/E2EClient/Program.cs67
-rw-r--r--scripts/Operational-E2E.ps1264
-rw-r--r--scripts/Publish-Release.ps176
-rw-r--r--scripts/Run-ElevatedE2E.ps118
-rw-r--r--scripts/Run-Final-ElevatedVerification.ps159
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