Integrate release recovery, semantic review, coverage and Windows packaging
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
# Real server coverage parity probe
|
||||
|
||||
Run with PowerShell7 on Windows against a built/staged server. The script launches
|
||||
its own hidden server on a free loopback port, creates synthetic data under a fresh
|
||||
timestamped `.local/rest-coverage-*` directory, captures25 requests/responses and
|
||||
asserts diagnostic parity, invalid-write rejection, source preservation and restart.
|
||||
It stops only its own process, including on failure. It never attaches to an
|
||||
existing server and does not use user credentials. The token it creates is fixed
|
||||
synthetic probe data, not suitable for a deployed server.
|
||||
|
||||
From the repository root, replace the executable path with the artifact under test:
|
||||
|
||||
```powershell
|
||||
$env:PATH = "$env:SystemRoot\System32;$env:SystemRoot;$env:SystemRoot\System32\Wbem"
|
||||
& harness/fixtures/rest-coverage/run-coverage-probe.ps1 -ServerExe 'C:\path\to\staged\arcadia-server.exe'
|
||||
```
|
||||
|
||||
For gnullvm builds, stage the required runtime DLL beside the executable. Clean
|
||||
PATH is deliberate: success should not depend on a developer compiler directory.
|
||||
The first launch and restart use the same isolated store; restart is a forced
|
||||
process stop after an acknowledged saved revision. This tests local persistence,
|
||||
not graceful-drain handling. Output directories and evidence are retained.
|
||||
|
||||
`coverage-probe.json` has incomplete, repaired and invalid source bundles with
|
||||
explicit synthetic UUIDs, plus five exact expected warning tuples. The repair
|
||||
deliberately leaves an independent `sequence_gap` warning. The final reintroduction
|
||||
of the incomplete bundle is an explicit replacement, not service Undo. No new API
|
||||
or production code is required by the probe.
|
||||
|
||||
Observed Windows execution and limitations are recorded in
|
||||
`harness/release-rest-coverage-verification-20260906.md`. This launch wrapper is
|
||||
Windows-specific; the fixture and HTTP oracles can be reused on other platforms.
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"incomplete": {
|
||||
"entry": "project.arcadia",
|
||||
"files": {
|
||||
"work.arcadia": "// Synthetic OA fixture: known responsibilities, explicit incomplete contexts.\noa_entity crew \"Crew\" id \"f0660906-0000-4000-8000-000000000002\" {}\noa_activity activity_group \"Manage service\" id \"f0660906-0000-4000-8000-000000000010\" {\n oa_activity a \"Prepare\" id \"f0660906-0000-4000-8000-000000000011\" {}\n oa_activity b \"Deliver\" id \"f0660906-0000-4000-8000-000000000012\" {}\n}\noa_activity c \"Verify\" id \"f0660906-0000-4000-8000-000000000013\" {}\noa_capability q \"Reliable delivery\" id \"f0660906-0000-4000-8000-000000000020\" {}\noa_mission m \"Deliver reliably\" id \"f0660906-0000-4000-8000-000000000021\" {}\nallocation allocate_a \"Prepare responsibility\" id \"f0660906-0000-4000-8000-000000000041\" { source @crew; target @a; }\nallocation allocate_b \"Deliver responsibility\" id \"f0660906-0000-4000-8000-000000000042\" { source @crew; target @b; }\nallocation allocate_c \"Verify responsibility\" id \"f0660906-0000-4000-8000-000000000043\" { source @crew; target @c; }\ninteraction eab \"Prepared service\" id \"f0660906-0000-4000-8000-000000000050\" { source @a; target @b; }\ninteraction ep \"Grouped service\" id \"f0660906-0000-4000-8000-000000000051\" { source @activity_group; target @c; }\ninteraction eca \"Verified service\" id \"f0660906-0000-4000-8000-000000000052\" { source @c; target @a; }\ncapability_involvement support \"Crew support\" id \"f0660906-0000-4000-8000-000000000060\" { source @crew; target @q; }",
|
||||
"behavior.arcadia": "oa_process delivery \"Delivery path\" id \"f0660906-0000-4000-8000-000000000030\" { steps [@ab_use]; }\noa_occurrence ab_use \"Use prepared service\" id \"f0660906-0000-4000-8000-000000000031\" {\n parent @delivery; body { Exchange { exchange @eab; sender none; receiver none; message_kind Asynchronous; reply_to none; values []; }; };\n}",
|
||||
"project.arcadia": "arcadia 5; project coverage \"REST coverage parity\" id \"f0660906-0000-4000-8000-000000000001\" { include \"work.arcadia\"; include \"behavior.arcadia\"; }"
|
||||
}
|
||||
},
|
||||
"repaired": {
|
||||
"entry": "project.arcadia",
|
||||
"files": {
|
||||
"work.arcadia": "// Synthetic OA fixture: known responsibilities, explicit incomplete contexts.\noa_entity crew \"Crew\" id \"f0660906-0000-4000-8000-000000000002\" {}\noa_activity activity_group \"Manage service\" id \"f0660906-0000-4000-8000-000000000010\" {\n oa_activity a \"Prepare\" id \"f0660906-0000-4000-8000-000000000011\" {}\n oa_activity b \"Deliver\" id \"f0660906-0000-4000-8000-000000000012\" {}\n}\noa_activity c \"Verify\" id \"f0660906-0000-4000-8000-000000000013\" {}\noa_capability q \"Reliable delivery\" id \"f0660906-0000-4000-8000-000000000020\" {}\noa_mission m \"Deliver reliably\" id \"f0660906-0000-4000-8000-000000000021\" {}\nallocation allocate_a \"Prepare responsibility\" id \"f0660906-0000-4000-8000-000000000041\" { source @crew; target @a; }\nallocation allocate_b \"Deliver responsibility\" id \"f0660906-0000-4000-8000-000000000042\" { source @crew; target @b; }\nallocation allocate_c \"Verify responsibility\" id \"f0660906-0000-4000-8000-000000000043\" { source @crew; target @c; }\ninteraction eab \"Prepared service\" id \"f0660906-0000-4000-8000-000000000050\" { source @a; target @b; }\ninteraction ep \"Grouped service\" id \"f0660906-0000-4000-8000-000000000051\" { source @a; target @c; }\ninteraction eca \"Verified service\" id \"f0660906-0000-4000-8000-000000000052\" { source @c; target @a; }\ncapability_involvement support \"Crew support\" id \"f0660906-0000-4000-8000-000000000060\" { source @crew; target @q; }",
|
||||
"behavior.arcadia": "oa_process delivery \"Delivery path\" id \"f0660906-0000-4000-8000-000000000030\" { steps [@ca_use, @ab_use, @pc_use]; }\noa_occurrence ab_use \"Use prepared service\" id \"f0660906-0000-4000-8000-000000000031\" {\n parent @delivery; body { Exchange { exchange @eab; sender none; receiver none; message_kind Asynchronous; reply_to none; values []; }; };\n}\noa_occurrence ca_use \"Use verified service\" id \"f0660906-0000-4000-8000-000000000032\" {\n parent @delivery; body { Exchange { exchange @eca; sender none; receiver none; message_kind Asynchronous; reply_to none; values []; }; };\n}\noa_occurrence pc_use \"Use responsible leaf exchange\" id \"f0660906-0000-4000-8000-000000000033\" {\n parent @delivery; body { Exchange { exchange @ep; sender none; receiver none; message_kind Asynchronous; reply_to none; values []; }; };\n}\nmission_capability mission_delivery \"Mission delivery\" id \"f0660906-0000-4000-8000-000000000061\" { source @m; target @q; }\nchain_involvement context \"Explicit delivery context\" id \"f0660906-0000-4000-8000-000000000062\" { source @delivery; target @q; }",
|
||||
"project.arcadia": "arcadia 5; project coverage \"REST coverage parity\" id \"f0660906-0000-4000-8000-000000000001\" { include \"work.arcadia\"; include \"behavior.arcadia\"; }"
|
||||
}
|
||||
},
|
||||
"invalid": {
|
||||
"entry": "project.arcadia",
|
||||
"files": {
|
||||
"work.arcadia": "// Synthetic OA fixture: known responsibilities, explicit incomplete contexts.\noa_entity crew \"Crew\" id \"f0660906-0000-4000-8000-000000000002\" {}\noa_activity activity_group \"Manage service\" id \"f0660906-0000-4000-8000-000000000010\" {\n oa_activity a \"Prepare\" id \"f0660906-0000-4000-8000-000000000011\" {}\n oa_activity b \"Deliver\" id \"f0660906-0000-4000-8000-000000000012\" {}\n}\noa_activity c \"Verify\" id \"f0660906-0000-4000-8000-000000000013\" {}\noa_capability q \"Reliable delivery\" id \"f0660906-0000-4000-8000-000000000020\" {}\noa_mission m \"Deliver reliably\" id \"f0660906-0000-4000-8000-000000000021\" {}\nallocation allocate_a \"Prepare responsibility\" id \"f0660906-0000-4000-8000-000000000041\" { source @crew; target @a; }\nallocation allocate_b \"Deliver responsibility\" id \"f0660906-0000-4000-8000-000000000042\" { source @crew; target @b; }\nallocation allocate_c \"Verify responsibility\" id \"f0660906-0000-4000-8000-000000000043\" { source @crew; target @c; }\ninteraction eab \"Prepared service\" id \"f0660906-0000-4000-8000-000000000050\" { source @a; target @b; }\ninteraction ep \"Grouped service\" id \"f0660906-0000-4000-8000-000000000051\" { source @crew; target @c; }\ninteraction eca \"Verified service\" id \"f0660906-0000-4000-8000-000000000052\" { source @c; target @a; }\ncapability_involvement support \"Crew support\" id \"f0660906-0000-4000-8000-000000000060\" { source @crew; target @q; }",
|
||||
"behavior.arcadia": "oa_process delivery \"Delivery path\" id \"f0660906-0000-4000-8000-000000000030\" { steps [@ab_use]; }\noa_occurrence ab_use \"Use prepared service\" id \"f0660906-0000-4000-8000-000000000031\" {\n parent @delivery; body { Exchange { exchange @eab; sender none; receiver none; message_kind Asynchronous; reply_to none; values []; }; };\n}",
|
||||
"project.arcadia": "arcadia 5; project coverage \"REST coverage parity\" id \"f0660906-0000-4000-8000-000000000001\" { include \"work.arcadia\"; include \"behavior.arcadia\"; }"
|
||||
}
|
||||
},
|
||||
"expected": [
|
||||
{
|
||||
"object_id": "f0660906-0000-4000-8000-000000000051",
|
||||
"path": "work.arcadia",
|
||||
"code": "non_leaf_work_exchange",
|
||||
"field": "source"
|
||||
},
|
||||
{
|
||||
"object_id": "f0660906-0000-4000-8000-000000000013",
|
||||
"path": "work.arcadia",
|
||||
"code": "work_without_behavior_context",
|
||||
"field": null
|
||||
},
|
||||
{
|
||||
"object_id": "f0660906-0000-4000-8000-000000000052",
|
||||
"path": "work.arcadia",
|
||||
"code": "exchange_without_behavior_context",
|
||||
"field": null
|
||||
},
|
||||
{
|
||||
"object_id": "f0660906-0000-4000-8000-000000000021",
|
||||
"path": "work.arcadia",
|
||||
"code": "mission_without_capability",
|
||||
"field": null
|
||||
},
|
||||
{
|
||||
"object_id": "f0660906-0000-4000-8000-000000000020",
|
||||
"path": "work.arcadia",
|
||||
"code": "capability_without_behavior_context",
|
||||
"field": null
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
param([Parameter(Mandatory=$true)][string]$ServerExe)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$taskRoot = (Resolve-Path (Join-Path $PSScriptRoot '../../..')).Path
|
||||
$exe = (Resolve-Path -LiteralPath $ServerExe).Path
|
||||
$runRoot = Join-Path $taskRoot ('.local/rest-coverage-' + (Get-Date -Format 'yyyyMMdd-HHmmss'))
|
||||
$storePath = Join-Path $runRoot 'store'
|
||||
New-Item -ItemType Directory -Path $runRoot,$storePath | Out-Null
|
||||
$payload = Get-Content -LiteralPath (Join-Path $PSScriptRoot 'coverage-probe.json') -Raw | ConvertFrom-Json -AsHashtable
|
||||
$testToken = 'release-coverage-probe-20260906-local-only'
|
||||
$tokenPath = Join-Path $runRoot 'synthetic-token.txt'
|
||||
[IO.File]::WriteAllText($tokenPath,$testToken)
|
||||
$listener = [Net.Sockets.TcpListener]::new([Net.IPAddress]::Loopback,0)
|
||||
$listener.Start()
|
||||
$probePort = $listener.LocalEndpoint.Port
|
||||
$listener.Stop()
|
||||
$base = "http://127.0.0.1:$probePort"
|
||||
$script:probeProcess = $null
|
||||
$script:sequence = 0
|
||||
$script:launch = 0
|
||||
$codes = @($payload.expected | ForEach-Object { $_.code })
|
||||
function Assert-Probe($condition,[string]$message) { if (-not $condition) { throw $message } }
|
||||
function Start-ProbeServer {
|
||||
$script:launch++
|
||||
$script:probeProcess = Start-Process -FilePath $exe -ArgumentList @('--bind',"127.0.0.1:$probePort",'--data-dir',('"'+$storePath+'"'),'--token-file',('"'+$tokenPath+'"')) -WindowStyle Hidden -PassThru -RedirectStandardOutput (Join-Path $runRoot "server-$script:launch.stdout.log") -RedirectStandardError (Join-Path $runRoot "server-$script:launch.stderr.log")
|
||||
for ($attempt = 0; $attempt -lt 40; $attempt++) {
|
||||
if ($script:probeProcess.HasExited) { throw 'Probe server exited before readiness' }
|
||||
try { $ready=Invoke-WebRequest "$base/health/ready" -TimeoutSec 1; if ($ready.StatusCode -eq 200) { return } } catch {}
|
||||
Start-Sleep -Milliseconds 250
|
||||
}
|
||||
throw 'Probe server did not become ready'
|
||||
}
|
||||
function Stop-ProbeServer {
|
||||
if ($null -ne $script:probeProcess -and -not $script:probeProcess.HasExited) {
|
||||
Stop-Process -InputObject $script:probeProcess
|
||||
$script:probeProcess.WaitForExit()
|
||||
}
|
||||
$script:probeProcess = $null
|
||||
}
|
||||
function Call-Probe([string]$name,[string]$method,[string]$path,$body=$null,[string]$etag='',[int]$expectedStatus=200) {
|
||||
$script:sequence++
|
||||
$stem = Join-Path $runRoot ('{0:D2}-{1}' -f $script:sequence,$name)
|
||||
$headers = @{Authorization="Bearer $testToken"}
|
||||
if ($etag) { $headers['If-Match']=$etag }
|
||||
$request = @{Uri="$base$path";Method=$method;Headers=$headers;SkipHttpErrorCheck=$true;TimeoutSec=40}
|
||||
if ($null -ne $body) {
|
||||
$json=ConvertTo-Json -InputObject $body -Depth 100 -Compress
|
||||
[IO.File]::WriteAllText("$stem.request.json",$json)
|
||||
$request.ContentType='application/json'
|
||||
$request.Body=[Text.Encoding]::UTF8.GetBytes($json)
|
||||
}
|
||||
$response=Invoke-WebRequest @request
|
||||
$content=[string]$response.Content
|
||||
[IO.File]::WriteAllText("$stem.response.txt",$content)
|
||||
$tag=[string]($response.Headers.ETag | Select-Object -First 1)
|
||||
@{method=$method;path=$path;status=[int]$response.StatusCode;etag=$tag} | ConvertTo-Json | Set-Content -LiteralPath "$stem.meta.json"
|
||||
Assert-Probe ($response.StatusCode -eq $expectedStatus) "$name expected $expectedStatus; got $($response.StatusCode): $content"
|
||||
$data=$null
|
||||
try { $data=ConvertFrom-Json -InputObject $content -AsHashtable } catch {}
|
||||
return @{content=$content;data=$data;etag=$tag;headers=$response.Headers}
|
||||
}
|
||||
function Tuples($issues) {
|
||||
return (@($issues | Where-Object { $_.code -in $codes } | ForEach-Object { "$($_.code)|$($_.object_id)|$($_.field)" } | Sort-Object) -join "`n")
|
||||
}
|
||||
function Assert-Coverage($response,[bool]$located=$false) {
|
||||
$found=@($response.data.issues | Where-Object { $_.code -in $codes })
|
||||
Assert-Probe ((Tuples $found) -ceq (Tuples $payload.expected)) 'Five exact coverage tuples differ'
|
||||
Assert-Probe (@($response.data.issues | Where-Object severity -eq 'error').Count -eq 0) 'Unexpected structural error'
|
||||
foreach ($issue in $found) {
|
||||
Assert-Probe ($issue.severity -eq 'warning') 'Coverage is not warning severity'
|
||||
if ($located) {
|
||||
Assert-Probe ($issue.path -eq 'work.arcadia' -and $issue.line -ge 1 -and $issue.column -ge 1 -and $null -ne $issue.span) 'Missing submitted-source location'
|
||||
}
|
||||
}
|
||||
}
|
||||
function Assert-Bundle($actual,$expected) {
|
||||
Assert-Probe ($actual.entry -ceq $expected.entry -and $actual.files.Count -eq $expected.files.Count) 'Bundle entry/file count changed'
|
||||
foreach ($key in $expected.files.Keys) { Assert-Probe ($actual.files[$key] -ceq $expected.files[$key]) "Source bytes changed: $key" }
|
||||
}
|
||||
try {
|
||||
@{exe=$exe;sha256=(Get-FileHash -LiteralPath $exe -Algorithm SHA256).Hash;port=$probePort;started=(Get-Date -Format o)} | ConvertTo-Json | Set-Content (Join-Path $runRoot 'run.json')
|
||||
Start-ProbeServer
|
||||
$created=Call-Probe 'create' 'POST' '/api/v1/projects' @{name='Coverage probe host'} '' 201
|
||||
$path=[string]($created.headers.Location | Select-Object -First 1)
|
||||
Assert-Probe ($path -like '/api/v1/projects/*') 'Create omitted resource Location'
|
||||
$before=Call-Probe 'before-model' 'GET' "$path/model"
|
||||
$beforeBundle=Call-Probe 'before-bundle' 'GET' "$path/source-project"
|
||||
$candidate=Call-Probe 'incomplete-candidate' 'POST' "$path/validation" @{format='source-project';source_project=$payload.incomplete}
|
||||
Assert-Probe $candidate.data.valid 'Incomplete model should be valid'
|
||||
Assert-Coverage $candidate $true
|
||||
$after=Call-Probe 'candidate-no-commit-model' 'GET' "$path/model"
|
||||
$afterBundle=Call-Probe 'candidate-no-commit-bundle' 'GET' "$path/source-project"
|
||||
Assert-Probe ($after.content -ceq $before.content -and $after.etag -ceq $before.etag) 'Candidate changed accepted model/revision'
|
||||
Assert-Bundle $afterBundle.data $beforeBundle.data
|
||||
$committed=Call-Probe 'commit-incomplete' 'PUT' "$path/source-project" $payload.incomplete $before.etag
|
||||
Assert-Coverage $committed $true
|
||||
Assert-Probe ($committed.data.changed -and $committed.etag -cne $before.etag) 'Warning-only commit did not advance revision'
|
||||
$model=Call-Probe 'incomplete-model' 'GET' "$path/model"
|
||||
$bundle=Call-Probe 'incomplete-bundle' 'GET' "$path/source-project"
|
||||
Assert-Bundle $bundle.data $payload.incomplete
|
||||
$source=Call-Probe 'incomplete-canonical' 'GET' "$path/source"
|
||||
$validation=Call-Probe 'incomplete-validation' 'GET' "$path/validation"
|
||||
Assert-Coverage $validation
|
||||
$jsonCheck=Call-Probe 'json-parity' 'POST' "$path/validation" @{format='json';model=$model.data}
|
||||
$textCheck=Call-Probe 'text-parity' 'POST' "$path/validation" @{format='arcadia';source=$source.content}
|
||||
Assert-Coverage $jsonCheck
|
||||
Assert-Coverage $textCheck
|
||||
Assert-Probe ($jsonCheck.data.valid -and $textCheck.data.valid) 'JSON/text parity candidate invalid'
|
||||
$invalid=Call-Probe 'invalid-candidate' 'POST' "$path/validation" @{format='source-project';source_project=$payload.invalid}
|
||||
Assert-Probe (-not $invalid.data.valid -and @($invalid.data.issues | Where-Object { $_.code -eq 'invalid_endpoints' -and $_.severity -eq 'error' }).Count -ge 1) 'Invalid endpoint not diagnosed'
|
||||
Assert-Probe ((Tuples $invalid.data.issues) -eq '') 'Structural error did not gate coverage'
|
||||
$null=Call-Probe 'invalid-write-rejected' 'PUT' "$path/source-project" $payload.invalid $model.etag 422
|
||||
$unchanged=Call-Probe 'rejection-model' 'GET' "$path/model"
|
||||
$unchangedBundle=Call-Probe 'rejection-bundle' 'GET' "$path/source-project"
|
||||
Assert-Probe ($unchanged.content -ceq $model.content -and $unchanged.etag -ceq $model.etag) 'Rejected write changed model/revision'
|
||||
Assert-Bundle $unchangedBundle.data $bundle.data
|
||||
$fixed=Call-Probe 'repair-write' 'PUT' "$path/source-project" $payload.repaired $model.etag
|
||||
Assert-Probe ((Tuples $fixed.data.issues) -eq '') 'Repair left coverage warnings'
|
||||
$fixedModel=Call-Probe 'repaired-model' 'GET' "$path/model"
|
||||
$fixedBundle=Call-Probe 'repaired-bundle' 'GET' "$path/source-project"
|
||||
Assert-Bundle $fixedBundle.data $payload.repaired
|
||||
$fixedValidation=Call-Probe 'repaired-validation' 'GET' "$path/validation"
|
||||
Stop-ProbeServer
|
||||
Start-ProbeServer
|
||||
$reopened=Call-Probe 'restart-model' 'GET' "$path/model"
|
||||
$reopenedBundle=Call-Probe 'restart-bundle' 'GET' "$path/source-project"
|
||||
$reopenedValidation=Call-Probe 'restart-validation' 'GET' "$path/validation"
|
||||
Assert-Probe ($reopened.content -ceq $fixedModel.content -and $reopened.etag -ceq $fixedModel.etag) 'Restart changed model/revision'
|
||||
Assert-Bundle $reopenedBundle.data $fixedBundle.data
|
||||
Assert-Probe ($reopenedValidation.content -ceq $fixedValidation.content) 'Restart changed validation'
|
||||
$again=Call-Probe 'restore-incomplete-by-replacement' 'PUT' "$path/source-project" $payload.incomplete $reopened.etag
|
||||
Assert-Coverage $again $true
|
||||
@{result='passed';requests=$script:sequence;resource=$path;initial_revision=$committed.data.revision;repaired_revision=$fixed.data.revision;final_revision=$again.data.revision;repair_issues=$fixed.data.issues;evidence=$runRoot} | ConvertTo-Json -Depth 20 | Set-Content (Join-Path $runRoot 'result.json')
|
||||
Get-Content (Join-Path $runRoot 'result.json')
|
||||
} catch {
|
||||
$_ | Out-String | Set-Content (Join-Path $runRoot 'failure.txt')
|
||||
throw
|
||||
} finally { Stop-ProbeServer }
|
||||
Reference in New Issue
Block a user