Export runtime capture probe coverage
This commit is contained in:
parent
e063828c2c
commit
419c747ba6
7 changed files with 290 additions and 1 deletions
|
|
@ -614,6 +614,14 @@ Recently cut:
|
|||
`artifacts/eveelectron-capture/latest/electron-shell-projection.json`, and
|
||||
exports it as `runtime.captureArtifacts[]`. The actual packaged window PNG
|
||||
remains a split blocker for EveElectron.
|
||||
- Runtime capture probe coverage is now exported as
|
||||
`runtimeCaptureProbeCoverage[]`. It records the lifecycle capture contract,
|
||||
capture owner, requested artifact kind, current semantic/cell-grid artifact,
|
||||
request path, pending proofs, and validation errors for each runtime with
|
||||
capture evidence. The consumer smokes assert that Unity UI Toolkit, Unity
|
||||
scene, and Electron currently have `json-projection` semantic artifacts while
|
||||
their contracted `png` captures remain owner-repo work; TUI's current
|
||||
`json-grid` artifact satisfies its contract but still needs EveTui ownership.
|
||||
- EveTui capture lifecycle now attaches a typed terminal-grid artifact instead
|
||||
of stopping at a request contract. `tools/evetui/evetui-capture-artifact.mjs`
|
||||
lowers `web/fixtures/aetheria-world-surface.json` through `EveTuiShell`,
|
||||
|
|
|
|||
|
|
@ -454,6 +454,14 @@ It records scene graph projection, embedded slots, and sidecar plugin projection
|
|||
metadata. The remaining blocker is Unity screenshot or frame-capture PNG
|
||||
production from EveUnity.
|
||||
|
||||
Runtime capture probe coverage is exported as `runtimeCaptureProbeCoverage[]`.
|
||||
Each record joins runtime status, capture owner, lifecycle capture contract,
|
||||
current semantic/cell-grid artifact, pending proofs, and artifact errors. This
|
||||
keeps `json-projection` evidence useful without letting it impersonate the
|
||||
contracted `png` artifact for Unity or Electron. Runtime-owner consumers can
|
||||
assert the ledger with
|
||||
`--expect-runtime-capture-probe <runtimeId:status:contractArtifactKind:captureOwnerRepo>`.
|
||||
|
||||
EveUnity split handoff evidence:
|
||||
|
||||
```powershell
|
||||
|
|
@ -579,7 +587,8 @@ Pending runtimes are allowed. Silent fake parity is not.
|
|||
## Next Cuts
|
||||
|
||||
1. Extend runtime-owned image/layout probes to the Unity/Electron capture
|
||||
blockers and move EveTui's JSON grid capture path into the EveTui owner repo.
|
||||
blockers with actual owner-produced image artifacts and move EveTui's JSON
|
||||
grid capture path into the EveTui owner repo.
|
||||
2. Normalize text scale and font loading across web, Flutter Android, Flutter
|
||||
desktop, and iOS.
|
||||
3. Give iOS a real `vn.stage` scene compositor instead of compact stacked
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
"worldSurfaceLoweringCoverage",
|
||||
"commandBoundaryCoverage",
|
||||
"screenshotComparisonMetrics",
|
||||
"runtimeCaptureProbeCoverage",
|
||||
"splitTargetBlockers",
|
||||
"splitHandoffMoveCoverage",
|
||||
"pluginHandoffMoveCoverage",
|
||||
|
|
@ -820,6 +821,118 @@
|
|||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"runtimeCaptureProbeCoverage": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"runtimeId",
|
||||
"runtimeStatus",
|
||||
"runtimeOwnerRepo",
|
||||
"splitTarget",
|
||||
"captureStatus",
|
||||
"captureOwnerRepo",
|
||||
"contractArtifactKind",
|
||||
"currentArtifactKind",
|
||||
"currentArtifactPath",
|
||||
"pendingProofs",
|
||||
"errors",
|
||||
"status",
|
||||
"severity",
|
||||
"detail"
|
||||
],
|
||||
"properties": {
|
||||
"runtimeId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"runtimeStatus": {
|
||||
"type": "string"
|
||||
},
|
||||
"runtimeOwnerRepo": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"splitTarget": {
|
||||
"type": "string"
|
||||
},
|
||||
"captureStatus": {
|
||||
"type": "string"
|
||||
},
|
||||
"lifecycleStatus": {
|
||||
"type": "string"
|
||||
},
|
||||
"captureOwnerRepo": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"captureKind": {
|
||||
"type": "string"
|
||||
},
|
||||
"contractArtifactKind": {
|
||||
"type": "string"
|
||||
},
|
||||
"contractRequestSchema": {
|
||||
"type": "string"
|
||||
},
|
||||
"requestBuilder": {
|
||||
"type": "string"
|
||||
},
|
||||
"advertisementPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"artifactPattern": {
|
||||
"type": "string"
|
||||
},
|
||||
"conformanceAttachment": {
|
||||
"type": "string"
|
||||
},
|
||||
"requiredProvider": {
|
||||
"type": "string"
|
||||
},
|
||||
"requiredSurface": {
|
||||
"type": "string"
|
||||
},
|
||||
"authority": {
|
||||
"type": "string"
|
||||
},
|
||||
"currentArtifactKind": {
|
||||
"type": "string"
|
||||
},
|
||||
"currentArtifactSchema": {
|
||||
"type": "string"
|
||||
},
|
||||
"currentArtifactPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"requestPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"sourceSurfacePath": {
|
||||
"type": "string"
|
||||
},
|
||||
"pendingProofs": {
|
||||
"type": "array"
|
||||
},
|
||||
"errors": {
|
||||
"type": "array"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"severity": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"detail": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
|
|
|||
|
|
@ -79,6 +79,10 @@ try {
|
|||
--expect-screenshot-metric web:sai-vn:color-tokens:pass `
|
||||
--expect-screenshot-metric web:sai-vn:bounding-boxes:pass `
|
||||
--expect-screenshot-metric electron-shell:aetheria-world:bounding-boxes:pending-capture `
|
||||
--expect-runtime-capture-probe unity-uitoolkit:semantic-artifact-present-capture-pending:png:EveUnity `
|
||||
--expect-runtime-capture-probe unity-scene:semantic-artifact-present-capture-pending:png:EveUnity `
|
||||
--expect-runtime-capture-probe electron-shell:semantic-artifact-present-capture-pending:png:EveElectron `
|
||||
--expect-runtime-capture-probe tui:contract-artifact-present:json-grid:EveTui `
|
||||
--expect-capability-gap "split-target:EveUnity:EveUnity:proof:Tagged UPM release" `
|
||||
--expect-conformance-handoff
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
|
|
|
|||
|
|
@ -142,6 +142,10 @@ node $consumerScript $consumerExport `
|
|||
--expect-runtime-capture-status tui:json-grid `
|
||||
--expect-runtime-capture-artifact tui:json-grid:gamecult.eve.tui_grid.v1:aetheria:aetheria.daemon.game `
|
||||
--expect-runtime-capture-status direct2d:missing `
|
||||
--expect-runtime-capture-probe unity-uitoolkit:semantic-artifact-present-capture-pending:png:EveUnity `
|
||||
--expect-runtime-capture-probe unity-scene:semantic-artifact-present-capture-pending:png:EveUnity `
|
||||
--expect-runtime-capture-probe electron-shell:semantic-artifact-present-capture-pending:png:EveElectron `
|
||||
--expect-runtime-capture-probe tui:contract-artifact-present:json-grid:EveTui `
|
||||
--expect-screenshot-metric web:embedded-surface:structure:pass `
|
||||
--expect-screenshot-metric web:embedded-surface:color-tokens:pass `
|
||||
--expect-screenshot-metric web:embedded-surface:text-presence:pass `
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ if (!exportDirectory) {
|
|||
" --expect-command-boundary-coverage <providerId:surfaceId:targetId:status:ownerRepo:runtimeId>",
|
||||
" --expect-world-lowering-gap <providerId:surfaceId:targetId:ownerRepo:runtimeId>",
|
||||
" --expect-screenshot-metric <runtimeId:fixtureId:metricKind:status>",
|
||||
" --expect-runtime-capture-probe <runtimeId:status:contractArtifactKind:captureOwnerRepo>",
|
||||
" --expect-conformance-handoff",
|
||||
" --expect-plugin-operation <pluginId:operation>",
|
||||
" --expect-plugin-capability <pluginId:capability>",
|
||||
|
|
@ -151,6 +152,7 @@ function validateIndex(index, directory, expectations, errors) {
|
|||
const worldSurfaceLoweringCoverage = Array.isArray(index.worldSurfaceLoweringCoverage) ? index.worldSurfaceLoweringCoverage : [];
|
||||
const commandBoundaryCoverage = Array.isArray(index.commandBoundaryCoverage) ? index.commandBoundaryCoverage : [];
|
||||
const screenshotComparisonMetrics = Array.isArray(index.screenshotComparisonMetrics) ? index.screenshotComparisonMetrics : [];
|
||||
const runtimeCaptureProbeCoverage = Array.isArray(index.runtimeCaptureProbeCoverage) ? index.runtimeCaptureProbeCoverage : [];
|
||||
const splitTargetBlockers = Array.isArray(index.splitTargetBlockers) ? index.splitTargetBlockers : [];
|
||||
const splitHandoffMoveCoverage = Array.isArray(index.splitHandoffMoveCoverage) ? index.splitHandoffMoveCoverage : [];
|
||||
const pluginHandoffMoveCoverage = Array.isArray(index.pluginHandoffMoveCoverage) ? index.pluginHandoffMoveCoverage : [];
|
||||
|
|
@ -186,6 +188,7 @@ function validateIndex(index, directory, expectations, errors) {
|
|||
validateWorldSurfaceLoweringCoverage(index.worldSurfaceLoweringCoverage, errors);
|
||||
validateCommandBoundaryCoverage(index.commandBoundaryCoverage, errors);
|
||||
validateScreenshotComparisonMetrics(index.screenshotComparisonMetrics, errors);
|
||||
validateRuntimeCaptureProbeCoverage(index.runtimeCaptureProbeCoverage, errors);
|
||||
validateWorldSurfaceLoweringGaps(index.worldSurfaceLoweringGaps, errors);
|
||||
for (const expectedGap of expectations.capabilityGaps) {
|
||||
if (!capabilityGaps.some(gap => capabilityGapText(gap).includes(expectedGap))) {
|
||||
|
|
@ -339,6 +342,22 @@ function validateIndex(index, directory, expectations, errors) {
|
|||
errors.push(`screenshotComparisonMetrics:${expectation.runtimeId}:${expectation.fixtureId}:${expectation.metricKind}:status:expected ${expectation.status} got ${metric.status || ""}`);
|
||||
}
|
||||
}
|
||||
for (const expectation of expectations.runtimeCaptureProbes) {
|
||||
const record = runtimeCaptureProbeCoverage.find(candidate => candidate.runtimeId === expectation.runtimeId);
|
||||
if (!record) {
|
||||
errors.push(`runtimeCaptureProbeCoverage:${expectation.runtimeId}:missing`);
|
||||
continue;
|
||||
}
|
||||
if (record.status !== expectation.status) {
|
||||
errors.push(`runtimeCaptureProbeCoverage:${expectation.runtimeId}:status:expected ${expectation.status} got ${record.status || ""}`);
|
||||
}
|
||||
if (record.contractArtifactKind !== expectation.contractArtifactKind) {
|
||||
errors.push(`runtimeCaptureProbeCoverage:${expectation.runtimeId}:contractArtifactKind:expected ${expectation.contractArtifactKind} got ${record.contractArtifactKind || ""}`);
|
||||
}
|
||||
if (record.captureOwnerRepo !== expectation.captureOwnerRepo) {
|
||||
errors.push(`runtimeCaptureProbeCoverage:${expectation.runtimeId}:captureOwnerRepo:expected ${expectation.captureOwnerRepo} got ${record.captureOwnerRepo || ""}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const expectedFixture of expectations.fixtures) {
|
||||
if (!fixtureIds.has(expectedFixture)) errors.push(`fixture:${expectedFixture}:missing`);
|
||||
|
|
@ -1030,6 +1049,31 @@ function validateScreenshotComparisonMetrics(records, errors) {
|
|||
}
|
||||
}
|
||||
|
||||
function validateRuntimeCaptureProbeCoverage(records, errors) {
|
||||
if (!Array.isArray(records)) {
|
||||
errors.push("runtimeCaptureProbeCoverage:missing");
|
||||
return;
|
||||
}
|
||||
if (!records.length) {
|
||||
errors.push("runtimeCaptureProbeCoverage:empty");
|
||||
return;
|
||||
}
|
||||
for (const [index, record] of records.entries()) {
|
||||
for (const field of ["runtimeId", "runtimeOwnerRepo", "captureOwnerRepo", "status", "severity", "detail"]) {
|
||||
if (!record?.[field]) errors.push(`runtimeCaptureProbeCoverage:${index}:${field}:missing`);
|
||||
}
|
||||
for (const field of ["pendingProofs", "errors"]) {
|
||||
if (!Array.isArray(record?.[field])) errors.push(`runtimeCaptureProbeCoverage:${index}:${field}:expected array`);
|
||||
}
|
||||
if (record?.currentArtifactPath && !record.currentArtifactKind) {
|
||||
errors.push(`runtimeCaptureProbeCoverage:${index}:currentArtifactKind:missing`);
|
||||
}
|
||||
if (record?.contractArtifactKind && !record.contractRequestSchema) {
|
||||
errors.push(`runtimeCaptureProbeCoverage:${index}:contractRequestSchema:missing`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validateWorldSurfaceLoweringGaps(gaps, errors) {
|
||||
if (!Array.isArray(gaps)) {
|
||||
errors.push("worldSurfaceLoweringGaps:missing");
|
||||
|
|
@ -1261,6 +1305,7 @@ function parseArguments(args) {
|
|||
commandBoundaryCoverage: [],
|
||||
worldLoweringGaps: [],
|
||||
screenshotMetrics: [],
|
||||
runtimeCaptureProbes: [],
|
||||
conformanceHandoff: false,
|
||||
};
|
||||
const optionTargets = new Map([
|
||||
|
|
@ -1318,6 +1363,7 @@ function parseArguments(args) {
|
|||
["--expect-command-boundary-coverage", expectations.commandBoundaryCoverage],
|
||||
["--expect-world-lowering-gap", expectations.worldLoweringGaps],
|
||||
["--expect-screenshot-metric", expectations.screenshotMetrics],
|
||||
["--expect-runtime-capture-probe", expectations.runtimeCaptureProbes],
|
||||
]);
|
||||
|
||||
for (let index = 1; index < args.length; index += 1) {
|
||||
|
|
@ -1418,6 +1464,8 @@ function parseArguments(args) {
|
|||
target.push(parseCommandBoundaryCoverageExpectation(value));
|
||||
} else if (option === "--expect-screenshot-metric") {
|
||||
target.push(parseScreenshotMetricExpectation(value));
|
||||
} else if (option === "--expect-runtime-capture-probe") {
|
||||
target.push(parseRuntimeCaptureProbeExpectation(value));
|
||||
} else if (option === "--expect-runtime-plugin-gap") {
|
||||
target.push(parseRuntimePluginGapExpectation(value));
|
||||
} else if (option === "--expect-runtime-plugin-projection") {
|
||||
|
|
@ -1453,6 +1501,20 @@ function parseScreenshotMetricExpectation(value) {
|
|||
};
|
||||
}
|
||||
|
||||
function parseRuntimeCaptureProbeExpectation(value) {
|
||||
const parts = value.split(":");
|
||||
if (parts.length !== 4 || parts.some(part => !part)) {
|
||||
console.error(`Expected runtime capture probe in <runtimeId:status:contractArtifactKind:captureOwnerRepo> form, got: ${value}`);
|
||||
process.exit(2);
|
||||
}
|
||||
return {
|
||||
runtimeId: parts[0],
|
||||
status: parts[1],
|
||||
contractArtifactKind: parts[2],
|
||||
captureOwnerRepo: parts[3],
|
||||
};
|
||||
}
|
||||
|
||||
function parseLocalProviderCatalogExpectation(value) {
|
||||
const parts = value.split(":");
|
||||
if (parts.length !== 3 || parts.some(part => !part)) {
|
||||
|
|
|
|||
|
|
@ -2003,6 +2003,7 @@ function buildConformanceExport(report) {
|
|||
worldSurfaceLoweringCoverage: collectWorldSurfaceLoweringCoverage(report),
|
||||
commandBoundaryCoverage: collectCommandBoundaryCoverage(report),
|
||||
screenshotComparisonMetrics: collectScreenshotComparisonMetrics(report),
|
||||
runtimeCaptureProbeCoverage: collectRuntimeCaptureProbeCoverage(report),
|
||||
worldSurfaceLoweringGaps: collectWorldSurfaceLoweringGaps(report),
|
||||
splitTargetBlockers: collectSplitTargetBlockers(report),
|
||||
splitHandoffMoveCoverage: collectSplitHandoffMoveCoverage(report),
|
||||
|
|
@ -2560,6 +2561,94 @@ function collectScreenshotComparisonMetrics(report) {
|
|||
`${left.runtimeId}:${left.fixtureId}:${left.metricKind}`.localeCompare(`${right.runtimeId}:${right.fixtureId}:${right.metricKind}`));
|
||||
}
|
||||
|
||||
function collectRuntimeCaptureProbeCoverage(report) {
|
||||
return (report.runtimes || [])
|
||||
.filter(runtime => (runtime.capture?.artifacts || []).length || runtime.lifecycle?.capture?.captureContract)
|
||||
.map(runtime => buildRuntimeCaptureProbeRecord(runtime))
|
||||
.sort((left, right) => left.runtimeId.localeCompare(right.runtimeId));
|
||||
}
|
||||
|
||||
function buildRuntimeCaptureProbeRecord(runtime) {
|
||||
const capture = runtime.capture || {};
|
||||
const lifecycleCapture = runtime.lifecycle?.capture || {};
|
||||
const contract = lifecycleCapture.captureContract || {};
|
||||
const artifacts = Array.isArray(capture.artifacts) ? capture.artifacts : [];
|
||||
const artifact = selectCaptureArtifactForContract(artifacts, contract) || artifacts[0] || {};
|
||||
const errors = runtime.captureArtifactErrors || [];
|
||||
const contractArtifactKind = contract.artifactKind || "";
|
||||
const currentArtifactKind = artifact.kind || "";
|
||||
const hasContract = Boolean(contractArtifactKind || contract.captureKind || contract.requestSchema);
|
||||
const artifactPresent = Boolean(currentArtifactKind && artifact.path);
|
||||
const contractArtifactPresent = hasContract && artifactPresent && currentArtifactKind === contractArtifactKind && !errors.length;
|
||||
const semanticArtifactOnly = hasContract && artifactPresent && currentArtifactKind !== contractArtifactKind && !errors.length;
|
||||
|
||||
let status = "missing-capture-contract";
|
||||
let severity = "blocker";
|
||||
let detail = "Runtime has capture evidence but no lifecycle capture contract.";
|
||||
if (contractArtifactPresent) {
|
||||
status = "contract-artifact-present";
|
||||
severity = "info";
|
||||
detail = "Runtime capture artifact satisfies the lifecycle capture contract.";
|
||||
} else if (semanticArtifactOnly) {
|
||||
status = "semantic-artifact-present-capture-pending";
|
||||
severity = contractArtifactKind === "png" ? "blocker" : "gap";
|
||||
detail = `Runtime publishes ${currentArtifactKind} semantic evidence; ${contractArtifactKind || "contract"} capture remains owner-repo work.`;
|
||||
} else if (hasContract && errors.length) {
|
||||
status = "invalid-current-artifact";
|
||||
severity = "blocker";
|
||||
detail = "Runtime capture artifact is declared but invalid or missing.";
|
||||
} else if (hasContract && !artifactPresent) {
|
||||
status = "contract-artifact-missing";
|
||||
severity = "blocker";
|
||||
detail = "Runtime lifecycle capture contract exists, but no capture artifact is attached.";
|
||||
}
|
||||
|
||||
return {
|
||||
runtimeId: runtime.id,
|
||||
runtimeStatus: runtime.status,
|
||||
runtimeOwnerRepo: runtime.ownerRepo || "",
|
||||
splitTarget: runtime.splitTarget || "",
|
||||
captureStatus: capture.status || "",
|
||||
lifecycleStatus: lifecycleCapture.status || "",
|
||||
captureOwnerRepo: contract.ownerRepo || lifecycleCapture.ownerRepo || runtime.splitTarget || runtime.ownerRepo || "",
|
||||
captureKind: contract.captureKind || "",
|
||||
contractArtifactKind,
|
||||
contractRequestSchema: contract.requestSchema || "",
|
||||
requestBuilder: contract.requestBuilder || "",
|
||||
advertisementPath: contract.advertisementPath || "",
|
||||
artifactPattern: contract.artifactPattern || "",
|
||||
conformanceAttachment: contract.conformanceAttachment || "",
|
||||
requiredProvider: contract.requiredProvider || "",
|
||||
requiredSurface: contract.requiredSurface || "",
|
||||
authority: contract.authority || "",
|
||||
currentArtifactKind,
|
||||
currentArtifactSchema: artifact.schema || "",
|
||||
currentArtifactPath: artifact.path || "",
|
||||
requestPath: artifact.requestPath || "",
|
||||
sourceSurfacePath: artifact.sourceSurfacePath || "",
|
||||
pendingProofs: lifecycleCapture.pendingProofs || [],
|
||||
errors,
|
||||
status,
|
||||
severity,
|
||||
detail,
|
||||
};
|
||||
}
|
||||
|
||||
function selectCaptureArtifactForContract(artifacts, contract) {
|
||||
if (!artifacts.length) return null;
|
||||
const requiredProvider = contract.requiredProvider || "";
|
||||
const requiredSurface = contract.requiredSurface || "";
|
||||
const contractArtifactKind = contract.artifactKind || "";
|
||||
return artifacts.find(artifact =>
|
||||
(!requiredProvider || artifact.providerId === requiredProvider)
|
||||
&& (!requiredSurface || artifact.surfaceId === requiredSurface)
|
||||
&& (!contractArtifactKind || artifact.kind === contractArtifactKind))
|
||||
|| artifacts.find(artifact =>
|
||||
(!requiredProvider || artifact.providerId === requiredProvider)
|
||||
&& (!requiredSurface || artifact.surfaceId === requiredSurface))
|
||||
|| null;
|
||||
}
|
||||
|
||||
function buildStructureMetric(runtime, fixture) {
|
||||
const checks = (fixture.checks || []).filter(check =>
|
||||
check.id.startsWith("component:")
|
||||
|
|
|
|||
Loading…
Reference in a new issue