Extend plugin ABI fixture coverage

This commit is contained in:
Meta Vens 2026-07-09 01:03:57 +01:00
commit 3385cff2ef
7 changed files with 117 additions and 9 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 |", "| --- | --- | --- | --- | --- | --- | --- | --- | --- |");