From 227c6bd59da6540eb6d7db197d704208142818b3 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 9 Jul 2026 11:33:42 +0100 Subject: [PATCH] Add plugin ABI wire schemas --- docs/eve-migration-roadmap.md | 6 +++ docs/plugin-architecture.md | 11 +++-- .../norn-graph.plugin-abi-fixture.json | 2 + .../incubating/norn-graph.plugin-handoff.json | 2 + .../incubating/sai-vn.plugin-abi-fixture.json | 2 + plugins/incubating/sai-vn.plugin-handoff.json | 2 + .../tex-math.plugin-abi-fixture.json | 2 + .../incubating/tex-math.plugin-handoff.json | 2 + ...cult.eve.plugin_abi.request.v1.schema.json | 41 +++++++++++++++++ ...ult.eve.plugin_abi.response.v1.schema.json | 45 +++++++++++++++++++ ...cult.eve.plugin_abi_fixture.v1.schema.json | 10 +++++ scripts/run-plugin-handoff-smoke.ps1 | 8 ++++ tools/parity/parity-manifest.json | 2 + tools/parity/run-parity.mjs | 16 +++++++ 14 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 schemas/gamecult.eve.plugin_abi.request.v1.schema.json create mode 100644 schemas/gamecult.eve.plugin_abi.response.v1.schema.json diff --git a/docs/eve-migration-roadmap.md b/docs/eve-migration-roadmap.md index 3ccf70c..34cf235 100644 --- a/docs/eve-migration-roadmap.md +++ b/docs/eve-migration-roadmap.md @@ -819,6 +819,12 @@ Recently cut: 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. +- Plugin sidecar request/response schema names are now backed by Eve contract + documents. `gamecult.eve.plugin_abi.request.v1` and + `gamecult.eve.plugin_abi.response.v1` live under `schemas/`; Sai, Norn, and + TeX ABI fixtures must name those same schemas, and parity fails if a sidecar + advertises a request/response schema that is missing from the contract + catalog. - 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 f40da69..5e8b83f 100644 --- a/docs/plugin-architecture.md +++ b/docs/plugin-architecture.md @@ -171,10 +171,13 @@ assets. It cannot decide whether a provider command is allowed, cannot persist app truth, and cannot write receipts except for its own runtime diagnostics. The incubating sidecar contract is checked in three places: the plugin manifest, -the plugin advertisement, and the ABI fixture. The parity harness compares -manifest and advertisement `runtime.sidecar` fields, verifies that sidecar -operations cover the ABI fixture operations, and exports the sidecar boundary -through the conformance pack for Sai, Norn, and EvePlugins to consume. +the plugin advertisement, and the ABI fixture. The manifest and advertisement +name `gamecult.eve.plugin_abi.request.v1` and +`gamecult.eve.plugin_abi.response.v1`; the ABI fixture must name the same wire +schemas. The parity harness compares manifest and advertisement +`runtime.sidecar` fields, verifies that sidecar operations cover the ABI fixture +operations, and exports the sidecar boundary through the conformance pack for +Sai, Norn, and EvePlugins to consume. Example ABI request: diff --git a/plugins/incubating/norn-graph.plugin-abi-fixture.json b/plugins/incubating/norn-graph.plugin-abi-fixture.json index ca9271f..fe3e566 100644 --- a/plugins/incubating/norn-graph.plugin-abi-fixture.json +++ b/plugins/incubating/norn-graph.plugin-abi-fixture.json @@ -4,6 +4,8 @@ "pluginId": "norn.graph", "ownerRepo": "Norn", "contract": "gamecult.eve.plugin_abi.v1", + "requestSchema": "gamecult.eve.plugin_abi.request.v1", + "responseSchema": "gamecult.eve.plugin_abi.response.v1", "sourceFixture": "sai-vn", "sourceFixtureRole": "optional-nested-placement-evidence", "authority": "Norn owns graph semantics independently; the Sai fixture only proves a provider surface can nest embed.norn when norn.graph is available.", diff --git a/plugins/incubating/norn-graph.plugin-handoff.json b/plugins/incubating/norn-graph.plugin-handoff.json index 6aba2ed..87e8d30 100644 --- a/plugins/incubating/norn-graph.plugin-handoff.json +++ b/plugins/incubating/norn-graph.plugin-handoff.json @@ -33,6 +33,8 @@ "gamecult.eve.plugin.v1", "gamecult.eve.plugin_advertisement.v1", "gamecult.eve.plugin_abi.v1", + "gamecult.eve.plugin_abi.request.v1", + "gamecult.eve.plugin_abi.response.v1", "gamecult.eve.plugin_abi_fixture.v1", "gamecult.eve.conformance_export.v1" ], diff --git a/plugins/incubating/sai-vn.plugin-abi-fixture.json b/plugins/incubating/sai-vn.plugin-abi-fixture.json index 6fc535a..3819b7f 100644 --- a/plugins/incubating/sai-vn.plugin-abi-fixture.json +++ b/plugins/incubating/sai-vn.plugin-abi-fixture.json @@ -4,6 +4,8 @@ "pluginId": "sai.vn", "ownerRepo": "Sai", "contract": "gamecult.eve.plugin_abi.v1", + "requestSchema": "gamecult.eve.plugin_abi.request.v1", + "responseSchema": "gamecult.eve.plugin_abi.response.v1", "sourceFixture": "sai-vn", "operations": [ { diff --git a/plugins/incubating/sai-vn.plugin-handoff.json b/plugins/incubating/sai-vn.plugin-handoff.json index a3432cf..57e2999 100644 --- a/plugins/incubating/sai-vn.plugin-handoff.json +++ b/plugins/incubating/sai-vn.plugin-handoff.json @@ -33,6 +33,8 @@ "gamecult.eve.plugin.v1", "gamecult.eve.plugin_advertisement.v1", "gamecult.eve.plugin_abi.v1", + "gamecult.eve.plugin_abi.request.v1", + "gamecult.eve.plugin_abi.response.v1", "gamecult.eve.plugin_abi_fixture.v1", "gamecult.eve.conformance_export.v1" ], diff --git a/plugins/incubating/tex-math.plugin-abi-fixture.json b/plugins/incubating/tex-math.plugin-abi-fixture.json index 8d28183..a59abfe 100644 --- a/plugins/incubating/tex-math.plugin-abi-fixture.json +++ b/plugins/incubating/tex-math.plugin-abi-fixture.json @@ -4,6 +4,8 @@ "pluginId": "tex.math", "ownerRepo": "EvePlugins", "contract": "gamecult.eve.plugin_abi.v1", + "requestSchema": "gamecult.eve.plugin_abi.request.v1", + "responseSchema": "gamecult.eve.plugin_abi.response.v1", "sourceFixture": "sai-vn", "sourceFixtureRole": "optional-nested-placement-evidence", "authority": "EvePlugins owns TeX semantics independently; the Sai fixture only proves a provider surface can nest embed.tex when tex.math is available.", diff --git a/plugins/incubating/tex-math.plugin-handoff.json b/plugins/incubating/tex-math.plugin-handoff.json index 0abd78e..e06b536 100644 --- a/plugins/incubating/tex-math.plugin-handoff.json +++ b/plugins/incubating/tex-math.plugin-handoff.json @@ -33,6 +33,8 @@ "gamecult.eve.plugin.v1", "gamecult.eve.plugin_advertisement.v1", "gamecult.eve.plugin_abi.v1", + "gamecult.eve.plugin_abi.request.v1", + "gamecult.eve.plugin_abi.response.v1", "gamecult.eve.plugin_abi_fixture.v1", "gamecult.eve.conformance_export.v1" ], diff --git a/schemas/gamecult.eve.plugin_abi.request.v1.schema.json b/schemas/gamecult.eve.plugin_abi.request.v1.schema.json new file mode 100644 index 0000000..4dd45a4 --- /dev/null +++ b/schemas/gamecult.eve.plugin_abi.request.v1.schema.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "gamecult.eve.plugin_abi.request.v1", + "title": "Eve Plugin ABI Request", + "type": "object", + "required": [ + "schema", + "pluginId", + "operation", + "requestId", + "input" + ], + "properties": { + "schema": { + "const": "gamecult.eve.plugin_abi.request.v1" + }, + "pluginId": { + "type": "string", + "minLength": 1 + }, + "operation": { + "type": "string", + "minLength": 1 + }, + "requestId": { + "type": "string", + "minLength": 1 + }, + "input": { + "type": "object" + }, + "context": { + "type": "object" + }, + "targetRuntime": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": true +} diff --git a/schemas/gamecult.eve.plugin_abi.response.v1.schema.json b/schemas/gamecult.eve.plugin_abi.response.v1.schema.json new file mode 100644 index 0000000..5ffeb12 --- /dev/null +++ b/schemas/gamecult.eve.plugin_abi.response.v1.schema.json @@ -0,0 +1,45 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "gamecult.eve.plugin_abi.response.v1", + "title": "Eve Plugin ABI Response", + "type": "object", + "required": [ + "schema", + "pluginId", + "operation", + "requestId", + "status", + "output" + ], + "properties": { + "schema": { + "const": "gamecult.eve.plugin_abi.response.v1" + }, + "pluginId": { + "type": "string", + "minLength": 1 + }, + "operation": { + "type": "string", + "minLength": 1 + }, + "requestId": { + "type": "string", + "minLength": 1 + }, + "status": { + "type": "string", + "minLength": 1 + }, + "output": { + "type": "object" + }, + "diagnostics": { + "type": "array", + "items": { + "type": "object" + } + } + }, + "additionalProperties": true +} diff --git a/schemas/gamecult.eve.plugin_abi_fixture.v1.schema.json b/schemas/gamecult.eve.plugin_abi_fixture.v1.schema.json index 4e17ad9..0a5a522 100644 --- a/schemas/gamecult.eve.plugin_abi_fixture.v1.schema.json +++ b/schemas/gamecult.eve.plugin_abi_fixture.v1.schema.json @@ -9,6 +9,8 @@ "pluginId", "ownerRepo", "contract", + "requestSchema", + "responseSchema", "operations" ], "properties": { @@ -31,6 +33,14 @@ "type": "string", "minLength": 1 }, + "requestSchema": { + "type": "string", + "minLength": 1 + }, + "responseSchema": { + "type": "string", + "minLength": 1 + }, "sourceFixture": { "type": "string", "minLength": 1 diff --git a/scripts/run-plugin-handoff-smoke.ps1 b/scripts/run-plugin-handoff-smoke.ps1 index 5254d02..93eb129 100644 --- a/scripts/run-plugin-handoff-smoke.ps1 +++ b/scripts/run-plugin-handoff-smoke.ps1 @@ -101,6 +101,12 @@ foreach ($handoffPath in $HandoffPaths) { } $operations = @($abiFixture.operations | ForEach-Object { $_.operation }) + if ($abiFixture.requestSchema -ne $manifest.runtime.sidecar.requestSchema) { + throw "Plugin handoff $($handoff.pluginId) ABI fixture request schema does not match sidecar: $($abiFixture.requestSchema)" + } + if ($abiFixture.responseSchema -ne $manifest.runtime.sidecar.responseSchema) { + throw "Plugin handoff $($handoff.pluginId) ABI fixture response schema does not match sidecar: $($abiFixture.responseSchema)" + } foreach ($operation in @("describe", "validate", "project", "lower", "measure", "apply")) { if (-not ($operations -contains $operation)) { throw "Plugin handoff $($handoff.pluginId) ABI fixture missing operation: $operation" @@ -132,6 +138,8 @@ foreach ($handoffPath in $HandoffPaths) { "gamecult.eve.plugin.v1", "gamecult.eve.plugin_advertisement.v1", "gamecult.eve.plugin_abi.v1", + "gamecult.eve.plugin_abi.request.v1", + "gamecult.eve.plugin_abi.response.v1", "gamecult.eve.plugin_abi_fixture.v1", "gamecult.eve.conformance_export.v1" )) { diff --git a/tools/parity/parity-manifest.json b/tools/parity/parity-manifest.json index 04448ef..7d45252 100644 --- a/tools/parity/parity-manifest.json +++ b/tools/parity/parity-manifest.json @@ -11,6 +11,8 @@ "gamecult.eve.conformance_export.v1": "schemas/gamecult.eve.conformance_export.v1.schema.json", "gamecult.eve.command.v1": "schemas/gamecult.eve.command.v1.schema.json", "gamecult.eve.plugin.v1": "schemas/gamecult.eve.plugin.v1.schema.json", + "gamecult.eve.plugin_abi.request.v1": "schemas/gamecult.eve.plugin_abi.request.v1.schema.json", + "gamecult.eve.plugin_abi.response.v1": "schemas/gamecult.eve.plugin_abi.response.v1.schema.json", "gamecult.eve.plugin_abi_fixture.v1": "schemas/gamecult.eve.plugin_abi_fixture.v1.schema.json", "gamecult.eve.plugin_advertisement.v1": "schemas/gamecult.eve.plugin_advertisement.v1.schema.json", "gamecult.eve.runtime_capability.v1": "schemas/gamecult.eve.runtime_capability.v1.schema.json", diff --git a/tools/parity/run-parity.mjs b/tools/parity/run-parity.mjs index d1cf4f1..a530229 100644 --- a/tools/parity/run-parity.mjs +++ b/tools/parity/run-parity.mjs @@ -604,6 +604,11 @@ function validatePluginRuntimeBoundary(plugin, runtimeBoundary) { 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].filter(Boolean)) { + if (!manifest.schemas?.[schemaId]) { + errors.push(`runtime.sidecar.schemaCatalog:${schemaId}:missing`); + } + } return errors; } @@ -926,6 +931,17 @@ async function validatePluginAbiFixture(plugin) { 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 || ""}`); + } + for (const schemaId of [abiFixture.requestSchema, abiFixture.responseSchema].filter(Boolean)) { + if (!manifest.schemas?.[schemaId]) { + errors.push(`${plugin.abiFixturePath}:schemaCatalog:${schemaId}:missing`); + } + } for (const operation of ["describe", "validate", "project", "lower", "measure", "apply"]) { if (!operations.has(operation)) errors.push(`${plugin.abiFixturePath}:operation:${operation}:missing`);