From 4d283f30221d17b19639cdddedac4d18225b55ea Mon Sep 17 00:00:00 2001 From: Agent Runner Date: Tue, 24 Mar 2026 05:44:18 -0700 Subject: [PATCH] feat: formalize runbook topology contract (#1905) --- .../integration-runbook-v1.schema.json | 55 ++++++++++++++++++- tests/IntegrationRunbook.Tests.ps1 | 3 + tests/TestHelpers.Schema.ps1 | 11 ++++ 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/docs/schemas/integration-runbook-v1.schema.json b/docs/schemas/integration-runbook-v1.schema.json index c40d4acc9..26a70f0a2 100644 --- a/docs/schemas/integration-runbook-v1.schema.json +++ b/docs/schemas/integration-runbook-v1.schema.json @@ -26,11 +26,62 @@ ] }, "status": { "type": "string", "enum": ["Passed", "Failed", "Skipped"] }, - "details": { "type": "object" } + "details": { + "type": "object", + "properties": { + "exitCode": { "type": ["integer", "null"] }, + "error": { "type": "string" }, + "loopIterations": { "type": "integer", "minimum": 0 }, + "finalStatusPath": { "type": "string" }, + "finalStatusReadError": { "type": "string" }, + "finalStatusMissing": { "type": "boolean" }, + "harness": { + "type": "object", + "properties": { + "runtimeSurface": { "const": "windows-native-teststand" }, + "processModelClass": { + "type": "string", + "enum": ["sequential-process-model", "parallel-process-model"] + }, + "windowsOnly": { "type": "boolean" }, + "requestedSimultaneous": { "type": "boolean" }, + "cellClass": { "type": ["string", "null"] }, + "executionCellLeasePath": { "type": "string" }, + "executionCellId": { "type": "string" }, + "executionCellLeaseId": { "type": "string" }, + "harnessInstanceLeasePath": { "type": "string" }, + "harnessInstanceLeaseId": { "type": "string" }, + "harnessInstanceId": { "type": "string" } + }, + "additionalProperties": true + }, + "executionTopology": { + "type": "object", + "properties": { + "runtimeSurface": { "const": "windows-native-teststand" }, + "processModelClass": { + "type": "string", + "enum": ["sequential-process-model", "parallel-process-model"] + }, + "windowsOnly": { "type": "boolean" }, + "requestedSimultaneous": { "type": "boolean" }, + "cellClass": { "type": ["string", "null"] }, + "executionCellLeasePath": { "type": "string" }, + "executionCellId": { "type": "string" }, + "executionCellLeaseId": { "type": "string" }, + "harnessInstanceLeasePath": { "type": "string" }, + "harnessInstanceLeaseId": { "type": "string" }, + "harnessInstanceId": { "type": "string" } + }, + "additionalProperties": true + } + }, + "additionalProperties": true + } }, "additionalProperties": false } } }, "additionalProperties": true -} \ No newline at end of file +} diff --git a/tests/IntegrationRunbook.Tests.ps1 b/tests/IntegrationRunbook.Tests.ps1 index bd4175e2b..02d1b0f91 100644 --- a/tests/IntegrationRunbook.Tests.ps1 +++ b/tests/IntegrationRunbook.Tests.ps1 @@ -11,6 +11,7 @@ Describe 'IntegrationRunbook - Phase Selection & JSON' -Tag 'Unit' { $global:runRoot = Resolve-Path (Join-Path $PSScriptRoot '..') $global:runScript = Resolve-Path $scriptPath $global:schemaFile = Resolve-Path $schemaPath + . "$PSScriptRoot/TestHelpers.Schema.ps1" } It 'emits JSON with expected schema id and core properties (subset phases)' { @@ -31,6 +32,7 @@ Describe 'IntegrationRunbook - Phase Selection & JSON' -Tag 'Unit' { Test-Path $tmp | Should -BeTrue $json = Get-Content $tmp -Raw | ConvertFrom-Json $json.schema | Should -Be 'integration-runbook-v1' + Assert-JsonShape -Path $tmp -Spec 'IntegrationRunbook' | Should -BeTrue $json.phases.Count | Should -Be 2 ($json.phases | ForEach-Object name) | Should -Be @('Prereqs','ViInputs') $json.overallStatus | Should -Match 'Passed|Failed' @@ -166,6 +168,7 @@ exit 0 Test-Path $tmp | Should -BeTrue $json = Get-Content $tmp -Raw | ConvertFrom-Json + Assert-JsonShape -Path $tmp -Spec 'IntegrationRunbook' | Should -BeTrue $loopPhase = $json.phases | Where-Object name -eq 'Loop' $loopPhase.status | Should -Be 'Passed' $loopPhase.details.loopIterations | Should -Be 2 diff --git a/tests/TestHelpers.Schema.ps1 b/tests/TestHelpers.Schema.ps1 index a7d4fc5ab..fcaf8ae51 100644 --- a/tests/TestHelpers.Schema.ps1 +++ b/tests/TestHelpers.Schema.ps1 @@ -102,6 +102,17 @@ $script:JsonShapeSpecs['LoopEvent'] = [pscustomobject]@{ } } +$script:JsonShapeSpecs['IntegrationRunbook'] = [pscustomobject]@{ + Required = @('schema','generated','phases','overallStatus') + Optional = @() + Types = @{ + schema = { param($v) $v -is [string] -and $v -eq 'integration-runbook-v1' } + generated = { param($v) ($v -is [string]) -or ($v -is [datetime]) } + overallStatus = { param($v) $v -is [string] -and $v -match '^(Passed|Failed)$' } + phases = { param($v) $v -is [object[]] } + } +} + function Assert-JsonShape { [CmdletBinding()] param( [Parameter(Mandatory)][string]$Path,