Graduate Norn plugin ownership from Eve

This commit is contained in:
Meta Vens 2026-07-10 02:53:51 +01:00
commit c02a807272
13 changed files with 77 additions and 324 deletions

View file

@ -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

View file

@ -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;

View file

@ -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"
}
}
]
}

View file

@ -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"
]
}

View file

@ -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."
]
}

View file

@ -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"
]
}
}

View file

@ -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",

View file

@ -8,7 +8,6 @@
"pluginId",
"title",
"owner",
"incubation",
"componentKinds",
"commands",
"runtime",

View file

@ -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"
)
)

View file

@ -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 `

View file

@ -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`);
}

View file

@ -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"
]
},
{

View file

@ -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,