Export runtime capture probe coverage

This commit is contained in:
Meta Vens 2026-07-09 16:13:06 +01:00
commit 419c747ba6
7 changed files with 290 additions and 1 deletions

View file

@ -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`,

View file

@ -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

View file

@ -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": {

View file

@ -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) {

View file

@ -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 `

View file

@ -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)) {

View file

@ -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:")