feat(reporting-governance): wire profile artifacts into contract and orchestrator

This commit is contained in:
Eve
2026-05-08 10:16:29 +08:00
parent 6366f70491
commit 3223feba93
8 changed files with 247 additions and 35 deletions

View File

@@ -1,9 +1,13 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import path from 'node:path';
import { executeGovernanceContract, runCompatibilityPreflight } from '../src/core/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: {
@@ -294,3 +298,53 @@ test('schema/version mismatch blocks contract before any runnable plan is produc
assert.ok(result.planning.receipt.notes.some((note) => note.includes('schema mismatch: decision_schema')));
assert.ok(result.planning.receipt.notes.some((note) => note.includes('plugin version 0.1.0-mainline is not declared compatible')));
});
test('executeGovernanceContract exposes deployment binding when profile artifact bindings are provided', () => {
const profileArtifact = {
...strictProfile,
kind: 'DeploymentProfileArtifact',
apiVersion: 'reporting-governance/v1alpha1',
metadata: {
...strictProfile.metadata,
runtime: 'openclaw',
compatibility_mode: 'strict_envelope',
},
spec: {
...strictProfile.spec,
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'
}
}
}
};
const result = executeGovernanceContract({
event: {
type: 'silence_timeout',
payload: { checkpoint_overdue: true }
},
capabilityDescriptor,
policyPacks: [noSilencePack],
context: {
signals: ['checkpoint_overdue']
},
profile: profileArtifact,
packageVersion: '0.1.0-mainline',
repoRootOverride: repoRoot,
});
assert.equal(result.preflight.status, 'pass');
assert.equal(result.deploymentBinding.entrypoint, path.resolve(repoRoot, 'scripts/watchdog_auto_notify_orchestrator.mjs'));
assert.equal(result.deploymentBinding.scripts.dispatcher, path.resolve(repoRoot, 'scripts/operator_notify_dispatcher.mjs'));
assert.equal(result.deploymentBinding.artifactRoots.queueItems, path.resolve(repoRoot, 'state/operator-notify-queue'));
});

View File

@@ -3,7 +3,11 @@ import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import { loadDeploymentProfileArtifact, createDeploymentBindingContract } from '../src/storage/profile-artifact.mjs';
import {
loadDeploymentProfileArtifact,
createDeploymentBindingContract,
validateDeploymentProfileArtifact,
} from '../src/storage/profile-artifact.mjs';
import { createRuntimeBinding } from '../src/adapters/index.mjs';
const packageRoot = path.resolve(import.meta.dirname, '..');
@@ -45,3 +49,58 @@ test('runtime binding can be instantiated from profile artifact binding contract
assert.equal(runtimeBinding.scripts.bridgeSupervisor, contract.scripts.bridgeSupervisor);
assert.equal(runtimeBinding.scripts.senderBinding, contract.scripts.senderBinding);
});
test('deployment profile artifact validation fails closed on boundary drift', () => {
assert.throws(
() => validateDeploymentProfileArtifact({}),
/kind must be DeploymentProfileArtifact/
);
assert.throws(
() => validateDeploymentProfileArtifact({
kind: 'DeploymentProfileArtifact',
apiVersion: 'reporting-governance/v1alpha1',
spec: {
package: { pluginVersion: '0.1.0-mainline' },
bindings: {
entrypoint: '',
scripts: { watchdog: 'scripts/long_task_watchdog.mjs' },
artifact_roots: { queueItems: 'state/operator-notify-queue' },
},
},
}),
/spec\.bindings\.entrypoint must be a non-empty string/
);
assert.throws(
() => validateDeploymentProfileArtifact({
kind: 'DeploymentProfileArtifact',
apiVersion: 'reporting-governance/v1alpha1',
spec: {
package: { pluginVersion: '' },
bindings: {
entrypoint: 'scripts/watchdog_auto_notify_orchestrator.mjs',
scripts: { watchdog: '' },
artifact_roots: { queueItems: 'state/operator-notify-queue' },
},
},
}),
/spec\.package\.pluginVersion must be a non-empty string/
);
assert.throws(
() => validateDeploymentProfileArtifact({
kind: 'DeploymentProfileArtifact',
apiVersion: 'reporting-governance/v1alpha1',
spec: {
package: { pluginVersion: '0.1.0-mainline' },
bindings: {
entrypoint: 'scripts/watchdog_auto_notify_orchestrator.mjs',
scripts: [],
artifact_roots: { queueItems: 'state/operator-notify-queue' },
},
},
}),
/spec\.bindings\.scripts must be an object record/
);
});

View File

@@ -9,6 +9,8 @@ import {
runWatchdogChain,
} from '../src/index.mjs';
const packageRoot = path.resolve(import.meta.dirname, '..');
function createFixtureRoot() {
return fs.mkdtempSync(path.join(os.tmpdir(), 'reporting-governance-plugin-'));
}
@@ -115,3 +117,35 @@ test('dry-run path produces verifiable pending receipt via package adapter', ()
fs.rmSync(root, { recursive: true, force: true });
}
});
test('orchestrator adapter can bootstrap from profile artifact loader path', () => {
const root = createFixtureRoot();
try {
mkdirs(root, ['evidence', 'events', 'queue', 'spool', 'receipts']);
const statePath = writeState(root);
const result = runOrchestratorAdapter({
profileId: 'strict-manager-mode',
repoRootOverride: path.resolve(packageRoot, '..', '..'),
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.ok, true);
assert.equal(result.result.watchdog.notificationCount, 1);
assert.equal(result.result.dispatcher.dispatchedCount, 1);
assert.equal(result.result.supervisor.pendingCount, 1);
const receipt = readSingleJson(path.join(root, 'receipts')).payload;
assert.equal(receipt.state, 'pending_external_send');
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});