diff --git a/docs/eve-migration-roadmap.md b/docs/eve-migration-roadmap.md index 20105fa..fc8837e 100644 --- a/docs/eve-migration-roadmap.md +++ b/docs/eve-migration-roadmap.md @@ -308,6 +308,11 @@ Recently cut: `plugins[].abiOperationContracts`. Sai and Norn can consume operation-level `input` and `expect` contracts for `describe`, `validate`, `project`, `lower`, `measure`, and `apply` without reading Eve-local fixture files. +- Runtime/plugin projection gaps are now exported as + `runtimePluginProjectionGaps[]`. The runtime-owner smoke asserts Unity and + Electron plugin gaps as typed records, so missing Sai, Norn, or TeX projection + support is owned by the runtime split target and does not imply those plugin + semantics should move into Eve core or Aetheria product code. - EveUnity now has a pending Unity scene handoff at `runtimes/incubating/eve-unity-scene/eveunity-scene-split-handoff.json`. The active `unity-uitoolkit` runtime remains a semantic UI surface lowerer; diff --git a/docs/parity-testing-harness.md b/docs/parity-testing-harness.md index fc5c4dc..50bd59b 100644 --- a/docs/parity-testing-harness.md +++ b/docs/parity-testing-harness.md @@ -113,6 +113,12 @@ The same facts are also exported as `worldSurfaceLoweringGaps[]` so runtime owners can consume provider id, surface id, target id, owner repo, runtime id, split target, and runtime status as typed fields instead of parsing the generic gap text. +Declared runtime/plugin projection gaps are exported as +`runtimePluginProjectionGaps[]`. These records carry runtime id, runtime owner, +split target, plugin id, reason, severity, and fixture lists so EveUnity, +EveElectron, Sai, Norn, and TeX owners can distinguish "runtime cannot project +this plugin yet" from "plugin semantics belong in Eve core." Consumer smokes can +assert them with `--expect-runtime-plugin-gap `. The conformance export includes runtime `capabilityManifestPath` and `capabilityManifestErrors` fields so external consumers can distinguish a diff --git a/schemas/gamecult.eve.conformance_export.v1.schema.json b/schemas/gamecult.eve.conformance_export.v1.schema.json index 1733583..5ea91ff 100644 --- a/schemas/gamecult.eve.conformance_export.v1.schema.json +++ b/schemas/gamecult.eve.conformance_export.v1.schema.json @@ -95,6 +95,58 @@ "additionalProperties": true } }, + "runtimePluginProjectionGaps": { + "type": "array", + "items": { + "type": "object", + "required": [ + "runtimeId", + "runtimeStatus", + "ownerRepo", + "splitTarget", + "pluginId", + "severity", + "reason", + "requiredFixtures", + "pluginFixtures" + ], + "properties": { + "runtimeId": { + "type": "string", + "minLength": 1 + }, + "runtimeStatus": { + "type": "string" + }, + "ownerRepo": { + "type": "string", + "minLength": 1 + }, + "splitTarget": { + "type": "string" + }, + "pluginId": { + "type": "string", + "minLength": 1 + }, + "severity": { + "type": "string", + "minLength": 1 + }, + "reason": { + "type": "string", + "minLength": 1 + }, + "requiredFixtures": { + "type": "array" + }, + "pluginFixtures": { + "type": "array" + } + }, + "additionalProperties": true + } + }, "worldSurfaceLoweringGaps": { "type": "array", "items": { diff --git a/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 b/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 index 78d1324..fa64317 100644 --- a/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 +++ b/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 @@ -54,6 +54,11 @@ node $consumerScript $consumerExport ` --expect-runtime-world-target unity-uitoolkit:unity-uitoolkit ` --expect-runtime-world-field unity-uitoolkit:unity-uitoolkit:supportLevel:ui-toolkit-semantic-command-surface ` --expect-runtime-world-field unity-uitoolkit:unity-uitoolkit:ownership:runtime-lowers-provider-world-surface-without-owning-world-state ` + --expect-runtime-plugin-gap unity-uitoolkit:tex.math:EveUnity ` + --expect-runtime-plugin-gap unity-scene:sai.vn:EveUnity ` + --expect-runtime-plugin-gap unity-scene:norn.graph:EveUnity ` + --expect-runtime-plugin-gap electron-shell:sai.vn:EveElectron ` + --expect-runtime-plugin-gap electron-shell:norn.graph:EveElectron ` --expect-runtime-handoff unity-uitoolkit ` --expect-runtime-command-schema web:gamecult.eve.command.v1 ` --expect-runtime-command-schema windows-flutter:gamecult.eve.command.v1 ` diff --git a/tools/conformance/consume-export.mjs b/tools/conformance/consume-export.mjs index 54d3962..f217174 100644 --- a/tools/conformance/consume-export.mjs +++ b/tools/conformance/consume-export.mjs @@ -18,6 +18,7 @@ if (!exportDirectory) { " --expect-split-target ", " --expect-capability-matrix", " --expect-capability-gap ", + " --expect-runtime-plugin-gap ", " --expect-world-lowering-gap ", " --expect-conformance-handoff", " --expect-plugin-operation ", @@ -122,6 +123,7 @@ function validateIndex(index, directory, expectations, errors) { const runtimes = mergeRuntimeRecords(Array.isArray(index.runtimes) ? index.runtimes : [], exportedRuntimeTargets); const splitTargets = Array.isArray(index.splitTargets) ? index.splitTargets : []; const capabilityGaps = Array.isArray(index.capabilityGaps) ? index.capabilityGaps : []; + const runtimePluginProjectionGaps = Array.isArray(index.runtimePluginProjectionGaps) ? index.runtimePluginProjectionGaps : []; const worldSurfaceLoweringGaps = Array.isArray(index.worldSurfaceLoweringGaps) ? index.worldSurfaceLoweringGaps : []; validatePluginRecords(plugins, errors); @@ -139,12 +141,28 @@ function validateIndex(index, directory, expectations, errors) { validateCapabilityMatrix(index.capabilityMatrix, packs, plugins, providers, runtimes, splitTargets, errors); } validateCapabilityGaps(index.capabilityGaps, errors); + validateRuntimePluginProjectionGaps(index.runtimePluginProjectionGaps, errors); validateWorldSurfaceLoweringGaps(index.worldSurfaceLoweringGaps, errors); for (const expectedGap of expectations.capabilityGaps) { if (!capabilityGaps.some(gap => capabilityGapText(gap).includes(expectedGap))) { errors.push(`capabilityGaps:${expectedGap}:missing`); } } + for (const expectation of expectations.runtimePluginGaps) { + const gap = runtimePluginProjectionGaps.find(candidate => + candidate.runtimeId === expectation.runtimeId && + candidate.pluginId === expectation.pluginId); + if (!gap) { + errors.push(`runtimePluginProjectionGaps:${expectation.runtimeId}:${expectation.pluginId}:missing`); + continue; + } + if (gap.ownerRepo !== expectation.ownerRepo) { + errors.push(`runtimePluginProjectionGaps:${expectation.runtimeId}:${expectation.pluginId}:ownerRepo:expected ${expectation.ownerRepo} got ${gap.ownerRepo || ""}`); + } + if (!gap.reason) { + errors.push(`runtimePluginProjectionGaps:${expectation.runtimeId}:${expectation.pluginId}:reason:missing`); + } + } for (const expectation of expectations.worldLoweringGaps) { const gap = worldSurfaceLoweringGaps.find(candidate => candidate.providerId === expectation.providerId && @@ -577,6 +595,21 @@ function validateCapabilityGaps(gaps, errors) { } } +function validateRuntimePluginProjectionGaps(gaps, errors) { + if (!Array.isArray(gaps)) { + errors.push("runtimePluginProjectionGaps:missing"); + return; + } + for (const [index, gap] of gaps.entries()) { + for (const field of ["runtimeId", "ownerRepo", "pluginId", "severity", "reason"]) { + if (!gap?.[field]) errors.push(`runtimePluginProjectionGaps:${index}:${field}:missing`); + } + for (const field of ["requiredFixtures", "pluginFixtures"]) { + if (!Array.isArray(gap?.[field])) errors.push(`runtimePluginProjectionGaps:${index}:${field}:expected array`); + } + } +} + function validateWorldSurfaceLoweringGaps(gaps, errors) { if (!Array.isArray(gaps)) { errors.push("worldSurfaceLoweringGaps:missing"); @@ -699,6 +732,7 @@ function parseArguments(args) { splitTargetProofs: [], capabilityMatrix: false, capabilityGaps: [], + runtimePluginGaps: [], worldLoweringGaps: [], conformanceHandoff: false, }; @@ -738,6 +772,7 @@ function parseArguments(args) { ["--expect-split-target-blocker", expectations.splitTargetBlockers], ["--expect-split-target-proof", expectations.splitTargetProofs], ["--expect-capability-gap", expectations.capabilityGaps], + ["--expect-runtime-plugin-gap", expectations.runtimePluginGaps], ["--expect-world-lowering-gap", expectations.worldLoweringGaps], ]); @@ -817,6 +852,8 @@ function parseArguments(args) { target.push(parseSplitTargetExpectation(value, "proof")); } else if (option === "--expect-world-lowering-gap") { target.push(parseWorldLoweringGapExpectation(value)); + } else if (option === "--expect-runtime-plugin-gap") { + target.push(parseRuntimePluginGapExpectation(value)); } else { target.push(value); } @@ -841,6 +878,19 @@ function parseWorldLoweringGapExpectation(value) { }; } +function parseRuntimePluginGapExpectation(value) { + const parts = value.split(":"); + if (parts.length !== 3 || parts.some(part => !part)) { + console.error(`Expected runtime plugin projection gap in form, got: ${value}`); + process.exit(2); + } + return { + runtimeId: parts[0], + pluginId: parts[1], + ownerRepo: parts[2], + }; +} + function parsePluginExpectation(value, field) { const separator = value.indexOf(":"); if (separator <= 0 || separator === value.length - 1) { diff --git a/tools/parity/run-parity.mjs b/tools/parity/run-parity.mjs index 45ea882..4ed8fda 100644 --- a/tools/parity/run-parity.mjs +++ b/tools/parity/run-parity.mjs @@ -1607,6 +1607,7 @@ function buildConformanceExport(report) { conformanceHandoffExportPath: makeHandoffExportPath("conformance", "EveConformance", report.repoStrategy.conformanceHandoffPath || ""), packs, capabilityMatrix: buildCapabilityMatrix(report), + runtimePluginProjectionGaps: collectRuntimePluginProjectionGaps(report), worldSurfaceLoweringGaps: collectWorldSurfaceLoweringGaps(report), capabilityGaps: collectCapabilityGaps(report), plugins: (report.plugins || []).map(plugin => ({ @@ -1828,7 +1829,7 @@ function collectCapabilityGaps(report) { for (const note of runtime.unsupportedPluginNotes || []) { addGap({ kind: "runtime", - ownerRepo: runtime.ownerRepo, + ownerRepo: resolveRuntimeProjectionOwnerRepo(runtime), subjectId: runtime.id, gap: `unsupported-plugin:${note}`, severity: "declared-gap", @@ -1862,6 +1863,36 @@ function collectCapabilityGaps(report) { return gaps; } +function collectRuntimePluginProjectionGaps(report) { + const gaps = []; + for (const runtime of report.runtimes || []) { + for (const plugin of runtime.unsupportedPlugins || []) { + if (!plugin.pluginId) continue; + gaps.push({ + runtimeId: runtime.id, + runtimeStatus: runtime.status || "", + ownerRepo: resolveRuntimeProjectionOwnerRepo(runtime), + splitTarget: runtime.splitTarget || "", + pluginId: plugin.pluginId, + severity: "declared-gap", + reason: plugin.reason || "unsupported", + requiredFixtures: runtime.requiredFixtures || [], + pluginFixtures: runtime.pluginFixtures || [], + }); + } + } + return gaps; +} + +function resolveRuntimeProjectionOwnerRepo(runtime) { + return runtime.lifecycle?.release?.ownerRepo + || runtime.lifecycle?.test?.ownerRepo + || runtime.lifecycle?.capture?.ownerRepo + || runtime.splitTarget + || runtime.ownerRepo + || ""; +} + function collectWorldSurfaceLoweringGaps(report) { const claimedTargets = new Set(); const runtimeByWorldTarget = new Map();