feat: wire minimal governance contract path
This commit is contained in:
@@ -24,6 +24,7 @@ plugins/reporting-governance/
|
|||||||
index.mjs
|
index.mjs
|
||||||
policy-evaluator.mjs
|
policy-evaluator.mjs
|
||||||
decision-runner.mjs
|
decision-runner.mjs
|
||||||
|
execute-governance-contract.mjs
|
||||||
adapters/
|
adapters/
|
||||||
storage/
|
storage/
|
||||||
reference/
|
reference/
|
||||||
@@ -68,6 +69,42 @@ Reference runtime compositions and migration notes.
|
|||||||
|
|
||||||
**The watchdog reference runtime composition belongs here**, as a reference implementation for OpenClaw rather than as package core logic.
|
**The watchdog reference runtime composition belongs here**, as a reference implementation for OpenClaw rather than as package core logic.
|
||||||
|
|
||||||
|
## Public surface and compatibility
|
||||||
|
|
||||||
|
Current **public package surface** is intentionally narrow:
|
||||||
|
|
||||||
|
- root export: `@openclaw/plugin-reporting-governance`
|
||||||
|
- adapter exports:
|
||||||
|
- `@openclaw/plugin-reporting-governance/adapters`
|
||||||
|
- `@openclaw/plugin-reporting-governance/adapters/watchdog`
|
||||||
|
- `@openclaw/plugin-reporting-governance/adapters/dispatcher`
|
||||||
|
- `@openclaw/plugin-reporting-governance/adapters/bridge-supervisor`
|
||||||
|
- `@openclaw/plugin-reporting-governance/adapters/sender-binding`
|
||||||
|
- `@openclaw/plugin-reporting-governance/adapters/orchestrator`
|
||||||
|
|
||||||
|
What is currently exposed from the root export:
|
||||||
|
|
||||||
|
- `evaluatePolicyPack(...)`
|
||||||
|
- `evaluatePolicies(...)`
|
||||||
|
- `planDecisionExecution(...)`
|
||||||
|
- `executeGovernanceContract(...)`
|
||||||
|
- package metadata helpers such as `packageName`
|
||||||
|
- package-owned adapter entrypoints and `runWatchdogChain(...)`
|
||||||
|
|
||||||
|
Compatibility posture for this slice:
|
||||||
|
|
||||||
|
- `0.1.0-mainline` should be treated as **pre-1.0, surface-tightening phase**.
|
||||||
|
- Deep imports into `src/` are **not supported API** even if files exist in-repo.
|
||||||
|
- Tests now explicitly enforce that private paths like `src/adapters/runtime-binding.mjs` stay outside `exports`.
|
||||||
|
- Adding a symbol to a file under `src/` does **not** mean it is public unless wired through package `exports`.
|
||||||
|
- Future tightening of root/adapters exports may still be a breaking change until a stable `1.0` surface is declared.
|
||||||
|
|
||||||
|
Practical migration rule:
|
||||||
|
|
||||||
|
- depend on package root exports or declared adapter subpaths only
|
||||||
|
- do not couple runtime integrations to repo-private file paths
|
||||||
|
- treat capability descriptors and schemas as package artifacts, but not as guaranteed JS import entrypoints unless exported later
|
||||||
|
|
||||||
## Current reference composition
|
## Current reference composition
|
||||||
|
|
||||||
The current reference composition is the OpenClaw watchdog chain:
|
The current reference composition is the OpenClaw watchdog chain:
|
||||||
@@ -93,6 +130,7 @@ The current package now includes a small but runnable `core/` implementation:
|
|||||||
|
|
||||||
- `src/core/policy-evaluator.mjs`
|
- `src/core/policy-evaluator.mjs`
|
||||||
- `src/core/decision-runner.mjs`
|
- `src/core/decision-runner.mjs`
|
||||||
|
- `src/core/execute-governance-contract.mjs`
|
||||||
- `src/core/index.mjs`
|
- `src/core/index.mjs`
|
||||||
|
|
||||||
Current package-core responsibilities:
|
Current package-core responsibilities:
|
||||||
@@ -103,6 +141,7 @@ Current package-core responsibilities:
|
|||||||
- choose the highest-precedence decision when multiple rules match
|
- choose the highest-precedence decision when multiple rules match
|
||||||
- convert a canonical decision into an execution plan, enforcement intent, and receipt skeleton
|
- convert a canonical decision into an execution plan, enforcement intent, and receipt skeleton
|
||||||
- truthfully degrade unsupported enforcement paths based on the capability descriptor
|
- truthfully degrade unsupported enforcement paths based on the capability descriptor
|
||||||
|
- provide one minimal contract path from `capability descriptor -> policy decision -> execution planning`
|
||||||
|
|
||||||
Still **runtime-adapter responsibility** at this stage:
|
Still **runtime-adapter responsibility** at this stage:
|
||||||
|
|
||||||
@@ -114,6 +153,22 @@ Still **runtime-adapter responsibility** at this stage:
|
|||||||
|
|
||||||
This means `core/` now owns evaluation and planning semantics, while adapters still own actual enforcement side effects.
|
This means `core/` now owns evaluation and planning semantics, while adapters still own actual enforcement side effects.
|
||||||
|
|
||||||
|
## Minimal end-to-end contract slice now included
|
||||||
|
|
||||||
|
This slice now has one small but testable contract path:
|
||||||
|
|
||||||
|
1. capability descriptor advertises real enforcement support
|
||||||
|
2. policy evaluator emits a canonical decision from event/evidence/context
|
||||||
|
3. decision runner converts that decision into execution planning
|
||||||
|
4. the result declares:
|
||||||
|
- adapter-dispatch actions required
|
||||||
|
- package-core actions possible locally
|
||||||
|
- blocked mandatory actions when capability support is missing
|
||||||
|
- truthful delivery / receipt state
|
||||||
|
|
||||||
|
This is intentionally **planning-level end-to-end**, not full live inline interception.
|
||||||
|
It proves contract alignment without pretending all runtime enforcement is already extracted.
|
||||||
|
|
||||||
## Not yet included
|
## Not yet included
|
||||||
|
|
||||||
This package still does **not** claim full implementation of:
|
This package still does **not** claim full implementation of:
|
||||||
@@ -124,4 +179,4 @@ This package still does **not** claim full implementation of:
|
|||||||
- complete rewrite / placeholder / review / status-downgrade adapter execution
|
- complete rewrite / placeholder / review / status-downgrade adapter execution
|
||||||
- non-watchdog full runtime governance interception
|
- non-watchdog full runtime governance interception
|
||||||
|
|
||||||
It now provides the first package-mainline evaluator / decision-runner core, but the remaining enforcement surface is still intentionally honest about adapter gaps.
|
It now provides the first package-mainline evaluator / decision-runner core, plus a minimal end-to-end contract proof, but the remaining enforcement surface is still intentionally honest about adapter gaps.
|
||||||
|
|||||||
@@ -14,6 +14,6 @@
|
|||||||
"./adapters/orchestrator": "./src/adapters/orchestrator.mjs"
|
"./adapters/orchestrator": "./src/adapters/orchestrator.mjs"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "node --test test/package-structure.test.mjs test/policy-evaluator.test.mjs test/decision-runner.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/decision-runner.test.mjs test/governance-contract.integration.test.mjs test/watchdog-chain.integration.test.mjs test/exports-boundary.integration.test.mjs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { evaluatePolicies } from './policy-evaluator.mjs';
|
||||||
|
import { planDecisionExecution } from './decision-runner.mjs';
|
||||||
|
|
||||||
|
export function executeGovernanceContract({
|
||||||
|
event,
|
||||||
|
evidence = [],
|
||||||
|
capabilityDescriptor = {},
|
||||||
|
policyPacks = [],
|
||||||
|
context = {},
|
||||||
|
} = {}) {
|
||||||
|
const evaluation = evaluatePolicies({
|
||||||
|
event,
|
||||||
|
evidence,
|
||||||
|
capabilityDescriptor,
|
||||||
|
policyPacks,
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
|
||||||
|
const planning = planDecisionExecution({
|
||||||
|
decision: evaluation.decision,
|
||||||
|
capabilityDescriptor,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
evaluation,
|
||||||
|
planning,
|
||||||
|
contract: {
|
||||||
|
runtime: capabilityDescriptor?.metadata?.id ?? capabilityDescriptor?.runtime?.name ?? 'unknown-runtime',
|
||||||
|
policy_id: evaluation.decision.policy_id,
|
||||||
|
decision: evaluation.decision.decision,
|
||||||
|
adapter_actions: planning.enforcement_intent.runtime_adapter_required,
|
||||||
|
package_actions: planning.enforcement_intent.package_core_actions,
|
||||||
|
blocked_actions: planning.enforcement_intent.blocked_actions.map((action) => action.action),
|
||||||
|
delivery_state: planning.receipt.delivery_state,
|
||||||
|
receipt_status: planning.receipt.status,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
export { evaluatePolicyPack, evaluatePolicies } from './policy-evaluator.mjs';
|
export { evaluatePolicyPack, evaluatePolicies } from './policy-evaluator.mjs';
|
||||||
export { planDecisionExecution } from './decision-runner.mjs';
|
export { planDecisionExecution } from './decision-runner.mjs';
|
||||||
|
export { executeGovernanceContract } from './execute-governance-contract.mjs';
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export const packageBoundaries = {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
export { evaluatePolicyPack, evaluatePolicies, planDecisionExecution } from './core/index.mjs';
|
export { evaluatePolicyPack, evaluatePolicies, planDecisionExecution, executeGovernanceContract } from './core/index.mjs';
|
||||||
export {
|
export {
|
||||||
createRuntimeBinding,
|
createRuntimeBinding,
|
||||||
runWatchdogAdapter,
|
runWatchdogAdapter,
|
||||||
|
|||||||
@@ -68,12 +68,14 @@ test('package root export resolves public package surface only', () => {
|
|||||||
packageName: plugin.packageName,
|
packageName: plugin.packageName,
|
||||||
hasRunWatchdogChain: typeof plugin.runWatchdogChain,
|
hasRunWatchdogChain: typeof plugin.runWatchdogChain,
|
||||||
hasPlanDecisionExecution: typeof plugin.planDecisionExecution,
|
hasPlanDecisionExecution: typeof plugin.planDecisionExecution,
|
||||||
|
hasExecuteGovernanceContract: typeof plugin.executeGovernanceContract,
|
||||||
}));
|
}));
|
||||||
`);
|
`);
|
||||||
|
|
||||||
assert.equal(result.packageName, '@openclaw/plugin-reporting-governance');
|
assert.equal(result.packageName, '@openclaw/plugin-reporting-governance');
|
||||||
assert.equal(result.hasRunWatchdogChain, 'function');
|
assert.equal(result.hasRunWatchdogChain, 'function');
|
||||||
assert.equal(result.hasPlanDecisionExecution, 'function');
|
assert.equal(result.hasPlanDecisionExecution, 'function');
|
||||||
|
assert.equal(result.hasExecuteGovernanceContract, 'function');
|
||||||
} finally {
|
} finally {
|
||||||
fs.rmSync(root, { recursive: true, force: true });
|
fs.rmSync(root, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
|
||||||
|
import { executeGovernanceContract } from '../src/core/execute-governance-contract.mjs';
|
||||||
|
import capabilityDescriptor from '../capabilities/openclaw-watchdog-reference.json' with { type: 'json' };
|
||||||
|
|
||||||
|
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',
|
||||||
|
intent: 'Prevent overdue checkpoints from becoming invisible.',
|
||||||
|
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',
|
||||||
|
suggested_status: 'in_progress',
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
operator_message_templates: {
|
||||||
|
checkpoint_forced: 'Required update: task exceeded allowed silence window.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test('capability descriptor -> policy evaluation -> decision planning yields adapter-compatible contract', () => {
|
||||||
|
const result = executeGovernanceContract({
|
||||||
|
event: {
|
||||||
|
type: 'silence_timeout',
|
||||||
|
payload: {
|
||||||
|
checkpoint_overdue: true,
|
||||||
|
result_available: true,
|
||||||
|
result_forwarded: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
evidence: [
|
||||||
|
{ id: 'ev-watchdog', quality: 'moderate', is_new: true }
|
||||||
|
],
|
||||||
|
capabilityDescriptor,
|
||||||
|
policyPacks: [noSilencePack],
|
||||||
|
context: {
|
||||||
|
signals: ['checkpoint_overdue'],
|
||||||
|
operator_context: { report_anchor_present: true }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(result.evaluation.decision.decision, 'force_checkpoint');
|
||||||
|
assert.equal(result.planning.receipt.delivery_state, 'pending_external_send');
|
||||||
|
assert.deepEqual(result.contract.adapter_actions, ['notify_operator']);
|
||||||
|
assert.deepEqual(result.contract.package_actions, ['emit_event']);
|
||||||
|
assert.deepEqual(result.contract.blocked_actions, []);
|
||||||
|
assert.equal(result.contract.receipt_status, 'planned');
|
||||||
|
assert.equal(result.contract.runtime, 'openclaw-watchdog-reference');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('contract truthfully degrades when capability descriptor cannot satisfy mandatory action', () => {
|
||||||
|
const limitedDescriptor = {
|
||||||
|
...capabilityDescriptor,
|
||||||
|
metadata: {
|
||||||
|
...capabilityDescriptor.metadata,
|
||||||
|
id: 'limited-openclaw-watchdog-reference'
|
||||||
|
},
|
||||||
|
capabilities: {
|
||||||
|
...capabilityDescriptor.capabilities,
|
||||||
|
enforcement: {
|
||||||
|
...capabilityDescriptor.capabilities.enforcement,
|
||||||
|
force_checkpoint: { supported: false, level: 'none' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = executeGovernanceContract({
|
||||||
|
event: {
|
||||||
|
type: 'silence_timeout',
|
||||||
|
payload: { checkpoint_overdue: true }
|
||||||
|
},
|
||||||
|
capabilityDescriptor: limitedDescriptor,
|
||||||
|
policyPacks: [noSilencePack],
|
||||||
|
context: {
|
||||||
|
signals: ['checkpoint_overdue']
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(result.evaluation.decision.decision, 'force_checkpoint');
|
||||||
|
assert.deepEqual(result.contract.adapter_actions, []);
|
||||||
|
assert.deepEqual(result.contract.blocked_actions, ['notify_operator']);
|
||||||
|
assert.equal(result.contract.receipt_status, 'degraded');
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user