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
|
`story.*`/`style.patch`; Aetheria proves the daemon command boundary; the web
|
||||||
lowerer resolves authored `action.command` through the shared Eve command
|
lowerer resolves authored `action.command` through the shared Eve command
|
||||||
intent path.
|
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.
|
- Provider advertisement validation now runs in the parity harness.
|
||||||
- Repixelizer and Aetheria provider advertisements are checked against
|
- Repixelizer and Aetheria provider advertisements are checked against
|
||||||
`gamecult.eve.provider_advertisement.v1`.
|
`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
|
interaction. Optional nested entries, such as a VN scene reserving space for
|
||||||
independently-owned Norn or TeX surfaces, degrade when unavailable; they do
|
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.
|
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
|
Runtimes compare these entries with local projection support and report
|
||||||
visible capability gaps; they must not import provider or plugin internals to
|
visible capability gaps; they must not import provider or plugin internals to
|
||||||
recover missing semantics.
|
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
|
"additionalProperties": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -971,6 +971,7 @@ function validateProviderPluginRequirements(provider, requirements, pluginResult
|
||||||
const errors = [];
|
const errors = [];
|
||||||
const pluginById = new Map((pluginResults || []).map(plugin => [plugin.pluginId, plugin]));
|
const pluginById = new Map((pluginResults || []).map(plugin => [plugin.pluginId, plugin]));
|
||||||
for (const requirement of requirements) {
|
for (const requirement of requirements) {
|
||||||
|
errors.push(...validateProviderPluginRequirementShape(provider, requirement));
|
||||||
const plugin = pluginById.get(requirement.pluginId);
|
const plugin = pluginById.get(requirement.pluginId);
|
||||||
const optional = requirement.availability && requirement.availability !== "required";
|
const optional = requirement.availability && requirement.availability !== "required";
|
||||||
if (!plugin) {
|
if (!plugin) {
|
||||||
|
|
@ -991,6 +992,28 @@ function validateProviderPluginRequirements(provider, requirements, pluginResult
|
||||||
return errors;
|
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) {
|
async function readJsonDocument(documentPath) {
|
||||||
return JSON.parse(await readFile(path.join(repoRoot, documentPath), "utf8"));
|
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",
|
"story.jump",
|
||||||
]);
|
]);
|
||||||
assert.equal(byPlugin.get("norn.graph").availability, "optional-nested");
|
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.deepEqual(byPlugin.get("norn.graph").optionalCapabilities, ["embed.norn"]);
|
||||||
assert.equal(byPlugin.get("tex.math").availability, "optional-nested");
|
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"]);
|
assert.deepEqual(byPlugin.get("tex.math").optionalCapabilities, ["embed.tex"]);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue