diff --git a/docs/eve-migration-roadmap.md b/docs/eve-migration-roadmap.md index 966fbfa..a4f0165 100644 --- a/docs/eve-migration-roadmap.md +++ b/docs/eve-migration-roadmap.md @@ -280,6 +280,13 @@ Exit criteria: Recently cut: +- Plugin manifests and advertisements now carry a 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. - 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 4097208..37fab6d 100644 --- a/docs/parity-testing-harness.md +++ b/docs/parity-testing-harness.md @@ -156,8 +156,12 @@ powershell -ExecutionPolicy Bypass -File .\scripts\run-plugin-handoff-smoke.ps1 That script validates the Sai and Norn handoff manifests against their plugin manifests, advertisements, ABI fixtures, move-set paths, contract inputs, and -external proofs. The plugin-owner conformance smoke also asserts those handoff -paths from the conformance export. +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 +export. Aetheria provider handoff smoke: diff --git a/docs/plugin-architecture.md b/docs/plugin-architecture.md index 2773d1e..d2578fa 100644 --- a/docs/plugin-architecture.md +++ b/docs/plugin-architecture.md @@ -114,6 +114,14 @@ not by importing plugin internals directly. A plugin implementation may use a same-language library internally, but that is an implementation detail behind the executable plugin boundary. +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. + The ownership rule is semantic, not packaging ceremony: the plugin owns the capability and representation shape, while providers own live state and renderer clients own projection. diff --git a/plugins/incubating/norn-graph.plugin-advertisement.json b/plugins/incubating/norn-graph.plugin-advertisement.json index 029fa1f..06f37be 100644 --- a/plugins/incubating/norn-graph.plugin-advertisement.json +++ b/plugins/incubating/norn-graph.plugin-advertisement.json @@ -4,6 +4,16 @@ "ownerService": "norn", "version": "0.1.0-incubating", "manifestAddress": "file://plugins/incubating/norn-graph.plugin.json", + "runtime": { + "invocationModel": "executable-sidecar", + "contract": "gamecult.eve.plugin_abi.v1", + "transports": ["cultmesh", "stdio"], + "authority": [ + "renderer-independent", + "no-provider-state-mutation", + "provider-accepts-or-denies-commands" + ] + }, "schemas": [ "norn.graph.document.v1", "norn.graph.layout_request.v1", diff --git a/plugins/incubating/norn-graph.plugin.json b/plugins/incubating/norn-graph.plugin.json index 4ded2e3..5634895 100644 --- a/plugins/incubating/norn-graph.plugin.json +++ b/plugins/incubating/norn-graph.plugin.json @@ -24,6 +24,16 @@ "effect": "focus-graph" } ], + "runtime": { + "invocationModel": "executable-sidecar", + "contract": "gamecult.eve.plugin_abi.v1", + "transports": ["cultmesh", "stdio"], + "authority": [ + "renderer-independent", + "no-provider-state-mutation", + "provider-accepts-or-denies-commands" + ] + }, "stateSchemas": [ "norn.graph.document.v1", "norn.graph.layout_request.v1", diff --git a/plugins/incubating/sai-vn.plugin-advertisement.json b/plugins/incubating/sai-vn.plugin-advertisement.json index 95e35df..17c7b1d 100644 --- a/plugins/incubating/sai-vn.plugin-advertisement.json +++ b/plugins/incubating/sai-vn.plugin-advertisement.json @@ -4,6 +4,16 @@ "ownerService": "sai", "version": "0.1.0-incubating", "manifestAddress": "file://plugins/incubating/sai-vn.plugin.json", + "runtime": { + "invocationModel": "executable-sidecar", + "contract": "gamecult.eve.plugin_abi.v1", + "transports": ["cultmesh", "stdio"], + "authority": [ + "renderer-independent", + "no-provider-state-mutation", + "provider-accepts-or-denies-commands" + ] + }, "schemas": [ "sai.vn.story_session.v1", "sai.vn.visual_manifest.v1" diff --git a/plugins/incubating/sai-vn.plugin.json b/plugins/incubating/sai-vn.plugin.json index 9a75c6e..1c21516 100644 --- a/plugins/incubating/sai-vn.plugin.json +++ b/plugins/incubating/sai-vn.plugin.json @@ -32,6 +32,16 @@ "effect": "jump-to-knot" } ], + "runtime": { + "invocationModel": "executable-sidecar", + "contract": "gamecult.eve.plugin_abi.v1", + "transports": ["cultmesh", "stdio"], + "authority": [ + "renderer-independent", + "no-provider-state-mutation", + "provider-accepts-or-denies-commands" + ] + }, "stateSchemas": [ "sai.vn.story_session.v1", "sai.vn.visual_manifest.v1" diff --git a/plugins/incubating/tex-math.plugin-advertisement.json b/plugins/incubating/tex-math.plugin-advertisement.json index 0c8ca4e..a540864 100644 --- a/plugins/incubating/tex-math.plugin-advertisement.json +++ b/plugins/incubating/tex-math.plugin-advertisement.json @@ -4,6 +4,16 @@ "ownerService": "eveplugins", "version": "0.1.0-incubating", "manifestAddress": "file://plugins/incubating/tex-math.plugin.json", + "runtime": { + "invocationModel": "executable-sidecar", + "contract": "gamecult.eve.plugin_abi.v1", + "transports": ["cultmesh", "stdio"], + "authority": [ + "renderer-independent", + "no-provider-state-mutation", + "provider-accepts-or-denies-commands" + ] + }, "schemas": [ "tex.math.source.v1", "tex.math.render_request.v1", diff --git a/plugins/incubating/tex-math.plugin.json b/plugins/incubating/tex-math.plugin.json index 6c6fe3b..6a60eb5 100644 --- a/plugins/incubating/tex-math.plugin.json +++ b/plugins/incubating/tex-math.plugin.json @@ -13,6 +13,16 @@ "embed.tex" ], "commands": [], + "runtime": { + "invocationModel": "executable-sidecar", + "contract": "gamecult.eve.plugin_abi.v1", + "transports": ["cultmesh", "stdio"], + "authority": [ + "renderer-independent", + "no-provider-state-mutation", + "provider-accepts-or-denies-commands" + ] + }, "stateSchemas": [ "tex.math.source.v1", "tex.math.render_request.v1", diff --git a/schemas/gamecult.eve.plugin.v1.schema.json b/schemas/gamecult.eve.plugin.v1.schema.json index 6e4d51d..00706e4 100644 --- a/schemas/gamecult.eve.plugin.v1.schema.json +++ b/schemas/gamecult.eve.plugin.v1.schema.json @@ -11,6 +11,7 @@ "incubation", "componentKinds", "commands", + "runtime", "stateSchemas", "fixtures", "capabilities" @@ -92,6 +93,40 @@ "additionalProperties": true } }, + "runtime": { + "type": "object", + "required": [ + "invocationModel", + "contract", + "transports", + "authority" + ], + "properties": { + "invocationModel": { + "type": "string", + "minLength": 1 + }, + "contract": { + "type": "string", + "minLength": 1 + }, + "transports": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "authority": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "additionalProperties": true + }, "stateSchemas": { "type": "array", "items": { diff --git a/schemas/gamecult.eve.plugin_advertisement.v1.schema.json b/schemas/gamecult.eve.plugin_advertisement.v1.schema.json index 3a054d5..e44f987 100644 --- a/schemas/gamecult.eve.plugin_advertisement.v1.schema.json +++ b/schemas/gamecult.eve.plugin_advertisement.v1.schema.json @@ -9,6 +9,7 @@ "ownerService", "version", "manifestAddress", + "runtime", "schemas", "componentKinds", "commands", @@ -34,6 +35,40 @@ "type": "string", "minLength": 1 }, + "runtime": { + "type": "object", + "required": [ + "invocationModel", + "contract", + "transports", + "authority" + ], + "properties": { + "invocationModel": { + "type": "string", + "minLength": 1 + }, + "contract": { + "type": "string", + "minLength": 1 + }, + "transports": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "authority": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "additionalProperties": true + }, "schemas": { "type": "array", "items": { diff --git a/scripts/run-plugin-handoff-smoke.ps1 b/scripts/run-plugin-handoff-smoke.ps1 index 79f7c5a..3e67bed 100644 --- a/scripts/run-plugin-handoff-smoke.ps1 +++ b/scripts/run-plugin-handoff-smoke.ps1 @@ -65,6 +65,31 @@ foreach ($handoffPath in $HandoffPaths) { if ($manifest.incubation.splitTarget -ne $handoff.splitTarget) { throw "Plugin handoff $($handoff.pluginId) split target does not match manifest: $($manifest.incubation.splitTarget)" } + if ($manifest.runtime.invocationModel -ne "executable-sidecar") { + throw "Plugin handoff $($handoff.pluginId) runtime is not executable-sidecar: $($manifest.runtime.invocationModel)" + } + if ($manifest.runtime.contract -ne "gamecult.eve.plugin_abi.v1") { + throw "Plugin handoff $($handoff.pluginId) runtime contract is not gamecult.eve.plugin_abi.v1: $($manifest.runtime.contract)" + } + if ($advertisement.runtime.invocationModel -ne $manifest.runtime.invocationModel) { + throw "Plugin handoff $($handoff.pluginId) advertisement runtime does not match manifest runtime: $($advertisement.runtime.invocationModel)" + } + foreach ($transport in @("cultmesh", "stdio")) { + if (-not (@($manifest.runtime.transports) -contains $transport)) { + throw "Plugin handoff $($handoff.pluginId) manifest runtime missing transport: $transport" + } + if (-not (@($advertisement.runtime.transports) -contains $transport)) { + throw "Plugin handoff $($handoff.pluginId) advertisement runtime missing transport: $transport" + } + } + foreach ($authority in @("renderer-independent", "no-provider-state-mutation", "provider-accepts-or-denies-commands")) { + if (-not (@($manifest.runtime.authority) -contains $authority)) { + throw "Plugin handoff $($handoff.pluginId) manifest runtime missing authority: $authority" + } + if (-not (@($advertisement.runtime.authority) -contains $authority)) { + throw "Plugin handoff $($handoff.pluginId) advertisement runtime missing authority: $authority" + } + } $operations = @($abiFixture.operations | ForEach-Object { $_.operation }) foreach ($operation in @("describe", "validate", "project", "lower", "measure", "apply")) { diff --git a/scripts/run-plugin-owner-conformance-consumer-smoke.ps1 b/scripts/run-plugin-owner-conformance-consumer-smoke.ps1 index 7318518..3456a16 100644 --- a/scripts/run-plugin-owner-conformance-consumer-smoke.ps1 +++ b/scripts/run-plugin-owner-conformance-consumer-smoke.ps1 @@ -54,7 +54,13 @@ node $consumerScript $consumerExport ` --expect-plugin-capability sai.vn:story.continue ` --expect-plugin-capability sai.vn:story.jump ` --expect-plugin-capability norn.graph:embed.norn ` - --expect-plugin-capability norn.graph:graph.node.activate + --expect-plugin-capability norn.graph:graph.node.activate ` + --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 norn.graph:executable-sidecar ` + --expect-plugin-runtime-transport norn.graph:cultmesh ` + --expect-plugin-runtime-authority norn.graph:no-provider-state-mutation 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 f530ade..3383318 100644 --- a/tools/conformance/consume-export.mjs +++ b/tools/conformance/consume-export.mjs @@ -21,6 +21,9 @@ if (!exportDirectory) { " --expect-conformance-handoff", " --expect-plugin-operation ", " --expect-plugin-capability ", + " --expect-plugin-runtime ", + " --expect-plugin-runtime-transport ", + " --expect-plugin-runtime-authority ", " --expect-plugin-handoff ", " --expect-provider-surface ", " --expect-provider-surface-kind ", @@ -153,6 +156,36 @@ function validateIndex(index, directory, expectations, errors) { errors.push(`plugins:${expectation.pluginId}:capability:${expectation.capability}:missing`); } } + for (const expectation of expectations.pluginRuntimes) { + const plugin = plugins.find(candidate => candidate.pluginId === expectation.pluginId); + if (!plugin) { + errors.push(`plugins:${expectation.pluginId}:missing`); + continue; + } + if (plugin.runtimeBoundary?.invocationModel !== expectation.invocationModel) { + errors.push(`plugins:${expectation.pluginId}:runtime:expected ${expectation.invocationModel} got ${plugin.runtimeBoundary?.invocationModel || ""}`); + } + } + for (const expectation of expectations.pluginRuntimeTransports) { + const plugin = plugins.find(candidate => candidate.pluginId === expectation.pluginId); + if (!plugin) { + errors.push(`plugins:${expectation.pluginId}:missing`); + continue; + } + if (!Array.isArray(plugin.runtimeBoundary?.transports) || !plugin.runtimeBoundary.transports.includes(expectation.transport)) { + errors.push(`plugins:${expectation.pluginId}:runtime.transport:${expectation.transport}:missing`); + } + } + for (const expectation of expectations.pluginRuntimeAuthorities) { + const plugin = plugins.find(candidate => candidate.pluginId === expectation.pluginId); + if (!plugin) { + errors.push(`plugins:${expectation.pluginId}:missing`); + continue; + } + if (!Array.isArray(plugin.runtimeBoundary?.authority) || !plugin.runtimeBoundary.authority.includes(expectation.authority)) { + errors.push(`plugins:${expectation.pluginId}:runtime.authority:${expectation.authority}:missing`); + } + } for (const expectedPlugin of expectations.pluginHandoffs) { const plugin = plugins.find(candidate => candidate.pluginId === expectedPlugin); if (!plugin) { @@ -422,6 +455,9 @@ function parseArguments(args) { plugins: [], pluginOperations: [], pluginCapabilities: [], + pluginRuntimes: [], + pluginRuntimeTransports: [], + pluginRuntimeAuthorities: [], pluginHandoffs: [], providers: [], providerSurfaces: [], @@ -454,6 +490,9 @@ function parseArguments(args) { ["--expect-split-target", expectations.splitTargets], ["--expect-plugin-operation", expectations.pluginOperations], ["--expect-plugin-capability", expectations.pluginCapabilities], + ["--expect-plugin-runtime", expectations.pluginRuntimes], + ["--expect-plugin-runtime-transport", expectations.pluginRuntimeTransports], + ["--expect-plugin-runtime-authority", expectations.pluginRuntimeAuthorities], ["--expect-plugin-handoff", expectations.pluginHandoffs], ["--expect-provider-surface", expectations.providerSurfaces], ["--expect-provider-surface-kind", expectations.providerSurfaceKinds], @@ -495,6 +534,12 @@ function parseArguments(args) { target.push(parsePluginExpectation(value, "operation")); } else if (option === "--expect-plugin-capability") { target.push(parsePluginExpectation(value, "capability")); + } else if (option === "--expect-plugin-runtime") { + target.push(parsePluginExpectation(value, "invocationModel")); + } else if (option === "--expect-plugin-runtime-transport") { + target.push(parsePluginExpectation(value, "transport")); + } else if (option === "--expect-plugin-runtime-authority") { + target.push(parsePluginExpectation(value, "authority")); } else if (option === "--expect-plugin-handoff") { target.push(value); } else if (option === "--expect-provider-surface") { diff --git a/tools/parity/parity-manifest.json b/tools/parity/parity-manifest.json index be64379..cc02a74 100644 --- a/tools/parity/parity-manifest.json +++ b/tools/parity/parity-manifest.json @@ -372,6 +372,12 @@ "graduationTrigger": "Split when VN/Ink semantics, story command fixtures, and ABI project/apply/validate cases are stable.", "capabilities": ["vn.stage", "story.choose", "story.continue", "story.jump"], "optionalPlugins": ["norn.graph", "tex.math"], + "expectedRuntime": { + "invocationModel": "executable-sidecar", + "contract": "gamecult.eve.plugin_abi.v1", + "transports": ["cultmesh", "stdio"], + "authority": ["renderer-independent", "no-provider-state-mutation", "provider-accepts-or-denies-commands"] + }, "requiredFixtures": ["sai-vn"], "schemaPath": "schemas/gamecult.eve.plugin.v1.schema.json", "manifestPath": "plugins/incubating/sai-vn.plugin.json", @@ -396,6 +402,12 @@ "splitTarget": "Norn/eve-plugin-graph", "graduationTrigger": "Split when embed.norn graph layout, interaction, fallback, and conformance fixtures are stable.", "capabilities": ["embed.norn", "graph.node.activate"], + "expectedRuntime": { + "invocationModel": "executable-sidecar", + "contract": "gamecult.eve.plugin_abi.v1", + "transports": ["cultmesh", "stdio"], + "authority": ["renderer-independent", "no-provider-state-mutation", "provider-accepts-or-denies-commands"] + }, "requiredFixtures": ["sai-vn"], "schemaPath": "schemas/gamecult.eve.plugin.v1.schema.json", "manifestPath": "plugins/incubating/norn-graph.plugin.json", @@ -420,6 +432,12 @@ "splitTarget": "EvePlugins/eve-plugin-tex", "graduationTrigger": "Split when TeX source, macro, baseline, cached render, and fallback fixtures are stable.", "capabilities": ["embed.tex", "tex.inline", "tex.block"], + "expectedRuntime": { + "invocationModel": "executable-sidecar", + "contract": "gamecult.eve.plugin_abi.v1", + "transports": ["cultmesh", "stdio"], + "authority": ["renderer-independent", "no-provider-state-mutation", "provider-accepts-or-denies-commands"] + }, "requiredFixtures": ["sai-vn"], "schemaPath": "schemas/gamecult.eve.plugin.v1.schema.json", "manifestPath": "plugins/incubating/tex-math.plugin.json", diff --git a/tools/parity/run-parity.mjs b/tools/parity/run-parity.mjs index 0636f8b..9572d86 100644 --- a/tools/parity/run-parity.mjs +++ b/tools/parity/run-parity.mjs @@ -443,6 +443,8 @@ async function evaluatePlugin(plugin, fixtureResults) { schema: "gamecult.eve.plugin_advertisement.v1", pluginId: plugin.pluginId, }); + const runtimeBoundary = await readPluginRuntimeBoundary(plugin); + const runtimeBoundaryErrors = validatePluginRuntimeBoundary(plugin, runtimeBoundary); const abiOperations = await readPluginAbiOperations(plugin.abiFixturePath); const abiErrors = await validatePluginAbiFixture(plugin); const status = missingPaths.length @@ -455,7 +457,9 @@ async function evaluatePlugin(plugin, fixtureResults) { ? "invalid-plugin-manifest" : advertisementErrors.length ? "invalid-plugin-advertisement" - : abiErrors.length + : runtimeBoundaryErrors.length + ? "invalid-plugin-runtime-boundary" + : abiErrors.length ? "invalid-plugin-abi-fixture" : plugin.kind === "incubating" ? "incubating" @@ -471,6 +475,8 @@ async function evaluatePlugin(plugin, fixtureResults) { splitTarget: plugin.splitTarget || "", capabilities: plugin.capabilities || [], optionalPlugins: plugin.optionalPlugins || [], + runtimeBoundary, + runtimeBoundaryErrors, requiredFixtures, missingRequiredFixtures, schemaPath: plugin.schemaPath || "", @@ -503,6 +509,60 @@ async function readPluginAbiOperations(abiFixturePath) { } } +async function readPluginRuntimeBoundary(plugin) { + try { + const manifestDocument = await readJsonDocument(plugin.manifestPath); + const advertisementDocument = await readJsonDocument(plugin.advertisementPath); + return { + invocationModel: manifestDocument.runtime?.invocationModel || "", + contract: manifestDocument.runtime?.contract || "", + transports: manifestDocument.runtime?.transports || [], + authority: manifestDocument.runtime?.authority || [], + advertisementInvocationModel: advertisementDocument.runtime?.invocationModel || "", + advertisementContract: advertisementDocument.runtime?.contract || "", + advertisementTransports: advertisementDocument.runtime?.transports || [], + advertisementAuthority: advertisementDocument.runtime?.authority || [], + }; + } catch { + return { + invocationModel: "", + contract: "", + transports: [], + authority: [], + advertisementInvocationModel: "", + advertisementContract: "", + advertisementTransports: [], + advertisementAuthority: [], + }; + } +} + +function validatePluginRuntimeBoundary(plugin, runtimeBoundary) { + const errors = []; + const expectedRuntime = plugin.expectedRuntime || {}; + if (!runtimeBoundary.invocationModel) errors.push("runtime.invocationModel:missing"); + if (!runtimeBoundary.contract) errors.push("runtime.contract:missing"); + if (!runtimeBoundary.transports?.length) errors.push("runtime.transports:missing"); + if (!runtimeBoundary.authority?.length) errors.push("runtime.authority:missing"); + if (runtimeBoundary.invocationModel !== runtimeBoundary.advertisementInvocationModel) { + errors.push(`runtime.invocationModel:advertisement:expected ${runtimeBoundary.invocationModel} got ${runtimeBoundary.advertisementInvocationModel}`); + } + if (runtimeBoundary.contract !== runtimeBoundary.advertisementContract) { + errors.push(`runtime.contract:advertisement:expected ${runtimeBoundary.contract} got ${runtimeBoundary.advertisementContract}`); + } + if (expectedRuntime.invocationModel && runtimeBoundary.invocationModel !== expectedRuntime.invocationModel) { + errors.push(`runtime.invocationModel:expected ${expectedRuntime.invocationModel} got ${runtimeBoundary.invocationModel}`); + } + if (expectedRuntime.contract && runtimeBoundary.contract !== expectedRuntime.contract) { + errors.push(`runtime.contract:expected ${expectedRuntime.contract} got ${runtimeBoundary.contract}`); + } + errors.push(...missingMembers(expectedRuntime.transports || [], runtimeBoundary.transports || [], "runtime.transports")); + 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")); + return errors; +} + async function evaluateFixtureMetadata(fixture) { const errors = []; if (!fixture.metadataPath) return { metadata: null, errors: ["metadataPath:missing"] }; @@ -1399,6 +1459,7 @@ function buildConformanceExport(report) { handoffExportPath: makeHandoffExportPath("plugin", plugin.pluginId, plugin.handoffPath), abiFixturePath: plugin.abiFixturePath, abiOperations: plugin.abiOperations || [], + runtimeBoundary: plugin.runtimeBoundary, optionalPlugins: plugin.optionalPlugins || [], capabilities: plugin.capabilities, })), @@ -1531,6 +1592,7 @@ function collectCapabilityGaps(report) { ...(plugin.missingIncubationFields || []).map(id => `metadata:${id}`), ...(plugin.schemaErrors || []).map(id => `schema:${id}`), ...(plugin.advertisementErrors || []).map(id => `advertisement:${id}`), + ...(plugin.runtimeBoundaryErrors || []).map(id => `runtime-boundary:${id}`), ...(plugin.abiErrors || []).map(id => `abi:${id}`), ]) { addGap({ @@ -1793,6 +1855,7 @@ function renderMarkdown(report) { ...plugin.missingIncubationFields.map(id => `metadata:${id}`), ...plugin.schemaErrors.map(id => `schema:${id}`), ...plugin.advertisementErrors.map(id => `advertisement:${id}`), + ...plugin.runtimeBoundaryErrors.map(id => `runtime-boundary:${id}`), ...plugin.abiErrors.map(id => `abi:${id}`), ].join(", "); lines.push(`| ${plugin.title || plugin.pluginId} | ${plugin.status} | ${plugin.ownerRepo} | ${(plugin.abiOperations || []).join(", ")} | ${plugin.abiFixturePath || ""} | ${plugin.capabilities.join(", ")} | ${missing} |`);