diff --git a/docs/eve-migration-roadmap.md b/docs/eve-migration-roadmap.md index a5736a4..c426462 100644 --- a/docs/eve-migration-roadmap.md +++ b/docs/eve-migration-roadmap.md @@ -867,9 +867,8 @@ Recently cut: graduates. The conformance export carries `conformanceHandoffPath`, and the generic consumer smoke asserts it so EveConformance split readiness is not trapped in Eve's parity manifest. -- Sai, Norn, and TeX now have plugin handoff manifests at +- Sai and TeX have plugin handoff manifests at `plugins/incubating/sai-vn.plugin-handoff.json`, - `plugins/incubating/norn-graph.plugin-handoff.json`, and `plugins/incubating/tex-math.plugin-handoff.json`, plus a shared verifier at `scripts/run-plugin-handoff-smoke.ps1`. The handoffs name the manifest, advertisement, ABI fixture, move sets, contract inputs, forbidden imports, @@ -877,6 +876,13 @@ Recently cut: carries plugin `handoffPath` and `pluginHandoffMoveCoverage[]`, and the plugin-owner smoke asserts them so Sai, Norn, and EvePlugins can consume the plugin boundary without reading Eve's parity manifest. +- Norn has crossed that handoff boundary. `Norn/plugins` now publishes the + `norn.graph` manifest, advertisement, and ABI fixture, while + `Norn/crates/norn-eve-plugin` runs the first executable sidecar over bounded + NDJSON stdio. Eve consumes those owner paths directly and no longer carries + a duplicate Norn manifest or handoff document. The plugin currently owns + `describe`, `validate`, `project`, and solver-backed `measure`; runtime + lowering and provider command acceptance remain outside Norn. - Aetheria now has a provider handoff manifest at `web/fixtures/aetheria-provider-handoff.json` and a verifier at `scripts/run-aetheria-provider-handoff-smoke.ps1`. The handoff names the diff --git a/docs/plugin-architecture.md b/docs/plugin-architecture.md index 1c02050..ef2bb18 100644 --- a/docs/plugin-architecture.md +++ b/docs/plugin-architecture.md @@ -456,8 +456,14 @@ Runtime projection support is not plugin hosting. A Unity, Electron, Flutter, TUI, or web client may provide an adapter for sidecar-advertised Norn or TeX output, but the sidecar daemon and plugin ABI remain runtime independent. If a Sai surface deploys with nested Norn or TeX, Sai composes the slots and keeps VN -stage authority; Norn and TeX still publish, validate, lower, and receive -commands through their own plugin contracts. +stage authority; Norn and TeX still publish and validate their own semantic +contracts. Each plugin advertises only the ABI operations it actually owns. + +The carrier does not define the plugin. The first executable Norn proof uses +UTF-8 NDJSON over stdio: one request document per line, one response per +request in order, blank lines ignored, diagnostics on stderr. A future +CultMesh daemon transport must carry the same typed request and response +documents; it must not create a second semantic API. Nested availability is a composition fact, not a Sai dependency story. A Sai surface can be deployed with a nested Norn or TeX surface only when that other @@ -474,6 +480,11 @@ Norn plugin responsibilities: - define fallback and capability-gap behavior when a renderer cannot run Norn; - provide graph/map fixtures for Eve parity. +Norn currently claims `describe`, `validate`, `project`, and `measure`. It does +not claim `lower` or `apply`: runtime lowerers own native projection, and +providers own command acceptance and receipts. Conformance validates advertised +operations rather than forcing every plugin to pretend it owns all ABI verbs. + TeX plugin responsibilities: - define `embed.tex` semantics; diff --git a/plugins/incubating/norn-graph.plugin-abi-fixture.json b/plugins/incubating/norn-graph.plugin-abi-fixture.json deleted file mode 100644 index fe3e566..0000000 --- a/plugins/incubating/norn-graph.plugin-abi-fixture.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "schema": "gamecult.eve.plugin_abi_fixture.v1", - "fixtureId": "norn-graph-plugin-abi", - "pluginId": "norn.graph", - "ownerRepo": "Norn", - "contract": "gamecult.eve.plugin_abi.v1", - "requestSchema": "gamecult.eve.plugin_abi.request.v1", - "responseSchema": "gamecult.eve.plugin_abi.response.v1", - "sourceFixture": "sai-vn", - "sourceFixtureRole": "optional-nested-placement-evidence", - "authority": "Norn owns graph semantics independently; the Sai fixture only proves a provider surface can nest embed.norn when norn.graph is available.", - "operations": [ - { - "operation": "describe", - "input": { - "manifest": "plugins/incubating/norn-graph.plugin.json", - "advertisement": "plugins/incubating/norn-graph.plugin-advertisement.json" - }, - "expect": { - "pluginId": "norn.graph", - "componentKinds": ["embed.norn"], - "commands": ["graph.node.activate", "graph.focus"], - "capabilities": ["embed.norn", "graph.node.activate"] - } - }, - { - "operation": "validate", - "input": { - "surface": "web/fixtures/sai-vn-surface.json", - "componentKinds": ["embed.norn"] - }, - "expect": { - "acceptedComponentKinds": ["embed.norn"], - "rejectedComponentKinds": ["embed.tex", "vn.stage"], - "requiredCapabilities": ["embed.norn"] - } - }, - { - "operation": "project", - "input": { - "surface": "web/fixtures/sai-vn-surface.json", - "nodeKind": "embed.norn" - }, - "expect": { - "projectionKind": "embedded-semantic-surface", - "ownedComponentKinds": ["embed.norn"], - "outputSchemas": ["norn.graph.document.v1", "norn.graph.layout_request.v1", "norn.graph.layout_result.v1"] - } - }, - { - "operation": "lower", - "input": { - "projection": "embedded-semantic-surface", - "targetRuntimes": ["web", "unity-uitoolkit"], - "componentKinds": ["embed.norn"] - }, - "expect": { - "loweringKind": "graph-viewport", - "preservedComponentKinds": ["embed.norn"], - "requiredSchemas": ["norn.graph.document.v1", "norn.graph.layout_result.v1"], - "commandEnvelope": "gamecult.eve.command.v1" - } - }, - { - "operation": "measure", - "input": { - "projection": "embedded-semantic-surface", - "layoutRequestSchema": "norn.graph.layout_request.v1" - }, - "expect": { - "measurementKind": "graph-layout-metrics", - "measurementOutputs": ["nodeBounds", "edgeRoutes", "viewportBounds"], - "preservesProviderAuthority": true - } - }, - { - "operation": "apply", - "input": { - "commands": ["graph.node.activate", "graph.focus"] - }, - "expect": { - "commandEffects": { - "graph.node.activate": "activate-node", - "graph.focus": "focus-graph" - }, - "receiptSchema": "gamecult.eve.command_receipt.v1" - } - } - ] -} diff --git a/plugins/incubating/norn-graph.plugin-advertisement.json b/plugins/incubating/norn-graph.plugin-advertisement.json deleted file mode 100644 index 71f372c..0000000 --- a/plugins/incubating/norn-graph.plugin-advertisement.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "schema": "gamecult.eve.plugin_advertisement.v1", - "pluginId": "norn.graph", - "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" - ], - "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", - "norn.graph.layout_request.v1", - "norn.graph.layout_result.v1" - ], - "componentKinds": [ - "embed.norn" - ], - "commands": [ - "graph.node.activate", - "graph.focus" - ], - "fixtures": [ - "sai-vn" - ] -} diff --git a/plugins/incubating/norn-graph.plugin-handoff.json b/plugins/incubating/norn-graph.plugin-handoff.json deleted file mode 100644 index 87e8d30..0000000 --- a/plugins/incubating/norn-graph.plugin-handoff.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "schema": "gamecult.eve.plugin_handoff.v1", - "pluginId": "norn.graph", - "ownerRepo": "Norn", - "currentHostRepo": "Eve", - "splitTarget": "Norn/eve-plugin-graph", - "manifestPath": "plugins/incubating/norn-graph.plugin.json", - "advertisementPath": "plugins/incubating/norn-graph.plugin-advertisement.json", - "abiFixturePath": "plugins/incubating/norn-graph.plugin-abi-fixture.json", - "authorityBoundary": "Norn owns graph document semantics, graph layout, graph interaction, fallback behavior, and plugin ABI behavior. Eve owns only plugin manifest, advertisement, ABI, provider requirement, and conformance export contracts.", - "moveSets": [ - { - "id": "plugin-manifest", - "destinationOwner": "Norn", - "currentPaths": [ - "plugins/incubating/norn-graph.plugin.json", - "plugins/incubating/norn-graph.plugin-advertisement.json" - ], - "replacementProof": "Norn publishes norn.graph plugin manifest and advertisement from the Norn repo or sidecar daemon." - }, - { - "id": "abi-conformance", - "destinationOwner": "Norn", - "currentPaths": [ - "plugins/incubating/norn-graph.plugin-abi-fixture.json", - "web/fixtures/sai-vn-surface.json", - "web/fixtures/sai-vn-surface.conformance.json" - ], - "replacementProof": "Norn owns embed.norn layout, interaction, fallback, and ABI conformance cases; the Sai fixture is only optional nested placement evidence." - } - ], - "contractInputs": [ - "gamecult.eve.plugin.v1", - "gamecult.eve.plugin_advertisement.v1", - "gamecult.eve.plugin_abi.v1", - "gamecult.eve.plugin_abi.request.v1", - "gamecult.eve.plugin_abi.response.v1", - "gamecult.eve.plugin_abi_fixture.v1", - "gamecult.eve.conformance_export.v1" - ], - "forbiddenImports": [ - "Aetheria provider state", - "Sai story session internals or VN command authority", - "renderer-native projection internals", - "provider command acceptance or receipt authority" - ], - "requiredExternalProofs": [ - "Norn publishes the norn.graph sidecar ABI without depending on Eve source layout.", - "Norn owns graph layout, interaction, fallback, and conformance fixtures.", - "Providers consume norn.graph through plugin advertisements and ABI operations.", - "Runtimes declare norn.graph projection support through runtime capability manifests." - ] -} diff --git a/plugins/incubating/norn-graph.plugin.json b/plugins/incubating/norn-graph.plugin.json deleted file mode 100644 index 74f3d36..0000000 --- a/plugins/incubating/norn-graph.plugin.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "schema": "gamecult.eve.plugin.v1", - "pluginId": "norn.graph", - "title": "Norn Graph", - "owner": "Norn", - "incubation": { - "ownerRepo": "Norn", - "currentHostRepo": "Eve", - "splitTarget": "Norn/eve-plugin-graph", - "graduationTrigger": "Split when graph layout, interaction, fallback, and conformance fixtures are stable." - }, - "componentKinds": [ - "embed.norn" - ], - "commands": [ - { - "command": "graph.node.activate", - "schema": "norn.graph.command.v1", - "effect": "activate-node" - }, - { - "command": "graph.focus", - "schema": "norn.graph.command.v1", - "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" - ], - "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", - "norn.graph.layout_request.v1", - "norn.graph.layout_result.v1" - ], - "fixtures": [ - { - "fixtureId": "sai-vn", - "schema": "gamecult.eve.surface.v1", - "path": "web/fixtures/sai-vn-surface.json", - "role": "optional-nested-placement-evidence", - "authority": "Norn semantics remain plugin-owned; the Sai fixture only proves a surface can nest embed.norn when norn.graph is available." - } - ], - "abiFixtures": [ - "plugins/incubating/norn-graph.plugin-abi-fixture.json" - ], - "capabilities": { - "requires": [ - "embed.norn" - ], - "optionalCapabilities": [ - "graph.node.activate", - "graph.pan", - "graph.zoom" - ] - } -} diff --git a/schemas/gamecult.eve.conformance_export.v1.schema.json b/schemas/gamecult.eve.conformance_export.v1.schema.json index 9d1821b..7969a60 100644 --- a/schemas/gamecult.eve.conformance_export.v1.schema.json +++ b/schemas/gamecult.eve.conformance_export.v1.schema.json @@ -144,7 +144,6 @@ "runtimeId", "runtimeStatus", "ownerRepo", - "splitTarget", "pluginId", "severity", "reason", @@ -982,8 +981,7 @@ "minLength": 1 }, "splitTarget": { - "type": "string", - "minLength": 1 + "type": "string" }, "manifestPath": { "type": "string", diff --git a/schemas/gamecult.eve.plugin.v1.schema.json b/schemas/gamecult.eve.plugin.v1.schema.json index f9584b0..65d2cb9 100644 --- a/schemas/gamecult.eve.plugin.v1.schema.json +++ b/schemas/gamecult.eve.plugin.v1.schema.json @@ -8,7 +8,6 @@ "pluginId", "title", "owner", - "incubation", "componentKinds", "commands", "runtime", diff --git a/scripts/run-plugin-handoff-smoke.ps1 b/scripts/run-plugin-handoff-smoke.ps1 index 93eb129..7639aa4 100644 --- a/scripts/run-plugin-handoff-smoke.ps1 +++ b/scripts/run-plugin-handoff-smoke.ps1 @@ -1,7 +1,6 @@ param( [string[]] $HandoffPaths = @( "plugins\incubating\sai-vn.plugin-handoff.json", - "plugins\incubating\norn-graph.plugin-handoff.json", "plugins\incubating\tex-math.plugin-handoff.json" ) ) diff --git a/scripts/run-plugin-owner-conformance-consumer-smoke.ps1 b/scripts/run-plugin-owner-conformance-consumer-smoke.ps1 index 59339c3..ce9a328 100644 --- a/scripts/run-plugin-owner-conformance-consumer-smoke.ps1 +++ b/scripts/run-plugin-owner-conformance-consumer-smoke.ps1 @@ -45,12 +45,9 @@ node $consumerScript $consumerExport ` --expect-plugin norn.graph ` --expect-plugin tex.math ` --expect-plugin-handoff sai.vn ` - --expect-plugin-handoff norn.graph ` --expect-plugin-handoff tex.math ` --expect-plugin-handoff-move sai.vn:plugin-manifest:current:exists:sai-vn.plugin.json ` --expect-plugin-handoff-move sai.vn:abi-conformance:current:exists:sai-vn.plugin-abi-fixture.json ` - --expect-plugin-handoff-move norn.graph:plugin-manifest:current:exists:norn-graph.plugin.json ` - --expect-plugin-handoff-move norn.graph:abi-conformance:current:exists:norn-graph.plugin-abi-fixture.json ` --expect-plugin-handoff-move tex.math:plugin-manifest:current:exists:tex-math.plugin.json ` --expect-plugin-handoff-move tex.math:abi-conformance:current:exists:tex-math.plugin-abi-fixture.json ` --expect-plugin-operation sai.vn:describe ` @@ -66,12 +63,8 @@ node $consumerScript $consumerExport ` --expect-plugin-operation norn.graph:describe ` --expect-plugin-operation norn.graph:validate ` --expect-plugin-operation norn.graph:project ` - --expect-plugin-operation norn.graph:lower ` --expect-plugin-operation norn.graph:measure ` - --expect-plugin-operation norn.graph:apply ` --expect-plugin-abi-operation-coverage norn.graph:describe:contracted:Norn ` - --expect-plugin-abi-operation-coverage norn.graph:lower:contracted:Norn ` - --expect-plugin-abi-operation-coverage norn.graph:apply:contracted:Norn ` --expect-provider-plugin-requirement gamecult.home.vn:sai.visual_novel.surface:norn.graph:optional-satisfied:Norn:optional-nested ` --expect-provider-plugin-requirement gamecult.home.vn:sai.visual_novel.surface:tex.math:optional-satisfied:EvePlugins:optional-nested ` --expect-independent-nested-plugin gamecult.home.vn:sai.visual_novel.surface:sai.vn:norn.graph:Norn ` @@ -96,15 +89,13 @@ node $consumerScript $consumerExport ` --expect-plugin-abi-field sai.vn:lower:expect.commandEnvelope:gamecult.eve.command.v1 ` --expect-plugin-abi-field sai.vn:apply:expect.receiptSchema:gamecult.eve.command_receipt.v1 ` --expect-plugin-runtime norn.graph:executable-sidecar ` - --expect-plugin-runtime-transport norn.graph:cultmesh ` + --expect-plugin-runtime-transport norn.graph:stdio ` --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.protocol:stdio-ndjson ` --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 ` - --expect-plugin-abi-field norn.graph:lower:expect.commandEnvelope:gamecult.eve.command.v1 ` - --expect-plugin-abi-field norn.graph:apply:expect.receiptSchema:gamecult.eve.command_receipt.v1 ` --expect-plugin-operation tex.math:describe ` --expect-plugin-operation tex.math:validate ` --expect-plugin-operation tex.math:project ` diff --git a/tools/conformance/consume-export.mjs b/tools/conformance/consume-export.mjs index 7b2e81c..92f7a0e 100644 --- a/tools/conformance/consume-export.mjs +++ b/tools/conformance/consume-export.mjs @@ -1129,9 +1129,10 @@ function validateWorldSurfaceLoweringGaps(gaps, errors) { function validatePluginRecords(plugins, errors) { for (const plugin of plugins) { const label = `plugins:${plugin.pluginId || "unknown"}`; - for (const field of ["pluginId", "status", "ownerRepo", "splitTarget", "manifestPath", "advertisementPath", "abiFixturePath"]) { + for (const field of ["pluginId", "status", "ownerRepo", "manifestPath", "advertisementPath", "abiFixturePath"]) { if (!plugin?.[field]) errors.push(`${label}:${field}:missing`); } + if (plugin.status !== "active" && !plugin.splitTarget) errors.push(`${label}:splitTarget:missing`); for (const field of ["capabilities", "abiOperations", "optionalPlugins"]) { if (!Array.isArray(plugin?.[field])) errors.push(`${label}:${field}:expected array`); } diff --git a/tools/parity/parity-manifest.json b/tools/parity/parity-manifest.json index d718440..e86523d 100644 --- a/tools/parity/parity-manifest.json +++ b/tools/parity/parity-manifest.json @@ -469,20 +469,18 @@ { "pluginId": "norn.graph", "title": "Norn Graph", - "kind": "external-owner-planned", + "kind": "external-owner", "ownerRepo": "Norn", - "repoRole": "plugin-incubating", - "splitTarget": "Norn/eve-plugin-graph", - "graduationTrigger": "Split when embed.norn graph layout, interaction, fallback, and conformance fixtures are stable.", + "repoRole": "plugin-owner", "capabilities": ["embed.norn", "graph.node.activate"], "expectedRuntime": { "invocationModel": "executable-sidecar", "contract": "gamecult.eve.plugin_abi.v1", - "transports": ["cultmesh", "stdio"], + "transports": ["stdio"], "authority": ["renderer-independent", "no-provider-state-mutation", "provider-accepts-or-denies-commands"], "sidecar": { "processKind": "long-running-daemon", - "protocol": "cultmesh-rpc-with-stdio-dev-transport", + "protocol": "stdio-ndjson", "requestSchema": "gamecult.eve.plugin_abi.request.v1", "responseSchema": "gamecult.eve.plugin_abi.response.v1", "commandEnvelope": "gamecult.eve.command.v1", @@ -492,17 +490,15 @@ }, "requiredFixtures": [], "schemaPath": "schemas/gamecult.eve.plugin.v1.schema.json", - "manifestPath": "plugins/incubating/norn-graph.plugin.json", - "abiFixturePath": "plugins/incubating/norn-graph.plugin-abi-fixture.json", - "handoffPath": "plugins/incubating/norn-graph.plugin-handoff.json", + "manifestPath": "../Norn/plugins/norn-graph.plugin.json", + "abiFixturePath": "../Norn/plugins/norn-graph.plugin-abi-fixture.json", "advertisementSchemaPath": "schemas/gamecult.eve.plugin_advertisement.v1.schema.json", - "advertisementPath": "plugins/incubating/norn-graph.plugin-advertisement.json", + "advertisementPath": "../Norn/plugins/norn-graph.plugin-advertisement.json", "expectedPaths": [ - "plugins/incubating/norn-graph.plugin.json", - "plugins/incubating/norn-graph.plugin-abi-fixture.json", - "plugins/incubating/norn-graph.plugin-advertisement.json", - "plugins/incubating/norn-graph.plugin-handoff.json", - "scripts/run-plugin-handoff-smoke.ps1" + "../Norn/plugins/norn-graph.plugin.json", + "../Norn/plugins/norn-graph.plugin-abi-fixture.json", + "../Norn/plugins/norn-graph.plugin-advertisement.json", + "../Norn/crates/norn-eve-plugin/src/main.rs" ] }, { diff --git a/tools/parity/run-parity.mjs b/tools/parity/run-parity.mjs index 9a35646..c251183 100644 --- a/tools/parity/run-parity.mjs +++ b/tools/parity/run-parity.mjs @@ -621,7 +621,9 @@ async function evaluatePlugin(plugin, fixtureResults) { ? "invalid-plugin-handoff" : plugin.kind === "incubating" ? "incubating" - : "external-owner-planned"; + : plugin.kind === "external-owner" + ? "active" + : "external-owner-planned"; return { pluginId: plugin.pluginId, @@ -978,7 +980,7 @@ function validateProviderPluginRequirements(provider, requirements, pluginResult if (!optional) errors.push(`${provider.advertisementPath}:${requirement.surfaceId}:plugin:${requirement.pluginId}:missing`); continue; } - if (plugin.status !== "incubating" && plugin.status !== "external-owner-planned") { + if (!["active", "incubating", "external-owner-planned"].includes(plugin.status)) { errors.push(`${provider.advertisementPath}:${requirement.surfaceId}:plugin:${requirement.pluginId}:status:${plugin.status}`); continue; } @@ -1141,7 +1143,8 @@ async function validatePluginAbiFixture(plugin) { } } - for (const operation of ["describe", "validate", "project", "lower", "measure", "apply"]) { + const claimedOperations = pluginManifest.runtime?.sidecar?.operations || []; + for (const operation of claimedOperations) { if (!operations.has(operation)) errors.push(`${plugin.abiFixturePath}:operation:${operation}:missing`); } errors.push(...missingMembers( @@ -1150,7 +1153,8 @@ async function validatePluginAbiFixture(plugin) { `${plugin.manifestPath}:runtime.sidecar.operations`, )); - if (!(pluginManifest.abiFixtures || []).includes(plugin.abiFixturePath)) { + const fixtureBasename = path.basename(plugin.abiFixturePath); + if (!(pluginManifest.abiFixtures || []).some(candidate => path.basename(candidate) === fixtureBasename)) { errors.push(`${plugin.manifestPath}:abiFixtures:${plugin.abiFixturePath}:missing`); } @@ -1173,34 +1177,40 @@ async function validatePluginAbiFixture(plugin) { errors.push(...missingMembers(manifestComponentKinds, project.ownedComponentKinds || [], `${plugin.abiFixturePath}:project.ownedComponentKinds`)); if (!project.projectionKind) errors.push(`${plugin.abiFixturePath}:project.projectionKind:missing`); - const lower = operations.get("lower")?.expect || {}; - errors.push(...missingMembers(manifestComponentKinds, lower.preservedComponentKinds || [], `${plugin.abiFixturePath}:lower.preservedComponentKinds`)); - if (!lower.loweringKind) errors.push(`${plugin.abiFixturePath}:lower.loweringKind:missing`); - - const measure = operations.get("measure")?.expect || {}; - if (!measure.measurementKind) errors.push(`${plugin.abiFixturePath}:measure.measurementKind:missing`); - if (!(measure.measurementOutputs || []).length) errors.push(`${plugin.abiFixturePath}:measure.measurementOutputs:missing`); - if (measure.preservesProviderAuthority !== true) errors.push(`${plugin.abiFixturePath}:measure.preservesProviderAuthority:expected true`); - - const apply = operations.get("apply")?.expect || {}; - if (apply.receiptSchema !== sidecarReceiptSchema) { - errors.push(`${plugin.abiFixturePath}:apply.receiptSchema:expected ${sidecarReceiptSchema} got ${apply.receiptSchema || ""}`); + if (claimedOperations.includes("lower")) { + const lower = operations.get("lower")?.expect || {}; + errors.push(...missingMembers(manifestComponentKinds, lower.preservedComponentKinds || [], `${plugin.abiFixturePath}:lower.preservedComponentKinds`)); + if (!lower.loweringKind) errors.push(`${plugin.abiFixturePath}:lower.loweringKind:missing`); } - if (manifestCommands.length) { - const commandEffects = apply.commandEffects || {}; - for (const command of manifestCommands) { - if (!commandEffects[command]) { - errors.push(`${plugin.abiFixturePath}:apply.commandEffects:${command}:missing`); - continue; - } - const expectedEffect = manifestCommandEffects.get(command); - if (commandEffects[command] !== expectedEffect) { - errors.push(`${plugin.abiFixturePath}:apply.commandEffects:${command}:expected ${expectedEffect} got ${commandEffects[command]}`); - } + if (claimedOperations.includes("measure")) { + const measure = operations.get("measure")?.expect || {}; + if (!measure.measurementKind) errors.push(`${plugin.abiFixturePath}:measure.measurementKind:missing`); + if (!(measure.measurementOutputs || []).length) errors.push(`${plugin.abiFixturePath}:measure.measurementOutputs:missing`); + if (measure.preservesProviderAuthority !== true) errors.push(`${plugin.abiFixturePath}:measure.preservesProviderAuthority:expected true`); + } + + if (claimedOperations.includes("apply")) { + const apply = operations.get("apply")?.expect || {}; + if (apply.receiptSchema !== sidecarReceiptSchema) { + errors.push(`${plugin.abiFixturePath}:apply.receiptSchema:expected ${sidecarReceiptSchema} got ${apply.receiptSchema || ""}`); + } + if (manifestCommands.length) { + const commandEffects = apply.commandEffects || {}; + for (const command of manifestCommands) { + if (!commandEffects[command]) { + errors.push(`${plugin.abiFixturePath}:apply.commandEffects:${command}:missing`); + continue; + } + + const expectedEffect = manifestCommandEffects.get(command); + if (commandEffects[command] !== expectedEffect) { + errors.push(`${plugin.abiFixturePath}:apply.commandEffects:${command}:expected ${expectedEffect} got ${commandEffects[command]}`); + } + } + } else if (!(apply.stateEffects || []).length) { + errors.push(`${plugin.abiFixturePath}:apply.stateEffects:missing`); } - } else if (!(apply.stateEffects || []).length) { - errors.push(`${plugin.abiFixturePath}:apply.stateEffects:missing`); } } catch (error) { errors.push(`${plugin.abiFixturePath}:invalid-json:${error instanceof Error ? error.message : String(error)}`); @@ -1862,6 +1872,7 @@ function evaluateSplitTargets(splitTargets, runtimeResults) { } function requiredIncubationFields(entry) { + if (entry.kind === "external-owner") return ["ownerRepo", "repoRole"]; if (entry.repoRole === "core") return ["ownerRepo", "repoRole"]; return ["ownerRepo", "repoRole", "graduationTrigger"]; } @@ -1993,8 +2004,8 @@ function summarize(fixtures, runtimes, plugins, providers, splitTargets) { passedFixtures: fixtures.filter(fixture => fixture.status === "pass").length, failedFixtures: fixtures.filter(fixture => fixture.status !== "pass").length, totalPlugins: plugins.length, - healthyPlugins: plugins.filter(plugin => plugin.status === "incubating" || plugin.status === "external-owner-planned").length, - failedPlugins: plugins.filter(plugin => plugin.status !== "incubating" && plugin.status !== "external-owner-planned").length, + healthyPlugins: plugins.filter(plugin => ["active", "incubating", "external-owner-planned"].includes(plugin.status)).length, + failedPlugins: plugins.filter(plugin => !["active", "incubating", "external-owner-planned"].includes(plugin.status)).length, totalProviders: providers.length, advertisedProviders: providers.filter(provider => provider.status === "advertised").length, failedProviders: providers.filter(provider => provider.status !== "advertised").length,