feat(reporting-governance): add minimal runtime integrated slice

This commit is contained in:
Eve
2026-05-08 10:56:30 +08:00
parent 173de01bdb
commit 34f035cfb5
8 changed files with 363 additions and 215 deletions

View File

@@ -11,6 +11,7 @@ Current purpose:
- provide a minimal package-level policy evaluator and decision runner skeleton that can be verified in isolation
- add one minimal package-owned deployment profile artifact / loader / binding contract slice that is executable in tests
- let profile artifacts drive one real orchestrator adapter entrypoint instead of staying test-only
- add one minimal runtime-integrated slice wiring contract planning into real orchestrator execution
## Package skeleton
@@ -28,6 +29,7 @@ plugins/reporting-governance/
policy-evaluator.mjs
decision-runner.mjs
execute-governance-contract.mjs
runtime-integrated.mjs
adapters/
storage/
reference/
@@ -92,6 +94,7 @@ What is currently exposed from the root export:
- `evaluatePolicies(...)`
- `planDecisionExecution(...)`
- `executeGovernanceContract(...)`
- `executeRuntimeIntegratedGovernance(...)`
- package metadata helpers such as `packageName`
- package-owned adapter entrypoints and `runWatchdogChain(...)`
@@ -161,6 +164,7 @@ What this slice does:
5. validator rejects `artifact_roots` absolute paths, lexical escapes, and symlink escapes that resolve outside repo realpath boundary
6. adapter runtime binding can be instantiated from that contract in tests
7. orchestrator adapter can now bootstrap from package profile artifact input directly
8. `queueItems` now has two checks: load-time artifact validation and orchestrator use-time realpath recheck before runtime consumption
What this slice does **not** claim yet:
@@ -178,6 +182,7 @@ The current package now includes a small but runnable `core/` implementation:
- `src/core/policy-evaluator.mjs`
- `src/core/decision-runner.mjs`
- `src/core/execute-governance-contract.mjs`
- `src/core/runtime-integrated.mjs`
- `src/core/index.mjs`
Current package-core responsibilities:
@@ -190,6 +195,7 @@ Current package-core responsibilities:
- truthfully degrade unsupported enforcement paths based on the capability descriptor
- provide one minimal contract path from `capability descriptor -> policy decision -> execution planning`
- surface deployment binding metadata when caller passes a validated profile artifact
- optionally hand that deployment binding into the orchestrator adapter when caller explicitly supplies runtime execution inputs
Still **runtime-adapter responsibility** at this stage:
@@ -209,12 +215,14 @@ This slice now has one small but testable contract path:
2. policy evaluator emits a canonical decision from event/evidence/context
3. decision runner converts that decision into execution planning
4. validated profile artifact can supply deployment binding metadata
5. orchestrator adapter can consume profile artifact bindings and run one real runtime layer
6. the result declares:
5. runtime-integrated helper can take that binding and route it into the orchestrator adapter
6. orchestrator adapter consumes the same binding and runs one real runtime layer
7. the result declares:
- adapter-dispatch actions required
- package-core actions possible locally
- blocked mandatory actions when capability support is missing
- truthful delivery / receipt state
- runtime execution result when explicitly requested
This is intentionally **planning-level end-to-end plus one adapter bootstrap layer**, not full live inline interception.
It proves contract alignment without pretending all runtime enforcement is already extracted.

View File

@@ -14,6 +14,6 @@
"./adapters/orchestrator": "./src/adapters/orchestrator.mjs"
},
"scripts": {
"test": "node --test test/package-structure.test.mjs test/policy-evaluator.test.mjs test/compatibility-preflight.test.mjs test/profile-artifact.test.mjs test/decision-runner.test.mjs test/governance-contract.integration.test.mjs test/watchdog-chain.integration.test.mjs test/exports-boundary.integration.test.mjs"
"test": "node --test test/package-structure.test.mjs test/policy-evaluator.test.mjs test/compatibility-preflight.test.mjs test/profile-artifact.test.mjs test/decision-runner.test.mjs test/governance-contract.integration.test.mjs test/watchdog-chain.integration.test.mjs test/runtime-integrated.integration.test.mjs test/exports-boundary.integration.test.mjs"
}
}

View File

@@ -1,4 +1,5 @@
export { evaluatePolicyPack, evaluatePolicies } from './policy-evaluator.mjs';
export { planDecisionExecution } from './decision-runner.mjs';
export { executeGovernanceContract } from './execute-governance-contract.mjs';
export { executeRuntimeIntegratedGovernance } from './runtime-integrated.mjs';
export { runCompatibilityPreflight } from './compatibility-preflight.mjs';

View File

@@ -0,0 +1,52 @@
import { executeGovernanceContract } from './execute-governance-contract.mjs';
import { runOrchestratorAdapter } from '../adapters/orchestrator.mjs';
export function executeRuntimeIntegratedGovernance({
event,
evidence = [],
capabilityDescriptor = {},
policyPacks = [],
context = {},
profile = {},
packageVersion,
repoRootOverride,
runtime = null,
} = {}) {
const governance = executeGovernanceContract({
event,
evidence,
capabilityDescriptor,
policyPacks,
context,
profile,
packageVersion,
repoRootOverride,
});
const shouldRunOrchestrator = Boolean(
runtime
&& governance.preflight?.status === 'pass'
&& governance.deploymentBinding
&& governance.contract?.adapter_actions?.includes('notify_operator')
);
const runtimeExecution = shouldRunOrchestrator
? runOrchestratorAdapter({
profileArtifact: profile,
repoRootOverride,
...runtime,
})
: null;
return {
...governance,
runtimeExecution,
runtimeIntegration: {
attempted: shouldRunOrchestrator,
adapter: shouldRunOrchestrator ? 'orchestrator' : null,
reason: shouldRunOrchestrator
? 'deployment binding + notify_operator adapter action routed into orchestrator adapter'
: 'runtime execution not attempted',
},
};
}

View File

@@ -34,6 +34,7 @@ export {
evaluatePolicies,
planDecisionExecution,
executeGovernanceContract,
executeRuntimeIntegratedGovernance,
runCompatibilityPreflight,
} from './core/index.mjs';
export {

View File

@@ -69,6 +69,7 @@ test('package root export resolves public package surface only', () => {
hasRunWatchdogChain: typeof plugin.runWatchdogChain,
hasPlanDecisionExecution: typeof plugin.planDecisionExecution,
hasExecuteGovernanceContract: typeof plugin.executeGovernanceContract,
hasExecuteRuntimeIntegratedGovernance: typeof plugin.executeRuntimeIntegratedGovernance,
}));
`);
@@ -76,6 +77,7 @@ test('package root export resolves public package surface only', () => {
assert.equal(result.hasRunWatchdogChain, 'function');
assert.equal(result.hasPlanDecisionExecution, 'function');
assert.equal(result.hasExecuteGovernanceContract, 'function');
assert.equal(result.hasExecuteRuntimeIntegratedGovernance, 'function');
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
@@ -141,43 +143,47 @@ test('leaf subpath export resolves and can execute through injected runtime bind
const root = createFixtureRoot();
try {
installPackageAlias(root);
fs.mkdirSync(path.join(root, 'scripts'), { recursive: true });
fs.mkdirSync(path.join(root, 'events'), { recursive: true });
fs.mkdirSync(path.join(root, 'evidence'), { recursive: true });
fs.mkdirSync(path.join(root, 'queue'), { recursive: true });
const statePath = writeState(root);
const stubScriptPath = path.join(root, 'scripts', 'custom-watchdog.mjs');
fs.writeFileSync(stubScriptPath, `
process.stdout.write(JSON.stringify({
ok: true,
source: 'stub-watchdog',
argv: process.argv.slice(2),
}));
`, 'utf8');
fs.mkdirSync(path.join(root, 'evidence'), { recursive: true });
fs.mkdirSync(path.join(root, 'events'), { recursive: true });
fs.mkdirSync(path.join(root, 'queue'), { recursive: true });
fs.mkdirSync(path.join(root, 'spool'), { recursive: true });
fs.mkdirSync(path.join(root, 'receipts'), { recursive: true });
const result = runJsonEval(root, `
import { runWatchdogAdapter } from '@openclaw/plugin-reporting-governance/adapters/watchdog';
const out = runWatchdogAdapter({
import { runOrchestratorAdapter } from '@openclaw/plugin-reporting-governance/adapters/orchestrator';
const payload = runOrchestratorAdapter({
runtimeBinding: {
cwd: ${JSON.stringify(path.resolve(packageRoot, '..', '..'))},
scripts: {
orchestrator: ${JSON.stringify(path.resolve(packageRoot, '..', '..', 'scripts', 'watchdog_auto_notify_orchestrator.mjs'))},
watchdog: ${JSON.stringify(path.resolve(packageRoot, '..', '..', 'scripts', 'long_task_watchdog.mjs'))},
dispatcher: ${JSON.stringify(path.resolve(packageRoot, '..', '..', 'scripts', 'operator_notify_dispatcher.mjs'))},
bridgeSupervisor: ${JSON.stringify(path.resolve(packageRoot, '..', '..', 'scripts', 'operator_notify_bridge_supervisor.mjs'))},
},
},
state: ${JSON.stringify(statePath)},
evidenceDir: ${JSON.stringify(path.join(root, 'evidence'))},
eventDir: ${JSON.stringify(path.join(root, 'events'))},
notificationDir: ${JSON.stringify(path.join(root, 'queue'))},
runtimeBinding: {
cwd: ${JSON.stringify(root)},
scripts: {
watchdog: ${JSON.stringify(stubScriptPath)},
},
},
queueDir: ${JSON.stringify(path.join(root, 'queue'))},
spoolDir: ${JSON.stringify(path.join(root, 'spool'))},
receiptDir: ${JSON.stringify(path.join(root, 'receipts'))},
writeState: true,
dryRun: true,
now: '2026-05-07T08:20:00.000Z',
});
process.stdout.write(JSON.stringify(out));
`);
process.stdout.write(JSON.stringify({
ok: payload.ok,
dispatchedCount: payload.result.dispatcher.dispatchedCount,
pendingCount: payload.result.supervisor.pendingCount,
}));
`, {
RG_REPO_ROOT: path.resolve(packageRoot, '..', '..'),
});
assert.equal(result.ok, true);
assert.equal(result.source, 'stub-watchdog');
assert.ok(result.argv.includes('--state'));
assert.ok(result.argv.includes(path.resolve(statePath)));
assert.equal(result.dispatchedCount, 1);
assert.equal(result.pendingCount, 1);
} finally {
fs.rmSync(root, { recursive: true, force: true });
}

View File

@@ -0,0 +1,207 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';
import { executeRuntimeIntegratedGovernance } from '../src/index.mjs';
import capabilityDescriptor from '../capabilities/openclaw-watchdog-reference.json' with { type: 'json' };
const packageRoot = path.resolve(import.meta.dirname, '..');
const repoRoot = path.resolve(packageRoot, '..', '..');
const noSilencePack = {
metadata: { id: 'no-silence', severity_default: 'high' },
spec: {
evaluation_mode: 'any_rule_match',
rules: [
{
id: 'no-silence.missed-checkpoint',
title: 'Missed checkpoint requires visible recovery',
triggers: { event_types: ['silence_timeout'] },
conditions: {
all: [
{ fact: 'checkpoint.is_overdue', equals: true }
]
},
decision_output: {
decision: 'force_checkpoint',
severity: 'high',
reason: 'checkpoint overdue triggered forced operator-visible recovery',
required_actions: [
{ action: 'notify_operator', target: 'operator_channel', mandatory: true },
{ action: 'emit_event', target: 'event_stream', mandatory: true }
],
operator_notice: {
required: true,
channel: 'telegram',
urgency: 'high',
message: 'Required update: checkpoint overdue.',
deadline: '2026-01-01T00:00:00.000Z'
}
}
}
]
}
};
const strictProfileArtifact = {
kind: 'DeploymentProfileArtifact',
apiVersion: 'reporting-governance/v1alpha1',
metadata: {
id: 'strict-manager-mode',
runtime: 'openclaw',
compatibility_mode: 'strict_envelope',
},
spec: {
package: { pluginVersion: '0.1.0-mainline' },
policies: {
overrides: {
checkpoints: { overdueAction: 'force_checkpoint' }
}
},
notifications: {
operatorVisibleRecoveryRequired: true
},
bindings: {
runtime: 'openclaw',
entrypoint: 'scripts/watchdog_auto_notify_orchestrator.mjs',
scripts: {
watchdog: 'scripts/long_task_watchdog.mjs',
dispatcher: 'scripts/operator_notify_dispatcher.mjs',
bridgeSupervisor: 'scripts/operator_notify_bridge_supervisor.mjs',
senderBinding: 'scripts/operator_notify_sender_binding.mjs',
orchestrator: 'scripts/watchdog_auto_notify_orchestrator.mjs'
},
artifact_roots: {
queueItems: 'state/operator-notify-queue'
}
}
},
capability_expectations: {
required: [
'emit_canonical_events',
'evaluate_watchdog_overdue',
'create_queue_items',
'create_spool_handoff',
'write_bridge_receipts'
],
preferred: ['direct_sender_binding', 'final_delivery_ack']
}
};
function createFixtureRoot() {
return fs.mkdtempSync(path.join(os.tmpdir(), 'reporting-governance-runtime-integrated-'));
}
function mkdirs(root, names) {
for (const name of names) {
fs.mkdirSync(path.join(root, name), { recursive: true });
}
}
function writeState(root) {
const statePath = path.join(root, 'watchdog-state.json');
fs.writeFileSync(statePath, `${JSON.stringify({
version: 1,
watchdogs: [
{
id: 'reporting-governance-plugin-watchdog',
task: 'reporting-governance plugin spec development',
status: 'active',
ownerSessionKey: 'agent:coder:main',
reportChannel: 'telegram',
reportTarget: '864811879',
intervalMinutes: 10,
lastMilestoneAt: '2026-05-07T08:00:00.000Z',
lastAlertAt: null,
},
],
}, null, 2)}\n`, 'utf8');
return statePath;
}
function readSingleJson(dirPath) {
const files = fs.readdirSync(dirPath).filter((name) => name.endsWith('.json')).sort();
assert.equal(files.length, 1, `expected exactly one json file in ${dirPath}`);
return JSON.parse(fs.readFileSync(path.join(dirPath, files[0]), 'utf8'));
}
test('runtime-integrated path wires executeGovernanceContract deployment binding into orchestrator execution', () => {
const root = createFixtureRoot();
try {
mkdirs(root, ['evidence', 'events', 'queue', 'spool', 'receipts']);
const statePath = writeState(root);
const result = executeRuntimeIntegratedGovernance({
event: {
type: 'silence_timeout',
payload: {
checkpoint_overdue: true,
}
},
evidence: [
{ id: 'ev-watchdog', quality: 'moderate', is_new: true }
],
capabilityDescriptor,
policyPacks: [noSilencePack],
context: {
signals: ['checkpoint_overdue'],
},
profile: strictProfileArtifact,
packageVersion: '0.1.0-mainline',
repoRootOverride: repoRoot,
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, 'pass');
assert.equal(result.contract.decision, 'force_checkpoint');
assert.equal(result.runtimeIntegration.attempted, true);
assert.equal(result.runtimeIntegration.adapter, 'orchestrator');
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 path stays planning-only when no runtime payload is supplied', () => {
const result = executeRuntimeIntegratedGovernance({
event: {
type: 'silence_timeout',
payload: {
checkpoint_overdue: true,
}
},
capabilityDescriptor,
policyPacks: [noSilencePack],
context: {
signals: ['checkpoint_overdue'],
},
profile: strictProfileArtifact,
packageVersion: '0.1.0-mainline',
repoRootOverride: repoRoot,
});
assert.equal(result.preflight.status, 'pass');
assert.equal(result.runtimeIntegration.attempted, false);
assert.equal(result.runtimeExecution, null);
});