diff --git a/docs/eve-migration-roadmap.md b/docs/eve-migration-roadmap.md index 4249917..7953818 100644 --- a/docs/eve-migration-roadmap.md +++ b/docs/eve-migration-roadmap.md @@ -710,6 +710,10 @@ Recently cut: Aetheria consumer smoke asserts editor ownership and the pending TUI lowering gap from the exported contract instead of letting editor support hide behind the game-surface scenario. +- The surface contract now states nested Norn and TeX placement as optional + composition, not Sai custody. The plugin-owner consumer can assert + provider-plugin requirement availability, so `sai.vn` remains required while + `norn.graph` and `tex.math` stay optional nested sidecar plugins. - Sai, Norn, and TeX now have plugin ABI fixtures for describe, validate, project, lower, measure, and apply behavior. The parity harness validates those fixtures against plugin manifests, so Eve proves the ABI shape without diff --git a/docs/parity-testing-harness.md b/docs/parity-testing-harness.md index 372e5aa..d59dd03 100644 --- a/docs/parity-testing-harness.md +++ b/docs/parity-testing-harness.md @@ -188,7 +188,9 @@ plugin owners can consume requirement coverage without reconstructing the provider/plugin join. `availability: optional-nested` means the parent surface can host that independent plugin when available; it is not a parent-plugin dependency. Consumer smokes can assert them with -`--expect-provider-plugin-requirement `. +`--expect-provider-plugin-requirement `. +Plugin-owner smokes should use the optional availability field when proving that +Sai's required VN plugin is distinct from optional nested Norn or TeX surfaces. The runtime conformance pack is evidence-shaped, not fixture-shaped. Its `packs/runtime.json` document includes `runtimeTargets` so runtime repos can diff --git a/docs/surface-contract-v1.md b/docs/surface-contract-v1.md index 8e3bef9..1e9f30a 100644 --- a/docs/surface-contract-v1.md +++ b/docs/surface-contract-v1.md @@ -146,10 +146,15 @@ placeholder; it must not invent a local substitute for the child document. ## Embedded Knowledge Surfaces -Eve must be able to place rich interactive surfaces inside another surface. For -Sai, this means the VN scene can host Norn and TeX as diegetic scene elements: -whiteboards, handheld tablets, hologram panes, cockpit screens, chalkboards, or -chromakey regions baked into sprite assets. +Eve must be able to place rich interactive surfaces inside another surface. +Nested placement is composition, not custody. A parent surface may reserve scene +space for another plugin's surface when that plugin is available, but the +nested plugin keeps its own ABI, capability claims, conformance pack, and +semantic owner. For Sai, this means a VN scene can deploy with Norn and TeX as +optional diegetic scene elements: whiteboards, handheld tablets, hologram +panes, cockpit screens, chalkboards, or chromakey regions baked into sprite +assets. Sai owns the VN stage. Norn owns graph semantics. TeX owns math and +typesetting semantics. ### `embed.norn` @@ -352,12 +357,16 @@ Sai requires Eve/CultUI to carry: - speaker, avatar, dialogue, and choices; - sprite layers with position, scale, and offsets; - external provider-owned cards/fragments; -- embedded Norn graph/map surfaces with clickable node targets; -- embedded TeX surfaces for equations, whitepapers, ledgers, and proofs; +- optional embedded Norn graph/map surfaces with clickable node targets when + `norn.graph` is advertised and available; +- optional embedded TeX surfaces for equations, whitepapers, ledgers, and proofs + when `tex.math` is advertised and available; - diegetic placement with perspective, skew, keystone, chromakey, and occlusion metadata; - synchronized style tokens; - story commands for continue, choice, jump, and style patch. -Those requirements are now inside the contract, not Sai-specific browser -behavior. +Those requirements are now inside the Eve surface and plugin-advertisement +contracts, not Sai-specific browser behavior. Optional nested Norn or TeX gaps +degrade the nested surface; they do not move graph, math, or renderer semantics +into Sai. diff --git a/scripts/run-plugin-owner-conformance-consumer-smoke.ps1 b/scripts/run-plugin-owner-conformance-consumer-smoke.ps1 index 63f2d75..b1aee15 100644 --- a/scripts/run-plugin-owner-conformance-consumer-smoke.ps1 +++ b/scripts/run-plugin-owner-conformance-consumer-smoke.ps1 @@ -48,7 +48,7 @@ node $consumerScript $consumerExport ` --expect-plugin-abi-operation-coverage sai.vn:describe:contracted:Sai ` --expect-plugin-abi-operation-coverage sai.vn:lower:contracted:Sai ` --expect-plugin-abi-operation-coverage sai.vn:apply:contracted:Sai ` - --expect-provider-plugin-requirement gamecult.home.vn:sai.visual_novel.surface:sai.vn:satisfied:Sai ` + --expect-provider-plugin-requirement gamecult.home.vn:sai.visual_novel.surface:sai.vn:satisfied:Sai:required ` --expect-plugin-operation norn.graph:describe ` --expect-plugin-operation norn.graph:validate ` --expect-plugin-operation norn.graph:project ` @@ -58,8 +58,8 @@ node $consumerScript $consumerExport ` --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 ` - --expect-provider-plugin-requirement gamecult.home.vn:sai.visual_novel.surface:tex.math:optional-satisfied:EvePlugins ` + --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-plugin-capability sai.vn:vn.stage ` --expect-plugin-capability sai.vn:story.choose ` --expect-plugin-capability sai.vn:story.continue ` diff --git a/tools/conformance/consume-export.mjs b/tools/conformance/consume-export.mjs index 3bbaa9e..be8af88 100644 --- a/tools/conformance/consume-export.mjs +++ b/tools/conformance/consume-export.mjs @@ -38,7 +38,7 @@ if (!exportDirectory) { " --expect-provider-surface-kind ", " --expect-provider-surface-field ", " --expect-interactive-world-surface ", - " --expect-provider-plugin-requirement ", + " --expect-provider-plugin-requirement ", " --expect-provider-command ", " --expect-provider-receipt-state ", " --expect-provider-handoff ", @@ -464,6 +464,9 @@ function validateIndex(index, directory, expectations, errors) { if (requirement.pluginOwnerRepo !== expectation.pluginOwnerRepo) { errors.push(`providerPluginRequirementCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.pluginId}:pluginOwnerRepo:expected ${expectation.pluginOwnerRepo} got ${requirement.pluginOwnerRepo || ""}`); } + if (expectation.availability && requirement.availability !== expectation.availability) { + errors.push(`providerPluginRequirementCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.pluginId}:availability:expected ${expectation.availability} got ${requirement.availability || ""}`); + } } for (const expectation of expectations.providerCommands) { const provider = providers.find(candidate => candidate.providerId === expectation.providerId); @@ -1406,8 +1409,8 @@ function parseInteractiveWorldSurfaceExpectation(value) { function parseProviderPluginRequirementExpectation(value) { const parts = value.split(":"); - if (parts.length !== 5 || parts.some(part => !part)) { - console.error(`Expected provider plugin requirement in form, got: ${value}`); + if (![5, 6].includes(parts.length) || parts.some(part => !part)) { + console.error(`Expected provider plugin requirement in form, got: ${value}`); process.exit(2); } return { @@ -1416,6 +1419,7 @@ function parseProviderPluginRequirementExpectation(value) { pluginId: parts[2], status: parts[3], pluginOwnerRepo: parts[4], + availability: parts[5] || "", }; }