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,
|
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
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)) {
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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`);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue