From f505b7e86c9f5f5904faec8494c435e39c0db461 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 9 Jul 2026 02:24:29 +0100 Subject: [PATCH] Define plugin sidecar ABI contract --- docs/eve-migration-roadmap.md | 15 +++--- docs/parity-testing-harness.md | 5 +- docs/plugin-architecture.md | 15 ++++-- .../norn-graph.plugin-advertisement.json | 12 ++++- plugins/incubating/norn-graph.plugin.json | 12 ++++- .../sai-vn.plugin-advertisement.json | 12 ++++- plugins/incubating/sai-vn.plugin.json | 12 ++++- .../tex-math.plugin-advertisement.json | 12 ++++- plugins/incubating/tex-math.plugin.json | 12 ++++- schemas/gamecult.eve.plugin.v1.schema.json | 54 ++++++++++++++++++- ...lt.eve.plugin_advertisement.v1.schema.json | 54 ++++++++++++++++++- scripts/run-plugin-handoff-smoke.ps1 | 14 +++++ ...lugin-owner-conformance-consumer-smoke.ps1 | 12 ++++- tools/conformance/consume-export.mjs | 29 ++++++++++ tools/parity/parity-manifest.json | 33 ++++++++++-- tools/parity/run-parity.mjs | 24 +++++++++ 16 files changed, 304 insertions(+), 23 deletions(-) diff --git a/docs/eve-migration-roadmap.md b/docs/eve-migration-roadmap.md index 0cbf936..5730747 100644 --- a/docs/eve-migration-roadmap.md +++ b/docs/eve-migration-roadmap.md @@ -304,13 +304,16 @@ Recently cut: Unity UI Toolkit release/test/capture statuses plus the tagged UPM release, batchmode runner, and capture artifact pending proofs from the exported runtime record. -- Plugin manifests and advertisements now carry a runtime-independent sidecar - ABI boundary. Sai, Norn, and TeX declare `invocationModel: +- Plugin manifests and advertisements now carry a typed runtime-independent + sidecar ABI boundary. Sai, Norn, and TeX declare `invocationModel: executable-sidecar`, `gamecult.eve.plugin_abi.v1`, CultMesh/stdio transports, - renderer independence, no provider-state mutation, and provider-owned command - acceptance. The parity harness validates manifest/advertisement agreement, - exports the runtime boundary, and the plugin-owner consumer smoke asserts the - Sai/Norn sidecar contract from the conformance pack. + renderer independence, no provider-state mutation, provider-owned command + acceptance, and `runtime.sidecar` fields for process kind, protocol, + request/response schemas, operations, command envelope, receipt schema, and + state authority. The parity harness validates manifest/advertisement + agreement, checks sidecar operations against ABI fixture operations, exports + the runtime boundary, and the plugin-owner consumer smoke asserts the Sai/Norn + sidecar contract from the conformance pack. - Conformance export now carries first-class `capabilityGaps[]` records derived from plugin errors, provider advertisement/scenario errors, runtime missing evidence, declared unsupported plugin projection, capture gaps, and split diff --git a/docs/parity-testing-harness.md b/docs/parity-testing-harness.md index 8e6c8be..6395fd2 100644 --- a/docs/parity-testing-harness.md +++ b/docs/parity-testing-harness.md @@ -164,8 +164,9 @@ manifests, advertisements, ABI fixtures, move-set paths, contract inputs, and external proofs. It also checks that the manifest and advertisement agree on the runtime-independent executable sidecar boundary: `gamecult.eve.plugin_abi.v1`, CultMesh/stdio transport, renderer independence, no provider-state mutation, -and provider-owned command acceptance. The plugin-owner conformance smoke also -asserts those handoff paths and runtime boundary claims from the conformance +provider-owned command acceptance, and `runtime.sidecar` process/protocol/schema +fields. The plugin-owner conformance smoke also asserts those handoff paths, +runtime boundary claims, and exported sidecar fields from the conformance export. Aetheria provider handoff smoke: diff --git a/docs/plugin-architecture.md b/docs/plugin-architecture.md index d2578fa..34a5f32 100644 --- a/docs/plugin-architecture.md +++ b/docs/plugin-architecture.md @@ -118,9 +118,12 @@ Plugin manifests and advertisements declare a `runtime` block. Its normal incubating shape is `invocationModel: executable-sidecar`, `contract: gamecult.eve.plugin_abi.v1`, CultMesh plus stdio transports, and authority limits such as `renderer-independent`, `no-provider-state-mutation`, -and `provider-accepts-or-denies-commands`. Runtimes consume that advertisement -as a capability boundary. A Unity or web projection adapter may render an -advertised capability; it does not become the plugin runtime. +and `provider-accepts-or-denies-commands`. The nested `runtime.sidecar` block +names the sidecar process kind, protocol, request/response schemas, ABI +operations, command envelope, receipt schema, and state authority rule. Runtimes +consume that advertisement as a capability boundary. A Unity or web projection +adapter may render an advertised capability; it does not become the plugin +runtime. The ownership rule is semantic, not packaging ceremony: the plugin owns the capability and representation shape, while providers own live state and @@ -167,6 +170,12 @@ runtime can return a proposed next state, diagnostics, measurements, or rendered 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. + Example ABI request: ```json diff --git a/plugins/incubating/norn-graph.plugin-advertisement.json b/plugins/incubating/norn-graph.plugin-advertisement.json index 06f37be..71f372c 100644 --- a/plugins/incubating/norn-graph.plugin-advertisement.json +++ b/plugins/incubating/norn-graph.plugin-advertisement.json @@ -12,7 +12,17 @@ "renderer-independent", "no-provider-state-mutation", "provider-accepts-or-denies-commands" - ] + ], + "sidecar": { + "processKind": "long-running-daemon", + "protocol": "cultmesh-rpc-with-stdio-dev-transport", + "requestSchema": "gamecult.eve.plugin_abi.request.v1", + "responseSchema": "gamecult.eve.plugin_abi.response.v1", + "operations": ["describe", "validate", "project", "lower", "measure", "apply"], + "commandEnvelope": "gamecult.eve.command.v1", + "receiptSchema": "gamecult.eve.command_receipt.v1", + "stateAuthority": "proposes-plugin-state-only-provider-accepts" + } }, "schemas": [ "norn.graph.document.v1", diff --git a/plugins/incubating/norn-graph.plugin.json b/plugins/incubating/norn-graph.plugin.json index 5634895..d18cd91 100644 --- a/plugins/incubating/norn-graph.plugin.json +++ b/plugins/incubating/norn-graph.plugin.json @@ -32,7 +32,17 @@ "renderer-independent", "no-provider-state-mutation", "provider-accepts-or-denies-commands" - ] + ], + "sidecar": { + "processKind": "long-running-daemon", + "protocol": "cultmesh-rpc-with-stdio-dev-transport", + "requestSchema": "gamecult.eve.plugin_abi.request.v1", + "responseSchema": "gamecult.eve.plugin_abi.response.v1", + "operations": ["describe", "validate", "project", "lower", "measure", "apply"], + "commandEnvelope": "gamecult.eve.command.v1", + "receiptSchema": "gamecult.eve.command_receipt.v1", + "stateAuthority": "proposes-plugin-state-only-provider-accepts" + } }, "stateSchemas": [ "norn.graph.document.v1", diff --git a/plugins/incubating/sai-vn.plugin-advertisement.json b/plugins/incubating/sai-vn.plugin-advertisement.json index 17c7b1d..a19bda9 100644 --- a/plugins/incubating/sai-vn.plugin-advertisement.json +++ b/plugins/incubating/sai-vn.plugin-advertisement.json @@ -12,7 +12,17 @@ "renderer-independent", "no-provider-state-mutation", "provider-accepts-or-denies-commands" - ] + ], + "sidecar": { + "processKind": "long-running-daemon", + "protocol": "cultmesh-rpc-with-stdio-dev-transport", + "requestSchema": "gamecult.eve.plugin_abi.request.v1", + "responseSchema": "gamecult.eve.plugin_abi.response.v1", + "operations": ["describe", "validate", "project", "lower", "measure", "apply"], + "commandEnvelope": "gamecult.eve.command.v1", + "receiptSchema": "gamecult.eve.command_receipt.v1", + "stateAuthority": "proposes-plugin-state-only-provider-accepts" + } }, "schemas": [ "sai.vn.story_session.v1", diff --git a/plugins/incubating/sai-vn.plugin.json b/plugins/incubating/sai-vn.plugin.json index 1c21516..3868949 100644 --- a/plugins/incubating/sai-vn.plugin.json +++ b/plugins/incubating/sai-vn.plugin.json @@ -40,7 +40,17 @@ "renderer-independent", "no-provider-state-mutation", "provider-accepts-or-denies-commands" - ] + ], + "sidecar": { + "processKind": "long-running-daemon", + "protocol": "cultmesh-rpc-with-stdio-dev-transport", + "requestSchema": "gamecult.eve.plugin_abi.request.v1", + "responseSchema": "gamecult.eve.plugin_abi.response.v1", + "operations": ["describe", "validate", "project", "lower", "measure", "apply"], + "commandEnvelope": "gamecult.eve.command.v1", + "receiptSchema": "gamecult.eve.command_receipt.v1", + "stateAuthority": "proposes-plugin-state-only-provider-accepts" + } }, "stateSchemas": [ "sai.vn.story_session.v1", diff --git a/plugins/incubating/tex-math.plugin-advertisement.json b/plugins/incubating/tex-math.plugin-advertisement.json index a540864..17ca946 100644 --- a/plugins/incubating/tex-math.plugin-advertisement.json +++ b/plugins/incubating/tex-math.plugin-advertisement.json @@ -12,7 +12,17 @@ "renderer-independent", "no-provider-state-mutation", "provider-accepts-or-denies-commands" - ] + ], + "sidecar": { + "processKind": "long-running-daemon", + "protocol": "cultmesh-rpc-with-stdio-dev-transport", + "requestSchema": "gamecult.eve.plugin_abi.request.v1", + "responseSchema": "gamecult.eve.plugin_abi.response.v1", + "operations": ["describe", "validate", "project", "lower", "measure", "apply"], + "commandEnvelope": "gamecult.eve.command.v1", + "receiptSchema": "gamecult.eve.plugin_receipt.v1", + "stateAuthority": "emits-render-results-only-provider-owns-source-state" + } }, "schemas": [ "tex.math.source.v1", diff --git a/plugins/incubating/tex-math.plugin.json b/plugins/incubating/tex-math.plugin.json index 6a60eb5..3c15b35 100644 --- a/plugins/incubating/tex-math.plugin.json +++ b/plugins/incubating/tex-math.plugin.json @@ -21,7 +21,17 @@ "renderer-independent", "no-provider-state-mutation", "provider-accepts-or-denies-commands" - ] + ], + "sidecar": { + "processKind": "long-running-daemon", + "protocol": "cultmesh-rpc-with-stdio-dev-transport", + "requestSchema": "gamecult.eve.plugin_abi.request.v1", + "responseSchema": "gamecult.eve.plugin_abi.response.v1", + "operations": ["describe", "validate", "project", "lower", "measure", "apply"], + "commandEnvelope": "gamecult.eve.command.v1", + "receiptSchema": "gamecult.eve.plugin_receipt.v1", + "stateAuthority": "emits-render-results-only-provider-owns-source-state" + } }, "stateSchemas": [ "tex.math.source.v1", diff --git a/schemas/gamecult.eve.plugin.v1.schema.json b/schemas/gamecult.eve.plugin.v1.schema.json index 00706e4..5e56006 100644 --- a/schemas/gamecult.eve.plugin.v1.schema.json +++ b/schemas/gamecult.eve.plugin.v1.schema.json @@ -99,7 +99,8 @@ "invocationModel", "contract", "transports", - "authority" + "authority", + "sidecar" ], "properties": { "invocationModel": { @@ -123,6 +124,57 @@ "type": "string", "minLength": 1 } + }, + "sidecar": { + "type": "object", + "required": [ + "processKind", + "protocol", + "requestSchema", + "responseSchema", + "operations", + "commandEnvelope", + "receiptSchema", + "stateAuthority" + ], + "properties": { + "processKind": { + "type": "string", + "minLength": 1 + }, + "protocol": { + "type": "string", + "minLength": 1 + }, + "requestSchema": { + "type": "string", + "minLength": 1 + }, + "responseSchema": { + "type": "string", + "minLength": 1 + }, + "operations": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "commandEnvelope": { + "type": "string", + "minLength": 1 + }, + "receiptSchema": { + "type": "string", + "minLength": 1 + }, + "stateAuthority": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": true } }, "additionalProperties": true diff --git a/schemas/gamecult.eve.plugin_advertisement.v1.schema.json b/schemas/gamecult.eve.plugin_advertisement.v1.schema.json index e44f987..a4cb424 100644 --- a/schemas/gamecult.eve.plugin_advertisement.v1.schema.json +++ b/schemas/gamecult.eve.plugin_advertisement.v1.schema.json @@ -41,7 +41,8 @@ "invocationModel", "contract", "transports", - "authority" + "authority", + "sidecar" ], "properties": { "invocationModel": { @@ -65,6 +66,57 @@ "type": "string", "minLength": 1 } + }, + "sidecar": { + "type": "object", + "required": [ + "processKind", + "protocol", + "requestSchema", + "responseSchema", + "operations", + "commandEnvelope", + "receiptSchema", + "stateAuthority" + ], + "properties": { + "processKind": { + "type": "string", + "minLength": 1 + }, + "protocol": { + "type": "string", + "minLength": 1 + }, + "requestSchema": { + "type": "string", + "minLength": 1 + }, + "responseSchema": { + "type": "string", + "minLength": 1 + }, + "operations": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "commandEnvelope": { + "type": "string", + "minLength": 1 + }, + "receiptSchema": { + "type": "string", + "minLength": 1 + }, + "stateAuthority": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": true } }, "additionalProperties": true diff --git a/scripts/run-plugin-handoff-smoke.ps1 b/scripts/run-plugin-handoff-smoke.ps1 index 3e67bed..be54634 100644 --- a/scripts/run-plugin-handoff-smoke.ps1 +++ b/scripts/run-plugin-handoff-smoke.ps1 @@ -90,12 +90,26 @@ foreach ($handoffPath in $HandoffPaths) { throw "Plugin handoff $($handoff.pluginId) advertisement runtime missing authority: $authority" } } + foreach ($sidecarProperty in @("processKind", "protocol", "requestSchema", "responseSchema", "commandEnvelope", "receiptSchema", "stateAuthority")) { + if (-not $manifest.runtime.sidecar.$sidecarProperty) { + throw "Plugin handoff $($handoff.pluginId) manifest sidecar missing property: $sidecarProperty" + } + if ($advertisement.runtime.sidecar.$sidecarProperty -ne $manifest.runtime.sidecar.$sidecarProperty) { + throw "Plugin handoff $($handoff.pluginId) advertisement sidecar $sidecarProperty does not match manifest: $($advertisement.runtime.sidecar.$sidecarProperty)" + } + } $operations = @($abiFixture.operations | ForEach-Object { $_.operation }) foreach ($operation in @("describe", "validate", "project", "lower", "measure", "apply")) { if (-not ($operations -contains $operation)) { throw "Plugin handoff $($handoff.pluginId) ABI fixture missing operation: $operation" } + if (-not (@($manifest.runtime.sidecar.operations) -contains $operation)) { + throw "Plugin handoff $($handoff.pluginId) manifest sidecar missing operation: $operation" + } + if (-not (@($advertisement.runtime.sidecar.operations) -contains $operation)) { + throw "Plugin handoff $($handoff.pluginId) advertisement sidecar missing operation: $operation" + } } foreach ($moveSet in @($handoff.moveSets)) { diff --git a/scripts/run-plugin-owner-conformance-consumer-smoke.ps1 b/scripts/run-plugin-owner-conformance-consumer-smoke.ps1 index 3456a16..af598f2 100644 --- a/scripts/run-plugin-owner-conformance-consumer-smoke.ps1 +++ b/scripts/run-plugin-owner-conformance-consumer-smoke.ps1 @@ -58,9 +58,19 @@ node $consumerScript $consumerExport ` --expect-plugin-runtime sai.vn:executable-sidecar ` --expect-plugin-runtime-transport sai.vn:cultmesh ` --expect-plugin-runtime-authority sai.vn:no-provider-state-mutation ` + --expect-plugin-runtime-field sai.vn:sidecar.processKind:long-running-daemon ` + --expect-plugin-runtime-field sai.vn:sidecar.protocol:cultmesh-rpc-with-stdio-dev-transport ` + --expect-plugin-runtime-field sai.vn:sidecar.requestSchema:gamecult.eve.plugin_abi.request.v1 ` + --expect-plugin-runtime-field sai.vn:sidecar.responseSchema:gamecult.eve.plugin_abi.response.v1 ` + --expect-plugin-runtime-field sai.vn:sidecar.stateAuthority:proposes-plugin-state-only-provider-accepts ` --expect-plugin-runtime norn.graph:executable-sidecar ` --expect-plugin-runtime-transport norn.graph:cultmesh ` - --expect-plugin-runtime-authority norn.graph:no-provider-state-mutation + --expect-plugin-runtime-authority norn.graph:no-provider-state-mutation ` + --expect-plugin-runtime-field norn.graph:sidecar.processKind:long-running-daemon ` + --expect-plugin-runtime-field norn.graph:sidecar.protocol:cultmesh-rpc-with-stdio-dev-transport ` + --expect-plugin-runtime-field norn.graph:sidecar.requestSchema:gamecult.eve.plugin_abi.request.v1 ` + --expect-plugin-runtime-field norn.graph:sidecar.responseSchema:gamecult.eve.plugin_abi.response.v1 ` + --expect-plugin-runtime-field norn.graph:sidecar.stateAuthority:proposes-plugin-state-only-provider-accepts if ($LASTEXITCODE -ne 0) { throw "Plugin owner conformance consumer smoke failed with exit code $LASTEXITCODE" diff --git a/tools/conformance/consume-export.mjs b/tools/conformance/consume-export.mjs index f61b537..bd37067 100644 --- a/tools/conformance/consume-export.mjs +++ b/tools/conformance/consume-export.mjs @@ -24,6 +24,7 @@ if (!exportDirectory) { " --expect-plugin-runtime ", " --expect-plugin-runtime-transport ", " --expect-plugin-runtime-authority ", + " --expect-plugin-runtime-field ", " --expect-plugin-handoff ", " --expect-provider-surface ", " --expect-provider-surface-kind ", @@ -189,6 +190,17 @@ function validateIndex(index, directory, expectations, errors) { 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 expectedPlugin of expectations.pluginHandoffs) { const plugin = plugins.find(candidate => candidate.pluginId === expectedPlugin); if (!plugin) { @@ -497,6 +509,7 @@ function parseArguments(args) { pluginRuntimes: [], pluginRuntimeTransports: [], pluginRuntimeAuthorities: [], + pluginRuntimeFields: [], pluginHandoffs: [], providers: [], providerSurfaces: [], @@ -535,6 +548,7 @@ function parseArguments(args) { ["--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-handoff", expectations.pluginHandoffs], ["--expect-provider-surface", expectations.providerSurfaces], ["--expect-provider-surface-kind", expectations.providerSurfaceKinds], @@ -585,6 +599,8 @@ function parseArguments(args) { 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-handoff") { target.push(value); } else if (option === "--expect-provider-surface") { @@ -640,6 +656,19 @@ function parsePluginExpectation(value, field) { }; } +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 parseProviderExpectation(value, field) { const separator = value.indexOf(":"); if (separator <= 0 || separator === value.length - 1) { diff --git a/tools/parity/parity-manifest.json b/tools/parity/parity-manifest.json index 1a3418c..98e7788 100644 --- a/tools/parity/parity-manifest.json +++ b/tools/parity/parity-manifest.json @@ -376,7 +376,16 @@ "invocationModel": "executable-sidecar", "contract": "gamecult.eve.plugin_abi.v1", "transports": ["cultmesh", "stdio"], - "authority": ["renderer-independent", "no-provider-state-mutation", "provider-accepts-or-denies-commands"] + "authority": ["renderer-independent", "no-provider-state-mutation", "provider-accepts-or-denies-commands"], + "sidecar": { + "processKind": "long-running-daemon", + "protocol": "cultmesh-rpc-with-stdio-dev-transport", + "requestSchema": "gamecult.eve.plugin_abi.request.v1", + "responseSchema": "gamecult.eve.plugin_abi.response.v1", + "commandEnvelope": "gamecult.eve.command.v1", + "receiptSchema": "gamecult.eve.command_receipt.v1", + "stateAuthority": "proposes-plugin-state-only-provider-accepts" + } }, "requiredFixtures": ["sai-vn"], "schemaPath": "schemas/gamecult.eve.plugin.v1.schema.json", @@ -406,7 +415,16 @@ "invocationModel": "executable-sidecar", "contract": "gamecult.eve.plugin_abi.v1", "transports": ["cultmesh", "stdio"], - "authority": ["renderer-independent", "no-provider-state-mutation", "provider-accepts-or-denies-commands"] + "authority": ["renderer-independent", "no-provider-state-mutation", "provider-accepts-or-denies-commands"], + "sidecar": { + "processKind": "long-running-daemon", + "protocol": "cultmesh-rpc-with-stdio-dev-transport", + "requestSchema": "gamecult.eve.plugin_abi.request.v1", + "responseSchema": "gamecult.eve.plugin_abi.response.v1", + "commandEnvelope": "gamecult.eve.command.v1", + "receiptSchema": "gamecult.eve.command_receipt.v1", + "stateAuthority": "proposes-plugin-state-only-provider-accepts" + } }, "requiredFixtures": ["sai-vn"], "schemaPath": "schemas/gamecult.eve.plugin.v1.schema.json", @@ -436,7 +454,16 @@ "invocationModel": "executable-sidecar", "contract": "gamecult.eve.plugin_abi.v1", "transports": ["cultmesh", "stdio"], - "authority": ["renderer-independent", "no-provider-state-mutation", "provider-accepts-or-denies-commands"] + "authority": ["renderer-independent", "no-provider-state-mutation", "provider-accepts-or-denies-commands"], + "sidecar": { + "processKind": "long-running-daemon", + "protocol": "cultmesh-rpc-with-stdio-dev-transport", + "requestSchema": "gamecult.eve.plugin_abi.request.v1", + "responseSchema": "gamecult.eve.plugin_abi.response.v1", + "commandEnvelope": "gamecult.eve.command.v1", + "receiptSchema": "gamecult.eve.plugin_receipt.v1", + "stateAuthority": "emits-render-results-only-provider-owns-source-state" + } }, "requiredFixtures": ["sai-vn"], "schemaPath": "schemas/gamecult.eve.plugin.v1.schema.json", diff --git a/tools/parity/run-parity.mjs b/tools/parity/run-parity.mjs index 9cb81b4..3317f6e 100644 --- a/tools/parity/run-parity.mjs +++ b/tools/parity/run-parity.mjs @@ -518,10 +518,12 @@ async function readPluginRuntimeBoundary(plugin) { 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 { @@ -529,10 +531,12 @@ async function readPluginRuntimeBoundary(plugin) { contract: "", transports: [], authority: [], + sidecar: {}, advertisementInvocationModel: "", advertisementContract: "", advertisementTransports: [], advertisementAuthority: [], + advertisementSidecar: {}, }; } } @@ -560,6 +564,21 @@ function validatePluginRuntimeBoundary(plugin, runtimeBoundary) { 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")); + 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; } @@ -837,6 +856,11 @@ async function validatePluginAbiFixture(plugin) { for (const operation of ["describe", "validate", "project", "lower", "measure", "apply"]) { 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`, + )); if (!(pluginManifest.abiFixtures || []).includes(plugin.abiFixturePath)) { errors.push(`${plugin.manifestPath}:abiFixtures:${plugin.abiFixturePath}:missing`);