Export typed split target blockers
This commit is contained in:
parent
acc3ed6945
commit
c0785ca9aa
6 changed files with 208 additions and 13 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue