From 88068c35ae0e1c664a0b99cf357b3649a607195d Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 9 Jul 2026 13:20:30 +0100 Subject: [PATCH] Add EveElectron plugin projection shells --- docs/eve-migration-roadmap.md | 41 +++++------ .../eve-electron/eve-runtime-capability.json | 28 ++++++-- .../eveelectron-split-handoff.json | 20 +++++- .../eve-electron/src/eve-electron-shell.mjs | 57 +++++++++++++++- .../test/eve-electron-shell.test.mjs | 68 ++++++++++++++++++- scripts/run-eveelectron-lifecycle-smoke.ps1 | 29 ++++++-- .../run-eveelectron-provider-shell-smoke.ps1 | 13 +++- .../run-eveelectron-split-handoff-smoke.ps1 | 26 ++++++- ...ntime-owner-conformance-consumer-smoke.ps1 | 11 +-- ...plit-target-conformance-consumer-smoke.ps1 | 1 + tools/parity/parity-manifest.json | 19 +++--- 11 files changed, 260 insertions(+), 53 deletions(-) diff --git a/docs/eve-migration-roadmap.md b/docs/eve-migration-roadmap.md index 7a89fae..2bb38fe 100644 --- a/docs/eve-migration-roadmap.md +++ b/docs/eve-migration-roadmap.md @@ -392,8 +392,9 @@ Recently cut: - Runtime/plugin projection claims are now exported as root `runtimePluginProjectionCoverage[]` records. Runtime-owner smokes assert Unity UI Toolkit support for `sai.vn`, `norn.graph`, and `tex.math`, Unity - scene support for the same three plugin capabilities, and the pending - Electron unsupported Sai projection until its generic lowerer exists. + scene support for the same three plugin capabilities, and Electron shell + support for the same sidecar plugin projection shells without importing + plugin semantics. - Provider/runtime/plugin projection coverage is now exported as `providerRuntimePluginProjectionCoverage[]`. The runtime-owner smoke proves the Sai VN provider surface lowers fully in web, lowers in Unity UI Toolkit @@ -402,10 +403,10 @@ Recently cut: impersonate required Sai VN, optional Norn graph, or optional TeX math projection. - Runtime/plugin projection gaps are now exported as - `runtimePluginProjectionGaps[]`. The runtime-owner smoke asserts Unity and - Electron plugin gaps as typed records, so missing Sai, Norn, or TeX projection - support is owned by the runtime split target and does not imply those plugin - semantics should move into Eve core or Aetheria product code. + `runtimePluginProjectionGaps[]`. The runtime-owner smoke asserts remaining + device-edge and TUI plugin gaps as typed records, so missing Sai, Norn, or TeX + projection support is owned by the runtime split target and does not imply + those plugin semantics should move into Eve core or Aetheria product code. - Split target blockers are now exported as `splitTargetBlockers[]` and as per-target `blockerRecords[]` with blocker kind, subject, owner, and source text. The split-target consumer smoke asserts Unity scene status/plugin @@ -468,11 +469,11 @@ Recently cut: - EveElectron now has a pending split target and handoff at `runtimes/incubating/eve-electron/eveelectron-split-handoff.json`. It does claim a provider-agnostic Electron shell surface-tree projection and command - surface, but not packaged rendering, embedded document rendering, plugin - projection, or captured window artifacts. The handoff draws the line between - the generic shell and Aetheria's Starbridge RTS product client, and the - split-target consumer smoke keeps release, packaged rendering, embedded - document rendering, and capture artifact proofs visible as blockers. + surface with Sai, Norn, and TeX sidecar plugin projection shells, but not + packaged rendering or captured window artifacts. The handoff draws the line + between the generic shell and Aetheria's Starbridge RTS product client, and + the split-target consumer smoke keeps release, packaged rendering, and + capture artifact proofs visible as blockers. - EveElectron now has a direct split handoff smoke at `scripts/run-eveelectron-split-handoff-smoke.ps1`. The smoke verifies the current generic shell, command, and world-surface lowering paths, the observed @@ -483,14 +484,15 @@ Recently cut: `runtimes/incubating/eve-electron/eve-runtime-capability.json` and a lifecycle smoke at `scripts/run-eveelectron-lifecycle-smoke.ps1`. The manifest declares provider advertisement consumption, command transport, and - surface-tree projection, claims `electron-shell` world/editor lowering, and - now carries executable release and capture request contracts. Release uses + surface-tree/plugin projection, claims `electron-shell` world/editor + lowering, and now carries executable release and capture request contracts. + Release uses `tools/eveelectron/eveelectron-release-contract.mjs` and `scripts/run-eveelectron-release-contract-smoke.ps1`; capture uses `tools/eveelectron/eveelectron-capture-contract.mjs` and `scripts/run-eveelectron-capture-contract-smoke.ps1`. The packaged Electron - app, packaged window rendering, plugin projection, and actual Electron window - capture artifacts remain pending EveElectron work. + app, packaged window rendering, and actual Electron window capture artifacts + remain pending EveElectron work. - Electron and TUI pending lifecycle details now live in their runtime capability manifests rather than being duplicated in `tools/parity/parity-manifest.json`. The parity ledger points at the @@ -864,10 +866,11 @@ Recently cut: - The parity harness now emits an `EveConformance`-shaped export under `artifacts/conformance/latest`, with a top-level index and per-pack JSON files for core, plugin, provider, and runtime consumers. -- Unity's plugin projection capability gap is now explicitly demoted. The UI - Toolkit runtime owns generic projection and command requests, while Sai, Norn, - and TeX semantics remain sidecar-plugin responsibilities. Unity only adds - projection adapters for capabilities a plugin advertises through Eve. +- Unity and Electron plugin projection capability gaps are now explicitly + demoted. UI Toolkit, Unity scene, and Electron runtimes own generic projection + and command requests, while Sai, Norn, and TeX semantics remain sidecar-plugin + responsibilities. Runtimes only add projection shells for capabilities a + plugin advertises through Eve. - Flutter and Unity now have runtime command-transport smoke evidence in the parity report. Flutter emits `gamecult.eve.command.v1` intents from lowered controls, and Unity command requests now carry the same command schema. diff --git a/runtimes/incubating/eve-electron/eve-runtime-capability.json b/runtimes/incubating/eve-electron/eve-runtime-capability.json index a577c00..d85cb4d 100644 --- a/runtimes/incubating/eve-electron/eve-runtime-capability.json +++ b/runtimes/incubating/eve-electron/eve-runtime-capability.json @@ -17,23 +17,38 @@ "providerAdvertisements", "commandTransport", "surfaceTreeProjection", - "embeddedDocuments" + "embeddedDocuments", + "pluginProjection" ], - "supportedPlugins": [], - "unsupportedPlugins": [ + "supportedPlugins": [ { "pluginId": "sai.vn", - "reason": "Electron provider-shell skeleton exists, but no adapter exists for Sai sidecar-advertised VN projection in this runtime." + "capabilities": [ + "vn.stage", + "story.choose", + "story.continue", + "story.jump" + ], + "projectionAdapter": "EveElectronShell.buildPluginProjection" }, { "pluginId": "norn.graph", - "reason": "Electron provider-shell skeleton exists, but no adapter exists for Norn sidecar-advertised graph projection in this runtime." + "capabilities": [ + "embed.norn" + ], + "projectionAdapter": "EveElectronShell.buildPluginProjection" }, { "pluginId": "tex.math", - "reason": "Electron provider-shell skeleton exists, but no adapter exists for TeX sidecar-advertised math projection in this runtime." + "capabilities": [ + "embed.tex", + "tex.inline", + "tex.block" + ], + "projectionAdapter": "EveElectronShell.buildPluginProjection" } ], + "unsupportedPlugins": [], "worldSurfaceLowering": [ { "targetId": "electron-shell", @@ -44,6 +59,7 @@ "evidencePaths": [ "runtimes/incubating/eve-electron/src/eve-electron-shell.mjs", "runtimes/incubating/eve-electron/test/eve-electron-shell.test.mjs", + "web/fixtures/sai-vn-surface.json", "web/fixtures/aetheria.provider-advertisement.json" ] } diff --git a/runtimes/incubating/eve-electron/eveelectron-split-handoff.json b/runtimes/incubating/eve-electron/eveelectron-split-handoff.json index eff8b6f..faf21c5 100644 --- a/runtimes/incubating/eve-electron/eveelectron-split-handoff.json +++ b/runtimes/incubating/eve-electron/eveelectron-split-handoff.json @@ -15,7 +15,8 @@ "currentPaths": [ "runtimes/incubating/eve-electron/package.json", "runtimes/incubating/eve-electron/src/eve-electron-shell.mjs", - "runtimes/incubating/eve-electron/test/eve-electron-shell.test.mjs" + "runtimes/incubating/eve-electron/test/eve-electron-shell.test.mjs", + "web/fixtures/sai-vn-surface.json" ], "observedProviderPaths": [ "E:\\Projects\\Aetheria\\Aetheria.Rts.Web\\Electron", @@ -54,6 +55,18 @@ "destinationOwner": "EveElectron", "replacementProof": "Electron claims provider-advertised world/editor surface targets with provider-agnostic surface-tree projection and command boundary evidence; packaged rendering and capture remain separate lifecycle proofs." }, + { + "id": "electron-plugin-projection", + "stage": "plugin-projection", + "currentPaths": [ + "runtimes/incubating/eve-electron/src/eve-electron-shell.mjs", + "runtimes/incubating/eve-electron/test/eve-electron-shell.test.mjs", + "runtimes/incubating/eve-electron/eve-runtime-capability.json", + "web/fixtures/sai-vn-surface.json" + ], + "destinationOwner": "EveElectron", + "replacementProof": "EveElectron owns shell projection metadata for Sai, Norn, and TeX sidecar-advertised components; Sai keeps story state, Norn keeps graph layout/state, and EvePlugins keeps TeX rendering semantics." + }, { "id": "electron-capture-lifecycle", "stage": "capture", @@ -78,12 +91,15 @@ "Aetheria RTS client modules as EveElectron runtime authority", "Aetheria Starbridge state as Electron shell truth", "provider-specific IPC contracts that bypass gamecult.eve.command.v1", - "plugin semantic internals", + "Sai story state, VN/Ink semantics, or story command interpretation as Electron shell authority", + "Norn graph layout, graph state, or command semantics as Electron shell authority", + "TeX parsing, typesetting, baseline metrics, or render-cache semantics as Electron shell authority", "renderer-local product state as daemon truth" ], "requiredExternalProofs": [ "EveElectron publishes a runtime capability manifest with provider advertisement consumption and command transport.", "EveElectron owns a provider-shell contract skeleton that selects advertised provider surfaces, projects Eve surface trees, and emits gamecult.eve.command.v1 intents.", + "EveElectron projects Sai, Norn, and TeX sidecar plugin shells without importing plugin semantic internals.", "EveElectron lowers at least one generic Eve provider surface that is not Aetheria.", "EveElectron lowers Aetheria world/editor surfaces through provider advertisements without importing Aetheria internals.", "Aetheria keeps Starbridge-specific UI, assets, authored flows, and receipts in the Aetheria repo.", diff --git a/runtimes/incubating/eve-electron/src/eve-electron-shell.mjs b/runtimes/incubating/eve-electron/src/eve-electron-shell.mjs index 1412763..ede8379 100644 --- a/runtimes/incubating/eve-electron/src/eve-electron-shell.mjs +++ b/runtimes/incubating/eve-electron/src/eve-electron-shell.mjs @@ -116,16 +116,19 @@ function buildShellNode(component) { const source = objectValue(component); const children = Array.isArray(source.children) ? source.children.map(buildShellNode) : []; const embeddedDocuments = normalizeEmbeddedDocuments(source.embeddedDocuments); + const componentKind = firstString(source.kind); + const pluginProjection = buildPluginProjection(componentKind, source); return { id: firstString(source.id), - componentKind: firstString(source.kind), - shellElementKind: shellElementKind(firstString(source.kind)), + componentKind, + shellElementKind: shellElementKind(componentKind), props: objectValue(source.props), layout: objectValue(source.layout), style: objectValue(source.style), stateBindingCount: Array.isArray(source.stateBindings) ? source.stateBindings.length : 0, embeddedDocumentCount: embeddedDocuments.length, embeddedDocuments, + ...(pluginProjection ? { pluginProjection } : {}), children, }; } @@ -146,6 +149,9 @@ function normalizeEmbeddedDocuments(value) { function shellElementKind(componentKind) { if (!componentKind) return "empty"; + if (componentKind === "vn.stage") return "sai-vn-stage-shell"; + if (componentKind === "embed.norn") return "norn-graph-shell"; + if (componentKind === "embed.tex") return "tex-math-shell"; if (componentKind.startsWith("control.")) return "command-control"; if (componentKind.startsWith("embed.")) return "plugin-placeholder"; if (componentKind === "surface.slot") return "embedded-surface-slot"; @@ -154,6 +160,53 @@ function shellElementKind(componentKind) { return "shell-node"; } +function buildPluginProjection(componentKind, component) { + if (componentKind === "vn.stage") { + return { + pluginId: "sai.vn", + projectionKind: "sai-vn-electron-stage-shell", + abiSchema: "gamecult.eve.plugin_abi.v1", + commandBoundary: "sidecar-advertised-plugin-abi", + capabilities: ["vn.stage", "story.choose", "story.continue", "story.jump"], + documentId: firstString(component.props?.storyId, component.id), + semanticOwner: "Sai", + }; + } + + if (componentKind === "embed.norn") { + return { + pluginId: "norn.graph", + projectionKind: "norn-graph-electron-overlay-shell", + abiSchema: "gamecult.eve.plugin_abi.v1", + commandBoundary: "sidecar-advertised-plugin-abi", + capabilities: ["embed.norn"], + documentId: firstString(component.props?.sourceUri, component.props?.documentId, component.id), + semanticOwner: "Norn", + }; + } + + if (componentKind === "embed.tex") { + return { + pluginId: "tex.math", + projectionKind: texProjectionKind(component), + abiSchema: "gamecult.eve.plugin_abi.v1", + commandBoundary: "sidecar-advertised-plugin-abi", + capabilities: ["embed.tex", "tex.inline", "tex.block"], + documentId: firstString(component.props?.sourceUri, component.props?.source, component.id), + semanticOwner: "EvePlugins", + }; + } + + return null; +} + +function texProjectionKind(component) { + const display = firstString(component.props?.display, "inline"); + if (display === "block") return "tex-math-electron-block-source-shell"; + if (display === "page") return "tex-math-electron-page-source-shell"; + return "tex-math-electron-inline-source-shell"; +} + function normalizeWorldInteraction(value) { const source = objectValue(value); return { diff --git a/runtimes/incubating/eve-electron/test/eve-electron-shell.test.mjs b/runtimes/incubating/eve-electron/test/eve-electron-shell.test.mjs index f1b47c1..06621c3 100644 --- a/runtimes/incubating/eve-electron/test/eve-electron-shell.test.mjs +++ b/runtimes/incubating/eve-electron/test/eve-electron-shell.test.mjs @@ -64,7 +64,9 @@ test("lowers provider surface trees into an Electron shell projection", () => { assert.equal(projection.root.children[0].stateBindingCount, 1); assert.equal(projection.root.children[1].shellElementKind, "command-control"); assert.equal(projection.root.children[1].props.command, "aetheria.daemon.commands"); - assert.equal(projection.root.children[2].shellElementKind, "plugin-placeholder"); + assert.equal(projection.root.children[2].shellElementKind, "norn-graph-shell"); + assert.equal(projection.root.children[2].pluginProjection.pluginId, "norn.graph"); + assert.equal(projection.root.children[2].pluginProjection.semanticOwner, "Norn"); assert.equal(projection.root.children[2].embeddedDocumentCount, 1); assert.deepEqual(projection.root.children[2].embeddedDocuments, [ { @@ -76,6 +78,41 @@ test("lowers provider surface trees into an Electron shell projection", () => { ]); }); +test("lowers Sai, Norn, and TeX sidecar plugin shells without owning semantics", () => { + const shell = new EveElectronShell(); + const saiSurface = JSON.parse(readFileSync(path.join(repoRoot, "web/fixtures/sai-vn-surface.json"), "utf8")); + const projection = shell.lowerSurface(saiSurface, saiAdvertisement(), "sai.visual_novel.surface"); + + assert.deepEqual(validateSchemaSubset(electronProjectionSchema, projection), []); + assert.equal(projection.providerId, "gamecult.home.vn"); + assert.equal(projection.surfaceId, "sai.visual_novel.surface"); + assert.equal(projection.root.shellElementKind, "sai-vn-stage-shell"); + assert.equal(projection.root.pluginProjection.pluginId, "sai.vn"); + assert.equal(projection.root.pluginProjection.semanticOwner, "Sai"); + assert.equal(projection.root.pluginProjection.commandBoundary, "sidecar-advertised-plugin-abi"); + assert.deepEqual(projection.root.pluginProjection.capabilities, [ + "vn.stage", + "story.choose", + "story.continue", + "story.jump", + ]); + + const norn = findNode(projection.root, "sai.graph"); + assert.equal(norn.shellElementKind, "norn-graph-shell"); + assert.equal(norn.pluginProjection.pluginId, "norn.graph"); + assert.equal(norn.pluginProjection.projectionKind, "norn-graph-electron-overlay-shell"); + assert.equal(norn.pluginProjection.semanticOwner, "Norn"); + assert.deepEqual(norn.pluginProjection.capabilities, ["embed.norn"]); + + const tex = findNode(projection.root, "sai.tex.log-power"); + assert.equal(tex.shellElementKind, "tex-math-shell"); + assert.equal(tex.pluginProjection.pluginId, "tex.math"); + assert.equal(tex.pluginProjection.projectionKind, "tex-math-electron-block-source-shell"); + assert.equal(tex.pluginProjection.semanticOwner, "EvePlugins"); + assert.equal(tex.pluginProjection.documentId, "\\\\mathrm{votes}(p)=1+\\\\lfloor\\\\log_b(1+p)\\\\rfloor"); + assert.deepEqual(tex.pluginProjection.capabilities, ["embed.tex", "tex.inline", "tex.block"]); +}); + test("rejects surface documents that do not match the advertised target", () => { const shell = new EveElectronShell(); assert.throws( @@ -115,6 +152,26 @@ function advertisement() { }; } +function saiAdvertisement() { + return { + schema: "gamecult.eve.provider_advertisement.v1", + providerId: "gamecult.home.vn", + surfaces: [ + { + surfaceId: "sai.visual_novel.surface", + transport: "local-json", + url: "web/fixtures/sai-vn-surface.json", + worldInteraction: { + projectionKind: "provider-authored-world-surface", + commandBoundary: "sai.vn.plugin.commands", + receiptSchema: "gamecult.eve.command_receipt.v1", + ownership: "provider-owns-story-state-plugin-sidecars-own-nested-semantics", + }, + }, + ], + }; +} + function surfaceDocument(surfaceId = "aetheria.daemon.game") { return { type: "surface-state", @@ -175,3 +232,12 @@ function surfaceDocument(surfaceId = "aetheria.daemon.game") { commands: [], }; } + +function findNode(root, id) { + if (root.id === id) return root; + for (const child of root.children || []) { + const found = findNode(child, id); + if (found) return found; + } + return null; +} diff --git a/scripts/run-eveelectron-lifecycle-smoke.ps1 b/scripts/run-eveelectron-lifecycle-smoke.ps1 index 08b59ca..b4d52de 100644 --- a/scripts/run-eveelectron-lifecycle-smoke.ps1 +++ b/scripts/run-eveelectron-lifecycle-smoke.ps1 @@ -39,13 +39,30 @@ if (-not $manifest.incubation.splitHandoff.manifestPath) { throw "EveElectron manifest missing split handoff path" } -foreach ($feature in @("providerAdvertisements", "commandTransport", "surfaceTreeProjection", "embeddedDocuments")) { +foreach ($feature in @("providerAdvertisements", "commandTransport", "surfaceTreeProjection", "embeddedDocuments", "pluginProjection")) { if (-not (@($manifest.supportedFeatures) -contains $feature)) { throw "EveElectron manifest missing provider-shell feature: $feature" } } -if (@($manifest.supportedPlugins).Count -ne 0) { - throw "EveElectron must not claim plugin projection before runtime projection adapters for sidecar-advertised capabilities exist" + +$expectedPlugins = @{ + "sai.vn" = @("vn.stage", "story.choose", "story.continue", "story.jump") + "norn.graph" = @("embed.norn") + "tex.math" = @("embed.tex", "tex.inline", "tex.block") +} +foreach ($pluginId in $expectedPlugins.Keys) { + $supported = @($manifest.supportedPlugins) | Where-Object { $_.pluginId -eq $pluginId } | Select-Object -First 1 + if (-not $supported) { + throw "EveElectron manifest missing supported plugin declaration: $pluginId" + } + foreach ($capability in $expectedPlugins[$pluginId]) { + if (-not (@($supported.capabilities) -contains $capability)) { + throw "EveElectron supported plugin $pluginId missing capability: $capability" + } + } + if ($supported.projectionAdapter -ne "EveElectronShell.buildPluginProjection") { + throw "EveElectron supported plugin $pluginId has unexpected projection adapter: $($supported.projectionAdapter)" + } } $worldSurfaceLoweringClaims = @() @@ -72,10 +89,10 @@ foreach ($evidencePath in @($worldSurfaceLoweringClaim.evidencePaths)) { } } -foreach ($pluginId in @("sai.vn", "norn.graph", "tex.math")) { +foreach ($pluginId in $expectedPlugins.Keys) { $unsupported = @($manifest.unsupportedPlugins) | Where-Object { $_.pluginId -eq $pluginId } | Select-Object -First 1 - if (-not $unsupported) { - throw "EveElectron manifest missing unsupported plugin declaration: $pluginId" + if ($unsupported) { + throw "EveElectron manifest must not report $pluginId unsupported while the shell projection is declared" } } diff --git a/scripts/run-eveelectron-provider-shell-smoke.ps1 b/scripts/run-eveelectron-provider-shell-smoke.ps1 index fd9379b..591f839 100644 --- a/scripts/run-eveelectron-provider-shell-smoke.ps1 +++ b/scripts/run-eveelectron-provider-shell-smoke.ps1 @@ -24,12 +24,19 @@ if ($manifest.lifecycle.test.status -ne "provider-shell-contract-skeleton") { throw "Unexpected EveElectron provider shell status: $($manifest.lifecycle.test.status)" } -foreach ($feature in @("providerAdvertisements", "commandTransport", "surfaceTreeProjection")) { +foreach ($feature in @("providerAdvertisements", "commandTransport", "surfaceTreeProjection", "pluginProjection")) { if (-not (@($manifest.supportedFeatures) -contains $feature)) { throw "EveElectron provider shell missing supported feature: $feature" } } +foreach ($pluginId in @("sai.vn", "norn.graph", "tex.math")) { + $supported = @($manifest.supportedPlugins) | Where-Object { $_.pluginId -eq $pluginId } | Select-Object -First 1 + if (-not $supported) { + throw "EveElectron provider shell missing supported plugin projection: $pluginId" + } +} + $worldSurfaceLoweringClaims = @() if ($null -ne $manifest.worldSurfaceLowering) { $worldSurfaceLoweringClaims = @($manifest.worldSurfaceLowering) @@ -80,14 +87,14 @@ foreach ($relativePath in $expectedFiles) { } $shellSource = Get-Content -LiteralPath (Join-Path $projectRoot "runtimes\incubating\eve-electron\src\eve-electron-shell.mjs") -Raw -foreach ($symbol in @("EveElectronShell", "selectSurface", "lowerSurface", "createCommandIntent", "normalizeSurfaceDocument", "buildShellNode", "shellElementKind", "worldInteraction", "commandBoundary", "receiptSchema", "electron-shell")) { +foreach ($symbol in @("EveElectronShell", "selectSurface", "lowerSurface", "createCommandIntent", "normalizeSurfaceDocument", "buildShellNode", "buildPluginProjection", "shellElementKind", "sai.vn", "norn.graph", "tex.math", "sidecar-advertised-plugin-abi", "worldInteraction", "commandBoundary", "receiptSchema", "electron-shell")) { if (-not $shellSource.Contains($symbol)) { throw "EveElectron provider shell source missing symbol: $symbol" } } $testSource = Get-Content -LiteralPath (Join-Path $projectRoot "runtimes\incubating\eve-electron\test\eve-electron-shell.test.mjs") -Raw -foreach ($symbol in @("selects the active advertised provider surface", "lowers provider surface trees into an Electron shell projection", "rejects surface documents that do not match the advertised target", "command intents carry provider-advertised boundaries", "aetheria.daemon.commands", "aetheria.eve_command_acceptance_status.v1")) { +foreach ($symbol in @("selects the active advertised provider surface", "lowers provider surface trees into an Electron shell projection", "lowers Sai, Norn, and TeX sidecar plugin shells without owning semantics", "sai-vn-stage-shell", "norn-graph-shell", "tex-math-shell", "rejects surface documents that do not match the advertised target", "command intents carry provider-advertised boundaries", "aetheria.daemon.commands", "aetheria.eve_command_acceptance_status.v1")) { if (-not $testSource.Contains($symbol)) { throw "EveElectron provider shell test missing symbol: $symbol" } diff --git a/scripts/run-eveelectron-split-handoff-smoke.ps1 b/scripts/run-eveelectron-split-handoff-smoke.ps1 index 2b98714..001eb53 100644 --- a/scripts/run-eveelectron-split-handoff-smoke.ps1 +++ b/scripts/run-eveelectron-split-handoff-smoke.ps1 @@ -34,7 +34,7 @@ if ($handoff.runtimeId -ne "electron-shell") { } $moveSets = @($handoff.moveSets) -foreach ($stage in @("runtime-body", "command", "world-surface-lowering", "capture")) { +foreach ($stage in @("runtime-body", "command", "world-surface-lowering", "plugin-projection", "capture")) { $moveSet = $moveSets | Where-Object { $_.stage -eq $stage } | Select-Object -First 1 if (-not $moveSet) { throw "EveElectron split handoff missing move set for stage: $stage" @@ -77,6 +77,20 @@ if (@($captureMoveSet.currentPaths).Count -eq 0) { throw "EveElectron capture lifecycle move set must declare currentPaths" } +$pluginMoveSet = $moveSets | Where-Object { $_.id -eq "electron-plugin-projection" } | Select-Object -First 1 +if (-not $pluginMoveSet) { + throw "EveElectron split handoff missing move set: electron-plugin-projection" +} +foreach ($relativePath in @( + "runtimes/incubating/eve-electron/src/eve-electron-shell.mjs", + "runtimes/incubating/eve-electron/test/eve-electron-shell.test.mjs", + "web/fixtures/sai-vn-surface.json" +)) { + if (-not (@($pluginMoveSet.currentPaths) -contains $relativePath)) { + throw "EveElectron plugin projection move set missing current path: $relativePath" + } +} + foreach ($contract in @( "gamecult.eve.surface.v1", "gamecult.eve.command.v1", @@ -96,4 +110,14 @@ foreach ($proof in @($handoff.requiredExternalProofs)) { } } +foreach ($forbiddenImport in @( + "Sai story state, VN/Ink semantics, or story command interpretation as Electron shell authority", + "Norn graph layout, graph state, or command semantics as Electron shell authority", + "TeX parsing, typesetting, baseline metrics, or render-cache semantics as Electron shell authority" +)) { + if (-not (@($handoff.forbiddenImports) -contains $forbiddenImport)) { + throw "EveElectron split handoff missing forbidden import: $forbiddenImport" + } +} + Write-Host "EveElectron split handoff smoke passed: $absoluteHandoffPath" diff --git a/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 b/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 index cf2901b..2e88df9 100644 --- a/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 +++ b/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 @@ -67,6 +67,7 @@ node $consumerScript $consumerExport ` --expect-runtime-feature electron-shell:commandTransport ` --expect-runtime-feature electron-shell:surfaceTreeProjection ` --expect-runtime-feature electron-shell:embeddedDocuments ` + --expect-runtime-feature electron-shell:pluginProjection ` --expect-runtime-feature unity-scene:embeddedDocuments ` --expect-runtime-feature tui:providerAdvertisements ` --expect-runtime-feature tui:commandTransport ` @@ -95,7 +96,9 @@ node $consumerScript $consumerExport ` --expect-runtime-plugin-projection unity-scene:sai.vn:supported:EveUnity ` --expect-runtime-plugin-projection unity-scene:norn.graph:supported:EveUnity ` --expect-runtime-plugin-projection unity-scene:tex.math:supported:EveUnity ` - --expect-runtime-plugin-projection electron-shell:sai.vn:unsupported:EveElectron ` + --expect-runtime-plugin-projection electron-shell:sai.vn:supported:EveElectron ` + --expect-runtime-plugin-projection electron-shell:norn.graph:supported:EveElectron ` + --expect-runtime-plugin-projection electron-shell:tex.math:supported:EveElectron ` --expect-provider-runtime-plugin-projection gamecult.home.vn:sai.visual_novel.surface:web:sai.vn:supported:Eve ` --expect-provider-runtime-plugin-projection gamecult.home.vn:sai.visual_novel.surface:web:tex.math:optional-supported:Eve ` --expect-provider-runtime-plugin-projection gamecult.home.vn:sai.visual_novel.surface:unity-uitoolkit:sai.vn:supported:EveUnity ` @@ -104,11 +107,11 @@ node $consumerScript $consumerExport ` --expect-provider-runtime-plugin-projection gamecult.home.vn:sai.visual_novel.surface:unity-scene:sai.vn:supported:EveUnity ` --expect-provider-runtime-plugin-projection gamecult.home.vn:sai.visual_novel.surface:unity-scene:norn.graph:optional-supported:EveUnity ` --expect-provider-runtime-plugin-projection gamecult.home.vn:sai.visual_novel.surface:unity-scene:tex.math:optional-supported:EveUnity ` + --expect-provider-runtime-plugin-projection gamecult.home.vn:sai.visual_novel.surface:electron-shell:sai.vn:supported:EveElectron ` + --expect-provider-runtime-plugin-projection gamecult.home.vn:sai.visual_novel.surface:electron-shell:norn.graph:optional-supported:EveElectron ` + --expect-provider-runtime-plugin-projection gamecult.home.vn:sai.visual_novel.surface:electron-shell:tex.math:optional-supported:EveElectron ` --expect-provider-runtime-plugin-projection gamecult.home.vn:sai.visual_novel.surface:ios-uikit:sai.vn:unsupported-runtime-projection:EveIOS ` --expect-provider-runtime-plugin-projection gamecult.home.vn:sai.visual_novel.surface:ios-uikit:norn.graph:optional-unsupported-runtime-projection:EveIOS ` - --expect-runtime-plugin-gap electron-shell:sai.vn:EveElectron ` - --expect-runtime-plugin-gap electron-shell:norn.graph:EveElectron ` - --expect-runtime-plugin-gap electron-shell:tex.math:EveElectron ` --expect-runtime-plugin-gap tui:sai.vn:EveTui ` --expect-runtime-plugin-gap tui:norn.graph:EveTui ` --expect-runtime-plugin-gap tui:tex.math:EveTui ` diff --git a/scripts/run-split-target-conformance-consumer-smoke.ps1 b/scripts/run-split-target-conformance-consumer-smoke.ps1 index 151a88c..6ae36e8 100644 --- a/scripts/run-split-target-conformance-consumer-smoke.ps1 +++ b/scripts/run-split-target-conformance-consumer-smoke.ps1 @@ -66,6 +66,7 @@ try { --expect-split-handoff-move EveElectron:electron-shell:electron-shell-runtime:current:exists:eve-electron-shell.mjs ` --expect-split-handoff-move EveElectron:electron-shell:electron-command-transport:current:exists:eve-electron-shell.mjs ` --expect-split-handoff-move EveElectron:electron-shell:electron-world-surface-lowering:current:exists:eve-electron-shell.mjs ` + --expect-split-handoff-move EveElectron:electron-shell:electron-plugin-projection:current:exists:sai-vn-surface.json ` --expect-split-handoff-move EveElectron:electron-shell:electron-capture-lifecycle:current:exists:eveelectron-capture-contract.mjs ` --expect-split-handoff-move EveElectron:electron-shell:electron-shell-runtime:observed-provider:exists:Aetheria.Rts.Web/Electron ` --expect-split-handoff-move EveElectron:electron-shell:electron-command-transport:observed-provider:exists:Aetheria.Rts.Web/Client/app.ts ` diff --git a/tools/parity/parity-manifest.json b/tools/parity/parity-manifest.json index 05b59d4..4bcb5f3 100644 --- a/tools/parity/parity-manifest.json +++ b/tools/parity/parity-manifest.json @@ -738,23 +738,23 @@ }, "splitHandoffPath": "runtimes/incubating/eve-electron/eveelectron-split-handoff.json", "requiredFixtures": ["embedded-surface", "aetheria-world"], - "pluginFixtures": [], - "supportedFeatures": ["providerAdvertisements", "commandTransport", "surfaceTreeProjection", "embeddedDocuments"], - "supportedPlugins": [], - "unsupportedPlugins": [ + "pluginFixtures": ["sai-vn"], + "supportedFeatures": ["providerAdvertisements", "commandTransport", "surfaceTreeProjection", "embeddedDocuments", "pluginProjection"], + "supportedPlugins": [ { "pluginId": "sai.vn", - "reason": "Electron provider-shell skeleton exists, but no adapter exists for Sai sidecar-advertised VN projection in this runtime." + "capabilities": ["vn.stage", "story.choose", "story.continue", "story.jump"] }, { "pluginId": "norn.graph", - "reason": "Electron provider-shell skeleton exists, but no adapter exists for Norn sidecar-advertised graph projection in this runtime." + "capabilities": ["embed.norn"] }, { "pluginId": "tex.math", - "reason": "Electron provider-shell skeleton exists, but no adapter exists for TeX sidecar-advertised math projection in this runtime." + "capabilities": ["embed.tex", "tex.inline", "tex.block"] } ], + "unsupportedPlugins": [], "worldSurfaceLowering": [ { "targetId": "electron-shell", @@ -765,6 +765,7 @@ "evidencePaths": [ "runtimes/incubating/eve-electron/src/eve-electron-shell.mjs", "runtimes/incubating/eve-electron/test/eve-electron-shell.test.mjs", + "web/fixtures/sai-vn-surface.json", "web/fixtures/aetheria.provider-advertisement.json" ] } @@ -787,11 +788,11 @@ "expectedSourceSymbols": [ { "path": "runtimes/incubating/eve-electron/src/eve-electron-shell.mjs", - "contains": ["EveElectronShell", "selectSurface", "lowerSurface", "createCommandIntent", "normalizeSurfaceDocument", "buildShellNode", "normalizeEmbeddedDocuments", "embeddedDocuments", "shellElementKind", "worldInteraction", "commandBoundary", "receiptSchema", "electron-shell"] + "contains": ["EveElectronShell", "selectSurface", "lowerSurface", "createCommandIntent", "normalizeSurfaceDocument", "buildShellNode", "buildPluginProjection", "normalizeEmbeddedDocuments", "embeddedDocuments", "shellElementKind", "sai.vn", "norn.graph", "tex.math", "sidecar-advertised-plugin-abi", "worldInteraction", "commandBoundary", "receiptSchema", "electron-shell"] }, { "path": "runtimes/incubating/eve-electron/test/eve-electron-shell.test.mjs", - "contains": ["selects the active advertised provider surface", "lowers provider surface trees into an Electron shell projection", "embeddedDocuments", "norn.map", "rejects surface documents that do not match the advertised target", "command intents carry provider-advertised boundaries", "aetheria.daemon.commands", "aetheria.eve_command_acceptance_status.v1"] + "contains": ["selects the active advertised provider surface", "lowers provider surface trees into an Electron shell projection", "lowers Sai, Norn, and TeX sidecar plugin shells without owning semantics", "embeddedDocuments", "norn.map", "sai-vn-stage-shell", "norn-graph-shell", "tex-math-shell", "rejects surface documents that do not match the advertised target", "command intents carry provider-advertised boundaries", "aetheria.daemon.commands", "aetheria.eve_command_acceptance_status.v1"] }, { "path": "tools/eveelectron/eveelectron-capture-contract.mjs",