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 { runOrchestratorAdapter, runWatchdogChain, } from '../src/index.mjs'; import { createDeploymentBindingContract } from '../src/storage/profile-artifact.mjs'; const packageRoot = path.resolve(import.meta.dirname, '..'); function createFixtureRoot() { return fs.mkdtempSync(path.join(os.tmpdir(), 'reporting-governance-plugin-')); } 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 { fileName: files[0], payload: JSON.parse(fs.readFileSync(path.join(dirPath, files[0]), 'utf8')), }; } test('package entrypoint can run watchdog chain through orchestrator adapter', () => { const root = createFixtureRoot(); try { mkdirs(root, ['evidence', 'events', 'queue', 'spool', 'receipts']); const statePath = writeState(root); const result = runWatchdogChain({ 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, senderCommand: `node -e "process.stdout.write(JSON.stringify({state:'sent'}))"`, now: '2026-05-07T08:20:00.000Z', }); assert.equal(result.ok, true); assert.deepEqual(result.executionOrder, ['runner', 'queue', 'dispatcher', 'bridge', 'sender', 'ack_or_blocked_or_pending']); assert.equal(result.result.watchdog.notificationCount, 1); assert.equal(result.result.dispatcher.dispatchedCount, 1); assert.equal(result.result.supervisor.ackedCount, 1); const queueItem = readSingleJson(path.join(root, 'queue')).payload; assert.equal(queueItem.status, 'acked'); } finally { fs.rmSync(root, { recursive: true, force: true }); } }); test('dry-run path produces verifiable pending receipt via package adapter', () => { const root = createFixtureRoot(); try { mkdirs(root, ['evidence', 'events', 'queue', 'spool', 'receipts']); const statePath = writeState(root); const result = runOrchestratorAdapter({ 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.orchestration.dryRun, true); assert.equal(result.result.watchdog.notificationCount, 1); assert.equal(result.result.dispatcher.dispatchedCount, 1); assert.equal(result.result.supervisor.pendingCount, 1); const queueItem = readSingleJson(path.join(root, 'queue')).payload; assert.equal(queueItem.status, 'dispatched'); const receipt = readSingleJson(path.join(root, 'receipts')).payload; assert.equal(receipt.state, 'pending_external_send'); assert.equal(receipt.supervisor_mode, 'dry_run'); assert.ok(receipt.suggested_command.includes('openclaw message send')); } finally { 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 }); } }); test('orchestrator adapter can use artifact_roots.queueItems as the default queueDir at execution time', () => { const root = createFixtureRoot(); try { mkdirs(root, ['evidence', 'events', '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'), 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.dispatcher.dispatchedCount >= 0, true); } finally { fs.rmSync(root, { recursive: true, force: true }); } }); test('orchestrator adapter fails closed at use time when artifact_roots.queueItems drifts through symlink escape', () => { const sandbox = fs.mkdtempSync(path.join(os.tmpdir(), 'reporting-governance-orchestrator-drift-')); try { const fakeRepoRoot = path.join(sandbox, 'repo'); const outsideRoot = path.join(sandbox, 'outside'); const realRepoRoot = path.resolve(packageRoot); fs.mkdirSync(path.join(fakeRepoRoot, 'scripts'), { recursive: true }); fs.mkdirSync(path.join(fakeRepoRoot, 'state', 'operator-notify-queue'), { recursive: true }); fs.mkdirSync(outsideRoot, { recursive: true }); for (const name of [ 'watchdog_auto_notify_orchestrator.mjs', 'long_task_watchdog.mjs', 'operator_notify_dispatcher.mjs', 'operator_notify_bridge_supervisor.mjs', 'operator_notify_sender_binding.mjs', ]) { fs.copyFileSync(path.join(realRepoRoot, 'scripts', name), path.join(fakeRepoRoot, 'scripts', name)); } const deploymentBinding = createDeploymentBindingContract({ artifact: { kind: 'DeploymentProfileArtifact', apiVersion: 'reporting-governance/v1alpha1', metadata: { id: 'drifted-queue-profile', runtime: 'openclaw', 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: { queueItems: 'state/operator-notify-queue' } } } }, repoRootOverride: fakeRepoRoot, }); fs.rmSync(path.join(fakeRepoRoot, 'state', 'operator-notify-queue'), { recursive: true, force: true }); fs.symlinkSync(outsideRoot, path.join(fakeRepoRoot, 'state', 'operator-notify-queue'), 'dir'); const statePath = writeState(sandbox); assert.throws( () => runOrchestratorAdapter({ deploymentBinding, repoRootOverride: fakeRepoRoot, state: statePath, evidenceDir: path.join(sandbox, 'evidence'), eventDir: path.join(sandbox, 'events'), spoolDir: path.join(sandbox, 'spool'), receiptDir: path.join(sandbox, 'receipts'), writeState: true, dryRun: true, now: '2026-05-07T08:20:00.000Z', }), /orchestrator adapter queueDir must stay within repo root: symlink resolution escapes realpath boundary/ ); } finally { fs.rmSync(sandbox, { recursive: true, force: true }); } });