Enforce optional nested plugin requirements
This commit is contained in:
parent
b20e534c9c
commit
b03ddc1083
5 changed files with 73 additions and 0 deletions
|
|
@ -1099,6 +1099,12 @@ Recently cut:
|
|||
`story.*`/`style.patch`; Aetheria proves the daemon command boundary; the web
|
||||
lowerer resolves authored `action.command` through the shared Eve command
|
||||
intent path.
|
||||
- Provider plugin requirements now distinguish required parent semantics from
|
||||
optional nested sidecar affordances at the contract layer. A Sai surface may
|
||||
require `sai.vn`, but nested `norn.graph` and `tex.math` entries must keep
|
||||
`requiredCapabilities` empty and advertise their nested affordances through
|
||||
`optionalCapabilities`, preserving Norn and TeX as independent sidecar
|
||||
plugins.
|
||||
- Provider advertisement validation now runs in the parity harness.
|
||||
- Repixelizer and Aetheria provider advertisements are checked against
|
||||
`gamecult.eve.provider_advertisement.v1`.
|
||||
|
|
|
|||
|
|
@ -90,6 +90,9 @@ Required top-level meanings:
|
|||
interaction. Optional nested entries, such as a VN scene reserving space for
|
||||
independently-owned Norn or TeX surfaces, degrade when unavailable; they do
|
||||
not make Sai or any other parent plugin own the nested plugin's semantics.
|
||||
Required entries put their gated capabilities in `requiredCapabilities`.
|
||||
Optional nested entries leave `requiredCapabilities` empty and put their
|
||||
nested affordance claims in `optionalCapabilities`.
|
||||
Runtimes compare these entries with local projection support and report
|
||||
visible capability gaps; they must not import provider or plugin internals to
|
||||
recover missing semantics.
|
||||
|
|
|
|||
|
|
@ -220,6 +220,45 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"availability": {
|
||||
"const": "required"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"requiredCapabilities": {
|
||||
"minItems": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"required": ["availability"],
|
||||
"properties": {
|
||||
"availability": {
|
||||
"const": "optional-nested"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"required": ["optionalCapabilities"],
|
||||
"properties": {
|
||||
"requiredCapabilities": {
|
||||
"maxItems": 0
|
||||
},
|
||||
"optionalCapabilities": {
|
||||
"minItems": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -971,6 +971,7 @@ function validateProviderPluginRequirements(provider, requirements, pluginResult
|
|||
const errors = [];
|
||||
const pluginById = new Map((pluginResults || []).map(plugin => [plugin.pluginId, plugin]));
|
||||
for (const requirement of requirements) {
|
||||
errors.push(...validateProviderPluginRequirementShape(provider, requirement));
|
||||
const plugin = pluginById.get(requirement.pluginId);
|
||||
const optional = requirement.availability && requirement.availability !== "required";
|
||||
if (!plugin) {
|
||||
|
|
@ -991,6 +992,28 @@ function validateProviderPluginRequirements(provider, requirements, pluginResult
|
|||
return errors;
|
||||
}
|
||||
|
||||
function validateProviderPluginRequirementShape(provider, requirement) {
|
||||
const label = `${provider.advertisementPath}:${requirement.surfaceId}:plugin:${requirement.pluginId || "unknown"}`;
|
||||
const errors = [];
|
||||
const availability = requirement.availability || "required";
|
||||
if (!requirement.pluginId) errors.push(`${label}:pluginId:missing`);
|
||||
|
||||
if (availability === "required" && !requirement.requiredCapabilities.length) {
|
||||
errors.push(`${label}:requiredCapabilities:missing`);
|
||||
}
|
||||
|
||||
if (availability === "optional-nested") {
|
||||
if (requirement.requiredCapabilities.length) {
|
||||
errors.push(`${label}:optional-nested:requiredCapabilities:must-be-empty`);
|
||||
}
|
||||
if (!requirement.optionalCapabilities.length) {
|
||||
errors.push(`${label}:optional-nested:optionalCapabilities:missing`);
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
async function readJsonDocument(documentPath) {
|
||||
return JSON.parse(await readFile(path.join(repoRoot, documentPath), "utf8"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,7 +52,9 @@ test("preserves Sai required and optional nested plugin requirements from advert
|
|||
"story.jump",
|
||||
]);
|
||||
assert.equal(byPlugin.get("norn.graph").availability, "optional-nested");
|
||||
assert.deepEqual(byPlugin.get("norn.graph").requiredCapabilities, []);
|
||||
assert.deepEqual(byPlugin.get("norn.graph").optionalCapabilities, ["embed.norn"]);
|
||||
assert.equal(byPlugin.get("tex.math").availability, "optional-nested");
|
||||
assert.deepEqual(byPlugin.get("tex.math").requiredCapabilities, []);
|
||||
assert.deepEqual(byPlugin.get("tex.math").optionalCapabilities, ["embed.tex"]);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue