summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rw-r--r--scripts/Launch-ElevatedVerification.ps110
-rw-r--r--scripts/Operational-E2E.ps1271
-rw-r--r--scripts/Publish-Release.ps147
-rw-r--r--scripts/Restart-PublishedServer.ps130
-rw-r--r--scripts/Run-Final-ElevatedVerification.ps120
5 files changed, 186 insertions, 192 deletions
diff --git a/scripts/Launch-ElevatedVerification.ps1 b/scripts/Launch-ElevatedVerification.ps1
new file mode 100644
index 0000000..b4327bd
--- /dev/null
+++ b/scripts/Launch-ElevatedVerification.ps1
@@ -0,0 +1,10 @@
+param([string]$EvidenceRoot = 'artifacts\evidence\sandbox-usability-precommit')
+
+$ErrorActionPreference = 'Stop'
+$verification = (Resolve-Path (Join-Path $PSScriptRoot 'Run-Final-ElevatedVerification.ps1')).Path
+$repository = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
+$arguments = @(
+ '-NoProfile', '-ExecutionPolicy', 'Bypass', '-NoExit', '-File', ('"' + $verification + '"'),
+ '-EvidenceRoot', $EvidenceRoot
+)
+Start-Process -FilePath 'C:\Program Files\PowerShell\7\pwsh.exe' -Verb RunAs -ArgumentList $arguments -WorkingDirectory $repository -WindowStyle Normal
diff --git a/scripts/Operational-E2E.ps1 b/scripts/Operational-E2E.ps1
index 4b964f1..b253198 100644
--- a/scripts/Operational-E2E.ps1
+++ b/scripts/Operational-E2E.ps1
@@ -3,122 +3,52 @@ param(
[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'
+ [string]$ReleaseZip = 'build\Release\publish\agent-sandbox-mcp-1.0.0-win-x64.zip',
+ [string]$PythonPath
)
$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
-$originalAddGlobalToolsToPath = $env:DOTNET_ADD_GLOBAL_TOOLS_TO_PATH
-$originalUserPath = [Environment]::GetEnvironmentVariable('Path', 'User')
$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
+$alias = 'python-e2e-' + [Guid]::NewGuid().ToString('N').Substring(0, 12)
$passed = $false
+$approved = $false
+$resolutionStorePath = $null
+$server = $null
+New-Item -ItemType Directory -Path $bundleRoot,$fixture | Out-Null
$fixtureAclBefore = (Get-Acl -LiteralPath $fixture).Sddl
try {
Expand-Archive -LiteralPath $zip -DestinationPath $bundleRoot
$bundle = (Get-ChildItem -LiteralPath $bundleRoot -Directory | Select-Object -First 1).FullName
- $e2eSkills = Join-Path $bundle 'e2e-skills'
- New-Item -ItemType Directory -Path $e2eSkills | Out-Null
- $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;
+ $agent = Join-Path $bundle 'agent-mcp.exe'
+ $configPath = Join-Path $bundle 'agent.yaml'
+ if (-not $PythonPath) {
+ $PythonPath = (& uv python find 3.14).Trim()
+ if ($LASTEXITCODE -ne 0 -or -not $PythonPath) { throw 'Python 3.14 could not be resolved with uv.' }
}
-}
-'@
- 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'
- $env:DOTNET_ADD_GLOBAL_TOOLS_TO_PATH = 'false'
+ $pythonLogical = [IO.Path]::GetFullPath($PythonPath)
+ $python = (Get-Item -LiteralPath $pythonLogical).Target
+ if (-not $python) { $python = $pythonLogical }
+ elseif (-not [IO.Path]::IsPathFullyQualified($python)) { $python = [IO.Path]::GetFullPath((Join-Path (Split-Path $pythonLogical) $python)) }
+ $python = (Resolve-Path -LiteralPath $python).Path
+ $pythonVersion = (& $python --version 2>&1 | Out-String).Trim()
+ if ($pythonVersion -notmatch '^Python 3\.14\.') { throw "Operational E2E requires Python 3.14; resolved '$pythonVersion'." }
+ $runtimeRoot = Split-Path $python
- $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.' }
+ Set-Content -LiteralPath (Join-Path $fixture 'README.md') -Encoding utf8NoBOM -Value @'
+# Graph metrics fixture
- $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
- }
+Implement `graph_metrics.py` and `test_graph_metrics.py`. The implementation must validate dependencies,
+produce a deterministic topological order, detect cycles, and calculate the weighted critical path.
+'@
- $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:
@@ -139,7 +69,7 @@ agent:
ollama:
endpoint: http://127.0.0.1:11434
model: deepseek-v4-flash:cloud
- think: false
+ think: true
keep_alive: 5m
options:
num_ctx: 32768
@@ -148,27 +78,21 @@ ollama:
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
+ profile: development
+ workspaces:
+ - $fixtureYaml
+ mounts: []
+ execution:
+ executables:
+ - $alias
+ shells: []
network:
- mode: deny
- allow: []
+ access: none
+ local_endpoints: []
+ restricted_endpoints: []
resources:
- memory_mb: 2048
- process_timeout_seconds: 300
+ profile: constrained
+ execution_timeout_seconds: 300
file_edit:
memory_backup_limit_mb: 16
limits:
@@ -180,95 +104,90 @@ localization:
directory: config/locales
fallback: en
skills:
- directory: e2e-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を確認してから完了すること。
-"@
+ Set-Content -LiteralPath $configPath -Value $config -Encoding utf8NoBOM
+
+ & $agent executable approve $alias --path $pythonLogical --runtime-root $runtimeRoot --yes --config $configPath --lang en
+ if ($LASTEXITCODE -ne 0) { throw 'Executable approval failed.' }
+ $approved = $true
+ & $agent sandbox confirm --yes --config $configPath --lang en
+ if ($LASTEXITCODE -ne 0) { throw 'Sandbox Environment Resolution confirmation failed.' }
+ $normalizedConfig = [IO.Path]::GetFullPath($configPath).ToUpperInvariant()
+ $hash = [Convert]::ToHexString([Security.Cryptography.SHA256]::HashData([Text.Encoding]::UTF8.GetBytes($normalizedConfig)))
+ $resolutionStorePath = Join-Path $env:LOCALAPPDATA "AgentSandboxMcp\environment-resolutions\$hash.json"
+ & $agent sandbox show --format json --config $configPath --lang en | Out-Null
+ if ($LASTEXITCODE -ne 0) { throw 'Effective Sandbox Policy show failed.' }
+ $request = @"
+実作業を行ってください。作業ディレクトリは $fixtureYaml です。
+README.md の仕様に従い、外部パッケージを使わず graph_metrics.py と test_graph_metrics.py を作成してください。
+テストには、正常なDAG、同順位時の決定的な順序、未知dependency、cycle、weighted critical pathを含めてください。
+さらに test_graph_metrics.py 自身が subprocess.run([sys.executable, ...]) で小さな子Pythonを起動して成功を検証するテストを含めてください。
+README.md の読取後、2ファイルの作成、最初のテスト実行、失敗時の修正と再実行は、分割せず単一の実装・検証Taskとして計画してください。
+最後に process_run で executable=$alias, cwd=$fixtureYaml, args=["-m","unittest","discover","-v"] を実行し、全テスト成功を確認してください。
+最終応答は日本語にし、作成ファイルとテスト結果を簡潔に報告してください。
+"@
$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.' }
+ dotnet run --project $clientProject -c Release --no-build -- stdio $agent $configPath $request
+ if ($LASTEXITCODE -ne 0) { throw 'E2E 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.' }
+ $server = Start-Process -FilePath $agent -ArgumentList @('serve','--config',$configPath,'--lang','ja','--transport','http') -WorkingDirectory $bundle -WindowStyle Hidden -PassThru
+ $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
}
- finally { if (-not $server.HasExited) { $server.Kill($true); $server.WaitForExit() } }
+ if (-not $ready) { throw 'HTTP server did not become ready.' }
+ dotnet run --project $clientProject -c Release --no-build -- http $endpoint unused $request
+ if ($LASTEXITCODE -ne 0) { throw 'E2E HTTP client failed.' }
+ if (-not $server.HasExited) { $server.Kill($true); $server.WaitForExit() }
+ $server = $null
}
- & $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.' }
+ & $python -m unittest discover -v -s $fixture
+ if ($LASTEXITCODE -ne 0) { throw 'Independent Python verification failed.' }
+ foreach ($required in 'graph_metrics.py','test_graph_metrics.py') {
+ if (-not (Test-Path -LiteralPath (Join-Path $fixture $required))) { throw "Expected artifact is missing: $required" }
+ }
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 }
+ $auditEvents = Get-Content -LiteralPath (Join-Path $bundle 'logs\audit.jsonl') | 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.' }
+ if (-not $appContainerSid) { throw 'Audit is missing the AppContainer SID.' }
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.write' -or $auditText -notmatch 'process.start' -or $auditText -notmatch 'network.rule.remove' -or $auditText -notmatch 'acl.restore') { throw 'Audit trace is incomplete.' }
+ if (-not ($auditEvents | Where-Object Event -eq 'process.start')) { throw 'Audit is missing process.start.' }
+ if (-not ($auditEvents | Where-Object Event -eq 'process.exit')) { throw 'Audit is missing process.exit.' }
+ if (-not ($auditEvents | Where-Object { $_.Event -eq 'network.rule.remove' -and $_.Data.residual_filters -eq '0' })) { throw 'Audit is missing zero-residual network.rule.remove.' }
+ $orphan = Get-Process -Name agent-mcp -ErrorAction SilentlyContinue | Where-Object { try { $_.Path -eq $agent } catch { $false } }
+ if ($orphan) { throw 'Operational Bundle left an orphan agent-mcp process.' }
$passed = $true
- Write-Output "Operational E2E $Transport/$HttpHost PASS: $root"
+ Write-Output "Operational E2E $Transport/$HttpHost PASS with $pythonVersion"
}
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
- $env:DOTNET_ADD_GLOBAL_TOOLS_TO_PATH = $originalAddGlobalToolsToPath
- $currentUserPath = [Environment]::GetEnvironmentVariable('Path', 'User')
- if ($currentUserPath -cne $originalUserPath) {
- Write-Error 'Operational E2E changed the persistent user PATH.'
+ if ($server -and -not $server.HasExited) { $server.Kill($true); $server.WaitForExit() }
+ if ($approved -and (Test-Path -LiteralPath $agent)) {
+ & $agent executable revoke $alias --yes --config $configPath --lang en | Out-Null
+ }
+ if ($resolutionStorePath -and (Test-Path -LiteralPath $resolutionStorePath)) {
+ Remove-Item -LiteralPath $resolutionStorePath -Force
}
- if ($passed) { Remove-Item -LiteralPath $root -Recurse -Force }
+ if ($passed -and (Test-Path -LiteralPath $root)) { Remove-Item -LiteralPath $root -Recurse -Force }
}
diff --git a/scripts/Publish-Release.ps1 b/scripts/Publish-Release.ps1
index a36c2b4..d794bb5 100644
--- a/scripts/Publish-Release.ps1
+++ b/scripts/Publish-Release.ps1
@@ -15,22 +15,18 @@ $bundleName = "agent-sandbox-mcp-$Version-win-x64"
$bundle = Join-Path $outputRootPath $bundleName
$zip = Join-Path $outputRootPath "$bundleName.zip"
$checksum = "$zip.sha256"
+$stagingRoot = Join-Path $outputRootPath (".staging-" + [Guid]::NewGuid().ToString('N'))
+$stagingBundle = Join-Path $stagingRoot $bundleName
New-Item -ItemType Directory -Force -Path $outputRootPath | Out-Null
-if (Test-Path -LiteralPath $bundle) {
- $resolvedBundle = (Resolve-Path -LiteralPath $bundle).Path
- if (-not $resolvedBundle.StartsWith($outputRootPath + [IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase)) {
- throw 'Existing Release Bundle escaped the validated output root.'
- }
- Get-ChildItem -LiteralPath $resolvedBundle -Force | Remove-Item -Recurse -Force
-}
if (Test-Path -LiteralPath $zip) { Remove-Item -LiteralPath $zip -Force }
if (Test-Path -LiteralPath $checksum) { Remove-Item -LiteralPath $checksum -Force }
+New-Item -ItemType Directory -Path $stagingBundle | Out-Null
$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
+dotnet publish $appProject -c Release -r win-x64 --self-contained true --no-restore --output $stagingBundle -p:Version=$Version
if ($LASTEXITCODE -ne 0) { throw "dotnet publish failed with exit code $LASTEXITCODE" }
$required = @(
@@ -38,23 +34,23 @@ $required = @(
'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" }
+ if (-not (Test-Path -LiteralPath (Join-Path $stagingBundle $relative))) { throw "Release Bundle is missing $relative" }
}
foreach ($relative in @('agent.local.yaml', 'config\agent.local.example.yaml')) {
- if (Test-Path -LiteralPath (Join-Path $bundle $relative)) { throw "Release Bundle must not contain local override input: $relative" }
+ if (Test-Path -LiteralPath (Join-Path $stagingBundle $relative)) { throw "Release Bundle must not contain local override input: $relative" }
}
-$manifestPath = Join-Path $bundle 'publish-manifest.sha256'
-Get-ChildItem -LiteralPath $bundle -File -Recurse |
+$manifestPath = Join-Path $stagingBundle 'publish-manifest.sha256'
+Get-ChildItem -LiteralPath $stagingBundle -File -Recurse |
Where-Object FullName -ne $manifestPath |
Sort-Object FullName |
ForEach-Object {
- $relative = [IO.Path]::GetRelativePath($bundle, $_.FullName).Replace('\', '/')
+ $relative = [IO.Path]::GetRelativePath($stagingBundle, $_.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
+Compress-Archive -LiteralPath $stagingBundle -DestinationPath $zip -CompressionLevel Optimal
$zipHash = (Get-FileHash -LiteralPath $zip -Algorithm SHA256).Hash.ToLowerInvariant()
"$zipHash $([IO.Path]::GetFileName($zip))" | Set-Content -LiteralPath $checksum -Encoding ascii
@@ -70,8 +66,8 @@ try {
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." }
+ & .\agent-mcp.exe sandbox resolve --config .\agent.yaml --lang en
+ if ($LASTEXITCODE -ne 0) { throw "Bundle resolution check failed with exit code $LASTEXITCODE." }
}
finally { Pop-Location }
}
@@ -80,6 +76,25 @@ finally {
if (Test-Path -LiteralPath $freshRoot) { Remove-Item -LiteralPath $freshRoot -Recurse -Force }
}
+# The ZIP is reproducible defaults only. The deployment directory keeps explicitly local,
+# publish-excluded inputs while all versioned defaults and binaries are replaced.
+try {
+ if (Test-Path -LiteralPath $bundle) {
+ $resolvedBundle = (Resolve-Path -LiteralPath $bundle).Path
+ if (-not $resolvedBundle.StartsWith($outputRootPath + [IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase)) {
+ throw 'Existing deployment Bundle escaped the validated output root.'
+ }
+ Get-ChildItem -LiteralPath $resolvedBundle -Force |
+ Where-Object Name -notin @('agent.local.yaml', 'local', 'logs', 'state') |
+ Remove-Item -Recurse -Force
+ }
+ else { New-Item -ItemType Directory -Path $bundle | Out-Null }
+ Copy-Item -Path (Join-Path $stagingBundle '*') -Destination $bundle -Recurse -Force
+}
+finally {
+ if (Test-Path -LiteralPath $stagingRoot) { Remove-Item -LiteralPath $stagingRoot -Recurse -Force }
+}
+
Write-Output "Bundle: $bundle"
Write-Output "ZIP: $zip"
Write-Output "SHA-256: $checksum"
diff --git a/scripts/Restart-PublishedServer.ps1 b/scripts/Restart-PublishedServer.ps1
new file mode 100644
index 0000000..58dd928
--- /dev/null
+++ b/scripts/Restart-PublishedServer.ps1
@@ -0,0 +1,30 @@
+param(
+ [string]$BundlePath = 'build\Release\publish\agent-sandbox-mcp-1.0.0-win-x64',
+ [switch]$StopOnly
+)
+
+$ErrorActionPreference = 'Stop'
+$repository = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
+$bundle = [IO.Path]::GetFullPath((Join-Path $repository $BundlePath))
+$publishRoot = [IO.Path]::GetFullPath((Join-Path $repository 'build\Release\publish'))
+if (-not $bundle.StartsWith($publishRoot + [IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase)) {
+ throw 'BundlePath must remain inside build\Release\publish.'
+}
+$agent = Join-Path $bundle 'agent-mcp.exe'
+$configuration = Join-Path $bundle 'agent.yaml'
+
+$matching = Get-Process -Name 'agent-mcp' -ErrorAction SilentlyContinue | Where-Object {
+ try { [IO.Path]::GetFullPath($_.Path).Equals($agent, [StringComparison]::OrdinalIgnoreCase) }
+ catch { $false }
+}
+foreach ($process in $matching) {
+ Stop-Process -Id $process.Id -Force
+ $process.WaitForExit()
+ Write-Output "Stopped published agent-mcp PID $($process.Id)."
+}
+if ($StopOnly) { exit 0 }
+if (-not (Test-Path -LiteralPath $agent) -or -not (Test-Path -LiteralPath $configuration)) {
+ throw 'Published agent-mcp executable or configuration is missing.'
+}
+$started = Start-Process -FilePath $agent -ArgumentList @('serve', '--config', $configuration) -WorkingDirectory $bundle -WindowStyle Hidden -PassThru
+Write-Output "Started published agent-mcp PID $($started.Id)."
diff --git a/scripts/Run-Final-ElevatedVerification.ps1 b/scripts/Run-Final-ElevatedVerification.ps1
index 9039c77..627bd65 100644
--- a/scripts/Run-Final-ElevatedVerification.ps1
+++ b/scripts/Run-Final-ElevatedVerification.ps1
@@ -12,6 +12,18 @@ if (-not $evidence.StartsWith($repository + [IO.Path]::DirectorySeparatorChar, [
}
New-Item -ItemType Directory -Force -Path $evidence | Out-Null
$failed = $false
+$metadata = Join-Path $evidence 'metadata.txt'
+$administrator = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(
+ [Security.Principal.WindowsBuiltInRole]::Administrator)
+@(
+ "timestamp=$(Get-Date -Format o)"
+ "commit=$(git -C $repository rev-parse HEAD)"
+ "administrator=$administrator"
+ "working_tree_clean=$(-not [bool](git -C $repository status --porcelain))"
+ "os=$([Environment]::OSVersion.VersionString)"
+ "dotnet=$(& dotnet --version)"
+) | Set-Content -LiteralPath $metadata -Encoding utf8NoBOM
+if (-not $administrator) { throw 'Final elevated verification must run as Windows Administrator.' }
function Invoke-VerificationStep {
param([string]$Name, [scriptblock]$Action)
@@ -50,6 +62,14 @@ Invoke-VerificationStep 'e2e-02-http-ipv4' {
Invoke-VerificationStep 'e2e-02-http-ipv6' {
& (Join-Path $PSScriptRoot 'Operational-E2E.ps1') -Transport http -HttpHost ::1
}
+Invoke-VerificationStep 'residual-state' {
+ $journalCount = @(Get-ChildItem -LiteralPath (Join-Path $bundle 'state\journals') -File -ErrorAction SilentlyContinue).Count
+ $backupCount = @(Get-ChildItem -LiteralPath (Join-Path $bundle 'state\backups') -File -Recurse -ErrorAction SilentlyContinue).Count
+ $bundleAgent = Join-Path $bundle 'agent-mcp.exe'
+ $processCount = @(Get-Process -Name agent-mcp -ErrorAction SilentlyContinue | Where-Object { try { $_.Path -eq $bundleAgent } catch { $false } }).Count
+ "journal_count=$journalCount backup_count=$backupCount published_process_count=$processCount"
+ if ($journalCount -ne 0 -or $backupCount -ne 0 -or $processCount -ne 0) { throw 'Task Resource residue was detected.' }
+}
if ($failed) {
Write-Error "Final elevated verification FAILED. Evidence: $evidence"