From 8de1dced44e231c1204503d6b614716c95cf6394 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 9 Jul 2026 11:53:46 +0100 Subject: [PATCH] Add runtime split handoff schema --- docs/eve-migration-roadmap.md | 5 + docs/parity-testing-harness.md | 4 + ...cult.eve.conformance_export.v1.schema.json | 3 + ...t.eve.runtime_split_handoff.v1.schema.json | 133 ++++++++++++++++++ scripts/run-conformance-consumer-smoke.ps1 | 1 + scripts/run-eveconformance-handoff-smoke.ps1 | 4 +- ...ntime-owner-conformance-consumer-smoke.ps1 | 1 + tools/conformance/eveconformance-handoff.json | 4 +- tools/parity/parity-manifest.json | 1 + tools/parity/run-parity.mjs | 17 +++ 10 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 schemas/gamecult.eve.runtime_split_handoff.v1.schema.json diff --git a/docs/eve-migration-roadmap.md b/docs/eve-migration-roadmap.md index d873557..85f0936 100644 --- a/docs/eve-migration-roadmap.md +++ b/docs/eve-migration-roadmap.md @@ -418,6 +418,11 @@ Recently cut: and command transport, does not treat Aetheria product paths as generic runtime source, names the required Eve contracts, and keeps capture as an external proof until EveUnity owns it. +- Runtime split handoff documents now have a catalog-backed schema, + `gamecult.eve.runtime_split_handoff.v1`. EveConformance exports that schema + so runtime owner repos can consume split target, move set, contract input, + forbidden import, and external proof shape without trusting only ad hoc smoke + script checks. - EveElectron now has a pending split target and handoff at `runtimes/incubating/eve-electron/eveelectron-split-handoff.json`. It does claim a provider-agnostic Electron shell surface-tree projection and command diff --git a/docs/parity-testing-harness.md b/docs/parity-testing-harness.md index 77db111..25a4c8f 100644 --- a/docs/parity-testing-harness.md +++ b/docs/parity-testing-harness.md @@ -216,6 +216,10 @@ Aetheria's current Electron client, and `replacement-required` records mark move sets where no generic runtime source exists yet. Consumer smokes can assert them with `--expect-split-handoff-move `. +Runtime split handoff documents themselves use +`gamecult.eve.runtime_split_handoff.v1`; the parity runner validates the +handoff document shape and exports any `splitHandoffErrors` on the runtime +record. After generating the parity report, `run-parity-harness.ps1` copies `artifacts/conformance/latest` into diff --git a/schemas/gamecult.eve.conformance_export.v1.schema.json b/schemas/gamecult.eve.conformance_export.v1.schema.json index 290b951..9382873 100644 --- a/schemas/gamecult.eve.conformance_export.v1.schema.json +++ b/schemas/gamecult.eve.conformance_export.v1.schema.json @@ -1023,6 +1023,9 @@ "splitHandoffPath": { "type": "string" }, + "splitHandoffErrors": { + "type": "array" + }, "splitHandoffExportPath": { "type": "string" }, diff --git a/schemas/gamecult.eve.runtime_split_handoff.v1.schema.json b/schemas/gamecult.eve.runtime_split_handoff.v1.schema.json new file mode 100644 index 0000000..8fc7aab --- /dev/null +++ b/schemas/gamecult.eve.runtime_split_handoff.v1.schema.json @@ -0,0 +1,133 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "gamecult.eve.runtime_split_handoff.v1", + "title": "Eve Runtime Split Handoff", + "type": "object", + "required": [ + "schema", + "splitTarget", + "ownerRepo", + "currentHostRepo", + "runtimeId", + "sourcePackageRoot", + "destinationPackageRoot", + "authorityBoundary", + "moveSets", + "contractInputs", + "forbiddenImports", + "requiredExternalProofs" + ], + "properties": { + "schema": { + "const": "gamecult.eve.runtime_split_handoff.v1" + }, + "splitTarget": { + "type": "string", + "minLength": 1 + }, + "ownerRepo": { + "type": "string", + "minLength": 1 + }, + "currentHostRepo": { + "type": "string", + "minLength": 1 + }, + "runtimeId": { + "type": "string", + "minLength": 1 + }, + "packageName": { + "type": "string" + }, + "sourcePackageRoot": { + "type": "string", + "minLength": 1 + }, + "destinationPackageRoot": { + "type": "string", + "minLength": 1 + }, + "authorityBoundary": { + "type": "string", + "minLength": 1 + }, + "moveSets": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "id", + "stage", + "destinationOwner", + "replacementProof" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "stage": { + "type": "string", + "minLength": 1 + }, + "currentPaths": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "observedProviderPaths": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "replacementRequired": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "destinationOwner": { + "type": "string", + "minLength": 1 + }, + "replacementProof": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": true + } + }, + "contractInputs": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "forbiddenImports": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "requiredExternalProofs": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "additionalProperties": true +} diff --git a/scripts/run-conformance-consumer-smoke.ps1 b/scripts/run-conformance-consumer-smoke.ps1 index 8c565a6..1436fd2 100644 --- a/scripts/run-conformance-consumer-smoke.ps1 +++ b/scripts/run-conformance-consumer-smoke.ps1 @@ -40,6 +40,7 @@ try { --expect-schema gamecult.eve.plugin_abi.response.v1 ` --expect-schema gamecult.eve.runtime_release_request.v1 ` --expect-schema gamecult.eve.runtime_capture_request.v1 ` + --expect-schema gamecult.eve.runtime_split_handoff.v1 ` --expect-capability-gap runtime:Fensalir:direct2d:capture:missing ` --expect-world-lowering-coverage aetheria:aetheria.daemon.game:web-reference:claimed:Eve:web ` --expect-world-lowering-coverage aetheria:aetheria.daemon.game:unity-uitoolkit:claimed:EveUnity:unity-uitoolkit ` diff --git a/scripts/run-eveconformance-handoff-smoke.ps1 b/scripts/run-eveconformance-handoff-smoke.ps1 index 4c5bd64..ca5694c 100644 --- a/scripts/run-eveconformance-handoff-smoke.ps1 +++ b/scripts/run-eveconformance-handoff-smoke.ps1 @@ -87,7 +87,8 @@ foreach ($contract in @( "gamecult.eve.runtime_release_request.v1", "gamecult.eve.runtime_capture_request.v1", "gamecult.eve.provider_advertisement.v1", - "gamecult.eve.runtime_capability.v1" + "gamecult.eve.runtime_capability.v1", + "gamecult.eve.runtime_split_handoff.v1" )) { if (-not (@($handoff.contractInputs) -contains $contract)) { throw "EveConformance handoff missing contract input: $contract" @@ -118,6 +119,7 @@ if (Test-Path -LiteralPath (Join-Path $absoluteExportDirectory "index.json")) { "gamecult.eve.provider_scenario.v1", "gamecult.eve.provider_advertisement.v1", "gamecult.eve.runtime_capability.v1", + "gamecult.eve.runtime_split_handoff.v1", "gamecult.eve.command_receipt.v1", "gamecult.eve.plugin.v1", "gamecult.eve.plugin_receipt.v1", diff --git a/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 b/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 index b1bc2f0..13ac2a8 100644 --- a/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 +++ b/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 @@ -43,6 +43,7 @@ node $consumerScript $consumerExport ` --expect-runtime direct2d ` --expect-schema gamecult.eve.runtime_release_request.v1 ` --expect-schema gamecult.eve.runtime_capture_request.v1 ` + --expect-schema gamecult.eve.runtime_split_handoff.v1 ` --expect-runtime-status web:active ` --expect-runtime-status windows-flutter:active ` --expect-runtime-status linux-flutter:active ` diff --git a/tools/conformance/eveconformance-handoff.json b/tools/conformance/eveconformance-handoff.json index 7a11279..52815e0 100644 --- a/tools/conformance/eveconformance-handoff.json +++ b/tools/conformance/eveconformance-handoff.json @@ -25,6 +25,7 @@ "schemas/gamecult.eve.provider_scenario.v1.schema.json", "schemas/gamecult.eve.provider_advertisement.v1.schema.json", "schemas/gamecult.eve.runtime_capability.v1.schema.json", + "schemas/gamecult.eve.runtime_split_handoff.v1.schema.json", "schemas/gamecult.eve.command_receipt.v1.schema.json", "schemas/gamecult.eve.plugin.v1.schema.json", "schemas/gamecult.eve.plugin_receipt.v1.schema.json", @@ -80,7 +81,8 @@ "gamecult.eve.runtime_release_request.v1", "gamecult.eve.runtime_capture_request.v1", "gamecult.eve.provider_advertisement.v1", - "gamecult.eve.runtime_capability.v1" + "gamecult.eve.runtime_capability.v1", + "gamecult.eve.runtime_split_handoff.v1" ], "forbiddenImports": [ "provider internals as conformance truth", diff --git a/tools/parity/parity-manifest.json b/tools/parity/parity-manifest.json index ae7fc5c..da709bf 100644 --- a/tools/parity/parity-manifest.json +++ b/tools/parity/parity-manifest.json @@ -19,6 +19,7 @@ "gamecult.eve.plugin_abi_fixture.v1": "schemas/gamecult.eve.plugin_abi_fixture.v1.schema.json", "gamecult.eve.plugin_advertisement.v1": "schemas/gamecult.eve.plugin_advertisement.v1.schema.json", "gamecult.eve.runtime_capability.v1": "schemas/gamecult.eve.runtime_capability.v1.schema.json", + "gamecult.eve.runtime_split_handoff.v1": "schemas/gamecult.eve.runtime_split_handoff.v1.schema.json", "gamecult.eve.runtime_release_request.v1": "schemas/gamecult.eve.runtime_release_request.v1.schema.json", "gamecult.eve.runtime_capture_request.v1": "schemas/gamecult.eve.runtime_capture_request.v1.schema.json", "gamecult.eve.provider_scenario.v1": "schemas/gamecult.eve.provider_scenario.v1.schema.json", diff --git a/tools/parity/run-parity.mjs b/tools/parity/run-parity.mjs index 05e2e0e..2e5ac8b 100644 --- a/tools/parity/run-parity.mjs +++ b/tools/parity/run-parity.mjs @@ -305,6 +305,7 @@ async function evaluateRuntime(runtime, fixtureResults) { const capabilityManifestErrors = await validateRuntimeCapabilityManifest(runtime); const capabilityManifestDocument = await readRuntimeCapabilityManifestDocument(runtime); const splitHandoffPath = await readRuntimeSplitHandoffPath(runtime); + const splitHandoffErrors = await validateRuntimeSplitHandoff(runtime, splitHandoffPath); const localProviderCatalogErrors = await validateRuntimeLocalProviderCatalog(runtime); const missingIncubationFields = requiredIncubationFields(runtime).filter(field => !runtime[field]); const pluginCapabilityGaps = collectPluginCapabilityGaps(runtime, fixtureResults); @@ -316,6 +317,7 @@ async function evaluateRuntime(runtime, fixtureResults) { if (runtime.kind === "active" && missingRequiredFeatures.length) status = "missing-required-feature"; if (runtime.kind === "active" && commandTransportSmokeErrors.length) status = "missing-command-transport-smoke"; if (runtime.kind === "active" && capabilityManifestErrors.length) status = "invalid-runtime-capability"; + if (runtime.kind === "active" && splitHandoffErrors.length) status = "invalid-runtime-split-handoff"; if (runtime.kind === "active" && localProviderCatalogErrors.length) status = "invalid-local-provider-catalog"; if (runtime.kind === "active" && missingIncubationFields.length) status = "missing-incubation-metadata"; if (runtime.kind === "active" && pluginCapabilityGaps.length && status === "active") status = "active-with-capability-gaps"; @@ -354,6 +356,7 @@ async function evaluateRuntime(runtime, fixtureResults) { capabilityManifest: runtime.capabilityManifest || null, capabilityManifestErrors, splitHandoffPath, + splitHandoffErrors, localProviderCatalog: runtime.localProviderCatalog || null, localProviderCatalogErrors, lifecycle: runtime.lifecycle || capabilityManifestDocument?.lifecycle || null, @@ -437,6 +440,18 @@ async function readRuntimeSplitHandoffPath(runtime) { } } +async function validateRuntimeSplitHandoff(runtime, splitHandoffPath) { + if (!splitHandoffPath) return []; + return validateJsonDocument( + manifest.schemas?.["gamecult.eve.runtime_split_handoff.v1"], + splitHandoffPath, + { + schema: "gamecult.eve.runtime_split_handoff.v1", + runtimeId: runtime.id, + }, + ); +} + function normalizePath(candidate) { return candidate.replaceAll("\\", "/").replace(/^\.\//, ""); } @@ -1804,6 +1819,7 @@ function buildConformanceExport(report) { capabilityManifestPath: runtime.capabilityManifest?.manifestPath || "", capabilityManifestErrors: runtime.capabilityManifestErrors || [], splitHandoffPath: runtime.splitHandoffPath || "", + splitHandoffErrors: runtime.splitHandoffErrors || [], splitHandoffExportPath: makeHandoffExportPath("runtime", runtime.id, runtime.splitHandoffPath || ""), commandTransportSchema: runtimeCommandTransportSchema(runtime), captureStatus: runtime.capture?.status || "", @@ -1975,6 +1991,7 @@ function collectCapabilityGaps(report) { ...(runtime.missingRequiredFeatures || []).map(id => `feature:${id}`), ...(runtime.commandTransportSmokeErrors || []).map(id => `command-smoke:${id}`), ...(runtime.capabilityManifestErrors || []).map(id => `runtime-capability:${id}`), + ...(runtime.splitHandoffErrors || []).map(id => `runtime-split-handoff:${id}`), ...(runtime.localProviderCatalogErrors || []).map(id => `local-provider-catalog:${id}`), ...(runtime.pluginCapabilityGaps || []).map(id => `plugin-capability:${id}`), ]) {