Export runtime plugin projection gaps

This commit is contained in:
Meta Vens 2026-07-09 02:54:25 +01:00
commit acc3ed6945
6 changed files with 150 additions and 1 deletions

View file

@ -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;

View file

@ -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 <runtimeId:pluginId:ownerRepo>`.
The conformance export includes runtime `capabilityManifestPath` and
`capabilityManifestErrors` fields so external consumers can distinguish a

View file

@ -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": {

View file

@ -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 `

View file

@ -18,6 +18,7 @@ if (!exportDirectory) {
" --expect-split-target <id>",
" --expect-capability-matrix",
" --expect-capability-gap <substring>",
" --expect-runtime-plugin-gap <runtimeId:pluginId:ownerRepo>",
" --expect-world-lowering-gap <providerId:surfaceId:targetId:ownerRepo:runtimeId>",
" --expect-conformance-handoff",
" --expect-plugin-operation <pluginId: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 <runtimeId:pluginId:ownerRepo> 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) {

View file

@ -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();