diff --git a/docs/eve-migration-roadmap.md b/docs/eve-migration-roadmap.md index e040c8f..f6ea6f9 100644 --- a/docs/eve-migration-roadmap.md +++ b/docs/eve-migration-roadmap.md @@ -280,6 +280,11 @@ Exit criteria: Recently cut: +- Sai, Norn, and TeX plugin ABI fixtures now cover `lower` and `measure` in + addition to `describe`, `validate`, `project`, and `apply`. Eve validates the + operation shape, preserved component kinds, measurement outputs, and provider + authority flag without importing plugin internals or deciding plugin + semantics. - The web reference now publishes a runtime capability manifest at `web/eve-runtime-capability.json`, and the parity harness validates it through the same runtime capability path as Unity. Web remains Eve's behavior oracle, @@ -354,9 +359,9 @@ Recently cut: command boundary consumption, command intents, and provider-owned receipt states without importing Aetheria internals into Eve. - Sai, Norn, and TeX now have plugin ABI fixtures for describe, validate, - project, and apply behavior. The parity harness validates those fixtures - against plugin manifests, so Eve proves the ABI shape without owning plugin - semantics. + project, lower, measure, and apply behavior. The parity harness validates + those fixtures against plugin manifests, so Eve proves the ABI shape without + owning plugin semantics. - Split-readiness reporting now exists for `EveFlutter` and `EveUnity`. The parity report lists member runtime health, required plugin capability claims, pending proofs, and blockers before either repo can graduate out of Eve diff --git a/docs/plugin-architecture.md b/docs/plugin-architecture.md index 477ee6e..c50a0d2 100644 --- a/docs/plugin-architecture.md +++ b/docs/plugin-architecture.md @@ -573,8 +573,8 @@ For Sai VN: - embedded surface slot fixture with optional Norn/TeX plugin dependencies; - degraded renderer fixture for clients without scene placement; - receipt round-trip fixture driven by a fake provider. -- ABI fixture proving `validate`, `apply`, and `project` produce the expected - plugin results without direct imports. +- ABI fixture proving `describe`, `validate`, `project`, `lower`, `measure`, + and `apply` publish the expected contract shape without direct imports. The parity manifest should be extended so runtimes declare plugin support: diff --git a/plugins/incubating/norn-graph.plugin-abi-fixture.json b/plugins/incubating/norn-graph.plugin-abi-fixture.json index a68fe76..2abf092 100644 --- a/plugins/incubating/norn-graph.plugin-abi-fixture.json +++ b/plugins/incubating/norn-graph.plugin-abi-fixture.json @@ -43,6 +43,32 @@ "outputSchemas": ["norn.graph.document.v1", "norn.graph.layout_request.v1", "norn.graph.layout_result.v1"] } }, + { + "operation": "lower", + "input": { + "projection": "embedded-semantic-surface", + "targetRuntimes": ["web", "unity-uitoolkit"], + "componentKinds": ["embed.norn"] + }, + "expect": { + "loweringKind": "graph-viewport", + "preservedComponentKinds": ["embed.norn"], + "requiredSchemas": ["norn.graph.document.v1", "norn.graph.layout_result.v1"], + "commandEnvelope": "gamecult.eve.command.v1" + } + }, + { + "operation": "measure", + "input": { + "projection": "embedded-semantic-surface", + "layoutRequestSchema": "norn.graph.layout_request.v1" + }, + "expect": { + "measurementKind": "graph-layout-metrics", + "measurementOutputs": ["nodeBounds", "edgeRoutes", "viewportBounds"], + "preservesProviderAuthority": true + } + }, { "operation": "apply", "input": { diff --git a/plugins/incubating/sai-vn.plugin-abi-fixture.json b/plugins/incubating/sai-vn.plugin-abi-fixture.json index ec53db2..6fc535a 100644 --- a/plugins/incubating/sai-vn.plugin-abi-fixture.json +++ b/plugins/incubating/sai-vn.plugin-abi-fixture.json @@ -43,6 +43,32 @@ "delegatedPlugins": ["norn.graph", "tex.math"] } }, + { + "operation": "lower", + "input": { + "projection": "semantic-surface", + "targetRuntimes": ["web", "unity-uitoolkit"], + "componentKinds": ["vn.stage", "panel.dialogue", "text.dialogue", "rail.actions"] + }, + "expect": { + "loweringKind": "runtime-neutral-stage-parts", + "preservedComponentKinds": ["vn.stage", "panel.dialogue", "text.dialogue", "rail.actions"], + "delegatedPlugins": ["norn.graph", "tex.math"], + "commandEnvelope": "gamecult.eve.command.v1" + } + }, + { + "operation": "measure", + "input": { + "projection": "semantic-surface", + "placementKinds": ["overlay", "keystone", "sprite-chromakey"] + }, + "expect": { + "measurementKind": "scene-placement-hints", + "measurementOutputs": ["preferredBounds", "safeTextRegion", "embeddedSurfaceSlots"], + "preservesProviderAuthority": true + } + }, { "operation": "apply", "input": { diff --git a/plugins/incubating/sai-vn.plugin.json b/plugins/incubating/sai-vn.plugin.json index fa5da50..9a75c6e 100644 --- a/plugins/incubating/sai-vn.plugin.json +++ b/plugins/incubating/sai-vn.plugin.json @@ -7,7 +7,7 @@ "ownerRepo": "Sai", "currentHostRepo": "Eve", "splitTarget": "Sai/sai-vn-plugin", - "graduationTrigger": "Split when VN/Ink semantics, story command fixtures, and ABI validate/apply/project cases are stable." + "graduationTrigger": "Split when VN/Ink semantics, story command fixtures, and ABI describe/validate/project/lower/measure/apply cases are stable." }, "componentKinds": [ "vn.stage", diff --git a/plugins/incubating/tex-math.plugin-abi-fixture.json b/plugins/incubating/tex-math.plugin-abi-fixture.json index 5ed753a..b6e2939 100644 --- a/plugins/incubating/tex-math.plugin-abi-fixture.json +++ b/plugins/incubating/tex-math.plugin-abi-fixture.json @@ -43,6 +43,32 @@ "outputSchemas": ["tex.math.render_request.v1", "tex.math.render_result.v1"] } }, + { + "operation": "lower", + "input": { + "projection": "render-request", + "targetRuntimes": ["web"], + "componentKinds": ["embed.tex"] + }, + "expect": { + "loweringKind": "typeset-fragment", + "preservedComponentKinds": ["embed.tex"], + "requiredSchemas": ["tex.math.render_request.v1", "tex.math.render_result.v1"], + "fallbackKind": "source-text" + } + }, + { + "operation": "measure", + "input": { + "projection": "render-request", + "sourceSchema": "tex.math.source.v1" + }, + "expect": { + "measurementKind": "typeset-baseline-metrics", + "measurementOutputs": ["baseline", "advance", "inkBounds", "fallbackBounds"], + "preservesProviderAuthority": true + } + }, { "operation": "apply", "input": { diff --git a/tools/parity/run-parity.mjs b/tools/parity/run-parity.mjs index fd95745..847c2d7 100644 --- a/tools/parity/run-parity.mjs +++ b/tools/parity/run-parity.mjs @@ -375,6 +375,7 @@ async function evaluatePlugin(plugin, fixtureResults) { schema: "gamecult.eve.plugin_advertisement.v1", pluginId: plugin.pluginId, }); + const abiOperations = await readPluginAbiOperations(plugin.abiFixturePath); const abiErrors = await validatePluginAbiFixture(plugin); const status = missingPaths.length ? "missing-body" @@ -407,6 +408,7 @@ async function evaluatePlugin(plugin, fixtureResults) { schemaPath: plugin.schemaPath || "", manifestPath: plugin.manifestPath || "", abiFixturePath: plugin.abiFixturePath || "", + abiOperations, advertisementSchemaPath: plugin.advertisementSchemaPath || "", advertisementPath: plugin.advertisementPath || "", schemaErrors, @@ -419,6 +421,19 @@ async function evaluatePlugin(plugin, fixtureResults) { }; } +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 evaluateFixtureMetadata(fixture) { const errors = []; if (!fixture.metadataPath) return { metadata: null, errors: ["metadataPath:missing"] }; @@ -616,7 +631,7 @@ async function validatePluginAbiFixture(plugin) { errors.push(`${plugin.abiFixturePath}:contract:expected gamecult.eve.plugin_abi.v1 got ${abiFixture.contract}`); } - for (const operation of ["describe", "validate", "project", "apply"]) { + for (const operation of ["describe", "validate", "project", "lower", "measure", "apply"]) { if (!operations.has(operation)) errors.push(`${plugin.abiFixturePath}:operation:${operation}:missing`); } @@ -643,6 +658,15 @@ async function validatePluginAbiFixture(plugin) { errors.push(...missingMembers(manifestComponentKinds, project.ownedComponentKinds || [], `${plugin.abiFixturePath}:project.ownedComponentKinds`)); if (!project.projectionKind) errors.push(`${plugin.abiFixturePath}:project.projectionKind:missing`); + 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`); + + 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`); + const apply = operations.get("apply")?.expect || {}; if (manifestCommands.length) { const commandEffects = apply.commandEffects || {}; @@ -1181,6 +1205,7 @@ function buildConformanceExport(report) { status: plugin.status, ownerRepo: plugin.ownerRepo, abiFixturePath: plugin.abiFixturePath, + abiOperations: plugin.abiOperations || [], capabilities: plugin.capabilities, })), providers: (report.providers || []).map(provider => ({ @@ -1323,7 +1348,7 @@ function renderMarkdown(report) { lines.push(`| ${fixture.title} | ${fixture.metadataPath} | ${fixture.metadata?.purpose || ""} | ${fixture.metadataErrors.join(", ")} |`); } - lines.push("", "## Plugins", "", "| Plugin | Status | Owner | ABI Fixture | Capabilities | Missing |", "| --- | --- | --- | --- | --- | --- |"); + lines.push("", "## Plugins", "", "| Plugin | Status | Owner | ABI Operations | ABI Fixture | Capabilities | Missing |", "| --- | --- | --- | --- | --- | --- | --- |"); for (const plugin of report.plugins || []) { const missing = [ ...plugin.missingPaths, @@ -1333,7 +1358,7 @@ function renderMarkdown(report) { ...plugin.advertisementErrors.map(id => `advertisement:${id}`), ...plugin.abiErrors.map(id => `abi:${id}`), ].join(", "); - lines.push(`| ${plugin.title || plugin.pluginId} | ${plugin.status} | ${plugin.ownerRepo} | ${plugin.abiFixturePath || ""} | ${plugin.capabilities.join(", ")} | ${missing} |`); + 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 | Witnesses | Missing |", "| --- | --- | --- | --- | --- | --- | --- | --- | --- |");