Schema-back owner handoff contracts

This commit is contained in:
Meta Vens 2026-07-09 12:01:04 +01:00
commit 811a4da395
12 changed files with 438 additions and 6 deletions

View file

@ -423,6 +423,13 @@ Recently cut:
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.
- Plugin, provider, and conformance handoff documents are now schema-backed as
`gamecult.eve.plugin_handoff.v1`, `gamecult.eve.provider_handoff.v1`, and
`gamecult.eve.conformance_handoff.v1`. The parity runner validates the
documents, exports the schema catalog entries, and owner-consumer smokes now
demand those contracts explicitly, so Sai, Norn, EvePlugins, Aetheria, and
EveConformance can consume handoff shape without depending on Eve's local
smoke script folklore.
- 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

View file

@ -220,6 +220,10 @@ 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.
Plugin, provider, and EveConformance handoff documents are also schema-backed:
`gamecult.eve.plugin_handoff.v1`, `gamecult.eve.provider_handoff.v1`, and
`gamecult.eve.conformance_handoff.v1` are exported through the schema catalog.
Owner consumer smokes assert those schemas before trusting handoff paths.
After generating the parity report, `run-parity-harness.ps1` copies
`artifacts/conformance/latest` into

View file

@ -0,0 +1,98 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "gamecult.eve.conformance_handoff.v1",
"title": "Eve Conformance Handoff",
"type": "object",
"required": [
"schema",
"splitTarget",
"ownerRepo",
"currentHostRepo",
"authorityBoundary",
"moveSets",
"contractInputs",
"forbiddenImports",
"requiredExternalProofs"
],
"properties": {
"schema": {
"const": "gamecult.eve.conformance_handoff.v1"
},
"splitTarget": {
"type": "string",
"minLength": 1
},
"ownerRepo": {
"type": "string",
"minLength": 1
},
"currentHostRepo": {
"type": "string",
"minLength": 1
},
"authorityBoundary": {
"type": "string",
"minLength": 1
},
"moveSets": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": [
"id",
"destinationOwner",
"currentPaths",
"replacementProof"
],
"properties": {
"id": {
"type": "string",
"minLength": 1
},
"destinationOwner": {
"type": "string",
"minLength": 1
},
"currentPaths": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"replacementProof": {
"type": "string",
"minLength": 1
}
},
"additionalProperties": true
}
},
"contractInputs": {
"type": "array",
"minItems": 1,
"items": {
"type": "string",
"minLength": 1
}
},
"forbiddenImports": {
"type": "array",
"minItems": 1,
"items": {
"type": "string",
"minLength": 1
}
},
"requiredExternalProofs": {
"type": "array",
"minItems": 1,
"items": {
"type": "string",
"minLength": 1
}
}
},
"additionalProperties": true
}

View file

@ -0,0 +1,118 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "gamecult.eve.plugin_handoff.v1",
"title": "Eve Plugin Handoff",
"type": "object",
"required": [
"schema",
"pluginId",
"ownerRepo",
"currentHostRepo",
"splitTarget",
"manifestPath",
"advertisementPath",
"abiFixturePath",
"authorityBoundary",
"moveSets",
"contractInputs",
"forbiddenImports",
"requiredExternalProofs"
],
"properties": {
"schema": {
"const": "gamecult.eve.plugin_handoff.v1"
},
"pluginId": {
"type": "string",
"minLength": 1
},
"ownerRepo": {
"type": "string",
"minLength": 1
},
"currentHostRepo": {
"type": "string",
"minLength": 1
},
"splitTarget": {
"type": "string",
"minLength": 1
},
"manifestPath": {
"type": "string",
"minLength": 1
},
"advertisementPath": {
"type": "string",
"minLength": 1
},
"abiFixturePath": {
"type": "string",
"minLength": 1
},
"authorityBoundary": {
"type": "string",
"minLength": 1
},
"moveSets": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": [
"id",
"destinationOwner",
"currentPaths",
"replacementProof"
],
"properties": {
"id": {
"type": "string",
"minLength": 1
},
"destinationOwner": {
"type": "string",
"minLength": 1
},
"currentPaths": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"replacementProof": {
"type": "string",
"minLength": 1
}
},
"additionalProperties": true
}
},
"contractInputs": {
"type": "array",
"minItems": 1,
"items": {
"type": "string",
"minLength": 1
}
},
"forbiddenImports": {
"type": "array",
"minItems": 1,
"items": {
"type": "string",
"minLength": 1
}
},
"requiredExternalProofs": {
"type": "array",
"minItems": 1,
"items": {
"type": "string",
"minLength": 1
}
}
},
"additionalProperties": true
}

View file

@ -0,0 +1,117 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "gamecult.eve.provider_handoff.v1",
"title": "Eve Provider Handoff",
"type": "object",
"required": [
"schema",
"providerId",
"ownerRepo",
"currentHostRepo",
"advertisementPath",
"scenarioPath",
"fixtureIds",
"authorityBoundary",
"moveSets",
"contractInputs",
"forbiddenImports",
"requiredExternalProofs"
],
"properties": {
"schema": {
"const": "gamecult.eve.provider_handoff.v1"
},
"providerId": {
"type": "string",
"minLength": 1
},
"ownerRepo": {
"type": "string",
"minLength": 1
},
"currentHostRepo": {
"type": "string",
"minLength": 1
},
"advertisementPath": {
"type": "string",
"minLength": 1
},
"scenarioPath": {
"type": "string",
"minLength": 1
},
"fixtureIds": {
"type": "array",
"minItems": 1,
"items": {
"type": "string",
"minLength": 1
}
},
"authorityBoundary": {
"type": "string",
"minLength": 1
},
"moveSets": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": [
"id",
"destinationOwner",
"currentPaths",
"replacementProof"
],
"properties": {
"id": {
"type": "string",
"minLength": 1
},
"destinationOwner": {
"type": "string",
"minLength": 1
},
"currentPaths": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"replacementProof": {
"type": "string",
"minLength": 1
}
},
"additionalProperties": true
}
},
"contractInputs": {
"type": "array",
"minItems": 1,
"items": {
"type": "string",
"minLength": 1
}
},
"forbiddenImports": {
"type": "array",
"minItems": 1,
"items": {
"type": "string",
"minLength": 1
}
},
"requiredExternalProofs": {
"type": "array",
"minItems": 1,
"items": {
"type": "string",
"minLength": 1
}
}
},
"additionalProperties": true
}

View file

@ -38,6 +38,7 @@ Push-Location $AetheriaRoot
try {
node $consumerScript $consumerExport `
--expect-pack provider `
--expect-schema gamecult.eve.provider_handoff.v1 `
--expect-fixture aetheria-world `
--expect-provider aetheria `
--expect-scenario aetheria-world-command-replay `

View file

@ -33,6 +33,7 @@ try {
node .\tools\conformance\consume-export.mjs $consumerExport `
--expect-capability-matrix `
--expect-schema gamecult.eve.conformance_export.v1 `
--expect-schema gamecult.eve.conformance_handoff.v1 `
--expect-schema gamecult.eve.capability_matrix.v1 `
--expect-schema gamecult.eve.command_receipt.v1 `
--expect-schema gamecult.eve.plugin_receipt.v1 `
@ -41,6 +42,8 @@ try {
--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-schema gamecult.eve.plugin_handoff.v1 `
--expect-schema gamecult.eve.provider_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 `

View file

@ -75,18 +75,21 @@ foreach ($contract in @(
"gamecult.eve.command.v1",
"gamecult.eve.command_receipt.v1",
"gamecult.eve.conformance_export.v1",
"gamecult.eve.conformance_handoff.v1",
"gamecult.eve.capability_matrix.v1",
"gamecult.eve.conformance_fixture.v1",
"gamecult.eve.provider_scenario.v1",
"gamecult.eve.plugin.v1",
"gamecult.eve.plugin_receipt.v1",
"gamecult.eve.plugin_advertisement.v1",
"gamecult.eve.plugin_handoff.v1",
"gamecult.eve.plugin_abi_fixture.v1",
"gamecult.eve.plugin_abi.request.v1",
"gamecult.eve.plugin_abi.response.v1",
"gamecult.eve.runtime_release_request.v1",
"gamecult.eve.runtime_capture_request.v1",
"gamecult.eve.provider_advertisement.v1",
"gamecult.eve.provider_handoff.v1",
"gamecult.eve.runtime_capability.v1",
"gamecult.eve.runtime_split_handoff.v1"
)) {
@ -114,16 +117,19 @@ if (Test-Path -LiteralPath (Join-Path $absoluteExportDirectory "index.json")) {
}
foreach ($schemaId in @(
"gamecult.eve.conformance_export.v1",
"gamecult.eve.conformance_handoff.v1",
"gamecult.eve.capability_matrix.v1",
"gamecult.eve.conformance_fixture.v1",
"gamecult.eve.provider_scenario.v1",
"gamecult.eve.provider_advertisement.v1",
"gamecult.eve.provider_handoff.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",
"gamecult.eve.plugin_advertisement.v1",
"gamecult.eve.plugin_handoff.v1",
"gamecult.eve.plugin_abi_fixture.v1",
"gamecult.eve.plugin_abi.request.v1",
"gamecult.eve.plugin_abi.response.v1",

View file

@ -34,6 +34,7 @@ node $consumerScript $consumerExport `
--expect-pack plugin `
--expect-schema gamecult.eve.plugin.v1 `
--expect-schema gamecult.eve.plugin_advertisement.v1 `
--expect-schema gamecult.eve.plugin_handoff.v1 `
--expect-schema gamecult.eve.plugin_abi_fixture.v1 `
--expect-schema gamecult.eve.plugin_abi.request.v1 `
--expect-schema gamecult.eve.plugin_abi.response.v1 `

View file

@ -20,16 +20,19 @@
"destinationOwner": "EveConformance",
"currentPaths": [
"schemas/gamecult.eve.conformance_export.v1.schema.json",
"schemas/gamecult.eve.conformance_handoff.v1.schema.json",
"schemas/gamecult.eve.capability_matrix.v1.schema.json",
"schemas/gamecult.eve.conformance_fixture.v1.schema.json",
"schemas/gamecult.eve.provider_scenario.v1.schema.json",
"schemas/gamecult.eve.provider_advertisement.v1.schema.json",
"schemas/gamecult.eve.provider_handoff.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",
"schemas/gamecult.eve.plugin_advertisement.v1.schema.json",
"schemas/gamecult.eve.plugin_handoff.v1.schema.json",
"schemas/gamecult.eve.plugin_abi_fixture.v1.schema.json",
"schemas/gamecult.eve.plugin_abi.request.v1.schema.json",
"schemas/gamecult.eve.plugin_abi.response.v1.schema.json",
@ -69,18 +72,21 @@
"gamecult.eve.command.v1",
"gamecult.eve.command_receipt.v1",
"gamecult.eve.conformance_export.v1",
"gamecult.eve.conformance_handoff.v1",
"gamecult.eve.capability_matrix.v1",
"gamecult.eve.conformance_fixture.v1",
"gamecult.eve.provider_scenario.v1",
"gamecult.eve.plugin.v1",
"gamecult.eve.plugin_receipt.v1",
"gamecult.eve.plugin_advertisement.v1",
"gamecult.eve.plugin_handoff.v1",
"gamecult.eve.plugin_abi_fixture.v1",
"gamecult.eve.plugin_abi.request.v1",
"gamecult.eve.plugin_abi.response.v1",
"gamecult.eve.runtime_release_request.v1",
"gamecult.eve.runtime_capture_request.v1",
"gamecult.eve.provider_advertisement.v1",
"gamecult.eve.provider_handoff.v1",
"gamecult.eve.runtime_capability.v1",
"gamecult.eve.runtime_split_handoff.v1"
],

View file

@ -18,12 +18,15 @@
"gamecult.eve.plugin_abi.response.v1": "schemas/gamecult.eve.plugin_abi.response.v1.schema.json",
"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.plugin_handoff.v1": "schemas/gamecult.eve.plugin_handoff.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",
"gamecult.eve.provider_advertisement.v1": "schemas/gamecult.eve.provider_advertisement.v1.schema.json"
"gamecult.eve.provider_advertisement.v1": "schemas/gamecult.eve.provider_advertisement.v1.schema.json",
"gamecult.eve.provider_handoff.v1": "schemas/gamecult.eve.provider_handoff.v1.schema.json",
"gamecult.eve.conformance_handoff.v1": "schemas/gamecult.eve.conformance_handoff.v1.schema.json"
},
"conformancePacks": [
{

View file

@ -28,11 +28,15 @@ const pluginResults = await Promise.all((manifest.pluginManifests || []).map(plu
const providerResults = await Promise.all((manifest.providerAdvertisements || []).map(provider => evaluateProvider(provider, fixtureResults, pluginResults)));
const runtimeResults = await Promise.all(manifest.runtimes.map(runtime => evaluateRuntime(runtime, fixtureResults)));
const splitTargetResults = evaluateSplitTargets(manifest.splitTargets || [], runtimeResults);
const conformanceHandoffErrors = await validateConformanceHandoff(manifest.repoStrategy?.conformanceHandoffPath || "");
const report = {
schema: "gamecult.eve.parity_report.v1",
generatedAt: new Date().toISOString(),
manifest: path.relative(repoRoot, manifestPath).replaceAll("\\", "/"),
repoStrategy: manifest.repoStrategy || {},
repoStrategy: {
...(manifest.repoStrategy || {}),
conformanceHandoffErrors,
},
conformancePacks: manifest.conformancePacks || [],
responsiveCases: manifest.responsiveCases || [],
summary: summarize(fixtureResults, runtimeResults, pluginResults, providerResults, splitTargetResults),
@ -60,7 +64,9 @@ console.log(`Parity report: ${path.relative(repoRoot, path.join(runDirectory, "p
console.log(`Conformance export: ${path.relative(repoRoot, path.join(conformanceOutputRoot, "latest", "index.md"))}`);
for (const error of conformanceExportErrors) console.error(`Conformance export error: ${error}`);
if (report.summary.failedFixtures > 0) process.exitCode = 1;
if (report.summary.failedPlugins > 0) process.exitCode = 1;
if (report.summary.failedProviders > 0) process.exitCode = 1;
if (conformanceHandoffErrors.length > 0) process.exitCode = 1;
if (conformanceExportErrors.length > 0) process.exitCode = 1;
async function evaluateFixture(fixture) {
@ -452,6 +458,19 @@ async function validateRuntimeSplitHandoff(runtime, splitHandoffPath) {
);
}
async function validateConformanceHandoff(handoffPath) {
if (!handoffPath) return ["handoffPath:missing"];
return validateJsonDocument(
manifest.schemas?.["gamecult.eve.conformance_handoff.v1"],
handoffPath,
{
schema: "gamecult.eve.conformance_handoff.v1",
splitTarget: "EveConformance",
ownerRepo: "EveConformance",
},
);
}
function normalizePath(candidate) {
return candidate.replaceAll("\\", "/").replace(/^\.\//, "");
}
@ -482,6 +501,7 @@ async function evaluatePlugin(plugin, fixtureResults) {
const abiOperations = await readPluginAbiOperations(plugin.abiFixturePath);
const abiOperationContracts = await readPluginAbiOperationContracts(plugin.abiFixturePath);
const abiErrors = await validatePluginAbiFixture(plugin);
const handoffErrors = await validatePluginHandoff(plugin);
const status = missingPaths.length
? "missing-body"
: missingRequiredFixtures.length
@ -496,9 +516,11 @@ async function evaluatePlugin(plugin, fixtureResults) {
? "invalid-plugin-runtime-boundary"
: abiErrors.length
? "invalid-plugin-abi-fixture"
: plugin.kind === "incubating"
? "incubating"
: "external-owner-planned";
: handoffErrors.length
? "invalid-plugin-handoff"
: plugin.kind === "incubating"
? "incubating"
: "external-owner-planned";
return {
pluginId: plugin.pluginId,
@ -518,6 +540,7 @@ async function evaluatePlugin(plugin, fixtureResults) {
manifestPath: plugin.manifestPath || "",
abiFixturePath: plugin.abiFixturePath || "",
handoffPath: plugin.handoffPath || "",
handoffErrors,
abiOperations,
abiOperationContracts,
advertisementSchemaPath: plugin.advertisementSchemaPath || "",
@ -532,6 +555,19 @@ async function evaluatePlugin(plugin, fixtureResults) {
};
}
async function validatePluginHandoff(plugin) {
if (!plugin.handoffPath) return [];
return validateJsonDocument(
manifest.schemas?.["gamecult.eve.plugin_handoff.v1"],
plugin.handoffPath,
{
schema: "gamecult.eve.plugin_handoff.v1",
pluginId: plugin.pluginId,
ownerRepo: plugin.ownerRepo,
},
);
}
async function readPluginAbiOperations(abiFixturePath) {
if (!abiFixturePath) return [];
try {
@ -703,6 +739,7 @@ async function evaluateProvider(provider, fixtureResults, pluginResults) {
const missingCommands = (provider.expectedCommands || []).filter(command => !commandIds.includes(command));
const scenarioErrors = await validateProviderScenario(provider, advertisement, fixtureResults, advertisedSchemaIds, surfaceIds, commandIds);
const scenario = scenarioErrors.length || !provider.scenarioPath ? null : await readJsonDocument(provider.scenarioPath);
const handoffErrors = await validateProviderHandoff(provider);
const status = missingPaths.length
? "missing-body"
: missingRequiredFixtures.length
@ -715,7 +752,9 @@ async function evaluateProvider(provider, fixtureResults, pluginResults) {
? "capability-gap"
: scenarioErrors.length
? "invalid-provider-scenario"
: "advertised";
: handoffErrors.length
? "invalid-provider-handoff"
: "advertised";
return {
providerId: provider.providerId,
@ -730,6 +769,7 @@ async function evaluateProvider(provider, fixtureResults, pluginResults) {
advertisementPath: provider.advertisementPath || "",
scenarioPath: provider.scenarioPath || "",
handoffPath: provider.handoffPath || "",
handoffErrors,
advertisementErrors,
scenarioErrors,
expectedPaths,
@ -758,6 +798,19 @@ async function evaluateProvider(provider, fixtureResults, pluginResults) {
};
}
async function validateProviderHandoff(provider) {
if (!provider.handoffPath) return [];
return validateJsonDocument(
manifest.schemas?.["gamecult.eve.provider_handoff.v1"],
provider.handoffPath,
{
schema: "gamecult.eve.provider_handoff.v1",
providerId: provider.providerId,
ownerRepo: provider.ownerRepo,
},
);
}
function collectProviderPluginRequirements(advertisement) {
const requirements = [];
for (const surface of advertisement.surfaces || []) {
@ -1687,6 +1740,7 @@ function summarize(fixtures, runtimes, plugins, providers, splitTargets) {
failedFixtures: fixtures.filter(fixture => fixture.status !== "pass").length,
totalPlugins: plugins.length,
healthyPlugins: plugins.filter(plugin => plugin.status === "incubating" || plugin.status === "external-owner-planned").length,
failedPlugins: plugins.filter(plugin => plugin.status !== "incubating" && plugin.status !== "external-owner-planned").length,
totalProviders: providers.length,
advertisedProviders: providers.filter(provider => provider.status === "advertised").length,
failedProviders: providers.filter(provider => provider.status !== "advertised").length,
@ -1784,6 +1838,7 @@ function buildConformanceExport(report) {
manifestPath: plugin.manifestPath,
advertisementPath: plugin.advertisementPath,
handoffPath: plugin.handoffPath,
handoffErrors: plugin.handoffErrors || [],
handoffExportPath: makeHandoffExportPath("plugin", plugin.pluginId, plugin.handoffPath),
abiFixturePath: plugin.abiFixturePath,
abiOperations: plugin.abiOperations || [],
@ -1799,6 +1854,7 @@ function buildConformanceExport(report) {
advertisementPath: provider.advertisementPath,
scenarioPath: provider.scenarioPath,
handoffPath: provider.handoffPath,
handoffErrors: provider.handoffErrors || [],
handoffExportPath: makeHandoffExportPath("provider", provider.providerId, provider.handoffPath),
scenarioId: provider.scenarioId,
receiptStates: provider.scenarioReceiptStates || [],
@ -1938,6 +1994,7 @@ function collectCapabilityGaps(report) {
...(plugin.advertisementErrors || []).map(id => `advertisement:${id}`),
...(plugin.runtimeBoundaryErrors || []).map(id => `runtime-boundary:${id}`),
...(plugin.abiErrors || []).map(id => `abi:${id}`),
...(plugin.handoffErrors || []).map(id => `handoff:${id}`),
]) {
addGap({
kind: "plugin",
@ -1961,6 +2018,7 @@ function collectCapabilityGaps(report) {
...(provider.missingSurfaceKinds || []).map(id => `surface-kind:${id}`),
...(provider.missingCommands || []).map(id => `command:${id}`),
...(provider.pluginRequirementErrors || []).map(id => `plugin:${id}`),
...(provider.handoffErrors || []).map(id => `handoff:${id}`),
]) {
addGap({
kind: "provider",
@ -2037,6 +2095,16 @@ function collectCapabilityGaps(report) {
}
}
for (const error of report.repoStrategy?.conformanceHandoffErrors || []) {
addGap({
kind: "conformance",
ownerRepo: "EveConformance",
subjectId: "EveConformance",
gap: `handoff:${error}`,
severity: "blocker",
});
}
return gaps;
}