From c2b8c4be3df90644aae6294cbe7191d2a8fa7304 Mon Sep 17 00:00:00 2001 From: Eve Date: Fri, 8 May 2026 11:45:26 +0800 Subject: [PATCH] fix: align degraded runtime route policy --- .../src/core/runtime-integrated.mjs | 18 +++++- .../runtime-integrated.integration.test.mjs | 64 ++++++++++++++++++- 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/plugins/reporting-governance/src/core/runtime-integrated.mjs b/plugins/reporting-governance/src/core/runtime-integrated.mjs index ce888cf..b7ade2a 100644 --- a/plugins/reporting-governance/src/core/runtime-integrated.mjs +++ b/plugins/reporting-governance/src/core/runtime-integrated.mjs @@ -28,14 +28,28 @@ function resolveRuntimeRoute({ governance, runtime, repoRootOverride }) { return createNotAttemptedResult('runtime execution not attempted'); } - if (governance.preflight?.status !== 'pass') { - return createNotAttemptedResult('runtime execution not attempted: compatibility preflight did not pass'); + if (governance.preflight?.status === 'fail_closed') { + return createNotAttemptedResult('runtime execution not attempted: compatibility preflight failed closed'); } if (!governance.deploymentBinding) { return createNotAttemptedResult('runtime execution not attempted: deployment binding is missing'); } + if (!['pass', 'degraded'].includes(governance.preflight?.status)) { + return createNotAttemptedResult('runtime execution not attempted: compatibility preflight did not produce a runnable status'); + } + + if (governance.preflight?.status === 'degraded') { + const adapterActions = Array.isArray(governance.contract?.adapter_actions) + ? governance.contract.adapter_actions + : []; + + if (adapterActions.length === 0) { + return createNotAttemptedResult('runtime execution not attempted: degraded compatibility preflight produced no adapter_action route'); + } + } + const adapterActions = Array.isArray(governance.contract?.adapter_actions) ? governance.contract.adapter_actions : []; diff --git a/plugins/reporting-governance/test/runtime-integrated.integration.test.mjs b/plugins/reporting-governance/test/runtime-integrated.integration.test.mjs index 53b57f9..282f3a4 100644 --- a/plugins/reporting-governance/test/runtime-integrated.integration.test.mjs +++ b/plugins/reporting-governance/test/runtime-integrated.integration.test.mjs @@ -190,6 +190,24 @@ function createStubRuntime() { }; } +function createDegradedDescriptor() { + return { + ...capabilityDescriptor, + metadata: { + ...capabilityDescriptor.metadata, + id: 'degraded-openclaw-watchdog-reference' + }, + capabilities: { + ...capabilityDescriptor.capabilities, + notification_path: { + ...capabilityDescriptor.capabilities.notification_path, + sender_binding: { supported: false, level: 'none' }, + direct_send: { supported: false, level: 'none' } + } + } + }; +} + test('runtime-integrated route matrix: no runtime stays planning-only', () => { const result = executeRuntimeIntegratedGovernance(createBaseArgs()); @@ -199,7 +217,7 @@ test('runtime-integrated route matrix: no runtime stays planning-only', () => { assert.equal(result.runtimeExecution, null); }); -test('runtime-integrated route matrix: preflight fail blocks runtime route', () => { +test('runtime-integrated route matrix: preflight fail_closed blocks runtime route', () => { const result = executeRuntimeIntegratedGovernance(createBaseArgs({ profile: { ...strictProfileArtifact, @@ -213,7 +231,7 @@ test('runtime-integrated route matrix: preflight fail blocks runtime route', () assert.equal(result.preflight.status, 'fail_closed'); assert.equal(result.runtimeIntegration.attempted, false); - assert.equal(result.runtimeIntegration.reason, 'runtime execution not attempted: compatibility preflight did not pass'); + assert.equal(result.runtimeIntegration.reason, 'runtime execution not attempted: compatibility preflight failed closed'); assert.equal(result.runtimeExecution, null); }); @@ -252,6 +270,48 @@ test('runtime-integrated route matrix: unknown adapter_action stays planning-onl assert.equal(result.runtimeExecution, null); }); +test('runtime-integrated route matrix: degraded preflight still runs queue/bridge route honestly', () => { + const root = createFixtureRoot(); + try { + mkdirs(root, ['evidence', 'events', 'queue', 'spool', 'receipts']); + const statePath = writeState(root); + + const result = executeRuntimeIntegratedGovernance(createBaseArgs({ + capabilityDescriptor: createDegradedDescriptor(), + runtime: { + state: statePath, + evidenceDir: path.join(root, 'evidence'), + eventDir: path.join(root, 'events'), + queueDir: path.join(root, 'queue'), + spoolDir: path.join(root, 'spool'), + receiptDir: path.join(root, 'receipts'), + writeState: true, + dryRun: true, + now: '2026-05-07T08:20:00.000Z', + }, + })); + + assert.equal(result.preflight.status, 'degraded'); + assert.equal(result.contract.adapter_actions[0], 'notify_operator'); + assert.equal(result.contract.delivery_state, 'pending_external_send'); + assert.equal(result.runtimeIntegration.attempted, true); + assert.equal(result.runtimeIntegration.adapter, 'orchestrator'); + assert.equal(result.runtimeIntegration.action, 'notify_operator'); + assert.equal(result.runtimeExecution.ok, true); + assert.equal(result.runtimeExecution.result.dispatcher.dispatchedCount, 1); + assert.equal(result.runtimeExecution.result.supervisor.pendingCount, 1); + + const queueItem = readSingleJson(path.join(root, 'queue')); + assert.equal(queueItem.status, 'dispatched'); + + const receipt = readSingleJson(path.join(root, 'receipts')); + assert.equal(receipt.state, 'pending_external_send'); + assert.equal(receipt.supervisor_mode, 'dry_run'); + } finally { + fs.rmSync(root, { recursive: true, force: true }); + } +}); + test('runtime-integrated route matrix: matched adapter_action runs orchestrator adapter runner', () => { const root = createFixtureRoot(); try {