Export plugin handoff move coverage
This commit is contained in:
parent
08da3eddd2
commit
e0bb031b66
6 changed files with 213 additions and 13 deletions
|
|
@ -384,9 +384,14 @@ Recently cut:
|
|||
`lower`, `measure`, and `apply` without reading Eve-local fixture files.
|
||||
- Plugin ABI operation coverage is now exported as root
|
||||
`pluginAbiOperationCoverage[]` records. The plugin-owner smoke asserts Sai
|
||||
and Norn `describe`, `lower`, and `apply` operations as `contracted`, so plugin
|
||||
owners can consume the coverage ledger without walking Eve's nested plugin
|
||||
records.
|
||||
Norn, and TeX `describe`, `lower`, and `apply` operations as `contracted`, so
|
||||
plugin owners can consume the coverage ledger without walking Eve's nested
|
||||
plugin records.
|
||||
- Plugin handoff move/source coverage is now exported as root
|
||||
`pluginHandoffMoveCoverage[]` records. The plugin-owner smoke asserts Sai,
|
||||
Norn, and TeX manifest and ABI-conformance move sets as current existing
|
||||
sources, so owner repos can see what must leave Eve incubation without
|
||||
reading Eve's parity manifest or plugin handoff files directly.
|
||||
- Provider/plugin requirement coverage is now exported as root
|
||||
`providerPluginRequirementCoverage[]` records with availability. The
|
||||
plugin-owner smoke asserts `sai.vn` as the required VN plugin and
|
||||
|
|
@ -698,15 +703,16 @@ Recently cut:
|
|||
graduates. The conformance export carries `conformanceHandoffPath`, and the
|
||||
generic consumer smoke asserts it so EveConformance split readiness is not
|
||||
trapped in Eve's parity manifest.
|
||||
- Sai and Norn now have plugin handoff manifests at
|
||||
`plugins/incubating/sai-vn.plugin-handoff.json` and
|
||||
`plugins/incubating/norn-graph.plugin-handoff.json`, plus a shared verifier
|
||||
at `scripts/run-plugin-handoff-smoke.ps1`. The handoffs name the manifest,
|
||||
- Sai, Norn, and TeX now have plugin handoff manifests at
|
||||
`plugins/incubating/sai-vn.plugin-handoff.json`,
|
||||
`plugins/incubating/norn-graph.plugin-handoff.json`, and
|
||||
`plugins/incubating/tex-math.plugin-handoff.json`, plus a shared verifier at
|
||||
`scripts/run-plugin-handoff-smoke.ps1`. The handoffs name the manifest,
|
||||
advertisement, ABI fixture, move sets, contract inputs, forbidden imports,
|
||||
and external proofs that must move to the owner repos. The conformance export
|
||||
carries plugin `handoffPath`, and the plugin-owner smoke asserts it so Sai
|
||||
and Norn can consume the plugin boundary without reading Eve's parity
|
||||
manifest.
|
||||
carries plugin `handoffPath` and `pluginHandoffMoveCoverage[]`, and the
|
||||
plugin-owner smoke asserts them so Sai, Norn, and EvePlugins can consume the
|
||||
plugin boundary without reading Eve's parity manifest.
|
||||
- Aetheria now has a provider handoff manifest at
|
||||
`web/fixtures/aetheria-provider-handoff.json` and a verifier at
|
||||
`scripts/run-aetheria-provider-handoff-smoke.ps1`. The handoff names the
|
||||
|
|
|
|||
|
|
@ -172,9 +172,14 @@ generic projection runtime from a runtime that can project plugin-declared
|
|||
capabilities.
|
||||
|
||||
Plugin handoff paths are exported as `plugins[].handoffPath` for owner repos
|
||||
such as Sai and Norn. These paths are incubation evidence for where plugin
|
||||
manifest, advertisement, ABI fixture, and conformance ownership must move; they
|
||||
are not plugin runtime state.
|
||||
such as Sai, Norn, and EvePlugins. These paths are incubation evidence for
|
||||
where plugin manifest, advertisement, ABI fixture, and conformance ownership
|
||||
must move; they are not plugin runtime state.
|
||||
Plugin handoff move/source coverage is exported as
|
||||
`pluginHandoffMoveCoverage[]`. Each record joins plugin id, owner repo, split
|
||||
target, handoff move set, destination owner, replacement proof, and current
|
||||
source path status. Consumer smokes can assert them with
|
||||
`--expect-plugin-handoff-move <pluginId:moveSetId:pathKind:status:path-substring>`.
|
||||
Plugin ABI fixture operations are exported as `plugins[].abiOperationContracts`
|
||||
with `operation`, `input`, and `expect` blocks. Plugin owners can consume the
|
||||
operation-level contract for `describe`, `validate`, `project`, `lower`,
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
"screenshotComparisonMetrics",
|
||||
"splitTargetBlockers",
|
||||
"splitHandoffMoveCoverage",
|
||||
"pluginHandoffMoveCoverage",
|
||||
"splitTargets"
|
||||
],
|
||||
"properties": {
|
||||
|
|
@ -910,6 +911,80 @@
|
|||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"pluginHandoffMoveCoverage": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"pluginId",
|
||||
"pluginOwnerRepo",
|
||||
"splitTarget",
|
||||
"handoffPath",
|
||||
"handoffExportPath",
|
||||
"moveSetId",
|
||||
"destinationOwner",
|
||||
"replacementProof",
|
||||
"pathKind",
|
||||
"sourcePath",
|
||||
"status",
|
||||
"severity"
|
||||
],
|
||||
"properties": {
|
||||
"pluginId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"pluginOwnerRepo": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"splitTarget": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"handoffPath": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"handoffExportPath": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"moveSetId": {
|
||||
"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
|
||||
}
|
||||
},
|
||||
"providers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
|
|
|||
|
|
@ -47,6 +47,12 @@ node $consumerScript $consumerExport `
|
|||
--expect-plugin-handoff sai.vn `
|
||||
--expect-plugin-handoff norn.graph `
|
||||
--expect-plugin-handoff tex.math `
|
||||
--expect-plugin-handoff-move sai.vn:plugin-manifest:current:exists:sai-vn.plugin.json `
|
||||
--expect-plugin-handoff-move sai.vn:abi-conformance:current:exists:sai-vn.plugin-abi-fixture.json `
|
||||
--expect-plugin-handoff-move norn.graph:plugin-manifest:current:exists:norn-graph.plugin.json `
|
||||
--expect-plugin-handoff-move norn.graph:abi-conformance:current:exists:norn-graph.plugin-abi-fixture.json `
|
||||
--expect-plugin-handoff-move tex.math:plugin-manifest:current:exists:tex-math.plugin.json `
|
||||
--expect-plugin-handoff-move tex.math:abi-conformance:current:exists:tex-math.plugin-abi-fixture.json `
|
||||
--expect-plugin-operation sai.vn:describe `
|
||||
--expect-plugin-operation sai.vn:validate `
|
||||
--expect-plugin-operation sai.vn:project `
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ if (!exportDirectory) {
|
|||
" --expect-plugin-abi-field <pluginId:operation:field.path:value>",
|
||||
" --expect-plugin-abi-operation-coverage <pluginId:operation:status:ownerRepo>",
|
||||
" --expect-plugin-handoff <pluginId>",
|
||||
" --expect-plugin-handoff-move <pluginId:moveSetId:pathKind:status:path-substring>",
|
||||
" --expect-provider-surface <providerId:surfaceId>",
|
||||
" --expect-provider-surface-kind <providerId:surfaceId:surfaceKind>",
|
||||
" --expect-provider-surface-field <providerId:surfaceId:field.path:value>",
|
||||
|
|
@ -147,6 +148,7 @@ function validateIndex(index, directory, expectations, errors) {
|
|||
const screenshotComparisonMetrics = Array.isArray(index.screenshotComparisonMetrics) ? index.screenshotComparisonMetrics : [];
|
||||
const splitTargetBlockers = Array.isArray(index.splitTargetBlockers) ? index.splitTargetBlockers : [];
|
||||
const splitHandoffMoveCoverage = Array.isArray(index.splitHandoffMoveCoverage) ? index.splitHandoffMoveCoverage : [];
|
||||
const pluginHandoffMoveCoverage = Array.isArray(index.pluginHandoffMoveCoverage) ? index.pluginHandoffMoveCoverage : [];
|
||||
const worldSurfaceLoweringGaps = Array.isArray(index.worldSurfaceLoweringGaps) ? index.worldSurfaceLoweringGaps : [];
|
||||
|
||||
validatePluginRecords(plugins, errors);
|
||||
|
|
@ -172,6 +174,7 @@ function validateIndex(index, directory, expectations, errors) {
|
|||
validateRuntimePluginProjectionGaps(index.runtimePluginProjectionGaps, errors);
|
||||
validateSplitTargetBlockers(index.splitTargetBlockers, errors);
|
||||
validateSplitHandoffMoveCoverage(index.splitHandoffMoveCoverage, errors);
|
||||
validatePluginHandoffMoveCoverage(index.pluginHandoffMoveCoverage, errors);
|
||||
validateWorldSurfaceLoweringCoverage(index.worldSurfaceLoweringCoverage, errors);
|
||||
validateCommandBoundaryCoverage(index.commandBoundaryCoverage, errors);
|
||||
validateScreenshotComparisonMetrics(index.screenshotComparisonMetrics, errors);
|
||||
|
|
@ -704,6 +707,17 @@ function validateIndex(index, directory, expectations, errors) {
|
|||
errors.push(`splitHandoffMoveCoverage:${expectation.splitTarget}:${expectation.runtimeId}:${expectation.moveSetId}:${expectation.pathKind}:${expectation.status}:${expectation.pathSubstring}:missing`);
|
||||
}
|
||||
}
|
||||
for (const expectation of expectations.pluginHandoffMoves) {
|
||||
const record = pluginHandoffMoveCoverage.find(candidate =>
|
||||
candidate.pluginId === expectation.pluginId &&
|
||||
candidate.moveSetId === expectation.moveSetId &&
|
||||
candidate.pathKind === expectation.pathKind &&
|
||||
candidate.status === expectation.status &&
|
||||
candidate.sourcePath?.includes(expectation.pathSubstring));
|
||||
if (!record) {
|
||||
errors.push(`pluginHandoffMoveCoverage:${expectation.pluginId}:${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) {
|
||||
|
|
@ -885,6 +899,21 @@ function validateSplitHandoffMoveCoverage(records, errors) {
|
|||
}
|
||||
}
|
||||
|
||||
function validatePluginHandoffMoveCoverage(records, errors) {
|
||||
if (!Array.isArray(records)) {
|
||||
errors.push("pluginHandoffMoveCoverage:missing");
|
||||
return;
|
||||
}
|
||||
for (const [index, record] of records.entries()) {
|
||||
for (const field of ["pluginId", "pluginOwnerRepo", "splitTarget", "handoffPath", "handoffExportPath", "moveSetId", "destinationOwner", "replacementProof", "pathKind", "sourcePath", "status", "severity"]) {
|
||||
if (!record?.[field]) errors.push(`pluginHandoffMoveCoverage:${index}:${field}:missing`);
|
||||
}
|
||||
if (typeof record?.pathExists !== "boolean") {
|
||||
errors.push(`pluginHandoffMoveCoverage:${index}:pathExists:expected boolean`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validateWorldSurfaceLoweringCoverage(records, errors) {
|
||||
if (!Array.isArray(records)) {
|
||||
errors.push("worldSurfaceLoweringCoverage:missing");
|
||||
|
|
@ -1104,6 +1133,7 @@ function parseArguments(args) {
|
|||
pluginAbiFields: [],
|
||||
pluginAbiOperationCoverage: [],
|
||||
pluginHandoffs: [],
|
||||
pluginHandoffMoves: [],
|
||||
providers: [],
|
||||
providerSurfaces: [],
|
||||
providerSurfaceKinds: [],
|
||||
|
|
@ -1161,6 +1191,7 @@ function parseArguments(args) {
|
|||
["--expect-plugin-abi-field", expectations.pluginAbiFields],
|
||||
["--expect-plugin-abi-operation-coverage", expectations.pluginAbiOperationCoverage],
|
||||
["--expect-plugin-handoff", expectations.pluginHandoffs],
|
||||
["--expect-plugin-handoff-move", expectations.pluginHandoffMoves],
|
||||
["--expect-provider-surface", expectations.providerSurfaces],
|
||||
["--expect-provider-surface-kind", expectations.providerSurfaceKinds],
|
||||
["--expect-provider-surface-field", expectations.providerSurfaceFields],
|
||||
|
|
@ -1233,6 +1264,8 @@ function parseArguments(args) {
|
|||
target.push(parsePluginAbiOperationCoverageExpectation(value));
|
||||
} else if (option === "--expect-plugin-handoff") {
|
||||
target.push(value);
|
||||
} else if (option === "--expect-plugin-handoff-move") {
|
||||
target.push(parsePluginHandoffMoveExpectation(value));
|
||||
} else if (option === "--expect-provider-surface") {
|
||||
target.push(parseProviderExpectation(value, "surfaceId"));
|
||||
} else if (option === "--expect-provider-surface-kind") {
|
||||
|
|
@ -1452,6 +1485,21 @@ function parseSplitHandoffMoveExpectation(value) {
|
|||
};
|
||||
}
|
||||
|
||||
function parsePluginHandoffMoveExpectation(value) {
|
||||
const parts = value.split(":");
|
||||
if (parts.length < 5 || parts.slice(0, 4).some(part => !part) || !parts.slice(4).join(":")) {
|
||||
console.error(`Expected plugin handoff move in <pluginId:moveSetId:pathKind:status:path-substring> form, got: ${value}`);
|
||||
process.exit(2);
|
||||
}
|
||||
return {
|
||||
pluginId: parts[0],
|
||||
moveSetId: parts[1],
|
||||
pathKind: parts[2],
|
||||
status: parts[3],
|
||||
pathSubstring: parts.slice(4).join(":"),
|
||||
};
|
||||
}
|
||||
|
||||
function parsePluginExpectation(value, field) {
|
||||
const separator = value.indexOf(":");
|
||||
if (separator <= 0 || separator === value.length - 1) {
|
||||
|
|
|
|||
|
|
@ -1967,6 +1967,7 @@ function buildConformanceExport(report) {
|
|||
worldSurfaceLoweringGaps: collectWorldSurfaceLoweringGaps(report),
|
||||
splitTargetBlockers: collectSplitTargetBlockers(report),
|
||||
splitHandoffMoveCoverage: collectSplitHandoffMoveCoverage(report),
|
||||
pluginHandoffMoveCoverage: collectPluginHandoffMoveCoverage(report),
|
||||
pluginAbiOperationCoverage: collectPluginAbiOperationCoverage(report),
|
||||
providerPluginRequirementCoverage: collectProviderPluginRequirementCoverage(report),
|
||||
capabilityGaps: collectCapabilityGaps(report),
|
||||
|
|
@ -2801,6 +2802,65 @@ function buildSplitHandoffMoveRecord(runtime, handoff, moveSet, source) {
|
|||
};
|
||||
}
|
||||
|
||||
function collectPluginHandoffMoveCoverage(report) {
|
||||
const records = [];
|
||||
for (const plugin of report.plugins || []) {
|
||||
if (!plugin.handoffPath) continue;
|
||||
const absoluteHandoffPath = path.join(repoRoot, plugin.handoffPath);
|
||||
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 : [];
|
||||
for (const sourcePath of currentPaths) {
|
||||
const absoluteSourcePath = path.join(repoRoot, sourcePath);
|
||||
records.push(buildPluginHandoffMoveRecord(plugin, handoff, moveSet, {
|
||||
pathKind: "current",
|
||||
sourcePath,
|
||||
pathExists: existsSync(absoluteSourcePath),
|
||||
}));
|
||||
}
|
||||
if (!currentPaths.length) {
|
||||
records.push(buildPluginHandoffMoveRecord(plugin, handoff, moveSet, {
|
||||
pathKind: "replacement-required",
|
||||
sourcePath: "(none)",
|
||||
pathExists: false,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
return records.sort((left, right) =>
|
||||
`${left.pluginId}:${left.moveSetId}:${left.pathKind}:${left.sourcePath}`
|
||||
.localeCompare(`${right.pluginId}:${right.moveSetId}:${right.pathKind}:${right.sourcePath}`));
|
||||
}
|
||||
|
||||
function buildPluginHandoffMoveRecord(plugin, handoff, moveSet, source) {
|
||||
const status = source.pathKind === "replacement-required"
|
||||
? "no-source-paths"
|
||||
: source.pathExists ? "exists" : "missing";
|
||||
return {
|
||||
pluginId: handoff.pluginId || plugin.pluginId || "",
|
||||
pluginOwnerRepo: handoff.ownerRepo || plugin.ownerRepo || "",
|
||||
splitTarget: handoff.splitTarget || plugin.splitTarget || "",
|
||||
handoffPath: plugin.handoffPath || "",
|
||||
handoffExportPath: makeHandoffExportPath("plugin", plugin.pluginId, plugin.handoffPath || ""),
|
||||
moveSetId: moveSet.id || "",
|
||||
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