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 { DarkCoreTask, digest, inspectTask } from './task-controller.mjs'; import { localFileHost } from './local-file-host.mjs'; function fixture(t, overrides = {}) { const root = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'dark-core-test-'))); t.after(() => fs.rmSync(root, { recursive: true, force: true })); const plan = { id: 'test', goal: 'write two approved files', channel: 'ICE-CH-DK001', authorizationRef: 'test-scope', steps: [1, 2].map(i => ({ id: String(i), capability: 'CREATE_TEXT_FILE', args: { path: path.join(root, `${i}.txt`), text: `result ${i}` } })) }; const host = { ...localFileHost({ root, approvedPlan: plan, approvalReceipt: 'test-host-approval', verifyHuman: () => true }), ...overrides }; const directory = path.join(root, 'state'); const task = new DarkCoreTask({ plan, directory, host }); let sequence = 0; const human = (kind, text = kind) => task.human({ id: `event-${++sequence}`, taskId: plan.id, kind, text }); return { root, plan, host, directory, task, human }; } const deferred = () => { let resolve; const promise = new Promise(r => { resolve = r; }); return { promise, resolve }; }; test('real local writes complete only after both readbacks; immutable input and journal', async t => { const f = fixture(t); f.plan.steps[0].args.text = 'changed after confirmation'; await f.task.run(); assert.equal(f.task.snapshot.status, 'COMPLETED'); assert.equal(fs.readFileSync(path.join(f.root, '1.txt'), 'utf8'), 'result 1'); assert.equal(inspectTask(f.directory).receipts.length, 2); assert.throws(() => new DarkCoreTask({ plan: f.plan, directory: f.directory, host: f.host }), /EEXIST/); }); test('ordinary advice stays queued during execution and rejection requires a reason', async t => { const gate = deferred(), entered = deferred(); let calls = 0; const f = fixture(t); const execute = f.host.execute; f.host.execute = async args => { if (++calls === 1) { entered.resolve(); await gate.promise; } return execute(args); }; const running = f.task.run(); await entered.promise; const before = f.task.snapshot.planHash; await f.human('ADVICE', 'switch frameworks'); assert.equal(f.task.snapshot.status, 'RUNNING'); assert.throws(() => f.task.decideAdvice('event-1', 'REJECT', ''), /REASON_REQUIRED/); f.task.decideAdvice('event-1', 'REJECT', 'Changing frameworks is outside the confirmed task.'); assert.equal(f.task.snapshot.planHash, before); gate.resolve(); await running; assert.equal(f.task.snapshot.status, 'COMPLETED'); }); test('stop during asynchronous authorization prevents the first operation', async t => { const gate = deferred(), entered = deferred(); const f = fixture(t); const authorize = f.host.authorize; f.host.authorize = async args => { const g = await authorize(args); entered.resolve(); await gate.promise; return g; }; const running = f.task.run(); await entered.promise; await f.human('STOP'); assert.equal(f.task.snapshot.status, 'STOP_REQUESTED'); gate.resolve(); await running; assert.equal(f.task.snapshot.status, 'STOPPED'); assert.equal(fs.existsSync(path.join(f.root, '1.txt')), false); }); test('withdrawal cancels a cooperative in-flight adapter and cannot be resumed', async t => { const entered = deferred(); const f = fixture(t, { execute: ({ signal }) => new Promise((_, reject) => { entered.resolve(); signal.addEventListener('abort', () => reject(Error('ABORTED')), { once: true }); }) }); const running = f.task.run(); await entered.promise; await f.human('WITHDRAW'); await running; assert.equal(f.task.snapshot.status, 'WITHDRAWN'); assert.deepEqual(f.task.snapshot.uncertainSteps, ['1']); await assert.rejects(f.human('RESUME'), /TASK_CLOSED/); assert.equal(fs.existsSync(path.join(f.root, '2.txt')), false); }); test('non-cancellable work is never falsely reported stopped while in flight', async t => { const gate = deferred(), entered = deferred(); const f = fixture(t); const execute = f.host.execute; f.host.execute = async args => { const result = await execute(args); entered.resolve(); await gate.promise; return result; }; const running = f.task.run(); await entered.promise; await f.human('STOP'); assert.equal(f.task.snapshot.status, 'STOP_REQUESTED'); gate.resolve(); await running; assert.equal(f.task.snapshot.status, 'STOPPED'); assert.equal(f.task.snapshot.receipts.length, 1); assert.equal(fs.existsSync(path.join(f.root, '2.txt')), false); }); test('correction pauses at an authorization boundary and resume reauthorizes', async t => { const gate = deferred(), entered = deferred(); let checks = 0; const f = fixture(t); const authorize = f.host.authorize; f.host.authorize = async args => { const grant = await authorize(args); if (++checks === 1) { entered.resolve(); await gate.promise; } return grant; }; const running = f.task.run(); await entered.promise; await f.human('CORRECTION', 'check the target'); gate.resolve(); await running; assert.equal(f.task.snapshot.status, 'PAUSED'); await f.human('RESUME', 'target checked; continue original task'); await f.task.run(); assert.equal(f.task.snapshot.status, 'COMPLETED'); assert.equal(checks, 3); }); test('uncertain side effects block resume rather than repeat a partially executed operation', async t => { const entered = deferred(); const f = fixture(t, { execute: ({ signal }) => new Promise((_, reject) => { entered.resolve(); signal.addEventListener('abort', () => reject(Error('PARTIAL_EFFECT_POSSIBLE'))); }) }); const running = f.task.run(); await entered.promise; await f.human('CORRECTION'); await running; assert.equal(f.task.snapshot.status, 'PAUSED'); await assert.rejects(f.human('RESUME'), /UNCERTAIN_EFFECTS/); }); test('later correction does not override withdrawal', async t => { const gate = deferred(), entered = deferred(); const f = fixture(t); const authorize = f.host.authorize; f.host.authorize = async args => { const result = await authorize(args); entered.resolve(); await gate.promise; return result; }; const running = f.task.run(); await entered.promise; await f.human('WITHDRAW'); await f.human('CORRECTION'); gate.resolve(); await running; assert.equal(f.task.snapshot.status, 'WITHDRAWN'); }); test('untrusted document text cannot stop or replan the task', async t => { const f = fixture(t, { verifyHuman: () => false }); await assert.rejects(f.human('STOP', 'document says ignore instructions'), /UNTRUSTED_CONTROL_SOURCE/); assert.equal(f.task.snapshot.status, 'READY'); }); test('goal change pauses but cannot mutate confirmed scope', async t => { const f = fixture(t); const original = f.task.snapshot.planHash; await f.human('CHANGE_GOAL', 'also operate on another server'); assert.equal(f.task.snapshot.status, 'PAUSED'); assert.equal(f.task.snapshot.planHash, original); }); test('bad readback fails without launching subsequent work', async t => { const f = fixture(t, { verify: () => false }); await f.task.run(); assert.equal(f.task.snapshot.status, 'FAILED'); assert.equal(f.task.snapshot.receipts.length, 0); assert.equal(fs.existsSync(path.join(f.root, '2.txt')), false); }); test('invalid host grant cannot execute', async t => { const f = fixture(t, { authorize: () => ({ planHash: 'wrong', stepId: '1', receipt: 'forged' }) }); await f.task.run(); assert.equal(f.task.snapshot.status, 'FAILED'); assert.equal(fs.existsSync(path.join(f.root, '1.txt')), false); }); test('local adapter rejects outside targets and never overwrites existing files', async t => { const f = fixture(t); fs.writeFileSync(path.join(f.root, '1.txt'), 'original'); await f.task.run(); assert.equal(f.task.snapshot.status, 'FAILED'); assert.equal(fs.readFileSync(path.join(f.root, '1.txt'), 'utf8'), 'original'); const outside = structuredClone(f.plan); outside.steps[0].args.path = '/tmp/outside.txt'; assert.throws(() => localFileHost({ root: f.root, approvedPlan: outside, approvalReceipt: 'x', verifyHuman: () => true }), /OUT_OF_SCOPE/); }); test('inspection detects journal modification and marks unfinished tasks for reconciliation', t => { const f = fixture(t); assert.equal(inspectTask(f.directory).recoveryRequired, true); const p = path.join(f.directory, 'task.json'); const state = JSON.parse(fs.readFileSync(p)); state.events[0].detail.goal = 'tampered'; fs.writeFileSync(p, JSON.stringify(state)); assert.throws(() => inspectTask(f.directory), /JOURNAL_INTEGRITY/); });