Clarify optional nested plugin ownership

This commit is contained in:
Meta Vens 2026-07-09 10:25:23 +01:00
commit 5d58f3e414
5 changed files with 34 additions and 15 deletions

View file

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

View file

@ -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 <providerId:surfaceId:pluginId:status:pluginOwnerRepo>`.
`--expect-provider-plugin-requirement <providerId:surfaceId:pluginId:status:pluginOwnerRepo[:availability]>`.
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

View file

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

View file

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

View file

@ -38,7 +38,7 @@ if (!exportDirectory) {
" --expect-provider-surface-kind <providerId:surfaceId:surfaceKind>",
" --expect-provider-surface-field <providerId:surfaceId:field.path:value>",
" --expect-interactive-world-surface <providerId:surfaceId:targetId:ownerRepo>",
" --expect-provider-plugin-requirement <providerId:surfaceId:pluginId:status:pluginOwnerRepo>",
" --expect-provider-plugin-requirement <providerId:surfaceId:pluginId:status:pluginOwnerRepo[:availability]>",
" --expect-provider-command <providerId:command>",
" --expect-provider-receipt-state <providerId:state>",
" --expect-provider-handoff <providerId>",
@ -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 <providerId:surfaceId:pluginId:status:pluginOwnerRepo> form, got: ${value}`);
if (![5, 6].includes(parts.length) || parts.some(part => !part)) {
console.error(`Expected provider plugin requirement in <providerId:surfaceId:pluginId:status:pluginOwnerRepo[:availability]> 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] || "",
};
}