commit 4cdfa9c6ebb0105352542990edd965b35b96ef9b Author: Meta Vens Date: Fri Jul 10 10:25:17 2026 +0100 Initialize Eve conformance owner diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5a70966 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +artifacts/ +node_modules/ + diff --git a/README.md b/README.md new file mode 100644 index 0000000..b66f2ed --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# EveConformance + +EveConformance owns the cross-repository evidence that an Eve provider, plugin, +or runtime obeys the contracts published by the Eve kernel. + +Eve owns schemas and the browser reference lowerer. Provider repos own live +state and scenarios. Plugin repos own semantic sidecars. Runtime repos own +native projection and captures. This repo reads those public surfaces and emits +portable parity reports and conformance packs; it does not import their private +implementation bodies. + +## Run + +```powershell +powershell -ExecutionPolicy Bypass -File .\scripts\run-conformance.ps1 +``` + +The default workspace assumes sibling repositories under `E:\Projects`. Set +`EVE_KERNEL_ROOT` when the Eve checkout lives elsewhere. + diff --git a/package.json b/package.json new file mode 100644 index 0000000..3d70e27 --- /dev/null +++ b/package.json @@ -0,0 +1,9 @@ +{ + "name": "@gamecult/eve-conformance", + "private": true, + "type": "module", + "scripts": { + "test": "powershell -ExecutionPolicy Bypass -File ./scripts/run-conformance.ps1" + } +} + diff --git a/scripts/run-conformance.ps1 b/scripts/run-conformance.ps1 new file mode 100644 index 0000000..2a09c94 --- /dev/null +++ b/scripts/run-conformance.ps1 @@ -0,0 +1,22 @@ +param( + [string] $KernelRoot = $(if ($env:EVE_KERNEL_ROOT) { $env:EVE_KERNEL_ROOT } else { "E:\Projects\Eve" }), + [string] $OutputDirectory = "artifacts\parity" +) + +$ErrorActionPreference = "Stop" +$repoRoot = Split-Path -Parent $PSScriptRoot +$env:EVE_KERNEL_ROOT = (Resolve-Path $KernelRoot).Path +$env:EVE_PARITY_OUTPUT = if ([IO.Path]::IsPathRooted($OutputDirectory)) { + $OutputDirectory +} else { + Join-Path $repoRoot $OutputDirectory +} +$env:EVE_CONFORMANCE_OUTPUT = Join-Path $repoRoot "artifacts\conformance" + +Push-Location $repoRoot +try { + node .\tools\parity\run-parity.mjs + node .\tools\conformance\consume-export.mjs .\artifacts\conformance\latest +} finally { + Pop-Location +} diff --git a/tools/conformance/attach-plugin-witness.mjs b/tools/conformance/attach-plugin-witness.mjs new file mode 100644 index 0000000..58faec0 --- /dev/null +++ b/tools/conformance/attach-plugin-witness.mjs @@ -0,0 +1,44 @@ +import { mkdir, readFile, writeFile } from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { validateSchemaSubset } from "../../runtimes/incubating/test-support/schema-subset.mjs"; + +const [witnessArgument, exportArgument] = process.argv.slice(2); +if (!witnessArgument || !exportArgument) { + console.error("Usage: node tools/conformance/attach-plugin-witness.mjs "); + process.exit(2); +} +const witnessPath = path.resolve(witnessArgument); +const exportDirectory = path.resolve(exportArgument); +const indexPath = path.join(exportDirectory, "index.json"); +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); +const witness = parseJson(await readFile(witnessPath, "utf8")); +const schema = parseJson(await readFile(path.join(repoRoot, "schemas/gamecult.eve.plugin_witness.v1.schema.json"), "utf8")); +const schemaErrors = validateSchemaSubset(schema, witness); +if (schemaErrors.length) throw new Error(`Plugin witness schema errors: ${schemaErrors.join("; ")}`); + +const attachmentRoot = path.join(exportDirectory, "attachments", "plugin", safeSegment(witness.witnessId)); +await mkdir(attachmentRoot, { recursive: true }); +const exportedWitnessPath = path.join(attachmentRoot, "witness.json"); +await writeFile(exportedWitnessPath, `${JSON.stringify(witness, null, 2)}\n`); + +const index = parseJson(await readFile(indexPath, "utf8")); +const record = { + witnessId: witness.witnessId, + pluginId: witness.pluginId, + ownerRepo: witness.ownerRepo, + status: witness.status, + transport: witness.transport, + operationCount: witness.operations.length, + witnessExportPath: slash(path.relative(exportDirectory, exportedWitnessPath)), +}; +index.pluginWitnesses = [ + ...(Array.isArray(index.pluginWitnesses) ? index.pluginWitnesses : []).filter(candidate => candidate.witnessId !== record.witnessId), + record, +].sort((left, right) => left.witnessId.localeCompare(right.witnessId)); +await writeFile(indexPath, `${JSON.stringify(index, null, 2)}\n`); +console.log(exportedWitnessPath); + +function safeSegment(value) { return String(value).replace(/[^a-zA-Z0-9._-]+/g, "-"); } +function slash(value) { return value.replaceAll("\\", "/"); } +function parseJson(value) { return JSON.parse(value.replace(/^\uFEFF/, "")); } diff --git a/tools/conformance/attach-runtime-witness.mjs b/tools/conformance/attach-runtime-witness.mjs new file mode 100644 index 0000000..c42adad --- /dev/null +++ b/tools/conformance/attach-runtime-witness.mjs @@ -0,0 +1,78 @@ +import { copyFile, mkdir, readFile, writeFile } from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { validateSchemaSubset } from "../../runtimes/incubating/test-support/schema-subset.mjs"; + +const [witnessArgument, exportArgument] = process.argv.slice(2); +if (!witnessArgument || !exportArgument) { + console.error("Usage: node tools/conformance/attach-runtime-witness.mjs "); + process.exit(2); +} + +const witnessPath = path.resolve(witnessArgument); +const exportDirectory = path.resolve(exportArgument); +const indexPath = path.join(exportDirectory, "index.json"); +const witness = parseJson(await readFile(witnessPath, "utf8")); +validateWitness(witness); +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); +const witnessSchema = parseJson(await readFile(path.join(repoRoot, "schemas/gamecult.eve.runtime_witness.v1.schema.json"), "utf8")); +const schemaErrors = validateSchemaSubset(witnessSchema, witness); +if (schemaErrors.length) throw new Error(`Runtime witness schema errors: ${schemaErrors.join("; ")}`); + +const attachmentRoot = path.join(exportDirectory, "attachments", "runtime", safeSegment(witness.witnessId)); +await mkdir(attachmentRoot, { recursive: true }); +const artifacts = []; +for (const artifact of witness.artifacts) { + const sourcePath = path.resolve(path.dirname(witnessPath), artifact.path); + const fileName = path.basename(sourcePath); + const destinationPath = path.join(attachmentRoot, fileName); + await copyFile(sourcePath, destinationPath); + artifacts.push({ + ...artifact, + exportPath: slash(path.relative(exportDirectory, destinationPath)), + }); +} + +const exportedWitness = { ...witness, artifacts }; +const exportedWitnessPath = path.join(attachmentRoot, "witness.json"); +await writeFile(exportedWitnessPath, `${JSON.stringify(exportedWitness, null, 2)}\n`); + +const index = parseJson(await readFile(indexPath, "utf8")); +const record = { + witnessId: witness.witnessId, + runtimeId: witness.runtimeId, + runtimeOwnerRepo: witness.runtimeOwnerRepo, + providerId: witness.providerId, + surfaceId: witness.surfaceId, + projectionKind: witness.projectionKind, + status: witness.status, + cacheState: witness.execution.cacheState, + witnessExportPath: slash(path.relative(exportDirectory, exportedWitnessPath)), + artifactCount: artifacts.length, +}; +index.runtimeWitnesses = [ + ...(Array.isArray(index.runtimeWitnesses) ? index.runtimeWitnesses : []).filter(candidate => candidate.witnessId !== record.witnessId), + record, +].sort((left, right) => left.witnessId.localeCompare(right.witnessId)); +await writeFile(indexPath, `${JSON.stringify(index, null, 2)}\n`); +console.log(exportedWitnessPath); + +function validateWitness(value) { + for (const field of ["witnessId", "runtimeId", "runtimeOwnerRepo", "providerId", "surfaceId", "projectionKind", "status", "generatedAtUtc", "authority"]) { + if (!value?.[field]) throw new Error(`Runtime witness missing ${field}.`); + } + if (value.schema !== "gamecult.eve.runtime_witness.v1") throw new Error(`Unexpected runtime witness schema: ${value.schema || ""}`); + if (!value.execution?.cacheState || !Number.isFinite(value.execution?.durationMs)) throw new Error("Runtime witness execution is incomplete."); + if (!value.assertions || typeof value.assertions !== "object") throw new Error("Runtime witness assertions are missing."); + if (value.receipts !== undefined && !Array.isArray(value.receipts)) throw new Error("Runtime witness receipts must be an array when present."); + if (!Array.isArray(value.artifacts) || !value.artifacts.length) throw new Error("Runtime witness artifacts are missing."); + for (const artifact of value.artifacts) { + if (!artifact.path || !/^[a-f0-9]{64}$/i.test(artifact.sha256 || "") || !(artifact.sizeBytes > 0)) { + throw new Error(`Runtime witness artifact is invalid: ${artifact.path || "unknown"}`); + } + } +} + +function safeSegment(value) { return String(value).replace(/[^a-zA-Z0-9._-]+/g, "-"); } +function slash(value) { return value.replaceAll("\\", "/"); } +function parseJson(value) { return JSON.parse(value.replace(/^\uFEFF/, "")); } diff --git a/tools/conformance/consume-export.mjs b/tools/conformance/consume-export.mjs new file mode 100644 index 0000000..140c97b --- /dev/null +++ b/tools/conformance/consume-export.mjs @@ -0,0 +1,2100 @@ +import { readFile } from "node:fs/promises"; +import { existsSync } from "node:fs"; +import { readFileSync } from "node:fs"; +import path from "node:path"; + +const { exportDirectory, expectations } = parseArguments(process.argv.slice(2)); +if (!exportDirectory) { + console.error([ + "Usage: node tools/conformance/consume-export.mjs [expectations]", + "", + "Expectations:", + " --expect-pack ", + " --expect-fixture ", + " --expect-plugin ", + " --expect-provider ", + " --expect-runtime ", + " --expect-scenario ", + " --expect-split-target ", + " --expect-capability-matrix", + " --expect-schema ", + " --expect-capability-gap ", + " --expect-runtime-plugin-projection ", + " --expect-provider-runtime-plugin-projection ", + " --expect-runtime-plugin-gap ", + " --expect-local-provider-catalog ", + " --expect-local-provider-catalog-provider ", + " --expect-local-provider-catalog-advertisement ", + " --expect-world-lowering-coverage ", + " --expect-command-boundary-coverage ", + " --expect-world-lowering-gap ", + " --expect-screenshot-metric ", + " --expect-runtime-capture-probe ", + " --expect-conformance-handoff", + " --expect-plugin-operation ", + " --expect-plugin-capability ", + " --expect-plugin-runtime ", + " --expect-plugin-runtime-transport ", + " --expect-plugin-runtime-authority ", + " --expect-plugin-runtime-field ", + " --expect-plugin-abi-field ", + " --expect-plugin-abi-operation-coverage ", + " --expect-plugin-handoff ", + " --expect-plugin-witness ", + " --expect-plugin-handoff-move ", + " --expect-provider-handoff-move ", + " --expect-provider-surface ", + " --expect-provider-surface-kind ", + " --expect-provider-surface-field ", + " --expect-interactive-world-surface ", + " --expect-provider-plugin-requirement ", + " --expect-independent-nested-plugin ", + " --expect-provider-command ", + " --expect-provider-receipt-state ", + " --expect-provider-handoff ", + " --expect-runtime-status ", + " --expect-runtime-feature ", + " --expect-runtime-world-target ", + " --expect-runtime-world-field ", + " --expect-runtime-handoff ", + " --expect-runtime-command-schema ", + " --expect-runtime-capture-status ", + " --expect-runtime-capture-artifact ", + " --expect-runtime-witness ", + " --expect-runtime-lifecycle-status ", + " --expect-runtime-lifecycle-pending ", + " --expect-runtime-lifecycle-field ", + " --expect-split-target-status ", + " --expect-split-target-blocker ", + " --expect-split-target-blocker-record ", + " --expect-split-handoff-move ", + " --expect-split-target-proof ", + ].join("\n")); + process.exit(2); +} + +const indexPath = path.join(exportDirectory, "index.json"); +const errors = []; + +if (!existsSync(indexPath)) { + errors.push(`index.json:missing:${indexPath}`); +} else { + const index = JSON.parse(await readFile(indexPath, "utf8")); + validateIndex(index, exportDirectory, expectations, errors); +} + +for (const error of errors) console.error(`Conformance consumer error: ${error}`); +if (errors.length) process.exit(1); + +console.log(`Conformance consumer smoke passed: ${exportDirectory}`); + +function validateIndex(index, directory, expectations, errors) { + if (index.schema !== "gamecult.eve.conformance_export.v1") { + errors.push(`schema:expected gamecult.eve.conformance_export.v1 got ${index.schema || ""}`); + } + + const packs = Array.isArray(index.packs) ? index.packs : []; + const packIds = new Set(packs.map(pack => pack.id)); + const fixtureIds = new Set(); + const exportedRuntimeTargets = []; + validateSchemaCatalog(index.schemaCatalog, directory, expectations.schemas, errors); + for (const requiredPack of ["core", "plugin", "provider", "runtime"]) { + if (!packIds.has(requiredPack)) errors.push(`pack:${requiredPack}:missing`); + } + for (const expectedPack of expectations.packs) { + if (!packIds.has(expectedPack)) errors.push(`pack:${expectedPack}:missing`); + } + + for (const pack of packs) { + const packPath = path.join(directory, "packs", `${pack.id}.json`); + if (!existsSync(packPath)) { + errors.push(`pack-file:${pack.id}:missing`); + continue; + } + + const packDocument = JSON.parse(readFileSyncUtf8(packPath)); + if (packDocument.id !== pack.id) { + errors.push(`pack-file:${pack.id}:id:expected ${pack.id} got ${packDocument.id || ""}`); + } + if ((packDocument.fixtures || []).length !== (pack.fixtures || []).length) { + errors.push(`pack-file:${pack.id}:fixture-count:expected ${(pack.fixtures || []).length} got ${(packDocument.fixtures || []).length}`); + } + + for (const fixture of packDocument.fixtures || []) { + if (fixture.fixtureId) fixtureIds.add(fixture.fixtureId); + if (!fixture.fixtureId) errors.push(`pack-file:${pack.id}:fixture:fixtureId:missing`); + if (!fixture.status) errors.push(`pack-file:${pack.id}:fixture:${fixture.fixtureId || "unknown"}:status:missing`); + if (!fixture.surface?.path) errors.push(`pack-file:${pack.id}:fixture:${fixture.fixtureId || "unknown"}:surface.path:missing`); + if (!fixture.metadataPath) errors.push(`pack-file:${pack.id}:fixture:${fixture.fixtureId || "unknown"}:metadataPath:missing`); + } + + if (pack.id === "runtime") { + const packRuntimeTargets = Array.isArray(packDocument.runtimeTargets) ? packDocument.runtimeTargets : []; + if (!packRuntimeTargets.length) errors.push("pack-file:runtime:runtimeTargets:missing"); + for (const runtime of packRuntimeTargets) { + exportedRuntimeTargets.push(runtime); + if (!runtime.runtimeId) errors.push("pack-file:runtime:runtimeTarget:runtimeId:missing"); + if (!runtime.status) errors.push(`pack-file:runtime:runtimeTarget:${runtime.runtimeId || "unknown"}:status:missing`); + if (!runtime.ownerRepo) errors.push(`pack-file:runtime:runtimeTarget:${runtime.runtimeId || "unknown"}:ownerRepo:missing`); + } + } + } + + const plugins = Array.isArray(index.plugins) ? index.plugins : []; + const pluginWitnesses = Array.isArray(index.pluginWitnesses) ? index.pluginWitnesses : []; + const providers = Array.isArray(index.providers) ? index.providers : []; + const runtimes = mergeRuntimeRecords(Array.isArray(index.runtimes) ? index.runtimes : [], exportedRuntimeTargets); + const splitTargets = Array.isArray(index.splitTargets) ? index.splitTargets : []; + const pluginAbiOperationCoverage = Array.isArray(index.pluginAbiOperationCoverage) ? index.pluginAbiOperationCoverage : []; + const providerPluginRequirementCoverage = Array.isArray(index.providerPluginRequirementCoverage) ? index.providerPluginRequirementCoverage : []; + const capabilityGaps = Array.isArray(index.capabilityGaps) ? index.capabilityGaps : []; + const runtimePluginProjectionCoverage = Array.isArray(index.runtimePluginProjectionCoverage) ? index.runtimePluginProjectionCoverage : []; + const providerRuntimePluginProjectionCoverage = Array.isArray(index.providerRuntimePluginProjectionCoverage) ? index.providerRuntimePluginProjectionCoverage : []; + const runtimePluginProjectionGaps = Array.isArray(index.runtimePluginProjectionGaps) ? index.runtimePluginProjectionGaps : []; + const localProviderCatalogs = Array.isArray(index.localProviderCatalogs) ? index.localProviderCatalogs : []; + 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 screenshotComparisonMetrics = Array.isArray(index.screenshotComparisonMetrics) ? index.screenshotComparisonMetrics : []; + const runtimeCaptureProbeCoverage = Array.isArray(index.runtimeCaptureProbeCoverage) ? index.runtimeCaptureProbeCoverage : []; + const runtimeWitnesses = Array.isArray(index.runtimeWitnesses) ? index.runtimeWitnesses : []; + 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 providerHandoffMoveCoverage = Array.isArray(index.providerHandoffMoveCoverage) ? index.providerHandoffMoveCoverage : []; + const worldSurfaceLoweringGaps = Array.isArray(index.worldSurfaceLoweringGaps) ? index.worldSurfaceLoweringGaps : []; + + validatePluginRecords(plugins, errors); + validatePluginWitnesses(pluginWitnesses, directory, errors); + validatePluginAbiOperationCoverage(index.pluginAbiOperationCoverage, errors); + validateProviderRecords(providers, errors); + validateProviderPluginRequirementCoverage(index.providerPluginRequirementCoverage, errors); + validateRuntimeRecords(runtimes, errors); + validateSplitTargetRecords(splitTargets, errors); + validateInteractiveWorldSurfaces(index.interactiveWorldSurfaces, errors); + + if (expectations.conformanceHandoff && !index.conformanceHandoffPath) { + errors.push("conformanceHandoffPath:missing"); + } + if (expectations.conformanceHandoff) { + validateExportedHandoff(index.conformanceHandoffExportPath, directory, "conformanceHandoff", errors); + } + if (expectations.capabilityMatrix) { + validateCapabilityMatrix(index.capabilityMatrix, packs, plugins, providers, runtimes, splitTargets, errors); + } + validateCapabilityGaps(index.capabilityGaps, errors); + validateRuntimePluginProjectionCoverage(index.runtimePluginProjectionCoverage, errors); + validateProviderRuntimePluginProjectionCoverage(index.providerRuntimePluginProjectionCoverage, errors); + validateRuntimePluginProjectionGaps(index.runtimePluginProjectionGaps, errors); + validateLocalProviderCatalogs(index.localProviderCatalogs, directory, errors); + validateSplitTargetBlockers(index.splitTargetBlockers, errors); + validateSplitHandoffMoveCoverage(index.splitHandoffMoveCoverage, errors); + validatePluginHandoffMoveCoverage(index.pluginHandoffMoveCoverage, errors); + validateProviderHandoffMoveCoverage(index.providerHandoffMoveCoverage, errors); + validateWorldSurfaceLoweringCoverage(index.worldSurfaceLoweringCoverage, errors); + validateCommandBoundaryCoverage(index.commandBoundaryCoverage, errors); + validateScreenshotComparisonMetrics(index.screenshotComparisonMetrics, errors); + validateRuntimeCaptureProbeCoverage(index.runtimeCaptureProbeCoverage, errors); + validateRuntimeWitnesses(runtimeWitnesses, directory, errors); + validateWorldSurfaceLoweringGaps(index.worldSurfaceLoweringGaps, errors); + for (const expectedGap of expectations.capabilityGaps) { + if (!capabilityGaps.some(gap => capabilityGapText(gap).includes(expectedGap))) { + errors.push(`capabilityGaps:${expectedGap}:missing`); + } + } + for (const expectation of expectations.runtimePluginProjectionCoverage) { + const record = runtimePluginProjectionCoverage.find(candidate => + candidate.runtimeId === expectation.runtimeId && + candidate.pluginId === expectation.pluginId); + if (!record) { + errors.push(`runtimePluginProjectionCoverage:${expectation.runtimeId}:${expectation.pluginId}:missing`); + continue; + } + if (record.status !== expectation.status) { + errors.push(`runtimePluginProjectionCoverage:${expectation.runtimeId}:${expectation.pluginId}:status:expected ${expectation.status} got ${record.status || ""}`); + } + if (record.runtimeOwnerRepo !== expectation.ownerRepo) { + errors.push(`runtimePluginProjectionCoverage:${expectation.runtimeId}:${expectation.pluginId}:runtimeOwnerRepo:expected ${expectation.ownerRepo} got ${record.runtimeOwnerRepo || ""}`); + } + } + for (const expectation of expectations.runtimeWitnesses) { + const witness = runtimeWitnesses.find(candidate => + candidate.runtimeId === expectation.runtimeId && + candidate.providerId === expectation.providerId && + candidate.surfaceId === expectation.surfaceId && + candidate.cacheState === expectation.cacheState); + if (!witness) { + errors.push(`runtimeWitnesses:${expectation.runtimeId}:${expectation.providerId}:${expectation.surfaceId}:${expectation.cacheState}:missing`); + continue; + } + if (witness.status !== expectation.status) { + errors.push(`runtimeWitnesses:${witness.witnessId}:status:expected ${expectation.status} got ${witness.status || ""}`); + } + } + for (const expectation of expectations.pluginWitnesses) { + const witness = pluginWitnesses.find(candidate => + candidate.pluginId === expectation.pluginId && candidate.transport === expectation.transport); + if (!witness) { + errors.push(`pluginWitnesses:${expectation.pluginId}:${expectation.transport}:missing`); + continue; + } + if (witness.status !== expectation.status) errors.push(`pluginWitnesses:${expectation.pluginId}:status:expected ${expectation.status} got ${witness.status || ""}`); + } + for (const expectation of expectations.providerRuntimePluginProjectionCoverage) { + const record = providerRuntimePluginProjectionCoverage.find(candidate => + candidate.providerId === expectation.providerId && + candidate.surfaceId === expectation.surfaceId && + candidate.runtimeId === expectation.runtimeId && + candidate.pluginId === expectation.pluginId); + if (!record) { + errors.push(`providerRuntimePluginProjectionCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.runtimeId}:${expectation.pluginId}:missing`); + continue; + } + if (record.status !== expectation.status) { + errors.push(`providerRuntimePluginProjectionCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.runtimeId}:${expectation.pluginId}:status:expected ${expectation.status} got ${record.status || ""}`); + } + if (record.runtimeOwnerRepo !== expectation.runtimeOwnerRepo) { + errors.push(`providerRuntimePluginProjectionCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.runtimeId}:${expectation.pluginId}:runtimeOwnerRepo:expected ${expectation.runtimeOwnerRepo} got ${record.runtimeOwnerRepo || ""}`); + } + } + for (const expectation of expectations.runtimePluginGaps) { + const gap = runtimePluginProjectionGaps.find(candidate => + candidate.runtimeId === expectation.runtimeId && + candidate.pluginId === expectation.pluginId); + if (!gap) { + errors.push(`runtimePluginProjectionGaps:${expectation.runtimeId}:${expectation.pluginId}:missing`); + continue; + } + if (gap.ownerRepo !== expectation.ownerRepo) { + errors.push(`runtimePluginProjectionGaps:${expectation.runtimeId}:${expectation.pluginId}:ownerRepo:expected ${expectation.ownerRepo} got ${gap.ownerRepo || ""}`); + } + if (!gap.reason) { + errors.push(`runtimePluginProjectionGaps:${expectation.runtimeId}:${expectation.pluginId}:reason:missing`); + } + } + for (const expectation of expectations.localProviderCatalogs) { + const catalog = localProviderCatalogs.find(candidate => candidate.runtimeId === expectation.runtimeId); + if (!catalog) { + errors.push(`localProviderCatalogs:${expectation.runtimeId}:missing`); + continue; + } + if (catalog.status !== expectation.status) { + errors.push(`localProviderCatalogs:${expectation.runtimeId}:status:expected ${expectation.status} got ${catalog.status || ""}`); + } + if (!catalog.catalogPath?.includes(expectation.pathSubstring) && !catalog.catalogExportPath?.includes(expectation.pathSubstring)) { + errors.push(`localProviderCatalogs:${expectation.runtimeId}:path:expected substring ${expectation.pathSubstring}`); + } + } + for (const expectation of expectations.localProviderCatalogProviders) { + const catalog = localProviderCatalogs.find(candidate => candidate.runtimeId === expectation.runtimeId); + if (!catalog) { + errors.push(`localProviderCatalogs:${expectation.runtimeId}:missing`); + continue; + } + if (!Array.isArray(catalog.providerIds) || !catalog.providerIds.includes(expectation.providerId)) { + errors.push(`localProviderCatalogs:${expectation.runtimeId}:provider:${expectation.providerId}:missing`); + } + } + for (const expectation of expectations.localProviderCatalogAdvertisements) { + const catalog = localProviderCatalogs.find(candidate => candidate.runtimeId === expectation.runtimeId); + if (!catalog) { + errors.push(`localProviderCatalogs:${expectation.runtimeId}:missing`); + continue; + } + if (!Array.isArray(catalog.advertisementPaths) || !catalog.advertisementPaths.some(candidate => candidate.includes(expectation.pathSubstring))) { + errors.push(`localProviderCatalogs:${expectation.runtimeId}:advertisement:${expectation.pathSubstring}:missing`); + } + } + for (const expectation of expectations.worldLoweringCoverage) { + const coverage = worldSurfaceLoweringCoverage.find(candidate => + candidate.providerId === expectation.providerId && + candidate.surfaceId === expectation.surfaceId && + candidate.targetId === expectation.targetId); + if (!coverage) { + errors.push(`worldSurfaceLoweringCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.targetId}:missing`); + continue; + } + if (coverage.status !== expectation.status) { + errors.push(`worldSurfaceLoweringCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.targetId}:status:expected ${expectation.status} got ${coverage.status || ""}`); + } + if (coverage.runtimeOwnerRepo !== expectation.ownerRepo) { + errors.push(`worldSurfaceLoweringCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.targetId}:runtimeOwnerRepo:expected ${expectation.ownerRepo} got ${coverage.runtimeOwnerRepo || ""}`); + } + if (coverage.runtimeId !== expectation.runtimeId) { + 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 && + candidate.surfaceId === expectation.surfaceId && + candidate.targetId === expectation.targetId); + if (!gap) { + errors.push(`worldSurfaceLoweringGaps:${expectation.providerId}:${expectation.surfaceId}:${expectation.targetId}:missing`); + continue; + } + if (gap.ownerRepo !== expectation.ownerRepo) { + errors.push(`worldSurfaceLoweringGaps:${expectation.providerId}:${expectation.surfaceId}:${expectation.targetId}:ownerRepo:expected ${expectation.ownerRepo} got ${gap.ownerRepo || ""}`); + } + if (gap.runtimeId !== expectation.runtimeId) { + errors.push(`worldSurfaceLoweringGaps:${expectation.providerId}:${expectation.surfaceId}:${expectation.targetId}:runtimeId:expected ${expectation.runtimeId} got ${gap.runtimeId || ""}`); + } + } + for (const expectation of expectations.screenshotMetrics) { + const metric = screenshotComparisonMetrics.find(candidate => + candidate.runtimeId === expectation.runtimeId && + candidate.fixtureId === expectation.fixtureId && + candidate.metricKind === expectation.metricKind); + if (!metric) { + errors.push(`screenshotComparisonMetrics:${expectation.runtimeId}:${expectation.fixtureId}:${expectation.metricKind}:missing`); + continue; + } + if (metric.status !== expectation.status) { + 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`); + } + for (const expectedPlugin of expectations.plugins) { + if (!plugins.some(plugin => plugin.pluginId === expectedPlugin)) errors.push(`plugins:${expectedPlugin}:missing`); + } + for (const expectation of expectations.pluginOperations) { + const plugin = plugins.find(candidate => candidate.pluginId === expectation.pluginId); + if (!plugin) { + errors.push(`plugins:${expectation.pluginId}:missing`); + continue; + } + if (!Array.isArray(plugin.abiOperations) || !plugin.abiOperations.includes(expectation.operation)) { + errors.push(`plugins:${expectation.pluginId}:operation:${expectation.operation}:missing`); + } + } + for (const expectation of expectations.pluginCapabilities) { + const plugin = plugins.find(candidate => candidate.pluginId === expectation.pluginId); + if (!plugin) { + errors.push(`plugins:${expectation.pluginId}:missing`); + continue; + } + if (!Array.isArray(plugin.capabilities) || !plugin.capabilities.includes(expectation.capability)) { + errors.push(`plugins:${expectation.pluginId}:capability:${expectation.capability}:missing`); + } + } + for (const expectation of expectations.pluginRuntimes) { + const plugin = plugins.find(candidate => candidate.pluginId === expectation.pluginId); + if (!plugin) { + errors.push(`plugins:${expectation.pluginId}:missing`); + continue; + } + if (plugin.runtimeBoundary?.invocationModel !== expectation.invocationModel) { + errors.push(`plugins:${expectation.pluginId}:runtime:expected ${expectation.invocationModel} got ${plugin.runtimeBoundary?.invocationModel || ""}`); + } + } + for (const expectation of expectations.pluginRuntimeTransports) { + const plugin = plugins.find(candidate => candidate.pluginId === expectation.pluginId); + if (!plugin) { + errors.push(`plugins:${expectation.pluginId}:missing`); + continue; + } + if (!Array.isArray(plugin.runtimeBoundary?.transports) || !plugin.runtimeBoundary.transports.includes(expectation.transport)) { + errors.push(`plugins:${expectation.pluginId}:runtime.transport:${expectation.transport}:missing`); + } + } + for (const expectation of expectations.pluginRuntimeAuthorities) { + const plugin = plugins.find(candidate => candidate.pluginId === expectation.pluginId); + if (!plugin) { + errors.push(`plugins:${expectation.pluginId}:missing`); + continue; + } + if (!Array.isArray(plugin.runtimeBoundary?.authority) || !plugin.runtimeBoundary.authority.includes(expectation.authority)) { + errors.push(`plugins:${expectation.pluginId}:runtime.authority:${expectation.authority}:missing`); + } + } + for (const expectation of expectations.pluginRuntimeFields) { + const plugin = plugins.find(candidate => candidate.pluginId === expectation.pluginId); + if (!plugin) { + errors.push(`plugins:${expectation.pluginId}:missing`); + continue; + } + const actual = readNestedField(plugin.runtimeBoundary, expectation.fieldPath); + if (actual !== expectation.value) { + errors.push(`plugins:${expectation.pluginId}:runtime.${expectation.fieldPath}:expected ${expectation.value} got ${actual || ""}`); + } + } + for (const expectation of expectations.pluginAbiFields) { + const plugin = plugins.find(candidate => candidate.pluginId === expectation.pluginId); + if (!plugin) { + errors.push(`plugins:${expectation.pluginId}:missing`); + continue; + } + const contracts = Array.isArray(plugin.abiOperationContracts) ? plugin.abiOperationContracts : []; + const contract = contracts.find(candidate => candidate.operation === expectation.operation); + if (!contract) { + errors.push(`plugins:${expectation.pluginId}:abiOperation:${expectation.operation}:missing`); + continue; + } + const actual = readNestedField(contract, expectation.fieldPath); + if (actual !== expectation.value) { + errors.push(`plugins:${expectation.pluginId}:abiOperation:${expectation.operation}.${expectation.fieldPath}:expected ${expectation.value} got ${actual || ""}`); + } + } + for (const expectation of expectations.pluginAbiOperationCoverage) { + const record = pluginAbiOperationCoverage.find(candidate => + candidate.pluginId === expectation.pluginId && + candidate.operation === expectation.operation); + if (!record) { + errors.push(`pluginAbiOperationCoverage:${expectation.pluginId}:${expectation.operation}:missing`); + continue; + } + if (record.status !== expectation.status) { + errors.push(`pluginAbiOperationCoverage:${expectation.pluginId}:${expectation.operation}:status:expected ${expectation.status} got ${record.status || ""}`); + } + if (record.ownerRepo !== expectation.ownerRepo) { + errors.push(`pluginAbiOperationCoverage:${expectation.pluginId}:${expectation.operation}:ownerRepo:expected ${expectation.ownerRepo} got ${record.ownerRepo || ""}`); + } + } + for (const expectedPlugin of expectations.pluginHandoffs) { + const plugin = plugins.find(candidate => candidate.pluginId === expectedPlugin); + if (!plugin) { + errors.push(`plugins:${expectedPlugin}:missing`); + continue; + } + if (!plugin.handoffPath) { + errors.push(`plugins:${expectedPlugin}:handoffPath:missing`); + } + validateExportedHandoff(plugin.handoffExportPath, directory, `plugins:${expectedPlugin}:handoff`, errors); + } + for (const expectedProvider of expectations.providers) { + if (!providers.some(provider => provider.providerId === expectedProvider)) errors.push(`providers:${expectedProvider}:missing`); + } + for (const expectation of expectations.providerSurfaces) { + const provider = providers.find(candidate => candidate.providerId === expectation.providerId); + if (!provider) { + errors.push(`providers:${expectation.providerId}:missing`); + continue; + } + if (!Array.isArray(provider.surfaces) || !provider.surfaces.includes(expectation.surfaceId)) { + errors.push(`providers:${expectation.providerId}:surface:${expectation.surfaceId}:missing`); + } + } + for (const expectation of expectations.providerSurfaceKinds) { + const provider = providers.find(candidate => candidate.providerId === expectation.providerId); + if (!provider) { + errors.push(`providers:${expectation.providerId}:missing`); + continue; + } + const surfaceKinds = Array.isArray(provider.surfaceKinds) ? provider.surfaceKinds : []; + const surface = surfaceKinds.find(candidate => candidate.surfaceId === expectation.surfaceId); + if (!surface) { + errors.push(`providers:${expectation.providerId}:surfaceKind:${expectation.surfaceId}:missing`); + } else if (surface.surfaceKind !== expectation.surfaceKind) { + errors.push(`providers:${expectation.providerId}:surfaceKind:${expectation.surfaceId}:expected ${expectation.surfaceKind} got ${surface.surfaceKind || ""}`); + } + } + for (const expectation of expectations.providerSurfaceFields) { + const provider = providers.find(candidate => candidate.providerId === expectation.providerId); + if (!provider) { + errors.push(`providers:${expectation.providerId}:missing`); + continue; + } + const surfaceContracts = Array.isArray(provider.surfaceContracts) ? provider.surfaceContracts : []; + const surface = surfaceContracts.find(candidate => candidate.surfaceId === expectation.surfaceId); + if (!surface) { + errors.push(`providers:${expectation.providerId}:surfaceContract:${expectation.surfaceId}:missing`); + continue; + } + const actual = readNestedField(surface, expectation.fieldPath); + if (actual !== expectation.value) { + errors.push(`providers:${expectation.providerId}:surfaceContract:${expectation.surfaceId}.${expectation.fieldPath}:expected ${expectation.value} got ${actual || ""}`); + } + } + for (const expectation of expectations.interactiveWorldSurfaces) { + const surface = interactiveWorldSurfaces.find(candidate => + candidate.providerId === expectation.providerId && + candidate.surfaceId === expectation.surfaceId); + if (!surface) { + errors.push(`interactiveWorldSurfaces:${expectation.providerId}:${expectation.surfaceId}:missing`); + continue; + } + if (surface.ownerRepo !== expectation.ownerRepo) { + errors.push(`interactiveWorldSurfaces:${expectation.providerId}:${expectation.surfaceId}:ownerRepo:expected ${expectation.ownerRepo} got ${surface.ownerRepo || ""}`); + } + if (!Array.isArray(surface.loweringTargets) || !surface.loweringTargets.includes(expectation.targetId)) { + errors.push(`interactiveWorldSurfaces:${expectation.providerId}:${expectation.surfaceId}:target:${expectation.targetId}:missing`); + } + for (const field of ["projectionKind", "commandBoundary", "receiptSchema", "ownership"]) { + if (!surface[field]) errors.push(`interactiveWorldSurfaces:${expectation.providerId}:${expectation.surfaceId}:${field}:missing`); + } + } + for (const expectation of expectations.providerPluginRequirements) { + const requirement = providerPluginRequirementCoverage.find(candidate => + candidate.providerId === expectation.providerId && + candidate.surfaceId === expectation.surfaceId && + candidate.pluginId === expectation.pluginId); + if (!requirement) { + errors.push(`providerPluginRequirementCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.pluginId}:missing`); + continue; + } + if (requirement.status !== expectation.status) { + errors.push(`providerPluginRequirementCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.pluginId}:status:expected ${expectation.status} got ${requirement.status || ""}`); + } + if (requirement.pluginOwnerRepo !== expectation.pluginOwnerRepo) { + errors.push(`providerPluginRequirementCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.pluginId}:pluginOwnerRepo:expected ${expectation.pluginOwnerRepo} got ${requirement.pluginOwnerRepo || ""}`); + } + if (expectation.availability && requirement.availability !== expectation.availability) { + errors.push(`providerPluginRequirementCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.pluginId}:availability:expected ${expectation.availability} got ${requirement.availability || ""}`); + } + } + for (const expectation of expectations.independentNestedPlugins) { + const parentRequirement = providerPluginRequirementCoverage.find(candidate => + candidate.providerId === expectation.providerId && + candidate.surfaceId === expectation.surfaceId && + candidate.pluginId === expectation.parentPluginId); + const nestedRequirement = providerPluginRequirementCoverage.find(candidate => + candidate.providerId === expectation.providerId && + candidate.surfaceId === expectation.surfaceId && + candidate.pluginId === expectation.nestedPluginId); + if (!parentRequirement) { + errors.push(`independentNestedPlugin:${expectation.providerId}:${expectation.surfaceId}:${expectation.parentPluginId}:parent:missing`); + continue; + } + if (!nestedRequirement) { + errors.push(`independentNestedPlugin:${expectation.providerId}:${expectation.surfaceId}:${expectation.nestedPluginId}:nested:missing`); + continue; + } + if (parentRequirement.availability !== "required") { + errors.push(`independentNestedPlugin:${expectation.providerId}:${expectation.surfaceId}:${expectation.parentPluginId}:parent.availability:expected required got ${parentRequirement.availability || ""}`); + } + if (nestedRequirement.availability !== "optional-nested") { + errors.push(`independentNestedPlugin:${expectation.providerId}:${expectation.surfaceId}:${expectation.nestedPluginId}:nested.availability:expected optional-nested got ${nestedRequirement.availability || ""}`); + } + if (!["optional-satisfied", "optional-degraded"].includes(nestedRequirement.status)) { + errors.push(`independentNestedPlugin:${expectation.providerId}:${expectation.surfaceId}:${expectation.nestedPluginId}:nested.status:expected optional-satisfied or optional-degraded got ${nestedRequirement.status || ""}`); + } + if (nestedRequirement.pluginOwnerRepo !== expectation.nestedOwnerRepo) { + errors.push(`independentNestedPlugin:${expectation.providerId}:${expectation.surfaceId}:${expectation.nestedPluginId}:nested.pluginOwnerRepo:expected ${expectation.nestedOwnerRepo} got ${nestedRequirement.pluginOwnerRepo || ""}`); + } + if (nestedRequirement.pluginOwnerRepo === parentRequirement.pluginOwnerRepo) { + errors.push(`independentNestedPlugin:${expectation.providerId}:${expectation.surfaceId}:${expectation.nestedPluginId}:owner:expected owner distinct from parent ${parentRequirement.pluginOwnerRepo || ""}`); + } + if (Array.isArray(nestedRequirement.requiredCapabilities) && nestedRequirement.requiredCapabilities.length) { + errors.push(`independentNestedPlugin:${expectation.providerId}:${expectation.surfaceId}:${expectation.nestedPluginId}:requiredCapabilities:expected empty got ${nestedRequirement.requiredCapabilities.join(",")}`); + } + if (!Array.isArray(nestedRequirement.optionalCapabilities) || !nestedRequirement.optionalCapabilities.length) { + errors.push(`independentNestedPlugin:${expectation.providerId}:${expectation.surfaceId}:${expectation.nestedPluginId}:optionalCapabilities:missing`); + } + } + for (const expectation of expectations.providerCommands) { + const provider = providers.find(candidate => candidate.providerId === expectation.providerId); + if (!provider) { + errors.push(`providers:${expectation.providerId}:missing`); + continue; + } + if (!Array.isArray(provider.commands) || !provider.commands.includes(expectation.command)) { + errors.push(`providers:${expectation.providerId}:command:${expectation.command}:missing`); + } + } + for (const expectation of expectations.providerReceiptStates) { + const provider = providers.find(candidate => candidate.providerId === expectation.providerId); + if (!provider) { + errors.push(`providers:${expectation.providerId}:missing`); + continue; + } + if (!Array.isArray(provider.receiptStates) || !provider.receiptStates.includes(expectation.state)) { + errors.push(`providers:${expectation.providerId}:receiptState:${expectation.state}:missing`); + } + } + for (const expectedProvider of expectations.providerHandoffs) { + const provider = providers.find(candidate => candidate.providerId === expectedProvider); + if (!provider) { + errors.push(`providers:${expectedProvider}:missing`); + continue; + } + if (!provider.handoffPath) { + errors.push(`providers:${expectedProvider}:handoffPath:missing`); + } + validateExportedHandoff(provider.handoffExportPath, directory, `providers:${expectedProvider}:handoff`, errors); + } + for (const expectedRuntime of expectations.runtimes) { + if (!runtimes.some(runtime => runtime.runtimeId === expectedRuntime)) errors.push(`runtimes:${expectedRuntime}:missing`); + } + for (const expectation of expectations.runtimeStatuses) { + const runtime = runtimes.find(candidate => candidate.runtimeId === expectation.runtimeId); + if (!runtime) { + errors.push(`runtimes:${expectation.runtimeId}:missing`); + continue; + } + if (runtime.status !== expectation.status) { + errors.push(`runtimes:${expectation.runtimeId}:status:expected ${expectation.status} got ${runtime.status || ""}`); + } + } + for (const expectation of expectations.runtimeFeatures) { + const runtime = runtimes.find(candidate => candidate.runtimeId === expectation.runtimeId); + if (!runtime) { + errors.push(`runtimes:${expectation.runtimeId}:missing`); + continue; + } + if (!Array.isArray(runtime.supportedFeatures) || !runtime.supportedFeatures.includes(expectation.feature)) { + errors.push(`runtimes:${expectation.runtimeId}:feature:${expectation.feature}:missing`); + } + } + for (const expectation of expectations.runtimeWorldTargets) { + const runtime = runtimes.find(candidate => candidate.runtimeId === expectation.runtimeId); + if (!runtime) { + errors.push(`runtimes:${expectation.runtimeId}:missing`); + continue; + } + const worldTargets = Array.isArray(runtime.worldSurfaceLowering) ? runtime.worldSurfaceLowering : []; + if (!worldTargets.some(candidate => candidate.targetId === expectation.targetId)) { + errors.push(`runtimes:${expectation.runtimeId}:worldSurfaceLowering:${expectation.targetId}:missing`); + } + } + for (const expectation of expectations.runtimeWorldFields) { + const runtime = runtimes.find(candidate => candidate.runtimeId === expectation.runtimeId); + if (!runtime) { + errors.push(`runtimes:${expectation.runtimeId}:missing`); + continue; + } + const worldTargets = Array.isArray(runtime.worldSurfaceLowering) ? runtime.worldSurfaceLowering : []; + const target = worldTargets.find(candidate => candidate.targetId === expectation.targetId); + if (!target) { + errors.push(`runtimes:${expectation.runtimeId}:worldSurfaceLowering:${expectation.targetId}:missing`); + continue; + } + const actual = readNestedField(target, expectation.fieldPath); + if (actual !== expectation.value) { + errors.push(`runtimes:${expectation.runtimeId}:worldSurfaceLowering:${expectation.targetId}.${expectation.fieldPath}:expected ${expectation.value} got ${actual || ""}`); + } + } + for (const expectation of expectations.runtimeCommandSchemas) { + const runtime = runtimes.find(candidate => candidate.runtimeId === expectation.runtimeId); + if (!runtime) { + errors.push(`runtimes:${expectation.runtimeId}:missing`); + continue; + } + if (runtime.commandTransportSchema !== expectation.schema) { + errors.push(`runtimes:${expectation.runtimeId}:commandTransportSchema:expected ${expectation.schema} got ${runtime.commandTransportSchema || ""}`); + } + } + for (const expectation of expectations.runtimeCaptureStatuses) { + const runtime = runtimes.find(candidate => candidate.runtimeId === expectation.runtimeId); + if (!runtime) { + errors.push(`runtimes:${expectation.runtimeId}:missing`); + continue; + } + if (runtime.captureStatus !== expectation.status) { + errors.push(`runtimes:${expectation.runtimeId}:captureStatus:expected ${expectation.status} got ${runtime.captureStatus || ""}`); + } + } + for (const expectation of expectations.runtimeCaptureArtifacts) { + const runtime = runtimes.find(candidate => candidate.runtimeId === expectation.runtimeId); + if (!runtime) { + errors.push(`runtimes:${expectation.runtimeId}:missing`); + continue; + } + const artifacts = Array.isArray(runtime.captureArtifacts) ? runtime.captureArtifacts : []; + const artifact = artifacts.find(candidate => + candidate.kind === expectation.kind + && candidate.schema === expectation.schema + && candidate.providerId === expectation.providerId + && candidate.surfaceId === expectation.surfaceId); + if (!artifact) { + errors.push(`runtimes:${expectation.runtimeId}:captureArtifacts:${expectation.kind}:${expectation.schema}:${expectation.providerId}:${expectation.surfaceId}:missing`); + } + const artifactErrors = Array.isArray(runtime.captureArtifactErrors) ? runtime.captureArtifactErrors : []; + if (artifactErrors.length) { + errors.push(`runtimes:${expectation.runtimeId}:captureArtifactErrors:${artifactErrors.join(",")}`); + } + } + for (const expectation of expectations.runtimeLifecycleStatuses) { + const runtime = runtimes.find(candidate => candidate.runtimeId === expectation.runtimeId); + if (!runtime) { + errors.push(`runtimes:${expectation.runtimeId}:missing`); + continue; + } + const stage = runtime.lifecycle?.[expectation.stage]; + if (!stage) { + errors.push(`runtimes:${expectation.runtimeId}:lifecycle.${expectation.stage}:missing`); + } else if (stage.status !== expectation.status) { + errors.push(`runtimes:${expectation.runtimeId}:lifecycle.${expectation.stage}.status:expected ${expectation.status} got ${stage.status || ""}`); + } + } + for (const expectation of expectations.runtimeLifecyclePendingProofs) { + const runtime = runtimes.find(candidate => candidate.runtimeId === expectation.runtimeId); + if (!runtime) { + errors.push(`runtimes:${expectation.runtimeId}:missing`); + continue; + } + const stage = runtime.lifecycle?.[expectation.stage]; + const pendingProofs = Array.isArray(stage?.pendingProofs) ? stage.pendingProofs : []; + if (!pendingProofs.some(proof => proof.includes(expectation.pendingProof))) { + errors.push(`runtimes:${expectation.runtimeId}:lifecycle.${expectation.stage}.pendingProof:${expectation.pendingProof}:missing`); + } + } + for (const expectation of expectations.runtimeLifecycleFields) { + const runtime = runtimes.find(candidate => candidate.runtimeId === expectation.runtimeId); + if (!runtime) { + errors.push(`runtimes:${expectation.runtimeId}:missing`); + continue; + } + const actual = readNestedField(runtime.lifecycle?.[expectation.stage], expectation.fieldPath); + if (actual !== expectation.value) { + errors.push(`runtimes:${expectation.runtimeId}:lifecycle.${expectation.stage}.${expectation.fieldPath}:expected ${expectation.value} got ${actual || ""}`); + } + } + for (const expectedRuntime of expectations.runtimeHandoffs) { + const runtime = runtimes.find(candidate => candidate.runtimeId === expectedRuntime); + if (!runtime) { + errors.push(`runtimes:${expectedRuntime}:missing`); + continue; + } + if (!runtime.splitHandoffPath) { + errors.push(`runtimes:${expectedRuntime}:splitHandoffPath:missing`); + } + validateExportedHandoff(runtime.splitHandoffExportPath, directory, `runtimes:${expectedRuntime}:splitHandoff`, errors); + } + for (const expectedScenario of expectations.scenarios) { + if (!providers.some(provider => provider.scenarioId === expectedScenario)) errors.push(`providers:scenario:${expectedScenario}:missing`); + } + for (const expectedSplitTarget of expectations.splitTargets) { + if (!splitTargets.some(target => target.id === expectedSplitTarget)) errors.push(`splitTargets:${expectedSplitTarget}:missing`); + } + for (const expectation of expectations.splitTargetStatuses) { + const target = splitTargets.find(candidate => candidate.id === expectation.targetId); + if (!target) { + errors.push(`splitTargets:${expectation.targetId}:missing`); + continue; + } + if (target.status !== expectation.status) { + errors.push(`splitTargets:${expectation.targetId}:status:expected ${expectation.status} got ${target.status || ""}`); + } + } + for (const expectation of expectations.splitTargetBlockers) { + const target = splitTargets.find(candidate => candidate.id === expectation.targetId); + if (!target) { + errors.push(`splitTargets:${expectation.targetId}:missing`); + continue; + } + const blockers = Array.isArray(target.blockers) ? target.blockers : []; + if (!blockers.some(blocker => blocker.includes(expectation.blocker))) { + errors.push(`splitTargets:${expectation.targetId}:blocker:${expectation.blocker}:missing`); + } + } + for (const expectation of expectations.splitTargetBlockerRecords) { + const blocker = splitTargetBlockers.find(candidate => + candidate.targetId === expectation.targetId && + candidate.kind === expectation.kind && + candidate.subject === expectation.subject); + if (!blocker) { + 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.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.providerHandoffMoves) { + const record = providerHandoffMoveCoverage.find(candidate => + candidate.providerId === expectation.providerId && + candidate.moveSetId === expectation.moveSetId && + candidate.pathKind === expectation.pathKind && + candidate.status === expectation.status && + candidate.sourcePath?.includes(expectation.pathSubstring)); + if (!record) { + errors.push(`providerHandoffMoveCoverage:${expectation.providerId}:${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) { + errors.push(`splitTargets:${expectation.targetId}:missing`); + continue; + } + const proofs = Array.isArray(target.proofs) ? target.proofs : []; + const proof = proofs.find(candidate => candidate.description?.includes(expectation.proof)); + if (!proof) { + errors.push(`splitTargets:${expectation.targetId}:proof:${expectation.proof}:missing`); + } else if (proof.status !== "passed") { + errors.push(`splitTargets:${expectation.targetId}:proof:${expectation.proof}:expected passed got ${proof.status || ""}`); + } + } + + if (!plugins.some(plugin => plugin.pluginId === "sai.vn" && plugin.abiFixturePath)) { + errors.push("plugins:sai.vn:abiFixturePath:missing"); + } + if (!providers.some(provider => provider.providerId === "aetheria" && provider.scenarioPath)) { + errors.push("providers:aetheria:scenarioPath:missing"); + } + if (!runtimes.some(runtime => runtime.runtimeId === "unity-uitoolkit" && runtime.commandTransportSchema === "gamecult.eve.command_invocation.v1")) { + errors.push("runtimes:unity-uitoolkit:commandTransportSchema:missing"); + } + if (!splitTargets.some(target => target.id === "EveUnity")) { + errors.push("splitTargets:EveUnity:missing"); + } +} + +function readFileSyncUtf8(filePath) { + return readFileSync(filePath, "utf8"); +} + +function validateExportedHandoff(exportPath, directory, label, errors) { + if (!exportPath) { + errors.push(`${label}:exportPath:missing`); + return; + } + if (!existsSync(path.join(directory, exportPath))) { + errors.push(`${label}:exportPath:${exportPath}:missing`); + } +} + +function validateCapabilityMatrix(matrix, packs, plugins, providers, runtimes, splitTargets, errors) { + if (!matrix) { + errors.push("capabilityMatrix:missing"); + return; + } + if (matrix.schema !== "gamecult.eve.capability_matrix.v1") { + errors.push(`capabilityMatrix:schema:expected gamecult.eve.capability_matrix.v1 got ${matrix.schema || ""}`); + } + if ((matrix.packs || []).length !== packs.length) { + errors.push(`capabilityMatrix:packs:expected ${packs.length} got ${(matrix.packs || []).length}`); + } + if ((matrix.plugins || []).length !== plugins.length) { + errors.push(`capabilityMatrix:plugins:expected ${plugins.length} got ${(matrix.plugins || []).length}`); + } + if ((matrix.providers || []).length !== providers.length) { + errors.push(`capabilityMatrix:providers:expected ${providers.length} got ${(matrix.providers || []).length}`); + } + if ((matrix.runtimes || []).length !== runtimes.length) { + errors.push(`capabilityMatrix:runtimes:expected ${runtimes.length} got ${(matrix.runtimes || []).length}`); + } + if ((matrix.splitTargets || []).length !== splitTargets.length) { + errors.push(`capabilityMatrix:splitTargets:expected ${splitTargets.length} got ${(matrix.splitTargets || []).length}`); + } + for (const packId of ["core", "plugin", "provider", "runtime"]) { + if (!(matrix.packs || []).some(pack => pack.packId === packId)) { + errors.push(`capabilityMatrix:packs:${packId}:missing`); + } + } + for (const plugin of plugins) { + if (!(matrix.plugins || []).some(candidate => candidate.pluginId === plugin.pluginId)) { + errors.push(`capabilityMatrix:plugins:${plugin.pluginId}:missing`); + } + } + for (const provider of providers) { + if (!(matrix.providers || []).some(candidate => candidate.providerId === provider.providerId)) { + errors.push(`capabilityMatrix:providers:${provider.providerId}:missing`); + } + } + for (const runtime of runtimes) { + if (!(matrix.runtimes || []).some(candidate => candidate.runtimeId === runtime.runtimeId)) { + errors.push(`capabilityMatrix:runtimes:${runtime.runtimeId}:missing`); + } + } +} + +function validateCapabilityGaps(gaps, errors) { + if (!Array.isArray(gaps)) { + errors.push("capabilityGaps:missing"); + return; + } + for (const [index, gap] of gaps.entries()) { + for (const field of ["kind", "subjectId", "gap", "severity"]) { + if (!gap?.[field]) errors.push(`capabilityGaps:${index}:${field}:missing`); + } + } +} + +function validateRuntimePluginProjectionCoverage(records, errors) { + if (!Array.isArray(records)) { + errors.push("runtimePluginProjectionCoverage:missing"); + return; + } + for (const [index, record] of records.entries()) { + for (const field of ["runtimeId", "runtimeStatus", "runtimeOwnerRepo", "pluginId", "pluginOwnerRepo", "status", "severity"]) { + if (!record?.[field]) errors.push(`runtimePluginProjectionCoverage:${index}:${field}:missing`); + } + for (const field of ["capabilities", "requiredFixtures", "pluginFixtures"]) { + if (!Array.isArray(record?.[field])) errors.push(`runtimePluginProjectionCoverage:${index}:${field}:expected array`); + } + if (record?.status === "supported" && !record.capabilities.length) { + errors.push(`runtimePluginProjectionCoverage:${index}:capabilities:missing`); + } + if (record?.status === "unsupported" && !record.reason) { + errors.push(`runtimePluginProjectionCoverage:${index}:reason:missing`); + } + } +} + +function validateProviderRuntimePluginProjectionCoverage(records, errors) { + if (!Array.isArray(records)) { + errors.push("providerRuntimePluginProjectionCoverage:missing"); + return; + } + for (const [index, record] of records.entries()) { + for (const field of ["providerId", "providerOwnerRepo", "surfaceId", "fixtureId", "runtimeId", "runtimeStatus", "runtimeOwnerRepo", "pluginId", "availability", "status", "severity"]) { + if (!record?.[field]) errors.push(`providerRuntimePluginProjectionCoverage:${index}:${field}:missing`); + } + for (const field of ["requiredCapabilities", "optionalCapabilities", "runtimeCapabilities", "missingRuntimeRequiredCapabilities", "missingRuntimeOptionalCapabilities"]) { + if (!Array.isArray(record?.[field])) errors.push(`providerRuntimePluginProjectionCoverage:${index}:${field}:expected array`); + } + if (record?.severity === "blocker" && record.availability !== "required") { + errors.push(`providerRuntimePluginProjectionCoverage:${index}:severity:optional entries cannot be blocker`); + } + } +} + +function validateRuntimePluginProjectionGaps(gaps, errors) { + if (!Array.isArray(gaps)) { + errors.push("runtimePluginProjectionGaps:missing"); + return; + } + for (const [index, gap] of gaps.entries()) { + for (const field of ["runtimeId", "ownerRepo", "pluginId", "severity", "reason"]) { + if (!gap?.[field]) errors.push(`runtimePluginProjectionGaps:${index}:${field}:missing`); + } + for (const field of ["requiredFixtures", "pluginFixtures"]) { + if (!Array.isArray(gap?.[field])) errors.push(`runtimePluginProjectionGaps:${index}:${field}:expected array`); + } + } +} + +function validateSplitTargetBlockers(blockers, errors) { + if (!Array.isArray(blockers)) { + errors.push("splitTargetBlockers:missing"); + return; + } + for (const [index, blocker] of blockers.entries()) { + for (const field of ["targetId", "ownerRepo", "kind", "severity", "subject", "text"]) { + if (!blocker?.[field]) errors.push(`splitTargetBlockers:${index}:${field}:missing`); + } + } +} + +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 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 validateProviderHandoffMoveCoverage(records, errors) { + if (!Array.isArray(records)) { + errors.push("providerHandoffMoveCoverage:missing"); + return; + } + for (const [index, record] of records.entries()) { + for (const field of ["providerId", "providerOwnerRepo", "handoffPath", "handoffExportPath", "moveSetId", "destinationOwner", "replacementProof", "pathKind", "sourcePath", "status", "severity"]) { + if (!record?.[field]) errors.push(`providerHandoffMoveCoverage:${index}:${field}:missing`); + } + if (typeof record?.pathExists !== "boolean") { + errors.push(`providerHandoffMoveCoverage:${index}:pathExists:expected boolean`); + } + } +} + +function validateWorldSurfaceLoweringCoverage(records, errors) { + if (!Array.isArray(records)) { + errors.push("worldSurfaceLoweringCoverage:missing"); + return; + } + for (const [index, record] of records.entries()) { + for (const field of ["providerId", "providerOwnerRepo", "surfaceId", "targetId", "status", "severity", "runtimeId", "runtimeOwnerRepo"]) { + if (!record?.[field]) errors.push(`worldSurfaceLoweringCoverage:${index}:${field}:missing`); + } + } +} + +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_invocation.v1") { + errors.push(`commandBoundaryCoverage:${index}:runtimeCommandSchema:expected gamecult.eve.command_invocation.v1 got ${record.runtimeCommandSchema || ""}`); + } + } +} + +function validateScreenshotComparisonMetrics(records, errors) { + if (!Array.isArray(records)) { + errors.push("screenshotComparisonMetrics:missing"); + return; + } + if (!records.length) { + errors.push("screenshotComparisonMetrics:empty"); + return; + } + for (const [index, record] of records.entries()) { + for (const field of ["runtimeId", "runtimeOwnerRepo", "fixtureId", "metricKind", "status", "evidenceLayer", "detail"]) { + if (!record?.[field]) errors.push(`screenshotComparisonMetrics:${index}:${field}:missing`); + } + if (typeof record?.score !== "number") { + errors.push(`screenshotComparisonMetrics:${index}:score:expected number`); + } + if (!["structure", "color-tokens", "bounding-boxes", "text-presence"].includes(record?.metricKind)) { + errors.push(`screenshotComparisonMetrics:${index}:metricKind:unexpected ${record?.metricKind || ""}`); + } + } +} + +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"); + return; + } + for (const [index, gap] of gaps.entries()) { + for (const field of ["providerId", "surfaceId", "targetId", "ownerRepo", "runtimeId", "severity"]) { + if (!gap?.[field]) errors.push(`worldSurfaceLoweringGaps:${index}:${field}:missing`); + } + } +} + +function validatePluginRecords(plugins, errors) { + for (const plugin of plugins) { + const label = `plugins:${plugin.pluginId || "unknown"}`; + for (const field of ["pluginId", "status", "ownerRepo", "manifestPath", "advertisementPath", "abiFixturePath"]) { + if (!plugin?.[field]) errors.push(`${label}:${field}:missing`); + } + if (plugin.status !== "active" && !plugin.splitTarget) errors.push(`${label}:splitTarget:missing`); + for (const field of ["capabilities", "abiOperations", "optionalPlugins"]) { + if (!Array.isArray(plugin?.[field])) errors.push(`${label}:${field}:expected array`); + } + if (!Array.isArray(plugin?.abiOperationContracts)) { + errors.push(`${label}:abiOperationContracts:expected array`); + } + for (const contract of plugin?.abiOperationContracts || []) { + if (!contract.operation) errors.push(`${label}:abiOperationContracts:operation:missing`); + if (!contract.input || typeof contract.input !== "object" || Array.isArray(contract.input)) { + errors.push(`${label}:abiOperationContracts:${contract.operation || "unknown"}:input:expected object`); + } + if (!contract.expect || typeof contract.expect !== "object" || Array.isArray(contract.expect)) { + errors.push(`${label}:abiOperationContracts:${contract.operation || "unknown"}:expect:expected object`); + } + } + } +} + +function validatePluginWitnesses(witnesses, directory, errors) { + if (!Array.isArray(witnesses)) return; + for (const [index, witness] of witnesses.entries()) { + for (const field of ["witnessId", "pluginId", "ownerRepo", "status", "transport", "operationCount", "witnessExportPath"]) { + if (witness?.[field] === undefined || witness?.[field] === "") errors.push(`pluginWitnesses:${index}:${field}:missing`); + } + const witnessPath = witness?.witnessExportPath ? path.join(directory, witness.witnessExportPath) : ""; + if (!witnessPath || !existsSync(witnessPath)) { + errors.push(`pluginWitnesses:${index}:witnessExportPath:missing`); + continue; + } + const document = JSON.parse(readFileSyncUtf8(witnessPath)); + if (document.schema !== "gamecult.eve.plugin_witness.v1") errors.push(`pluginWitnesses:${index}:schema:unexpected`); + if ((document.operations || []).length !== witness.operationCount) errors.push(`pluginWitnesses:${index}:operationCount:mismatch`); + } +} + +function validatePluginAbiOperationCoverage(records, errors) { + if (!Array.isArray(records)) { + errors.push("pluginAbiOperationCoverage:missing"); + return; + } + for (const [index, record] of records.entries()) { + for (const field of ["pluginId", "ownerRepo", "status", "operation", "abiFixturePath"]) { + if (!record?.[field]) errors.push(`pluginAbiOperationCoverage:${index}:${field}:missing`); + } + for (const field of ["inputKeys", "expectKeys"]) { + if (!Array.isArray(record?.[field])) errors.push(`pluginAbiOperationCoverage:${index}:${field}:expected array`); + } + } +} + +function validateProviderRecords(providers, errors) { + for (const provider of providers) { + const label = `providers:${provider.providerId || "unknown"}`; + for (const field of ["providerId", "status", "ownerRepo", "advertisementPath"]) { + if (!provider?.[field]) errors.push(`${label}:${field}:missing`); + } + for (const field of ["surfaces", "surfaceKinds", "surfaceContracts", "commands", "pluginRequirements", "receiptStates"]) { + if (!Array.isArray(provider?.[field])) errors.push(`${label}:${field}:expected array`); + } + } +} + +function validateProviderPluginRequirementCoverage(records, errors) { + if (!Array.isArray(records)) { + errors.push("providerPluginRequirementCoverage:missing"); + return; + } + for (const [index, record] of records.entries()) { + for (const field of ["providerId", "providerOwnerRepo", "surfaceId", "pluginId", "pluginStatus", "availability", "status"]) { + if (!record?.[field]) errors.push(`providerPluginRequirementCoverage:${index}:${field}:missing`); + } + for (const field of ["requiredCapabilities", "optionalCapabilities", "missingRequiredCapabilities", "missingOptionalCapabilities"]) { + if (!Array.isArray(record?.[field])) errors.push(`providerPluginRequirementCoverage:${index}:${field}:expected array`); + } + } +} + +function validateLocalProviderCatalogs(catalogs, directory, errors) { + if (!Array.isArray(catalogs)) { + errors.push("localProviderCatalogs:missing"); + return; + } + for (const [index, catalog] of catalogs.entries()) { + for (const field of ["runtimeId", "ownerRepo", "catalogPath", "catalogExportPath", "schema", "purpose", "status"]) { + if (!catalog?.[field]) errors.push(`localProviderCatalogs:${index}:${field}:missing`); + } + for (const field of ["providerIds", "advertisementPaths", "errors"]) { + if (!Array.isArray(catalog?.[field])) errors.push(`localProviderCatalogs:${index}:${field}:expected array`); + } + if (typeof catalog?.surfaceCount !== "number") errors.push(`localProviderCatalogs:${index}:surfaceCount:expected number`); + if (catalog?.catalogExportPath && !existsSync(path.join(directory, catalog.catalogExportPath))) { + errors.push(`localProviderCatalogs:${index}:catalogExportPath:${catalog.catalogExportPath}:missing`); + } + } +} + +function validateInteractiveWorldSurfaces(surfaces, errors) { + if (!Array.isArray(surfaces)) { + errors.push("interactiveWorldSurfaces:missing"); + return; + } + for (const [index, surface] of surfaces.entries()) { + for (const field of ["providerId", "ownerRepo", "surfaceId", "surfaceKind", "interactionModel", "projectionKind", "commandBoundary", "receiptSchema", "ownership"]) { + if (!surface?.[field]) errors.push(`interactiveWorldSurfaces:${index}:${field}:missing`); + } + for (const field of ["stateSchemas", "loweringTargets"]) { + if (!Array.isArray(surface?.[field])) errors.push(`interactiveWorldSurfaces:${index}:${field}:expected array`); + } + } +} + +function validateRuntimeRecords(runtimes, errors) { + for (const runtime of runtimes) { + const label = `runtimes:${runtime.runtimeId || "unknown"}`; + for (const field of ["runtimeId", "status", "ownerRepo", "splitTarget", "captureStatus"]) { + if (!runtime?.[field]) errors.push(`${label}:${field}:missing`); + } + for (const field of ["supportedFeatures", "supportedPlugins", "unsupportedPlugins", "capabilityManifestErrors", "lifecycleErrors", "worldSurfaceLowering"]) { + if (!Array.isArray(runtime?.[field])) errors.push(`${label}:${field}:expected array`); + } + } +} + +function validateRuntimeWitnesses(witnesses, directory, errors) { + if (!Array.isArray(witnesses)) return; + for (const [index, witness] of witnesses.entries()) { + for (const field of ["witnessId", "runtimeId", "runtimeOwnerRepo", "providerId", "surfaceId", "projectionKind", "status", "cacheState", "witnessExportPath", "artifactCount"]) { + if (witness?.[field] === undefined || witness?.[field] === "") errors.push(`runtimeWitnesses:${index}:${field}:missing`); + } + const witnessPath = witness?.witnessExportPath ? path.join(directory, witness.witnessExportPath) : ""; + if (!witnessPath || !existsSync(witnessPath)) { + errors.push(`runtimeWitnesses:${index}:witnessExportPath:missing`); + continue; + } + const document = JSON.parse(readFileSyncUtf8(witnessPath)); + if (document.schema !== "gamecult.eve.runtime_witness.v1") errors.push(`runtimeWitnesses:${index}:schema:unexpected`); + if ((document.artifacts || []).length !== witness.artifactCount) errors.push(`runtimeWitnesses:${index}:artifactCount:mismatch`); + for (const artifact of document.artifacts || []) { + if (!artifact.exportPath || !existsSync(path.join(directory, artifact.exportPath))) { + errors.push(`runtimeWitnesses:${index}:artifact:${artifact.kind || "unknown"}:missing`); + } + } + } +} + +function validateSplitTargetRecords(splitTargets, errors) { + for (const target of splitTargets) { + const label = `splitTargets:${target.id || "unknown"}`; + for (const field of ["id", "ownerRepo", "status", "runtimeStatuses"]) { + if (!target?.[field]) errors.push(`${label}:${field}:missing`); + } + for (const field of ["runtimes", "requiredRuntimeStatuses", "requiredFeatures", "requiredPlugins", "proofs", "pendingProofs", "blockers", "blockerRecords"]) { + if (!Array.isArray(target?.[field])) errors.push(`${label}:${field}:expected array`); + } + } +} + +function validateSchemaCatalog(schemaCatalog, directory, expectedSchemas, errors) { + if (!Array.isArray(schemaCatalog)) { + errors.push("schemaCatalog:missing"); + return; + } + for (const [index, schema] of schemaCatalog.entries()) { + const label = `schemaCatalog:${index}`; + for (const field of ["schemaId", "path", "exportPath"]) { + if (!schema?.[field]) errors.push(`${label}:${field}:missing`); + } + if (schema?.exportPath && !existsSync(path.join(directory, schema.exportPath))) { + errors.push(`${label}:exportPath:${schema.exportPath}:missing`); + } + } + for (const schemaId of expectedSchemas) { + const schema = schemaCatalog.find(candidate => candidate.schemaId === schemaId); + if (!schema) { + errors.push(`schemaCatalog:${schemaId}:missing`); + continue; + } + if (!schema.exportPath || !existsSync(path.join(directory, schema.exportPath))) { + errors.push(`schemaCatalog:${schemaId}:exportPath:missing`); + } + } +} + +function capabilityGapText(gap) { + return [ + gap.kind, + gap.ownerRepo, + gap.subjectId, + gap.gap, + gap.severity, + gap.detail, + ].filter(Boolean).join(":"); +} + +function parseArguments(args) { + const exportPath = args[0] ? path.resolve(args[0]) : ""; + const expectations = { + packs: [], + schemas: [], + fixtures: [], + plugins: [], + pluginOperations: [], + pluginCapabilities: [], + pluginRuntimes: [], + pluginRuntimeTransports: [], + pluginRuntimeAuthorities: [], + pluginRuntimeFields: [], + pluginAbiFields: [], + pluginAbiOperationCoverage: [], + pluginHandoffs: [], + pluginWitnesses: [], + pluginHandoffMoves: [], + providers: [], + providerSurfaces: [], + providerSurfaceKinds: [], + providerSurfaceFields: [], + interactiveWorldSurfaces: [], + providerPluginRequirements: [], + independentNestedPlugins: [], + providerCommands: [], + providerReceiptStates: [], + providerHandoffs: [], + providerHandoffMoves: [], + runtimes: [], + runtimeStatuses: [], + runtimeFeatures: [], + runtimeWorldTargets: [], + runtimeWorldFields: [], + runtimeHandoffs: [], + runtimeCommandSchemas: [], + runtimeCaptureStatuses: [], + runtimeCaptureArtifacts: [], + runtimeWitnesses: [], + runtimeLifecycleStatuses: [], + runtimeLifecyclePendingProofs: [], + runtimeLifecycleFields: [], + scenarios: [], + splitTargets: [], + splitTargetStatuses: [], + splitTargetBlockers: [], + splitTargetBlockerRecords: [], + splitHandoffMoves: [], + splitTargetProofs: [], + capabilityMatrix: false, + capabilityGaps: [], + runtimePluginProjectionCoverage: [], + providerRuntimePluginProjectionCoverage: [], + runtimePluginGaps: [], + localProviderCatalogs: [], + localProviderCatalogProviders: [], + localProviderCatalogAdvertisements: [], + worldLoweringCoverage: [], + commandBoundaryCoverage: [], + worldLoweringGaps: [], + screenshotMetrics: [], + runtimeCaptureProbes: [], + conformanceHandoff: false, + }; + const optionTargets = new Map([ + ["--expect-pack", expectations.packs], + ["--expect-schema", expectations.schemas], + ["--expect-fixture", expectations.fixtures], + ["--expect-plugin", expectations.plugins], + ["--expect-provider", expectations.providers], + ["--expect-runtime", expectations.runtimes], + ["--expect-scenario", expectations.scenarios], + ["--expect-split-target", expectations.splitTargets], + ["--expect-plugin-operation", expectations.pluginOperations], + ["--expect-plugin-capability", expectations.pluginCapabilities], + ["--expect-plugin-runtime", expectations.pluginRuntimes], + ["--expect-plugin-runtime-transport", expectations.pluginRuntimeTransports], + ["--expect-plugin-runtime-authority", expectations.pluginRuntimeAuthorities], + ["--expect-plugin-runtime-field", expectations.pluginRuntimeFields], + ["--expect-plugin-abi-field", expectations.pluginAbiFields], + ["--expect-plugin-abi-operation-coverage", expectations.pluginAbiOperationCoverage], + ["--expect-plugin-handoff", expectations.pluginHandoffs], + ["--expect-plugin-witness", expectations.pluginWitnesses], + ["--expect-plugin-handoff-move", expectations.pluginHandoffMoves], + ["--expect-provider-surface", expectations.providerSurfaces], + ["--expect-provider-surface-kind", expectations.providerSurfaceKinds], + ["--expect-provider-surface-field", expectations.providerSurfaceFields], + ["--expect-interactive-world-surface", expectations.interactiveWorldSurfaces], + ["--expect-provider-plugin-requirement", expectations.providerPluginRequirements], + ["--expect-independent-nested-plugin", expectations.independentNestedPlugins], + ["--expect-provider-command", expectations.providerCommands], + ["--expect-provider-receipt-state", expectations.providerReceiptStates], + ["--expect-provider-handoff", expectations.providerHandoffs], + ["--expect-provider-handoff-move", expectations.providerHandoffMoves], + ["--expect-runtime-status", expectations.runtimeStatuses], + ["--expect-runtime-feature", expectations.runtimeFeatures], + ["--expect-runtime-world-target", expectations.runtimeWorldTargets], + ["--expect-runtime-world-field", expectations.runtimeWorldFields], + ["--expect-runtime-handoff", expectations.runtimeHandoffs], + ["--expect-runtime-command-schema", expectations.runtimeCommandSchemas], + ["--expect-runtime-capture-status", expectations.runtimeCaptureStatuses], + ["--expect-runtime-capture-artifact", expectations.runtimeCaptureArtifacts], + ["--expect-runtime-witness", expectations.runtimeWitnesses], + ["--expect-runtime-lifecycle-status", expectations.runtimeLifecycleStatuses], + ["--expect-runtime-lifecycle-pending", expectations.runtimeLifecyclePendingProofs], + ["--expect-runtime-lifecycle-field", expectations.runtimeLifecycleFields], + ["--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-projection", expectations.runtimePluginProjectionCoverage], + ["--expect-provider-runtime-plugin-projection", expectations.providerRuntimePluginProjectionCoverage], + ["--expect-runtime-plugin-gap", expectations.runtimePluginGaps], + ["--expect-local-provider-catalog", expectations.localProviderCatalogs], + ["--expect-local-provider-catalog-provider", expectations.localProviderCatalogProviders], + ["--expect-local-provider-catalog-advertisement", expectations.localProviderCatalogAdvertisements], + ["--expect-world-lowering-coverage", expectations.worldLoweringCoverage], + ["--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) { + const option = args[index]; + if (option === "--expect-capability-matrix") { + expectations.capabilityMatrix = true; + continue; + } + if (option === "--expect-conformance-handoff") { + expectations.conformanceHandoff = true; + continue; + } + const target = optionTargets.get(option); + if (!target) { + console.error(`Unknown option: ${option}`); + process.exit(2); + } + const value = args[index + 1]; + if (!value || value.startsWith("--")) { + console.error(`Missing value for option: ${option}`); + process.exit(2); + } + if (option === "--expect-plugin-operation") { + target.push(parsePluginExpectation(value, "operation")); + } else if (option === "--expect-plugin-capability") { + target.push(parsePluginExpectation(value, "capability")); + } else if (option === "--expect-plugin-runtime") { + target.push(parsePluginExpectation(value, "invocationModel")); + } else if (option === "--expect-plugin-runtime-transport") { + target.push(parsePluginExpectation(value, "transport")); + } else if (option === "--expect-plugin-runtime-authority") { + target.push(parsePluginExpectation(value, "authority")); + } else if (option === "--expect-plugin-runtime-field") { + target.push(parsePluginRuntimeFieldExpectation(value)); + } else if (option === "--expect-plugin-abi-field") { + target.push(parsePluginAbiFieldExpectation(value)); + } else if (option === "--expect-plugin-abi-operation-coverage") { + target.push(parsePluginAbiOperationCoverageExpectation(value)); + } else if (option === "--expect-plugin-handoff") { + target.push(value); + } else if (option === "--expect-plugin-witness") { + target.push(parsePluginWitnessExpectation(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") { + target.push(parseProviderSurfaceKindExpectation(value)); + } else if (option === "--expect-provider-surface-field") { + target.push(parseProviderSurfaceFieldExpectation(value)); + } else if (option === "--expect-interactive-world-surface") { + target.push(parseInteractiveWorldSurfaceExpectation(value)); + } else if (option === "--expect-provider-plugin-requirement") { + target.push(parseProviderPluginRequirementExpectation(value)); + } else if (option === "--expect-independent-nested-plugin") { + target.push(parseIndependentNestedPluginExpectation(value)); + } else if (option === "--expect-provider-command") { + target.push(parseProviderExpectation(value, "command")); + } else if (option === "--expect-provider-receipt-state") { + target.push(parseProviderExpectation(value, "state")); + } else if (option === "--expect-provider-handoff") { + target.push(value); + } else if (option === "--expect-provider-handoff-move") { + target.push(parseProviderHandoffMoveExpectation(value)); + } else if (option === "--expect-runtime-status") { + target.push(parseRuntimeExpectation(value, "status")); + } else if (option === "--expect-runtime-feature") { + target.push(parseRuntimeExpectation(value, "feature")); + } else if (option === "--expect-runtime-world-target") { + target.push(parseRuntimeWorldTargetExpectation(value)); + } else if (option === "--expect-runtime-world-field") { + target.push(parseRuntimeWorldFieldExpectation(value)); + } else if (option === "--expect-runtime-handoff") { + target.push(value); + } else if (option === "--expect-runtime-command-schema") { + target.push(parseRuntimeExpectation(value, "schema")); + } else if (option === "--expect-runtime-capture-status") { + target.push(parseRuntimeExpectation(value, "status")); + } else if (option === "--expect-runtime-capture-artifact") { + target.push(parseRuntimeCaptureArtifactExpectation(value)); + } else if (option === "--expect-runtime-witness") { + target.push(parseRuntimeWitnessExpectation(value)); + } else if (option === "--expect-runtime-lifecycle-status") { + target.push(parseRuntimeLifecycleExpectation(value, "status")); + } else if (option === "--expect-runtime-lifecycle-pending") { + target.push(parseRuntimeLifecycleExpectation(value, "pendingProof")); + } else if (option === "--expect-runtime-lifecycle-field") { + target.push(parseRuntimeLifecycleFieldExpectation(value)); + } else if (option === "--expect-split-target-status") { + target.push(parseSplitTargetExpectation(value, "status")); + } else if (option === "--expect-split-target-blocker") { + 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") { + 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-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") { + target.push(parseRuntimePluginProjectionCoverageExpectation(value)); + } else if (option === "--expect-provider-runtime-plugin-projection") { + target.push(parseProviderRuntimePluginProjectionCoverageExpectation(value)); + } else if (option === "--expect-local-provider-catalog") { + target.push(parseLocalProviderCatalogExpectation(value)); + } else if (option === "--expect-local-provider-catalog-provider") { + target.push(parseLocalProviderCatalogProviderExpectation(value)); + } else if (option === "--expect-local-provider-catalog-advertisement") { + target.push(parseLocalProviderCatalogAdvertisementExpectation(value)); + } else { + target.push(value); + } + index += 1; + } + + return { exportDirectory: exportPath, expectations }; +} + +function parseScreenshotMetricExpectation(value) { + const parts = value.split(":"); + if (parts.length !== 4 || parts.some(part => !part)) { + console.error(`Expected screenshot metric in form, got: ${value}`); + process.exit(2); + } + return { + runtimeId: parts[0], + fixtureId: parts[1], + metricKind: parts[2], + status: parts[3], + }; +} + +function parseRuntimeCaptureProbeExpectation(value) { + const parts = value.split(":"); + if (parts.length !== 4 || parts.some(part => !part)) { + console.error(`Expected runtime capture probe in form, got: ${value}`); + process.exit(2); + } + return { + runtimeId: parts[0], + status: parts[1], + contractArtifactKind: parts[2], + captureOwnerRepo: parts[3], + }; +} + +function parseIndependentNestedPluginExpectation(value) { + const parts = value.split(":"); + if (parts.length !== 5 || parts.some(part => !part)) { + console.error(`Expected independent nested plugin in form, got: ${value}`); + process.exit(2); + } + return { + providerId: parts[0], + surfaceId: parts[1], + parentPluginId: parts[2], + nestedPluginId: parts[3], + nestedOwnerRepo: parts[4], + }; +} + +function parseLocalProviderCatalogExpectation(value) { + const parts = value.split(":"); + if (parts.length !== 3 || parts.some(part => !part)) { + console.error(`Expected local provider catalog in form, got: ${value}`); + process.exit(2); + } + return { + runtimeId: parts[0], + status: parts[1], + pathSubstring: parts[2], + }; +} + +function parseLocalProviderCatalogProviderExpectation(value) { + const parts = value.split(":"); + if (parts.length !== 2 || parts.some(part => !part)) { + console.error(`Expected local provider catalog provider in form, got: ${value}`); + process.exit(2); + } + return { + runtimeId: parts[0], + providerId: parts[1], + }; +} + +function parseLocalProviderCatalogAdvertisementExpectation(value) { + const parts = value.split(":"); + if (parts.length !== 2 || parts.some(part => !part)) { + console.error(`Expected local provider catalog advertisement in form, got: ${value}`); + process.exit(2); + } + return { + runtimeId: parts[0], + pathSubstring: parts[1], + }; +} + +function parseRuntimeCaptureArtifactExpectation(value) { + const parts = value.split(":"); + if (parts.length !== 5 || parts.some(part => !part)) { + console.error(`Expected runtime capture artifact in form, got: ${value}`); + process.exit(2); + } + return { + runtimeId: parts[0], + kind: parts[1], + schema: parts[2], + providerId: parts[3], + surfaceId: parts[4], + }; +} + +function parseWorldLoweringGapExpectation(value) { + const parts = value.split(":"); + if (parts.length !== 5 || parts.some(part => !part)) { + console.error(`Expected world lowering gap in form, got: ${value}`); + process.exit(2); + } + return { + providerId: parts[0], + surfaceId: parts[1], + targetId: parts[2], + ownerRepo: parts[3], + runtimeId: parts[4], + }; +} + +function parseWorldLoweringCoverageExpectation(value) { + const parts = value.split(":"); + if (parts.length !== 6 || parts.some(part => !part)) { + console.error(`Expected world lowering 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 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)) { + console.error(`Expected runtime plugin projection gap in form, got: ${value}`); + process.exit(2); + } + return { + runtimeId: parts[0], + pluginId: parts[1], + ownerRepo: parts[2], + }; +} + +function parseRuntimePluginProjectionCoverageExpectation(value) { + const parts = value.split(":"); + if (parts.length !== 4 || parts.some(part => !part)) { + console.error(`Expected runtime plugin projection coverage in form, got: ${value}`); + process.exit(2); + } + return { + runtimeId: parts[0], + pluginId: parts[1], + status: parts[2], + ownerRepo: parts[3], + }; +} + +function parseProviderRuntimePluginProjectionCoverageExpectation(value) { + const parts = value.split(":"); + if (parts.length !== 6 || parts.some(part => !part)) { + console.error(`Expected provider runtime plugin projection coverage in form, got: ${value}`); + process.exit(2); + } + return { + providerId: parts[0], + surfaceId: parts[1], + runtimeId: parts[2], + pluginId: parts[3], + status: parts[4], + runtimeOwnerRepo: parts[5], + }; +} + +function parseSplitTargetBlockerRecordExpectation(value) { + const parts = value.split(":"); + if (parts.length < 3 || !parts[0] || !parts[1] || !parts.slice(2).join(":")) { + console.error(`Expected split target blocker record in form, got: ${value}`); + process.exit(2); + } + return { + targetId: parts[0], + kind: parts[1], + subject: parts.slice(2).join(":"), + }; +} + +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 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 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 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 parseProviderHandoffMoveExpectation(value) { + const parts = value.split(":"); + if (parts.length < 5 || parts.slice(0, 4).some(part => !part) || !parts.slice(4).join(":")) { + console.error(`Expected provider handoff move in form, got: ${value}`); + process.exit(2); + } + return { + providerId: 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) { + console.error(`Expected plugin ${field} in form, got: ${value}`); + process.exit(2); + } + return { + pluginId: value.slice(0, separator), + [field]: value.slice(separator + 1), + }; +} + +function parsePluginRuntimeFieldExpectation(value) { + const parts = value.split(":"); + if (parts.length < 3 || !parts[0] || !parts[1] || !parts.slice(2).join(":")) { + console.error(`Expected plugin runtime field in form, got: ${value}`); + process.exit(2); + } + return { + pluginId: parts[0], + fieldPath: parts[1], + value: parts.slice(2).join(":"), + }; +} + +function parsePluginAbiFieldExpectation(value) { + const parts = value.split(":"); + if (parts.length < 4 || !parts[0] || !parts[1] || !parts[2] || !parts.slice(3).join(":")) { + console.error(`Expected plugin ABI field in form, got: ${value}`); + process.exit(2); + } + return { + pluginId: parts[0], + operation: parts[1], + fieldPath: parts[2], + value: parts.slice(3).join(":"), + }; +} + +function parsePluginAbiOperationCoverageExpectation(value) { + const parts = value.split(":"); + if (parts.length !== 4 || parts.some(part => !part)) { + console.error(`Expected plugin ABI operation coverage in form, got: ${value}`); + process.exit(2); + } + return { + pluginId: parts[0], + operation: parts[1], + status: parts[2], + ownerRepo: parts[3], + }; +} + +function parseProviderSurfaceFieldExpectation(value) { + const parts = value.split(":"); + if (parts.length < 4 || !parts[0] || !parts[1] || !parts[2] || !parts.slice(3).join(":")) { + console.error(`Expected provider surface field in form, got: ${value}`); + process.exit(2); + } + return { + providerId: parts[0], + surfaceId: parts[1], + fieldPath: parts[2], + value: parts.slice(3).join(":"), + }; +} + +function parseInteractiveWorldSurfaceExpectation(value) { + const parts = value.split(":"); + if (parts.length !== 4 || parts.some(part => !part)) { + console.error(`Expected interactive world surface in form, got: ${value}`); + process.exit(2); + } + return { + providerId: parts[0], + surfaceId: parts[1], + targetId: parts[2], + ownerRepo: parts[3], + }; +} + +function parseProviderPluginRequirementExpectation(value) { + const parts = value.split(":"); + if (![5, 6].includes(parts.length) || parts.some(part => !part)) { + console.error(`Expected provider plugin requirement in form, got: ${value}`); + process.exit(2); + } + return { + providerId: parts[0], + surfaceId: parts[1], + pluginId: parts[2], + status: parts[3], + pluginOwnerRepo: parts[4], + availability: parts[5] || "", + }; +} + +function parseProviderExpectation(value, field) { + const separator = value.indexOf(":"); + if (separator <= 0 || separator === value.length - 1) { + console.error(`Expected provider ${field} in form, got: ${value}`); + process.exit(2); + } + return { + providerId: value.slice(0, separator), + [field]: value.slice(separator + 1), + }; +} + +function parsePluginWitnessExpectation(value) { + const parts = value.split(":"); + if (parts.length !== 3 || parts.some(part => !part)) { + console.error(`Expected plugin witness in form, got: ${value}`); + process.exit(2); + } + return { pluginId: parts[0], transport: parts[1], status: parts[2] }; +} + +function parseProviderSurfaceKindExpectation(value) { + const parts = value.split(":"); + if (parts.length !== 3 || parts.some(part => !part)) { + console.error(`Expected provider surface kind in form, got: ${value}`); + process.exit(2); + } + return { + providerId: parts[0], + surfaceId: parts[1], + surfaceKind: parts[2], + }; +} + +function parseRuntimeExpectation(value, field) { + const separator = value.indexOf(":"); + if (separator <= 0 || separator === value.length - 1) { + console.error(`Expected runtime ${field} in form, got: ${value}`); + process.exit(2); + } + return { + runtimeId: value.slice(0, separator), + [field]: value.slice(separator + 1), + }; +} + +function parseRuntimeWitnessExpectation(value) { + const parts = value.split(":"); + if (parts.length !== 5 || parts.some(part => !part)) { + console.error(`Expected runtime witness in form, got: ${value}`); + process.exit(2); + } + return { + runtimeId: parts[0], + providerId: parts[1], + surfaceId: parts[2], + cacheState: parts[3], + status: parts[4], + }; +} + +function parseRuntimeWorldTargetExpectation(value) { + const separator = value.indexOf(":"); + if (separator <= 0 || separator === value.length - 1) { + console.error(`Expected runtime world target in form, got: ${value}`); + process.exit(2); + } + return { + runtimeId: value.slice(0, separator), + targetId: value.slice(separator + 1), + }; +} + +function parseRuntimeWorldFieldExpectation(value) { + const parts = value.split(":"); + if (parts.length < 4 || !parts[0] || !parts[1] || !parts[2] || !parts.slice(3).join(":")) { + console.error(`Expected runtime world field in form, got: ${value}`); + process.exit(2); + } + return { + runtimeId: parts[0], + targetId: parts[1], + fieldPath: parts[2], + value: parts.slice(3).join(":"), + }; +} + +function parseRuntimeLifecycleFieldExpectation(value) { + const parts = value.split(":"); + if (parts.length < 4 || !parts[0] || !parts[1] || !parts[2] || !parts.slice(3).join(":")) { + console.error(`Expected runtime lifecycle field in form, got: ${value}`); + process.exit(2); + } + return { + runtimeId: parts[0], + stage: parts[1], + fieldPath: parts[2], + value: parts.slice(3).join(":"), + }; +} + +function readNestedField(source, fieldPath) { + let value = source; + for (const segment of fieldPath.split(".")) { + if (!value || typeof value !== "object") return ""; + value = value[segment]; + } + return typeof value === "string" ? value : ""; +} + +function parseRuntimeLifecycleExpectation(value, field) { + const parts = value.split(":"); + if (parts.length < 3 || !parts[0] || !parts[1] || !parts.slice(2).join(":")) { + console.error(`Expected runtime lifecycle ${field} in form, got: ${value}`); + process.exit(2); + } + return { + runtimeId: parts[0], + stage: parts[1], + [field]: parts.slice(2).join(":"), + }; +} + +function parseSplitTargetExpectation(value, field) { + const separator = value.indexOf(":"); + if (separator <= 0 || separator === value.length - 1) { + console.error(`Expected split target ${field} in form, got: ${value}`); + process.exit(2); + } + return { + targetId: value.slice(0, separator), + [field]: value.slice(separator + 1), + }; +} + +function mergeRuntimeRecords(topLevelRuntimes, runtimeTargets) { + const byId = new Map(); + for (const runtime of runtimeTargets) { + if (runtime.runtimeId) byId.set(runtime.runtimeId, runtime); + } + for (const runtime of topLevelRuntimes) { + if (!runtime.runtimeId) continue; + byId.set(runtime.runtimeId, { + ...(byId.get(runtime.runtimeId) || {}), + ...runtime, + }); + } + return [...byId.values()]; +} diff --git a/tools/parity/export-fixture.mjs b/tools/parity/export-fixture.mjs new file mode 100644 index 0000000..9fd34cf --- /dev/null +++ b/tools/parity/export-fixture.mjs @@ -0,0 +1,36 @@ +import { mkdir, readFile, writeFile } from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { compileEveDsl } from "../../web/eve-dsl.js"; + +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); +const manifest = JSON.parse(await readFile(path.join(repoRoot, "tools/parity/parity-manifest.json"), "utf8")); + +const fixtureId = process.argv[2] || "cultui-inspector"; +const outputPath = process.argv[3] + ? path.resolve(process.argv[3]) + : path.join(repoRoot, "artifacts/parity/fixtures", `${fixtureId}.surface.json`); + +const fixture = manifest.fixtures.find(candidate => + candidate.id === fixtureId || candidate.expect?.providerId === fixtureId); + +if (!fixture) { + throw new Error(`Unknown parity fixture: ${fixtureId}`); +} + +const surface = await loadSurface(fixture.surface); +surface.fixtureId = fixture.id; +surface.values ||= surface.mesh?.snapshot?.() || {}; +delete surface.mesh; + +await mkdir(path.dirname(outputPath), { recursive: true }); +await writeFile(outputPath, `${JSON.stringify(surface, null, 2)}\n`); +console.log(outputPath); + +async function loadSurface(surface) { + const absolutePath = path.join(repoRoot, surface.path); + const source = await readFile(absolutePath, "utf8"); + if (surface.transport === "local-eve-dsl") return compileEveDsl(source); + if (surface.transport === "local-json") return JSON.parse(source); + throw new Error(`Unsupported parity surface transport: ${surface.transport}`); +} diff --git a/tools/parity/parity-manifest.json b/tools/parity/parity-manifest.json new file mode 100644 index 0000000..3f19d3f --- /dev/null +++ b/tools/parity/parity-manifest.json @@ -0,0 +1,3104 @@ +{ + "schema": "gamecult.eve.parity_manifest.v1", + "repoStrategy": { + "boundaryRule": "Eve core owns contracts; plugins own semantics; runtime repos own native projection; provider repos own live truth.", + "incubationPolicy": "Eve may host runtimes, plugins, and conformance packs only while their contract is still forming.", + "graduationDocument": "../Eve/docs/repo-strategy.md", + "conformanceHandoffPath": "" + }, + "schemas": { + "gamecult.eve.conformance_fixture.v1": "../Eve/schemas/gamecult.eve.conformance_fixture.v1.schema.json", + "gamecult.eve.conformance_export.v1": "../Eve/schemas/gamecult.eve.conformance_export.v1.schema.json", + "gamecult.eve.capability_matrix.v1": "../Eve/schemas/gamecult.eve.capability_matrix.v1.schema.json", + "gamecult.eve.command.v1": "../Eve/schemas/gamecult.eve.command.v1.schema.json", + "gamecult.eve.command_invocation.v1": "../Eve/schemas/gamecult.eve.command_invocation.v1.schema.json", + "gamecult.eve.command_receipt.v1": "../Eve/schemas/gamecult.eve.command_receipt.v1.schema.json", + "gamecult.eve.plugin.v1": "../Eve/schemas/gamecult.eve.plugin.v1.schema.json", + "gamecult.eve.plugin_receipt.v1": "../Eve/schemas/gamecult.eve.plugin_receipt.v1.schema.json", + "gamecult.eve.plugin_abi.request.v1": "../Eve/schemas/gamecult.eve.plugin_abi.request.v1.schema.json", + "gamecult.eve.plugin_abi.response.v1": "../Eve/schemas/gamecult.eve.plugin_abi.response.v1.schema.json", + "gamecult.eve.plugin_abi_fixture.v1": "../Eve/schemas/gamecult.eve.plugin_abi_fixture.v1.schema.json", + "gamecult.eve.plugin_advertisement.v1": "../Eve/schemas/gamecult.eve.plugin_advertisement.v1.schema.json", + "gamecult.eve.plugin_handoff.v1": "../Eve/schemas/gamecult.eve.plugin_handoff.v1.schema.json", + "gamecult.eve.plugin_witness.v1": "../Eve/schemas/gamecult.eve.plugin_witness.v1.schema.json", + "gamecult.eve.runtime_capability.v1": "../Eve/schemas/gamecult.eve.runtime_capability.v1.schema.json", + "gamecult.eve.runtime_lifecycle.v1": "../Eve/schemas/gamecult.eve.runtime_lifecycle.v1.schema.json", + "gamecult.eve.runtime_split_handoff.v1": "../Eve/schemas/gamecult.eve.runtime_split_handoff.v1.schema.json", + "gamecult.eve.runtime_release_request.v1": "../Eve/schemas/gamecult.eve.runtime_release_request.v1.schema.json", + "gamecult.eve.runtime_capture_request.v1": "../Eve/schemas/gamecult.eve.runtime_capture_request.v1.schema.json", + "gamecult.eve.runtime_witness.v1": "../Eve/schemas/gamecult.eve.runtime_witness.v1.schema.json", + "gamecult.eve.web_layout_probe.v1": "../Eve/schemas/gamecult.eve.web_layout_probe.v1.schema.json", + "gamecult.eve.provider_scenario.v1": "../Eve/schemas/gamecult.eve.provider_scenario.v1.schema.json", + "gamecult.eve.provider_advertisement.v1": "../Eve/schemas/gamecult.eve.provider_advertisement.v1.schema.json", + "gamecult.eve.local_provider_catalog.v1": "../Eve/schemas/gamecult.eve.local_provider_catalog.v1.schema.json", + "gamecult.eve.provider_handoff.v1": "../Eve/schemas/gamecult.eve.provider_handoff.v1.schema.json", + "gamecult.eve.conformance_handoff.v1": "../Eve/schemas/gamecult.eve.conformance_handoff.v1.schema.json", + "gamecult.eve.electron_shell_projection.v1": "../Eve/schemas/gamecult.eve.electron_shell_projection.v1.schema.json", + "gamecult.eve.unity_uitoolkit_projection.v1": "../Eve/schemas/gamecult.eve.unity_uitoolkit_projection.v1.schema.json", + "gamecult.eve.unity_scene_projection.v1": "../Eve/schemas/gamecult.eve.unity_scene_projection.v1.schema.json", + "gamecult.eve.unity_playable_world_asset_manifest.v1": "../Eve/schemas/gamecult.eve.unity_playable_world_asset_manifest.v1.schema.json", + "gamecult.eve.tui_grid.v1": "../Eve/schemas/gamecult.eve.tui_grid.v1.schema.json" + }, + "conformancePacks": [ + { + "id": "core", + "ownerRepo": "Eve", + "exitCriteria": "Stays in Eve; proves surface contract, CultUI DSL, bindings, commands, and embedded documents." + }, + { + "id": "plugin", + "ownerRepo": "EvePlugins until owner repos stabilize", + "exitCriteria": "Moves when plugin semantics, fixtures, and conformance cases are stable." + }, + { + "id": "provider", + "ownerRepo": "Provider repo", + "exitCriteria": "Moves when fixture carries product-specific state, assets, flows, or receipts." + }, + { + "id": "runtime", + "ownerRepo": "Runtime repo", + "exitCriteria": "Moves when runtime has its own build/deploy lifecycle or platform SDK pressure." + } + ], + "responsiveCases": [ + { + "id": "phone", + "width": 390, + "height": 844, + "orientation": "portrait", + "deviceScale": 1 + }, + { + "id": "tablet", + "width": 768, + "height": 1024, + "orientation": "portrait", + "deviceScale": 1 + }, + { + "id": "desktop", + "width": 1280, + "height": 720, + "orientation": "landscape", + "deviceScale": 1 + } + ], + "androidDeviceCases": [ + { + "id": "native-portrait", + "orientation": "portrait" + }, + { + "id": "native-landscape", + "orientation": "landscape" + } + ], + "fixtures": [ + { + "id": "cultui-inspector", + "title": "CultUI Inspector", + "pack": "core", + "ownerRepo": "Eve", + "surface": { + "transport": "local-eve-dsl", + "path": "web/fixtures/cultui-slider-inspector.eve" + }, + "metadataPath": "web/fixtures/cultui-slider-inspector.conformance.json", + "expect": { + "providerId": "eve.cultui.inspector", + "componentKinds": [ + "surface", + "partition", + "pane", + "label", + "control.slider" + ], + "minimumCounts": { + "partition": 8, + "control.slider": 3, + "label": 3 + }, + "controlSkins": [ + "inspector.orangeSlider" + ], + "controlParts": [ + "track", + "fill", + "thumb" + ], + "bindings": [ + "tester.fov", + "tester.throttleDecay", + "tester.flowSpeed" + ] + }, + "sourceRoot": "../Eve" + }, + { + "id": "repixelizer", + "title": "Repixelizer", + "pack": "provider", + "ownerRepo": "Repixelizer", + "surface": { + "transport": "local-json", + "path": "web/fixtures/repixelizer.eve-surface.json" + }, + "metadataPath": "web/fixtures/repixelizer.conformance.json", + "expect": { + "providerId": "repixelizer", + "componentKinds": [ + "surface", + "pane", + "card", + "text", + "image.preview", + "canvas.preview", + "canvas.editor", + "input.file", + "dropzone", + "input.number", + "control.button", + "status.stage" + ], + "styleTokens": [ + "colorBackgroundTop", + "colorPanel", + "colorAccent", + "fontTitle", + "fontBody", + "imageRendering" + ] + }, + "sourceRoot": "../Eve" + }, + { + "id": "sai-vn", + "title": "Sai VN", + "pack": "plugin", + "ownerRepo": "Sai", + "requiredPlugins": [ + { + "pluginId": "sai.vn", + "capabilities": [ + "vn.stage", + "story.choose", + "story.continue", + "story.jump" + ] + } + ], + "optionalNestedPlugins": [ + { + "pluginId": "norn.graph", + "capabilities": [ + "embed.norn" + ] + }, + { + "pluginId": "tex.math", + "capabilities": [ + "embed.tex" + ] + } + ], + "surface": { + "transport": "local-json", + "path": "web/fixtures/sai-vn-surface.json" + }, + "metadataPath": "web/fixtures/sai-vn-surface.conformance.json", + "expect": { + "providerId": "gamecult.home.vn", + "componentKinds": [ + "vn.stage", + "image.background", + "embed.norn", + "embed.tex", + "panel.dialogue", + "rail.actions", + "control.button" + ], + "styleTokens": [ + "colorBackground", + "colorPanel", + "colorPanelAlt", + "colorText", + "colorAccent", + "fontBody", + "fontTitle", + "pixelArt" + ], + "commandDescriptors": [ + "story.continue", + "story.choose", + "story.jump", + "style.patch" + ], + "commandDescriptorSchema": "gamecult.eve.command.v1", + "commandReferencesDeclared": true + }, + "sourceRoot": "../Eve" + }, + { + "id": "aetheria-world", + "title": "Aetheria Interactive World", + "pack": "provider", + "ownerRepo": "Aetheria", + "sourceRoot": "../Aetheria", + "surface": { + "transport": "local-json", + "path": "conformance/eve/aetheria-world-surface.json" + }, + "metadataPath": "conformance/eve/aetheria-world-surface.conformance.json", + "expect": { + "providerId": "aetheria", + "componentKinds": [ + "surface", + "card", + "text", + "metric", + "partition", + "field.surface2d", + "control.button" + ], + "styleTokens": [ + "colorBackground", + "colorPanel", + "colorText", + "colorMuted", + "colorAccent" + ], + "bindings": [ + "aetheria.daemon.frameId", + "aetheria.daemon.soaView.gravityField", + "aetheria.daemon.commands" + ], + "commandDescriptors": [ + "aetheria.daemon.commands" + ], + "commandDescriptorSchema": "gamecult.eve.command.v1", + "commandReferencesDeclared": true + } + }, + { + "id": "eve-world-smoke", + "title": "Generic Interactive World Smoke", + "pack": "core", + "ownerRepo": "Eve", + "surface": { + "transport": "local-json", + "path": "web/fixtures/eve-world-smoke-surface.json" + }, + "metadataPath": "web/fixtures/eve-world-smoke-surface.conformance.json", + "expect": { + "providerId": "eve.world-smoke", + "componentKinds": [ + "surface", + "card", + "text", + "metric", + "field.surface2d", + "control.button" + ], + "styleTokens": [ + "colorBackground", + "colorPanel", + "colorText", + "colorMuted", + "colorAccent", + "fontBody", + "fontTitle" + ], + "bindings": [ + "eve.world-smoke.frame", + "eve.world-smoke.field" + ], + "commandDescriptors": [ + "eve.world-smoke.commands" + ], + "commandDescriptorSchema": "gamecult.eve.command.v1", + "commandReferencesDeclared": true + }, + "sourceRoot": "../Eve" + }, + { + "id": "fensalir-direct2d", + "title": "Fensalir Direct2D Adapter", + "pack": "runtime", + "ownerRepo": "Fensalir", + "surface": { + "transport": "local-json", + "path": "web/fixtures/fensalir-direct2d-surface.json" + }, + "metadataPath": "web/fixtures/fensalir-direct2d-surface.conformance.json", + "expect": { + "providerId": "fensalir.direct2d", + "componentKinds": [ + "surface", + "card", + "text", + "metric", + "partition", + "surface.slot", + "control.button" + ], + "styleTokens": [ + "colorBackground", + "colorPanel", + "colorText", + "colorMuted", + "colorAccent" + ], + "bindings": [ + "fensalir.overlay.phase" + ], + "embeddedDocuments": [ + "fensalir.adapter.detail" + ], + "commandDescriptors": [ + "fensalir.overlay.commands" + ], + "commandDescriptorSchema": "gamecult.eve.command.v1", + "commandReferencesDeclared": true, + "authorityStates": [ + "fresh" + ], + "authorityOwners": [ + "runtime" + ], + "witnessRefs": 1 + }, + "sourceRoot": "../Eve" + }, + { + "id": "authority-staleness", + "title": "Authority And Staleness Witness", + "pack": "core", + "ownerRepo": "Eve", + "surface": { + "transport": "local-json", + "path": "web/fixtures/authority-staleness-witness.json" + }, + "metadataPath": "web/fixtures/authority-staleness-witness.conformance.json", + "expect": { + "providerId": "gamecult.eve.authority-witness", + "componentKinds": [ + "surface", + "card", + "text", + "metric", + "partition" + ], + "styleTokens": [ + "colorBackground", + "colorPanel", + "colorText", + "colorMuted", + "colorAccent", + "colorWarning", + "colorDanger" + ], + "bindings": [ + "authority.temperature.accepted", + "authority.sensor.echo", + "authority.missing.field", + "authority.orbit.predicted", + "authority.orbit.accepted" + ], + "commandDescriptors": [ + "authority.fixture.patch" + ], + "commandDescriptorSchema": "gamecult.eve.command.v1", + "commandReferencesDeclared": true, + "authorityStates": [ + "accepted", + "denied", + "fresh", + "missing", + "pending", + "predicted", + "reconciled", + "stale" + ], + "authorityOwners": [ + "provider", + "renderer" + ], + "witnessRefs": 8, + "receiptRefs": 2 + }, + "sourceRoot": "../Eve" + }, + { + "id": "reactive-dsl", + "title": "Reactive DSL", + "pack": "core", + "ownerRepo": "Eve", + "surface": { + "transport": "local-eve-dsl", + "path": "web/fixtures/reactive-composition.eve" + }, + "metadataPath": "web/fixtures/reactive-composition.conformance.json", + "expect": { + "providerId": "gamecult.eve.reactive-demo", + "componentKinds": [ + "surface", + "card", + "text", + "metric", + "list", + "control.button" + ], + "bindings": [ + "face.name", + "face.body", + "swarm.heat", + "swarm.eventCount", + "swarm.latestEvent" + ] + }, + "sourceRoot": "../Eve" + }, + { + "id": "huginn-cc", + "title": "Huginn .cc", + "pack": "provider", + "ownerRepo": "Huginn", + "surface": { + "transport": "local-eve-dsl", + "path": "web/fixtures/huginn-cc-surface.eve" + }, + "metadataPath": "web/fixtures/huginn-cc-surface.conformance.json", + "expect": { + "providerId": "cultcache.huginn.inspector", + "componentKinds": [ + "surface", + "card", + "text", + "list" + ], + "bindings": [ + "huginn.catalog.entries", + "huginn.records.entries" + ] + }, + "sourceRoot": "../Eve" + }, + { + "id": "embedded-surface", + "title": "Embedded Surface Slot", + "pack": "core", + "ownerRepo": "Eve", + "surface": { + "transport": "local-json", + "path": "web/fixtures/cultui-embedded-surface.json" + }, + "metadataPath": "web/fixtures/cultui-embedded-surface.conformance.json", + "expect": { + "providerId": "gamecult.eve.embedded-demo", + "componentKinds": [ + "surface", + "card", + "text", + "surface.slot" + ], + "embeddedDocuments": [ + "inventory.dropdown" + ], + "styleTokens": [ + "colorBackground", + "colorPanel", + "colorText", + "colorMuted", + "colorAccent" + ] + }, + "sourceRoot": "../Eve" + } + ], + "providerAdvertisements": [ + { + "providerId": "repixelizer", + "title": "Repixelizer", + "kind": "service.product", + "ownerRepo": "Repixelizer", + "repoRole": "provider-external", + "graduationTrigger": "Provider-owned product state, assets, sessions, and receipts stay in Repixelizer; Eve keeps only the advertisement-shaped fixture.", + "schemaPath": "schemas/gamecult.eve.provider_advertisement.v1.schema.json", + "advertisementPath": "web/fixtures/repixelizer.provider-advertisement.json", + "requiredFixtures": [ + "repixelizer" + ], + "expectedSchemas": [ + "gamecult.eve.surface.v1" + ], + "expectedSurfaces": [ + "repixelizer.operator.surface" + ], + "expectedCommands": [ + "repixelizer.job.submit", + "repixelizer.job.cancel_own" + ], + "sourceRoot": "../Eve" + }, + { + "providerId": "gamecult.home.vn", + "title": "GameCult Compound VN", + "kind": "plugin.fixture.provider", + "ownerRepo": "Sai", + "repoRole": "provider-fixture", + "graduationTrigger": "Move this advertisement-shaped VN fixture to Sai when sai-vn-plugin owns VN/Ink semantics and conformance packs.", + "schemaPath": "schemas/gamecult.eve.provider_advertisement.v1.schema.json", + "advertisementPath": "web/fixtures/sai-vn.provider-advertisement.json", + "requiredFixtures": [ + + ], + "expectedSchemas": [ + "gamecult.eve.surface.v1", + "gamecult.eve.command.v1", + "gamecult.eve.command_invocation.v1", + "sai.vn.story_session.v1" + ], + "expectedSurfaces": [ + "sai.visual_novel.surface" + ], + "expectedCommands": [ + "story.continue", + "story.choose", + "story.jump", + "style.patch" + ], + "sourceRoot": "../Eve" + }, + { + "providerId": "aetheria", + "title": "Aetheria", + "kind": "game.runtime", + "ownerRepo": "Aetheria", + "repoRole": "provider-external", + "sourceRoot": "../Aetheria", + "graduationTrigger": "Aetheria owns RTS/ARPG world surfaces, generated level state, assets, command receipts, and scenario conformance; Eve keeps contract fixtures only.", + "schemaPath": "schemas/gamecult.eve.provider_advertisement.v1.schema.json", + "advertisementPath": "conformance/eve/aetheria.provider-advertisement.json", + "scenarioPath": "conformance/eve/aetheria-world-scenario.json", + "requiredFixtures": [ + "aetheria-world" + ], + "expectedSchemas": [ + "gamecult.eve.surface.v1", + "gamecult.eve.command.v1", + "gamecult.eve.command_invocation.v1", + "gamecult.eve.unity_playable_world_asset_manifest.v1", + "aetheria.runtime.daemon.frame.v1" + ], + "expectedSurfaces": [ + "aetheria.daemon.game", + "aetheria.daemon.editor" + ], + "expectedSurfaceKinds": { + "aetheria.daemon.game": "interactive-world", + "aetheria.daemon.editor": "interactive-world-editor" + }, + "expectedCommands": [ + "aetheria.daemon.commands" + ], + "expectedPaths": [ + "conformance/eve/provider-pack.json", + "conformance/eve/aetheria-world-scenario.json", + "scripts/verify-eve-conformance-pack.mjs" + ] + }, + { + "providerId": "eve.world-smoke", + "title": "Generic Interactive World Smoke", + "kind": "contract.fixture", + "ownerRepo": "Eve", + "repoRole": "core-contract-fixture", + "graduationTrigger": "Stays in Eve core as the minimal non-product interactive-world control fixture.", + "schemaPath": "schemas/gamecult.eve.provider_advertisement.v1.schema.json", + "advertisementPath": "web/fixtures/eve-world-smoke.provider-advertisement.json", + "scenarioPath": "web/fixtures/eve-world-smoke-scenario.json", + "requiredFixtures": [ + "eve-world-smoke" + ], + "expectedSchemas": [ + "gamecult.eve.surface.v1", + "gamecult.eve.command.v1", + "gamecult.eve.command_invocation.v1", + "eve.world_smoke.frame.v1", + "eve.world_smoke.command_receipt.v1" + ], + "expectedSurfaces": [ + "eve.world-smoke.surface" + ], + "expectedSurfaceKinds": { + "eve.world-smoke.surface": "interactive-world" + }, + "expectedCommands": [ + "eve.world-smoke.commands" + ], + "expectedPaths": [ + "web/fixtures/eve-world-smoke-surface.json", + "web/fixtures/eve-world-smoke-surface.conformance.json", + "web/fixtures/eve-world-smoke-scenario.json" + ], + "sourceRoot": "../Eve" + } + ], + "pluginManifests": [ + { + "pluginId": "sai.vn", + "title": "Sai Visual Novel", + "kind": "external-owner", + "ownerRepo": "Sai", + "repoRole": "plugin-owner", + "splitTarget": "Sai", + "graduationTrigger": "Graduated: Sai owns retained Ink playback, fixtures, ABI sidecar, and release lifecycle.", + "capabilities": [ + "vn.stage", + "story.choose", + "story.continue", + "story.jump" + ], + "optionalPlugins": [ + "norn.graph", + "tex.math" + ], + "expectedRuntime": { + "invocationModel": "executable-sidecar", + "contract": "gamecult.eve.plugin_abi.v1", + "transports": [ + "stdio" + ], + "authority": [ + "renderer-independent", + "no-provider-state-mutation", + "provider-accepts-or-denies-commands" + ], + "sidecar": { + "processKind": "long-running-daemon", + "protocol": "stdio-ndjson", + "requestSchema": "gamecult.eve.plugin_abi.request.v1", + "responseSchema": "gamecult.eve.plugin_abi.response.v1", + "commandEnvelope": "gamecult.eve.command_invocation.v1", + "receiptSchema": "gamecult.eve.command_receipt.v1", + "stateAuthority": "sai-sidecar-owns-story-session-provider-owns-command-acceptance" + } + }, + "requiredFixtures": [ + "sai-vn" + ], + "schemaPath": "schemas/gamecult.eve.plugin.v1.schema.json", + "manifestPath": "../Sai/plugins/sai-vn.plugin.json", + "abiFixturePath": "../Sai/plugins/sai-vn.plugin-abi-fixture.json", + "advertisementSchemaPath": "schemas/gamecult.eve.plugin_advertisement.v1.schema.json", + "advertisementPath": "../Sai/plugins/sai-vn.plugin-advertisement.json", + "expectedPaths": [ + "../Sai/plugins/sai-vn.plugin.json", + "../Sai/plugins/sai-vn.plugin-abi-fixture.json", + "../Sai/plugins/sai-vn.plugin-advertisement.json", + "../Sai/src/eve-sidecar.cjs" + ], + "sourceRoot": "../Eve" + }, + { + "pluginId": "norn.graph", + "title": "Norn Graph", + "kind": "external-owner", + "ownerRepo": "Norn", + "repoRole": "plugin-owner", + "splitTarget": "Norn", + "graduationTrigger": "Graduated: Norn owns graph semantics, fixtures, ABI sidecar, and release lifecycle.", + "capabilities": [ + "embed.norn", + "graph.node.activate" + ], + "expectedRuntime": { + "invocationModel": "executable-sidecar", + "contract": "gamecult.eve.plugin_abi.v1", + "transports": [ + "stdio" + ], + "authority": [ + "renderer-independent", + "no-provider-state-mutation", + "provider-accepts-or-denies-commands" + ], + "sidecar": { + "processKind": "long-running-daemon", + "protocol": "stdio-ndjson", + "requestSchema": "gamecult.eve.plugin_abi.request.v1", + "responseSchema": "gamecult.eve.plugin_abi.response.v1", + "commandEnvelope": "gamecult.eve.command_invocation.v1", + "receiptSchema": "gamecult.eve.command_receipt.v1", + "stateAuthority": "proposes-plugin-state-only-provider-accepts" + } + }, + "requiredFixtures": [ + + ], + "schemaPath": "schemas/gamecult.eve.plugin.v1.schema.json", + "manifestPath": "../Norn/plugins/norn-graph.plugin.json", + "abiFixturePath": "../Norn/plugins/norn-graph.plugin-abi-fixture.json", + "advertisementSchemaPath": "schemas/gamecult.eve.plugin_advertisement.v1.schema.json", + "advertisementPath": "../Norn/plugins/norn-graph.plugin-advertisement.json", + "expectedPaths": [ + "../Norn/plugins/norn-graph.plugin.json", + "../Norn/plugins/norn-graph.plugin-abi-fixture.json", + "../Norn/plugins/norn-graph.plugin-advertisement.json", + "../Norn/crates/norn-eve-plugin/src/main.rs" + ], + "sourceRoot": "../Eve" + }, + { + "pluginId": "tex.math", + "title": "TeX Math", + "kind": "external-owner", + "ownerRepo": "EvePlugins", + "repoRole": "plugin-owner", + "splitTarget": "EvePlugins/eve-plugin-tex", + "graduationTrigger": "Graduated: EvePlugins owns the executable TeX sidecar, fixtures, tests, and conformance witness.", + "capabilities": [ + "embed.tex", + "tex.inline", + "tex.block" + ], + "expectedRuntime": { + "invocationModel": "executable-sidecar", + "contract": "gamecult.eve.plugin_abi.v1", + "transports": [ + "stdio" + ], + "authority": [ + "renderer-independent", + "no-provider-state-mutation", + "provider-accepts-or-denies-commands" + ], + "sidecar": { + "processKind": "long-running-daemon", + "protocol": "stdio-ndjson", + "requestSchema": "gamecult.eve.plugin_abi.request.v1", + "responseSchema": "gamecult.eve.plugin_abi.response.v1", + "commandEnvelope": "gamecult.eve.command_invocation.v1", + "receiptSchema": "gamecult.eve.plugin_receipt.v1", + "stateAuthority": "emits-render-results-only-provider-owns-source-state" + } + }, + "requiredFixtures": [ + + ], + "schemaPath": "schemas/gamecult.eve.plugin.v1.schema.json", + "manifestPath": "../EvePlugins/plugins/eve-plugin-tex/plugin.json", + "abiFixturePath": "../EvePlugins/plugins/eve-plugin-tex/plugin-abi-fixture.json", + "advertisementSchemaPath": "schemas/gamecult.eve.plugin_advertisement.v1.schema.json", + "advertisementPath": "../EvePlugins/plugins/eve-plugin-tex/advertisement.json", + "expectedPaths": [ + "../EvePlugins/plugins/eve-plugin-tex/plugin.json", + "../EvePlugins/plugins/eve-plugin-tex/plugin-abi-fixture.json", + "../EvePlugins/plugins/eve-plugin-tex/advertisement.json", + "../EvePlugins/plugins/eve-plugin-tex/src/sidecar.mjs" + ], + "sourceRoot": "../Eve" + } + ], + "splitTargets": [ + { + "id": "EveFlutter", + "ownerRepo": "EveFlutter", + "repoRole": "runtime-target", + "status": "incubating", + "runtimes": [ + "windows-flutter", + "linux-flutter", + "android-flutter" + ], + "requiredRuntimeStatuses": [ + "active" + ], + "requiredFeatures": [ + "embeddedDocuments" + ], + "requiredPlugins": [ + { + "pluginId": "sai.vn", + "capabilities": [ + "vn.stage", + "story.choose", + "story.continue", + "story.jump" + ] + }, + { + "pluginId": "norn.graph", + "capabilities": [ + "embed.norn" + ] + }, + { + "pluginId": "tex.math", + "capabilities": [ + "embed.tex" + ] + } + ], + "proofs": [ + { + "description": "Provider picker consumes provider advertisements without Eve source-layout knowledge.", + "status": "passed", + "evidencePaths": [ + "../EveFlutter/lib/main.dart", + "../EveFlutter/test/parity_smoke_test.dart", + "tools/parity/run-parity.mjs" + ] + }, + { + "description": "Conformance pack can be consumed without depending on Eve\u0027s local harness layout.", + "status": "passed", + "evidencePaths": [ + "../EveFlutter/scripts/run-eveflutter-conformance-consumer-smoke.ps1", + "tools/conformance/consume-export.mjs", + "tools/parity/run-parity.mjs" + ] + }, + { + "description": "Flutter lifecycle evidence is declared and smoke-checked for EveFlutter incubation.", + "status": "passed", + "evidencePaths": [ + "../EveFlutter/eveflutter-lifecycle.json", + "../EveFlutter/scripts/run-eveflutter-lifecycle-smoke.ps1", + "../EveFlutter/scripts/capture-flutter-parity.ps1", + "../EveFlutter/scripts/capture-linux-flutter-parity.ps1", + "../EveFlutter/scripts/capture-android-flutter-parity.ps1" + ] + } + ], + "pendingProofs": [ + "Tagged EveFlutter release, test runner, and capture runners are owned by EveFlutter rather than Eve incubation." + ] + }, + { + "id": "EveUnity", + "ownerRepo": "EveUnity", + "repoRole": "runtime-target", + "status": "incubating", + "runtimes": [ + "unity-uitoolkit", + "unity-scene" + ], + "requiredRuntimeStatuses": [ + "active" + ], + "requiredFeatures": [ + "embeddedDocuments" + ], + "requiredPlugins": [ + { + "pluginId": "sai.vn", + "capabilities": [ + "vn.stage", + "story.choose", + "story.continue", + "story.jump" + ] + }, + { + "pluginId": "norn.graph", + "capabilities": [ + "embed.norn" + ] + } + ], + "proofs": [ + { + "description": "Unity UI Toolkit package lifecycle is owned and tested from EveUnity.", + "status": "passed", + "evidencePaths": [ + "../EveUnity/packages/org.gamecult.eve.unity-uitoolkit/eve-runtime-capability.json", + "../EveUnity/scripts/pack-uitoolkit.ps1", + "../EveUnity/scripts/run-uitoolkit-tests.ps1" + ] + }, + { + "description": "Aetheria consumes Unity surface and UI Toolkit packages from EveUnity.", + "status": "passed", + "evidencePaths": [ + "../EveUnity/packages/org.gamecult.eve.surface/package.json", + "../EveUnity/packages/org.gamecult.eve.unity-uitoolkit/package.json", + "../Aetheria/Packages/manifest.json" + ] + }, + { + "description": "Unity scene runtime is owned by EveUnity and its owner-repo sources are present.", + "status": "passed", + "evidencePaths": [ + "../EveUnity/packages/org.gamecult.eve.unity-scene/eve-runtime-capability.json", + "../EveUnity/packages/org.gamecult.eve.unity-scene/package.json" + ] + }, + { + "description": "Unity scene lifecycle is owned and tested from EveUnity.", + "status": "passed", + "evidencePaths": [ + "../EveUnity/packages/org.gamecult.eve.unity-scene/eve-runtime-capability.json", + "../EveUnity/scripts/pack.ps1", + "../EveUnity/scripts/run-package-tests.ps1" + ] + }, + { + "description": "Aetheria daemon 3D ARPG world lowers through a generic EveUnity playable-world client without Aetheria runtime authority.", + "status": "passed", + "evidencePaths": [ + "../EveUnity/packages/org.gamecult.eve.unity-scene/eve-runtime-capability.json", + "../Aetheria/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveUnitySceneProviderBridge.cs", + "../Aetheria/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs" + ] + }, + { + "description": "Unity scene graph projection lowerer plus Sai, Norn, and TeX projection adapters exist without claiming provider simulation, plugin semantics, or capture ownership.", + "status": "passed", + "evidencePaths": [ + "../EveUnity/packages/org.gamecult.eve.unity-scene/package.json", + "../EveUnity/packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs", + "../EveUnity/packages/org.gamecult.eve.unity-scene/Runtime/SaiVisualNovelUnitySceneProjectionAdapter.cs", + "../EveUnity/packages/org.gamecult.eve.unity-scene/Runtime/NornGraphUnitySceneProjectionAdapter.cs", + "../EveUnity/packages/org.gamecult.eve.unity-scene/Runtime/TeXMathUnitySceneProjectionAdapter.cs", + "../EveUnity/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs" + ] + } + ], + "pendingProofs": [ + "Tagged UPM release is cut from EveUnity.", + "Unity PlayMode frame capture is attached to conformance for Aetheria and the generic world-smoke provider.", + "Generic live CultMesh/CultNet provider transport replaces Aetheria local state-file polling." + ] + }, + { + "id": "EveElectron", + "ownerRepo": "EveElectron", + "repoRole": "runtime-target", + "status": "graduated", + "runtimes": [ + "electron-shell" + ], + "requiredRuntimeStatuses": [ + "active" + ], + "requiredFeatures": [ + "providerAdvertisements", + "surfaceTreeProjection", + "embeddedDocuments" + ], + "requiredPlugins": [ + + ], + "proofs": [ + { + "description": "EveElectron owner repository publishes the generic runtime package.", + "status": "passed", + "evidencePaths": [ + "../EveElectron/package.json", + "../EveElectron/eve-runtime-capability.json", + "../EveElectron/src/index.mjs" + ] + }, + { + "description": "EveElectron owns secure window and preload lifecycle.", + "status": "passed", + "evidencePaths": [ + "../EveElectron/src/window-host.mjs", + "../EveElectron/src/eve-provider-preload.cjs", + "../EveElectron/test/window-host.test.mjs", + "../EveElectron/test/preload.test.cjs" + ] + }, + { + "description": "Aetheria consumes EveElectron for generic window and preload authority while retaining provider transport and daemon lifecycle.", + "status": "passed", + "evidencePaths": [ + "../Aetheria/Aetheria.Rts.Web/package.json", + "../Aetheria/Aetheria.Rts.Web/Electron/main.ts", + "../Aetheria/Aetheria.Rts.Web/Electron/preload.cjs", + "../Aetheria/Aetheria.Rts.Web/Client/app.ts" + ] + } + ], + "pendingProofs": [ + + ] + }, + { + "id": "EveTui", + "ownerRepo": "EveTui", + "repoRole": "runtime-target", + "status": "incubating", + "runtimes": [ + "tui" + ], + "requiredRuntimeStatuses": [ + "active" + ], + "requiredFeatures": [ + "providerAdvertisements", + "embeddedDocuments", + "commandTransport" + ], + "requiredPlugins": [ + + ], + "proofs": [ + { + "description": "EveTui split handoff is machine-readable.", + "status": "passed", + "evidencePaths": [ + "runtimes/incubating/eve-tui/evetui-split-handoff.json", + "scripts/run-evetui-split-handoff-smoke.ps1", + "docs/eve-migration-roadmap.md", + "docs/parity-testing-harness.md" + ] + }, + { + "description": "EveTui lifecycle contract is machine-readable and smoke-checked.", + "status": "passed", + "evidencePaths": [ + "runtimes/incubating/eve-tui/eve-runtime-capability.json", + "scripts/run-evetui-lifecycle-smoke.ps1", + "scripts/run-evetui-provider-shell-smoke.ps1" + ] + }, + { + "description": "EveTui provider-shell skeleton selects advertised surfaces, lowers Eve surface trees into terminal grids, and emits Eve command intents.", + "status": "passed", + "evidencePaths": [ + "runtimes/incubating/eve-tui/package.json", + "runtimes/incubating/eve-tui/src/eve-tui-shell.mjs", + "runtimes/incubating/eve-tui/test/eve-tui-shell.test.mjs", + "scripts/run-evetui-provider-shell-smoke.ps1" + ] + } + ], + "pendingProofs": [ + "TUI package release is cut from EveTui rather than Eve incubation.", + "TUI capture path is owned by EveTui rather than Eve incubation." + ] + } + ], + "runtimes": [ + { + "id": "electron-shell", + "title": "Electron shell", + "kind": "active", + "ownerRepo": "EveElectron", + "repoRole": "runtime-target", + "splitTarget": "EveElectron", + "currentHostRepo": "EveElectron", + "sourceRoot": "../EveElectron", + "graduationTrigger": "Graduated: the runtime body and package are owned here.", + "capabilityManifest": { + "schemaPath": "schemas/gamecult.eve.runtime_capability.v1.schema.json", + "manifestPath": "eve-runtime-capability.json" + }, + "requiredFixtures": [ + "embedded-surface", + "aetheria-world", + "eve-world-smoke" + ], + "pluginFixtures": [ + "sai-vn" + ], + "supportedFeatures": [ + "providerAdvertisements", + "commandTransport", + "surfaceTreeProjection", + "embeddedDocuments", + "pluginProjection", + "securePreloadBridge", + "windowLifecycle" + ], + "supportedPlugins": [ + { + "pluginId": "sai.vn", + "capabilities": [ + "vn.stage", + "story.choose", + "story.continue", + "story.jump" + ] + }, + { + "pluginId": "norn.graph", + "capabilities": [ + "embed.norn" + ] + }, + { + "pluginId": "tex.math", + "capabilities": [ + "embed.tex", + "tex.inline", + "tex.block" + ] + } + ], + "unsupportedPlugins": [ + + ], + "worldSurfaceLowering": [ + { + "targetId": "electron-shell", + "surfaceKinds": [ + "interactive-world", + "interactive-world-editor" + ], + "projectionKinds": [ + "provider-authored-world-surface", + "provider-authored-world-editor-surface" + ], + "supportLevel": "provider-shell-surface-tree-command-surface", + "ownership": "runtime-lowers-provider-world-surface-without-owning-world-state", + "evidencePaths": [ + "src/eve-electron-shell.mjs", + "src/window-host.mjs", + "src/eve-provider-preload.cjs", + "test/shell.test.mjs", + "test/window-host.test.mjs", + "test/preload.test.cjs" + ] + } + ], + "expectedPaths": [ + "../EveElectron/package.json", + "../EveElectron/package-lock.json", + "../EveElectron/eve-runtime-capability.json", + "../EveElectron/src/eve-electron-shell.mjs", + "../EveElectron/src/window-host.mjs", + "../EveElectron/src/eve-provider-preload.cjs", + "../EveElectron/test/shell.test.mjs", + "../EveElectron/test/window-host.test.mjs", + "../EveElectron/test/preload.test.cjs" + ], + "expectedSourceSymbols": [ + { + "path": "../EveElectron/src/eve-electron-shell.mjs", + "contains": [ + "EveElectronShell", + "selectSurface", + "lowerSurface", + "createCommandIntent", + "normalizeSurfaceDocument", + "buildShellNode", + "buildPluginProjection", + "normalizeEmbeddedDocuments", + "embeddedDocuments", + "shellElementKind", + "sai.vn", + "norn.graph", + "tex.math", + "sidecar-advertised-plugin-abi", + "worldInteraction", + "commandBoundary", + "receiptSchema", + "electron-shell" + ] + }, + { + "path": "../EveElectron/test/shell.test.mjs", + "contains": [ + "lowers a non-product interactive world", + "fixture.world", + "provider-authored-world-surface", + "fixture.world.commands" + ] + }, + { + "path": "../EveElectron/src/window-host.mjs", + "contains": [ + "createEveElectronWindow", + "registerEveWindowControls", + "contextIsolation", + "nodeIntegration", + "setWindowOpenHandler" + ] + }, + { + "path": "../EveElectron/src/eve-provider-preload.cjs", + "contains": [ + "installEveProviderBridge", + "providerAdvertisement", + "surface", + "document", + "submitCommand", + "windowControl" + ] + } + ], + "capture": { + "status": "owner-window-png", + "note": "EveElectron owns native window PNG production for the generic and Aetheria provider surfaces.", + "artifacts": [ + { + "kind": "png", + "schema": "image/png", + "path": "artifacts/capture/generic-world/window.png", + "runtimeId": "electron-shell", + "providerId": "eve.world-smoke", + "surfaceId": "eve.world-smoke.surface", + "sourceSurfacePath": "../Eve/web/fixtures/eve-world-smoke-surface.json", + "requestPath": "artifacts/capture/generic-world/runtime-witness.json" + }, + { + "kind": "png", + "schema": "image/png", + "path": "artifacts/capture/aetheria-world/window.png", + "runtimeId": "electron-shell", + "providerId": "aetheria", + "surfaceId": "aetheria.daemon.game", + "sourceSurfacePath": "../Aetheria/conformance/eve/aetheria-world-surface.json", + "requestPath": "artifacts/capture/aetheria-world/runtime-witness.json" + } + ] + } + }, + { + "id": "tui", + "title": "Compact TUI", + "kind": "active", + "ownerRepo": "EveTui", + "repoRole": "runtime-target", + "splitTarget": "EveTui", + "graduationTrigger": "Split when TUI has a provider-agnostic terminal/grid lowerer, provider advertisement consumption, command transport, terminal capability manifest, and transcript/cell-grid capture evidence.", + "capabilityManifest": { + "schemaPath": "schemas/gamecult.eve.runtime_capability.v1.schema.json", + "manifestPath": "runtimes/incubating/eve-tui/eve-runtime-capability.json" + }, + "splitHandoffPath": "runtimes/incubating/eve-tui/evetui-split-handoff.json", + "requiredFixtures": [ + "embedded-surface", + "aetheria-world", + "eve-world-smoke" + ], + "pluginFixtures": [ + "sai-vn" + ], + "supportedFeatures": [ + "providerAdvertisements", + "commandTransport", + "terminalGridSummary", + "terminalGridLowering", + "embeddedDocuments", + "pluginProjection" + ], + "supportedPlugins": [ + { + "pluginId": "sai.vn", + "capabilities": [ + "vn.stage", + "story.choose", + "story.continue", + "story.jump" + ] + }, + { + "pluginId": "norn.graph", + "capabilities": [ + "embed.norn" + ] + }, + { + "pluginId": "tex.math", + "capabilities": [ + "embed.tex", + "tex.inline", + "tex.block" + ] + } + ], + "unsupportedPlugins": [ + + ], + "worldSurfaceLowering": [ + { + "targetId": "tui", + "surfaceKinds": [ + "interactive-world", + "interactive-world-editor" + ], + "projectionKinds": [ + "provider-authored-world-surface", + "provider-authored-world-editor-surface" + ], + "supportLevel": "terminal-grid-command-surface", + "ownership": "runtime-lowers-provider-world-surface-without-owning-world-state", + "lossiness": "terminal-grid-summary-only", + "evidencePaths": [ + "runtimes/incubating/eve-tui/src/eve-tui-shell.mjs", + "runtimes/incubating/eve-tui/test/eve-tui-shell.test.mjs", + "web/fixtures/sai-vn-surface.json", + "../Aetheria/conformance/eve/aetheria.provider-advertisement.json", + "web/fixtures/eve-world-smoke.provider-advertisement.json" + ] + } + ], + "expectedPaths": [ + "runtimes/incubating/eve-tui/package.json", + "runtimes/incubating/eve-tui/src/eve-tui-shell.mjs", + "runtimes/incubating/eve-tui/test/eve-tui-shell.test.mjs", + "runtimes/incubating/eve-tui/evetui-split-handoff.json", + "runtimes/incubating/eve-tui/eve-runtime-capability.json", + "scripts/run-evetui-split-handoff-smoke.ps1", + "scripts/run-evetui-lifecycle-smoke.ps1", + "scripts/run-evetui-provider-shell-smoke.ps1", + "scripts/run-evetui-capture-contract-smoke.ps1", + "scripts/run-evetui-capture-smoke.ps1", + "scripts/run-evetui-release-contract-smoke.ps1", + "tools/evetui/evetui-capture-contract.mjs", + "tools/evetui/evetui-capture-contract.test.mjs", + "tools/evetui/evetui-capture-artifact.mjs", + "tools/evetui/evetui-capture-artifact.test.mjs", + "tools/evetui/evetui-release-contract.mjs", + "tools/evetui/evetui-release-contract.test.mjs" + ], + "expectedSourceSymbols": [ + { + "path": "runtimes/incubating/eve-tui/src/eve-tui-shell.mjs", + "contains": [ + "EveTuiShell", + "selectSurface", + "lowerSurface", + "normalizeSurfaceDocument", + "buildComponentLines", + "buildPluginProjection", + "collectPluginProjections", + "collectEmbeddedDocumentSlots", + "normalizeEmbeddedDocuments", + "embeddedDocumentSlots", + "pluginProjections", + "terminalElementKind", + "sai.vn", + "norn.graph", + "tex.math", + "sidecar-advertised-plugin-abi", + "createCommandIntent", + "renderSummary", + "gamecult.eve.tui_grid.v1", + "commandBoundary", + "receiptSchema" + ] + }, + { + "path": "runtimes/incubating/eve-tui/test/eve-tui-shell.test.mjs", + "contains": [ + "selects advertised provider surfaces", + "command intents carry provider-advertised boundaries", + "summary grid is explicitly lossy", + "lowers provider surface trees into a terminal grid", + "lowers Sai, Norn, and TeX plugin surfaces into compact terminal fallbacks", + "embeddedDocumentSlots", + "pluginProjections", + "optional-nested", + "norn.map", + "sai-vn-terminal-stage-summary", + "norn-graph-terminal-outline", + "tex-math-terminal-block-source", + "rejects surface documents that do not match the advertised TUI target", + "aetheria.daemon.commands" + ] + }, + { + "path": "tools/evetui/evetui-capture-contract.mjs", + "contains": [ + "buildTuiCaptureRequest", + "gamecult.eve.runtime_capture_request.v1", + "artifactKind", + "loweringTargets", + "writeCaptureRequest" + ] + }, + { + "path": "tools/evetui/evetui-capture-contract.test.mjs", + "contains": [ + "builds TUI capture request from provider advertisement", + "builds TUI capture request from a declared generic world provider", + "rejects capture request when the surface does not advertise TUI", + "artifacts/evetui-capture/smoke/tui-grid.json", + "eve.world-smoke.commands" + ] + }, + { + "path": "tools/evetui/evetui-capture-artifact.mjs", + "contains": [ + "buildTuiCaptureArtifact", + "EveTuiShell", + "gamecult.eve.tui_grid.v1", + "writeTuiCaptureArtifact", + "request-output" + ] + }, + { + "path": "tools/evetui/evetui-capture-artifact.test.mjs", + "contains": [ + "builds TUI JSON grid capture artifact", + "builds TUI JSON grid capture artifact from a generic world provider", + "gamecult.eve.tui_grid.v1", + "artifacts/evetui-capture/smoke/tui-grid.json", + "eve.world-smoke.commands" + ] + }, + { + "path": "tools/evetui/evetui-release-contract.mjs", + "contains": [ + "buildTuiReleaseRequest", + "gamecult.eve.runtime_release_request.v1", + "evetui-release", + "writeReleaseRequest" + ] + }, + { + "path": "tools/evetui/evetui-release-contract.test.mjs", + "contains": [ + "builds EveTui release request from package manifest", + "evetui-v0.1.0", + "artifacts/evetui-release/0.1.0/eve-tui-0.1.0.tgz" + ] + } + ], + "capture": { + "status": "json-grid", + "note": "TUI attaches a provider-advertisement-backed gamecult.eve.tui_grid.v1 JSON capture artifact; packaged runtime ownership remains an EveTui split blocker.", + "artifacts": [ + { + "kind": "json-grid", + "schema": "gamecult.eve.tui_grid.v1", + "path": "artifacts/evetui-capture/latest/tui-grid.json", + "providerId": "aetheria", + "surfaceId": "aetheria.daemon.game", + "sourceSurfacePath": "../Aetheria/conformance/eve/aetheria-world-surface.json", + "requestPath": "artifacts/evetui-capture/latest/capture-request.json" + }, + { + "kind": "json-grid", + "schema": "gamecult.eve.tui_grid.v1", + "path": "artifacts/evetui-capture/latest/eve-world-smoke-tui-grid.json", + "providerId": "eve.world-smoke", + "surfaceId": "eve.world-smoke.surface", + "sourceSurfacePath": "web/fixtures/eve-world-smoke-surface.json", + "requestPath": "artifacts/evetui-capture/latest/eve-world-smoke-capture-request.json" + } + ] + }, + "sourceRoot": "../Eve" + }, + { + "id": "unity-scene", + "title": "Unity / Scene", + "kind": "active", + "ownerRepo": "EveUnity", + "repoRole": "runtime-target", + "splitTarget": "EveUnity", + "graduationTrigger": "Graduated: EveUnity owns the provider-agnostic Unity scene package, release lifecycle, runtime tests, and generic PlayMode capture. The Aetheria daemon product witness remains active lifecycle work.", + "capabilityManifest": { + "schemaPath": "schemas/gamecult.eve.runtime_capability.v1.schema.json", + "manifestPath": "packages/org.gamecult.eve.unity-scene/eve-runtime-capability.json" + }, + "requiredFixtures": [ + "aetheria-world", + "eve-world-smoke" + ], + "pluginFixtures": [ + "sai-vn" + ], + "supportedFeatures": [ + "providerAdvertisements", + "commandTransport", + "providerSurfaceSession", + "providerSurfaceSource", + "providerSurfaceDocumentSource", + "liveProviderTransportBridge", + "livePlayableWorldClient", + "providerCommandReceipts", + "providerAssetManifestDocumentSource", + "playableWorldClientHost", + "playableWorldClientBootstrap", + "playableWorldInputDriver", + "playableWorldCameraRig", + "sceneGraphProjection", + "playableWorldProjection", + "playableWorldRuntimeHost", + "playableWorldScenePresentation", + "unityGameObjectSceneSink", + "providerAssetManifestResolution", + "providerAssetManifestSource", + "embeddedDocuments" + ], + "supportedPlugins": [ + { + "pluginId": "sai.vn", + "capabilities": [ + "vn.stage", + "story.choose", + "story.continue", + "story.jump" + ], + "projectionAdapter": "SaiVisualNovelUnitySceneProjectionAdapter" + }, + { + "pluginId": "norn.graph", + "capabilities": [ + "embed.norn" + ], + "projectionAdapter": "NornGraphUnitySceneProjectionAdapter" + }, + { + "pluginId": "tex.math", + "capabilities": [ + "embed.tex", + "tex.inline", + "tex.block", + "tex.scene-placement" + ], + "projectionAdapter": "TeXMathUnitySceneProjectionAdapter" + } + ], + "unsupportedPlugins": [ + + ], + "worldSurfaceLowering": [ + { + "targetId": "unity-scene", + "surfaceKinds": [ + "interactive-world" + ], + "projectionKinds": [ + "provider-authored-world-surface" + ], + "supportLevel": "unity-playable-world-scene-command-surface", + "ownership": "runtime-lowers-provider-world-surface-without-owning-world-state", + "evidencePaths": [ + "packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneClientSession.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneProviderConnection.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneLiveProviderBridge.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldLiveClient.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldRuntime.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldClientHost.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldClientBootstrap.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldInputDriver.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldCameraRig.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityProviderRefreshSource.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldPresenter.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityGameObjectPlayableWorldSceneSink.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldAssetManifest.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/SaiVisualNovelUnitySceneProjectionAdapter.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/NornGraphUnitySceneProjectionAdapter.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/TeXMathUnitySceneProjectionAdapter.cs", + "packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs" + ] + } + ], + "expectedPaths": [ + "packages/org.gamecult.eve.unity-scene/package.json", + "packages/org.gamecult.eve.unity-scene/eve-runtime-capability.json", + "packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneClientSession.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneProviderConnection.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneLiveProviderBridge.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldLiveClient.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldRuntime.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldClientHost.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldClientBootstrap.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldInputDriver.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldCameraRig.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityProviderRefreshSource.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldPresenter.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityGameObjectPlayableWorldSceneSink.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldAssetManifest.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/SaiVisualNovelUnitySceneProjectionAdapter.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/NornGraphUnitySceneProjectionAdapter.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/TeXMathUnitySceneProjectionAdapter.cs", + "packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs", + "packages/org.gamecult.eve.unity-scene/Runtime/GameCult.Eve.UnityScene.asmdef", + "scripts/pack.ps1", + "scripts/build-release-request.ps1", + "packages/org.gamecult.eve.unity-scene/Tests/Editor/GameCult.Eve.UnityScene.Tests.asmdef", + "scripts/run-package-tests.ps1", + "scripts/build-capture-request.ps1" + ], + "expectedSourceSymbols": [ + { + "path": "packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs", + "contains": [ + "EveUnitySceneSurfaceLowerer", + "EveUnitySceneProviderSurfaceAdvertisement", + "WorldInteraction", + "CommandBoundary", + "ReceiptSchema", + "EveSurfaceCommandRequest", + "BuildSceneGraph", + "BuildPlayableWorld", + "EveUnityPlayableWorldProjection", + "EveUnityPlayableWorldEntity", + "BuildEmbeddedDocumentSlots", + "BuildPluginProjection", + "SaiVisualNovelUnitySceneProjectionAdapter", + "TeXMathUnitySceneProjectionAdapter", + "EveUnitySceneEmbeddedDocumentSlot", + "EveUnityScenePluginProjection", + "EveUnitySceneNode", + "SceneObjectKind", + "playable-world-root", + "unity-scene" + ] + }, + { + "path": "packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneClientSession.cs", + "contains": [ + "EveUnitySceneClientSession", + "EveUnitySceneProviderSurfaceSnapshot", + "ActiveVersion", + "Connect", + "ApplySnapshot", + "CreateMoveIntent", + "CreateFocusIntent", + "CreateTargetIntent", + "CreateActionIntent", + "CreatePlayableWorldIntent", + "commandId", + "targetPosition", + "unity-scene" + ] + }, + { + "path": "packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneProviderConnection.cs", + "contains": [ + "IEveUnitySceneProviderSurfaceSource", + "IEveUnitySceneCommandSink", + "IEveUnitySceneProviderSurfaceDocumentSource", + "EveUnitySceneProviderSurfaceDocument", + "EveUnitySceneProviderSurfaceDocumentSource", + "ToSnapshot", + "DocumentAvailable", + "EveUnitySceneProviderConnection", + "SnapshotAvailable", + "CurrentSnapshot", + "Connect", + "Refresh", + "SubmitMoveIntent", + "SubmitFocusIntent", + "SubmitTargetIntent", + "SubmitActionIntent", + "Disconnect" + ] + }, + { + "path": "packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneLiveProviderBridge.cs", + "contains": [ + "IEveUnitySceneLiveProviderTransport", + "EveUnitySceneLiveProviderTransportBehaviour", + "EveUnitySceneLiveProviderBridgeComponent", + "EveUnitySceneLiveProviderBridge", + "IEveUnitySceneProviderSurfaceDocumentSource", + "IEveUnityPlayableWorldAssetManifestDocumentSource", + "IEveUnitySceneCommandSink", + "IEveUnitySceneCommandReceiptSource", + "IEveUnityProviderRefreshSource", + "SubmitCommand", + "CommandReceiptAvailable", + "transportBehaviour" + ] + }, + { + "path": "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldLiveClient.cs", + "contains": [ + "EveUnityPlayableWorldLiveClient", + "IEveUnitySceneCommandReceiptSource", + "EveUnitySceneCommandReceipt", + "EveUnitySceneProviderConnection", + "EveUnityPlayableWorldPresenter", + "ActiveWorld", + "LastPresentation", + "LastReceipt", + "ReceiptAvailable", + "Connect", + "Refresh", + "SubmitMoveIntent", + "SubmitFocusIntent", + "SubmitTargetIntent", + "SubmitActionIntent", + "ProjectionUpdated", + "IsProviderOwned", + "Disconnect" + ] + }, + { + "path": "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldRuntime.cs", + "contains": [ + "EveUnityPlayableWorldRuntime", + "CreateForGameObjectScene", + "EveUnityLivePlayableWorldAssetProvider", + "IEveUnitySceneProviderSurfaceDocumentSource", + "IEveUnitySceneCommandSink", + "IEveUnityPlayableWorldAssetManifestDocumentSource", + "IEveUnitySceneCommandReceiptSource", + "EveUnityPlayableWorldAssetManifestCache", + "AssetManifests", + "ActiveWorld", + "SubmitMoveIntent", + "SubmitMoveVectorIntent", + "SubmitFocusIntent", + "SubmitTargetIntent", + "SubmitActionIntent", + "Connect", + "Refresh", + "Disconnect" + ] + }, + { + "path": "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldClientHost.cs", + "contains": [ + "EveUnityPlayableWorldClientHost", + "MonoBehaviour", + "providerSurfaceDocuments", + "IEveUnitySceneProviderSurfaceDocumentSource", + "IEveUnitySceneCommandSink", + "IEveUnityPlayableWorldAssetManifestDocumentSource", + "IEveUnitySceneCommandReceiptSource", + "IEveUnityGameObjectAssetProvider", + "IEveUnityProviderRefreshSource", + "Configure", + "ConnectOnEnable", + "CreateForGameObjectScene", + "Connect", + "Refresh", + "SceneRoot", + "SubmitMoveIntent", + "SubmitMoveVectorIntent", + "SubmitFocusIntent", + "SubmitTargetIntent", + "SubmitActionIntent", + "Disconnect" + ] + }, + { + "path": "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldClientBootstrap.cs", + "contains": [ + "EveUnityPlayableWorldClientBootstrap", + "ConfigureProvider", + "Mount", + "ResolveHost", + "ResolveSceneRoot", + "ResolveCameraTransform", + "EveUnityPlayableWorldClientHost", + "EveUnityPlayableWorldInputDriver", + "EveUnityPlayableWorldCameraRig", + "IEveUnitySceneProviderSurfaceDocumentSource", + "IEveUnitySceneCommandSink", + "IEveUnityPlayableWorldAssetManifestDocumentSource", + "IEveUnitySceneCommandReceiptSource", + "IEveUnityGameObjectAssetProvider" + ] + }, + { + "path": "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldInputDriver.cs", + "contains": [ + "EveUnityPlayableWorldInputDriver", + "EveUnityPlayableWorldMoveVector", + "FromCameraRelativeInput", + "SubmitMoveVectorInput", + "SubmitPrimaryAction", + "Input.GetAxisRaw", + "Input.GetButtonDown", + "directionX", + "directionY", + "scalarValue", + "SubmitMoveVectorIntent" + ] + }, + { + "path": "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldCameraRig.cs", + "contains": [ + "EveUnityPlayableWorldCameraRig", + "ApplyRig", + "arpg.orbital-follow.v1", + "EveUnityPlayableWorldEntityMarker", + "SceneRoot", + "LookAt" + ] + }, + { + "path": "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldPresenter.cs", + "contains": [ + "IEveUnityPlayableWorldSceneSink", + "IEveUnityPlayableWorldAssetResolver", + "EveUnityPlayableWorldPresenter", + "EveUnityPlayableWorldAssetBinding", + "EveUnityPlayableWorldPresentation", + "EveUnityAssetRefResolver", + "ConfigureWorld", + "UpsertEntity", + "RemoveEntity", + "provider-asset-ref", + "unity-generated-placeholder" + ] + }, + { + "path": "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityGameObjectPlayableWorldSceneSink.cs", + "contains": [ + "EveUnityGameObjectPlayableWorldSceneSink", + "IEveUnityGameObjectAssetProvider", + "EveUnityResourcesAssetProvider", + "EveUnityPlayableWorldEntityMarker", + "GameObject", + "Transform", + "Resources.Load", + "PrimitiveType.Capsule", + "DestroyImmediate", + "localPosition", + "localRotation" + ] + }, + { + "path": "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldAssetManifest.cs", + "contains": [ + "EveUnityPlayableWorldAssetManifest", + "IEveUnityPlayableWorldAssetManifestSource", + "IEveUnityPlayableWorldAssetManifestDocumentSource", + "EveUnityPlayableWorldAssetManifestDocumentSource", + "EveUnityPlayableWorldAssetManifestDocument", + "EveUnityPlayableWorldAssetManifestDocumentEntry", + "FromDocument", + "DocumentAvailable", + "gamecult.eve.unity_playable_world_asset_manifest.v1", + "EveUnityPlayableWorldAssetManifestCache", + "EveUnityPlayableWorldAssetManifestEntry", + "EveUnityManifestGameObjectAssetProvider", + "ManifestAvailable", + "GetForWorld", + "CreateGameObjectAssetProvider", + "ManifestRef", + "ResourcesPath", + "PrefabKey", + "NormalizeResourcesPath", + "ResolvePrefab", + "Resources.Load" + ] + }, + { + "path": "packages/org.gamecult.eve.unity-scene/Runtime/SaiVisualNovelUnitySceneProjectionAdapter.cs", + "contains": [ + "SaiVisualNovelUnitySceneProjectionAdapter", + "sai.vn", + "vn.stage", + "story.choose", + "story.continue", + "story.jump", + "gamecult.eve.plugin_abi.v1", + "sidecar-advertised-plugin-abi", + "Sai" + ] + }, + { + "path": "packages/org.gamecult.eve.unity-scene/Runtime/NornGraphUnitySceneProjectionAdapter.cs", + "contains": [ + "NornGraphUnitySceneProjectionAdapter", + "norn.graph", + "embed.norn", + "gamecult.eve.plugin_abi.v1", + "sidecar-advertised-plugin-abi", + "Norn" + ] + }, + { + "path": "packages/org.gamecult.eve.unity-scene/Runtime/TeXMathUnitySceneProjectionAdapter.cs", + "contains": [ + "TeXMathUnitySceneProjectionAdapter", + "tex.math", + "embed.tex", + "tex.scene-placement", + "gamecult.eve.plugin_abi.v1", + "sidecar-advertised-plugin-abi", + "EvePlugins" + ] + }, + { + "path": "packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs", + "contains": [ + "LowerCarriesProviderAdvertisedWorldBoundary", + "LowerBuildsProviderAgnosticSceneGraphFromSurfaceTree", + "LowerExtractsPlayableArpgWorldFromGenericScene3dSurface", + "GenericClientSessionConsumesAetheriaPlayableWorldSnapshotWithoutAetheriaTypes", + "GenericProviderConnectionAppliesLiveSnapshotsAndSubmitsCommandsThroughSink", + "ProviderSurfaceDocumentSourceFeedsPlayableClientWithoutAetheriaTypes", + "PlayableWorldRuntimeComposesProviderDocumentsAssetsReceiptsAndSceneSink", + "LiveProviderBridgeFeedsPlayableWorldRuntimeThroughTransportPorts", + "PlayableWorldClientHostMountsInterfaceProviderWithoutProviderTypes", + "PlayableWorldClientBootstrapWiresGenericUnityClientFromProviderInterfaces", + "PlayableWorldClientHostSubmitsProviderOwnedMoveVector", + "PlayableWorldInputDriverBuildsCameraRelativeMoveVectorWithoutProviderTypes", + "PlayableWorldCameraRigFollowsAdvertisedPlayerEntityWithoutProviderTypes", + "PlayableWorldPresenterInstantiatesUpdatesAndDespawnsProviderEntities", + "LivePlayableWorldClientPresentsProviderSnapshotsAndKeepsCommandsProviderOwned", + "LivePlayableWorldClientRefreshesFromProviderSnapshotAfterReceiptWithoutOwningMovement", + "AssetManifestMapsProviderAssetRefsToUnityLoadKeysWithoutAetheriaTypes", + "AssetManifestCacheTracksPlayableWorldManifestPointerAndLiveUpdates", + "AssetManifestDocumentSourceFeedsCacheWithoutChangingSceneLowering", + "FakeProviderSurfaceSource", + "FakeProviderSurfaceDocumentSource", + "FakeCommandReceiptSource", + "FakeAssetManifestSource", + "FakeAssetManifestDocumentSource", + "FakeLiveProviderTransport", + "FakeCommandSink", + "FakePlayableWorldSceneSink", + "FakePlayableWorldProviderComponent", + "SaiVisualNovelLowersThroughRuntimeProjectionAdapterWithoutOwningStoryState", + "CommandIntentCarriesAdvertisedBoundaryWithoutOwningReceipts", + "pending", + "reconciled", + "world-projection-node", + "playable-world-root", + "playable-world-entity", + "world-field-3d", + "arpg-third-person", + "cultmesh://aetheria/assets/manifest", + "cultmesh://aetheria/eve/surfaces/aetheria.daemon.game", + "aetheria.daemon.move_intent", + "sai-vn-scene-stage", + "norn-graph-scene-projection", + "tex-math-scene-projection", + "norn.map", + "cultmesh://aetheria/norn/map", + "sai.vn", + "norn.graph", + "tex.math", + "sidecar-advertised-plugin-abi", + "aetheria.daemon.commands", + "aetheria.eve_command_acceptance_status.v1" + ] + } + ], + "capture": { + "status": "partial-world-smoke-playmode-capture", + "note": "The owner-controlled generic world-smoke PlayMode PNG passes; the separately running Aetheria daemon capture remains active lifecycle work.", + "artifacts": [ + { + "kind": "png", + "schema": "gamecult.eve.runtime_frame_capture.v1", + "path": "artifacts/playmode/world-smoke.png", + "providerId": "eve.world-smoke", + "surfaceId": "eve.world-smoke.surface" + } + ] + }, + "sourceRoot": "../EveUnity", + "currentHostRepo": "EveUnity", + "lifecycle": { + "release": { + "ownerRepo": "EveUnity", + "status": "owner-repo-upm-artifact", + "releaseContract": { + "ownerRepo": "EveUnity", + "packageName": "org.gamecult.eve.unity-scene", + "packageRoot": "packages/org.gamecult.eve.unity-scene", + "versionSource": "packages/org.gamecult.eve.unity-scene/package.json", + "tagPattern": "eveunity-scene-v{version}", + "artifactKind": "upm-package", + "requestSchema": "gamecult.eve.runtime_release_request.v1", + "requestBuilder": "scripts/build-release-request.ps1", + "artifactBuilder": "scripts/pack.ps1", + "artifactPattern": "artifacts/eveunity-scene-release/{version}/org.gamecult.eve.unity-scene-{version}.tgz", + "publishProof": "Tagged Unity scene package release is created from the EveUnity repository.", + "requiredPackageDependencies": [ + { + "packageName": "org.gamecult.eve.surface", + "version": "0.1.0", + "packageManager": "upm", + "ownerRepo": "EveUnity", + "purpose": "Shared Unity DTOs for gamecult.eve.surface.v1 documents." + } + ] + }, + "evidencePaths": [ + "packages/org.gamecult.eve.unity-scene/package.json", + "packages/org.gamecult.eve.unity-scene/Runtime/GameCult.Eve.UnityScene.asmdef", + "scripts/pack.ps1", + "scripts/build-release-request.ps1" + ], + "pendingProofs": [ + "Cut and publish the first tagged EveUnity package release." + ] + }, + "test": { + "ownerRepo": "EveUnity", + "status": "unity-scene-editmode-batchmode", + "testContract": { + "ownerRepo": "EveUnity", + "runnerKind": "unity-editmode-batchmode", + "runnerScript": "scripts/run-package-tests.ps1", + "consumerProject": "E:\\Projects\\Aetheria", + "packageName": "org.gamecult.eve.unity-scene", + "testAssembly": "GameCult.Eve.UnityScene.Tests", + "testPlatform": "EditMode", + "resultsArtifact": "artifacts/aetheria-unity-scene-editmode/{stamp}/unity-editmode-results.xml", + "logArtifact": "artifacts/aetheria-unity-scene-editmode/{stamp}/unity-editmode.log", + "manifestMutation": "Temporarily adds org.gamecult.eve.unity-scene to Aetheria Unity testables, runs GameCult.Eve.UnityScene.Tests, and restores Packages/manifest.json." + }, + "evidencePaths": [ + "packages/org.gamecult.eve.unity-scene/Tests/Editor/GameCult.Eve.UnityScene.Tests.asmdef", + "packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs", + "scripts/run-package-tests.ps1" + ], + "pendingProofs": [ + "Move the Aetheria consumer scenario into a provider-owned conformance pack that installs EveUnity from its owner repository." + ] + }, + "capture": { + "ownerRepo": "EveUnity", + "status": "partial-world-smoke-playmode-capture", + "captureContract": { + "ownerRepo": "EveUnity", + "runtimeId": "unity-scene", + "targetId": "unity-scene", + "requestSchema": "gamecult.eve.runtime_capture_request.v1", + "requestBuilder": "scripts/build-capture-request.ps1", + "advertisementPath": "Aetheria:conformance/eve/aetheria.provider-advertisement.json", + "captureKind": "unity-scene-frame-png", + "artifactKind": "png", + "artifactPattern": "artifacts/eveunity-scene-capture/{stamp}/unity-scene.png", + "conformanceAttachment": "runtime.captureArtifacts[]", + "requiredProvider": "aetheria", + "requiredSurface": "aetheria.daemon.game", + "additionalProviderSurfaces": [ + { + "providerId": "eve.world-smoke", + "surfaceId": "eve.world-smoke.surface", + "purpose": "Generic non-product interactive-world capture control." + } + ], + "authority": "Capture observes Unity scene projection only; provider state, command acceptance, and receipts stay provider-owned.", + "publishProof": "Unity scene screenshot or frame-capture PNG artifact is attached to the runtime conformance report from EveUnity." + }, + "evidencePaths": [ + "scripts/build-capture-request.ps1", + "scripts/run-playmode-capture.ps1", + "TestProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs", + "artifacts/playmode/world-smoke.png" + ], + "pendingProofs": [ + "Attach the Aetheria daemon PlayMode PNG; the generic world-smoke control is captured by EveUnity." + ] + } + } + }, + { + "id": "web", + "title": "Web reference", + "kind": "active", + "ownerRepo": "Eve", + "repoRole": "core", + "splitTarget": "Eve", + "graduationTrigger": "Stay in Eve while it is the behavior oracle; split only if it becomes a deployable runtime product.", + "capabilityManifest": { + "schemaPath": "schemas/gamecult.eve.runtime_capability.v1.schema.json", + "manifestPath": "web/eve-runtime-capability.json" + }, + "semanticHarness": true, + "requiredFixtures": [ + "embedded-surface", + "eve-world-smoke" + ], + "pluginFixtures": [ + "sai-vn" + ], + "supportedFeatures": [ + "embeddedDocuments", + "providerAdvertisements", + "stateBindings", + "styleTokens", + "authorityWitnesses" + ], + "localProviderCatalog": { + "path": "web/local-provider-catalog.json", + "requiredProviders": [ + "eve.world-smoke", + "repixelizer", + "gamecult.home.vn" + ], + "requiredAdvertisements": [ + "web/fixtures/eve-world-smoke.provider-advertisement.json", + "web/fixtures/repixelizer.provider-advertisement.json", + "web/fixtures/sai-vn.provider-advertisement.json" + ] + }, + "supportedPlugins": [ + { + "pluginId": "sai.vn", + "capabilities": [ + "vn.stage", + "story.choose", + "story.continue", + "story.jump" + ] + }, + { + "pluginId": "norn.graph", + "capabilities": [ + "embed.norn" + ] + }, + { + "pluginId": "tex.math", + "capabilities": [ + "embed.tex" + ] + } + ], + "worldSurfaceLowering": [ + { + "targetId": "web-reference", + "surfaceKinds": [ + "interactive-world", + "interactive-world-editor" + ], + "projectionKinds": [ + "provider-authored-world-surface", + "provider-authored-world-editor-surface" + ], + "supportLevel": "reference-dom-canvas-command-boundary", + "ownership": "runtime-lowers-provider-world-surface-without-owning-world-state", + "evidencePaths": [ + "web/surface.js", + "packages/eve-browser-lowering/src/index.ts", + "web/fixtures/eve-world-smoke.provider-advertisement.json" + ] + } + ], + "expectedSourceSymbols": [ + { + "path": "web/surface.js", + "contains": [ + "loadLocalProviderCatalog", + "loadProviderAdvertisement", + "mergeProviderAdvertisement", + "publishCommandIntent", + "browser.reference" + ] + }, + { + "path": "web/provider-advertisements.mjs", + "contains": [ + "mergeProviderAdvertisement", + "mergeProviderSurface", + "findProviderCatalogEntry" + ] + }, + { + "path": "web/provider-advertisements.test.mjs", + "contains": [ + "preserves Sai required and optional nested plugin requirements from advertisement", + "merges generic world fixture transport with advertised world interaction", + "requiresPlugins", + "worldInteraction", + "optional-nested", + "eve.world-smoke.commands" + ] + }, + { + "path": "web/local-provider-catalog.json", + "contains": [ + "eve-world-smoke.provider-advertisement.json", + "repixelizer.provider-advertisement.json", + "sai-vn.provider-advertisement.json" + ] + }, + { + "path": "packages/eve-browser-lowering/src/index.ts", + "contains": [ + "surface.slot", + "embeddedDocuments", + "cultui-embedded-slot", + "createEveCommandIntent", + "resolveComponentCommandId", + "localAdvertisement", + "activeSurfaceId" + ] + } + ], + "commandTransportSmoke": { + "schema": "gamecult.eve.command_invocation.v1", + "expectedPaths": [ + "packages/eve-browser-lowering/src/index.ts", + "packages/eve-browser-lowering/test/browser-lowering.test.mjs", + "web/surface.js" + ], + "expectedSourceSymbols": [ + { + "path": "packages/eve-browser-lowering/src/index.ts", + "contains": [ + "EveCommandIntent", + "createEveCommandIntent", + "schema: \"gamecult.eve.command_invocation.v1\"", + "clientId", + "commandBoundary", + "receiptSchema", + "activeSurfaceId" + ] + }, + { + "path": "packages/eve-browser-lowering/test/browser-lowering.test.mjs", + "contains": [ + "activeSurfaceId", + "aetheria.daemon.editor", + "aetheria.daemon.editor.commands", + "component.fallback.boundary" + ] + }, + { + "path": "web/surface.js", + "contains": [ + "publishCommandIntent", + "/hermodr/commands/eve", + "createEveCommandIntent", + "browser.reference", + "activeSurfaceId" + ] + } + ] + }, + "lifecycle": { + "release": { + "ownerRepo": "Eve", + "status": "reference-oracle-in-core", + "evidencePaths": [ + "web/index.html", + "web/surface.js", + "web/provider-advertisements.mjs", + "packages/eve-browser-lowering/src/index.ts" + ], + "pendingProofs": [ + "Split to EveWeb only if the browser reference gains independent deployment and release lifecycle." + ] + }, + "test": { + "ownerRepo": "Eve", + "status": "node-tests-and-parity-harness", + "command": "node --test web/eve-dsl.test.mjs \u0026\u0026 npm --prefix packages/eve-browser-lowering test \u0026\u0026 powershell -ExecutionPolicy Bypass -File .\\scripts\\run-parity-harness.ps1", + "evidencePaths": [ + "web/eve-dsl.test.mjs", + "web/provider-advertisements.test.mjs", + "packages/eve-browser-lowering/test/browser-lowering.test.mjs", + "tools/parity/parity-manifest.json", + "scripts/run-parity-harness.ps1" + ], + "pendingProofs": [ + + ] + }, + "capture": { + "ownerRepo": "Eve", + "status": "chrome-headless", + "evidencePaths": [ + "scripts/capture-web-reference.ps1", + "scripts/run-parity-smoke.ps1", + "tools/parity/render-contact-sheet.py" + ], + "pendingProofs": [ + + ] + } + }, + "capture": { + "status": "chrome-headless", + "note": "Chrome headless captures phone, tablet, and desktop PNGs through the provider query parameter.", + "layoutProbes": [ + { + "fixtureId": "embedded-surface", + "providerId": "gamecult.eve.embedded-demo", + "surfaceId": "gamecult.eve.embedded-demo", + "schema": "gamecult.eve.web_layout_probe.v1", + "path": "artifacts/web-reference-layout-probe/latest/embedded-surface.json", + "runner": "scripts/run-web-reference-layout-probe-smoke.ps1" + }, + { + "fixtureId": "sai-vn", + "providerId": "gamecult.home.vn", + "surfaceId": "sai.visual_novel.surface", + "schema": "gamecult.eve.web_layout_probe.v1", + "path": "artifacts/web-reference-layout-probe/latest/sai-vn.json", + "runner": "scripts/run-web-reference-layout-probe-smoke.ps1" + }, + { + "fixtureId": "eve-world-smoke", + "providerId": "eve.world-smoke", + "surfaceId": "eve.world-smoke.surface", + "schema": "gamecult.eve.web_layout_probe.v1", + "path": "artifacts/web-reference-layout-probe/latest/eve-world-smoke.json", + "runner": "scripts/run-web-reference-layout-probe-smoke.ps1" + } + ] + }, + "sourceRoot": "../Eve" + }, + { + "id": "windows-flutter", + "title": "Windows / Flutter", + "kind": "active", + "ownerRepo": "EveFlutter", + "repoRole": "runtime-target", + "splitTarget": "EveFlutter", + "currentHostRepo": "EveFlutter", + "sourceRoot": "../EveFlutter", + "graduationTrigger": "Graduated: EveFlutter owns Flutter projection, provider selection, command intents, platform builds, tests, and captures.", + "lifecycleManifest": { + "schemaPath": "schemas/gamecult.eve.runtime_lifecycle.v1.schema.json", + "manifestPath": "eveflutter-lifecycle.json" + }, + "requiredFixtures": [ + "embedded-surface" + ], + "pluginFixtures": [ + "sai-vn" + ], + "supportedFeatures": [ + "embeddedDocuments" + ], + "supportedPlugins": [ + { + "pluginId": "sai.vn", + "capabilities": [ + "vn.stage", + "story.choose", + "story.continue", + "story.jump" + ] + }, + { + "pluginId": "norn.graph", + "capabilities": [ + "embed.norn" + ] + }, + { + "pluginId": "tex.math", + "capabilities": [ + "embed.tex" + ] + } + ], + "expectedPaths": [ + "lib/main.dart", + "scripts/capture-flutter-parity.ps1" + ], + "expectedSourceSymbols": [ + { + "path": "lib/main.dart", + "contains": [ + "embeddedDocuments", + "surface.slot", + "EveEmbeddedDocumentSlot", + "EveProviderCatalog", + "EveProviderPicker" + ] + } + ], + "commandTransportSmoke": { + "schema": "gamecult.eve.command_invocation.v1", + "expectedPaths": [ + "lib/main.dart", + "test/parity_smoke_test.dart" + ], + "expectedSourceSymbols": [ + { + "path": "lib/main.dart", + "contains": [ + "EveCommandIntent", + "createEveCommandIntent", + "resolveComponentCommandId", + "commandSink", + "gamecult.eve.command_invocation.v1", + "flutter-parity" + ] + }, + { + "path": "test/parity_smoke_test.dart", + "contains": [ + "emits a provider-routed Eve command intent", + "world.example.commands", + "world.example.focus" + ] + } + ] + }, + "capture": { + "status": "golden", + "note": "Uses the Flutter parity golden smoke on the Windows host." + } + }, + { + "id": "linux-flutter", + "title": "Linux / Flutter", + "kind": "active", + "ownerRepo": "EveFlutter", + "repoRole": "runtime-target", + "splitTarget": "EveFlutter", + "currentHostRepo": "EveFlutter", + "sourceRoot": "../EveFlutter", + "graduationTrigger": "Graduated: EveFlutter owns Linux build and capture lifecycle.", + "lifecycleManifest": { + "schemaPath": "schemas/gamecult.eve.runtime_lifecycle.v1.schema.json", + "manifestPath": "eveflutter-lifecycle.json" + }, + "requiredFixtures": [ + "embedded-surface" + ], + "pluginFixtures": [ + "sai-vn" + ], + "supportedFeatures": [ + "embeddedDocuments" + ], + "supportedPlugins": [ + { + "pluginId": "sai.vn", + "capabilities": [ + "vn.stage", + "story.choose", + "story.continue", + "story.jump" + ] + }, + { + "pluginId": "norn.graph", + "capabilities": [ + "embed.norn" + ] + }, + { + "pluginId": "tex.math", + "capabilities": [ + "embed.tex" + ] + } + ], + "expectedPaths": [ + "linux", + "scripts/capture-linux-flutter-parity.ps1" + ], + "expectedSourceSymbols": [ + { + "path": "lib/main.dart", + "contains": [ + "embeddedDocuments", + "surface.slot", + "EveEmbeddedDocumentSlot", + "EveProviderCatalog", + "EveProviderPicker" + ] + } + ], + "commandTransportSmoke": { + "schema": "gamecult.eve.command_invocation.v1", + "expectedPaths": [ + "lib/main.dart", + "test/parity_smoke_test.dart" + ], + "expectedSourceSymbols": [ + { + "path": "lib/main.dart", + "contains": [ + "EveCommandIntent", + "createEveCommandIntent", + "resolveComponentCommandId", + "commandSink", + "gamecult.eve.command_invocation.v1", + "flutter-parity" + ] + }, + { + "path": "test/parity_smoke_test.dart", + "contains": [ + "emits a provider-routed Eve command intent", + "world.example.commands", + "world.example.focus" + ] + } + ] + }, + "capture": { + "status": "ssh-golden", + "note": "Runs the Flutter parity golden smoke on Nightwing over SSH and pulls back phone, tablet, and desktop PNGs." + } + }, + { + "id": "ios-uikit", + "title": "iOS / UIKit", + "kind": "active", + "ownerRepo": "Eve", + "repoRole": "runtime-incubating", + "splitTarget": "EveIOS", + "graduationTrigger": "Split when UIKit renderer has generic surface lowering, sensor edge release flow, and device CI independent of Eve core.", + "requiredFixtures": [ + "embedded-surface" + ], + "pluginFixtures": [ + + ], + "supportedFeatures": [ + "embeddedDocuments" + ], + "unsupportedPlugins": [ + + ], + "expectedPaths": [ + "Sources/EVEViewController.mm", + "scripts/capture-eve-screenshot.ps1" + ], + "expectedSourceSymbols": [ + { + "path": "Sources/EVEViewController.mm", + "contains": [ + "surface.slot", + "embeddedDocuments", + "accessibilityIdentifier" + ] + } + ], + "capture": { + "status": "ssh-png", + "note": "SSH capture script foregrounds EveCanvas and requests latest-screenshot.png." + }, + "sourceRoot": "../Eve" + }, + { + "id": "android-flutter", + "title": "Android / Flutter", + "kind": "active", + "ownerRepo": "EveFlutter", + "repoRole": "runtime-target", + "splitTarget": "EveFlutter", + "currentHostRepo": "EveFlutter", + "sourceRoot": "../EveFlutter", + "graduationTrigger": "Graduated: EveFlutter owns Android APK, discovery, command, plugin projection, and capture lifecycle.", + "lifecycleManifest": { + "schemaPath": "schemas/gamecult.eve.runtime_lifecycle.v1.schema.json", + "manifestPath": "eveflutter-lifecycle.json" + }, + "requiredFixtures": [ + "embedded-surface" + ], + "pluginFixtures": [ + "sai-vn" + ], + "supportedFeatures": [ + "embeddedDocuments" + ], + "supportedPlugins": [ + { + "pluginId": "sai.vn", + "capabilities": [ + "vn.stage", + "story.choose", + "story.continue", + "story.jump" + ] + }, + { + "pluginId": "norn.graph", + "capabilities": [ + "embed.norn" + ] + }, + { + "pluginId": "tex.math", + "capabilities": [ + "embed.tex" + ] + } + ], + "expectedPaths": [ + "lib/main.dart", + "scripts/capture-android-flutter-parity.ps1" + ], + "expectedSourceSymbols": [ + { + "path": "lib/main.dart", + "contains": [ + "embeddedDocuments", + "surface.slot", + "EveEmbeddedDocumentSlot", + "EveProviderCatalog", + "EveProviderPicker" + ] + } + ], + "commandTransportSmoke": { + "schema": "gamecult.eve.command_invocation.v1", + "expectedPaths": [ + "lib/main.dart", + "test/parity_smoke_test.dart" + ], + "expectedSourceSymbols": [ + { + "path": "lib/main.dart", + "contains": [ + "EveCommandIntent", + "createEveCommandIntent", + "resolveComponentCommandId", + "commandSink", + "gamecult.eve.command_invocation.v1", + "flutter-parity" + ] + }, + { + "path": "test/parity_smoke_test.dart", + "contains": [ + "emits a provider-routed Eve command intent", + "world.example.commands", + "world.example.focus" + ] + } + ] + }, + "capture": { + "status": "adb-png", + "note": "Builds the shared Flutter parity APK, installs it on Periwinkle, and pulls responsive plus physical-panel screencaps through adb." + } + }, + { + "id": "android-kotlin", + "title": "Android / Kotlin device edge", + "kind": "active", + "ownerRepo": "Eve", + "repoRole": "runtime-incubating", + "splitTarget": "EveAndroid", + "graduationTrigger": "Split when Kotlin device edge has generic surface traversal, sensor release flow, and Android-specific CI.", + "requiredFixtures": [ + "embedded-surface" + ], + "pluginFixtures": [ + + ], + "supportedFeatures": [ + "cultmesh-dashboard-documents", + "sensor-uplink", + "embeddedDocuments", + "styleTokens" + ], + "unsupportedPlugins": [ + + ], + "expectedPaths": [ + "android/app/src/main/java/org/gamecult/eve/MainActivity.kt", + "scripts/build-android.ps1" + ], + "expectedSourceSymbols": [ + { + "path": "android/app/src/main/java/org/gamecult/eve/MainActivity.kt", + "contains": [ + "surface.slot", + "embeddedDocuments", + "contentDescription", + "tokenLabel", + "tokenTextSize", + "typefaceForToken", + "fontBody", + "fontTitle", + "fontMono" + ] + } + ], + "capture": { + "status": "manual", + "note": "Native Kotlin preserves CultUI embedded document slots in its lightweight dashboard renderer; full responsive GUI capture still belongs to Android Flutter." + }, + "sourceRoot": "../Eve" + }, + { + "id": "unity-uitoolkit", + "title": "Unity / UI Toolkit", + "kind": "active", + "ownerRepo": "EveUnity", + "repoRole": "runtime-target", + "splitTarget": "EveUnity", + "graduationTrigger": "Graduated: EveUnity owns the UI Toolkit package, plugin projection adapters, package build, and Unity tests.", + "sourceRoot": "../EveUnity", + "currentHostRepo": "EveUnity", + "capabilityManifest": { + "schemaPath": "schemas/gamecult.eve.runtime_capability.v1.schema.json", + "manifestPath": "packages/org.gamecult.eve.unity-uitoolkit/eve-runtime-capability.json" + }, + "requiredFixtures": [ + "embedded-surface", + "eve-world-smoke" + ], + "pluginFixtures": [ + "sai-vn" + ], + "supportedFeatures": [ + "embeddedDocuments" + ], + "supportedPlugins": [ + { + "pluginId": "sai.vn", + "capabilities": [ + "vn.stage", + "story.choose", + "story.continue", + "story.jump" + ] + }, + { + "pluginId": "norn.graph", + "capabilities": [ + "embed.norn" + ] + }, + { + "pluginId": "tex.math", + "capabilities": [ + "embed.tex", + "tex.inline", + "tex.block" + ] + } + ], + "unsupportedPlugins": [ + + ], + "worldSurfaceLowering": [ + { + "targetId": "unity-uitoolkit", + "surfaceKinds": [ + "interactive-world", + "interactive-world-editor" + ], + "projectionKinds": [ + "provider-authored-world-surface", + "provider-authored-world-editor-surface" + ], + "supportLevel": "ui-toolkit-semantic-command-surface", + "ownership": "runtime-lowers-provider-world-surface-without-owning-world-state", + "evidencePaths": [ + "packages/org.gamecult.eve.unity-uitoolkit/Runtime/EveUiToolkitSurfaceLowerer.cs", + "packages/org.gamecult.eve.unity-uitoolkit/Tests/Editor/EveUiToolkitSurfaceLowererTests.cs", + "../Aetheria/conformance/eve/aetheria.provider-advertisement.json", + "../Eve/web/fixtures/eve-world-smoke.provider-advertisement.json" + ] + } + ], + "expectedPaths": [ + "packages/org.gamecult.eve.unity-uitoolkit/Runtime/EveUiToolkitSurfaceLowerer.cs", + "packages/org.gamecult.eve.unity-uitoolkit/Runtime/IEveUiToolkitPluginProjectionAdapter.cs", + "packages/org.gamecult.eve.unity-uitoolkit/Runtime/SaiVisualNovelUiToolkitProjectionAdapter.cs", + "packages/org.gamecult.eve.unity-uitoolkit/Runtime/NornGraphUiToolkitProjectionAdapter.cs", + "packages/org.gamecult.eve.unity-uitoolkit/Runtime/TeXMathUiToolkitProjectionAdapter.cs", + "packages/org.gamecult.eve.unity-uitoolkit/Tests/Editor/GameCult.Eve.UnityUIToolkit.Tests.asmdef", + "packages/org.gamecult.eve.unity-uitoolkit/Tests/Editor/EveUiToolkitSurfaceLowererTests.cs", + "packages/org.gamecult.eve.surface/Runtime/EveSurfaceDocument.cs", + "scripts/pack-uitoolkit.ps1", + "scripts/run-uitoolkit-tests.ps1" + ], + "expectedSourceSymbols": [ + { + "path": "packages/org.gamecult.eve.unity-uitoolkit/Runtime/EveUiToolkitSurfaceLowerer.cs", + "contains": [ + "LowerEmbeddedDocument(", + "EmbeddedDocuments", + "eve-embedded-document", + "FindPluginProjectionAdapter", + "projectionAdapter.Lower" + ] + }, + { + "path": "packages/org.gamecult.eve.unity-uitoolkit/Runtime/IEveUiToolkitPluginProjectionAdapter.cs", + "contains": [ + "PluginId", + "Capabilities", + "CanLower", + "Lower(" + ] + }, + { + "path": "packages/org.gamecult.eve.unity-uitoolkit/Runtime/SaiVisualNovelUiToolkitProjectionAdapter.cs", + "contains": [ + "sai.vn", + "vn.stage", + "story.choose", + "story.continue", + "story.jump", + "StoryButton", + "EmitStoryCommand", + "EveSurfaceCommandRequest", + "sai-vn-stage", + "sai-vn-dialogue", + "sai-vn-actions" + ] + }, + { + "path": "packages/org.gamecult.eve.unity-uitoolkit/Runtime/NornGraphUiToolkitProjectionAdapter.cs", + "contains": [ + "norn.graph", + "embed.norn", + "NornGraphUiToolkitProjectionAdapter", + "norn-graph", + "EmitGraphCommand", + "EveSurfaceCommandRequest" + ] + }, + { + "path": "packages/org.gamecult.eve.unity-uitoolkit/Runtime/TeXMathUiToolkitProjectionAdapter.cs", + "contains": [ + "tex.math", + "embed.tex", + "TeXMathUiToolkitProjectionAdapter", + "tex-math", + "tex-math-source-fallback" + ] + }, + { + "path": "packages/org.gamecult.eve.unity-uitoolkit/Tests/Editor/EveUiToolkitSurfaceLowererTests.cs", + "contains": [ + "FindPluginProjectionAdapter", + "SaiStageLowersThroughRuntimeProjectionAdapter", + "NornGraphLowersThroughRuntimeProjectionAdapter", + "TeXMathLowersThroughRuntimeProjectionAdapterAsSourceFallback", + "EmbeddedDocumentSlotMountsResolverBackedChildSurface", + "eve-plugin-projection" + ] + } + ], + "commandTransportSmoke": { + "schema": "gamecult.eve.command_invocation.v1", + "expectedPaths": [ + "../EveUnity/packages/org.gamecult.eve.surface/Runtime/EveSurfaceDocument.cs", + "../EveUnity/packages/org.gamecult.eve.unity-uitoolkit/Runtime/EveUiToolkitSurfaceLowerer.cs" + ], + "expectedSourceSymbols": [ + { + "path": "../EveUnity/packages/org.gamecult.eve.surface/Runtime/EveSurfaceDocument.cs", + "contains": [ + "EveSurfaceCommandRequest", + "SchemaId", + "gamecult.eve.command_invocation.v1", + "ClientId" + ] + }, + { + "path": "../EveUnity/packages/org.gamecult.eve.unity-uitoolkit/Runtime/EveUiToolkitSurfaceLowerer.cs", + "contains": [ + "commandSink", + "EmitCommand", + "ResolveOperation", + "unity-uitoolkit" + ] + } + ] + }, + "lifecycle": { + "release": { + "ownerRepo": "EveUnity", + "status": "upm-package-artifact-smoke", + "releaseContract": { + "ownerRepo": "EveUnity", + "packageName": "org.gamecult.eve.unity-uitoolkit", + "packageRoot": "packages/org.gamecult.eve.unity-uitoolkit", + "versionSource": "packages/org.gamecult.eve.unity-uitoolkit/package.json", + "tagPattern": "eveunity-uitoolkit-v{version}", + "artifactKind": "upm-package", + "requestSchema": "gamecult.eve.runtime_release_request.v1", + "requestBuilder": "scripts/pack-uitoolkit.ps1", + "artifactBuilder": "scripts/pack-uitoolkit.ps1", + "artifactPattern": "artifacts/eveunity-uitoolkit-release/{version}/org.gamecult.eve.unity-uitoolkit-{version}.tgz", + "publishProof": "Tagged UPM release is created from the EveUnity repo and references this package root.", + "requiredPackageDependencies": [ + { + "packageName": "org.gamecult.eve.surface", + "version": "0.1.0", + "packageManager": "upm", + "ownerRepo": "EveUnity", + "purpose": "Shared Unity DTOs for gamecult.eve.surface.v1 documents." + } + ] + }, + "evidencePaths": [ + "packages/org.gamecult.eve.unity-uitoolkit/package.json", + "packages/org.gamecult.eve.unity-uitoolkit/README.md", + "scripts/pack-uitoolkit.ps1" + ], + "pendingProofs": [ + "Tagged UPM release cut from EveUnity rather than Eve incubation." + ] + }, + "test": { + "ownerRepo": "EveUnity", + "status": "batchmode-editmode-tests-and-consumer-build-smoke", + "command": "powershell -ExecutionPolicy Bypass -File .\\scripts\\run-uitoolkit-tests.ps1", + "testContract": { + "ownerRepo": "EveUnity", + "runnerKind": "unity-editmode-batchmode", + "runnerScript": "scripts/run-uitoolkit-tests.ps1", + "consumerProject": "E:\\Projects\\Aetheria", + "unityExeDefault": "C:\\Program Files\\Unity\\Hub\\Editor\\6000.4.2f1\\Editor\\Unity.exe", + "packageName": "org.gamecult.eve.unity-uitoolkit", + "testAssembly": "GameCult.Eve.UnityUIToolkit.Tests", + "testPlatform": "EditMode", + "resultsArtifact": "artifacts/aetheria-unity-editmode/{stamp}/unity-editmode-results.xml", + "logArtifact": "artifacts/aetheria-unity-editmode/{stamp}/unity-editmode.log", + "manifestMutation": "Temporarily adds org.gamecult.eve.unity-uitoolkit to Aetheria Packages/manifest.json testables and restores the manifest after the run.", + "managedAssemblyDependencies": [ + { + "assemblyName": "GameCult.Caching.dll", + "packageId": "GameCult.Caching", + "packageManager": "nuget", + "ownerRepo": "CultLib", + "resolutionMode": "Unity precompiled reference during EveUnity incubation.", + "dependencyContract": { + "sourceAuthority": "CultLib", + "packageSource": "nuget", + "packageId": "GameCult.Caching", + "assemblyName": "GameCult.Caching.dll", + "versionPolicy": "CultLib-owned package version stream.", + "unityResolution": "EveUnity consumes this as a Unity precompiled reference during incubation.", + "handoffRequirement": "EveUnity split must resolve this through CultLib\u0027s NuGet/package feed or a documented Unity import without vendoring CultLib ownership into Eve." + } + }, + { + "assemblyName": "GameCult.Mesh.dll", + "packageId": "GameCult.Mesh", + "packageManager": "nuget", + "ownerRepo": "CultLib", + "resolutionMode": "Unity precompiled reference during EveUnity incubation.", + "dependencyContract": { + "sourceAuthority": "CultLib", + "packageSource": "nuget", + "packageId": "GameCult.Mesh", + "assemblyName": "GameCult.Mesh.dll", + "versionPolicy": "CultLib-owned package version stream.", + "unityResolution": "EveUnity consumes this as a Unity precompiled reference during incubation.", + "handoffRequirement": "EveUnity split must resolve this through CultLib\u0027s NuGet/package feed or a documented Unity import without vendoring CultLib ownership into Eve." + } + }, + { + "assemblyName": "MessagePack.Annotations.dll", + "packageId": "MessagePack.Annotations", + "packageManager": "nuget", + "ownerRepo": "CultLib", + "resolutionMode": "Unity precompiled reference during EveUnity incubation.", + "dependencyContract": { + "sourceAuthority": "CultLib", + "packageSource": "nuget", + "packageId": "MessagePack.Annotations", + "assemblyName": "MessagePack.Annotations.dll", + "versionPolicy": "CultLib-owned transitive package version stream.", + "unityResolution": "EveUnity consumes this as a Unity precompiled reference during incubation.", + "handoffRequirement": "EveUnity split must resolve this through CultLib\u0027s NuGet/package feed or a documented Unity import without vendoring CultLib ownership into Eve." + } + } + ] + }, + "evidencePaths": [ + "packages/org.gamecult.eve.unity-uitoolkit/Tests/Editor/GameCult.Eve.UnityUIToolkit.Tests.asmdef", + "packages/org.gamecult.eve.unity-uitoolkit/Tests/Editor/EveUiToolkitSurfaceLowererTests.cs", + "scripts/run-uitoolkit-tests.ps1" + ], + "pendingProofs": [ + "Cut and publish the first tagged EveUnity UI Toolkit package release." + ] + }, + "capture": { + "ownerRepo": "EveUnity", + "status": "pending-editor-capture", + "captureContract": { + "ownerRepo": "EveUnity", + "runtimeId": "unity-uitoolkit", + "targetId": "unity-uitoolkit", + "requestSchema": "gamecult.eve.runtime_capture_request.v1", + "requestBuilder": "scripts/build-uitoolkit-capture-request.ps1", + "advertisementPath": "../Aetheria/conformance/eve/aetheria.provider-advertisement.json", + "captureKind": "unity-editor-or-batchmode-png", + "artifactKind": "png", + "artifactPattern": "artifacts/eveunity-uitoolkit-capture/{stamp}/unity-uitoolkit.png", + "conformanceAttachment": "runtime.captureArtifacts[]", + "requiredProvider": "aetheria", + "requiredSurface": "aetheria.daemon.game", + "additionalProviderSurfaces": [ + { + "providerId": "eve.world-smoke", + "surfaceId": "eve.world-smoke.surface", + "purpose": "Generic non-product interactive-world capture control." + }, + { + "providerId": "aetheria", + "surfaceId": "aetheria.daemon.editor", + "purpose": "Aetheria interactive-world-editor capture control." + } + ], + "authority": "Capture observes runtime projection only; provider state, command acceptance, and receipts stay provider-owned.", + "publishProof": "Unity editor or batchmode capture artifact is attached to the runtime conformance report from EveUnity." + }, + "evidencePaths": [ + "packages/org.gamecult.eve.unity-uitoolkit/eve-runtime-capability.json", + "scripts/build-uitoolkit-capture-request.ps1" + ], + "pendingProofs": [ + "Unity batchmode or editor capture artifact attached to the runtime conformance report." + ] + } + }, + "capture": { + "status": "pending-native-capture", + "note": "Unity editor or batchmode PNG capture must be produced and attached by EveUnity; Eve does not manufacture a runtime proxy artifact.", + "artifacts": [ + + ] + } + }, + { + "id": "direct2d", + "title": "Fensalir Direct2D", + "kind": "pending", + "ownerRepo": "Fensalir", + "repoRole": "runtime-external-target", + "splitTarget": "Fensalir or EveDirect2D if it earns a separate body", + "graduationTrigger": "Do not graduate until the Eve-to-AquariumUiDocument adapter and Direct2D frame capture exist.", + "adapterSpike": "external", + "demotionReason": "Fensalir has an external Aquarium.EveSurface adapter spike and a recorded gamecult.eve.surface.v1 adapter fixture, but Eve still has no Direct2D frame capture or runtime-owned command smoke.", + "activationCriteria": [ + "Fensalir runs recorded gamecult.eve.surface.v1 fixtures without provider-specific renderer knowledge.", + "Fensalir emits gamecult.eve.command_invocation.v1 intents through the advertised provider command boundary.", + "Direct2D capture artifacts are produced and attached to the parity report." + ], + "requiredFixtures": [ + "fensalir-direct2d" + ], + "pluginFixtures": [ + + ], + "supportedFeatures": [ + "embeddedDocuments" + ], + "expectedPaths": [ + "web/fixtures/fensalir-direct2d-surface.json" + ], + "expectedExternalPaths": [ + "E:\\Projects\\Fensalir\\src\\Aquarium.EveSurface\\EveSurfaceRuntime.cs", + "E:\\Projects\\Fensalir\\docs\\eve-direct2d-client-surface.md" + ], + "expectedExternalSourceSymbols": [ + { + "path": "E:\\Projects\\Fensalir\\src\\Aquarium.EveSurface\\EveSurfaceRuntime.cs", + "contains": [ + "FENSALIR_EVE_BROKER", + "AquariumUiDocument Ui =\u003e ComposeUi()", + "ReadSurface(root)", + "EveCompositionElement", + "DirectWriteOverlay" + ] + } + ], + "capture": { + "status": "missing", + "note": "External Fensalir adapter spike and recorded surface fixture exist; Eve still needs runtime-owned command smoke and Direct2D frame capture before this runtime can become active." + }, + "sourceRoot": "../Eve" + } + ] +} \ No newline at end of file diff --git a/tools/parity/run-parity.mjs b/tools/parity/run-parity.mjs new file mode 100644 index 0000000..337331d --- /dev/null +++ b/tools/parity/run-parity.mjs @@ -0,0 +1,3657 @@ +import { mkdir, readFile, writeFile } from "node:fs/promises"; +import { existsSync, readFileSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); +const manifestPath = path.join(repoRoot, "tools/parity/parity-manifest.json"); +const kernelRoot = process.env.EVE_KERNEL_ROOT + ? path.resolve(process.env.EVE_KERNEL_ROOT) + : repoRoot; +const { compileEveDsl } = await import(pathToFileURL(path.join(kernelRoot, "web/eve-dsl.js"))); +const outputRoot = process.env.EVE_PARITY_OUTPUT + ? path.resolve(process.env.EVE_PARITY_OUTPUT) + : path.join(repoRoot, "artifacts/parity"); +const conformanceOutputRoot = process.env.EVE_CONFORMANCE_OUTPUT + ? path.resolve(process.env.EVE_CONFORMANCE_OUTPUT) + : path.join(repoRoot, "artifacts/conformance"); + +const manifest = JSON.parse(await readFile(manifestPath, "utf8")); +const stamp = new Date().toISOString().replace(/[:.]/g, "-"); +const runDirectory = path.join(outputRoot, stamp); + +await mkdir(runDirectory, { recursive: true }); + +const fixtureResults = []; +for (const fixture of manifest.fixtures) { + fixtureResults.push(await evaluateFixture(fixture)); +} + +const pluginResults = await Promise.all((manifest.pluginManifests || []).map(plugin => evaluatePlugin(plugin, fixtureResults))); +const providerResults = await Promise.all((manifest.providerAdvertisements || []).map(provider => evaluateProvider(provider, fixtureResults, pluginResults))); +const runtimeResults = await Promise.all(manifest.runtimes.map(runtime => evaluateRuntime(runtime, fixtureResults))); +const splitTargetResults = evaluateSplitTargets(manifest.splitTargets || [], runtimeResults); +const conformanceHandoffErrors = await validateConformanceHandoff(manifest.repoStrategy?.conformanceHandoffPath || ""); +const report = { + schema: "gamecult.eve.parity_report.v1", + generatedAt: new Date().toISOString(), + manifest: path.relative(repoRoot, manifestPath).replaceAll("\\", "/"), + repoStrategy: { + ...(manifest.repoStrategy || {}), + conformanceHandoffErrors, + }, + conformancePacks: manifest.conformancePacks || [], + responsiveCases: manifest.responsiveCases || [], + summary: summarize(fixtureResults, runtimeResults, pluginResults, providerResults, splitTargetResults), + fixtures: fixtureResults, + plugins: pluginResults, + providers: providerResults, + splitTargets: splitTargetResults, + runtimes: runtimeResults, +}; +const conformanceExport = buildConformanceExport(report); +const conformanceExportErrors = validateSchemaSubset( + JSON.parse(await readFile(path.join(repoRoot, manifest.schemas["gamecult.eve.conformance_export.v1"]), "utf8")), + conformanceExport, + "conformanceExport", +); + +await writeFile(path.join(runDirectory, "parity-report.json"), `${JSON.stringify(report, null, 2)}\n`); +await writeFile(path.join(runDirectory, "parity-report.md"), renderMarkdown(report)); +await writeFile(path.join(outputRoot, "latest.json"), `${JSON.stringify(report, null, 2)}\n`); +await writeFile(path.join(outputRoot, "latest.md"), renderMarkdown(report)); +await writeConformanceExport(conformanceExport, path.join(conformanceOutputRoot, stamp)); +await writeConformanceExport(conformanceExport, path.join(conformanceOutputRoot, "latest")); + +console.log(`Parity report: ${path.relative(repoRoot, path.join(runDirectory, "parity-report.md"))}`); +console.log(`Conformance export: ${path.relative(repoRoot, path.join(conformanceOutputRoot, "latest", "index.md"))}`); +for (const error of conformanceExportErrors) console.error(`Conformance export error: ${error}`); +if (report.summary.failedFixtures > 0) process.exitCode = 1; +if (report.summary.failedPlugins > 0) process.exitCode = 1; +if (report.summary.failedProviders > 0) process.exitCode = 1; +if (conformanceHandoffErrors.length > 0) process.exitCode = 1; +if (conformanceExportErrors.length > 0) process.exitCode = 1; + +async function evaluateFixture(fixture) { + const startedAt = Date.now(); + const state = await loadSurface(fixture, fixture.surface); + const root = state.surface?.root; + const nodes = root ? flattenSurface(root) : []; + const kindCounts = countBy(nodes, node => node.kind || "unknown"); + const bindings = [...new Set(nodes.flatMap(readBindings))].sort(); + const embeddedDocuments = [...new Set(nodes + .flatMap(node => node.embeddedDocuments || []) + .map(slot => slot.slotId || slot.documentId) + .filter(Boolean))].sort(); + const controlSkins = state.surface?.styles?.controlSkins || {}; + const skinNames = Object.keys(controlSkins); + const skinNodes = Object.values(controlSkins).flatMap(skin => skin.children || []); + const controlPartNames = [...new Set([...nodes, ...skinNodes] + .filter(node => node.kind === "control.part") + .map(node => node.props?.name) + .filter(Boolean))].sort(); + const tokenNames = Object.keys(state.surface?.styles?.tokens || {}).sort(); + const commandDescriptors = readCommandDescriptors(state.commands || []); + const commandIds = commandDescriptors.map(command => command.command).sort(); + const commandReferences = readCommandReferences(nodes); + const commandDescriptorErrors = await validateCommandDescriptors(fixture, commandDescriptors); + const authorityWitnesses = readAuthorityWitnesses(nodes); + const authorityStates = [...new Set(authorityWitnesses.map(witness => witness.state))].filter(Boolean).sort(); + const authorityOwners = [...new Set(authorityWitnesses.map(witness => witness.owner))].filter(Boolean).sort(); + const receiptRefs = [...new Set(authorityWitnesses.map(witness => witness.receiptRef))].filter(Boolean).sort(); + const witnessRefs = [...new Set(authorityWitnesses.map(witness => witness.witnessRef))].filter(Boolean).sort(); + const textNodeCount = nodes.filter(isTextLikeNode).length; + const nonEmptyTextNodeCount = nodes.filter(hasVisibleText).length; + const layoutBoxCount = nodes.filter(hasLayoutBox).length; + const metadataResult = await evaluateFixtureMetadata(fixture); + const checks = []; + + addCheck(checks, "providerId", state.providerId === fixture.expect.providerId, { + expected: fixture.expect.providerId, + actual: state.providerId, + }); + + for (const kind of fixture.expect.componentKinds || []) { + addCheck(checks, `component:${kind}`, Boolean(kindCounts[kind]), { + expected: "present", + actual: kindCounts[kind] || 0, + }); + } + + for (const [kind, minimum] of Object.entries(fixture.expect.minimumCounts || {})) { + addCheck(checks, `minimum:${kind}`, (kindCounts[kind] || 0) >= minimum, { + expected: minimum, + actual: kindCounts[kind] || 0, + }); + } + + for (const skin of fixture.expect.controlSkins || []) { + addCheck(checks, `skin:${skin}`, skinNames.includes(skin), { + expected: "present", + actual: skinNames, + }); + } + + for (const part of fixture.expect.controlParts || []) { + addCheck(checks, `controlPart:${part}`, controlPartNames.includes(part), { + expected: "present", + actual: controlPartNames, + }); + } + + for (const token of fixture.expect.styleTokens || []) { + addCheck(checks, `styleToken:${token}`, tokenNames.includes(token), { + expected: "present", + actual: tokenNames, + }); + } + + for (const binding of fixture.expect.bindings || []) { + addCheck(checks, `binding:${binding}`, bindings.includes(binding), { + expected: "present", + actual: bindings, + }); + } + + for (const slot of fixture.expect.embeddedDocuments || []) { + addCheck(checks, `embedded:${slot}`, embeddedDocuments.includes(slot), { + expected: "present", + actual: embeddedDocuments, + }); + } + + for (const command of fixture.expect.commandDescriptors || []) { + addCheck(checks, `command:${command}`, commandIds.includes(command), { + expected: "present", + actual: commandIds, + }); + } + + for (const error of commandDescriptorErrors) { + addCheck(checks, `commandDescriptor:${error}`, false, { + expected: "valid command descriptor", + actual: error, + }); + } + + if (fixture.expect.commandReferencesDeclared) { + for (const reference of commandReferences) { + addCheck(checks, `commandReference:${reference.nodeId}:${reference.command}`, commandIds.includes(reference.command), { + expected: "declared command descriptor", + actual: commandIds, + }); + } + } + + for (const state of fixture.expect.authorityStates || []) { + addCheck(checks, `authorityState:${state}`, authorityStates.includes(state), { + expected: "present", + actual: authorityStates, + }); + } + + for (const owner of fixture.expect.authorityOwners || []) { + addCheck(checks, `authorityOwner:${owner}`, authorityOwners.includes(owner), { + expected: "present", + actual: authorityOwners, + }); + } + + if (fixture.expect.receiptRefs) { + addCheck(checks, "receiptRefs", receiptRefs.length >= fixture.expect.receiptRefs, { + expected: fixture.expect.receiptRefs, + actual: receiptRefs.length, + }); + } + + if (fixture.expect.witnessRefs) { + addCheck(checks, "witnessRefs", witnessRefs.length >= fixture.expect.witnessRefs, { + expected: fixture.expect.witnessRefs, + actual: witnessRefs.length, + }); + } + + for (const error of metadataResult.errors) { + addCheck(checks, `metadata:${error}`, false, { + expected: "valid fixture metadata", + actual: error, + }); + } + + const knownPacks = new Set((manifest.conformancePacks || []).map(pack => pack.id)); + if (fixture.pack) { + addCheck(checks, "conformancePack", knownPacks.has(fixture.pack), { + expected: [...knownPacks].sort(), + actual: fixture.pack, + }); + } else { + addCheck(checks, "conformancePack", false, { + expected: "fixture declares pack", + actual: "", + }); + } + + return { + id: fixture.id, + title: fixture.title, + pack: fixture.pack || "", + ownerRepo: fixture.ownerRepo || "", + surface: fixture.surface || {}, + requiredPlugins: fixture.requiredPlugins || [], + optionalNestedPlugins: fixture.optionalNestedPlugins || [], + status: checks.every(check => check.pass) ? "pass" : "fail", + durationMs: Date.now() - startedAt, + providerId: state.providerId, + componentCount: nodes.length, + kindCounts, + styleTokens: tokenNames, + controlSkins: skinNames, + controlParts: controlPartNames, + bindings, + embeddedDocuments, + commands: commandIds, + commandReferences, + commandDescriptorErrors, + authorityStates, + authorityOwners, + receiptRefs, + witnessRefs, + authorityWitnesses, + textNodeCount, + nonEmptyTextNodeCount, + layoutBoxCount, + metadataPath: fixture.metadataPath || "", + metadata: metadataResult.metadata, + metadataErrors: metadataResult.errors, + checks, + }; +} + +async function loadSurface(owner, surface) { + const absolutePath = resolveOwnedPath(owner, surface.path); + const source = await readFile(absolutePath, "utf8"); + if (surface.transport === "local-eve-dsl") return compileEveDsl(source); + if (surface.transport === "local-json") return JSON.parse(source); + throw new Error(`Unsupported parity surface transport: ${surface.transport}`); +} + +async function evaluateRuntime(runtime, fixtureResults) { + const expectedPaths = runtime.expectedPaths || []; + const missingPaths = expectedPaths.filter(candidate => !existsSync(resolveRuntimeOwnedPath(runtime, candidate))); + const expectedExternalPaths = runtime.expectedExternalPaths || []; + const missingExternalPaths = expectedExternalPaths.filter(candidate => !existsSync(candidate)); + const expectedSourceSymbols = runtime.expectedSourceSymbols || []; + const missingSourceSymbols = []; + for (const expectation of expectedSourceSymbols) { + const sourcePath = expectation.path || ""; + const absolutePath = resolveRuntimeOwnedPath(runtime, sourcePath); + if (!existsSync(absolutePath)) { + missingSourceSymbols.push(`${sourcePath}:missing`); + continue; + } + + const source = await readFile(absolutePath, "utf8"); + for (const symbol of expectation.contains || []) { + if (!source.includes(symbol)) missingSourceSymbols.push(`${sourcePath}:${symbol}`); + } + } + const expectedExternalSourceSymbols = runtime.expectedExternalSourceSymbols || []; + const missingExternalSourceSymbols = []; + for (const expectation of expectedExternalSourceSymbols) { + const sourcePath = expectation.path || ""; + if (!existsSync(sourcePath)) { + missingExternalSourceSymbols.push(`${sourcePath}:missing`); + continue; + } + + const source = await readFile(sourcePath, "utf8"); + for (const symbol of expectation.contains || []) { + if (!source.includes(symbol)) missingExternalSourceSymbols.push(`${sourcePath}:${symbol}`); + } + } + const requiredFixtures = runtime.requiredFixtures || []; + const missingRequiredFixtures = requiredFixtures.filter(id => !fixtureResults.some(fixture => fixture.id === id && fixture.status === "pass")); + const supportedFeatures = runtime.supportedFeatures || []; + const supportedPlugins = runtime.supportedPlugins || []; + const missingRequiredFeatures = []; + if (requiredFixtures.includes("embedded-surface") && !supportedFeatures.includes("embeddedDocuments")) { + missingRequiredFeatures.push("embeddedDocuments"); + } + const commandTransportSmokeErrors = await validateRuntimeCommandTransportSmoke(runtime); + const capabilityManifestErrors = await validateRuntimeCapabilityManifest(runtime); + const capabilityManifestDocument = await readRuntimeCapabilityManifestDocument(runtime); + const lifecycleManifestDocument = await readRuntimeLifecycleManifestDocument(runtime); + const lifecycle = runtime.lifecycle || capabilityManifestDocument?.lifecycle || readLifecycleStages(lifecycleManifestDocument); + const capture = runtime.capture || capabilityManifestDocument?.capture || null; + const captureArtifactErrors = validateRuntimeCaptureArtifacts(runtime, capture); + const lifecycleErrors = await validateRuntimeLifecycle(runtime, lifecycle, lifecycleManifestDocument); + const splitHandoffPath = await readRuntimeSplitHandoffPath(runtime); + const splitHandoffErrors = await validateRuntimeSplitHandoff(runtime, splitHandoffPath); + const localProviderCatalogErrors = await validateRuntimeLocalProviderCatalog(runtime); + const missingIncubationFields = requiredIncubationFields(runtime).filter(field => !runtime[field]); + const pluginCapabilityGaps = collectPluginCapabilityGaps(runtime, fixtureResults); + const unsupportedPluginNotes = collectUnsupportedPluginNotes(runtime, fixtureResults); + let status = runtime.kind === "active" ? "active" : "pending"; + if (missingPaths.length) status = "missing-body"; + if (runtime.kind === "active" && missingSourceSymbols.length) status = "missing-source-symbol"; + if (runtime.kind === "active" && missingRequiredFixtures.length) status = "missing-required-fixture"; + if (runtime.kind === "active" && missingRequiredFeatures.length) status = "missing-required-feature"; + if (runtime.kind === "active" && commandTransportSmokeErrors.length) status = "missing-command-transport-smoke"; + if (runtime.kind === "active" && capabilityManifestErrors.length) status = "invalid-runtime-capability"; + if (runtime.kind === "active" && lifecycleErrors.length) status = "invalid-runtime-lifecycle"; + if (runtime.kind === "active" && captureArtifactErrors.length) status = "invalid-runtime-capture-artifact"; + if (runtime.kind === "active" && splitHandoffErrors.length) status = "invalid-runtime-split-handoff"; + if (runtime.kind === "active" && localProviderCatalogErrors.length) status = "invalid-local-provider-catalog"; + if (runtime.kind === "active" && missingIncubationFields.length) status = "missing-incubation-metadata"; + if (runtime.kind === "active" && pluginCapabilityGaps.length && status === "active") status = "active-with-capability-gaps"; + if (runtime.kind === "pending" && runtime.adapterSpike === "external" && !missingExternalPaths.length && !missingExternalSourceSymbols.length) { + status = "external-adapter-spike"; + } + if (["ssh-png", "adb-png", "golden"].includes(runtime.capture?.status) && status === "pending") { + status = "capture-ready"; + } + return { + id: runtime.id, + title: runtime.title, + kind: runtime.kind, + status, + semanticHarness: Boolean(runtime.semanticHarness), + expectedPaths, + missingPaths, + expectedExternalPaths, + missingExternalPaths, + expectedSourceSymbols, + missingSourceSymbols, + expectedExternalSourceSymbols, + missingExternalSourceSymbols, + requiredFixtures, + pluginFixtures: runtime.pluginFixtures || [], + missingRequiredFixtures, + supportedFeatures, + supportedPlugins, + unsupportedPlugins: runtime.unsupportedPlugins || [], + unsupportedPluginNotes, + worldSurfaceLowering: runtime.worldSurfaceLowering || [], + missingRequiredFeatures, + commandTransportSmoke: runtime.commandTransportSmoke || null, + commandTransport: runtime.commandTransport || capabilityManifestDocument?.commandTransport || null, + commandTransportSmokeErrors, + capabilityManifest: runtime.capabilityManifest || null, + capabilityManifestErrors, + lifecycleManifest: runtime.lifecycleManifest || null, + lifecycleErrors, + splitHandoffPath, + splitHandoffErrors, + localProviderCatalog: runtime.localProviderCatalog || null, + localProviderCatalogErrors, + lifecycle, + captureArtifactErrors, + ownerRepo: runtime.ownerRepo || "", + repoRole: runtime.repoRole || "", + graduationTrigger: runtime.graduationTrigger || "", + splitTarget: runtime.splitTarget || "", + adapterSpike: runtime.adapterSpike || "", + demotionReason: runtime.demotionReason || "", + activationCriteria: runtime.activationCriteria || [], + missingIncubationFields, + pluginCapabilityGaps, + capture, + }; +} + +function runtimeCommandTransportSchema(runtime) { + return runtime.commandTransportSmoke?.schema || runtime.commandTransport?.schema || ""; +} + +async function readRuntimeCapabilityManifestDocument(runtime) { + const manifestPath = runtime.capabilityManifest?.manifestPath; + if (!manifestPath) return null; + try { + return JSON.parse(await readFile(resolveRuntimeOwnedPath(runtime, manifestPath), "utf8")); + } catch { + return null; + } +} + +async function readRuntimeLifecycleManifestDocument(runtime) { + const lifecyclePath = runtime.lifecycleManifest?.manifestPath; + if (!lifecyclePath) return null; + try { + return JSON.parse(await readFile(resolveRuntimeOwnedPath(runtime, lifecyclePath), "utf8")); + } catch { + return null; + } +} + +function readLifecycleStages(lifecycleDocument) { + if (!lifecycleDocument) return null; + return { + release: lifecycleDocument.release, + test: lifecycleDocument.test, + capture: lifecycleDocument.capture, + }; +} + +async function validateRuntimeLifecycle(runtime, lifecycle, lifecycleDocument) { + if (!lifecycle && !runtime.lifecycleManifest) return []; + + const schemaPath = runtime.lifecycleManifest?.schemaPath || manifest.schemas?.["gamecult.eve.runtime_lifecycle.v1"]; + const errors = []; + if (runtime.lifecycleManifest?.manifestPath) { + if (!lifecycleDocument) { + errors.push(`${runtime.lifecycleManifest.manifestPath}:unreadable`); + } else { + errors.push(...await validateJsonDocumentAgainstSchemaPath( + schemaPath, + lifecycleDocument, + `runtimeLifecycleManifest:${runtime.id}`, + )); + if (lifecycleDocument.schema !== "gamecult.eve.runtime_lifecycle.v1") { + errors.push(`${runtime.lifecycleManifest.manifestPath}:schema:expected gamecult.eve.runtime_lifecycle.v1 got ${lifecycleDocument.schema || ""}`); + } + if (!Array.isArray(lifecycleDocument.runtimes) || !lifecycleDocument.runtimes.includes(runtime.id)) { + errors.push(`${runtime.lifecycleManifest.manifestPath}:runtimes:${runtime.id}:missing`); + } + } + } + + if (!lifecycle) { + errors.push("lifecycle:missing"); + return errors; + } + + const ownerRepo = lifecycleDocument?.ownerRepo + || lifecycle.release?.ownerRepo + || lifecycle.test?.ownerRepo + || lifecycle.capture?.ownerRepo + || runtime.splitTarget + || runtime.ownerRepo + || ""; + const currentHostRepo = lifecycleDocument?.currentHostRepo || runtime.ownerRepo || "Eve"; + const lifecycleEnvelope = { + schema: "gamecult.eve.runtime_lifecycle.v1", + splitTarget: lifecycleDocument?.splitTarget || runtime.splitTarget || runtime.id, + ownerRepo, + currentHostRepo, + status: lifecycleDocument?.status || runtime.status || "", + runtimes: lifecycleDocument?.runtimes || [runtime.id], + release: lifecycle.release, + test: lifecycle.test, + capture: lifecycle.capture, + remainingSplitBlockers: lifecycleDocument?.remainingSplitBlockers || [], + }; + + const schemaErrors = await validateJsonDocumentAgainstSchemaPath(schemaPath, lifecycleEnvelope, `runtimeLifecycle:${runtime.id}`); + errors.push(...schemaErrors); + + for (const stage of ["release", "test", "capture"]) { + const stageDocument = lifecycle[stage]; + for (const evidencePath of stageDocument?.evidencePaths || []) { + if (!existsSync(resolveRuntimeOwnedPath(runtime, evidencePath))) { + errors.push(`lifecycle.${stage}.evidence:${evidencePath}:missing`); + } + } + } + + return errors; +} + +async function validateRuntimeLocalProviderCatalog(runtime) { + const catalogConfig = runtime.localProviderCatalog; + if (!catalogConfig) return []; + + const errors = []; + const catalogPath = catalogConfig.path || ""; + const absolutePath = resolveWorkspacePath(catalogPath); + if (!existsSync(absolutePath)) return [`${catalogPath}:missing`]; + + let catalog; + try { + catalog = JSON.parse(await readFile(absolutePath, "utf8")); + } catch (error) { + return [`${catalogPath}:invalid-json:${error instanceof Error ? error.message : String(error)}`]; + } + + errors.push(...await validateJsonDocument( + manifest.schemas?.["gamecult.eve.local_provider_catalog.v1"], + catalogPath, + { + schema: "gamecult.eve.local_provider_catalog.v1", + }, + )); + + const providers = Array.isArray(catalog.providers) ? catalog.providers : []; + const providerIds = new Set(providers.map(provider => provider.providerId).filter(Boolean)); + for (const providerId of catalogConfig.requiredProviders || []) { + if (!providerIds.has(providerId)) errors.push(`${catalogPath}:provider:${providerId}:missing`); + } + + const catalogDirectory = path.posix.dirname(normalizePath(catalogPath)); + const advertisedPaths = new Set(providers + .map(provider => resolveCatalogPath(catalogDirectory, provider.advertisement || "")) + .filter(Boolean)); + for (const advertisementPath of catalogConfig.requiredAdvertisements || []) { + const normalized = normalizePath(advertisementPath); + if (!advertisedPaths.has(normalized)) { + errors.push(`${catalogPath}:advertisement:${advertisementPath}:unreferenced`); + } + if (!existsSync(resolveWorkspacePath(advertisementPath))) { + errors.push(`${advertisementPath}:missing`); + } + } + + return errors; +} + +async function readRuntimeSplitHandoffPath(runtime) { + if (runtime?.splitHandoffPath) return runtime.splitHandoffPath; + const capabilityManifest = runtime?.capabilityManifest; + if (!capabilityManifest?.manifestPath) return ""; + const documentPath = resolveWorkspacePath(capabilityManifest.manifestPath); + if (!existsSync(documentPath)) return ""; + try { + const document = JSON.parse(await readFile(documentPath, "utf8")); + return document.incubation?.splitHandoff?.manifestPath || ""; + } catch { + return ""; + } +} + +async function validateRuntimeSplitHandoff(runtime, splitHandoffPath) { + if (!splitHandoffPath) return []; + return validateJsonDocument( + manifest.schemas?.["gamecult.eve.runtime_split_handoff.v1"], + splitHandoffPath, + { + schema: "gamecult.eve.runtime_split_handoff.v1", + runtimeId: runtime.id, + }, + ); +} + +async function validateConformanceHandoff(handoffPath) { + if (!handoffPath) return []; + return validateJsonDocument( + manifest.schemas?.["gamecult.eve.conformance_handoff.v1"], + handoffPath, + { + schema: "gamecult.eve.conformance_handoff.v1", + splitTarget: "EveConformance", + ownerRepo: "EveConformance", + }, + ); +} + +function normalizePath(candidate) { + return candidate.replaceAll("\\", "/").replace(/^\.\//, ""); +} + +function resolveCatalogPath(catalogDirectory, candidate) { + const normalized = normalizePath(candidate); + if (!normalized) return ""; + if (/^(https?:|\/)/i.test(normalized)) return normalized; + return path.posix.normalize(path.posix.join(catalogDirectory, normalized)); +} + +async function evaluatePlugin(plugin, fixtureResults) { + const expectedPaths = plugin.expectedPaths || []; + const missingPaths = expectedPaths.filter(candidate => !existsSync(path.join(repoRoot, candidate))); + const requiredFixtures = plugin.requiredFixtures || []; + const missingRequiredFixtures = requiredFixtures.filter(id => !fixtureResults.some(fixture => fixture.id === id && fixture.status === "pass")); + const missingIncubationFields = requiredIncubationFields(plugin).filter(field => !plugin[field]); + const schemaErrors = await validateJsonDocument(plugin.schemaPath, plugin.manifestPath, { + schema: "gamecult.eve.plugin.v1", + pluginId: plugin.pluginId, + }); + const advertisementErrors = await validateJsonDocument(plugin.advertisementSchemaPath, plugin.advertisementPath, { + schema: "gamecult.eve.plugin_advertisement.v1", + pluginId: plugin.pluginId, + }); + const runtimeBoundary = await readPluginRuntimeBoundary(plugin); + const runtimeBoundaryErrors = validatePluginRuntimeBoundary(plugin, runtimeBoundary); + const abiOperations = await readPluginAbiOperations(plugin.abiFixturePath); + const abiOperationContracts = await readPluginAbiOperationContracts(plugin.abiFixturePath); + const abiErrors = await validatePluginAbiFixture(plugin); + const handoffErrors = await validatePluginHandoff(plugin); + const status = missingPaths.length + ? "missing-body" + : missingRequiredFixtures.length + ? "missing-required-fixture" + : missingIncubationFields.length + ? "missing-incubation-metadata" + : schemaErrors.length + ? "invalid-plugin-manifest" + : advertisementErrors.length + ? "invalid-plugin-advertisement" + : runtimeBoundaryErrors.length + ? "invalid-plugin-runtime-boundary" + : abiErrors.length + ? "invalid-plugin-abi-fixture" + : handoffErrors.length + ? "invalid-plugin-handoff" + : plugin.kind === "incubating" + ? "incubating" + : plugin.kind === "external-owner" + ? "active" + : "external-owner-planned"; + + return { + pluginId: plugin.pluginId, + title: plugin.title, + kind: plugin.kind, + ownerRepo: plugin.ownerRepo || "", + repoRole: plugin.repoRole || "", + graduationTrigger: plugin.graduationTrigger || "", + splitTarget: plugin.splitTarget || "", + capabilities: plugin.capabilities || [], + optionalPlugins: plugin.optionalPlugins || [], + runtimeBoundary, + runtimeBoundaryErrors, + requiredFixtures, + missingRequiredFixtures, + schemaPath: plugin.schemaPath || "", + manifestPath: plugin.manifestPath || "", + abiFixturePath: plugin.abiFixturePath || "", + handoffPath: plugin.handoffPath || "", + handoffErrors, + abiOperations, + abiOperationContracts, + advertisementSchemaPath: plugin.advertisementSchemaPath || "", + advertisementPath: plugin.advertisementPath || "", + schemaErrors, + advertisementErrors, + abiErrors, + expectedPaths, + missingPaths, + missingIncubationFields, + status, + }; +} + +async function validatePluginHandoff(plugin) { + if (!plugin.handoffPath) return []; + return validateJsonDocument( + manifest.schemas?.["gamecult.eve.plugin_handoff.v1"], + plugin.handoffPath, + { + schema: "gamecult.eve.plugin_handoff.v1", + pluginId: plugin.pluginId, + ownerRepo: plugin.ownerRepo, + }, + ); +} + +async function readPluginAbiOperations(abiFixturePath) { + if (!abiFixturePath) return []; + try { + const abiFixture = await readJsonDocument(abiFixturePath); + return (abiFixture.operations || []) + .map(operation => operation.operation) + .filter(Boolean) + .sort(); + } catch { + return []; + } +} + +async function readPluginAbiOperationContracts(abiFixturePath) { + if (!abiFixturePath) return []; + try { + const abiFixture = await readJsonDocument(abiFixturePath); + return (abiFixture.operations || []) + .filter(operation => operation.operation) + .map(operation => ({ + operation: operation.operation, + input: operation.input || {}, + expect: operation.expect || {}, + })) + .sort((left, right) => left.operation.localeCompare(right.operation)); + } catch { + return []; + } +} + +async function readPluginRuntimeBoundary(plugin) { + try { + const manifestDocument = await readJsonDocument(plugin.manifestPath); + const advertisementDocument = await readJsonDocument(plugin.advertisementPath); + return { + invocationModel: manifestDocument.runtime?.invocationModel || "", + contract: manifestDocument.runtime?.contract || "", + transports: manifestDocument.runtime?.transports || [], + authority: manifestDocument.runtime?.authority || [], + sidecar: manifestDocument.runtime?.sidecar || {}, + advertisementInvocationModel: advertisementDocument.runtime?.invocationModel || "", + advertisementContract: advertisementDocument.runtime?.contract || "", + advertisementTransports: advertisementDocument.runtime?.transports || [], + advertisementAuthority: advertisementDocument.runtime?.authority || [], + advertisementSidecar: advertisementDocument.runtime?.sidecar || {}, + }; + } catch { + return { + invocationModel: "", + contract: "", + transports: [], + authority: [], + sidecar: {}, + advertisementInvocationModel: "", + advertisementContract: "", + advertisementTransports: [], + advertisementAuthority: [], + advertisementSidecar: {}, + }; + } +} + +function validatePluginRuntimeBoundary(plugin, runtimeBoundary) { + const errors = []; + const expectedRuntime = plugin.expectedRuntime || {}; + if (!runtimeBoundary.invocationModel) errors.push("runtime.invocationModel:missing"); + if (!runtimeBoundary.contract) errors.push("runtime.contract:missing"); + if (!runtimeBoundary.transports?.length) errors.push("runtime.transports:missing"); + if (!runtimeBoundary.authority?.length) errors.push("runtime.authority:missing"); + if (runtimeBoundary.invocationModel !== runtimeBoundary.advertisementInvocationModel) { + errors.push(`runtime.invocationModel:advertisement:expected ${runtimeBoundary.invocationModel} got ${runtimeBoundary.advertisementInvocationModel}`); + } + if (runtimeBoundary.contract !== runtimeBoundary.advertisementContract) { + errors.push(`runtime.contract:advertisement:expected ${runtimeBoundary.contract} got ${runtimeBoundary.advertisementContract}`); + } + if (expectedRuntime.invocationModel && runtimeBoundary.invocationModel !== expectedRuntime.invocationModel) { + errors.push(`runtime.invocationModel:expected ${expectedRuntime.invocationModel} got ${runtimeBoundary.invocationModel}`); + } + if (expectedRuntime.contract && runtimeBoundary.contract !== expectedRuntime.contract) { + errors.push(`runtime.contract:expected ${expectedRuntime.contract} got ${runtimeBoundary.contract}`); + } + errors.push(...missingMembers(expectedRuntime.transports || [], runtimeBoundary.transports || [], "runtime.transports")); + errors.push(...missingMembers(expectedRuntime.transports || [], runtimeBoundary.advertisementTransports || [], "runtime.advertisementTransports")); + errors.push(...missingMembers(expectedRuntime.authority || [], runtimeBoundary.authority || [], "runtime.authority")); + errors.push(...missingMembers(expectedRuntime.authority || [], runtimeBoundary.advertisementAuthority || [], "runtime.advertisementAuthority")); + errors.push(...comparePluginSidecar(expectedRuntime.sidecar || {}, runtimeBoundary.sidecar || {}, "runtime.sidecar")); + errors.push(...comparePluginSidecar(runtimeBoundary.sidecar || {}, runtimeBoundary.advertisementSidecar || {}, "runtime.advertisementSidecar")); + for (const schemaId of [ + runtimeBoundary.sidecar?.requestSchema, + runtimeBoundary.sidecar?.responseSchema, + runtimeBoundary.sidecar?.commandEnvelope, + runtimeBoundary.sidecar?.receiptSchema, + ].filter(Boolean)) { + if (!manifest.schemas?.[schemaId]) { + errors.push(`runtime.sidecar.schemaCatalog:${schemaId}:missing`); + } + } + return errors; +} + +function comparePluginSidecar(expected, actual, label) { + const errors = []; + for (const key of ["processKind", "protocol", "requestSchema", "responseSchema", "commandEnvelope", "receiptSchema", "stateAuthority"]) { + if (expected[key] && expected[key] !== actual[key]) { + errors.push(`${label}.${key}:expected ${expected[key]} got ${actual[key] || ""}`); + } + } + if (Array.isArray(expected.operations) && expected.operations.length) { + errors.push(...missingMembers(expected.operations, actual.operations || [], `${label}.operations`)); + } + return errors; +} + +async function evaluateFixtureMetadata(fixture) { + const errors = []; + if (!fixture.metadataPath) return { metadata: null, errors: ["metadataPath:missing"] }; + + errors.push(...await validateOwnedJsonDocument( + fixture, + manifest.schemas?.["gamecult.eve.conformance_fixture.v1"], + fixture.metadataPath, + { + schema: "gamecult.eve.conformance_fixture.v1", + fixtureId: fixture.id, + }, + )); + if (errors.length) return { metadata: null, errors }; + + const metadata = await readOwnedJsonDocument(fixture, fixture.metadataPath); + if (metadata.pack !== fixture.pack) errors.push(`pack:expected ${fixture.pack} got ${metadata.pack}`); + if (metadata.ownerRepo !== fixture.ownerRepo) errors.push(`ownerRepo:expected ${fixture.ownerRepo} got ${metadata.ownerRepo}`); + if (metadata.surface?.transport !== fixture.surface?.transport) { + errors.push(`surface.transport:expected ${fixture.surface?.transport} got ${metadata.surface?.transport}`); + } + if (metadata.surface?.path !== fixture.surface?.path) { + errors.push(`surface.path:expected ${fixture.surface?.path} got ${metadata.surface?.path}`); + } + if (!Array.isArray(metadata.asserts) || metadata.asserts.length === 0) errors.push("asserts:empty"); + + return { metadata, errors }; +} + +async function evaluateProvider(provider, fixtureResults, pluginResults) { + const expectedPaths = provider.expectedPaths || []; + const missingPaths = expectedPaths.filter(candidate => !existsSync(resolveOwnedPath(provider, candidate))); + const requiredFixtures = provider.requiredFixtures || []; + const missingRequiredFixtures = requiredFixtures.filter(id => !fixtureResults.some(fixture => fixture.id === id && fixture.status === "pass")); + const missingIncubationFields = requiredIncubationFields(provider).filter(field => !provider[field]); + const advertisementErrors = await validateOwnedJsonDocument(provider, provider.schemaPath, provider.advertisementPath, { + schema: "gamecult.eve.provider_advertisement.v1", + providerId: provider.providerId, + }); + const advertisement = advertisementErrors.length ? null : await readOwnedJsonDocument(provider, provider.advertisementPath); + const schemaIds = advertisement ? extractProviderSchemaIds(advertisement.schemas || []) : []; + const advertisedSchemaIds = advertisement ? [...new Set([ + ...schemaIds, + ...(advertisement.surfaces || []).map(surface => surface.schema).filter(Boolean), + ...(advertisement.commands || []).map(command => command.schema).filter(Boolean), + ])].sort() : []; + const surfaceIds = advertisement ? (advertisement.surfaces || []).map(surface => surface.surfaceId).filter(Boolean).sort() : []; + const surfaceKinds = advertisement ? collectProviderSurfaceKinds(advertisement) : []; + const surfaceContracts = advertisement ? collectProviderSurfaceContracts(advertisement) : []; + const commandIds = advertisement ? (advertisement.commands || []).map(command => command.command).filter(Boolean).sort() : []; + const witnessKinds = advertisement ? (advertisement.witnesses || []).map(witness => witness.kind).filter(Boolean).sort() : []; + const pluginRequirements = advertisement ? collectProviderPluginRequirements(advertisement) : []; + const pluginRequirementErrors = validateProviderPluginRequirements(provider, pluginRequirements, pluginResults); + const missingSchemas = (provider.expectedSchemas || []).filter(schema => !advertisedSchemaIds.includes(schema)); + const missingSurfaces = (provider.expectedSurfaces || []).filter(surface => !surfaceIds.includes(surface)); + const missingSurfaceKinds = validateProviderSurfaceKinds(provider, surfaceKinds); + const missingCommands = (provider.expectedCommands || []).filter(command => !commandIds.includes(command)); + const scenarioErrors = await validateProviderScenario(provider, advertisement, fixtureResults, advertisedSchemaIds, surfaceIds, commandIds); + const scenario = scenarioErrors.length || !provider.scenarioPath ? null : await readOwnedJsonDocument(provider, provider.scenarioPath); + const handoffErrors = await validateProviderHandoff(provider); + const status = missingPaths.length + ? "missing-body" + : missingRequiredFixtures.length + ? "missing-required-fixture" + : missingIncubationFields.length + ? "missing-ownership-metadata" + : advertisementErrors.length + ? "invalid-provider-advertisement" + : missingSchemas.length || missingSurfaces.length || missingSurfaceKinds.length || missingCommands.length || pluginRequirementErrors.length + ? "capability-gap" + : scenarioErrors.length + ? "invalid-provider-scenario" + : handoffErrors.length + ? "invalid-provider-handoff" + : "advertised"; + + return { + providerId: provider.providerId, + title: provider.title, + kind: provider.kind, + ownerRepo: provider.ownerRepo || "", + repoRole: provider.repoRole || "", + graduationTrigger: provider.graduationTrigger || "", + requiredFixtures, + missingRequiredFixtures, + schemaPath: provider.schemaPath || "", + advertisementPath: provider.advertisementPath || "", + scenarioPath: provider.scenarioPath || "", + handoffPath: provider.handoffPath || "", + handoffErrors, + advertisementErrors, + scenarioErrors, + expectedPaths, + missingPaths, + missingIncubationFields, + expectedSchemas: provider.expectedSchemas || [], + expectedSurfaces: provider.expectedSurfaces || [], + expectedSurfaceKinds: provider.expectedSurfaceKinds || {}, + expectedCommands: provider.expectedCommands || [], + schemaIds, + advertisedSchemaIds, + surfaceIds, + surfaceKinds, + surfaceContracts, + commandIds, + witnessKinds, + pluginRequirements, + pluginRequirementErrors, + missingSchemas, + missingSurfaces, + missingSurfaceKinds, + missingCommands, + scenarioId: scenario?.scenarioId || "", + scenarioReceiptStates: scenario ? [...new Set((scenario.expectedReceipts || []).map(receipt => receipt.state).filter(Boolean))].sort() : [], + status, + }; +} + +async function validateProviderHandoff(provider) { + if (!provider.handoffPath) return []; + return validateJsonDocument( + manifest.schemas?.["gamecult.eve.provider_handoff.v1"], + provider.handoffPath, + { + schema: "gamecult.eve.provider_handoff.v1", + providerId: provider.providerId, + ownerRepo: provider.ownerRepo, + }, + ); +} + +function collectProviderPluginRequirements(advertisement) { + const requirements = []; + for (const surface of advertisement.surfaces || []) { + for (const requirement of surface.requiresPlugins || []) { + requirements.push({ + surfaceId: surface.surfaceId || "", + surfaceKey: surface.key || surface.url || "", + pluginId: requirement.pluginId || "", + versionRange: requirement.versionRange || "", + availability: requirement.availability || "required", + requiredCapabilities: requirement.requiredCapabilities || [], + optionalCapabilities: requirement.optionalCapabilities || [], + }); + } + } + return requirements; +} + +function collectProviderSurfaceKinds(advertisement) { + return (advertisement.surfaces || []) + .filter(surface => surface.surfaceId && surface.surfaceKind) + .map(surface => ({ + surfaceId: surface.surfaceId, + surfaceKind: surface.surfaceKind, + interactionModel: surface.interactionModel || "", + })) + .sort((left, right) => left.surfaceId.localeCompare(right.surfaceId)); +} + +function collectProviderSurfaceContracts(advertisement) { + return (advertisement.surfaces || []) + .filter(surface => surface.surfaceId && surface.worldInteraction) + .map(surface => ({ + surfaceId: surface.surfaceId, + surfaceKind: surface.surfaceKind || "", + interactionModel: surface.interactionModel || "", + worldInteraction: surface.worldInteraction || {}, + })) + .sort((left, right) => left.surfaceId.localeCompare(right.surfaceId)); +} + +function validateProviderSurfaceKinds(provider, surfaceKinds) { + const errors = []; + const kindsBySurface = new Map(surfaceKinds.map(surface => [surface.surfaceId, surface.surfaceKind])); + for (const [surfaceId, expectedKind] of Object.entries(provider.expectedSurfaceKinds || {})) { + const actualKind = kindsBySurface.get(surfaceId); + if (!actualKind) { + errors.push(`${surfaceId}:surfaceKind:missing`); + } else if (actualKind !== expectedKind) { + errors.push(`${surfaceId}:surfaceKind:expected ${expectedKind} got ${actualKind}`); + } + } + return errors; +} + +function validateProviderPluginRequirements(provider, requirements, pluginResults) { + const errors = []; + const pluginById = new Map((pluginResults || []).map(plugin => [plugin.pluginId, plugin])); + for (const requirement of requirements) { + errors.push(...validateProviderPluginRequirementShape(provider, requirement)); + const plugin = pluginById.get(requirement.pluginId); + const optional = requirement.availability && requirement.availability !== "required"; + if (!plugin) { + if (!optional) errors.push(`${provider.advertisementPath}:${requirement.surfaceId}:plugin:${requirement.pluginId}:missing`); + continue; + } + if (!["active", "incubating", "external-owner-planned"].includes(plugin.status)) { + errors.push(`${provider.advertisementPath}:${requirement.surfaceId}:plugin:${requirement.pluginId}:status:${plugin.status}`); + continue; + } + const capabilities = new Set(plugin.capabilities || []); + for (const capability of requirement.requiredCapabilities || []) { + if (!capabilities.has(capability)) { + errors.push(`${provider.advertisementPath}:${requirement.surfaceId}:plugin:${requirement.pluginId}:${capability}:missing`); + } + } + } + return errors; +} + +function validateProviderPluginRequirementShape(provider, requirement) { + const label = `${provider.advertisementPath}:${requirement.surfaceId}:plugin:${requirement.pluginId || "unknown"}`; + const errors = []; + const availability = requirement.availability || "required"; + if (!requirement.pluginId) errors.push(`${label}:pluginId:missing`); + + if (availability === "required" && !requirement.requiredCapabilities.length) { + errors.push(`${label}:requiredCapabilities:missing`); + } + + if (availability === "optional-nested") { + if (requirement.requiredCapabilities.length) { + errors.push(`${label}:optional-nested:requiredCapabilities:must-be-empty`); + } + if (!requirement.optionalCapabilities.length) { + errors.push(`${label}:optional-nested:optionalCapabilities:missing`); + } + } + + return errors; +} + +async function readJsonDocument(documentPath) { + return JSON.parse(await readFile(resolveWorkspacePath(documentPath), "utf8")); +} + +function resolveOwnedPath(owner, candidate) { + return path.resolve(repoRoot, owner?.sourceRoot || ".", candidate); +} + +function resolveWorkspacePath(candidate) { + const localPath = path.resolve(repoRoot, candidate); + if (existsSync(localPath) || candidate.startsWith("..")) return localPath; + return path.resolve(kernelRoot, candidate); +} + +async function readOwnedJsonDocument(owner, documentPath) { + return JSON.parse(await readFile(resolveOwnedPath(owner, documentPath), "utf8")); +} + +async function validateOwnedJsonDocument(owner, schemaPath, documentPath, expected = {}) { + const errors = []; + if (!schemaPath) errors.push("schemaPath:missing"); + if (!documentPath) errors.push("documentPath:missing"); + if (errors.length) return errors; + + const absoluteSchemaPath = resolveWorkspacePath(schemaPath); + const absoluteDocumentPath = resolveOwnedPath(owner, documentPath); + if (!existsSync(absoluteSchemaPath)) errors.push(`${schemaPath}:missing`); + if (!existsSync(absoluteDocumentPath)) errors.push(`${documentPath}:missing`); + if (errors.length) return errors; + + try { + const schema = JSON.parse(await readFile(absoluteSchemaPath, "utf8")); + const document = JSON.parse(await readFile(absoluteDocumentPath, "utf8")); + errors.push(...validateSchemaSubset(schema, document)); + for (const [key, value] of Object.entries(expected)) { + if (document[key] !== value) errors.push(`${documentPath}:${key}:expected ${value} got ${document[key]}`); + } + } catch (error) { + errors.push(`${documentPath}:invalid-json:${error instanceof Error ? error.message : String(error)}`); + } + return errors; +} + +async function validateProviderScenario(provider, advertisement, fixtureResults, advertisedSchemaIds, surfaceIds, commandIds) { + if (!provider.scenarioPath) return []; + + const errors = await validateOwnedJsonDocument( + provider, + manifest.schemas?.["gamecult.eve.provider_scenario.v1"], + provider.scenarioPath, + { + schema: "gamecult.eve.provider_scenario.v1", + providerId: provider.providerId, + }, + ); + if (errors.length) return errors; + + try { + const scenario = await readOwnedJsonDocument(provider, provider.scenarioPath); + if (scenario.ownerRepo !== provider.ownerRepo) { + errors.push(`${provider.scenarioPath}:ownerRepo:expected ${provider.ownerRepo} got ${scenario.ownerRepo}`); + } + if (scenario.advertisementPath !== provider.advertisementPath) { + errors.push(`${provider.scenarioPath}:advertisementPath:expected ${provider.advertisementPath} got ${scenario.advertisementPath}`); + } + + const advertisedScenario = (advertisement?.conformanceScenarios || []).find(candidate => + candidate.scenarioId === scenario.scenarioId && candidate.path === provider.scenarioPath); + if (!advertisedScenario) { + errors.push(`${provider.advertisementPath}:conformanceScenarios:${scenario.scenarioId}:missing`); + } + + for (const fixtureId of scenario.requires?.fixtures || []) { + if (!fixtureResults.some(fixture => fixture.id === fixtureId && fixture.status === "pass")) { + errors.push(`${provider.scenarioPath}:requires.fixtures:${fixtureId}:missing`); + } + } + errors.push(...missingMembers(scenario.requires?.surfaces || [], surfaceIds, `${provider.scenarioPath}:requires.surfaces`)); + errors.push(...missingMembers(scenario.requires?.commands || [], commandIds, `${provider.scenarioPath}:requires.commands`)); + errors.push(...missingMembers(scenario.requires?.schemas || [], advertisedSchemaIds, `${provider.scenarioPath}:requires.schemas`)); + const advertisedWorldSurfaces = new Map((advertisement.surfaces || []) + .filter(surface => surface.surfaceId && surface.worldInteraction) + .map(surface => [surface.surfaceId, surface])); + for (const requirement of scenario.requires?.worldSurfaces || []) { + const advertisedSurface = advertisedWorldSurfaces.get(requirement.surfaceId); + if (!advertisedSurface) { + errors.push(`${provider.scenarioPath}:requires.worldSurfaces:${requirement.surfaceId}:missing`); + continue; + } + if (advertisedSurface.surfaceKind !== requirement.surfaceKind) { + errors.push(`${provider.scenarioPath}:requires.worldSurfaces:${requirement.surfaceId}:surfaceKind:expected ${requirement.surfaceKind} got ${advertisedSurface.surfaceKind || ""}`); + } + for (const key of ["projectionKind", "commandBoundary", "receiptSchema"]) { + const actual = advertisedSurface.worldInteraction?.[key] || ""; + if (actual !== requirement[key]) { + errors.push(`${provider.scenarioPath}:requires.worldSurfaces:${requirement.surfaceId}:${key}:expected ${requirement[key]} got ${actual}`); + } + } + } + + const receipts = scenario.expectedReceipts || []; + const receiptKeys = new Set(receipts.map(receipt => `${receipt.command}:${receipt.commandId}`)); + for (const intent of scenario.commandIntents || []) { + if (!commandIds.includes(intent.command)) { + errors.push(`${provider.scenarioPath}:commandIntents:${intent.command}:unadvertised`); + } + if (!receiptKeys.has(`${intent.command}:${intent.commandId}`)) { + errors.push(`${provider.scenarioPath}:expectedReceipts:${intent.command}:${intent.commandId}:missing`); + } + } + + for (const receipt of receipts) { + if (!commandIds.includes(receipt.command)) { + errors.push(`${provider.scenarioPath}:expectedReceipts:${receipt.command}:unadvertised`); + } + if (receipt.ownerRepo !== provider.ownerRepo) { + errors.push(`${provider.scenarioPath}:expectedReceipts:${receipt.receiptId}:ownerRepo:expected ${provider.ownerRepo} got ${receipt.ownerRepo}`); + } + if (!["accepted", "denied", "pending", "reconciled"].includes(receipt.state)) { + errors.push(`${provider.scenarioPath}:expectedReceipts:${receipt.receiptId}:state:${receipt.state}:unknown`); + } + if (receipt.schema && !advertisedSchemaIds.includes(receipt.schema)) { + errors.push(`${provider.scenarioPath}:expectedReceipts:${receipt.receiptId}:schema:${receipt.schema}:unadvertised`); + } + } + } catch (error) { + errors.push(`${provider.scenarioPath}:invalid-json:${error instanceof Error ? error.message : String(error)}`); + } + + return errors; +} + +async function validatePluginAbiFixture(plugin) { + const errors = await validateJsonDocument( + manifest.schemas?.["gamecult.eve.plugin_abi_fixture.v1"], + plugin.abiFixturePath, + { + schema: "gamecult.eve.plugin_abi_fixture.v1", + pluginId: plugin.pluginId, + }, + ); + if (errors.length) return errors; + + try { + const abiFixture = await readJsonDocument(plugin.abiFixturePath); + const pluginManifest = await readJsonDocument(plugin.manifestPath); + const operations = new Map((abiFixture.operations || []).map(operation => [operation.operation, operation])); + + if (abiFixture.ownerRepo !== plugin.ownerRepo) { + errors.push(`${plugin.abiFixturePath}:ownerRepo:expected ${plugin.ownerRepo} got ${abiFixture.ownerRepo}`); + } + if (abiFixture.contract !== "gamecult.eve.plugin_abi.v1") { + errors.push(`${plugin.abiFixturePath}:contract:expected gamecult.eve.plugin_abi.v1 got ${abiFixture.contract}`); + } + if (abiFixture.requestSchema !== pluginManifest.runtime?.sidecar?.requestSchema) { + errors.push(`${plugin.abiFixturePath}:requestSchema:expected ${pluginManifest.runtime?.sidecar?.requestSchema || ""} got ${abiFixture.requestSchema || ""}`); + } + if (abiFixture.responseSchema !== pluginManifest.runtime?.sidecar?.responseSchema) { + errors.push(`${plugin.abiFixturePath}:responseSchema:expected ${pluginManifest.runtime?.sidecar?.responseSchema || ""} got ${abiFixture.responseSchema || ""}`); + } + const sidecarReceiptSchema = pluginManifest.runtime?.sidecar?.receiptSchema || ""; + for (const schemaId of [abiFixture.requestSchema, abiFixture.responseSchema, sidecarReceiptSchema].filter(Boolean)) { + if (!manifest.schemas?.[schemaId]) { + errors.push(`${plugin.abiFixturePath}:schemaCatalog:${schemaId}:missing`); + } + } + + const claimedOperations = pluginManifest.runtime?.sidecar?.operations || []; + for (const operation of claimedOperations) { + if (!operations.has(operation)) errors.push(`${plugin.abiFixturePath}:operation:${operation}:missing`); + } + errors.push(...missingMembers( + [...operations.keys()].filter(Boolean), + pluginManifest.runtime?.sidecar?.operations || [], + `${plugin.manifestPath}:runtime.sidecar.operations`, + )); + + const fixtureBasename = path.basename(plugin.abiFixturePath); + if (!(pluginManifest.abiFixtures || []).some(candidate => path.basename(candidate) === fixtureBasename)) { + errors.push(`${plugin.manifestPath}:abiFixtures:${plugin.abiFixturePath}:missing`); + } + + const manifestCommands = (pluginManifest.commands || []).map(command => command.command); + const manifestComponentKinds = pluginManifest.componentKinds || []; + const manifestCommandEffects = new Map((pluginManifest.commands || []).map(command => [command.command, command.effect])); + + const describe = operations.get("describe")?.expect || {}; + if (describe.pluginId !== plugin.pluginId) { + errors.push(`${plugin.abiFixturePath}:describe.pluginId:expected ${plugin.pluginId} got ${describe.pluginId}`); + } + errors.push(...missingMembers(manifestComponentKinds, describe.componentKinds || [], `${plugin.abiFixturePath}:describe.componentKinds`)); + errors.push(...missingMembers(manifestCommands, describe.commands || [], `${plugin.abiFixturePath}:describe.commands`)); + errors.push(...missingMembers(plugin.capabilities || [], describe.capabilities || [], `${plugin.abiFixturePath}:describe.capabilities`)); + + const validate = operations.get("validate")?.expect || {}; + errors.push(...missingMembers(manifestComponentKinds, validate.acceptedComponentKinds || [], `${plugin.abiFixturePath}:validate.acceptedComponentKinds`)); + + const project = operations.get("project")?.expect || {}; + errors.push(...missingMembers(manifestComponentKinds, project.ownedComponentKinds || [], `${plugin.abiFixturePath}:project.ownedComponentKinds`)); + if (!project.projectionKind) errors.push(`${plugin.abiFixturePath}:project.projectionKind:missing`); + + if (claimedOperations.includes("lower")) { + const lower = operations.get("lower")?.expect || {}; + errors.push(...missingMembers(manifestComponentKinds, lower.preservedComponentKinds || [], `${plugin.abiFixturePath}:lower.preservedComponentKinds`)); + if (!lower.loweringKind) errors.push(`${plugin.abiFixturePath}:lower.loweringKind:missing`); + } + + if (claimedOperations.includes("measure")) { + const measure = operations.get("measure")?.expect || {}; + if (!measure.measurementKind) errors.push(`${plugin.abiFixturePath}:measure.measurementKind:missing`); + if (!(measure.measurementOutputs || []).length) errors.push(`${plugin.abiFixturePath}:measure.measurementOutputs:missing`); + if (measure.preservesProviderAuthority !== true) errors.push(`${plugin.abiFixturePath}:measure.preservesProviderAuthority:expected true`); + } + + if (claimedOperations.includes("apply")) { + const apply = operations.get("apply")?.expect || {}; + if (apply.receiptSchema !== sidecarReceiptSchema) { + errors.push(`${plugin.abiFixturePath}:apply.receiptSchema:expected ${sidecarReceiptSchema} got ${apply.receiptSchema || ""}`); + } + if (manifestCommands.length) { + const commandEffects = apply.commandEffects || {}; + for (const command of manifestCommands) { + if (!commandEffects[command]) { + errors.push(`${plugin.abiFixturePath}:apply.commandEffects:${command}:missing`); + continue; + } + + const expectedEffect = manifestCommandEffects.get(command); + if (commandEffects[command] !== expectedEffect) { + errors.push(`${plugin.abiFixturePath}:apply.commandEffects:${command}:expected ${expectedEffect} got ${commandEffects[command]}`); + } + } + } else if (!(apply.stateEffects || []).length) { + errors.push(`${plugin.abiFixturePath}:apply.stateEffects:missing`); + } + } + } catch (error) { + errors.push(`${plugin.abiFixturePath}:invalid-json:${error instanceof Error ? error.message : String(error)}`); + } + + return errors; +} + +function missingMembers(expected, actual, label) { + const actualSet = new Set(actual); + return expected.filter(item => !actualSet.has(item)).map(item => `${label}:${item}:missing`); +} + +async function validateJsonDocument(schemaPath, documentPath, expected = {}) { + const errors = []; + if (!schemaPath) errors.push("schemaPath:missing"); + if (!documentPath) errors.push("documentPath:missing"); + if (!schemaPath || !documentPath) return errors; + + const absoluteSchemaPath = resolveWorkspacePath(schemaPath); + const absoluteDocumentPath = resolveWorkspacePath(documentPath); + if (!existsSync(absoluteSchemaPath)) errors.push(`${schemaPath}:missing`); + if (!existsSync(absoluteDocumentPath)) errors.push(`${documentPath}:missing`); + if (errors.length) return errors; + + try { + const schema = JSON.parse(await readFile(absoluteSchemaPath, "utf8")); + const document = JSON.parse(await readFile(absoluteDocumentPath, "utf8")); + errors.push(...validateSchemaSubset(schema, document)); + for (const [key, value] of Object.entries(expected)) { + if (document[key] !== value) errors.push(`${documentPath}:${key}:expected ${value} got ${document[key]}`); + } + } catch (error) { + errors.push(`${documentPath}:invalid-json:${error instanceof Error ? error.message : String(error)}`); + } + + return errors; +} + +async function validateJsonDocumentAgainstSchemaPath(schemaPath, document, label) { + const errors = []; + if (!schemaPath) return ["schemaPath:missing"]; + + const absoluteSchemaPath = resolveWorkspacePath(schemaPath); + if (!existsSync(absoluteSchemaPath)) return [`${schemaPath}:missing`]; + + try { + const schema = JSON.parse(await readFile(absoluteSchemaPath, "utf8")); + errors.push(...validateSchemaSubset(schema, document, label)); + } catch (error) { + errors.push(`${label}:invalid-json:${error instanceof Error ? error.message : String(error)}`); + } + + return errors; +} + +function extractProviderSchemaIds(schemas) { + return schemas.map(schema => { + if (typeof schema === "string") return schema; + if (schema && typeof schema === "object") return schema.schema || schema.id || ""; + return ""; + }).filter(Boolean).sort(); +} + +function validateSchemaSubset(schema, value, pointer = "$", rootSchema = schema) { + const errors = []; + if (!schema || typeof schema !== "object") return errors; + + if (schema.$ref) { + const resolved = resolveSchemaReference(rootSchema, schema.$ref); + return resolved + ? validateSchemaSubset(resolved, value, pointer, rootSchema) + : [`${pointer}:unresolved-ref:${schema.$ref}`]; + } + + if (schema.const !== undefined && value !== schema.const) { + errors.push(`${pointer}:const:${schema.const}`); + } + if (schema.enum && !schema.enum.includes(value)) { + errors.push(`${pointer}:enum:${schema.enum.join("|")}`); + } + + if (schema.type && !matchesSchemaType(value, schema.type)) { + errors.push(`${pointer}:type:${schema.type}`); + return errors; + } + + if (schema.minLength !== undefined && typeof value === "string" && value.length < schema.minLength) { + errors.push(`${pointer}:minLength:${schema.minLength}`); + } + if (schema.minItems !== undefined && Array.isArray(value) && value.length < schema.minItems) { + errors.push(`${pointer}:minItems:${schema.minItems}`); + } + + if (schema.required && value && typeof value === "object" && !Array.isArray(value)) { + for (const key of schema.required) { + if (value[key] === undefined) errors.push(`${pointer}.${key}:required`); + } + } + + if (schema.properties && value && typeof value === "object" && !Array.isArray(value)) { + for (const [key, childSchema] of Object.entries(schema.properties)) { + if (value[key] !== undefined) { + errors.push(...validateSchemaSubset(childSchema, value[key], `${pointer}.${key}`, rootSchema)); + } + } + } + + if (schema.items && Array.isArray(value)) { + value.forEach((item, index) => { + errors.push(...validateSchemaSubset(schema.items, item, `${pointer}[${index}]`, rootSchema)); + }); + } + + return errors; +} + +function resolveSchemaReference(rootSchema, reference) { + if (!reference.startsWith("#/")) return null; + return reference.slice(2).split("/").reduce((current, segment) => { + if (!current || typeof current !== "object") return null; + return current[segment.replaceAll("~1", "/").replaceAll("~0", "~")]; + }, rootSchema); +} + +function matchesSchemaType(value, type) { + switch (type) { + case "array": + return Array.isArray(value); + case "object": + return value !== null && typeof value === "object" && !Array.isArray(value); + case "string": + return typeof value === "string"; + case "number": + return typeof value === "number"; + case "boolean": + return typeof value === "boolean"; + default: + return true; + } +} + +function collectPluginCapabilityGaps(runtime, fixtureResults) { + const supported = new Map((runtime.supportedPlugins || []).map(plugin => [plugin.pluginId, new Set(plugin.capabilities || [])])); + const unsupported = new Map((runtime.unsupportedPlugins || []).map(plugin => [plugin.pluginId, plugin.reason || "unsupported"])); + const fixtureById = new Map((manifest.fixtures || []).map(fixture => [fixture.id, fixture])); + const gaps = []; + const fixtureIds = [...new Set([...(runtime.requiredFixtures || []), ...(runtime.pluginFixtures || [])])]; + for (const fixtureId of fixtureIds) { + const fixtureResult = fixtureResults.find(fixture => fixture.id === fixtureId); + if (!fixtureResult || fixtureResult.status !== "pass") continue; + const fixture = fixtureById.get(fixtureId); + for (const requirement of fixture?.requiredPlugins || []) { + if (unsupported.has(requirement.pluginId)) continue; + const capabilities = supported.get(requirement.pluginId); + if (!capabilities) { + gaps.push(`${fixtureId}:${requirement.pluginId}`); + continue; + } + for (const capability of requirement.capabilities || []) { + if (!capabilities.has(capability)) gaps.push(`${fixtureId}:${requirement.pluginId}:${capability}`); + } + } + } + return gaps; +} + +function collectUnsupportedPluginNotes(runtime, fixtureResults) { + const unsupported = new Map((runtime.unsupportedPlugins || []).map(plugin => [plugin.pluginId, plugin.reason || "unsupported"])); + const fixtureById = new Map((manifest.fixtures || []).map(fixture => [fixture.id, fixture])); + const notes = []; + const fixtureIds = [...new Set([...(runtime.requiredFixtures || []), ...(runtime.pluginFixtures || [])])]; + for (const fixtureId of fixtureIds) { + const fixtureResult = fixtureResults.find(fixture => fixture.id === fixtureId); + if (!fixtureResult || fixtureResult.status !== "pass") continue; + const fixture = fixtureById.get(fixtureId); + for (const requirement of fixture?.requiredPlugins || []) { + const reason = unsupported.get(requirement.pluginId); + if (reason) notes.push(`${fixtureId}:${requirement.pluginId}:${reason}`); + } + } + return notes; +} + +async function validateRuntimeCommandTransportSmoke(runtime) { + const smoke = runtime.commandTransportSmoke; + if (!smoke) return []; + + const errors = []; + if (smoke.schema !== "gamecult.eve.command_invocation.v1") { + errors.push(`schema:expected gamecult.eve.command_invocation.v1 got ${smoke.schema || ""}`); + } + + for (const candidate of smoke.expectedPaths || []) { + if (!existsSync(resolveRuntimeOwnedPath(runtime, candidate))) errors.push(`path:${candidate}:missing`); + } + + for (const expectation of smoke.expectedSourceSymbols || []) { + const sourcePath = expectation.path || ""; + const absolutePath = resolveRuntimeOwnedPath(runtime, sourcePath); + if (!existsSync(absolutePath)) { + errors.push(`source:${sourcePath}:missing`); + continue; + } + + const source = await readFile(absolutePath, "utf8"); + for (const symbol of expectation.contains || []) { + if (!source.includes(symbol)) errors.push(`source:${sourcePath}:${symbol}:missing`); + } + } + + return errors; +} + +async function validateRuntimeCapabilityManifest(runtime) { + const capabilityManifest = runtime.capabilityManifest; + if (!capabilityManifest) return []; + + const manifestDocumentPath = path.relative(repoRoot, resolveRuntimeOwnedPath(runtime, capabilityManifest.manifestPath)); + const errors = await validateJsonDocument(capabilityManifest.schemaPath, manifestDocumentPath, { + schema: "gamecult.eve.runtime_capability.v1", + runtimeId: runtime.id, + }); + if (errors.length) return errors; + + const documentPath = resolveRuntimeOwnedPath(runtime, capabilityManifest.manifestPath); + const document = JSON.parse(await readFile(documentPath, "utf8")); + const supportedFeatures = document.supportedFeatures || []; + const supportedPlugins = document.supportedPlugins || []; + const unsupportedPlugins = document.unsupportedPlugins || []; + const worldSurfaceLowering = document.worldSurfaceLowering || []; + const commandTransport = document.commandTransport || {}; + const lifecycle = document.lifecycle || {}; + + errors.push(...missingMembers(runtime.supportedFeatures || [], supportedFeatures, `${capabilityManifest.manifestPath}:supportedFeatures`)); + errors.push(...comparePluginCapabilityClaims( + runtime.supportedPlugins || [], + supportedPlugins, + `${capabilityManifest.manifestPath}:supportedPlugins`, + )); + errors.push(...compareUnsupportedPluginClaims( + runtime.unsupportedPlugins || [], + unsupportedPlugins, + `${capabilityManifest.manifestPath}:unsupportedPlugins`, + )); + errors.push(...compareWorldSurfaceLoweringClaims( + runtime.worldSurfaceLowering || [], + worldSurfaceLowering, + `${capabilityManifest.manifestPath}:worldSurfaceLowering`, + runtime, + )); + + const expectedCommandSchema = runtimeCommandTransportSchema(runtime); + if (expectedCommandSchema && commandTransport.schema !== expectedCommandSchema) { + errors.push(`${capabilityManifest.manifestPath}:commandTransport.schema:expected ${expectedCommandSchema} got ${commandTransport.schema || ""}`); + } + + const incubation = document.incubation || {}; + for (const [key, value] of Object.entries({ + ownerRepo: runtime.splitTarget || runtime.ownerRepo || "", + currentHostRepo: runtime.currentHostRepo || "Eve", + splitTarget: runtime.splitTarget || "", + graduationTrigger: runtime.graduationTrigger || "", + })) { + if (value && incubation[key] !== value) { + errors.push(`${capabilityManifest.manifestPath}:incubation.${key}:expected ${value} got ${incubation[key] || ""}`); + } + } + + errors.push(...compareRuntimeLifecycleClaims( + runtime.lifecycle || {}, + lifecycle, + capabilityManifest.manifestPath, + runtime, + )); + + return errors; +} + +function compareWorldSurfaceLoweringClaims(expectedClaims, actualClaims, label, runtime) { + const errors = []; + const actualByTarget = new Map((actualClaims || []).map(claim => [claim.targetId, claim])); + for (const expected of expectedClaims || []) { + const actual = actualByTarget.get(expected.targetId); + if (!actual) { + errors.push(`${label}:${expected.targetId}:missing`); + continue; + } + for (const key of ["supportLevel", "ownership"]) { + if ((expected[key] || "") !== (actual[key] || "")) { + errors.push(`${label}:${expected.targetId}.${key}:expected ${expected[key] || ""} got ${actual[key] || ""}`); + } + } + errors.push(...missingMembers(expected.surfaceKinds || [], actual.surfaceKinds || [], `${label}:${expected.targetId}.surfaceKinds`)); + errors.push(...missingMembers(expected.projectionKinds || [], actual.projectionKinds || [], `${label}:${expected.targetId}.projectionKinds`)); + errors.push(...missingMembers(expected.evidencePaths || [], actual.evidencePaths || [], `${label}:${expected.targetId}.evidencePaths`)); + for (const evidencePath of actual.evidencePaths || []) { + if (!existsSync(resolveRuntimeOwnedPath(runtime, evidencePath))) { + errors.push(`${label}:${expected.targetId}.evidencePath:${evidencePath}:missing`); + } + } + } + return errors; +} + +function compareRuntimeLifecycleClaims(expectedLifecycle, actualLifecycle, manifestPath, runtime) { + const errors = []; + for (const stage of ["release", "test", "capture"]) { + const expected = expectedLifecycle[stage]; + const actual = actualLifecycle[stage]; + const label = `${manifestPath}:lifecycle.${stage}`; + if (!expected) continue; + if (!actual) { + errors.push(`${label}:missing`); + continue; + } + + for (const key of ["ownerRepo", "status"]) { + if ((expected[key] || "") !== (actual[key] || "")) { + errors.push(`${label}.${key}:expected ${expected[key] || ""} got ${actual[key] || ""}`); + } + } + + errors.push(...missingMembers(expected.evidencePaths || [], actual.evidencePaths || [], `${label}.evidencePaths`)); + errors.push(...missingMembers(expected.pendingProofs || [], actual.pendingProofs || [], `${label}.pendingProofs`)); + errors.push(...compareReleaseContract(expected.releaseContract, actual.releaseContract, `${label}.releaseContract`, runtime)); + errors.push(...compareTestContract(expected.testContract, actual.testContract, `${label}.testContract`, runtime)); + errors.push(...compareCaptureContract(expected.captureContract, actual.captureContract, `${label}.captureContract`)); + + for (const evidencePath of actual.evidencePaths || []) { + if (!existsSync(resolveRuntimeOwnedPath(runtime, evidencePath))) { + errors.push(`${label}.evidencePaths:${evidencePath}:missing`); + } + } + } + return errors; +} + +function compareCaptureContract(expected, actual, label) { + const errors = []; + if (!expected) return errors; + if (!actual) return [`${label}:missing`]; + for (const key of [ + "ownerRepo", + "runtimeId", + "targetId", + "requestSchema", + "requestBuilder", + "advertisementPath", + "captureKind", + "artifactKind", + "artifactPattern", + "conformanceAttachment", + "requiredProvider", + "requiredSurface", + "authority", + "publishProof", + ]) { + if ((expected[key] || "") !== (actual[key] || "")) { + errors.push(`${label}.${key}:expected ${expected[key] || ""} got ${actual[key] || ""}`); + } + } + if (actual.requestSchema && !manifest.schemas?.[actual.requestSchema]) { + errors.push(`${label}.requestSchema:${actual.requestSchema}:missing-schema-catalog-entry`); + } + const expectedAdditionalSurfaces = captureSurfaceClaimKeys(expected.additionalProviderSurfaces || []); + const actualAdditionalSurfaces = captureSurfaceClaimKeys(actual.additionalProviderSurfaces || []); + errors.push(...missingMembers(expectedAdditionalSurfaces, actualAdditionalSurfaces, `${label}.additionalProviderSurfaces`)); + return errors; +} + +function captureSurfaceClaimKeys(claims) { + return (Array.isArray(claims) ? claims : []) + .filter(claim => claim && typeof claim === "object") + .map(claim => `${claim.providerId || ""}:${claim.surfaceId || ""}`) + .filter(claim => claim !== ":"); +} + +function compareTestContract(expected, actual, label, runtime) { + const errors = []; + if (!expected) return errors; + if (!actual) return [`${label}:missing`]; + for (const key of [ + "ownerRepo", + "runnerKind", + "runnerScript", + "consumerProject", + "unityExeDefault", + "packageName", + "testAssembly", + "testPlatform", + "resultsArtifact", + "logArtifact", + "manifestMutation", + ]) { + if ((expected[key] || "") !== (actual[key] || "")) { + errors.push(`${label}.${key}:expected ${expected[key] || ""} got ${actual[key] || ""}`); + } + } + if (actual.runnerScript && !existsSync(resolveRuntimeOwnedPath(runtime, actual.runnerScript))) { + errors.push(`${label}.runnerScript:${actual.runnerScript}:missing`); + } + if (actual.consumerProject && !existsSync(actual.consumerProject)) { + errors.push(`${label}.consumerProject:${actual.consumerProject}:missing`); + } + errors.push(...compareDependencyRecords( + expected.managedAssemblyDependencies || [], + actual.managedAssemblyDependencies || [], + `${label}.managedAssemblyDependencies`, + [ + "assemblyName", + "packageId", + "packageManager", + "ownerRepo", + "resolutionMode", + "dependencyContract.sourceAuthority", + "dependencyContract.packageSource", + "dependencyContract.packageId", + "dependencyContract.assemblyName", + "dependencyContract.versionPolicy", + "dependencyContract.unityResolution", + "dependencyContract.handoffRequirement", + ], + )); + return errors; +} + +function compareReleaseContract(expected, actual, label, runtime) { + const errors = []; + if (!expected) return errors; + if (!actual) return [`${label}:missing`]; + for (const key of ["ownerRepo", "packageName", "packageRoot", "versionSource", "tagPattern", "artifactKind", "requestSchema", "requestBuilder", "artifactBuilder", "artifactPattern", "publishProof"]) { + if ((expected[key] || "") !== (actual[key] || "")) { + errors.push(`${label}.${key}:expected ${expected[key] || ""} got ${actual[key] || ""}`); + } + } + if (actual.requestSchema && !manifest.schemas?.[actual.requestSchema]) { + errors.push(`${label}.requestSchema:${actual.requestSchema}:missing-schema-catalog-entry`); + } + for (const key of ["packageRoot", "versionSource", "requestBuilder", "artifactBuilder"]) { + if (actual[key] && !existsSync(resolveRuntimeOwnedPath(runtime, actual[key]))) { + errors.push(`${label}.${key}:${actual[key]}:missing`); + } + } + errors.push(...compareDependencyRecords( + expected.requiredPackageDependencies || [], + actual.requiredPackageDependencies || [], + `${label}.requiredPackageDependencies`, + ["packageName", "version", "packageManager", "ownerRepo", "purpose"], + )); + return errors; +} + +function resolveRuntimeOwnedPath(runtime, candidate) { + if (!candidate) return repoRoot; + if (path.isAbsolute(candidate)) return candidate; + const sourceRoot = runtime?.sourceRoot || "."; + return path.resolve(repoRoot, sourceRoot, candidate); +} + +function compareDependencyRecords(expectedRecords, actualRecords, label, keys) { + const errors = []; + for (const expected of expectedRecords) { + const identityKey = expected.packageName ? "packageName" : "assemblyName"; + const actual = actualRecords.find(candidate => candidate?.[identityKey] === expected[identityKey]); + if (!actual) { + errors.push(`${label}:${expected[identityKey]}:missing`); + continue; + } + for (const key of keys) { + const expectedValue = getByPath(expected, key) || ""; + const actualValue = getByPath(actual, key) || ""; + if (expectedValue !== actualValue) { + errors.push(`${label}:${expected[identityKey]}.${key}:expected ${expectedValue} got ${actualValue}`); + } + } + } + return errors; +} + +function getByPath(record, dottedPath) { + return dottedPath.split(".").reduce((current, segment) => current?.[segment], record); +} + +function comparePluginCapabilityClaims(expectedPlugins, actualPlugins, label) { + const errors = []; + const actual = new Map(actualPlugins.map(plugin => [plugin.pluginId, new Set(plugin.capabilities || [])])); + for (const plugin of expectedPlugins) { + const capabilities = actual.get(plugin.pluginId); + if (!capabilities) { + errors.push(`${label}:${plugin.pluginId}:missing`); + continue; + } + for (const capability of plugin.capabilities || []) { + if (!capabilities.has(capability)) errors.push(`${label}:${plugin.pluginId}:${capability}:missing`); + } + } + return errors; +} + +function compareUnsupportedPluginClaims(expectedPlugins, actualPlugins, label) { + const errors = []; + const actual = new Map(actualPlugins.map(plugin => [plugin.pluginId, plugin.reason || ""])); + for (const plugin of expectedPlugins) { + const reason = actual.get(plugin.pluginId); + if (!reason) { + errors.push(`${label}:${plugin.pluginId}:missing`); + continue; + } + if (plugin.reason && reason !== plugin.reason) { + errors.push(`${label}:${plugin.pluginId}:reason:expected ${plugin.reason} got ${reason}`); + } + } + return errors; +} + +function evaluateSplitTargets(splitTargets, runtimeResults) { + const runtimeById = new Map(runtimeResults.map(runtime => [runtime.id, runtime])); + return splitTargets.map(target => { + const requiredRuntimeStatuses = target.requiredRuntimeStatuses || ["active"]; + const requiredFeatures = target.requiredFeatures || []; + const requiredPlugins = target.requiredPlugins || []; + const proofs = target.proofs || []; + const runtimeIds = target.runtimes || []; + const blockers = []; + const blockerRecords = []; + const runtimeStatuses = {}; + const addBlocker = (text, record) => { + blockers.push(text); + blockerRecords.push({ + targetId: target.id, + ownerRepo: target.ownerRepo || "", + severity: "blocker", + subject: record.subject || "", + ...record, + text, + }); + }; + + for (const runtimeId of runtimeIds) { + const runtime = runtimeById.get(runtimeId); + if (!runtime) { + addBlocker(`runtime:${runtimeId}:missing`, { + kind: "runtime-missing", + subject: runtimeId, + runtimeId, + }); + continue; + } + + runtimeStatuses[runtimeId] = runtime.status; + if (!requiredRuntimeStatuses.includes(runtime.status)) { + addBlocker(`runtime:${runtimeId}:status:${runtime.status}`, { + kind: "runtime-status", + subject: runtimeId, + runtimeId, + status: runtime.status, + requiredStatuses: requiredRuntimeStatuses, + }); + } + + for (const feature of requiredFeatures) { + if (!runtime.supportedFeatures.includes(feature)) { + addBlocker(`runtime:${runtimeId}:feature:${feature}`, { + kind: "runtime-feature", + subject: `${runtimeId}:${feature}`, + runtimeId, + feature, + }); + } + } + + const supportedPlugins = new Map(runtime.supportedPlugins.map(plugin => [plugin.pluginId, new Set(plugin.capabilities || [])])); + const unsupportedPlugins = new Map((runtime.unsupportedPlugins || []).map(plugin => [plugin.pluginId, plugin.reason || "unsupported"])); + for (const requirement of requiredPlugins) { + if (unsupportedPlugins.has(requirement.pluginId)) { + addBlocker(`runtime:${runtimeId}:unsupported-plugin:${requirement.pluginId}:${unsupportedPlugins.get(requirement.pluginId)}`, { + kind: "runtime-plugin-projection", + subject: `${runtimeId}:${requirement.pluginId}`, + runtimeId, + pluginId: requirement.pluginId, + reason: unsupportedPlugins.get(requirement.pluginId), + }); + continue; + } + const capabilities = supportedPlugins.get(requirement.pluginId); + if (!capabilities) { + addBlocker(`runtime:${runtimeId}:plugin:${requirement.pluginId}`, { + kind: "runtime-plugin-missing", + subject: `${runtimeId}:${requirement.pluginId}`, + runtimeId, + pluginId: requirement.pluginId, + }); + continue; + } + + for (const capability of requirement.capabilities || []) { + if (!capabilities.has(capability)) { + addBlocker(`runtime:${runtimeId}:plugin:${requirement.pluginId}:${capability}`, { + kind: "runtime-plugin-capability", + subject: `${runtimeId}:${requirement.pluginId}:${capability}`, + runtimeId, + pluginId: requirement.pluginId, + capability, + }); + } + } + } + } + + for (const proof of proofs) { + const missingEvidence = (proof.evidencePaths || []).filter(candidate => !existsSync(path.join(repoRoot, candidate))); + if (proof.status !== "passed") { + const description = proof.description || "unnamed"; + addBlocker(`proof:${description}:status:${proof.status || "missing"}`, { + kind: "proof-status", + subject: description, + proof: description, + status: proof.status || "missing", + }); + } + for (const evidencePath of missingEvidence) { + const description = proof.description || "unnamed"; + addBlocker(`proof:${description}:evidence:${evidencePath}:missing`, { + kind: "proof-evidence", + subject: `${description}:${evidencePath}`, + proof: description, + evidencePath, + status: "missing", + }); + } + } + + for (const proof of target.pendingProofs || []) { + addBlocker(`proof:${proof}`, { + kind: "pending-proof", + subject: proof, + proof, + }); + } + + return { + id: target.id, + ownerRepo: target.ownerRepo || "", + repoRole: target.repoRole || "", + status: blockers.length ? "incubating" : target.status === "graduated" ? "graduated" : "ready-to-split", + declaredStatus: target.status || "", + runtimes: runtimeIds, + runtimeStatuses, + requiredRuntimeStatuses, + requiredFeatures, + requiredPlugins, + proofs, + pendingProofs: target.pendingProofs || [], + blockers, + blockerRecords, + }; + }); +} + +function requiredIncubationFields(entry) { + if (entry.kind === "external-owner") return ["ownerRepo", "repoRole"]; + if (entry.repoRole === "core") return ["ownerRepo", "repoRole"]; + return ["ownerRepo", "repoRole", "graduationTrigger"]; +} + +function flattenSurface(root) { + const nodes = []; + const visit = (node) => { + nodes.push(node); + for (const child of node.children || []) visit(child); + }; + visit(root); + return nodes; +} + +function readBindings(node) { + const props = node.props || {}; + const bindings = []; + if (props.bind) bindings.push(props.bind); + if (props.action?.target) bindings.push(props.action.target); + return bindings; +} + +function readCommandDescriptors(commands) { + return commands + .filter(command => command && typeof command === "object") + .map(command => ({ + ...command, + command: typeof command.command === "string" ? command.command : "", + schema: typeof command.schema === "string" ? command.schema : "", + })) + .filter(command => command.command); +} + +async function validateCommandDescriptors(fixture, commandDescriptors) { + if (!fixture.expect.commandDescriptorSchema) return []; + const schemaPath = manifest.schemas?.[fixture.expect.commandDescriptorSchema]; + if (!schemaPath) return [`${fixture.expect.commandDescriptorSchema}:schemaPath:missing`]; + const absoluteSchemaPath = path.join(repoRoot, schemaPath); + if (!existsSync(absoluteSchemaPath)) return [`${schemaPath}:missing`]; + const schema = JSON.parse(await readFile(absoluteSchemaPath, "utf8")); + return commandDescriptors.flatMap(command => + validateSchemaSubset(schema, command, `command:${command.command}`)); +} + +function readCommandReferences(nodes) { + return nodes.flatMap(node => { + const props = node.props || {}; + const action = objectProps(props.action); + const command = firstNonEmptyString(props.command, props.commandId, action.command, action.target, action.type, node.commandId); + if (!command) return []; + return [{ + nodeId: node.id || "", + kind: node.kind || "", + command, + }]; + }).sort((a, b) => `${a.nodeId}:${a.command}`.localeCompare(`${b.nodeId}:${b.command}`)); +} + +function readAuthorityWitnesses(nodes) { + return nodes.flatMap(node => { + const props = node.props || {}; + const freshness = objectProps(props.freshness); + const state = firstNonEmptyString(props.authorityState, props.truthState, props.commandState, freshness.state); + const owner = firstNonEmptyString(props.authorityOwner, props.owner, props.authority); + const witnessRef = firstNonEmptyString(props.witnessRef, props.sourceId); + const receiptRef = firstNonEmptyString(props.receiptRef, props.receiptId); + if (!state && !owner && !witnessRef && !receiptRef) return []; + return [{ + nodeId: node.id || "", + kind: node.kind || "", + state, + owner, + witnessRef, + receiptRef, + }]; + }).sort((a, b) => `${a.nodeId}:${a.state}`.localeCompare(`${b.nodeId}:${b.state}`)); +} + +function firstNonEmptyString(...values) { + for (const value of values) { + if (typeof value === "string" && value.trim()) return value; + } + return ""; +} + +function hasVisibleText(node) { + const props = objectProps(node.props); + return isTextLikeNode(node) && Boolean(firstNonEmptyString( + node.text, + node.value, + props.text, + props.value, + props.label, + props.title, + props.body, + )); +} + +function isTextLikeNode(node) { + const kind = typeof node.kind === "string" ? node.kind : ""; + return kind === "text" || kind.startsWith("text."); +} + +function hasLayoutBox(node) { + const layout = objectProps(node.layout); + return ["x", "y", "width", "height"].some(key => layout[key] !== undefined); +} + +function objectProps(value) { + return value && typeof value === "object" && !Array.isArray(value) ? value : {}; +} + +function countBy(items, selector) { + const counts = {}; + for (const item of items) { + const key = selector(item); + counts[key] = (counts[key] || 0) + 1; + } + return counts; +} + +function addCheck(checks, id, pass, detail) { + checks.push({ id, pass, ...detail }); +} + +function summarize(fixtures, runtimes, plugins, providers, splitTargets) { + return { + totalFixtures: fixtures.length, + passedFixtures: fixtures.filter(fixture => fixture.status === "pass").length, + failedFixtures: fixtures.filter(fixture => fixture.status !== "pass").length, + totalPlugins: plugins.length, + healthyPlugins: plugins.filter(plugin => ["active", "incubating", "external-owner-planned"].includes(plugin.status)).length, + failedPlugins: plugins.filter(plugin => !["active", "incubating", "external-owner-planned"].includes(plugin.status)).length, + totalProviders: providers.length, + advertisedProviders: providers.filter(provider => provider.status === "advertised").length, + failedProviders: providers.filter(provider => provider.status !== "advertised").length, + totalRuntimes: runtimes.length, + activeRuntimes: runtimes.filter(runtime => runtime.status === "active").length, + pendingRuntimes: runtimes.filter(runtime => runtime.status !== "active").length, + totalSplitTargets: splitTargets.length, + readySplitTargets: splitTargets.filter(target => target.status === "ready-to-split").length, + graduatedSplitTargets: splitTargets.filter(target => target.status === "graduated").length, + }; +} + +function buildConformanceExport(report) { + const packs = (report.conformancePacks || []).map(pack => { + const fixtures = report.fixtures + .filter(fixture => fixture.pack === pack.id) + .map(fixture => ({ + fixtureId: fixture.id, + title: fixture.title, + ownerRepo: fixture.ownerRepo, + status: fixture.status, + surface: fixture.surface, + metadataPath: fixture.metadataPath, + purpose: fixture.metadata?.purpose || "", + exitCriteria: fixture.metadata?.exitCriteria || "", + asserts: fixture.metadata?.asserts || [], + requiredPlugins: fixture.requiredPlugins || [], + optionalNestedPlugins: fixture.optionalNestedPlugins || fixture.metadata?.optionalNestedPlugins || [], + })); + const runtimeTargets = pack.id === "runtime" + ? (report.runtimes || []).map(runtime => ({ + runtimeId: runtime.id, + title: runtime.title, + status: runtime.status, + ownerRepo: runtime.ownerRepo, + splitTarget: runtime.splitTarget, + repoRole: runtime.repoRole, + supportedFeatures: runtime.supportedFeatures, + supportedPlugins: runtime.supportedPlugins, + unsupportedPlugins: runtime.unsupportedPlugins, + worldSurfaceLowering: runtime.worldSurfaceLowering, + requiredFixtures: runtime.requiredFixtures, + pluginFixtures: runtime.pluginFixtures, + capabilityManifestPath: runtime.capabilityManifest?.manifestPath || "", + lifecycleManifestPath: runtime.lifecycleManifest?.manifestPath || "", + commandTransportSchema: runtimeCommandTransportSchema(runtime), + captureStatus: runtime.capture?.status || "", + captureArtifacts: runtime.capture?.artifacts || [], + captureArtifactErrors: runtime.captureArtifactErrors || [], + lifecycle: runtime.lifecycle, + lifecycleErrors: runtime.lifecycleErrors || [], + missingEvidence: [ + ...runtime.missingPaths, + ...runtime.missingRequiredFixtures.map(id => `fixture:${id}`), + ...runtime.missingRequiredFeatures.map(id => `feature:${id}`), + ...runtime.commandTransportSmokeErrors.map(id => `command-smoke:${id}`), + ...runtime.capabilityManifestErrors.map(id => `runtime-capability:${id}`), + ...runtime.lifecycleErrors.map(id => `runtime-lifecycle:${id}`), + ...runtime.captureArtifactErrors.map(id => `runtime-capture:${id}`), + ...runtime.localProviderCatalogErrors.map(id => `local-provider-catalog:${id}`), + ], + })) + : []; + + return { + id: pack.id, + ownerRepo: pack.ownerRepo || "", + exitCriteria: pack.exitCriteria || "", + fixtures, + ...(runtimeTargets.length ? { runtimeTargets } : {}), + }; + }); + + return { + schema: "gamecult.eve.conformance_export.v1", + generatedAt: report.generatedAt, + sourceManifest: report.manifest, + boundaryRule: report.repoStrategy.boundaryRule || "", + incubationPolicy: report.repoStrategy.incubationPolicy || "", + schemaCatalog: buildSchemaCatalog(), + conformanceHandoffPath: report.repoStrategy.conformanceHandoffPath || "", + conformanceHandoffExportPath: makeHandoffExportPath("conformance", "EveConformance", report.repoStrategy.conformanceHandoffPath || ""), + packs, + capabilityMatrix: buildCapabilityMatrix(report), + runtimePluginProjectionCoverage: collectRuntimePluginProjectionCoverage(report), + runtimePluginProjectionGaps: collectRuntimePluginProjectionGaps(report), + providerRuntimePluginProjectionCoverage: collectProviderRuntimePluginProjectionCoverage(report), + localProviderCatalogs: collectLocalProviderCatalogs(report), + interactiveWorldSurfaces: collectInteractiveWorldSurfaces(report), + worldSurfaceLoweringCoverage: collectWorldSurfaceLoweringCoverage(report), + commandBoundaryCoverage: collectCommandBoundaryCoverage(report), + screenshotComparisonMetrics: collectScreenshotComparisonMetrics(report), + runtimeCaptureProbeCoverage: collectRuntimeCaptureProbeCoverage(report), + worldSurfaceLoweringGaps: collectWorldSurfaceLoweringGaps(report), + splitTargetBlockers: collectSplitTargetBlockers(report), + splitHandoffMoveCoverage: collectSplitHandoffMoveCoverage(report), + pluginHandoffMoveCoverage: collectPluginHandoffMoveCoverage(report), + providerHandoffMoveCoverage: collectProviderHandoffMoveCoverage(report), + pluginAbiOperationCoverage: collectPluginAbiOperationCoverage(report), + providerPluginRequirementCoverage: collectProviderPluginRequirementCoverage(report), + capabilityGaps: collectCapabilityGaps(report), + plugins: (report.plugins || []).map(plugin => ({ + pluginId: plugin.pluginId, + status: plugin.status, + ownerRepo: plugin.ownerRepo, + splitTarget: plugin.splitTarget, + graduationTrigger: plugin.graduationTrigger, + manifestPath: plugin.manifestPath, + advertisementPath: plugin.advertisementPath, + handoffPath: plugin.handoffPath, + handoffErrors: plugin.handoffErrors || [], + handoffExportPath: makeHandoffExportPath("plugin", plugin.pluginId, plugin.handoffPath), + abiFixturePath: plugin.abiFixturePath, + abiOperations: plugin.abiOperations || [], + abiOperationContracts: plugin.abiOperationContracts || [], + runtimeBoundary: plugin.runtimeBoundary, + optionalPlugins: plugin.optionalPlugins || [], + capabilities: plugin.capabilities, + })), + providers: (report.providers || []).map(provider => ({ + providerId: provider.providerId, + status: provider.status, + ownerRepo: provider.ownerRepo, + advertisementPath: provider.advertisementPath, + scenarioPath: provider.scenarioPath, + handoffPath: provider.handoffPath, + handoffErrors: provider.handoffErrors || [], + handoffExportPath: makeHandoffExportPath("provider", provider.providerId, provider.handoffPath), + scenarioId: provider.scenarioId, + receiptStates: provider.scenarioReceiptStates || [], + surfaces: provider.surfaceIds, + surfaceKinds: provider.surfaceKinds, + surfaceContracts: provider.surfaceContracts, + commands: provider.commandIds, + pluginRequirements: provider.pluginRequirements, + })), + runtimes: (report.runtimes || []).map(runtime => ({ + runtimeId: runtime.id, + status: runtime.status, + ownerRepo: runtime.ownerRepo, + splitTarget: runtime.splitTarget, + supportedFeatures: runtime.supportedFeatures, + supportedPlugins: runtime.supportedPlugins, + unsupportedPlugins: runtime.unsupportedPlugins, + capabilityManifestPath: runtime.capabilityManifest?.manifestPath || "", + capabilityManifestErrors: runtime.capabilityManifestErrors || [], + lifecycleManifestPath: runtime.lifecycleManifest?.manifestPath || "", + lifecycleErrors: runtime.lifecycleErrors || [], + splitHandoffPath: runtime.splitHandoffPath || "", + splitHandoffErrors: runtime.splitHandoffErrors || [], + splitHandoffExportPath: makeHandoffExportPath("runtime", runtime.id, runtime.splitHandoffPath || ""), + commandTransportSchema: runtimeCommandTransportSchema(runtime), + captureStatus: runtime.capture?.status || "", + captureArtifacts: runtime.capture?.artifacts || [], + captureArtifactErrors: runtime.captureArtifactErrors || [], + ...(runtime.lifecycle ? { lifecycle: runtime.lifecycle } : {}), + worldSurfaceLowering: runtime.worldSurfaceLowering || [], + })), + splitTargets: report.splitTargets || [], + }; +} + +function buildSchemaCatalog() { + return Object.entries(manifest.schemas || {}) + .map(([schemaId, schemaPath]) => ({ + schemaId, + path: schemaPath, + exportPath: `schemas/${path.basename(schemaPath)}`, + })) + .sort((left, right) => left.schemaId.localeCompare(right.schemaId)); +} + +function collectLocalProviderCatalogs(report) { + return (report.runtimes || []) + .filter(runtime => runtime.localProviderCatalog?.path) + .map(runtime => { + const catalogPath = runtime.localProviderCatalog.path; + let catalog = {}; + try { + catalog = JSON.parse(readFileSync(resolveWorkspacePath(catalogPath), "utf8")); + } catch { + catalog = {}; + } + const providers = Array.isArray(catalog.providers) ? catalog.providers : []; + const catalogDirectory = path.posix.dirname(normalizePath(catalogPath)); + return { + runtimeId: runtime.id, + ownerRepo: runtime.ownerRepo || "", + catalogPath, + catalogExportPath: makeLocalProviderCatalogExportPath(runtime.id, catalogPath), + schema: catalog.schema || "", + purpose: catalog.purpose || "", + status: (runtime.localProviderCatalogErrors || []).length ? "invalid" : "valid", + providerIds: providers.map(provider => provider.providerId).filter(Boolean).sort(), + advertisementPaths: providers + .map(provider => resolveCatalogPath(catalogDirectory, provider.advertisement || "")) + .filter(Boolean) + .sort(), + surfaceCount: providers.reduce((count, provider) => count + (Array.isArray(provider.surfaces) ? provider.surfaces.length : 0), 0), + errors: runtime.localProviderCatalogErrors || [], + }; + }) + .sort((left, right) => left.runtimeId.localeCompare(right.runtimeId)); +} + +function makeLocalProviderCatalogExportPath(runtimeId, catalogPath) { + if (!runtimeId || !catalogPath) return ""; + return `catalogs/${runtimeId}/${path.basename(catalogPath)}`; +} + +function buildCapabilityMatrix(report) { + const packs = (report.conformancePacks || []).map(pack => { + const fixtures = (report.fixtures || []).filter(fixture => fixture.pack === pack.id); + return { + packId: pack.id, + ownerRepo: pack.ownerRepo || "", + fixtureCount: fixtures.length, + passedFixtures: fixtures.filter(fixture => fixture.status === "pass").length, + failedFixtures: fixtures.filter(fixture => fixture.status !== "pass").length, + exitCriteria: pack.exitCriteria || "", + }; + }); + + const plugins = (report.plugins || []).map(plugin => ({ + pluginId: plugin.pluginId, + ownerRepo: plugin.ownerRepo, + status: plugin.status, + capabilities: plugin.capabilities || [], + abiOperations: plugin.abiOperations || [], + handoffExportPath: makeHandoffExportPath("plugin", plugin.pluginId, plugin.handoffPath), + })); + + const providers = (report.providers || []).map(provider => ({ + providerId: provider.providerId, + ownerRepo: provider.ownerRepo, + status: provider.status, + surfaces: provider.surfaceIds || [], + surfaceKinds: provider.surfaceKinds || [], + surfaceContracts: provider.surfaceContracts || [], + commands: provider.commandIds || [], + receiptStates: provider.scenarioReceiptStates || [], + handoffExportPath: makeHandoffExportPath("provider", provider.providerId, provider.handoffPath), + })); + + const runtimes = (report.runtimes || []).map(runtime => ({ + runtimeId: runtime.id, + ownerRepo: runtime.ownerRepo, + splitTarget: runtime.splitTarget, + status: runtime.status, + supportedFeatures: runtime.supportedFeatures || [], + supportedPlugins: runtime.supportedPlugins || [], + unsupportedPlugins: runtime.unsupportedPlugins || [], + worldSurfaceLowering: runtime.worldSurfaceLowering || [], + commandTransportSchema: runtimeCommandTransportSchema(runtime), + captureStatus: runtime.capture?.status || "", + captureArtifacts: runtime.capture?.artifacts || [], + splitHandoffExportPath: makeHandoffExportPath("runtime", runtime.id, runtime.splitHandoffPath || ""), + })); + + const splitTargets = (report.splitTargets || []).map(target => ({ + id: target.id, + ownerRepo: target.ownerRepo, + status: target.status, + blockerCount: (target.blockers || []).length, + passedProofs: (target.proofs || []).filter(proof => proof.status === "passed").length, + })); + + return { + schema: "gamecult.eve.capability_matrix.v1", + generatedAt: report.generatedAt, + summary: report.summary || {}, + capabilityGapCount: collectCapabilityGaps(report).length, + handoffs: { + conformance: report.repoStrategy.conformanceHandoffPath ? 1 : 0, + plugins: plugins.filter(plugin => plugin.handoffExportPath).length, + providers: providers.filter(provider => provider.handoffExportPath).length, + runtimes: runtimes.filter(runtime => runtime.splitHandoffExportPath).length, + }, + packs, + plugins, + providers, + runtimes, + splitTargets, + }; +} + +function collectCapabilityGaps(report) { + const gaps = []; + const addGap = ({ kind, ownerRepo, subjectId, gap, severity = "gap", detail = "" }) => { + if (!kind || !subjectId || !gap) return; + gaps.push({ + kind, + ownerRepo: ownerRepo || "", + subjectId, + gap, + severity, + detail, + }); + }; + + for (const plugin of report.plugins || []) { + for (const error of [ + ...(plugin.missingPaths || []).map(id => `path:${id}`), + ...(plugin.missingRequiredFixtures || []).map(id => `fixture:${id}`), + ...(plugin.missingIncubationFields || []).map(id => `metadata:${id}`), + ...(plugin.schemaErrors || []).map(id => `schema:${id}`), + ...(plugin.advertisementErrors || []).map(id => `advertisement:${id}`), + ...(plugin.runtimeBoundaryErrors || []).map(id => `runtime-boundary:${id}`), + ...(plugin.abiErrors || []).map(id => `abi:${id}`), + ...(plugin.handoffErrors || []).map(id => `handoff:${id}`), + ]) { + addGap({ + kind: "plugin", + ownerRepo: plugin.ownerRepo, + subjectId: plugin.pluginId, + gap: error, + severity: "blocker", + }); + } + } + + for (const provider of report.providers || []) { + for (const error of [ + ...(provider.missingPaths || []).map(id => `path:${id}`), + ...(provider.missingRequiredFixtures || []).map(id => `fixture:${id}`), + ...(provider.missingIncubationFields || []).map(id => `metadata:${id}`), + ...(provider.advertisementErrors || []).map(id => `advertisement:${id}`), + ...(provider.scenarioErrors || []).map(id => `scenario:${id}`), + ...(provider.missingSchemas || []).map(id => `schema:${id}`), + ...(provider.missingSurfaces || []).map(id => `surface:${id}`), + ...(provider.missingSurfaceKinds || []).map(id => `surface-kind:${id}`), + ...(provider.missingCommands || []).map(id => `command:${id}`), + ...(provider.pluginRequirementErrors || []).map(id => `plugin:${id}`), + ...(provider.handoffErrors || []).map(id => `handoff:${id}`), + ]) { + addGap({ + kind: "provider", + ownerRepo: provider.ownerRepo, + subjectId: provider.providerId, + gap: error, + severity: "blocker", + }); + } + } + + for (const runtime of report.runtimes || []) { + if (runtime.capture?.status === "missing") { + addGap({ + kind: "runtime", + ownerRepo: runtime.ownerRepo, + subjectId: runtime.id, + gap: "capture:missing", + severity: runtime.kind === "active" ? "blocker" : "activation-blocker", + }); + } + for (const error of [ + ...(runtime.missingPaths || []).map(id => `path:${id}`), + ...(runtime.missingExternalPaths || []).map(id => `external:${id}`), + ...(runtime.missingSourceSymbols || []).map(id => `source:${id}`), + ...(runtime.missingExternalSourceSymbols || []).map(id => `external-source:${id}`), + ...(runtime.missingRequiredFixtures || []).map(id => `fixture:${id}`), + ...(runtime.missingRequiredFeatures || []).map(id => `feature:${id}`), + ...(runtime.commandTransportSmokeErrors || []).map(id => `command-smoke:${id}`), + ...(runtime.capabilityManifestErrors || []).map(id => `runtime-capability:${id}`), + ...(runtime.lifecycleErrors || []).map(id => `runtime-lifecycle:${id}`), + ...(runtime.captureArtifactErrors || []).map(id => `runtime-capture:${id}`), + ...(runtime.splitHandoffErrors || []).map(id => `runtime-split-handoff:${id}`), + ...(runtime.localProviderCatalogErrors || []).map(id => `local-provider-catalog:${id}`), + ...(runtime.pluginCapabilityGaps || []).map(id => `plugin-capability:${id}`), + ]) { + addGap({ + kind: "runtime", + ownerRepo: runtime.ownerRepo, + subjectId: runtime.id, + gap: error, + severity: "blocker", + }); + } + for (const note of runtime.unsupportedPluginNotes || []) { + addGap({ + kind: "runtime", + ownerRepo: resolveRuntimeProjectionOwnerRepo(runtime), + subjectId: runtime.id, + gap: `unsupported-plugin:${note}`, + severity: "declared-gap", + }); + } + } + + for (const gap of collectWorldSurfaceLoweringGaps(report)) { + addGap({ + kind: "runtime", + ownerRepo: gap.ownerRepo, + subjectId: gap.runtimeId, + gap: `provider:${gap.providerId}:surface:${gap.surfaceId}:world-lowering-target:${gap.targetId}:missing-runtime`, + severity: gap.severity, + detail: gap.detail, + }); + } + + for (const target of report.splitTargets || []) { + for (const blocker of target.blockers || []) { + addGap({ + kind: "split-target", + ownerRepo: target.ownerRepo, + subjectId: target.id, + gap: blocker, + severity: "blocker", + }); + } + } + + for (const error of report.repoStrategy?.conformanceHandoffErrors || []) { + addGap({ + kind: "conformance", + ownerRepo: "EveConformance", + subjectId: "EveConformance", + gap: `handoff:${error}`, + severity: "blocker", + }); + } + + return gaps; +} + +function collectRuntimePluginProjectionGaps(report) { + const gaps = []; + for (const runtime of report.runtimes || []) { + for (const plugin of runtime.unsupportedPlugins || []) { + if (!plugin.pluginId) continue; + gaps.push({ + runtimeId: runtime.id, + runtimeStatus: runtime.status || "", + ownerRepo: resolveRuntimeProjectionOwnerRepo(runtime), + splitTarget: runtime.splitTarget || "", + pluginId: plugin.pluginId, + severity: "declared-gap", + reason: plugin.reason || "unsupported", + requiredFixtures: runtime.requiredFixtures || [], + pluginFixtures: runtime.pluginFixtures || [], + }); + } + } + return gaps; +} + +function collectRuntimePluginProjectionCoverage(report) { + const pluginById = new Map((report.plugins || []).map(plugin => [plugin.pluginId, plugin])); + const coverage = []; + for (const runtime of report.runtimes || []) { + for (const plugin of runtime.supportedPlugins || []) { + if (!plugin.pluginId) continue; + const pluginRecord = pluginById.get(plugin.pluginId); + coverage.push({ + runtimeId: runtime.id, + runtimeStatus: runtime.status || "", + runtimeOwnerRepo: resolveRuntimeProjectionOwnerRepo(runtime), + splitTarget: runtime.splitTarget || "", + pluginId: plugin.pluginId, + pluginOwnerRepo: pluginRecord?.ownerRepo || "", + pluginStatus: pluginRecord?.status || "", + status: "supported", + severity: "ok", + capabilities: plugin.capabilities || [], + reason: "", + requiredFixtures: runtime.requiredFixtures || [], + pluginFixtures: runtime.pluginFixtures || [], + }); + } + for (const plugin of runtime.unsupportedPlugins || []) { + if (!plugin.pluginId) continue; + const pluginRecord = pluginById.get(plugin.pluginId); + coverage.push({ + runtimeId: runtime.id, + runtimeStatus: runtime.status || "", + runtimeOwnerRepo: resolveRuntimeProjectionOwnerRepo(runtime), + splitTarget: runtime.splitTarget || "", + pluginId: plugin.pluginId, + pluginOwnerRepo: pluginRecord?.ownerRepo || "", + pluginStatus: pluginRecord?.status || "", + status: "unsupported", + severity: "declared-gap", + capabilities: [], + reason: plugin.reason || "unsupported", + requiredFixtures: runtime.requiredFixtures || [], + pluginFixtures: runtime.pluginFixtures || [], + }); + } + } + return coverage.sort((left, right) => + `${left.runtimeId}:${left.pluginId}:${left.status}`.localeCompare(`${right.runtimeId}:${right.pluginId}:${right.status}`)); +} + +function collectProviderRuntimePluginProjectionCoverage(report) { + const runtimeProjectionByKey = new Map(); + for (const record of collectRuntimePluginProjectionCoverage(report)) { + runtimeProjectionByKey.set(`${record.runtimeId}:${record.pluginId}`, record); + } + const fixtureIdByPath = new Map((report.fixtures || []) + .map(fixture => [normalizePath(fixture.surface?.path || ""), fixture.id])); + const coverage = []; + for (const provider of report.providers || []) { + for (const requirement of provider.pluginRequirements || []) { + const fixtureId = fixtureIdByPath.get(normalizePath(requirement.surfaceKey || "")) || ""; + if (!fixtureId) continue; + const runtimes = (report.runtimes || []) + .filter(runtime => (runtime.pluginFixtures || []).includes(fixtureId)); + for (const runtime of runtimes) { + const projection = runtimeProjectionByKey.get(`${runtime.id}:${requirement.pluginId}`); + coverage.push(buildProviderRuntimePluginProjectionRecord(provider, requirement, runtime, projection, fixtureId)); + } + } + } + return coverage.sort((left, right) => + `${left.providerId}:${left.surfaceId}:${left.runtimeId}:${left.pluginId}` + .localeCompare(`${right.providerId}:${right.surfaceId}:${right.runtimeId}:${right.pluginId}`)); +} + +function buildProviderRuntimePluginProjectionRecord(provider, requirement, runtime, projection, fixtureId) { + const availability = requirement.availability || "required"; + const optional = availability !== "required"; + const runtimeCapabilities = new Set(projection?.capabilities || []); + const missingRuntimeRequiredCapabilities = (requirement.requiredCapabilities || []) + .filter(capability => !runtimeCapabilities.has(capability)); + const missingRuntimeOptionalCapabilities = (requirement.optionalCapabilities || []) + .filter(capability => projection?.status === "supported" && !runtimeCapabilities.has(capability)); + let status = optional ? "optional-supported" : "supported"; + if (!projection) { + status = optional ? "optional-missing-runtime-projection" : "missing-runtime-projection"; + } else if (projection.status !== "supported") { + status = optional ? "optional-unsupported-runtime-projection" : "unsupported-runtime-projection"; + } else if (missingRuntimeRequiredCapabilities.length) { + status = optional ? "optional-missing-runtime-capability" : "missing-runtime-capability"; + } else if (missingRuntimeOptionalCapabilities.length) { + status = "optional-missing-runtime-capability"; + } + + return { + providerId: provider.providerId, + providerOwnerRepo: provider.ownerRepo || "", + surfaceId: requirement.surfaceId || "", + surfaceKey: requirement.surfaceKey || "", + fixtureId, + runtimeId: runtime.id, + runtimeStatus: runtime.status || "", + runtimeOwnerRepo: resolveRuntimeProjectionOwnerRepo(runtime), + splitTarget: runtime.splitTarget || "", + pluginId: requirement.pluginId || "", + pluginOwnerRepo: projection?.pluginOwnerRepo || "", + availability, + status, + severity: status === "supported" || status === "optional-supported" + ? "ok" + : optional ? "degraded" : "blocker", + requiredCapabilities: requirement.requiredCapabilities || [], + optionalCapabilities: requirement.optionalCapabilities || [], + runtimeCapabilities: projection?.capabilities || [], + missingRuntimeRequiredCapabilities, + missingRuntimeOptionalCapabilities, + reason: projection?.reason || "", + }; +} + +function collectPluginAbiOperationCoverage(report) { + const requiredOperations = ["describe", "validate", "project", "lower", "measure", "apply"]; + const coverage = []; + for (const plugin of report.plugins || []) { + const operations = new Map((plugin.abiOperationContracts || []).map(contract => [contract.operation, contract])); + for (const operation of requiredOperations) { + const contract = operations.get(operation); + const input = contract?.input || {}; + const expect = contract?.expect || {}; + coverage.push({ + pluginId: plugin.pluginId, + ownerRepo: plugin.ownerRepo || "", + splitTarget: plugin.splitTarget || "", + status: contract ? "contracted" : "missing-contract", + operation, + abiFixturePath: plugin.abiFixturePath || "", + inputKeys: Object.keys(input).sort(), + expectKeys: Object.keys(expect).sort(), + projectionKind: expect.projectionKind || "", + loweringKind: expect.loweringKind || "", + measurementKind: expect.measurementKind || "", + commandEnvelope: expect.commandEnvelope || "", + receiptSchema: expect.receiptSchema || "", + preservesProviderAuthority: expect.preservesProviderAuthority === true, + }); + } + } + return coverage; +} + +function collectProviderPluginRequirementCoverage(report) { + const pluginById = new Map((report.plugins || []).map(plugin => [plugin.pluginId, plugin])); + const coverage = []; + for (const provider of report.providers || []) { + for (const requirement of provider.pluginRequirements || []) { + const plugin = pluginById.get(requirement.pluginId); + const pluginCapabilities = new Set(plugin?.capabilities || []); + const missingRequiredCapabilities = (requirement.requiredCapabilities || []) + .filter(capability => !pluginCapabilities.has(capability)); + const missingOptionalCapabilities = (requirement.optionalCapabilities || []) + .filter(capability => plugin && !pluginCapabilities.has(capability)); + const optional = requirement.availability && requirement.availability !== "required"; + coverage.push({ + providerId: provider.providerId, + providerOwnerRepo: provider.ownerRepo || "", + surfaceId: requirement.surfaceId || "", + pluginId: requirement.pluginId || "", + pluginOwnerRepo: plugin?.ownerRepo || "", + pluginStatus: plugin?.status || "missing", + versionRange: requirement.versionRange || "", + availability: requirement.availability || "required", + status: !plugin + ? optional ? "optional-missing-plugin" : "missing-plugin" + : missingRequiredCapabilities.length + ? optional ? "optional-missing-capability" : "missing-capability" + : optional ? "optional-satisfied" : "satisfied", + requiredCapabilities: requirement.requiredCapabilities || [], + optionalCapabilities: requirement.optionalCapabilities || [], + missingRequiredCapabilities, + missingOptionalCapabilities, + }); + } + } + return coverage; +} + +function collectScreenshotComparisonMetrics(report) { + const fixtureById = new Map((report.fixtures || []).map(fixture => [fixture.id, fixture])); + const metrics = []; + for (const runtime of report.runtimes || []) { + const fixtureIds = [...new Set([ + ...(runtime.requiredFixtures || []), + ...(runtime.pluginFixtures || []), + ])].sort(); + for (const fixtureId of fixtureIds) { + const fixture = fixtureById.get(fixtureId); + if (!fixture) continue; + metrics.push(buildStructureMetric(runtime, fixture)); + metrics.push(buildColorTokenMetric(runtime, fixture)); + metrics.push(buildBoundingBoxMetric(runtime, fixture)); + metrics.push(buildTextPresenceMetric(runtime, fixture)); + } + } + return metrics.sort((left, right) => + `${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 providerMatches = !contract.requiredProvider || artifact.providerId === contract.requiredProvider; + const surfaceMatches = !contract.requiredSurface || artifact.surfaceId === contract.requiredSurface; + const contractTargetMatches = providerMatches && surfaceMatches; + const contractArtifactPresent = hasContract && artifactPresent && contractTargetMatches && currentArtifactKind === contractArtifactKind && !errors.length; + const controlArtifactPresent = hasContract && artifactPresent && !contractTargetMatches && 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 (controlArtifactPresent) { + status = "control-artifact-present-product-capture-pending"; + severity = "blocker"; + detail = `Runtime publishes a ${currentArtifactKind} control artifact for ${artifact.providerId || "another provider"}; ${contract.requiredProvider || "the required provider"} product capture remains owner-repo work.`; + } 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 || "", + additionalProviderSurfaces: contract.additionalProviderSurfaces || [], + 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:") + || check.id.startsWith("minimum:") + || check.id.startsWith("controlPart:") + || check.id.startsWith("skin:") + || check.id.startsWith("embedded:")); + return buildScreenshotMetric(runtime, fixture, { + metricKind: "structure", + status: checks.length ? checks.every(check => check.pass) ? "pass" : "fail" : "not-required", + score: ratio(checks.filter(check => check.pass).length, checks.length), + expected: checks.map(check => check.id).sort(), + actual: { + kindCounts: fixture.kindCounts || {}, + embeddedDocuments: fixture.embeddedDocuments || [], + controlParts: fixture.controlParts || [], + controlSkins: fixture.controlSkins || [], + }, + detail: "Compares authored surface structure and slot/control anatomy, not byte-identical pixels.", + }); +} + +function buildColorTokenMetric(runtime, fixture) { + const checks = (fixture.checks || []).filter(check => check.id.startsWith("styleToken:")); + return buildScreenshotMetric(runtime, fixture, { + metricKind: "color-tokens", + status: checks.length ? checks.every(check => check.pass) ? "pass" : "fail" : "not-required", + score: ratio(checks.filter(check => check.pass).length, checks.length), + expected: checks.map(check => check.id.replace("styleToken:", "")).sort(), + actual: fixture.styleTokens || [], + detail: "Compares declared CultUI style token presence before runtime-specific color sampling exists.", + }); +} + +function buildBoundingBoxMetric(runtime, fixture) { + const layoutProbe = readRuntimeLayoutProbe(runtime, fixture.id); + if (layoutProbe.config) { + if (layoutProbe.document && !layoutProbe.errors.length) { + const measured = Number(layoutProbe.document.summary?.measuredNodeCount || 0); + const total = Number(layoutProbe.document.summary?.nodeCount || 0); + return buildScreenshotMetric(runtime, fixture, { + metricKind: "bounding-boxes", + status: measured > 0 ? "pass" : "fail", + score: ratio(measured, total), + expected: { + schema: layoutProbe.config.schema || "", + path: layoutProbe.config.path || "", + providerId: layoutProbe.config.providerId || "", + surfaceId: layoutProbe.config.surfaceId || "", + }, + actual: { + nodeCount: total, + measuredNodeCount: measured, + zeroAreaCount: Number(layoutProbe.document.summary?.zeroAreaCount || 0), + path: layoutProbe.config.path || "", + }, + evidenceLayer: "runtime-layout-probe", + detail: "Compares runtime-owned DOM bounding boxes from the web reference layout probe.", + }); + } + return buildScreenshotMetric(runtime, fixture, { + metricKind: "bounding-boxes", + status: "fail", + score: 0, + expected: { + schema: layoutProbe.config.schema || "", + path: layoutProbe.config.path || "", + }, + actual: { + errors: layoutProbe.errors, + }, + evidenceLayer: "runtime-layout-probe", + detail: "Declared runtime layout probe exists, but the artifact is missing or invalid.", + }); + } + + const captureStatus = runtime.capture?.status || ""; + if (captureStatus === "json-grid") { + const artifact = (runtime.capture?.artifacts || []).find(candidate => candidate.kind === "json-grid"); + const errors = runtime.captureArtifactErrors || []; + return buildScreenshotMetric(runtime, fixture, { + metricKind: "bounding-boxes", + status: errors.length ? "fail" : "terminal-grid-capture", + score: errors.length ? 0 : 1, + expected: { + source: "runtime terminal grid capture", + schema: artifact?.schema || "gamecult.eve.tui_grid.v1", + path: artifact?.path || "", + }, + actual: { + captureStatus, + artifactKind: artifact?.kind || "", + errors, + }, + evidenceLayer: "runtime-terminal-grid-capture", + detail: errors.length + ? "Declared terminal grid capture artifact is missing or invalid." + : "Terminal grid capture is present; graphical bounding boxes are not a TUI invariant.", + }); + } + const runtimeHasCaptureBody = ["chrome-headless", "ssh-png", "adb-png", "golden", "ssh-golden"].includes(captureStatus); + let status = "pass"; + let score = 1; + let detail = "Authored layout boxes are present in the surface evidence."; + if (!fixture.layoutBoxCount) { + status = runtimeHasCaptureBody ? "pending-runtime-probe" : "pending-capture"; + score = 0; + detail = runtimeHasCaptureBody + ? "Runtime capture exists, but no exported layout/bounding-box probe is attached yet." + : "Bounding-box comparison is blocked until this runtime publishes capture or layout probe artifacts."; + } + return buildScreenshotMetric(runtime, fixture, { + metricKind: "bounding-boxes", + status, + score, + expected: { + source: runtimeHasCaptureBody ? "runtime layout/capture probe" : "runtime capture artifact", + }, + actual: { + layoutBoxCount: fixture.layoutBoxCount || 0, + captureStatus, + }, + detail, + }); +} + +function buildTextPresenceMetric(runtime, fixture) { + const expectedText = (fixture.textNodeCount || 0) > 0; + const status = expectedText + ? fixture.nonEmptyTextNodeCount > 0 ? "pass" : "fail" + : "not-required"; + return buildScreenshotMetric(runtime, fixture, { + metricKind: "text-presence", + status, + score: expectedText ? ratio(fixture.nonEmptyTextNodeCount || 0, fixture.textNodeCount || 1) : 1, + expected: { + textNodes: fixture.textNodeCount || 0, + }, + actual: { + nonEmptyTextNodes: fixture.nonEmptyTextNodeCount || 0, + }, + detail: "Compares authored text presence before runtime OCR/text-layer extraction exists.", + }); +} + +function buildScreenshotMetric(runtime, fixture, metric) { + return { + runtimeId: runtime.id, + runtimeStatus: runtime.status || "", + runtimeOwnerRepo: resolveRuntimeProjectionOwnerRepo(runtime), + splitTarget: runtime.splitTarget || "", + fixtureId: fixture.id, + fixturePack: fixture.pack || "", + fixtureOwnerRepo: fixture.ownerRepo || "", + captureStatus: runtime.capture?.status || "", + metricKind: metric.metricKind, + status: metric.status, + score: Number(metric.score.toFixed(3)), + expected: metric.expected, + actual: metric.actual, + evidenceLayer: metric.evidenceLayer || "surface-structure", + detail: metric.detail, + }; +} + +function ratio(numerator, denominator) { + return denominator ? Math.max(0, Math.min(1, numerator / denominator)) : 1; +} + +function readRuntimeLayoutProbe(runtime, fixtureId) { + const config = (runtime.capture?.layoutProbes || []).find(candidate => candidate.fixtureId === fixtureId); + if (!config) return { config: null, document: null, errors: [] }; + const errors = []; + const probePath = config.path || ""; + const absoluteProbePath = path.join(repoRoot, probePath); + if (!probePath || !existsSync(absoluteProbePath)) { + return { config, document: null, errors: [`${probePath || "layoutProbe.path"}:missing`] }; + } + let document; + try { + document = JSON.parse(readFileSync(absoluteProbePath, "utf8")); + } catch (error) { + return { config, document: null, errors: [`${probePath}:invalid-json:${error instanceof Error ? error.message : String(error)}`] }; + } + if (config.schema && document.schema !== config.schema) errors.push(`${probePath}:schema:expected ${config.schema} got ${document.schema || ""}`); + if (config.providerId && document.providerId !== config.providerId) errors.push(`${probePath}:providerId:expected ${config.providerId} got ${document.providerId || ""}`); + if (config.surfaceId && document.surfaceId !== config.surfaceId) errors.push(`${probePath}:surfaceId:expected ${config.surfaceId} got ${document.surfaceId || ""}`); + if (document.fixtureId !== fixtureId) errors.push(`${probePath}:fixtureId:expected ${fixtureId} got ${document.fixtureId || ""}`); + const schemaPath = config.schema ? manifest.schemas?.[config.schema] : ""; + if (schemaPath) { + try { + const schema = JSON.parse(readFileSync(path.join(repoRoot, schemaPath), "utf8")); + errors.push(...validateSchemaSubset(schema, document, `layoutProbe:${fixtureId}`)); + } catch (error) { + errors.push(`${schemaPath}:unreadable:${error instanceof Error ? error.message : String(error)}`); + } + } + return { config, document, errors }; +} + +function validateRuntimeCaptureArtifacts(runtime, capture) { + const artifacts = Array.isArray(capture?.artifacts) ? capture.artifacts : []; + const errors = []; + for (const artifact of artifacts) { + const artifactPath = artifact.path || ""; + if (!artifactPath) { + errors.push("artifact.path:missing"); + continue; + } + const absoluteArtifactPath = resolveRuntimeOwnedPath(runtime, artifactPath); + if (!existsSync(absoluteArtifactPath)) { + errors.push(`${artifactPath}:missing`); + continue; + } + if (artifact.kind === "png") { + continue; + } + let document; + try { + document = JSON.parse(readFileSync(absoluteArtifactPath, "utf8")); + } catch (error) { + errors.push(`${artifactPath}:invalid-json:${error instanceof Error ? error.message : String(error)}`); + continue; + } + for (const [field, expected] of [ + ["schema", artifact.schema], + ["runtimeId", artifact.runtimeId], + ["providerId", artifact.providerId], + ["surfaceId", artifact.surfaceId], + ]) { + if (expected && document[field] !== expected) { + errors.push(`${artifactPath}:${field}:expected ${expected} got ${document[field] || ""}`); + } + } + if (artifact.schema && manifest.schemas?.[artifact.schema]) { + try { + const schema = JSON.parse(readFileSync(path.join(repoRoot, manifest.schemas[artifact.schema]), "utf8")); + errors.push(...validateSchemaSubset(schema, document, `captureArtifact:${artifactPath}`)); + } catch (error) { + errors.push(`${manifest.schemas[artifact.schema]}:unreadable:${error instanceof Error ? error.message : String(error)}`); + } + } + for (const linkedPath of [artifact.sourceSurfacePath, artifact.requestPath].filter(Boolean)) { + if (!existsSync(resolveRuntimeOwnedPath(runtime, linkedPath))) { + errors.push(`${artifactPath}:linked-path:${linkedPath}:missing`); + } + } + } + return errors; +} + +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 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 collectProviderHandoffMoveCoverage(report) { + const records = []; + for (const provider of report.providers || []) { + if (!provider.handoffPath) continue; + const absoluteHandoffPath = path.join(repoRoot, provider.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(buildProviderHandoffMoveRecord(provider, handoff, moveSet, { + pathKind: "current", + sourcePath, + pathExists: existsSync(absoluteSourcePath), + })); + } + if (!currentPaths.length) { + records.push(buildProviderHandoffMoveRecord(provider, handoff, moveSet, { + pathKind: "replacement-required", + sourcePath: "(none)", + pathExists: false, + })); + } + } + } + return records.sort((left, right) => + `${left.providerId}:${left.moveSetId}:${left.pathKind}:${left.sourcePath}` + .localeCompare(`${right.providerId}:${right.moveSetId}:${right.pathKind}:${right.sourcePath}`)); +} + +function buildProviderHandoffMoveRecord(provider, handoff, moveSet, source) { + const status = source.pathKind === "replacement-required" + ? "no-source-paths" + : source.pathExists ? "exists" : "missing"; + return { + providerId: handoff.providerId || provider.providerId || "", + providerOwnerRepo: handoff.ownerRepo || provider.ownerRepo || "", + handoffPath: provider.handoffPath || "", + handoffExportPath: makeHandoffExportPath("provider", provider.providerId, provider.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 || []) { + for (const surface of provider.surfaceContracts || []) { + const worldInteraction = surface.worldInteraction || {}; + surfaces.push({ + providerId: provider.providerId, + ownerRepo: provider.ownerRepo || "", + surfaceId: surface.surfaceId, + surfaceKind: surface.surfaceKind || "", + interactionModel: surface.interactionModel || "", + projectionKind: worldInteraction.projectionKind || "", + stateSchemas: worldInteraction.stateSchemas || [], + commandBoundary: worldInteraction.commandBoundary || "", + receiptSchema: worldInteraction.receiptSchema || "", + loweringTargets: worldInteraction.loweringTargets || [], + ownership: worldInteraction.ownership || "", + }); + } + } + return surfaces.sort((left, right) => + `${left.providerId}:${left.surfaceId}`.localeCompare(`${right.providerId}:${right.surfaceId}`)); +} + +function collectWorldSurfaceLoweringCoverage(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 status = claim ? "claimed" : runtime ? "missing-claim" : "missing-runtime"; + coverage.push({ + providerId: surface.providerId, + providerOwnerRepo: surface.ownerRepo || "", + surfaceId: surface.surfaceId, + surfaceKind: surface.surfaceKind || "", + projectionKind: surface.projectionKind || "", + targetId, + status, + severity: status === "claimed" ? "ok" : "blocker", + runtimeId: runtime?.id || "world-surface-lowering", + runtimeOwnerRepo: runtime ? resolveRuntimeProjectionOwnerRepo(runtime) : "Eve", + splitTarget: runtime?.splitTarget || "", + runtimeStatus: runtime?.status || "", + supportLevel: claim?.supportLevel || "", + loweringOwnership: claim?.ownership || "", + }); + } + } + 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 ? runtimeCommandTransportSchema(runtime) : ""; + 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_invocation.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 + || runtime.lifecycle?.capture?.ownerRepo + || runtime.splitTarget + || runtime.ownerRepo + || ""; +} + +function collectWorldSurfaceLoweringGaps(report) { + const { claimByTarget, runtimeByTarget } = buildWorldSurfaceLoweringTargetIndex(report); + const gaps = []; + for (const provider of report.providers || []) { + for (const surface of provider.surfaceContracts || []) { + for (const targetId of surface.worldInteraction?.loweringTargets || []) { + if (claimByTarget.has(targetId)) continue; + const targetRuntime = runtimeByTarget.get(targetId); + gaps.push({ + providerId: provider.providerId, + providerOwnerRepo: provider.ownerRepo || "", + surfaceId: surface.surfaceId, + surfaceKind: surface.surfaceKind || "", + projectionKind: surface.worldInteraction?.projectionKind || "", + targetId, + ownerRepo: targetRuntime ? resolveRuntimeProjectionOwnerRepo(targetRuntime) : "Eve", + runtimeId: targetRuntime?.id || "world-surface-lowering", + splitTarget: targetRuntime?.splitTarget || "", + runtimeStatus: targetRuntime?.status || "", + severity: "blocker", + detail: targetRuntime + ? `splitTarget:${targetRuntime.splitTarget || ""}:status:${targetRuntime.status || ""}` + : "no-runtime-target-declared", + }); + } + } + } + return gaps; +} + +function buildWorldSurfaceLoweringTargetIndex(report) { + const claimByTarget = new Map(); + const runtimeByTarget = new Map(); + for (const runtime of report.runtimes || []) { + if (runtime.id) runtimeByTarget.set(runtime.id, runtime); + for (const claim of runtime.worldSurfaceLowering || []) { + if (!claim.targetId) continue; + claimByTarget.set(claim.targetId, claim); + runtimeByTarget.set(claim.targetId, runtime); + } + } + return { claimByTarget, runtimeByTarget }; +} + +async function writeConformanceExport(conformanceExport, directory) { + await mkdir(path.join(directory, "packs"), { recursive: true }); + await mkdir(path.join(directory, "handoffs"), { recursive: true }); + await mkdir(path.join(directory, "schemas"), { recursive: true }); + await writeFile(path.join(directory, "index.json"), `${JSON.stringify(conformanceExport, null, 2)}\n`); + await writeFile(path.join(directory, "index.md"), renderConformanceExportMarkdown(conformanceExport)); + for (const pack of conformanceExport.packs) { + await writeFile(path.join(directory, "packs", `${pack.id}.json`), `${JSON.stringify(pack, null, 2)}\n`); + } + for (const schema of conformanceExport.schemaCatalog || []) { + if (!schema.path || !schema.exportPath) continue; + const sourcePath = resolveWorkspacePath(schema.path); + if (!existsSync(sourcePath)) continue; + const destinationPath = path.join(directory, schema.exportPath); + await mkdir(path.dirname(destinationPath), { recursive: true }); + await writeFile(destinationPath, await readFile(sourcePath, "utf8")); + } + for (const handoff of collectConformanceHandoffs(conformanceExport)) { + if (!handoff.sourcePath || !handoff.exportPath) continue; + const sourcePath = resolveWorkspacePath(handoff.sourcePath); + if (!existsSync(sourcePath)) continue; + const destinationPath = path.join(directory, handoff.exportPath); + await mkdir(path.dirname(destinationPath), { recursive: true }); + await writeFile(destinationPath, await readFile(sourcePath, "utf8")); + } + for (const catalog of conformanceExport.localProviderCatalogs || []) { + if (!catalog.catalogPath || !catalog.catalogExportPath) continue; + const sourcePath = resolveWorkspacePath(catalog.catalogPath); + if (!existsSync(sourcePath)) continue; + const destinationPath = path.join(directory, catalog.catalogExportPath); + await mkdir(path.dirname(destinationPath), { recursive: true }); + await writeFile(destinationPath, await readFile(sourcePath, "utf8")); + } +} + +function collectConformanceHandoffs(conformanceExport) { + return [ + { + sourcePath: conformanceExport.conformanceHandoffPath || "", + exportPath: conformanceExport.conformanceHandoffExportPath || "", + }, + ...(conformanceExport.plugins || []).map(plugin => ({ + sourcePath: plugin.handoffPath || "", + exportPath: plugin.handoffExportPath || "", + })), + ...(conformanceExport.providers || []).map(provider => ({ + sourcePath: provider.handoffPath || "", + exportPath: provider.handoffExportPath || "", + })), + ...(conformanceExport.runtimes || []).map(runtime => ({ + sourcePath: runtime.splitHandoffPath || "", + exportPath: runtime.splitHandoffExportPath || "", + })), + ]; +} + +function makeHandoffExportPath(kind, id, sourcePath) { + if (!sourcePath) return ""; + return `handoffs/${kind}-${slugify(id)}.json`; +} + +function slugify(value) { + return String(value || "unknown") + .replace(/[^a-zA-Z0-9._-]+/g, "-") + .replace(/^-+|-+$/g, "") + || "unknown"; +} + +function renderConformanceExportMarkdown(conformanceExport) { + const lines = [ + "# Eve Conformance Export", + "", + `Generated: ${conformanceExport.generatedAt}`, + "", + `Boundary rule: ${conformanceExport.boundaryRule || "not declared"}`, + `Conformance handoff: ${conformanceExport.conformanceHandoffExportPath || conformanceExport.conformanceHandoffPath || "not declared"}`, + `Capability matrix: ${conformanceExport.capabilityMatrix?.schema || "not declared"}`, + `Capability gaps: ${(conformanceExport.capabilityGaps || []).length}`, + "", + "## Packs", + "", + "| Pack | Owner | Fixtures | Exit |", + "| --- | --- | ---: | --- |", + ]; + + for (const pack of conformanceExport.packs) { + const runtimeTargets = (pack.runtimeTargets || []).length; + const evidenceCount = runtimeTargets ? `${pack.fixtures.length} fixtures, ${runtimeTargets} runtimes` : `${pack.fixtures.length}`; + lines.push(`| ${pack.id} | ${pack.ownerRepo} | ${evidenceCount} | ${pack.exitCriteria} |`); + } + + lines.push("", "## Fixtures", "", "| Pack | Fixture | Status | Owner | Surface | Metadata |", "| --- | --- | --- | --- | --- | --- |"); + for (const pack of conformanceExport.packs) { + for (const fixture of pack.fixtures) { + lines.push(`| ${pack.id} | ${fixture.title} | ${fixture.status} | ${fixture.ownerRepo} | ${fixture.surface?.path || ""} | ${fixture.metadataPath || ""} |`); + } + } + + lines.push("", "## Runtime Targets", "", "| Runtime | Status | Split Target | Command Schema | Unsupported Plugins |", "| --- | --- | --- | --- | --- |"); + const runtimePack = conformanceExport.packs.find(pack => pack.id === "runtime"); + const runtimeTargets = runtimePack?.runtimeTargets?.length ? runtimePack.runtimeTargets : conformanceExport.runtimes; + for (const runtime of runtimeTargets) { + const unsupported = (runtime.unsupportedPlugins || []).map(plugin => plugin.pluginId).join(", "); + const commandSchema = runtime.commandTransportSchema || ""; + lines.push(`| ${runtime.runtimeId} | ${runtime.status} | ${runtime.splitTarget || ""} | ${commandSchema} | ${unsupported} |`); + } + + return `${lines.join("\n")}\n`; +} + +function renderMarkdown(report) { + const lines = [ + "# Eve Parity Report", + "", + `Generated: ${report.generatedAt}`, + "", + "## Summary", + "", + `- Fixtures: ${report.summary.passedFixtures}/${report.summary.totalFixtures} passed`, + `- Plugins: ${report.summary.healthyPlugins}/${report.summary.totalPlugins} declared`, + `- Providers: ${report.summary.advertisedProviders}/${report.summary.totalProviders} advertised`, + `- Runtimes: ${report.summary.activeRuntimes}/${report.summary.totalRuntimes} active`, + `- Split targets: ${report.summary.readySplitTargets}/${report.summary.totalSplitTargets} ready`, + "", + "## Repo Strategy", + "", + `- Boundary rule: ${report.repoStrategy.boundaryRule || "not declared"}`, + `- Incubation policy: ${report.repoStrategy.incubationPolicy || "not declared"}`, + "", + "## Conformance Packs", + "", + "| Pack | Owner | Exit |", + "| --- | --- | --- |", + ]; + + for (const pack of report.conformancePacks || []) { + lines.push(`| ${pack.id} | ${pack.ownerRepo || ""} | ${pack.exitCriteria || ""} |`); + } + + lines.push( + "", + "## Responsive Cases", + "", + "| Case | Size |", + "| --- | --- |", + ); + + for (const viewport of report.responsiveCases || []) { + lines.push(`| ${viewport.id} | ${viewport.width} x ${viewport.height} |`); + } + + lines.push( + "", + "## Fixtures", + "", + "| Fixture | Pack | Status | Provider | Components | Failed Checks |", + "| --- | --- | --- | --- | ---: | --- |", + ); + + for (const fixture of report.fixtures) { + const failed = fixture.checks.filter(check => !check.pass).map(check => check.id).join(", ") || ""; + lines.push(`| ${fixture.title} | ${fixture.pack} | ${fixture.status} | ${fixture.providerId} | ${fixture.componentCount} | ${failed} |`); + } + + lines.push("", "## Fixture Commands", "", "| Fixture | Descriptors | References | Descriptor Errors |", "| --- | --- | --- | --- |"); + for (const fixture of report.fixtures) { + const refs = fixture.commandReferences.map(reference => `${reference.nodeId}:${reference.command}`).join(", "); + lines.push(`| ${fixture.title} | ${fixture.commands.join(", ")} | ${refs} | ${fixture.commandDescriptorErrors.join(", ")} |`); + } + + lines.push("", "## Authority Witnesses", "", "| Fixture | States | Owners | Witness Refs | Receipt Refs |", "| --- | --- | --- | ---: | ---: |"); + for (const fixture of report.fixtures) { + lines.push(`| ${fixture.title} | ${fixture.authorityStates.join(", ")} | ${fixture.authorityOwners.join(", ")} | ${fixture.witnessRefs.length} | ${fixture.receiptRefs.length} |`); + } + + lines.push("", "## Fixture Metadata", "", "| Fixture | Metadata | Purpose | Errors |", "| --- | --- | --- | --- |"); + for (const fixture of report.fixtures) { + lines.push(`| ${fixture.title} | ${fixture.metadataPath} | ${fixture.metadata?.purpose || ""} | ${fixture.metadataErrors.join(", ")} |`); + } + + lines.push("", "## Plugins", "", "| Plugin | Status | Owner | ABI Operations | ABI Fixture | Capabilities | Missing |", "| --- | --- | --- | --- | --- | --- | --- |"); + for (const plugin of report.plugins || []) { + const missing = [ + ...plugin.missingPaths, + ...plugin.missingRequiredFixtures.map(id => `fixture:${id}`), + ...plugin.missingIncubationFields.map(id => `metadata:${id}`), + ...plugin.schemaErrors.map(id => `schema:${id}`), + ...plugin.advertisementErrors.map(id => `advertisement:${id}`), + ...plugin.runtimeBoundaryErrors.map(id => `runtime-boundary:${id}`), + ...plugin.abiErrors.map(id => `abi:${id}`), + ].join(", "); + lines.push(`| ${plugin.title || plugin.pluginId} | ${plugin.status} | ${plugin.ownerRepo} | ${(plugin.abiOperations || []).join(", ")} | ${plugin.abiFixturePath || ""} | ${plugin.capabilities.join(", ")} | ${missing} |`); + } + + lines.push("", "## Providers", "", "| Provider | Status | Owner | Scenario | Receipt States | Surfaces | Commands | Plugin Requirements | Witnesses | Missing |", "| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |"); + for (const provider of report.providers || []) { + const missing = [ + ...provider.missingPaths, + ...provider.missingRequiredFixtures.map(id => `fixture:${id}`), + ...provider.missingIncubationFields.map(id => `metadata:${id}`), + ...provider.advertisementErrors.map(id => `advertisement:${id}`), + ...provider.scenarioErrors.map(id => `scenario:${id}`), + ...provider.missingSchemas.map(id => `schema:${id}`), + ...provider.missingSurfaces.map(id => `surface:${id}`), + ...provider.missingSurfaceKinds.map(id => `surface-kind:${id}`), + ...provider.missingCommands.map(id => `command:${id}`), + ...provider.pluginRequirementErrors.map(id => `plugin:${id}`), + ].join(", "); + lines.push(`| ${provider.title || provider.providerId} | ${provider.status} | ${provider.ownerRepo} | ${provider.scenarioId || ""} | ${provider.scenarioReceiptStates.join(", ")} | ${provider.surfaceIds.join(", ")} | ${provider.commandIds.join(", ")} | ${summarizeProviderPluginRequirements(provider.pluginRequirements)} | ${provider.witnessKinds.join(", ")} | ${missing} |`); + } + + lines.push("", "## Runtimes", "", "| Runtime | Status | Owner | Lifecycle | Capture | Command Smoke | Required Fixtures | Plugin Fixtures | Features | Plugin Gaps | Unsupported Plugins | Missing |", "| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |"); + for (const runtime of report.runtimes) { + const missing = [ + ...runtime.missingPaths, + ...runtime.missingExternalPaths.map(id => `external:${id}`), + ...runtime.missingSourceSymbols.map(id => `source:${id}`), + ...runtime.missingExternalSourceSymbols.map(id => `external-source:${id}`), + ...runtime.missingRequiredFixtures.map(id => `fixture:${id}`), + ...runtime.missingRequiredFeatures.map(id => `feature:${id}`), + ...runtime.commandTransportSmokeErrors.map(id => `command-smoke:${id}`), + ...runtime.capabilityManifestErrors.map(id => `runtime-capability:${id}`), + ...runtime.localProviderCatalogErrors.map(id => `local-provider-catalog:${id}`), + ...runtime.missingIncubationFields.map(id => `metadata:${id}`), + ].join(", "); + lines.push(`| ${runtime.title} | ${runtime.status} | ${runtime.ownerRepo} | ${summarizeLifecycle(runtime.lifecycle)} | ${runtime.capture?.status || "unknown"} | ${runtimeCommandTransportSchema(runtime)} | ${runtime.requiredFixtures.join(", ")} | ${runtime.pluginFixtures.join(", ")} | ${runtime.supportedFeatures.join(", ")} | ${runtime.pluginCapabilityGaps.join(", ")} | ${runtime.unsupportedPluginNotes.join(", ")} | ${missing} |`); + } + + lines.push("", "## Split Readiness", "", "| Target | Status | Owner | Runtimes | Blockers |", "| --- | --- | --- | --- | --- |"); + for (const target of report.splitTargets) { + const runtimes = target.runtimes.map(runtimeId => `${runtimeId}:${target.runtimeStatuses[runtimeId] || "missing"}`).join(", "); + lines.push(`| ${target.id} | ${target.status} | ${target.ownerRepo} | ${runtimes} | ${target.blockers.join("
")} |`); + } + + lines.push("", "## Split Proofs", "", "| Target | Proof | Status | Evidence |", "| --- | --- | --- | --- |"); + for (const target of report.splitTargets) { + for (const proof of target.proofs || []) { + lines.push(`| ${target.id} | ${proof.description || ""} | ${proof.status || ""} | ${(proof.evidencePaths || []).join("
")} |`); + } + } + + lines.push("", "## Runtime Notes", ""); + for (const runtime of report.runtimes) { + if (runtime.capture?.note) lines.push(`- ${runtime.title}: ${runtime.capture.note}`); + if (runtime.adapterSpike) lines.push(`- ${runtime.title} adapter: ${runtime.adapterSpike}`); + if (runtime.demotionReason) lines.push(`- ${runtime.title} demotion: ${runtime.demotionReason}`); + for (const criterion of runtime.activationCriteria || []) { + lines.push(`- ${runtime.title} activation: ${criterion}`); + } + } + + return `${lines.join("\n")}\n`; +} + +function summarizeProviderPluginRequirements(requirements = []) { + return requirements + .map(requirement => `${requirement.surfaceId}:${requirement.pluginId}(${(requirement.requiredCapabilities || []).join(", ")})`) + .join("
"); +} + +function summarizeLifecycle(lifecycle) { + if (!lifecycle) return ""; + return ["release", "test", "capture"] + .map(stage => lifecycle[stage] ? `${stage}:${lifecycle[stage].status || "unknown"}` : `${stage}:missing`) + .join("
"); +}