Assert independent nested plugin composition
This commit is contained in:
parent
7c7cfcde8e
commit
bc7d7eaf30
4 changed files with 73 additions and 0 deletions
|
|
@ -682,6 +682,13 @@ Recently cut:
|
|||
agreement, checks sidecar operations against ABI fixture operations, exports
|
||||
the runtime boundary, and the plugin-owner consumer smoke asserts the Sai and
|
||||
Norn sidecar contracts from the conformance pack.
|
||||
- The plugin-owner conformance consumer now asserts independent nested plugin
|
||||
composition directly. `--expect-independent-nested-plugin` verifies that the
|
||||
Sai VN provider surface requires `sai.vn` while `norn.graph` and `tex.math`
|
||||
remain `optional-nested`, separately owned, sidecar-provided capabilities.
|
||||
That keeps a Sai surface free to deploy with Norn or TeX when available
|
||||
without turning either plugin into a Sai dependency or runtime-hosted
|
||||
semantic authority.
|
||||
- Conformance export now carries first-class `capabilityGaps[]` records derived
|
||||
from plugin errors, provider advertisement/scenario errors, runtime missing
|
||||
evidence, declared unsupported plugin projection, capture gaps, and split
|
||||
|
|
|
|||
|
|
@ -216,6 +216,11 @@ Consumer smokes can assert them with
|
|||
`--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.
|
||||
For the parent/nested relationship itself, use
|
||||
`--expect-independent-nested-plugin <providerId:surfaceId:parentPluginId:nestedPluginId:nestedOwnerRepo>`;
|
||||
this checks that the parent plugin is required, the nested plugin is
|
||||
`optional-nested`, the nested owner is separate, and nested capabilities remain
|
||||
optional composition rather than parent-plugin dependency.
|
||||
|
||||
The runtime conformance pack is evidence-shaped, not fixture-shaped. Its
|
||||
`packs/runtime.json` document includes `runtimeTargets` so runtime repos can
|
||||
|
|
|
|||
|
|
@ -74,6 +74,8 @@ node $consumerScript $consumerExport `
|
|||
--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:optional-nested `
|
||||
--expect-provider-plugin-requirement gamecult.home.vn:sai.visual_novel.surface:tex.math:optional-satisfied:EvePlugins:optional-nested `
|
||||
--expect-independent-nested-plugin gamecult.home.vn:sai.visual_novel.surface:sai.vn:norn.graph:Norn `
|
||||
--expect-independent-nested-plugin gamecult.home.vn:sai.visual_novel.surface:sai.vn:tex.math:EvePlugins `
|
||||
--expect-plugin-capability sai.vn:vn.stage `
|
||||
--expect-plugin-capability sai.vn:story.choose `
|
||||
--expect-plugin-capability sai.vn:story.continue `
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ if (!exportDirectory) {
|
|||
" --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[:availability]>",
|
||||
" --expect-independent-nested-plugin <providerId:surfaceId:parentPluginId:nestedPluginId:nestedOwnerRepo>",
|
||||
" --expect-provider-command <providerId:command>",
|
||||
" --expect-provider-receipt-state <providerId:state>",
|
||||
" --expect-provider-handoff <providerId>",
|
||||
|
|
@ -550,6 +551,45 @@ function validateIndex(index, directory, expectations, errors) {
|
|||
errors.push(`providerPluginRequirementCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.pluginId}:availability:expected ${expectation.availability} got ${requirement.availability || ""}`);
|
||||
}
|
||||
}
|
||||
for (const expectation of expectations.independentNestedPlugins) {
|
||||
const parentRequirement = providerPluginRequirementCoverage.find(candidate =>
|
||||
candidate.providerId === expectation.providerId &&
|
||||
candidate.surfaceId === expectation.surfaceId &&
|
||||
candidate.pluginId === expectation.parentPluginId);
|
||||
const nestedRequirement = providerPluginRequirementCoverage.find(candidate =>
|
||||
candidate.providerId === expectation.providerId &&
|
||||
candidate.surfaceId === expectation.surfaceId &&
|
||||
candidate.pluginId === expectation.nestedPluginId);
|
||||
if (!parentRequirement) {
|
||||
errors.push(`independentNestedPlugin:${expectation.providerId}:${expectation.surfaceId}:${expectation.parentPluginId}:parent:missing`);
|
||||
continue;
|
||||
}
|
||||
if (!nestedRequirement) {
|
||||
errors.push(`independentNestedPlugin:${expectation.providerId}:${expectation.surfaceId}:${expectation.nestedPluginId}:nested:missing`);
|
||||
continue;
|
||||
}
|
||||
if (parentRequirement.availability !== "required") {
|
||||
errors.push(`independentNestedPlugin:${expectation.providerId}:${expectation.surfaceId}:${expectation.parentPluginId}:parent.availability:expected required got ${parentRequirement.availability || ""}`);
|
||||
}
|
||||
if (nestedRequirement.availability !== "optional-nested") {
|
||||
errors.push(`independentNestedPlugin:${expectation.providerId}:${expectation.surfaceId}:${expectation.nestedPluginId}:nested.availability:expected optional-nested got ${nestedRequirement.availability || ""}`);
|
||||
}
|
||||
if (!["optional-satisfied", "optional-degraded"].includes(nestedRequirement.status)) {
|
||||
errors.push(`independentNestedPlugin:${expectation.providerId}:${expectation.surfaceId}:${expectation.nestedPluginId}:nested.status:expected optional-satisfied or optional-degraded got ${nestedRequirement.status || ""}`);
|
||||
}
|
||||
if (nestedRequirement.pluginOwnerRepo !== expectation.nestedOwnerRepo) {
|
||||
errors.push(`independentNestedPlugin:${expectation.providerId}:${expectation.surfaceId}:${expectation.nestedPluginId}:nested.pluginOwnerRepo:expected ${expectation.nestedOwnerRepo} got ${nestedRequirement.pluginOwnerRepo || ""}`);
|
||||
}
|
||||
if (nestedRequirement.pluginOwnerRepo === parentRequirement.pluginOwnerRepo) {
|
||||
errors.push(`independentNestedPlugin:${expectation.providerId}:${expectation.surfaceId}:${expectation.nestedPluginId}:owner:expected owner distinct from parent ${parentRequirement.pluginOwnerRepo || ""}`);
|
||||
}
|
||||
if (Array.isArray(nestedRequirement.requiredCapabilities) && nestedRequirement.requiredCapabilities.length) {
|
||||
errors.push(`independentNestedPlugin:${expectation.providerId}:${expectation.surfaceId}:${expectation.nestedPluginId}:requiredCapabilities:expected empty got ${nestedRequirement.requiredCapabilities.join(",")}`);
|
||||
}
|
||||
if (!Array.isArray(nestedRequirement.optionalCapabilities) || !nestedRequirement.optionalCapabilities.length) {
|
||||
errors.push(`independentNestedPlugin:${expectation.providerId}:${expectation.surfaceId}:${expectation.nestedPluginId}:optionalCapabilities:missing`);
|
||||
}
|
||||
}
|
||||
for (const expectation of expectations.providerCommands) {
|
||||
const provider = providers.find(candidate => candidate.providerId === expectation.providerId);
|
||||
if (!provider) {
|
||||
|
|
@ -1270,6 +1310,7 @@ function parseArguments(args) {
|
|||
providerSurfaceFields: [],
|
||||
interactiveWorldSurfaces: [],
|
||||
providerPluginRequirements: [],
|
||||
independentNestedPlugins: [],
|
||||
providerCommands: [],
|
||||
providerReceiptStates: [],
|
||||
providerHandoffs: [],
|
||||
|
|
@ -1332,6 +1373,7 @@ function parseArguments(args) {
|
|||
["--expect-provider-surface-field", expectations.providerSurfaceFields],
|
||||
["--expect-interactive-world-surface", expectations.interactiveWorldSurfaces],
|
||||
["--expect-provider-plugin-requirement", expectations.providerPluginRequirements],
|
||||
["--expect-independent-nested-plugin", expectations.independentNestedPlugins],
|
||||
["--expect-provider-command", expectations.providerCommands],
|
||||
["--expect-provider-receipt-state", expectations.providerReceiptStates],
|
||||
["--expect-provider-handoff", expectations.providerHandoffs],
|
||||
|
|
@ -1416,6 +1458,8 @@ function parseArguments(args) {
|
|||
target.push(parseInteractiveWorldSurfaceExpectation(value));
|
||||
} else if (option === "--expect-provider-plugin-requirement") {
|
||||
target.push(parseProviderPluginRequirementExpectation(value));
|
||||
} else if (option === "--expect-independent-nested-plugin") {
|
||||
target.push(parseIndependentNestedPluginExpectation(value));
|
||||
} else if (option === "--expect-provider-command") {
|
||||
target.push(parseProviderExpectation(value, "command"));
|
||||
} else if (option === "--expect-provider-receipt-state") {
|
||||
|
|
@ -1515,6 +1559,21 @@ function parseRuntimeCaptureProbeExpectation(value) {
|
|||
};
|
||||
}
|
||||
|
||||
function parseIndependentNestedPluginExpectation(value) {
|
||||
const parts = value.split(":");
|
||||
if (parts.length !== 5 || parts.some(part => !part)) {
|
||||
console.error(`Expected independent nested plugin in <providerId:surfaceId:parentPluginId:nestedPluginId:nestedOwnerRepo> form, got: ${value}`);
|
||||
process.exit(2);
|
||||
}
|
||||
return {
|
||||
providerId: parts[0],
|
||||
surfaceId: parts[1],
|
||||
parentPluginId: parts[2],
|
||||
nestedPluginId: parts[3],
|
||||
nestedOwnerRepo: parts[4],
|
||||
};
|
||||
}
|
||||
|
||||
function parseLocalProviderCatalogExpectation(value) {
|
||||
const parts = value.split(":");
|
||||
if (parts.length !== 3 || parts.some(part => !part)) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue