Export split handoff move coverage
This commit is contained in:
parent
79db997e28
commit
b9f7592edf
6 changed files with 229 additions and 7 deletions
|
|
@ -310,6 +310,12 @@ Recently cut:
|
|||
and Unity UI Toolkit are `covered` because they advertise
|
||||
`gamecult.eve.command.v1`; Unity scene and TUI stay visible as
|
||||
`missing-runtime-claim` until their generic lowerers exist.
|
||||
- The conformance export carries `splitHandoffMoveCoverage[]`, joining runtime
|
||||
split handoff move sets to source path status. EveElectron now exposes the
|
||||
existing Aetheria `Aetheria.Rts.Web/Electron` and command transport paths as
|
||||
`observed-provider` sources that must be replaced by a provider-agnostic
|
||||
shell; Unity scene exposes `replacement-required` move sets because no
|
||||
generic scene body or command transport source exists yet.
|
||||
- The conformance export schema and consumer smoke now require minimum typed
|
||||
item shapes for plugins, providers, runtimes, and split targets. Owner repos
|
||||
can rely on exported ids, owners, statuses, split targets, manifest or
|
||||
|
|
|
|||
|
|
@ -183,6 +183,15 @@ 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.
|
||||
Runtime split handoff move/source coverage is exported as
|
||||
`splitHandoffMoveCoverage[]`. Each record joins a split target, runtime id,
|
||||
handoff move set, destination owner, replacement proof, and source path status.
|
||||
`current` paths are Eve-incubated code that still needs to leave,
|
||||
`observed-provider` paths are provider-owned pressure sources such as
|
||||
Aetheria's current Electron client, and `replacement-required` records mark
|
||||
move sets where no generic runtime source exists yet. Consumer smokes can
|
||||
assert them with
|
||||
`--expect-split-handoff-move <splitTarget:runtimeId:moveSetId:pathKind:status:path-substring>`.
|
||||
|
||||
After generating the parity report, `run-parity-harness.ps1` copies
|
||||
`artifacts/conformance/latest` into
|
||||
|
|
@ -266,12 +275,16 @@ target being present is not enough; future owner repos must be able to consume
|
|||
why it is or is not ready to leave Eve incubation. EveElectron is intentionally
|
||||
pending: its handoff names the generic Electron shell boundary, while the
|
||||
Starbridge RTS client remains Aetheria-owned product code until a
|
||||
provider-agnostic shell exists. EveTui is also intentionally pending: Aetheria
|
||||
advertises `tui` as an agent-facing lowering target, but Eve should not claim
|
||||
that target until a real terminal/grid lowerer and transcript capture exist.
|
||||
EveUnity includes both the active `unity-uitoolkit` package proof and the
|
||||
pending `unity-scene` runtime boundary; UI Toolkit support is not treated as
|
||||
full Unity scene/world lowering.
|
||||
provider-agnostic shell exists. The same smoke asserts that the Aetheria
|
||||
Electron source paths named by the handoff currently exist as
|
||||
`observed-provider` sources, so the extraction pressure is visible without
|
||||
turning those paths into EveElectron authority. EveTui is also intentionally
|
||||
pending: Aetheria advertises `tui` as an agent-facing lowering target, but Eve
|
||||
should not claim that target until a real terminal/grid lowerer and transcript
|
||||
capture exist. EveUnity includes both the active `unity-uitoolkit` package proof
|
||||
and the pending `unity-scene` runtime boundary; UI Toolkit support is not
|
||||
treated as full Unity scene/world lowering, and `unity-scene` move sets remain
|
||||
`replacement-required` until a generic scene body exists.
|
||||
|
||||
The Flutter widget smoke also exercises `EveProviderCatalog` and
|
||||
`EveProviderPicker` against a conformance-export-shaped provider list. That is
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"worldSurfaceLoweringCoverage",
|
||||
"commandBoundaryCoverage",
|
||||
"splitTargetBlockers",
|
||||
"splitHandoffMoveCoverage",
|
||||
"splitTargets"
|
||||
],
|
||||
"properties": {
|
||||
|
|
@ -838,6 +839,85 @@
|
|||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"splitHandoffMoveCoverage": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"splitTarget",
|
||||
"runtimeId",
|
||||
"runtimeOwnerRepo",
|
||||
"handoffPath",
|
||||
"handoffExportPath",
|
||||
"moveSetId",
|
||||
"stage",
|
||||
"destinationOwner",
|
||||
"replacementProof",
|
||||
"pathKind",
|
||||
"sourcePath",
|
||||
"status",
|
||||
"severity"
|
||||
],
|
||||
"properties": {
|
||||
"splitTarget": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"runtimeId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"runtimeOwnerRepo": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"handoffPath": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"handoffExportPath": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"moveSetId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"stage": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"destinationOwner": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"replacementProof": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"pathKind": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"sourcePath": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"pathExists": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"severity": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"splitTargets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@ try {
|
|||
--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-handoff-move EveUnity:unity-scene:unity-scene-runtime-body:replacement-required:no-source-paths:none `
|
||||
--expect-split-handoff-move EveUnity:unity-scene:unity-scene-command-transport:replacement-required:no-source-paths:none `
|
||||
--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" `
|
||||
|
|
@ -55,6 +57,8 @@ try {
|
|||
--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-handoff-move EveElectron:electron-shell:electron-shell-runtime:observed-provider:exists:Aetheria.Rts.Web/Electron `
|
||||
--expect-split-handoff-move EveElectron:electron-shell:electron-command-transport:observed-provider:exists:Aetheria.Rts.Web/Client/app.ts `
|
||||
--expect-split-target-blocker "EveElectron:Electron shell runtime body exists outside Aetheria product code" `
|
||||
--expect-split-target EveTui `
|
||||
--expect-split-target-status EveTui:incubating `
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ if (!exportDirectory) {
|
|||
" --expect-split-target-status <targetId:status>",
|
||||
" --expect-split-target-blocker <targetId:blocker-substring>",
|
||||
" --expect-split-target-blocker-record <targetId:kind:subject>",
|
||||
" --expect-split-handoff-move <splitTarget:runtimeId:moveSetId:pathKind:status:path-substring>",
|
||||
" --expect-split-target-proof <targetId:proof-substring>",
|
||||
].join("\n"));
|
||||
process.exit(2);
|
||||
|
|
@ -136,6 +137,7 @@ function validateIndex(index, directory, expectations, errors) {
|
|||
const worldSurfaceLoweringCoverage = Array.isArray(index.worldSurfaceLoweringCoverage) ? index.worldSurfaceLoweringCoverage : [];
|
||||
const commandBoundaryCoverage = Array.isArray(index.commandBoundaryCoverage) ? index.commandBoundaryCoverage : [];
|
||||
const splitTargetBlockers = Array.isArray(index.splitTargetBlockers) ? index.splitTargetBlockers : [];
|
||||
const splitHandoffMoveCoverage = Array.isArray(index.splitHandoffMoveCoverage) ? index.splitHandoffMoveCoverage : [];
|
||||
const worldSurfaceLoweringGaps = Array.isArray(index.worldSurfaceLoweringGaps) ? index.worldSurfaceLoweringGaps : [];
|
||||
|
||||
validatePluginRecords(plugins, errors);
|
||||
|
|
@ -158,6 +160,7 @@ function validateIndex(index, directory, expectations, errors) {
|
|||
validateCapabilityGaps(index.capabilityGaps, errors);
|
||||
validateRuntimePluginProjectionGaps(index.runtimePluginProjectionGaps, errors);
|
||||
validateSplitTargetBlockers(index.splitTargetBlockers, errors);
|
||||
validateSplitHandoffMoveCoverage(index.splitHandoffMoveCoverage, errors);
|
||||
validateWorldSurfaceLoweringCoverage(index.worldSurfaceLoweringCoverage, errors);
|
||||
validateCommandBoundaryCoverage(index.commandBoundaryCoverage, errors);
|
||||
validateWorldSurfaceLoweringGaps(index.worldSurfaceLoweringGaps, errors);
|
||||
|
|
@ -609,6 +612,18 @@ function validateIndex(index, directory, expectations, errors) {
|
|||
errors.push(`splitTargetBlockers:${expectation.targetId}:${expectation.kind}:${expectation.subject}:missing`);
|
||||
}
|
||||
}
|
||||
for (const expectation of expectations.splitHandoffMoves) {
|
||||
const record = splitHandoffMoveCoverage.find(candidate =>
|
||||
candidate.splitTarget === expectation.splitTarget &&
|
||||
candidate.runtimeId === expectation.runtimeId &&
|
||||
candidate.moveSetId === expectation.moveSetId &&
|
||||
candidate.pathKind === expectation.pathKind &&
|
||||
candidate.status === expectation.status &&
|
||||
candidate.sourcePath?.includes(expectation.pathSubstring));
|
||||
if (!record) {
|
||||
errors.push(`splitHandoffMoveCoverage:${expectation.splitTarget}:${expectation.runtimeId}:${expectation.moveSetId}:${expectation.pathKind}:${expectation.status}:${expectation.pathSubstring}:missing`);
|
||||
}
|
||||
}
|
||||
for (const expectation of expectations.splitTargetProofs) {
|
||||
const target = splitTargets.find(candidate => candidate.id === expectation.targetId);
|
||||
if (!target) {
|
||||
|
|
@ -736,6 +751,21 @@ function validateSplitTargetBlockers(blockers, errors) {
|
|||
}
|
||||
}
|
||||
|
||||
function validateSplitHandoffMoveCoverage(records, errors) {
|
||||
if (!Array.isArray(records)) {
|
||||
errors.push("splitHandoffMoveCoverage:missing");
|
||||
return;
|
||||
}
|
||||
for (const [index, record] of records.entries()) {
|
||||
for (const field of ["splitTarget", "runtimeId", "runtimeOwnerRepo", "handoffPath", "handoffExportPath", "moveSetId", "stage", "destinationOwner", "replacementProof", "pathKind", "sourcePath", "status", "severity"]) {
|
||||
if (!record?.[field]) errors.push(`splitHandoffMoveCoverage:${index}:${field}:missing`);
|
||||
}
|
||||
if (typeof record?.pathExists !== "boolean") {
|
||||
errors.push(`splitHandoffMoveCoverage:${index}:pathExists:expected boolean`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validateWorldSurfaceLoweringCoverage(records, errors) {
|
||||
if (!Array.isArray(records)) {
|
||||
errors.push("worldSurfaceLoweringCoverage:missing");
|
||||
|
|
@ -931,6 +961,7 @@ function parseArguments(args) {
|
|||
splitTargetStatuses: [],
|
||||
splitTargetBlockers: [],
|
||||
splitTargetBlockerRecords: [],
|
||||
splitHandoffMoves: [],
|
||||
splitTargetProofs: [],
|
||||
capabilityMatrix: false,
|
||||
capabilityGaps: [],
|
||||
|
|
@ -978,6 +1009,7 @@ function parseArguments(args) {
|
|||
["--expect-split-target-status", expectations.splitTargetStatuses],
|
||||
["--expect-split-target-blocker", expectations.splitTargetBlockers],
|
||||
["--expect-split-target-blocker-record", expectations.splitTargetBlockerRecords],
|
||||
["--expect-split-handoff-move", expectations.splitHandoffMoves],
|
||||
["--expect-split-target-proof", expectations.splitTargetProofs],
|
||||
["--expect-capability-gap", expectations.capabilityGaps],
|
||||
["--expect-runtime-plugin-gap", expectations.runtimePluginGaps],
|
||||
|
|
@ -1066,6 +1098,8 @@ function parseArguments(args) {
|
|||
target.push(parseSplitTargetExpectation(value, "blocker"));
|
||||
} else if (option === "--expect-split-target-blocker-record") {
|
||||
target.push(parseSplitTargetBlockerRecordExpectation(value));
|
||||
} else if (option === "--expect-split-handoff-move") {
|
||||
target.push(parseSplitHandoffMoveExpectation(value));
|
||||
} else if (option === "--expect-split-target-proof") {
|
||||
target.push(parseSplitTargetExpectation(value, "proof"));
|
||||
} else if (option === "--expect-world-lowering-gap") {
|
||||
|
|
@ -1158,6 +1192,22 @@ function parseSplitTargetBlockerRecordExpectation(value) {
|
|||
};
|
||||
}
|
||||
|
||||
function parseSplitHandoffMoveExpectation(value) {
|
||||
const parts = value.split(":");
|
||||
if (parts.length < 6 || parts.slice(0, 5).some(part => !part) || !parts.slice(5).join(":")) {
|
||||
console.error(`Expected split handoff move in <splitTarget:runtimeId:moveSetId:pathKind:status:path-substring> form, got: ${value}`);
|
||||
process.exit(2);
|
||||
}
|
||||
return {
|
||||
splitTarget: parts[0],
|
||||
runtimeId: parts[1],
|
||||
moveSetId: parts[2],
|
||||
pathKind: parts[3],
|
||||
status: parts[4],
|
||||
pathSubstring: parts.slice(5).join(":"),
|
||||
};
|
||||
}
|
||||
|
||||
function parsePluginExpectation(value, field) {
|
||||
const separator = value.indexOf(":");
|
||||
if (separator <= 0 || separator === value.length - 1) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import { existsSync } from "node:fs";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { compileEveDsl } from "../../web/eve-dsl.js";
|
||||
|
|
@ -1675,6 +1675,7 @@ function buildConformanceExport(report) {
|
|||
commandBoundaryCoverage: collectCommandBoundaryCoverage(report),
|
||||
worldSurfaceLoweringGaps: collectWorldSurfaceLoweringGaps(report),
|
||||
splitTargetBlockers: collectSplitTargetBlockers(report),
|
||||
splitHandoffMoveCoverage: collectSplitHandoffMoveCoverage(report),
|
||||
pluginAbiOperationCoverage: collectPluginAbiOperationCoverage(report),
|
||||
providerPluginRequirementCoverage: collectProviderPluginRequirementCoverage(report),
|
||||
capabilityGaps: collectCapabilityGaps(report),
|
||||
|
|
@ -2017,6 +2018,74 @@ function collectSplitTargetBlockers(report) {
|
|||
return (report.splitTargets || []).flatMap(target => target.blockerRecords || []);
|
||||
}
|
||||
|
||||
function collectSplitHandoffMoveCoverage(report) {
|
||||
const records = [];
|
||||
for (const runtime of report.runtimes || []) {
|
||||
if (!runtime.splitHandoffPath) continue;
|
||||
const absoluteHandoffPath = path.join(repoRoot, runtime.splitHandoffPath);
|
||||
if (!existsSync(absoluteHandoffPath)) continue;
|
||||
|
||||
let handoff;
|
||||
try {
|
||||
handoff = JSON.parse(readFileSync(absoluteHandoffPath, "utf8"));
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const moveSet of handoff.moveSets || []) {
|
||||
const currentPaths = Array.isArray(moveSet.currentPaths) ? moveSet.currentPaths : [];
|
||||
const observedProviderPaths = Array.isArray(moveSet.observedProviderPaths) ? moveSet.observedProviderPaths : [];
|
||||
for (const sourcePath of currentPaths) {
|
||||
const absoluteSourcePath = path.join(repoRoot, sourcePath);
|
||||
records.push(buildSplitHandoffMoveRecord(runtime, handoff, moveSet, {
|
||||
pathKind: "current",
|
||||
sourcePath,
|
||||
pathExists: existsSync(absoluteSourcePath),
|
||||
}));
|
||||
}
|
||||
for (const sourcePath of observedProviderPaths) {
|
||||
records.push(buildSplitHandoffMoveRecord(runtime, handoff, moveSet, {
|
||||
pathKind: "observed-provider",
|
||||
sourcePath,
|
||||
pathExists: existsSync(sourcePath),
|
||||
}));
|
||||
}
|
||||
if (!currentPaths.length && !observedProviderPaths.length) {
|
||||
records.push(buildSplitHandoffMoveRecord(runtime, handoff, moveSet, {
|
||||
pathKind: "replacement-required",
|
||||
sourcePath: "(none)",
|
||||
pathExists: false,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
return records.sort((left, right) =>
|
||||
`${left.splitTarget}:${left.runtimeId}:${left.moveSetId}:${left.pathKind}:${left.sourcePath}`
|
||||
.localeCompare(`${right.splitTarget}:${right.runtimeId}:${right.moveSetId}:${right.pathKind}:${right.sourcePath}`));
|
||||
}
|
||||
|
||||
function buildSplitHandoffMoveRecord(runtime, handoff, moveSet, source) {
|
||||
const status = source.pathKind === "replacement-required"
|
||||
? "no-source-paths"
|
||||
: source.pathExists ? "exists" : "missing";
|
||||
return {
|
||||
splitTarget: handoff.splitTarget || runtime.splitTarget || "",
|
||||
runtimeId: handoff.runtimeId || runtime.id || "",
|
||||
runtimeOwnerRepo: handoff.ownerRepo || resolveRuntimeProjectionOwnerRepo(runtime),
|
||||
handoffPath: runtime.splitHandoffPath || "",
|
||||
handoffExportPath: makeHandoffExportPath("runtime", runtime.id, runtime.splitHandoffPath || ""),
|
||||
moveSetId: moveSet.id || "",
|
||||
stage: moveSet.stage || "",
|
||||
destinationOwner: moveSet.destinationOwner || "",
|
||||
replacementProof: moveSet.replacementProof || "",
|
||||
pathKind: source.pathKind,
|
||||
sourcePath: normalizePath(source.sourcePath),
|
||||
pathExists: source.pathExists,
|
||||
status,
|
||||
severity: "blocker",
|
||||
};
|
||||
}
|
||||
|
||||
function collectInteractiveWorldSurfaces(report) {
|
||||
const surfaces = [];
|
||||
for (const provider of report.providers || []) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue