feat(reporting-governance): add profile artifact binding slice

This commit is contained in:
Eve
2026-05-08 10:07:26 +08:00
parent 000f6b6a8b
commit 6366f70491
15 changed files with 695 additions and 6 deletions

View File

@@ -177,7 +177,59 @@ This matters architecturally because it proves the plugin can:
- preserve honest delivery semantics instead of collapsing `dispatched` into false success
- provide a migration path from repo scripts toward package-level adapters and deployment profiles
The mainline next step is therefore not “more watchdog patching,” but formalizing this runtime composition inside the plugins adapter and capability model so future package extraction has a stable target.
## Compatibility envelope and legacy compatibility mode
The architecture now draws a hard line between two caller postures:
### Compatibility envelope
A caller is inside the compatibility envelope once it supplies either:
- a deployment profile / profile artifact, or
- a package version pin
Inside this envelope, runtime compatibility is enforced against the descriptor as a truth contract:
- canonical schema paths must match
- requested plugin version must be declared compatible
- required capability expectations must be satisfied
- requested actions must be supportable or honestly degraded
If not, the system fails closed before producing a runnable enforcement contract.
### Legacy compatibility mode
Legacy compatibility mode exists only so older callers that still invoke package core without profile/package metadata do not break immediately.
Behavior in this mode:
- no version pin is assumed
- schema mismatch is surfaced in `schema_checks` but does not hard-fail by itself
- preflight records migration debt as notes
- truth semantics for actual planning are still preserved
This is a migration concession, not a long-term steady state.
New callers should move to profile/package-backed invocation.
## Minimal package profile artifact trajectory
Architecture is also now advanced one notch from “profiles are external YAML docs” toward “profiles are package artifacts with a loader boundary”.
Current minimal slice:
- package-owned profile artifact snapshot: `plugins/reporting-governance/profiles/strict-manager-mode.profile.json`
- package loader: `src/storage/profile-artifact.mjs#loadDeploymentProfileArtifact(...)`
- binding contract projector: `src/storage/profile-artifact.mjs#createDeploymentBindingContract(...)`
Architectural meaning:
- package can carry one portable profile artifact under its own boundary
- storage layer owns loading/package-artifact interpretation
- runtime binding can be derived from the artifact rather than hardcoded entirely in docs
- tests prove the artifact resolves into concrete script and runtime-artifact paths
This is intentionally still a **minimal verifiable slice**, not the full deployment system.
It proves the package boundary can own profile artifacts and bind them into runtime execution inputs.
Primary follow-on specs:

View File

@@ -9,6 +9,7 @@ Current purpose:
- fix boundaries between `core/`, `adapters/`, `storage/`, and reference implementations
- prepare the next implementation round for evaluator / decision-runner extraction
- 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
## Package skeleton
@@ -17,6 +18,7 @@ plugins/reporting-governance/
package.json
README.md
capabilities/
profiles/
docs/
examples/
src/
@@ -62,6 +64,7 @@ Durable I/O contracts for governance artifacts:
- queue items
- spool artifacts
- receipts
- decision/profile/package artifacts
- future decisions / audit manifests
### `src/reference/`
@@ -99,6 +102,23 @@ Compatibility posture for this slice:
- 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.
### Compatibility envelope vs legacy compatibility mode
This slice now makes the boundary explicit:
- **compatibility envelope present** = caller provides a deployment profile and/or package version pin, so `runCompatibilityPreflight(...)` must enforce canonical schema paths, declared plugin compatibility, required expectations, and action support **fail-closed**.
- **legacy compatibility mode** = caller omits profile + package version entirely, so preflight keeps old call sites alive, records the missing version pin as a note, and does **not** fail only because descriptor schema/version metadata drifted.
Hard rule:
- legacy mode is a caller-compatibility concession, **not** a relaxed truth model.
- once any profile/package compatibility envelope is supplied, schema mismatch becomes blocking again.
Practical migration rule:
- new integrations should always send a profile artifact or package version pin.
- old integrations may temporarily call without one, but should treat returned notes as migration debt.
Practical migration rule:
- depend on package root exports or declared adapter subpaths only
@@ -117,6 +137,7 @@ Package-home documentation:
- `src/reference/openclaw-watchdog-chain.md`
- `capabilities/openclaw-watchdog-reference.json`
- `profiles/strict-manager-mode.profile.json`
Mainline background specs remain in:
@@ -124,6 +145,38 @@ Mainline background specs remain in:
- `docs/specs/reporting-governance-adapter-interface.md`
- `docs/specs/reporting-governance-deployment-model.md`
## Minimal profile artifact / loader / binding contract slice
This round adds one small but real package artifact path:
- package artifact: `profiles/strict-manager-mode.profile.json`
- loader: `src/storage/profile-artifact.mjs#loadDeploymentProfileArtifact(...)`
- binding contract: `src/storage/profile-artifact.mjs#createDeploymentBindingContract(...)`
What this slice does:
1. package ships a profile artifact snapshot under package boundary
2. loader resolves that artifact from package-local path
3. binding contract translates profile-declared script/artifact roots into concrete repo/runtime paths
4. adapter runtime binding can be instantiated from that contract in tests
What this slice does **not** claim yet:
- full profile schema validation pipeline
- automatic YAML -> artifact generation
- generalized multi-profile packaging
- production deployment installer
It is intentionally the smallest verifiable step that proves package profile artifacts are executable inputs rather than documentation only.
## Current reference composition
The current reference composition is the OpenClaw watchdog chain:
```text
watchdog -> queue -> dispatcher -> bridge -> sender binding -> acked|blocked|pending_external_send
```
## Minimal evaluator / decision runner now included
The current package now includes a small but runnable `core/` implementation:
@@ -179,4 +232,4 @@ This package still does **not** claim full implementation of:
- complete rewrite / placeholder / review / status-downgrade adapter execution
- non-watchdog full runtime governance interception
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.
It now provides the first package-mainline evaluator / decision-runner core, a compatibility-envelope boundary, and a minimal package profile artifact/binding slice, but the remaining enforcement surface is still intentionally honest about adapter gaps.

View File

@@ -0,0 +1,258 @@
{
"$schema": "../../../schemas/reporting-governance/capability-descriptor.schema.json",
"apiVersion": "reporting-governance/v1alpha1",
"kind": "AdapterCapabilities",
"metadata": {
"id": "openclaw-watchdog-reference",
"title": "OpenClaw Watchdog Reference Runtime Composition",
"version": "1.0.0",
"owner": "openclaw",
"summary": "Reference capability descriptor for the OpenClaw watchdog -> queue -> dispatcher -> bridge -> sender-binding composition.",
"tags": [
"openclaw",
"watchdog",
"reference-runtime",
"queue-bridge-sender"
]
},
"runtime": {
"name": "openclaw",
"mode": "hybrid",
"surfaces": [
"watchdog",
"queue",
"dispatcher",
"bridge",
"sender_binding",
"orchestrator",
"scheduler",
"storage"
],
"entrypoint": "scripts/watchdog_auto_notify_orchestrator.mjs"
},
"compatibility": {
"plugin_spec_versions": [
"0.1.0-mainline"
],
"event_schema": "schemas/reporting-governance/event-envelope.schema.json",
"evidence_schema": "schemas/reporting-governance/evidence.schema.json",
"decision_schema": "schemas/reporting-governance/decision.schema.json",
"capabilities_schema": "schemas/reporting-governance/capability-descriptor.schema.json"
},
"capabilities": {
"observation": {
"task_lifecycle": {
"supported": true,
"level": "partial",
"notes": [
"Current reference path is strongest for overdue/watchdog-related task state rather than all inline lifecycle hooks."
]
},
"subagent_lifecycle": {
"supported": true,
"level": "partial",
"notes": [
"Indirectly supported through watchdog/governor state and runtime artifacts, not yet a full dedicated package module."
]
},
"checkpoint_obligations": {
"supported": true,
"level": "partial",
"notes": [
"Current reference composition enforces overdue visibility but not yet all checkpoint-shape checks inline."
]
},
"outgoing_report_attempts": {
"supported": true,
"level": "partial",
"notes": [
"Visible in notification path artifacts, but not yet generalized as a full package-wide hook surface."
]
},
"watchdog_state": {
"supported": true,
"level": "full"
},
"queue_spool_receipts": {
"supported": true,
"level": "full"
}
},
"normalization": {
"canonical_events": {
"supported": true,
"level": "full"
},
"canonical_evidence": {
"supported": true,
"level": "partial",
"notes": [
"Watchdog evidence and delivery receipts are strong; broader non-watchdog evidence shaping is still package-next work."
]
},
"decision_inputs": {
"supported": true,
"level": "partial",
"notes": [
"Enough for watchdog-driven enforcement; not yet a generalized evaluator package input pipeline."
]
},
"correlation_propagation": {
"supported": true,
"level": "partial",
"notes": [
"Correlation is preserved across the watchdog queue/bridge chain where available."
]
}
},
"enforcement": {
"block_transition": {
"supported": true,
"level": "partial",
"notes": [
"Blocking exists in queue/dispatch/send paths, but not yet as a universal inline transition gate."
]
},
"rewrite_message": {
"supported": false,
"level": "none",
"notes": [
"Not part of the reference watchdog composition yet."
]
},
"annotate_placeholder": {
"supported": false,
"level": "none"
},
"force_checkpoint": {
"supported": true,
"level": "partial",
"notes": [
"The composition can force operator-visible recovery/notice, but not yet a generalized checkpoint runner."
]
},
"request_review": {
"supported": false,
"level": "none"
},
"downgrade_status": {
"supported": false,
"level": "none"
},
"escalate": {
"supported": true,
"level": "full"
}
},
"notification_path": {
"queue_items": {
"supported": true,
"level": "full"
},
"spool_handoff": {
"supported": true,
"level": "full"
},
"sender_binding": {
"supported": true,
"level": "full"
},
"direct_send": {
"supported": true,
"level": "partial",
"notes": [
"Supported only when the deployment provides a working sender runtime such as openclaw-cli."
]
},
"receipts": {
"supported": true,
"level": "full"
},
"final_delivery_proof": {
"supported": true,
"level": "partial",
"notes": [
"Ack proof exists only when sender runtime returns proven sent state."
]
},
"truth_model": {
"delivery_states": [
"prepared",
"queued",
"dispatched",
"pending_external_send",
"acked",
"blocked"
],
"ack_requires_proven_send": true,
"pending_external_send_supported": true
}
},
"watchdog": {
"scheduler_installation": {
"supported": true,
"level": "partial",
"notes": [
"Cron wrapper exists, but installation remains environment-bound rather than package-core."
]
},
"watchdog_evaluation": {
"supported": true,
"level": "full"
},
"watchdog_event_emission": {
"supported": true,
"level": "full"
},
"operator_recovery_path": {
"supported": true,
"level": "full"
},
"watchdog_closure": {
"supported": true,
"level": "full"
}
},
"storage": {
"persist_events": {
"supported": true,
"level": "full"
},
"persist_evidence": {
"supported": true,
"level": "full"
},
"persist_decisions": {
"supported": false,
"level": "none",
"notes": [
"Decision-store extraction is still the next stage."
]
},
"persist_receipts": {
"supported": true,
"level": "full"
},
"preserve_original_attempted_message": {
"supported": true,
"level": "partial",
"notes": [
"Notification payloads and sender-attempt artifacts are retained for the reference path."
]
}
}
},
"defaults": {
"reporting_mode": "watchdog-auto-notify",
"channel": "telegram",
"policy_ids": [
"no-silence",
"mandatory-checkpoint-structure"
],
"watchdog_policy_id": "no-silence"
},
"notes": [
"This descriptor intentionally describes the current OpenClaw watchdog reference composition, not the full future plugin capability surface.",
"Evaluator and decision-runner extraction should consume this descriptor as the runtime truth contract."
]
}

View File

@@ -0,0 +1,81 @@
{
"$schema": "../../../schemas/reporting-governance/capability-descriptor.schema.json",
"apiVersion": "reporting-governance/v1alpha1",
"kind": "AdapterCapabilities",
"metadata": {
"id": "example-openclaw-watchdog-reference",
"title": "Example OpenClaw Watchdog Reference Descriptor",
"version": "1.0.0"
},
"runtime": {
"name": "openclaw",
"mode": "hybrid",
"surfaces": [
"watchdog",
"queue",
"dispatcher",
"bridge",
"sender_binding",
"orchestrator",
"storage"
],
"entrypoint": "scripts/watchdog_auto_notify_orchestrator.mjs"
},
"compatibility": {
"plugin_spec_versions": [
"0.1.0-mainline"
]
},
"capabilities": {
"observation": {
"task_lifecycle": {"supported": true, "level": "partial"},
"subagent_lifecycle": {"supported": true, "level": "partial"},
"checkpoint_obligations": {"supported": true, "level": "partial"},
"outgoing_report_attempts": {"supported": true, "level": "partial"},
"watchdog_state": {"supported": true, "level": "full"},
"queue_spool_receipts": {"supported": true, "level": "full"}
},
"normalization": {
"canonical_events": {"supported": true, "level": "full"},
"canonical_evidence": {"supported": true, "level": "partial"},
"decision_inputs": {"supported": true, "level": "partial"},
"correlation_propagation": {"supported": true, "level": "partial"}
},
"enforcement": {
"block_transition": {"supported": true, "level": "partial"},
"rewrite_message": {"supported": false, "level": "none"},
"annotate_placeholder": {"supported": false, "level": "none"},
"force_checkpoint": {"supported": true, "level": "partial"},
"request_review": {"supported": false, "level": "none"},
"downgrade_status": {"supported": false, "level": "none"},
"escalate": {"supported": true, "level": "full"}
},
"notification_path": {
"queue_items": {"supported": true, "level": "full"},
"spool_handoff": {"supported": true, "level": "full"},
"sender_binding": {"supported": true, "level": "full"},
"direct_send": {"supported": true, "level": "partial"},
"receipts": {"supported": true, "level": "full"},
"final_delivery_proof": {"supported": true, "level": "partial"},
"truth_model": {
"delivery_states": ["prepared", "queued", "dispatched", "pending_external_send", "acked", "blocked"],
"ack_requires_proven_send": true,
"pending_external_send_supported": true
}
},
"watchdog": {
"scheduler_installation": {"supported": true, "level": "partial"},
"watchdog_evaluation": {"supported": true, "level": "full"},
"watchdog_event_emission": {"supported": true, "level": "full"},
"operator_recovery_path": {"supported": true, "level": "full"},
"watchdog_closure": {"supported": true, "level": "full"}
},
"storage": {
"persist_events": {"supported": true, "level": "full"},
"persist_evidence": {"supported": true, "level": "full"},
"persist_decisions": {"supported": false, "level": "none"},
"persist_receipts": {"supported": true, "level": "full"},
"preserve_original_attempted_message": {"supported": true, "level": "partial"}
}
}
}

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/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/exports-boundary.integration.test.mjs"
}
}

View File

@@ -0,0 +1,36 @@
{
"$schema": "../../../schemas/reporting-governance/deployment-profile.schema.json",
"apiVersion": "reporting-governance/v1alpha1",
"kind": "DeploymentProfileArtifact",
"metadata": {
"id": "strict-manager-mode",
"version": "1.0.0",
"runtime": "openclaw",
"source_profile": "profiles/strict-manager-mode.yaml",
"compatibility_mode": "strict_envelope"
},
"spec": {
"package": {
"pluginVersion": "0.1.0-mainline"
},
"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": {
"watchdogEvidence": "state/long-task-watchdog",
"canonicalEvents": "state/long-task-watchdog-events",
"queueItems": "state/operator-notify-queue",
"spoolArtifacts": "state/operator-notify-dispatch-spool",
"bridgeReceipts": "state/operator-notify-bridge-receipts",
"senderAttempts": "state/operator-notify-sender-attempts"
}
}
}
}

View File

@@ -5,3 +5,4 @@ export { runSenderBindingAdapter } from './sender-binding.mjs';
export { runOrchestratorAdapter } from './orchestrator.mjs';
export { createRuntimeBinding } from './runtime-binding.mjs';
export { loadDeploymentProfileArtifact, createDeploymentBindingContract } from '../storage/profile-artifact.mjs';

View File

@@ -0,0 +1,49 @@
# OpenClaw Watchdog Reference Runtime Composition
This file places the existing watchdog auto-notify chain inside the reporting-governance package skeleton as a **reference implementation**.
## Placement decision
The watchdog chain is **not** part of `src/core/`.
It is categorized under `src/reference/` and represented through package adapter boundaries in `src/adapters/`.
## Why
The chain is runtime-specific and deployment-shaped:
- it depends on OpenClaw execution patterns
- it depends on queue / spool / bridge / sender boundaries
- it reflects truthful delivery-state handling across runtime hops
Those are exactly the properties of a reference adapter composition, not runtime-agnostic governance core.
## Reference chain
```text
scripts/long_task_watchdog.mjs
-> scripts/operator_notify_dispatcher.mjs
-> scripts/operator_notify_bridge_supervisor.mjs
-> scripts/operator_notify_sender_binding.mjs
-> acked | blocked | pending_external_send
```
## Package mapping
- `src/adapters/watchdog-adapter.mjs` → watchdog trigger + canonical event seeding
- `src/adapters/dispatcher-adapter.mjs` → queue to spool handoff
- `src/adapters/bridge-adapter.mjs` → spool consumption + receipt writeback
- `src/adapters/sender-binding-adapter.mjs` → sender contract boundary
- `src/adapters/orchestrator-adapter.mjs` → deterministic composition entrypoint
## Runtime artifact classes
The current reference implementation still writes runtime artifacts in repo-level state directories such as:
- `state/long-task-watchdog/`
- `state/long-task-watchdog-events/`
- `state/operator-notify-queue/`
- `state/operator-notify-dispatch-spool/`
- `state/operator-notify-bridge-receipts/`
- `state/operator-notify-sender-attempts/`
Future package extraction should preserve these artifact classes through `src/storage/` contracts rather than raw script coupling.

View File

@@ -0,0 +1 @@
export { loadDeploymentProfileArtifact, createDeploymentBindingContract } from './profile-artifact.mjs';

View File

@@ -0,0 +1,54 @@
import fs from 'node:fs';
import path from 'node:path';
const packageRoot = path.resolve(import.meta.dirname, '..', '..');
const repoRoot = path.resolve(packageRoot, '..', '..');
function readJsonFile(filePath) {
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
}
export function resolvePackageArtifactPath(...segments) {
return path.resolve(packageRoot, ...segments);
}
export function loadDeploymentProfileArtifact({ artifactPath, profileId } = {}) {
const resolvedPath = path.resolve(
artifactPath
?? resolvePackageArtifactPath('profiles', `${profileId ?? 'strict-manager-mode'}.profile.json`)
);
const artifact = readJsonFile(resolvedPath);
return {
artifactPath: resolvedPath,
artifact,
};
}
export function createDeploymentBindingContract({ artifact, repoRootOverride } = {}) {
if (!artifact?.spec?.bindings) {
throw new Error('deployment profile artifact bindings are required');
}
const root = path.resolve(repoRootOverride ?? repoRoot);
const scripts = Object.fromEntries(
Object.entries(artifact.spec.bindings.scripts ?? {}).map(([key, relativePath]) => [key, path.resolve(root, relativePath)])
);
const artifactRoots = Object.fromEntries(
Object.entries(artifact.spec.bindings.artifact_roots ?? {}).map(([key, relativePath]) => [key, path.resolve(root, relativePath)])
);
return {
runtime: artifact.spec.bindings.runtime ?? artifact.metadata?.runtime ?? 'unknown-runtime',
entrypoint: path.resolve(root, artifact.spec.bindings.entrypoint),
pluginVersion: artifact.spec?.package?.pluginVersion ?? null,
compatibilityMode: artifact.metadata?.compatibility_mode ?? 'strict_envelope',
scripts,
artifactRoots,
};
}
export const __testables = {
packageRoot,
repoRoot,
readJsonFile,
};

View File

@@ -43,7 +43,30 @@ test('runCompatibilityPreflight passes strict profile against reference descript
assert.equal(result.errors.length, 0);
});
test('runCompatibilityPreflight fails closed on schema/version mismatch', () => {
test('runCompatibilityPreflight keeps legacy compatibility mode open when caller provides no compatibility envelope', () => {
const brokenDescriptor = {
...capabilityDescriptor,
compatibility: {
...capabilityDescriptor.compatibility,
plugin_spec_versions: ['9.9.9'],
decision_schema: 'schemas/reporting-governance/not-the-canonical-decision.schema.json'
}
};
const result = runCompatibilityPreflight({
capabilityDescriptor: brokenDescriptor
});
assert.equal(result.status, 'pass');
assert.equal(result.requested_profile, null);
assert.equal(result.requested_plugin_version, null);
assert.equal(result.compatibility.version_ok, true);
assert.equal(result.errors.length, 0);
assert.ok(result.compatibility.schema_checks.some((entry) => entry.key === 'decision_schema' && entry.ok === false));
assert.ok(result.notes.some((note) => note.includes('skipped plugin version pin')));
});
test('runCompatibilityPreflight fails closed on schema/version mismatch once compatibility envelope is present', () => {
const brokenDescriptor = {
...capabilityDescriptor,
compatibility: {

View File

@@ -81,7 +81,7 @@ test('package root export resolves public package surface only', () => {
}
});
test('adapters subpath export resolves package-owned adapter index', () => {
test('adapters subpath export resolves package-owned adapter index plus profile artifact loader helpers', () => {
const root = createFixtureRoot();
try {
installPackageAlias(root);
@@ -93,7 +93,9 @@ test('adapters subpath export resolves package-owned adapter index', () => {
`);
assert.deepEqual(result.adapterKeys, [
'createDeploymentBindingContract',
'createRuntimeBinding',
'loadDeploymentProfileArtifact',
'runBridgeSupervisorAdapter',
'runDispatcherAdapter',
'runOrchestratorAdapter',

View File

@@ -131,6 +131,36 @@ test('executeGovernanceContract stays compatible for legacy callers without prof
assert.deepEqual(result.contract.package_actions, ['emit_event']);
});
test('legacy caller still fails once profile supplies compatibility envelope against mismatched descriptor', () => {
const brokenDescriptor = {
...capabilityDescriptor,
compatibility: {
...capabilityDescriptor.compatibility,
plugin_spec_versions: ['9.9.9'],
decision_schema: 'schemas/reporting-governance/not-the-canonical-decision.schema.json'
}
};
const result = executeGovernanceContract({
event: {
type: 'silence_timeout',
payload: { checkpoint_overdue: true }
},
capabilityDescriptor: brokenDescriptor,
policyPacks: [noSilencePack],
context: {
signals: ['checkpoint_overdue']
},
profile: strictProfile,
packageVersion: '0.1.0-mainline'
});
assert.equal(result.preflight.status, 'fail_closed');
assert.equal(result.contract.delivery_state, 'blocked');
assert.equal(result.contract.receipt_status, 'blocked');
assert.ok(result.planning.receipt.notes.some((note) => note.includes('schema mismatch: decision_schema')));
});
test('contract truthfully degrades when notify path can queue but cannot directly dispatch', () => {
const limitedDescriptor = {
...capabilityDescriptor,

View File

@@ -19,9 +19,11 @@ const requiredPaths = [
'src/adapters/sender-binding.mjs',
'src/adapters/orchestrator.mjs',
'src/storage',
'src/storage/profile-artifact.mjs',
'src/reference/openclaw-watchdog-chain.md',
'capabilities/openclaw-watchdog-reference.json',
'examples/openclaw-watchdog-reference.descriptor.example.json'
'examples/openclaw-watchdog-reference.descriptor.example.json',
'profiles/strict-manager-mode.profile.json'
];
test('reporting-governance package skeleton paths exist', () => {

View File

@@ -0,0 +1,47 @@
import test from 'node:test';
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 { createRuntimeBinding } from '../src/adapters/index.mjs';
const packageRoot = path.resolve(import.meta.dirname, '..');
const repoRoot = path.resolve(packageRoot, '..', '..');
test('deployment profile artifact loads from package profiles and preserves compatibility envelope metadata', () => {
const { artifactPath, artifact } = loadDeploymentProfileArtifact({ profileId: 'strict-manager-mode' });
assert.equal(path.relative(packageRoot, artifactPath), path.join('profiles', 'strict-manager-mode.profile.json'));
assert.equal(artifact.kind, 'DeploymentProfileArtifact');
assert.equal(artifact.metadata.id, 'strict-manager-mode');
assert.equal(artifact.metadata.compatibility_mode, 'strict_envelope');
assert.equal(artifact.spec.package.pluginVersion, '0.1.0-mainline');
});
test('deployment binding contract resolves package artifact into real repo script and artifact paths', () => {
const { artifact } = loadDeploymentProfileArtifact({ profileId: 'strict-manager-mode' });
const binding = createDeploymentBindingContract({ artifact });
assert.equal(binding.runtime, 'openclaw');
assert.equal(binding.pluginVersion, '0.1.0-mainline');
assert.equal(binding.compatibilityMode, 'strict_envelope');
assert.equal(binding.entrypoint, path.resolve(repoRoot, 'scripts/watchdog_auto_notify_orchestrator.mjs'));
assert.equal(binding.scripts.watchdog, path.resolve(repoRoot, 'scripts/long_task_watchdog.mjs'));
assert.equal(binding.artifactRoots.queueItems, path.resolve(repoRoot, 'state/operator-notify-queue'));
assert.equal(fs.existsSync(binding.scripts.orchestrator), true);
});
test('runtime binding can be instantiated from profile artifact binding contract', () => {
const { artifact } = loadDeploymentProfileArtifact({ profileId: 'strict-manager-mode' });
const contract = createDeploymentBindingContract({ artifact });
const runtimeBinding = createRuntimeBinding({
cwd: repoRoot,
scripts: contract.scripts,
});
assert.equal(runtimeBinding.cwd, repoRoot);
assert.equal(runtimeBinding.scripts.dispatcher, contract.scripts.dispatcher);
assert.equal(runtimeBinding.scripts.bridgeSupervisor, contract.scripts.bridgeSupervisor);
assert.equal(runtimeBinding.scripts.senderBinding, contract.scripts.senderBinding);
});