diff --git a/docs/eve-migration-roadmap.md b/docs/eve-migration-roadmap.md index f133941..5d91603 100644 --- a/docs/eve-migration-roadmap.md +++ b/docs/eve-migration-roadmap.md @@ -280,6 +280,14 @@ Exit criteria: Recently cut: +- Runtime owner conformance consumption now verifies runtime target health + instead of only runtime presence. The generic consumer supports + `--expect-runtime-status`, `--expect-runtime-feature`, + `--expect-runtime-command-schema`, and `--expect-runtime-capture-status`; the + export carries top-level `captureStatus`; and the parity harness runs a + runtime-owner smoke that proves active Flutter/Unity/web targets while keeping + Fensalir Direct2D explicitly demoted as `external-adapter-spike` with missing + capture. - Provider owner conformance consumption now verifies the interactive-world contract instead of only provider presence. The generic conformance consumer supports `--expect-provider-surface`, `--expect-provider-command`, and diff --git a/scripts/run-eveflutter-conformance-consumer-smoke.ps1 b/scripts/run-eveflutter-conformance-consumer-smoke.ps1 index 203eb7b..cfe3986 100644 --- a/scripts/run-eveflutter-conformance-consumer-smoke.ps1 +++ b/scripts/run-eveflutter-conformance-consumer-smoke.ps1 @@ -49,7 +49,14 @@ try { --expect-runtime windows-flutter ` --expect-runtime linux-flutter ` --expect-runtime android-flutter ` + --expect-runtime-status windows-flutter:active ` + --expect-runtime-feature windows-flutter:embeddedDocuments ` + --expect-runtime-command-schema windows-flutter:gamecult.eve.command.v1 ` + --expect-runtime-capture-status windows-flutter:golden ` --expect-split-target EveFlutter + if ($LASTEXITCODE -ne 0) { + throw "EveFlutter conformance consumer smoke failed with exit code $LASTEXITCODE" + } } finally { Pop-Location } diff --git a/scripts/run-parity-harness.ps1 b/scripts/run-parity-harness.ps1 index 4e6212c..e4ee1f3 100644 --- a/scripts/run-parity-harness.ps1 +++ b/scripts/run-parity-harness.ps1 @@ -16,6 +16,7 @@ try { node .\tools\parity\run-parity.mjs & (Join-Path $PSScriptRoot "run-conformance-consumer-smoke.ps1") & (Join-Path $PSScriptRoot "run-plugin-owner-conformance-consumer-smoke.ps1") + & (Join-Path $PSScriptRoot "run-runtime-owner-conformance-consumer-smoke.ps1") } finally { Pop-Location } diff --git a/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 b/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 new file mode 100644 index 0000000..50fe3cd --- /dev/null +++ b/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 @@ -0,0 +1,63 @@ +param( + [string] $ExportDirectory = "artifacts\conformance\latest", + [string] $ConsumerDirectory = "artifacts\runtime-owner-conformance-consumer-smoke" +) + +$ErrorActionPreference = "Stop" + +$projectRoot = Split-Path -Parent $PSScriptRoot +$sourceExport = if ([System.IO.Path]::IsPathRooted($ExportDirectory)) { + $ExportDirectory +} else { + Join-Path $projectRoot $ExportDirectory +} +$consumerRoot = if ([System.IO.Path]::IsPathRooted($ConsumerDirectory)) { + $ConsumerDirectory +} else { + Join-Path $projectRoot $ConsumerDirectory +} +$consumerExport = Join-Path $consumerRoot "export" + +if (-not (Test-Path $sourceExport)) { + throw "Conformance export not found: $sourceExport" +} + +if (Test-Path $consumerExport) { + Remove-Item -LiteralPath $consumerExport -Recurse -Force +} +New-Item -ItemType Directory -Force -Path $consumerRoot | Out-Null +Copy-Item -LiteralPath $sourceExport -Destination $consumerExport -Recurse + +$consumerScript = Join-Path $projectRoot "tools\conformance\consume-export.mjs" + +node $consumerScript $consumerExport ` + --expect-pack runtime ` + --expect-runtime web ` + --expect-runtime windows-flutter ` + --expect-runtime linux-flutter ` + --expect-runtime android-flutter ` + --expect-runtime unity-uitoolkit ` + --expect-runtime direct2d ` + --expect-runtime-status web:active ` + --expect-runtime-status windows-flutter:active ` + --expect-runtime-status linux-flutter:active ` + --expect-runtime-status android-flutter:active ` + --expect-runtime-status unity-uitoolkit:active ` + --expect-runtime-status direct2d:external-adapter-spike ` + --expect-runtime-feature web:providerAdvertisements ` + --expect-runtime-feature windows-flutter:embeddedDocuments ` + --expect-runtime-feature unity-uitoolkit:embeddedDocuments ` + --expect-runtime-feature direct2d:embeddedDocuments ` + --expect-runtime-command-schema web:gamecult.eve.command.v1 ` + --expect-runtime-command-schema windows-flutter:gamecult.eve.command.v1 ` + --expect-runtime-command-schema unity-uitoolkit:gamecult.eve.command.v1 ` + --expect-runtime-capture-status web:chrome-headless ` + --expect-runtime-capture-status windows-flutter:golden ` + --expect-runtime-capture-status linux-flutter:ssh-golden ` + --expect-runtime-capture-status android-flutter:adb-png ` + --expect-runtime-capture-status unity-uitoolkit:semantic ` + --expect-runtime-capture-status direct2d:missing + +if ($LASTEXITCODE -ne 0) { + throw "Runtime owner conformance consumer smoke failed with exit code $LASTEXITCODE" +} diff --git a/tools/conformance/consume-export.mjs b/tools/conformance/consume-export.mjs index 836b47c..3091318 100644 --- a/tools/conformance/consume-export.mjs +++ b/tools/conformance/consume-export.mjs @@ -21,6 +21,10 @@ if (!exportDirectory) { " --expect-provider-surface ", " --expect-provider-command ", " --expect-provider-receipt-state ", + " --expect-runtime-status ", + " --expect-runtime-feature ", + " --expect-runtime-command-schema ", + " --expect-runtime-capture-status ", ].join("\n")); process.exit(2); } @@ -48,6 +52,7 @@ function validateIndex(index, directory, expectations, errors) { const packs = Array.isArray(index.packs) ? index.packs : []; const packIds = new Set(packs.map(pack => pack.id)); const fixtureIds = new Set(); + const exportedRuntimeTargets = []; for (const requiredPack of ["core", "plugin", "provider", "runtime"]) { if (!packIds.has(requiredPack)) errors.push(`pack:${requiredPack}:missing`); } @@ -79,9 +84,10 @@ function validateIndex(index, directory, expectations, errors) { } if (pack.id === "runtime") { - const runtimeTargets = Array.isArray(packDocument.runtimeTargets) ? packDocument.runtimeTargets : []; - if (!runtimeTargets.length) errors.push("pack-file:runtime:runtimeTargets:missing"); - for (const runtime of runtimeTargets) { + const packRuntimeTargets = Array.isArray(packDocument.runtimeTargets) ? packDocument.runtimeTargets : []; + if (!packRuntimeTargets.length) errors.push("pack-file:runtime:runtimeTargets:missing"); + for (const runtime of packRuntimeTargets) { + exportedRuntimeTargets.push(runtime); if (!runtime.runtimeId) errors.push("pack-file:runtime:runtimeTarget:runtimeId:missing"); if (!runtime.status) errors.push(`pack-file:runtime:runtimeTarget:${runtime.runtimeId || "unknown"}:status:missing`); if (!runtime.ownerRepo) errors.push(`pack-file:runtime:runtimeTarget:${runtime.runtimeId || "unknown"}:ownerRepo:missing`); @@ -91,7 +97,7 @@ function validateIndex(index, directory, expectations, errors) { const plugins = Array.isArray(index.plugins) ? index.plugins : []; const providers = Array.isArray(index.providers) ? index.providers : []; - const runtimes = Array.isArray(index.runtimes) ? index.runtimes : []; + const runtimes = mergeRuntimeRecords(Array.isArray(index.runtimes) ? index.runtimes : [], exportedRuntimeTargets); const splitTargets = Array.isArray(index.splitTargets) ? index.splitTargets : []; for (const expectedFixture of expectations.fixtures) { @@ -156,6 +162,46 @@ function validateIndex(index, directory, expectations, errors) { for (const expectedRuntime of expectations.runtimes) { if (!runtimes.some(runtime => runtime.runtimeId === expectedRuntime)) errors.push(`runtimes:${expectedRuntime}:missing`); } + for (const expectation of expectations.runtimeStatuses) { + const runtime = runtimes.find(candidate => candidate.runtimeId === expectation.runtimeId); + if (!runtime) { + errors.push(`runtimes:${expectation.runtimeId}:missing`); + continue; + } + if (runtime.status !== expectation.status) { + errors.push(`runtimes:${expectation.runtimeId}:status:expected ${expectation.status} got ${runtime.status || ""}`); + } + } + for (const expectation of expectations.runtimeFeatures) { + const runtime = runtimes.find(candidate => candidate.runtimeId === expectation.runtimeId); + if (!runtime) { + errors.push(`runtimes:${expectation.runtimeId}:missing`); + continue; + } + if (!Array.isArray(runtime.supportedFeatures) || !runtime.supportedFeatures.includes(expectation.feature)) { + errors.push(`runtimes:${expectation.runtimeId}:feature:${expectation.feature}:missing`); + } + } + for (const expectation of expectations.runtimeCommandSchemas) { + const runtime = runtimes.find(candidate => candidate.runtimeId === expectation.runtimeId); + if (!runtime) { + errors.push(`runtimes:${expectation.runtimeId}:missing`); + continue; + } + if (runtime.commandTransportSchema !== expectation.schema) { + errors.push(`runtimes:${expectation.runtimeId}:commandTransportSchema:expected ${expectation.schema} got ${runtime.commandTransportSchema || ""}`); + } + } + for (const expectation of expectations.runtimeCaptureStatuses) { + const runtime = runtimes.find(candidate => candidate.runtimeId === expectation.runtimeId); + if (!runtime) { + errors.push(`runtimes:${expectation.runtimeId}:missing`); + continue; + } + if (runtime.captureStatus !== expectation.status) { + errors.push(`runtimes:${expectation.runtimeId}:captureStatus:expected ${expectation.status} got ${runtime.captureStatus || ""}`); + } + } for (const expectedScenario of expectations.scenarios) { if (!providers.some(provider => provider.scenarioId === expectedScenario)) errors.push(`providers:scenario:${expectedScenario}:missing`); } @@ -194,6 +240,10 @@ function parseArguments(args) { providerCommands: [], providerReceiptStates: [], runtimes: [], + runtimeStatuses: [], + runtimeFeatures: [], + runtimeCommandSchemas: [], + runtimeCaptureStatuses: [], scenarios: [], splitTargets: [], }; @@ -210,6 +260,10 @@ function parseArguments(args) { ["--expect-provider-surface", expectations.providerSurfaces], ["--expect-provider-command", expectations.providerCommands], ["--expect-provider-receipt-state", expectations.providerReceiptStates], + ["--expect-runtime-status", expectations.runtimeStatuses], + ["--expect-runtime-feature", expectations.runtimeFeatures], + ["--expect-runtime-command-schema", expectations.runtimeCommandSchemas], + ["--expect-runtime-capture-status", expectations.runtimeCaptureStatuses], ]); for (let index = 1; index < args.length; index += 1) { @@ -234,6 +288,14 @@ function parseArguments(args) { target.push(parseProviderExpectation(value, "command")); } else if (option === "--expect-provider-receipt-state") { target.push(parseProviderExpectation(value, "state")); + } else if (option === "--expect-runtime-status") { + target.push(parseRuntimeExpectation(value, "status")); + } else if (option === "--expect-runtime-feature") { + target.push(parseRuntimeExpectation(value, "feature")); + } else if (option === "--expect-runtime-command-schema") { + target.push(parseRuntimeExpectation(value, "schema")); + } else if (option === "--expect-runtime-capture-status") { + target.push(parseRuntimeExpectation(value, "status")); } else { target.push(value); } @@ -266,3 +328,30 @@ function parseProviderExpectation(value, field) { [field]: value.slice(separator + 1), }; } + +function parseRuntimeExpectation(value, field) { + const separator = value.indexOf(":"); + if (separator <= 0 || separator === value.length - 1) { + console.error(`Expected runtime ${field} in form, got: ${value}`); + process.exit(2); + } + return { + runtimeId: value.slice(0, separator), + [field]: value.slice(separator + 1), + }; +} + +function mergeRuntimeRecords(topLevelRuntimes, runtimeTargets) { + const byId = new Map(); + for (const runtime of runtimeTargets) { + if (runtime.runtimeId) byId.set(runtime.runtimeId, runtime); + } + for (const runtime of topLevelRuntimes) { + if (!runtime.runtimeId) continue; + byId.set(runtime.runtimeId, { + ...(byId.get(runtime.runtimeId) || {}), + ...runtime, + }); + } + return [...byId.values()]; +} diff --git a/tools/parity/run-parity.mjs b/tools/parity/run-parity.mjs index 0630c9f..44c684f 100644 --- a/tools/parity/run-parity.mjs +++ b/tools/parity/run-parity.mjs @@ -1373,6 +1373,7 @@ function buildConformanceExport(report) { capabilityManifestPath: runtime.capabilityManifest?.manifestPath || "", capabilityManifestErrors: runtime.capabilityManifestErrors || [], commandTransportSchema: runtime.commandTransportSmoke?.schema || "", + captureStatus: runtime.capture?.status || "", lifecycle: runtime.lifecycle, })), splitTargets: report.splitTargets || [],