fix: preserve governance contract compatibility

This commit is contained in:
Eve
2026-05-08 09:58:24 +08:00
parent 47a2c4c727
commit 000f6b6a8b
3 changed files with 95 additions and 18 deletions

View File

@@ -38,6 +38,13 @@ export function runCompatibilityPreflight({ capabilityDescriptor = {}, profile =
const runtimeId = capabilityDescriptor?.metadata?.id ?? capabilityDescriptor?.runtime?.name ?? 'unknown-runtime';
const requestedPluginVersion = profile?.spec?.package?.pluginVersion ?? packageVersion ?? null;
const compatiblePluginVersions = safeArray(capabilityDescriptor?.compatibility?.plugin_spec_versions);
const hasCompatibilityEnvelope = Boolean(
requestedPluginVersion ||
profile?.metadata?.id ||
safeArray(profile?.capability_expectations?.required).length > 0 ||
safeArray(profile?.capability_expectations?.preferred).length > 0 ||
collectExpectedActions(profile).length > 0
);
const errors = [];
const warnings = [];
@@ -46,15 +53,17 @@ export function runCompatibilityPreflight({ capabilityDescriptor = {}, profile =
const schemaChecks = Object.entries(CANONICAL_SCHEMA_PATHS).map(([key, expectedPath]) => {
const actualPath = capabilityDescriptor?.compatibility?.[key] ?? null;
const ok = actualPath === expectedPath;
if (!ok) {
if (!ok && hasCompatibilityEnvelope) {
errors.push(`schema mismatch: ${key} expected ${expectedPath} but got ${actualPath ?? 'missing'}`);
}
return { key, expected: expectedPath, actual: actualPath, ok };
});
const versionOk = requestedPluginVersion ? compatiblePluginVersions.includes(requestedPluginVersion) : false;
const versionOk = requestedPluginVersion ? compatiblePluginVersions.includes(requestedPluginVersion) : true;
if (!versionOk) {
errors.push(`plugin version ${requestedPluginVersion ?? 'missing'} is not declared compatible by runtime ${runtimeId}`);
} else if (!requestedPluginVersion) {
notes.push('compatibility preflight skipped plugin version pin because caller did not provide profile.spec.package.pluginVersion or packageVersion');
}
const requiredExpectations = safeArray(profile?.capability_expectations?.required).map((expectation) =>

View File

@@ -2,7 +2,25 @@ import { evaluatePolicies } from './policy-evaluator.mjs';
import { runCompatibilityPreflight } from './compatibility-preflight.mjs';
import { planDecisionExecution } from './decision-runner.mjs';
function createBlockedReceipt({ evaluation, preflight }) {
return {
policy_id: evaluation.decision.policy_id,
decision: evaluation.decision.decision,
status: 'blocked',
delivery_state: 'blocked',
enforcement_intent: [],
blocked_actions: [],
operator_notice_required: Boolean(evaluation.decision.operator_notice?.required),
notes: [
'Compatibility preflight failed closed; execution planning was blocked before any runnable contract could be produced.',
...preflight.errors,
],
};
}
function createBlockedContract({ capabilityDescriptor = {}, evaluation, preflight }) {
const receipt = createBlockedReceipt({ evaluation, preflight });
return {
evaluation,
preflight,
@@ -16,19 +34,7 @@ function createBlockedContract({ capabilityDescriptor = {}, evaluation, prefligh
runtime_adapter_required: [],
package_core_actions: []
},
receipt: {
policy_id: evaluation.decision.policy_id,
decision: evaluation.decision.decision,
status: 'failed',
delivery_state: 'blocked',
enforcement_intent: [],
blocked_actions: [],
operator_notice_required: Boolean(evaluation.decision.operator_notice?.required),
notes: [
'Compatibility preflight failed closed; execution planning was blocked before any runnable contract could be produced.',
...preflight.errors
]
}
receipt
},
contract: {
runtime: capabilityDescriptor?.metadata?.id ?? capabilityDescriptor?.runtime?.name ?? 'unknown-runtime',
@@ -37,8 +43,8 @@ function createBlockedContract({ capabilityDescriptor = {}, evaluation, prefligh
adapter_actions: [],
package_actions: [],
blocked_actions: [],
delivery_state: 'blocked',
receipt_status: 'failed'
delivery_state: receipt.delivery_state,
receipt_status: receipt.status,
}
};
}