diff --git a/docs/eve-migration-roadmap.md b/docs/eve-migration-roadmap.md index 408a2e9..1f5b141 100644 --- a/docs/eve-migration-roadmap.md +++ b/docs/eve-migration-roadmap.md @@ -304,6 +304,12 @@ Recently cut: as `claimed`, `missing-claim`, or `missing-runtime`. The generic conformance consumer now asserts Aetheria's claimed web/UI Toolkit targets and missing Unity scene/TUI targets from the same typed surface. +- The conformance export carries `commandBoundaryCoverage[]`, joining each + provider-owned interactive world surface target to its runtime owner, + provider command boundary, receipt schema, and runtime command envelope. Web + 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 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 diff --git a/docs/parity-testing-harness.md b/docs/parity-testing-harness.md index ba80770..cb04786 100644 --- a/docs/parity-testing-harness.md +++ b/docs/parity-testing-harness.md @@ -126,6 +126,15 @@ receipt schema, lowering targets, and ownership rule so runtime lowerers can discover provider-authored world surfaces without importing provider source layout. Consumer smokes can assert advertised targets with `--expect-interactive-world-surface `. +The command path for those targets is exported as +`commandBoundaryCoverage[]`. Each record joins a provider-owned surface, +advertised lowering target, runtime owner, provider command boundary, receipt +schema, and runtime command envelope schema. Status is `covered` only when the +runtime has claimed the target and advertises `gamecult.eve.command.v1`; +otherwise it reports `missing-runtime`, `missing-runtime-claim`, +`missing-provider-boundary`, or `missing-command-transport`. Consumer smokes can +assert it with +`--expect-command-boundary-coverage `. Declared runtime/plugin projection gaps are exported as `runtimePluginProjectionGaps[]`. These records carry runtime id, runtime owner, split target, plugin id, reason, severity, and fixture lists so EveUnity, diff --git a/schemas/gamecult.eve.conformance_export.v1.schema.json b/schemas/gamecult.eve.conformance_export.v1.schema.json index 503bb7d..5c500bf 100644 --- a/schemas/gamecult.eve.conformance_export.v1.schema.json +++ b/schemas/gamecult.eve.conformance_export.v1.schema.json @@ -15,6 +15,7 @@ "providerPluginRequirementCoverage", "interactiveWorldSurfaces", "worldSurfaceLoweringCoverage", + "commandBoundaryCoverage", "splitTargetBlockers", "splitTargets" ], @@ -414,6 +415,79 @@ "additionalProperties": true } }, + "commandBoundaryCoverage": { + "type": "array", + "items": { + "type": "object", + "required": [ + "providerId", + "providerOwnerRepo", + "surfaceId", + "surfaceKind", + "targetId", + "runtimeId", + "runtimeOwnerRepo", + "status", + "severity", + "commandBoundary", + "receiptSchema", + "runtimeCommandSchema" + ], + "properties": { + "providerId": { + "type": "string", + "minLength": 1 + }, + "providerOwnerRepo": { + "type": "string", + "minLength": 1 + }, + "surfaceId": { + "type": "string", + "minLength": 1 + }, + "surfaceKind": { + "type": "string" + }, + "targetId": { + "type": "string", + "minLength": 1 + }, + "runtimeId": { + "type": "string", + "minLength": 1 + }, + "runtimeOwnerRepo": { + "type": "string", + "minLength": 1 + }, + "splitTarget": { + "type": "string" + }, + "runtimeStatus": { + "type": "string" + }, + "status": { + "type": "string", + "minLength": 1 + }, + "severity": { + "type": "string", + "minLength": 1 + }, + "commandBoundary": { + "type": "string" + }, + "receiptSchema": { + "type": "string" + }, + "runtimeCommandSchema": { + "type": "string" + } + }, + "additionalProperties": true + } + }, "plugins": { "type": "array", "items": { diff --git a/scripts/run-conformance-consumer-smoke.ps1 b/scripts/run-conformance-consumer-smoke.ps1 index 69bd604..0fa2a36 100644 --- a/scripts/run-conformance-consumer-smoke.ps1 +++ b/scripts/run-conformance-consumer-smoke.ps1 @@ -39,6 +39,10 @@ try { --expect-world-lowering-coverage aetheria:aetheria.daemon.game:unity-uitoolkit:claimed:EveUnity:unity-uitoolkit ` --expect-world-lowering-coverage aetheria:aetheria.daemon.game:unity-scene:missing-claim:EveUnity:unity-scene ` --expect-world-lowering-coverage aetheria:aetheria.daemon.editor:tui:missing-claim:EveTui:tui ` + --expect-command-boundary-coverage aetheria:aetheria.daemon.game:web-reference:covered:Eve:web ` + --expect-command-boundary-coverage aetheria:aetheria.daemon.game:unity-uitoolkit:covered:EveUnity:unity-uitoolkit ` + --expect-command-boundary-coverage aetheria:aetheria.daemon.game:unity-scene:missing-runtime-claim:EveUnity:unity-scene ` + --expect-command-boundary-coverage aetheria:aetheria.daemon.editor:tui:missing-runtime-claim:EveTui:tui ` --expect-world-lowering-gap aetheria:aetheria.daemon.game:unity-scene:EveUnity:unity-scene ` --expect-world-lowering-gap aetheria:aetheria.daemon.game:tui:EveTui:tui ` --expect-world-lowering-gap aetheria:aetheria.daemon.editor:tui:EveTui:tui ` diff --git a/tools/conformance/consume-export.mjs b/tools/conformance/consume-export.mjs index fb3945d..9c1220c 100644 --- a/tools/conformance/consume-export.mjs +++ b/tools/conformance/consume-export.mjs @@ -20,6 +20,7 @@ if (!exportDirectory) { " --expect-capability-gap ", " --expect-runtime-plugin-gap ", " --expect-world-lowering-coverage ", + " --expect-command-boundary-coverage ", " --expect-world-lowering-gap ", " --expect-conformance-handoff", " --expect-plugin-operation ", @@ -133,6 +134,7 @@ function validateIndex(index, directory, expectations, errors) { const runtimePluginProjectionGaps = Array.isArray(index.runtimePluginProjectionGaps) ? index.runtimePluginProjectionGaps : []; const interactiveWorldSurfaces = Array.isArray(index.interactiveWorldSurfaces) ? index.interactiveWorldSurfaces : []; 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 worldSurfaceLoweringGaps = Array.isArray(index.worldSurfaceLoweringGaps) ? index.worldSurfaceLoweringGaps : []; @@ -157,6 +159,7 @@ function validateIndex(index, directory, expectations, errors) { validateRuntimePluginProjectionGaps(index.runtimePluginProjectionGaps, errors); validateSplitTargetBlockers(index.splitTargetBlockers, errors); validateWorldSurfaceLoweringCoverage(index.worldSurfaceLoweringCoverage, errors); + validateCommandBoundaryCoverage(index.commandBoundaryCoverage, errors); validateWorldSurfaceLoweringGaps(index.worldSurfaceLoweringGaps, errors); for (const expectedGap of expectations.capabilityGaps) { if (!capabilityGaps.some(gap => capabilityGapText(gap).includes(expectedGap))) { @@ -197,6 +200,25 @@ function validateIndex(index, directory, expectations, errors) { errors.push(`worldSurfaceLoweringCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.targetId}:runtimeId:expected ${expectation.runtimeId} got ${coverage.runtimeId || ""}`); } } + for (const expectation of expectations.commandBoundaryCoverage) { + const coverage = commandBoundaryCoverage.find(candidate => + candidate.providerId === expectation.providerId && + candidate.surfaceId === expectation.surfaceId && + candidate.targetId === expectation.targetId); + if (!coverage) { + errors.push(`commandBoundaryCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.targetId}:missing`); + continue; + } + if (coverage.status !== expectation.status) { + errors.push(`commandBoundaryCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.targetId}:status:expected ${expectation.status} got ${coverage.status || ""}`); + } + if (coverage.runtimeOwnerRepo !== expectation.ownerRepo) { + errors.push(`commandBoundaryCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.targetId}:runtimeOwnerRepo:expected ${expectation.ownerRepo} got ${coverage.runtimeOwnerRepo || ""}`); + } + if (coverage.runtimeId !== expectation.runtimeId) { + errors.push(`commandBoundaryCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.targetId}:runtimeId:expected ${expectation.runtimeId} got ${coverage.runtimeId || ""}`); + } + } for (const expectation of expectations.worldLoweringGaps) { const gap = worldSurfaceLoweringGaps.find(candidate => candidate.providerId === expectation.providerId && @@ -726,6 +748,21 @@ function validateWorldSurfaceLoweringCoverage(records, errors) { } } +function validateCommandBoundaryCoverage(records, errors) { + if (!Array.isArray(records)) { + errors.push("commandBoundaryCoverage:missing"); + return; + } + for (const [index, record] of records.entries()) { + for (const field of ["providerId", "providerOwnerRepo", "surfaceId", "targetId", "runtimeId", "runtimeOwnerRepo", "status", "severity", "commandBoundary", "receiptSchema"]) { + if (!record?.[field]) errors.push(`commandBoundaryCoverage:${index}:${field}:missing`); + } + if (record?.status === "covered" && record.runtimeCommandSchema !== "gamecult.eve.command.v1") { + errors.push(`commandBoundaryCoverage:${index}:runtimeCommandSchema:expected gamecult.eve.command.v1 got ${record.runtimeCommandSchema || ""}`); + } + } +} + function validateWorldSurfaceLoweringGaps(gaps, errors) { if (!Array.isArray(gaps)) { errors.push("worldSurfaceLoweringGaps:missing"); @@ -899,6 +936,7 @@ function parseArguments(args) { capabilityGaps: [], runtimePluginGaps: [], worldLoweringCoverage: [], + commandBoundaryCoverage: [], worldLoweringGaps: [], conformanceHandoff: false, }; @@ -944,6 +982,7 @@ function parseArguments(args) { ["--expect-capability-gap", expectations.capabilityGaps], ["--expect-runtime-plugin-gap", expectations.runtimePluginGaps], ["--expect-world-lowering-coverage", expectations.worldLoweringCoverage], + ["--expect-command-boundary-coverage", expectations.commandBoundaryCoverage], ["--expect-world-lowering-gap", expectations.worldLoweringGaps], ]); @@ -1033,6 +1072,8 @@ function parseArguments(args) { target.push(parseWorldLoweringGapExpectation(value)); } else if (option === "--expect-world-lowering-coverage") { target.push(parseWorldLoweringCoverageExpectation(value)); + } else if (option === "--expect-command-boundary-coverage") { + target.push(parseCommandBoundaryCoverageExpectation(value)); } else if (option === "--expect-runtime-plugin-gap") { target.push(parseRuntimePluginGapExpectation(value)); } else { @@ -1075,6 +1116,22 @@ function parseWorldLoweringCoverageExpectation(value) { }; } +function parseCommandBoundaryCoverageExpectation(value) { + const parts = value.split(":"); + if (parts.length !== 6 || parts.some(part => !part)) { + console.error(`Expected command boundary coverage in form, got: ${value}`); + process.exit(2); + } + return { + providerId: parts[0], + surfaceId: parts[1], + targetId: parts[2], + status: parts[3], + ownerRepo: parts[4], + runtimeId: parts[5], + }; +} + function parseRuntimePluginGapExpectation(value) { const parts = value.split(":"); if (parts.length !== 3 || parts.some(part => !part)) { diff --git a/tools/parity/run-parity.mjs b/tools/parity/run-parity.mjs index 1612a00..7ff8efe 100644 --- a/tools/parity/run-parity.mjs +++ b/tools/parity/run-parity.mjs @@ -1672,6 +1672,7 @@ function buildConformanceExport(report) { runtimePluginProjectionGaps: collectRuntimePluginProjectionGaps(report), interactiveWorldSurfaces: collectInteractiveWorldSurfaces(report), worldSurfaceLoweringCoverage: collectWorldSurfaceLoweringCoverage(report), + commandBoundaryCoverage: collectCommandBoundaryCoverage(report), worldSurfaceLoweringGaps: collectWorldSurfaceLoweringGaps(report), splitTargetBlockers: collectSplitTargetBlockers(report), pluginAbiOperationCoverage: collectPluginAbiOperationCoverage(report), @@ -2069,6 +2070,46 @@ function collectWorldSurfaceLoweringCoverage(report) { return coverage; } +function collectCommandBoundaryCoverage(report) { + const { claimByTarget, runtimeByTarget } = buildWorldSurfaceLoweringTargetIndex(report); + const coverage = []; + for (const surface of collectInteractiveWorldSurfaces(report)) { + for (const targetId of surface.loweringTargets || []) { + const claim = claimByTarget.get(targetId); + const runtime = runtimeByTarget.get(targetId); + const runtimeCommandSchema = runtime?.commandTransportSmoke?.schema || ""; + const missingProviderBoundary = !surface.commandBoundary || !surface.receiptSchema; + let status = "covered"; + if (!runtime) { + status = "missing-runtime"; + } else if (!claim) { + status = "missing-runtime-claim"; + } else if (missingProviderBoundary) { + status = "missing-provider-boundary"; + } else if (runtimeCommandSchema !== "gamecult.eve.command.v1") { + status = "missing-command-transport"; + } + coverage.push({ + providerId: surface.providerId, + providerOwnerRepo: surface.ownerRepo || "", + surfaceId: surface.surfaceId, + surfaceKind: surface.surfaceKind || "", + targetId, + runtimeId: runtime?.id || "world-surface-lowering", + runtimeOwnerRepo: runtime ? resolveRuntimeProjectionOwnerRepo(runtime) : "Eve", + splitTarget: runtime?.splitTarget || "", + runtimeStatus: runtime?.status || "", + status, + severity: status === "covered" ? "ok" : "blocker", + commandBoundary: surface.commandBoundary || "", + receiptSchema: surface.receiptSchema || "", + runtimeCommandSchema, + }); + } + } + return coverage; +} + function resolveRuntimeProjectionOwnerRepo(runtime) { return runtime.lifecycle?.release?.ownerRepo || runtime.lifecycle?.test?.ownerRepo