Define plugin sidecar ABI contract

This commit is contained in:
Meta Vens 2026-07-09 02:24:29 +01:00
commit f505b7e86c
16 changed files with 304 additions and 23 deletions

View file

@ -304,13 +304,16 @@ Recently cut:
Unity UI Toolkit release/test/capture statuses plus the tagged UPM release, Unity UI Toolkit release/test/capture statuses plus the tagged UPM release,
batchmode runner, and capture artifact pending proofs from the exported batchmode runner, and capture artifact pending proofs from the exported
runtime record. runtime record.
- Plugin manifests and advertisements now carry a runtime-independent sidecar - Plugin manifests and advertisements now carry a typed runtime-independent
ABI boundary. Sai, Norn, and TeX declare `invocationModel: sidecar ABI boundary. Sai, Norn, and TeX declare `invocationModel:
executable-sidecar`, `gamecult.eve.plugin_abi.v1`, CultMesh/stdio transports, executable-sidecar`, `gamecult.eve.plugin_abi.v1`, CultMesh/stdio transports,
renderer independence, no provider-state mutation, and provider-owned command renderer independence, no provider-state mutation, provider-owned command
acceptance. The parity harness validates manifest/advertisement agreement, acceptance, and `runtime.sidecar` fields for process kind, protocol,
exports the runtime boundary, and the plugin-owner consumer smoke asserts the request/response schemas, operations, command envelope, receipt schema, and
Sai/Norn sidecar contract from the conformance pack. 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 - Conformance export now carries first-class `capabilityGaps[]` records derived
from plugin errors, provider advertisement/scenario errors, runtime missing from plugin errors, provider advertisement/scenario errors, runtime missing
evidence, declared unsupported plugin projection, capture gaps, and split evidence, declared unsupported plugin projection, capture gaps, and split

View file

@ -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 external proofs. It also checks that the manifest and advertisement agree on
the runtime-independent executable sidecar boundary: `gamecult.eve.plugin_abi.v1`, the runtime-independent executable sidecar boundary: `gamecult.eve.plugin_abi.v1`,
CultMesh/stdio transport, renderer independence, no provider-state mutation, CultMesh/stdio transport, renderer independence, no provider-state mutation,
and provider-owned command acceptance. The plugin-owner conformance smoke also provider-owned command acceptance, and `runtime.sidecar` process/protocol/schema
asserts those handoff paths and runtime boundary claims from the conformance fields. The plugin-owner conformance smoke also asserts those handoff paths,
runtime boundary claims, and exported sidecar fields from the conformance
export. export.
Aetheria provider handoff smoke: Aetheria provider handoff smoke:

View file

@ -118,9 +118,12 @@ Plugin manifests and advertisements declare a `runtime` block. Its normal
incubating shape is `invocationModel: executable-sidecar`, incubating shape is `invocationModel: executable-sidecar`,
`contract: gamecult.eve.plugin_abi.v1`, CultMesh plus stdio transports, and `contract: gamecult.eve.plugin_abi.v1`, CultMesh plus stdio transports, and
authority limits such as `renderer-independent`, `no-provider-state-mutation`, authority limits such as `renderer-independent`, `no-provider-state-mutation`,
and `provider-accepts-or-denies-commands`. Runtimes consume that advertisement and `provider-accepts-or-denies-commands`. The nested `runtime.sidecar` block
as a capability boundary. A Unity or web projection adapter may render an names the sidecar process kind, protocol, request/response schemas, ABI
advertised capability; it does not become the plugin runtime. 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 The ownership rule is semantic, not packaging ceremony: the plugin owns the
capability and representation shape, while providers own live state and 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 assets. It cannot decide whether a provider command is allowed, cannot persist
app truth, and cannot write receipts except for its own runtime diagnostics. 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: Example ABI request:
```json ```json

View file

@ -12,7 +12,17 @@
"renderer-independent", "renderer-independent",
"no-provider-state-mutation", "no-provider-state-mutation",
"provider-accepts-or-denies-commands" "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": [ "schemas": [
"norn.graph.document.v1", "norn.graph.document.v1",

View file

@ -32,7 +32,17 @@
"renderer-independent", "renderer-independent",
"no-provider-state-mutation", "no-provider-state-mutation",
"provider-accepts-or-denies-commands" "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": [ "stateSchemas": [
"norn.graph.document.v1", "norn.graph.document.v1",

View file

@ -12,7 +12,17 @@
"renderer-independent", "renderer-independent",
"no-provider-state-mutation", "no-provider-state-mutation",
"provider-accepts-or-denies-commands" "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": [ "schemas": [
"sai.vn.story_session.v1", "sai.vn.story_session.v1",

View file

@ -40,7 +40,17 @@
"renderer-independent", "renderer-independent",
"no-provider-state-mutation", "no-provider-state-mutation",
"provider-accepts-or-denies-commands" "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": [ "stateSchemas": [
"sai.vn.story_session.v1", "sai.vn.story_session.v1",

View file

@ -12,7 +12,17 @@
"renderer-independent", "renderer-independent",
"no-provider-state-mutation", "no-provider-state-mutation",
"provider-accepts-or-denies-commands" "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": [ "schemas": [
"tex.math.source.v1", "tex.math.source.v1",

View file

@ -21,7 +21,17 @@
"renderer-independent", "renderer-independent",
"no-provider-state-mutation", "no-provider-state-mutation",
"provider-accepts-or-denies-commands" "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": [ "stateSchemas": [
"tex.math.source.v1", "tex.math.source.v1",

View file

@ -99,7 +99,8 @@
"invocationModel", "invocationModel",
"contract", "contract",
"transports", "transports",
"authority" "authority",
"sidecar"
], ],
"properties": { "properties": {
"invocationModel": { "invocationModel": {
@ -123,6 +124,57 @@
"type": "string", "type": "string",
"minLength": 1 "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 "additionalProperties": true

View file

@ -41,7 +41,8 @@
"invocationModel", "invocationModel",
"contract", "contract",
"transports", "transports",
"authority" "authority",
"sidecar"
], ],
"properties": { "properties": {
"invocationModel": { "invocationModel": {
@ -65,6 +66,57 @@
"type": "string", "type": "string",
"minLength": 1 "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 "additionalProperties": true

View file

@ -90,12 +90,26 @@ foreach ($handoffPath in $HandoffPaths) {
throw "Plugin handoff $($handoff.pluginId) advertisement runtime missing authority: $authority" 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 }) $operations = @($abiFixture.operations | ForEach-Object { $_.operation })
foreach ($operation in @("describe", "validate", "project", "lower", "measure", "apply")) { foreach ($operation in @("describe", "validate", "project", "lower", "measure", "apply")) {
if (-not ($operations -contains $operation)) { if (-not ($operations -contains $operation)) {
throw "Plugin handoff $($handoff.pluginId) ABI fixture missing operation: $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)) { foreach ($moveSet in @($handoff.moveSets)) {

View file

@ -58,9 +58,19 @@ node $consumerScript $consumerExport `
--expect-plugin-runtime sai.vn:executable-sidecar ` --expect-plugin-runtime sai.vn:executable-sidecar `
--expect-plugin-runtime-transport sai.vn:cultmesh ` --expect-plugin-runtime-transport sai.vn:cultmesh `
--expect-plugin-runtime-authority sai.vn:no-provider-state-mutation ` --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 norn.graph:executable-sidecar `
--expect-plugin-runtime-transport norn.graph:cultmesh ` --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) { if ($LASTEXITCODE -ne 0) {
throw "Plugin owner conformance consumer smoke failed with exit code $LASTEXITCODE" throw "Plugin owner conformance consumer smoke failed with exit code $LASTEXITCODE"

View file

@ -24,6 +24,7 @@ if (!exportDirectory) {
" --expect-plugin-runtime <pluginId:invocationModel>", " --expect-plugin-runtime <pluginId:invocationModel>",
" --expect-plugin-runtime-transport <pluginId:transport>", " --expect-plugin-runtime-transport <pluginId:transport>",
" --expect-plugin-runtime-authority <pluginId:authority>", " --expect-plugin-runtime-authority <pluginId:authority>",
" --expect-plugin-runtime-field <pluginId:field.path:value>",
" --expect-plugin-handoff <pluginId>", " --expect-plugin-handoff <pluginId>",
" --expect-provider-surface <providerId:surfaceId>", " --expect-provider-surface <providerId:surfaceId>",
" --expect-provider-surface-kind <providerId:surfaceId:surfaceKind>", " --expect-provider-surface-kind <providerId:surfaceId:surfaceKind>",
@ -189,6 +190,17 @@ function validateIndex(index, directory, expectations, errors) {
errors.push(`plugins:${expectation.pluginId}:runtime.authority:${expectation.authority}:missing`); 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) { for (const expectedPlugin of expectations.pluginHandoffs) {
const plugin = plugins.find(candidate => candidate.pluginId === expectedPlugin); const plugin = plugins.find(candidate => candidate.pluginId === expectedPlugin);
if (!plugin) { if (!plugin) {
@ -497,6 +509,7 @@ function parseArguments(args) {
pluginRuntimes: [], pluginRuntimes: [],
pluginRuntimeTransports: [], pluginRuntimeTransports: [],
pluginRuntimeAuthorities: [], pluginRuntimeAuthorities: [],
pluginRuntimeFields: [],
pluginHandoffs: [], pluginHandoffs: [],
providers: [], providers: [],
providerSurfaces: [], providerSurfaces: [],
@ -535,6 +548,7 @@ function parseArguments(args) {
["--expect-plugin-runtime", expectations.pluginRuntimes], ["--expect-plugin-runtime", expectations.pluginRuntimes],
["--expect-plugin-runtime-transport", expectations.pluginRuntimeTransports], ["--expect-plugin-runtime-transport", expectations.pluginRuntimeTransports],
["--expect-plugin-runtime-authority", expectations.pluginRuntimeAuthorities], ["--expect-plugin-runtime-authority", expectations.pluginRuntimeAuthorities],
["--expect-plugin-runtime-field", expectations.pluginRuntimeFields],
["--expect-plugin-handoff", expectations.pluginHandoffs], ["--expect-plugin-handoff", expectations.pluginHandoffs],
["--expect-provider-surface", expectations.providerSurfaces], ["--expect-provider-surface", expectations.providerSurfaces],
["--expect-provider-surface-kind", expectations.providerSurfaceKinds], ["--expect-provider-surface-kind", expectations.providerSurfaceKinds],
@ -585,6 +599,8 @@ function parseArguments(args) {
target.push(parsePluginExpectation(value, "transport")); target.push(parsePluginExpectation(value, "transport"));
} else if (option === "--expect-plugin-runtime-authority") { } else if (option === "--expect-plugin-runtime-authority") {
target.push(parsePluginExpectation(value, "authority")); target.push(parsePluginExpectation(value, "authority"));
} else if (option === "--expect-plugin-runtime-field") {
target.push(parsePluginRuntimeFieldExpectation(value));
} else if (option === "--expect-plugin-handoff") { } else if (option === "--expect-plugin-handoff") {
target.push(value); target.push(value);
} else if (option === "--expect-provider-surface") { } 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 <pluginId:field.path:value> form, got: ${value}`);
process.exit(2);
}
return {
pluginId: parts[0],
fieldPath: parts[1],
value: parts.slice(2).join(":"),
};
}
function parseProviderExpectation(value, field) { function parseProviderExpectation(value, field) {
const separator = value.indexOf(":"); const separator = value.indexOf(":");
if (separator <= 0 || separator === value.length - 1) { if (separator <= 0 || separator === value.length - 1) {

View file

@ -376,7 +376,16 @@
"invocationModel": "executable-sidecar", "invocationModel": "executable-sidecar",
"contract": "gamecult.eve.plugin_abi.v1", "contract": "gamecult.eve.plugin_abi.v1",
"transports": ["cultmesh", "stdio"], "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"], "requiredFixtures": ["sai-vn"],
"schemaPath": "schemas/gamecult.eve.plugin.v1.schema.json", "schemaPath": "schemas/gamecult.eve.plugin.v1.schema.json",
@ -406,7 +415,16 @@
"invocationModel": "executable-sidecar", "invocationModel": "executable-sidecar",
"contract": "gamecult.eve.plugin_abi.v1", "contract": "gamecult.eve.plugin_abi.v1",
"transports": ["cultmesh", "stdio"], "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"], "requiredFixtures": ["sai-vn"],
"schemaPath": "schemas/gamecult.eve.plugin.v1.schema.json", "schemaPath": "schemas/gamecult.eve.plugin.v1.schema.json",
@ -436,7 +454,16 @@
"invocationModel": "executable-sidecar", "invocationModel": "executable-sidecar",
"contract": "gamecult.eve.plugin_abi.v1", "contract": "gamecult.eve.plugin_abi.v1",
"transports": ["cultmesh", "stdio"], "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"], "requiredFixtures": ["sai-vn"],
"schemaPath": "schemas/gamecult.eve.plugin.v1.schema.json", "schemaPath": "schemas/gamecult.eve.plugin.v1.schema.json",

View file

@ -518,10 +518,12 @@ async function readPluginRuntimeBoundary(plugin) {
contract: manifestDocument.runtime?.contract || "", contract: manifestDocument.runtime?.contract || "",
transports: manifestDocument.runtime?.transports || [], transports: manifestDocument.runtime?.transports || [],
authority: manifestDocument.runtime?.authority || [], authority: manifestDocument.runtime?.authority || [],
sidecar: manifestDocument.runtime?.sidecar || {},
advertisementInvocationModel: advertisementDocument.runtime?.invocationModel || "", advertisementInvocationModel: advertisementDocument.runtime?.invocationModel || "",
advertisementContract: advertisementDocument.runtime?.contract || "", advertisementContract: advertisementDocument.runtime?.contract || "",
advertisementTransports: advertisementDocument.runtime?.transports || [], advertisementTransports: advertisementDocument.runtime?.transports || [],
advertisementAuthority: advertisementDocument.runtime?.authority || [], advertisementAuthority: advertisementDocument.runtime?.authority || [],
advertisementSidecar: advertisementDocument.runtime?.sidecar || {},
}; };
} catch { } catch {
return { return {
@ -529,10 +531,12 @@ async function readPluginRuntimeBoundary(plugin) {
contract: "", contract: "",
transports: [], transports: [],
authority: [], authority: [],
sidecar: {},
advertisementInvocationModel: "", advertisementInvocationModel: "",
advertisementContract: "", advertisementContract: "",
advertisementTransports: [], advertisementTransports: [],
advertisementAuthority: [], advertisementAuthority: [],
advertisementSidecar: {},
}; };
} }
} }
@ -560,6 +564,21 @@ function validatePluginRuntimeBoundary(plugin, runtimeBoundary) {
errors.push(...missingMembers(expectedRuntime.transports || [], runtimeBoundary.advertisementTransports || [], "runtime.advertisementTransports")); errors.push(...missingMembers(expectedRuntime.transports || [], runtimeBoundary.advertisementTransports || [], "runtime.advertisementTransports"));
errors.push(...missingMembers(expectedRuntime.authority || [], runtimeBoundary.authority || [], "runtime.authority")); errors.push(...missingMembers(expectedRuntime.authority || [], runtimeBoundary.authority || [], "runtime.authority"));
errors.push(...missingMembers(expectedRuntime.authority || [], runtimeBoundary.advertisementAuthority || [], "runtime.advertisementAuthority")); 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; return errors;
} }
@ -837,6 +856,11 @@ async function validatePluginAbiFixture(plugin) {
for (const operation of ["describe", "validate", "project", "lower", "measure", "apply"]) { for (const operation of ["describe", "validate", "project", "lower", "measure", "apply"]) {
if (!operations.has(operation)) errors.push(`${plugin.abiFixturePath}:operation:${operation}:missing`); 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)) { if (!(pluginManifest.abiFixtures || []).includes(plugin.abiFixturePath)) {
errors.push(`${plugin.manifestPath}:abiFixtures:${plugin.abiFixturePath}:missing`); errors.push(`${plugin.manifestPath}:abiFixtures:${plugin.abiFixturePath}:missing`);