Export typed split target blockers

This commit is contained in:
Meta Vens 2026-07-09 02:57:14 +01:00
commit c0785ca9aa
6 changed files with 208 additions and 13 deletions

View file

@ -313,6 +313,11 @@ Recently cut:
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.
- Split target blockers are now exported as `splitTargetBlockers[]` and as
per-target `blockerRecords[]` with blocker kind, subject, owner, and source
text. The split-target consumer smoke asserts Unity scene status/plugin
blockers, Electron feature blockers, and TUI command-transport blockers
without scraping the human `blockers[]` strings.
- 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

@ -144,8 +144,11 @@ plus release contract fields such as package name, artifact kind, tag pattern,
and package root, plus Unity EditMode test runner contract fields such as
runner script, package name, test assembly, and platform. It also asserts that
web and Unity UI Toolkit claim their provider-advertised world-surface lowering
targets. Split blockers stay machine-readable instead of hiding in handoff
prose.
targets. Split blockers are exported both as human-readable `blockers[]` and as
typed `blockerRecords[]` / root `splitTargetBlockers[]` entries. Split-target
consumers can assert blocker kind and subject with
`--expect-split-target-blocker-record <targetId:kind:subject>` instead of
scraping prose from handoff notes.
After generating the parity report, `run-parity-harness.ps1` copies
`artifacts/conformance/latest` into

View file

@ -11,6 +11,7 @@
"plugins",
"providers",
"runtimes",
"splitTargetBlockers",
"splitTargets"
],
"properties": {
@ -147,6 +148,74 @@
"additionalProperties": true
}
},
"splitTargetBlockers": {
"type": "array",
"items": {
"type": "object",
"required": [
"targetId",
"ownerRepo",
"kind",
"severity",
"subject",
"text"
],
"properties": {
"targetId": {
"type": "string",
"minLength": 1
},
"ownerRepo": {
"type": "string",
"minLength": 1
},
"kind": {
"type": "string",
"minLength": 1
},
"severity": {
"type": "string",
"minLength": 1
},
"subject": {
"type": "string",
"minLength": 1
},
"runtimeId": {
"type": "string"
},
"pluginId": {
"type": "string"
},
"feature": {
"type": "string"
},
"capability": {
"type": "string"
},
"proof": {
"type": "string"
},
"evidencePath": {
"type": "string"
},
"status": {
"type": "string"
},
"requiredStatuses": {
"type": "array"
},
"reason": {
"type": "string"
},
"text": {
"type": "string",
"minLength": 1
}
},
"additionalProperties": true
}
},
"worldSurfaceLoweringGaps": {
"type": "array",
"items": {
@ -445,7 +514,8 @@
"requiredPlugins",
"proofs",
"pendingProofs",
"blockers"
"blockers",
"blockerRecords"
],
"properties": {
"id": {
@ -489,6 +559,9 @@
},
"blockers": {
"type": "array"
},
"blockerRecords": {
"type": "array"
}
},
"additionalProperties": true

View file

@ -42,6 +42,9 @@ try {
--expect-split-target-proof "EveUnity:Unity lifecycle evidence is declared" `
--expect-split-target-proof "EveUnity:EveUnity split handoff is machine-readable" `
--expect-split-target-blocker "EveUnity:runtime:unity-scene:status:pending" `
--expect-split-target-blocker-record EveUnity:runtime-status:unity-scene `
--expect-split-target-blocker-record EveUnity:runtime-plugin-projection:unity-scene:sai.vn `
--expect-split-target-blocker-record "EveUnity:pending-proof:Unity scene runtime body exists outside Aetheria product code." `
--expect-split-target-blocker "EveUnity:Tagged UPM release" `
--expect-split-target-blocker "EveUnity:Unity batchmode EditMode runner" `
--expect-split-target-blocker "EveUnity:Unity batchmode or editor capture artifact" `
@ -50,11 +53,14 @@ try {
--expect-split-target-status EveElectron:incubating `
--expect-split-target-proof "EveElectron:EveElectron split handoff is machine-readable" `
--expect-split-target-blocker "EveElectron:runtime:electron-shell:status:pending" `
--expect-split-target-blocker-record EveElectron:runtime-status:electron-shell `
--expect-split-target-blocker-record EveElectron:runtime-feature:electron-shell:providerAdvertisements `
--expect-split-target-blocker "EveElectron:Electron shell runtime body exists outside Aetheria product code" `
--expect-split-target EveTui `
--expect-split-target-status EveTui:incubating `
--expect-split-target-proof "EveTui:EveTui split handoff is machine-readable" `
--expect-split-target-blocker "EveTui:runtime:tui:status:pending" `
--expect-split-target-blocker-record EveTui:runtime-feature:tui:commandTransport `
--expect-split-target-blocker "EveTui:TUI runtime body exists outside provider product code"
if ($LASTEXITCODE -ne 0) {
throw "Split target conformance consumer smoke failed with exit code $LASTEXITCODE"

View file

@ -47,6 +47,7 @@ if (!exportDirectory) {
" --expect-runtime-lifecycle-field <runtimeId:stage:field.path:value>",
" --expect-split-target-status <targetId:status>",
" --expect-split-target-blocker <targetId:blocker-substring>",
" --expect-split-target-blocker-record <targetId:kind:subject>",
" --expect-split-target-proof <targetId:proof-substring>",
].join("\n"));
process.exit(2);
@ -124,6 +125,7 @@ function validateIndex(index, directory, expectations, errors) {
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 splitTargetBlockers = Array.isArray(index.splitTargetBlockers) ? index.splitTargetBlockers : [];
const worldSurfaceLoweringGaps = Array.isArray(index.worldSurfaceLoweringGaps) ? index.worldSurfaceLoweringGaps : [];
validatePluginRecords(plugins, errors);
@ -142,6 +144,7 @@ function validateIndex(index, directory, expectations, errors) {
}
validateCapabilityGaps(index.capabilityGaps, errors);
validateRuntimePluginProjectionGaps(index.runtimePluginProjectionGaps, errors);
validateSplitTargetBlockers(index.splitTargetBlockers, errors);
validateWorldSurfaceLoweringGaps(index.worldSurfaceLoweringGaps, errors);
for (const expectedGap of expectations.capabilityGaps) {
if (!capabilityGaps.some(gap => capabilityGapText(gap).includes(expectedGap))) {
@ -495,6 +498,15 @@ function validateIndex(index, directory, expectations, errors) {
errors.push(`splitTargets:${expectation.targetId}:blocker:${expectation.blocker}:missing`);
}
}
for (const expectation of expectations.splitTargetBlockerRecords) {
const blocker = splitTargetBlockers.find(candidate =>
candidate.targetId === expectation.targetId &&
candidate.kind === expectation.kind &&
candidate.subject === expectation.subject);
if (!blocker) {
errors.push(`splitTargetBlockers:${expectation.targetId}:${expectation.kind}:${expectation.subject}:missing`);
}
}
for (const expectation of expectations.splitTargetProofs) {
const target = splitTargets.find(candidate => candidate.id === expectation.targetId);
if (!target) {
@ -610,6 +622,18 @@ function validateRuntimePluginProjectionGaps(gaps, errors) {
}
}
function validateSplitTargetBlockers(blockers, errors) {
if (!Array.isArray(blockers)) {
errors.push("splitTargetBlockers:missing");
return;
}
for (const [index, blocker] of blockers.entries()) {
for (const field of ["targetId", "ownerRepo", "kind", "severity", "subject", "text"]) {
if (!blocker?.[field]) errors.push(`splitTargetBlockers:${index}:${field}:missing`);
}
}
}
function validateWorldSurfaceLoweringGaps(gaps, errors) {
if (!Array.isArray(gaps)) {
errors.push("worldSurfaceLoweringGaps:missing");
@ -676,7 +700,7 @@ function validateSplitTargetRecords(splitTargets, errors) {
for (const field of ["id", "ownerRepo", "status", "runtimeStatuses"]) {
if (!target?.[field]) errors.push(`${label}:${field}:missing`);
}
for (const field of ["runtimes", "requiredRuntimeStatuses", "requiredFeatures", "requiredPlugins", "proofs", "pendingProofs", "blockers"]) {
for (const field of ["runtimes", "requiredRuntimeStatuses", "requiredFeatures", "requiredPlugins", "proofs", "pendingProofs", "blockers", "blockerRecords"]) {
if (!Array.isArray(target?.[field])) errors.push(`${label}:${field}:expected array`);
}
}
@ -729,6 +753,7 @@ function parseArguments(args) {
splitTargets: [],
splitTargetStatuses: [],
splitTargetBlockers: [],
splitTargetBlockerRecords: [],
splitTargetProofs: [],
capabilityMatrix: false,
capabilityGaps: [],
@ -770,6 +795,7 @@ function parseArguments(args) {
["--expect-runtime-lifecycle-field", expectations.runtimeLifecycleFields],
["--expect-split-target-status", expectations.splitTargetStatuses],
["--expect-split-target-blocker", expectations.splitTargetBlockers],
["--expect-split-target-blocker-record", expectations.splitTargetBlockerRecords],
["--expect-split-target-proof", expectations.splitTargetProofs],
["--expect-capability-gap", expectations.capabilityGaps],
["--expect-runtime-plugin-gap", expectations.runtimePluginGaps],
@ -848,6 +874,8 @@ function parseArguments(args) {
target.push(parseSplitTargetExpectation(value, "status"));
} else if (option === "--expect-split-target-blocker") {
target.push(parseSplitTargetExpectation(value, "blocker"));
} else if (option === "--expect-split-target-blocker-record") {
target.push(parseSplitTargetBlockerRecordExpectation(value));
} else if (option === "--expect-split-target-proof") {
target.push(parseSplitTargetExpectation(value, "proof"));
} else if (option === "--expect-world-lowering-gap") {
@ -891,6 +919,19 @@ function parseRuntimePluginGapExpectation(value) {
};
}
function parseSplitTargetBlockerRecordExpectation(value) {
const parts = value.split(":");
if (parts.length < 3 || !parts[0] || !parts[1] || !parts.slice(2).join(":")) {
console.error(`Expected split target blocker record in <targetId:kind:subject> form, got: ${value}`);
process.exit(2);
}
return {
targetId: parts[0],
kind: parts[1],
subject: parts.slice(2).join(":"),
};
}
function parsePluginExpectation(value, field) {
const separator = value.indexOf(":");
if (separator <= 0 || separator === value.length - 1) {

View file

@ -1349,23 +1349,50 @@ function evaluateSplitTargets(splitTargets, runtimeResults) {
const proofs = target.proofs || [];
const runtimeIds = target.runtimes || [];
const blockers = [];
const blockerRecords = [];
const runtimeStatuses = {};
const addBlocker = (text, record) => {
blockers.push(text);
blockerRecords.push({
targetId: target.id,
ownerRepo: target.ownerRepo || "",
severity: "blocker",
subject: record.subject || "",
...record,
text,
});
};
for (const runtimeId of runtimeIds) {
const runtime = runtimeById.get(runtimeId);
if (!runtime) {
blockers.push(`runtime:${runtimeId}:missing`);
addBlocker(`runtime:${runtimeId}:missing`, {
kind: "runtime-missing",
subject: runtimeId,
runtimeId,
});
continue;
}
runtimeStatuses[runtimeId] = runtime.status;
if (!requiredRuntimeStatuses.includes(runtime.status)) {
blockers.push(`runtime:${runtimeId}:status:${runtime.status}`);
addBlocker(`runtime:${runtimeId}:status:${runtime.status}`, {
kind: "runtime-status",
subject: runtimeId,
runtimeId,
status: runtime.status,
requiredStatuses: requiredRuntimeStatuses,
});
}
for (const feature of requiredFeatures) {
if (!runtime.supportedFeatures.includes(feature)) {
blockers.push(`runtime:${runtimeId}:feature:${feature}`);
addBlocker(`runtime:${runtimeId}:feature:${feature}`, {
kind: "runtime-feature",
subject: `${runtimeId}:${feature}`,
runtimeId,
feature,
});
}
}
@ -1373,18 +1400,35 @@ function evaluateSplitTargets(splitTargets, runtimeResults) {
const unsupportedPlugins = new Map((runtime.unsupportedPlugins || []).map(plugin => [plugin.pluginId, plugin.reason || "unsupported"]));
for (const requirement of requiredPlugins) {
if (unsupportedPlugins.has(requirement.pluginId)) {
blockers.push(`runtime:${runtimeId}:unsupported-plugin:${requirement.pluginId}:${unsupportedPlugins.get(requirement.pluginId)}`);
addBlocker(`runtime:${runtimeId}:unsupported-plugin:${requirement.pluginId}:${unsupportedPlugins.get(requirement.pluginId)}`, {
kind: "runtime-plugin-projection",
subject: `${runtimeId}:${requirement.pluginId}`,
runtimeId,
pluginId: requirement.pluginId,
reason: unsupportedPlugins.get(requirement.pluginId),
});
continue;
}
const capabilities = supportedPlugins.get(requirement.pluginId);
if (!capabilities) {
blockers.push(`runtime:${runtimeId}:plugin:${requirement.pluginId}`);
addBlocker(`runtime:${runtimeId}:plugin:${requirement.pluginId}`, {
kind: "runtime-plugin-missing",
subject: `${runtimeId}:${requirement.pluginId}`,
runtimeId,
pluginId: requirement.pluginId,
});
continue;
}
for (const capability of requirement.capabilities || []) {
if (!capabilities.has(capability)) {
blockers.push(`runtime:${runtimeId}:plugin:${requirement.pluginId}:${capability}`);
addBlocker(`runtime:${runtimeId}:plugin:${requirement.pluginId}:${capability}`, {
kind: "runtime-plugin-capability",
subject: `${runtimeId}:${requirement.pluginId}:${capability}`,
runtimeId,
pluginId: requirement.pluginId,
capability,
});
}
}
}
@ -1393,15 +1437,32 @@ function evaluateSplitTargets(splitTargets, runtimeResults) {
for (const proof of proofs) {
const missingEvidence = (proof.evidencePaths || []).filter(candidate => !existsSync(path.join(repoRoot, candidate)));
if (proof.status !== "passed") {
blockers.push(`proof:${proof.description || "unnamed"}:status:${proof.status || "missing"}`);
const description = proof.description || "unnamed";
addBlocker(`proof:${description}:status:${proof.status || "missing"}`, {
kind: "proof-status",
subject: description,
proof: description,
status: proof.status || "missing",
});
}
for (const evidencePath of missingEvidence) {
blockers.push(`proof:${proof.description || "unnamed"}:evidence:${evidencePath}:missing`);
const description = proof.description || "unnamed";
addBlocker(`proof:${description}:evidence:${evidencePath}:missing`, {
kind: "proof-evidence",
subject: `${description}:${evidencePath}`,
proof: description,
evidencePath,
status: "missing",
});
}
}
for (const proof of target.pendingProofs || []) {
blockers.push(`proof:${proof}`);
addBlocker(`proof:${proof}`, {
kind: "pending-proof",
subject: proof,
proof,
});
}
return {
@ -1418,6 +1479,7 @@ function evaluateSplitTargets(splitTargets, runtimeResults) {
proofs,
pendingProofs: target.pendingProofs || [],
blockers,
blockerRecords,
};
});
}
@ -1609,6 +1671,7 @@ function buildConformanceExport(report) {
capabilityMatrix: buildCapabilityMatrix(report),
runtimePluginProjectionGaps: collectRuntimePluginProjectionGaps(report),
worldSurfaceLoweringGaps: collectWorldSurfaceLoweringGaps(report),
splitTargetBlockers: collectSplitTargetBlockers(report),
capabilityGaps: collectCapabilityGaps(report),
plugins: (report.plugins || []).map(plugin => ({
pluginId: plugin.pluginId,
@ -1884,6 +1947,10 @@ function collectRuntimePluginProjectionGaps(report) {
return gaps;
}
function collectSplitTargetBlockers(report) {
return (report.splitTargets || []).flatMap(target => target.blockerRecords || []);
}
function resolveRuntimeProjectionOwnerRepo(runtime) {
return runtime.lifecycle?.release?.ownerRepo
|| runtime.lifecycle?.test?.ownerRepo