reporting-governance: enforce preflight fail-closed gate
This commit is contained in:
121
plugins/reporting-governance/src/core/action-support.mjs
Normal file
121
plugins/reporting-governance/src/core/action-support.mjs
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
function getByPath(source, dottedPath) {
|
||||||
|
if (!source || !dottedPath) return undefined;
|
||||||
|
return dottedPath.split('.').reduce((acc, key) => (acc == null ? undefined : acc[key]), source);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isTruthySupportLevel(level) {
|
||||||
|
return level === 'partial' || level === 'full';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSupported(node) {
|
||||||
|
return Boolean(node?.supported) && isTruthySupportLevel(node?.level);
|
||||||
|
}
|
||||||
|
|
||||||
|
function enforcementSupport(capabilityDescriptor = {}, actionName) {
|
||||||
|
return capabilityDescriptor?.capabilities?.enforcement?.[actionName] ?? { supported: false, level: 'none' };
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapActionToCapability(action) {
|
||||||
|
switch (action) {
|
||||||
|
case 'rewrite_message':
|
||||||
|
return 'rewrite_message';
|
||||||
|
case 'record_placeholder':
|
||||||
|
return 'annotate_placeholder';
|
||||||
|
case 'set_status':
|
||||||
|
return 'downgrade_status';
|
||||||
|
case 'request_review':
|
||||||
|
return 'request_review';
|
||||||
|
case 'raise_escalation':
|
||||||
|
return 'escalate';
|
||||||
|
case 'block_transition':
|
||||||
|
return 'block_transition';
|
||||||
|
case 'notify_operator':
|
||||||
|
return 'notify_operator';
|
||||||
|
case 'dispatch_message':
|
||||||
|
return 'dispatch_message';
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function evaluateActionSupport(capabilityDescriptor = {}, action) {
|
||||||
|
switch (action) {
|
||||||
|
case 'force_checkpoint': {
|
||||||
|
const capability = enforcementSupport(capabilityDescriptor, 'force_checkpoint');
|
||||||
|
const supported = isSupported(capability);
|
||||||
|
return {
|
||||||
|
action,
|
||||||
|
supported,
|
||||||
|
level: capability.level ?? 'none',
|
||||||
|
capability: 'force_checkpoint',
|
||||||
|
required_paths: ['capabilities.enforcement.force_checkpoint'],
|
||||||
|
execution_mode: 'package_core_signal',
|
||||||
|
mode: supported ? 'pass' : 'fail_closed'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case 'notify_operator': {
|
||||||
|
const queue = capabilityDescriptor?.capabilities?.notification_path?.queue_items ?? { supported: false, level: 'none' };
|
||||||
|
const sender = capabilityDescriptor?.capabilities?.notification_path?.sender_binding ?? { supported: false, level: 'none' };
|
||||||
|
const directSend = capabilityDescriptor?.capabilities?.notification_path?.direct_send ?? { supported: false, level: 'none' };
|
||||||
|
const queueSupported = isSupported(queue);
|
||||||
|
const senderSupported = isSupported(sender) || isSupported(directSend);
|
||||||
|
return {
|
||||||
|
action,
|
||||||
|
supported: queueSupported,
|
||||||
|
degraded: queueSupported && !senderSupported,
|
||||||
|
level: queueSupported && senderSupported ? 'full' : queueSupported ? 'partial' : 'none',
|
||||||
|
capability: 'notify_operator',
|
||||||
|
required_paths: ['capabilities.notification_path.queue_items'],
|
||||||
|
advisory_paths: ['capabilities.notification_path.sender_binding', 'capabilities.notification_path.direct_send'],
|
||||||
|
execution_mode: queueSupported && !senderSupported ? 'runtime_adapter_dispatch_deferred' : 'runtime_adapter_dispatch',
|
||||||
|
mode: queueSupported && !senderSupported ? 'honest_degrade' : queueSupported ? 'pass' : 'fail_closed'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case 'dispatch_message': {
|
||||||
|
const directSend = capabilityDescriptor?.capabilities?.notification_path?.direct_send ?? { supported: false, level: 'none' };
|
||||||
|
const sender = capabilityDescriptor?.capabilities?.notification_path?.sender_binding ?? { supported: false, level: 'none' };
|
||||||
|
const supported = isSupported(directSend) || isSupported(sender);
|
||||||
|
return {
|
||||||
|
action,
|
||||||
|
supported,
|
||||||
|
level: supported ? (directSend.level === 'full' || sender.level === 'full' ? 'full' : 'partial') : 'none',
|
||||||
|
capability: 'dispatch_message',
|
||||||
|
required_paths: ['capabilities.notification_path.direct_send', 'capabilities.notification_path.sender_binding'],
|
||||||
|
execution_mode: 'runtime_adapter_dispatch',
|
||||||
|
mode: supported ? 'pass' : 'fail_closed'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
const capabilityName = mapActionToCapability(action);
|
||||||
|
const capability = capabilityName ? enforcementSupport(capabilityDescriptor, capabilityName) : { supported: true, level: 'full' };
|
||||||
|
const supported = capabilityName ? isSupported(capability) : true;
|
||||||
|
return {
|
||||||
|
action,
|
||||||
|
supported,
|
||||||
|
level: capability.level ?? 'full',
|
||||||
|
capability: capabilityName,
|
||||||
|
required_paths: capabilityName ? [`capabilities.enforcement.${capabilityName}`] : [],
|
||||||
|
execution_mode: 'package_core_signal',
|
||||||
|
mode: supported ? 'pass' : 'fail_closed'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function assessCapabilityExpectation(capabilityDescriptor, expectation, expectationPaths = {}) {
|
||||||
|
const candidatePaths = expectationPaths[expectation] ?? [];
|
||||||
|
const matchedPath = candidatePaths.find((candidatePath) => isSupported(getByPath(capabilityDescriptor, candidatePath)));
|
||||||
|
return {
|
||||||
|
expectation,
|
||||||
|
supported: Boolean(matchedPath),
|
||||||
|
matched_path: matchedPath ?? candidatePaths[0] ?? null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const __testables = {
|
||||||
|
getByPath,
|
||||||
|
isTruthySupportLevel,
|
||||||
|
isSupported,
|
||||||
|
enforcementSupport,
|
||||||
|
mapActionToCapability
|
||||||
|
};
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { assessCapabilityExpectation, evaluateActionSupport } from './action-support.mjs';
|
||||||
|
|
||||||
const CANONICAL_SCHEMA_PATHS = Object.freeze({
|
const CANONICAL_SCHEMA_PATHS = Object.freeze({
|
||||||
event_schema: 'schemas/reporting-governance/event-envelope.schema.json',
|
event_schema: 'schemas/reporting-governance/event-envelope.schema.json',
|
||||||
evidence_schema: 'schemas/reporting-governance/evidence.schema.json',
|
evidence_schema: 'schemas/reporting-governance/evidence.schema.json',
|
||||||
@@ -20,25 +22,6 @@ function safeArray(value) {
|
|||||||
return Array.isArray(value) ? value : [];
|
return Array.isArray(value) ? value : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getByPath(source, dottedPath) {
|
|
||||||
if (!source || !dottedPath) return undefined;
|
|
||||||
return dottedPath.split('.').reduce((acc, key) => (acc == null ? undefined : acc[key]), source);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isSupported(node) {
|
|
||||||
return Boolean(node?.supported) && (node?.level === 'partial' || node?.level === 'full');
|
|
||||||
}
|
|
||||||
|
|
||||||
function assessCapabilityExpectation(capabilityDescriptor, expectation) {
|
|
||||||
const candidatePaths = EXPECTATION_CAPABILITY_PATHS[expectation] ?? [];
|
|
||||||
const matchedPath = candidatePaths.find((candidatePath) => isSupported(getByPath(capabilityDescriptor, candidatePath)));
|
|
||||||
return {
|
|
||||||
expectation,
|
|
||||||
supported: Boolean(matchedPath),
|
|
||||||
matched_path: matchedPath ?? candidatePaths[0] ?? null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function collectExpectedActions(profile = {}) {
|
function collectExpectedActions(profile = {}) {
|
||||||
const actions = [];
|
const actions = [];
|
||||||
const overdueAction = profile?.spec?.policies?.overrides?.checkpoints?.overdueAction;
|
const overdueAction = profile?.spec?.policies?.overrides?.checkpoints?.overdueAction;
|
||||||
@@ -51,48 +34,6 @@ function collectExpectedActions(profile = {}) {
|
|||||||
return [...new Set(actions)];
|
return [...new Set(actions)];
|
||||||
}
|
}
|
||||||
|
|
||||||
function evaluateActionSupport(capabilityDescriptor = {}, action) {
|
|
||||||
switch (action) {
|
|
||||||
case 'force_checkpoint':
|
|
||||||
return {
|
|
||||||
action,
|
|
||||||
supported: isSupported(getByPath(capabilityDescriptor, 'capabilities.enforcement.force_checkpoint')),
|
|
||||||
required_paths: ['capabilities.enforcement.force_checkpoint'],
|
|
||||||
mode: 'fail_closed'
|
|
||||||
};
|
|
||||||
case 'notify_operator': {
|
|
||||||
const queueSupported = isSupported(getByPath(capabilityDescriptor, 'capabilities.notification_path.queue_items'));
|
|
||||||
const senderSupported = isSupported(getByPath(capabilityDescriptor, 'capabilities.notification_path.sender_binding'))
|
|
||||||
|| isSupported(getByPath(capabilityDescriptor, 'capabilities.notification_path.direct_send'));
|
|
||||||
return {
|
|
||||||
action,
|
|
||||||
supported: queueSupported,
|
|
||||||
degraded: queueSupported && !senderSupported,
|
|
||||||
required_paths: ['capabilities.notification_path.queue_items'],
|
|
||||||
advisory_paths: ['capabilities.notification_path.sender_binding', 'capabilities.notification_path.direct_send'],
|
|
||||||
mode: queueSupported && !senderSupported ? 'honest_degrade' : 'pass'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case 'dispatch_message': {
|
|
||||||
const directSendSupported = isSupported(getByPath(capabilityDescriptor, 'capabilities.notification_path.direct_send'));
|
|
||||||
const senderSupported = isSupported(getByPath(capabilityDescriptor, 'capabilities.notification_path.sender_binding'));
|
|
||||||
return {
|
|
||||||
action,
|
|
||||||
supported: directSendSupported || senderSupported,
|
|
||||||
required_paths: ['capabilities.notification_path.direct_send', 'capabilities.notification_path.sender_binding'],
|
|
||||||
mode: directSendSupported || senderSupported ? 'pass' : 'fail_closed'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return {
|
|
||||||
action,
|
|
||||||
supported: true,
|
|
||||||
required_paths: [],
|
|
||||||
mode: 'pass'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function runCompatibilityPreflight({ capabilityDescriptor = {}, profile = {}, packageVersion } = {}) {
|
export function runCompatibilityPreflight({ capabilityDescriptor = {}, profile = {}, packageVersion } = {}) {
|
||||||
const runtimeId = capabilityDescriptor?.metadata?.id ?? capabilityDescriptor?.runtime?.name ?? 'unknown-runtime';
|
const runtimeId = capabilityDescriptor?.metadata?.id ?? capabilityDescriptor?.runtime?.name ?? 'unknown-runtime';
|
||||||
const requestedPluginVersion = profile?.spec?.package?.pluginVersion ?? packageVersion ?? null;
|
const requestedPluginVersion = profile?.spec?.package?.pluginVersion ?? packageVersion ?? null;
|
||||||
@@ -117,10 +58,10 @@ export function runCompatibilityPreflight({ capabilityDescriptor = {}, profile =
|
|||||||
}
|
}
|
||||||
|
|
||||||
const requiredExpectations = safeArray(profile?.capability_expectations?.required).map((expectation) =>
|
const requiredExpectations = safeArray(profile?.capability_expectations?.required).map((expectation) =>
|
||||||
assessCapabilityExpectation(capabilityDescriptor, expectation)
|
assessCapabilityExpectation(capabilityDescriptor, expectation, EXPECTATION_CAPABILITY_PATHS)
|
||||||
);
|
);
|
||||||
const preferredExpectations = safeArray(profile?.capability_expectations?.preferred).map((expectation) =>
|
const preferredExpectations = safeArray(profile?.capability_expectations?.preferred).map((expectation) =>
|
||||||
assessCapabilityExpectation(capabilityDescriptor, expectation)
|
assessCapabilityExpectation(capabilityDescriptor, expectation, EXPECTATION_CAPABILITY_PATHS)
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const result of requiredExpectations) {
|
for (const result of requiredExpectations) {
|
||||||
|
|||||||
@@ -1,19 +1,13 @@
|
|||||||
|
import { evaluateActionSupport } from './action-support.mjs';
|
||||||
|
|
||||||
function safeArray(value) {
|
function safeArray(value) {
|
||||||
return Array.isArray(value) ? value : [];
|
return Array.isArray(value) ? value : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
function enforcementSupport(capabilityDescriptor = {}, actionName) {
|
|
||||||
return capabilityDescriptor?.capabilities?.enforcement?.[actionName] ?? { supported: false, level: 'none' };
|
|
||||||
}
|
|
||||||
|
|
||||||
function notificationTruthModel(capabilityDescriptor = {}) {
|
function notificationTruthModel(capabilityDescriptor = {}) {
|
||||||
return capabilityDescriptor?.capabilities?.notification_path?.truth_model ?? {};
|
return capabilityDescriptor?.capabilities?.notification_path?.truth_model ?? {};
|
||||||
}
|
}
|
||||||
|
|
||||||
function isTruthySupportLevel(level) {
|
|
||||||
return level === 'partial' || level === 'full';
|
|
||||||
}
|
|
||||||
|
|
||||||
function createReceipt({ decision, actionPlans, blockedActions, delivery_state, notes = [] }) {
|
function createReceipt({ decision, actionPlans, blockedActions, delivery_state, notes = [] }) {
|
||||||
return {
|
return {
|
||||||
policy_id: decision.policy_id,
|
policy_id: decision.policy_id,
|
||||||
@@ -27,68 +21,6 @@ function createReceipt({ decision, actionPlans, blockedActions, delivery_state,
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapActionToCapability(action) {
|
|
||||||
switch (action) {
|
|
||||||
case 'rewrite_message':
|
|
||||||
return 'rewrite_message';
|
|
||||||
case 'record_placeholder':
|
|
||||||
return 'annotate_placeholder';
|
|
||||||
case 'set_status':
|
|
||||||
return 'downgrade_status';
|
|
||||||
case 'request_review':
|
|
||||||
return 'request_review';
|
|
||||||
case 'raise_escalation':
|
|
||||||
return 'escalate';
|
|
||||||
case 'block_transition':
|
|
||||||
return 'block_transition';
|
|
||||||
case 'notify_operator':
|
|
||||||
return 'notify_operator';
|
|
||||||
case 'dispatch_message':
|
|
||||||
return 'dispatch_message';
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function actionSupport(capabilityDescriptor = {}, actionName) {
|
|
||||||
switch (actionName) {
|
|
||||||
case 'notify_operator': {
|
|
||||||
const queue = capabilityDescriptor?.capabilities?.notification_path?.queue_items ?? { supported: false, level: 'none' };
|
|
||||||
const sender = capabilityDescriptor?.capabilities?.notification_path?.sender_binding ?? { supported: false, level: 'none' };
|
|
||||||
const directSend = capabilityDescriptor?.capabilities?.notification_path?.direct_send ?? { supported: false, level: 'none' };
|
|
||||||
const queueSupported = queue.supported && isTruthySupportLevel(queue.level);
|
|
||||||
const senderSupported = (sender.supported && isTruthySupportLevel(sender.level)) || (directSend.supported && isTruthySupportLevel(directSend.level));
|
|
||||||
return {
|
|
||||||
supported: queueSupported,
|
|
||||||
level: queueSupported && senderSupported ? 'full' : queueSupported ? 'partial' : 'none',
|
|
||||||
capability: 'notify_operator',
|
|
||||||
mode: queueSupported && !senderSupported ? 'runtime_adapter_dispatch_deferred' : 'runtime_adapter_dispatch'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case 'dispatch_message': {
|
|
||||||
const directSend = capabilityDescriptor?.capabilities?.notification_path?.direct_send ?? { supported: false, level: 'none' };
|
|
||||||
const sender = capabilityDescriptor?.capabilities?.notification_path?.sender_binding ?? { supported: false, level: 'none' };
|
|
||||||
const supported = (directSend.supported && isTruthySupportLevel(directSend.level)) || (sender.supported && isTruthySupportLevel(sender.level));
|
|
||||||
return {
|
|
||||||
supported,
|
|
||||||
level: supported ? (directSend.level === 'full' || sender.level === 'full' ? 'full' : 'partial') : 'none',
|
|
||||||
capability: 'dispatch_message',
|
|
||||||
mode: 'runtime_adapter_dispatch'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
const capabilityName = mapActionToCapability(actionName);
|
|
||||||
const capability = capabilityName ? enforcementSupport(capabilityDescriptor, capabilityName) : { supported: true, level: 'full' };
|
|
||||||
return {
|
|
||||||
supported: capabilityName ? capability.supported && isTruthySupportLevel(capability.level) : true,
|
|
||||||
level: capability.level ?? 'full',
|
|
||||||
capability: capabilityName,
|
|
||||||
mode: 'package_core_signal'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function planDecisionExecution({ decision, capabilityDescriptor = {} }) {
|
export function planDecisionExecution({ decision, capabilityDescriptor = {} }) {
|
||||||
if (!decision) {
|
if (!decision) {
|
||||||
throw new Error('decision is required');
|
throw new Error('decision is required');
|
||||||
@@ -101,12 +33,12 @@ export function planDecisionExecution({ decision, capabilityDescriptor = {} }) {
|
|||||||
const notes = [];
|
const notes = [];
|
||||||
|
|
||||||
for (const requiredAction of safeArray(decision.required_actions)) {
|
for (const requiredAction of safeArray(decision.required_actions)) {
|
||||||
const support = actionSupport(capabilityDescriptor, requiredAction.action);
|
const support = evaluateActionSupport(capabilityDescriptor, requiredAction.action);
|
||||||
|
|
||||||
if (support.supported) {
|
if (support.supported) {
|
||||||
actionPlans.push({
|
actionPlans.push({
|
||||||
...requiredAction,
|
...requiredAction,
|
||||||
execution_mode: support.mode,
|
execution_mode: support.execution_mode,
|
||||||
capability: support.capability,
|
capability: support.capability,
|
||||||
support_level: support.level
|
support_level: support.level
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,56 @@
|
|||||||
import { evaluatePolicies } from './policy-evaluator.mjs';
|
import { evaluatePolicies } from './policy-evaluator.mjs';
|
||||||
|
import { runCompatibilityPreflight } from './compatibility-preflight.mjs';
|
||||||
import { planDecisionExecution } from './decision-runner.mjs';
|
import { planDecisionExecution } from './decision-runner.mjs';
|
||||||
|
|
||||||
|
function createBlockedContract({ capabilityDescriptor = {}, evaluation, preflight }) {
|
||||||
|
return {
|
||||||
|
evaluation,
|
||||||
|
preflight,
|
||||||
|
planning: {
|
||||||
|
decision: evaluation.decision,
|
||||||
|
enforcement_intent: {
|
||||||
|
policy_id: evaluation.decision.policy_id,
|
||||||
|
decision: evaluation.decision.decision,
|
||||||
|
planned_actions: [],
|
||||||
|
blocked_actions: [],
|
||||||
|
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
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
contract: {
|
||||||
|
runtime: capabilityDescriptor?.metadata?.id ?? capabilityDescriptor?.runtime?.name ?? 'unknown-runtime',
|
||||||
|
policy_id: evaluation.decision.policy_id,
|
||||||
|
decision: evaluation.decision.decision,
|
||||||
|
adapter_actions: [],
|
||||||
|
package_actions: [],
|
||||||
|
blocked_actions: [],
|
||||||
|
delivery_state: 'blocked',
|
||||||
|
receipt_status: 'failed'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function executeGovernanceContract({
|
export function executeGovernanceContract({
|
||||||
event,
|
event,
|
||||||
evidence = [],
|
evidence = [],
|
||||||
capabilityDescriptor = {},
|
capabilityDescriptor = {},
|
||||||
policyPacks = [],
|
policyPacks = [],
|
||||||
context = {},
|
context = {},
|
||||||
|
profile = {},
|
||||||
|
packageVersion,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
const evaluation = evaluatePolicies({
|
const evaluation = evaluatePolicies({
|
||||||
event,
|
event,
|
||||||
@@ -16,6 +60,16 @@ export function executeGovernanceContract({
|
|||||||
context,
|
context,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const preflight = runCompatibilityPreflight({
|
||||||
|
capabilityDescriptor,
|
||||||
|
profile,
|
||||||
|
packageVersion,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (preflight.status === 'fail_closed') {
|
||||||
|
return createBlockedContract({ capabilityDescriptor, evaluation, preflight });
|
||||||
|
}
|
||||||
|
|
||||||
const planning = planDecisionExecution({
|
const planning = planDecisionExecution({
|
||||||
decision: evaluation.decision,
|
decision: evaluation.decision,
|
||||||
capabilityDescriptor,
|
capabilityDescriptor,
|
||||||
@@ -23,6 +77,7 @@ export function executeGovernanceContract({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
evaluation,
|
evaluation,
|
||||||
|
preflight,
|
||||||
planning,
|
planning,
|
||||||
contract: {
|
contract: {
|
||||||
runtime: capabilityDescriptor?.metadata?.id ?? capabilityDescriptor?.runtime?.name ?? 'unknown-runtime',
|
runtime: capabilityDescriptor?.metadata?.id ?? capabilityDescriptor?.runtime?.name ?? 'unknown-runtime',
|
||||||
|
|||||||
@@ -94,10 +94,13 @@ test('capability descriptor -> policy evaluation -> decision planning yields ada
|
|||||||
context: {
|
context: {
|
||||||
signals: ['checkpoint_overdue'],
|
signals: ['checkpoint_overdue'],
|
||||||
operator_context: { report_anchor_present: true }
|
operator_context: { report_anchor_present: true }
|
||||||
}
|
},
|
||||||
|
profile: strictProfile,
|
||||||
|
packageVersion: '0.1.0-mainline'
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.equal(result.evaluation.decision.decision, 'force_checkpoint');
|
assert.equal(result.evaluation.decision.decision, 'force_checkpoint');
|
||||||
|
assert.equal(result.preflight.status, 'pass');
|
||||||
assert.equal(result.planning.receipt.delivery_state, 'pending_external_send');
|
assert.equal(result.planning.receipt.delivery_state, 'pending_external_send');
|
||||||
assert.deepEqual(result.contract.adapter_actions, ['notify_operator']);
|
assert.deepEqual(result.contract.adapter_actions, ['notify_operator']);
|
||||||
assert.deepEqual(result.contract.package_actions, ['emit_event']);
|
assert.deepEqual(result.contract.package_actions, ['emit_event']);
|
||||||
@@ -139,10 +142,13 @@ test('contract truthfully degrades when notify path can queue but cannot directl
|
|||||||
policyPacks: [noSilencePack],
|
policyPacks: [noSilencePack],
|
||||||
context: {
|
context: {
|
||||||
signals: ['checkpoint_overdue']
|
signals: ['checkpoint_overdue']
|
||||||
}
|
},
|
||||||
|
profile: strictProfile,
|
||||||
|
packageVersion: '0.1.0-mainline'
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.equal(result.evaluation.decision.decision, 'force_checkpoint');
|
assert.equal(result.evaluation.decision.decision, 'force_checkpoint');
|
||||||
|
assert.equal(result.preflight.status, 'degraded');
|
||||||
assert.deepEqual(result.contract.adapter_actions, ['notify_operator']);
|
assert.deepEqual(result.contract.adapter_actions, ['notify_operator']);
|
||||||
assert.deepEqual(result.contract.blocked_actions, []);
|
assert.deepEqual(result.contract.blocked_actions, []);
|
||||||
assert.equal(result.contract.receipt_status, 'planned');
|
assert.equal(result.contract.receipt_status, 'planned');
|
||||||
@@ -181,10 +187,18 @@ test('contract fails closed when capability descriptor cannot satisfy mandatory
|
|||||||
policyPacks: [noSilencePack],
|
policyPacks: [noSilencePack],
|
||||||
context: {
|
context: {
|
||||||
signals: ['checkpoint_overdue']
|
signals: ['checkpoint_overdue']
|
||||||
}
|
},
|
||||||
|
profile: strictProfile,
|
||||||
|
packageVersion: '0.1.0-mainline'
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.equal(result.evaluation.decision.decision, 'force_checkpoint');
|
assert.equal(result.evaluation.decision.decision, 'force_checkpoint');
|
||||||
assert.deepEqual(result.contract.adapter_actions, ['notify_operator']);
|
assert.equal(result.preflight.status, 'fail_closed');
|
||||||
assert.equal(result.contract.receipt_status, 'planned');
|
assert.deepEqual(result.planning.enforcement_intent.planned_actions, []);
|
||||||
|
assert.deepEqual(result.contract.adapter_actions, []);
|
||||||
|
assert.deepEqual(result.contract.package_actions, []);
|
||||||
|
assert.deepEqual(result.contract.blocked_actions, []);
|
||||||
|
assert.equal(result.contract.delivery_state, 'blocked');
|
||||||
|
assert.equal(result.contract.receipt_status, 'failed');
|
||||||
|
assert.ok(result.planning.receipt.notes.some((note) => note.includes('failed closed')));
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user