Define plugin sidecar ABI contract
This commit is contained in:
parent
a947062f7b
commit
f505b7e86c
16 changed files with 304 additions and 23 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ if (!exportDirectory) {
|
|||
" --expect-plugin-runtime <pluginId:invocationModel>",
|
||||
" --expect-plugin-runtime-transport <pluginId:transport>",
|
||||
" --expect-plugin-runtime-authority <pluginId:authority>",
|
||||
" --expect-plugin-runtime-field <pluginId:field.path:value>",
|
||||
" --expect-plugin-handoff <pluginId>",
|
||||
" --expect-provider-surface <providerId:surfaceId>",
|
||||
" --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`);
|
||||
}
|
||||
}
|
||||
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 <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) {
|
||||
const separator = value.indexOf(":");
|
||||
if (separator <= 0 || separator === value.length - 1) {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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`);
|
||||
|
|
|
|||
Loading…
Reference in a new issue