mirror of
https://github.com/PR0M3TH3AN/TORCH.git
synced 2026-10-29 00:41:58 +00:00
371 lines
20 KiB
JavaScript
371 lines
20 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { execFileSync, spawnSync } from 'node:child_process';
|
|
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { basename, join } from 'node:path';
|
|
import test from 'node:test';
|
|
import { openControlPlane } from '../../src/control-plane/service.mjs';
|
|
import { CANDIDATE_ACCEPTANCE_EVIDENCE } from '../../src/cli.mjs';
|
|
|
|
const CLI = new URL('../../bin/torch.mjs', import.meta.url).pathname;
|
|
|
|
function repo() {
|
|
const root = mkdtempSync(join(tmpdir(), 'torch-cli-'));
|
|
execFileSync('git', ['init', '-b', 'main', root]);
|
|
execFileSync('git', ['-C', root, 'config', 'user.email', '[email protected]']);
|
|
execFileSync('git', ['-C', root, 'config', 'user.name', 'TORCH Test']);
|
|
writeFileSync(join(root, 'README.md'), '# Fixture\n');
|
|
execFileSync('git', ['-C', root, 'add', '.']);
|
|
execFileSync('git', ['-C', root, 'commit', '-m', 'fixture']);
|
|
return root;
|
|
}
|
|
|
|
function run(root, args) {
|
|
return spawnSync(process.execPath, [CLI, ...args], {
|
|
cwd: root,
|
|
env: {
|
|
...process.env,
|
|
XDG_DATA_HOME: join(tmpdir(), `${basename(root)}-data`),
|
|
TORCH_BIN_HOME: join(tmpdir(), `${basename(root)}-bin`),
|
|
},
|
|
encoding: 'utf8',
|
|
});
|
|
}
|
|
|
|
function releaseCandidate(root, version) {
|
|
const candidateRoot = join(root, `release-${version}`);
|
|
mkdirSync(join(candidateRoot, 'bin'), { recursive: true });
|
|
writeFileSync(join(candidateRoot, 'bin', 'torch.mjs'), '#!/usr/bin/env node\n');
|
|
chmodSync(join(candidateRoot, 'bin', 'torch.mjs'), 0o755);
|
|
const acceptanceMarkers = [...new Set(Object.values(CANDIDATE_ACCEPTANCE_EVIDENCE).flat())];
|
|
writeFileSync(join(candidateRoot, 'acceptance.mjs'), `console.log(${JSON.stringify(acceptanceMarkers.join('\n'))});\n`);
|
|
writeFileSync(join(candidateRoot, 'package.json'), `${JSON.stringify({
|
|
name: 'torch-agent-fleet', version, bin: { torch: 'bin/torch.mjs' },
|
|
scripts: {
|
|
test: 'node acceptance.mjs',
|
|
lint: 'node -e "process.exit(0)"',
|
|
check: 'node -e "process.exit(0)"',
|
|
},
|
|
}, null, 2)}\n`);
|
|
writeFileSync(join(candidateRoot, 'torch-release.json'), `${JSON.stringify({
|
|
schema: 'torch.dev/release/v1alpha1', version,
|
|
state: { reads: ['torch.dev/state/v1alpha1'], writes: 'torch.dev/state/v1alpha1', rollback_safe: true },
|
|
}, null, 2)}\n`);
|
|
return candidateRoot;
|
|
}
|
|
|
|
test('SCN-cli-init: init reports analysis without creating project state', () => {
|
|
const root = repo();
|
|
const result = run(root, ['init', '--json']);
|
|
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
const output = JSON.parse(result.stdout);
|
|
assert.equal(output.command, 'init');
|
|
assert.equal(output.analysis.mutationPerformed, false);
|
|
assert.equal(existsSync(join(root, '.torch')), false);
|
|
});
|
|
|
|
test('SCN-cli-install-approval: install requires an explicit reviewed approval flag', () => {
|
|
const root = repo();
|
|
const result = run(root, ['install', '--json']);
|
|
assert.equal(result.status, 2);
|
|
assert.equal(JSON.parse(result.stdout).error, 'APPROVAL_REQUIRED');
|
|
assert.equal(existsSync(join(root, '.torch')), false);
|
|
});
|
|
|
|
test('SCN-cli-domain-review: a generated proposal must be approved before install', () => {
|
|
const root = repo();
|
|
const proposalPath = join(root, 'fleet-proposal.json');
|
|
const generated = run(root, ['domains', '--output', proposalPath, '--json']);
|
|
assert.equal(generated.status, 0, generated.stderr || generated.stdout);
|
|
const proposal = JSON.parse(readFileSync(proposalPath, 'utf8'));
|
|
assert.equal(proposal.review.status, 'pending');
|
|
assert.equal(proposal.domains.length > 0, true);
|
|
|
|
const rejected = run(root, ['install', '--proposal', proposalPath, '--yes', '--json']);
|
|
assert.equal(rejected.status, 2);
|
|
assert.equal(JSON.parse(rejected.stdout).error, 'PROPOSAL_NOT_APPROVED');
|
|
assert.equal(existsSync(join(root, '.torch')), false);
|
|
|
|
proposal.review = {
|
|
status: 'approved', reviewedAt: '2026-09-27T00:00:00Z', reviewedBy: 'fixture-owner', notes: [],
|
|
};
|
|
writeFileSync(proposalPath, `${JSON.stringify(proposal, null, 2)}\n`);
|
|
const installed = run(root, ['install', '--proposal', proposalPath, '--yes', '--json']);
|
|
assert.equal(installed.status, 0, installed.stderr || installed.stdout);
|
|
assert.equal(existsSync(join(root, '.torch', 'domain-proposal.approved.json')), true);
|
|
});
|
|
|
|
test('SCN-cli-install-provider-default: generated roles can install with Codex alone or choose a default from mixed adapters', () => {
|
|
const root = repo();
|
|
const proposalPath = join(root, 'fleet-proposal.json');
|
|
const generated = run(root, ['domains', '--output', proposalPath, '--json']);
|
|
assert.equal(generated.status, 0, generated.stderr || generated.stdout);
|
|
const proposal = JSON.parse(readFileSync(proposalPath, 'utf8'));
|
|
proposal.review = { status: 'approved', reviewedAt: '2026-09-29T00:00:00Z', reviewedBy: 'fixture-owner', notes: [] };
|
|
writeFileSync(proposalPath, JSON.stringify(proposal));
|
|
const codex = run(root, ['install', '--proposal', proposalPath, '--runtime', 'codex', '--dry-run', '--json']);
|
|
assert.equal(codex.status, 0, codex.stderr || codex.stdout);
|
|
assert.equal(JSON.parse(codex.stdout).defaultRuntime, 'codex');
|
|
assert.equal(existsSync(join(root, '.torch')), false);
|
|
const result = run(root, ['install', '--proposal', proposalPath, '--runtime', 'claude,codex',
|
|
'--default-runtime', 'codex', '--yes', '--json']);
|
|
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
const config = JSON.parse(readFileSync(join(root, '.torch', 'torch.yaml'), 'utf8'));
|
|
assert.equal(config.runtimes.default, 'codex');
|
|
assert.equal(config.session_manager.runtime, 'codex');
|
|
assert.ok(config.domains.every((domain) => domain.runtime === 'codex'));
|
|
assert.equal(config.runtimes.codex.model, 'gpt-6-luna');
|
|
assert.equal(config.runtimes.claude.model, 'sonnet');
|
|
});
|
|
|
|
test('SCN-cli-lifecycle-surface: runtime selection, selective startup, capture, brief, list, and detach are public', () => {
|
|
const root = repo();
|
|
const proposalPath = join(root, 'fleet-proposal.json');
|
|
assert.equal(run(root, ['domains', '--output', proposalPath, '--json']).status, 0);
|
|
const proposal = JSON.parse(readFileSync(proposalPath, 'utf8'));
|
|
proposal.review = {
|
|
status: 'approved', reviewedAt: '2026-09-27T00:00:00Z', reviewedBy: 'fixture-owner', notes: [],
|
|
};
|
|
writeFileSync(proposalPath, `${JSON.stringify(proposal, null, 2)}\n`);
|
|
const preview = run(root, [
|
|
'install', '--proposal', proposalPath, '--runtime', 'claude', '--dry-run', '--json',
|
|
]);
|
|
assert.equal(preview.status, 0, preview.stderr || preview.stdout);
|
|
assert.deepEqual(JSON.parse(preview.stdout).runtimes, ['claude']);
|
|
const installed = run(root, [
|
|
'install', '--proposal', proposalPath, '--runtime', 'claude', '--yes', '--json',
|
|
]);
|
|
assert.equal(installed.status, 0, installed.stderr || installed.stdout);
|
|
const config = JSON.parse(readFileSync(join(root, '.torch', 'torch.yaml'), 'utf8'));
|
|
assert.deepEqual(Object.keys(config.runtimes).sort(), ['claude', 'default']);
|
|
execFileSync('git', ['-C', root, 'add', '.torch', 'fleet-proposal.json']);
|
|
execFileSync('git', ['-C', root, 'commit', '-m', 'install torch']);
|
|
const worktreeParent = join(tmpdir(), `${basename(root)}-cli-worktrees`);
|
|
const worktrees = run(root, ['worktrees', '--parent', worktreeParent, '--yes', '--json']);
|
|
assert.equal(worktrees.status, 0, worktrees.stderr || worktrees.stdout);
|
|
const worker = proposal.domains[0].id;
|
|
|
|
const listed = run(root, ['list', '--json']);
|
|
assert.equal(listed.status, 0, listed.stderr || listed.stdout);
|
|
assert.equal(JSON.parse(listed.stdout).agents.some((agent) => agent.areaId === worker), true);
|
|
const brief = run(root, ['brief', '--area', worker, '--json']);
|
|
assert.equal(brief.status, 0, brief.stderr || brief.stdout);
|
|
assert.equal(JSON.parse(brief.stdout).areas[0].areaId, worker);
|
|
assert.match(JSON.parse(brief.stdout).areas[0].prompt, /Common fleet rules/);
|
|
const selective = run(root, ['up', '--only', worker, '--dry-run', '--json']);
|
|
assert.equal(selective.status, 0, selective.stderr || selective.stdout);
|
|
assert.deepEqual(JSON.parse(selective.stdout).actions.map((action) => action.areaId), [worker]);
|
|
const unknown = run(root, ['up', '--only', 'not-a-domain', '--dry-run', '--json']);
|
|
assert.equal(unknown.status, 1);
|
|
assert.equal(JSON.parse(unknown.stdout).blockers[0].code, 'FLEET_IDENTITY_MISSING');
|
|
const captured = run(root, ['capture', '--json']);
|
|
assert.equal(captured.status, 0, captured.stderr || captured.stdout);
|
|
assert.equal(existsSync(JSON.parse(captured.stdout).snapshotPath), true);
|
|
assert.equal(JSON.parse(run(root, ['detach', '--json']).stdout).error, 'APPROVAL_REQUIRED');
|
|
const detached = run(root, ['detach', '--yes', '--json']);
|
|
assert.equal(detached.status, 0, detached.stderr || detached.stdout);
|
|
assert.deepEqual(JSON.parse(detached.stdout).preserved, [
|
|
'tracked organization', 'worktrees', 'branches', 'local state',
|
|
]);
|
|
const doctor = run(root, ['doctor', '--json']);
|
|
assert.equal(doctor.status, 0, doctor.stderr || doctor.stdout);
|
|
assert.equal(JSON.parse(doctor.stdout).findings.some((finding) => finding.code === 'FLEET_DETACHED'), true);
|
|
const uninstallPlan = run(root, ['uninstall', '--dry-run', '--json']);
|
|
assert.equal(uninstallPlan.status, 0, uninstallPlan.stderr || uninstallPlan.stdout);
|
|
assert.equal(JSON.parse(uninstallPlan.stdout).action, 'uninstall');
|
|
assert.equal(JSON.parse(run(root, ['uninstall', '--json']).stdout).error, 'APPROVAL_REQUIRED');
|
|
const uninstalled = run(root, ['uninstall', '--yes', '--json']);
|
|
assert.equal(uninstalled.status, 0, uninstalled.stderr || uninstalled.stdout);
|
|
assert.equal(JSON.parse(uninstalled.stdout).mutationPerformed, true);
|
|
assert.equal(existsSync(join(root, '.torch', 'torch.yaml')), true);
|
|
const unapprovedPurge = run(root, ['uninstall', '--purge', '--json']);
|
|
assert.equal(unapprovedPurge.status, 2);
|
|
assert.equal(JSON.parse(unapprovedPurge.stdout).error, 'APPROVAL_REQUIRED');
|
|
});
|
|
|
|
test('SCN-cli-integration-drain: the FIFO lander requires explicit confirmation and is available as a one-shot command', () => {
|
|
const root = repo();
|
|
const proposalPath = join(root, 'fleet-proposal.json');
|
|
assert.equal(run(root, ['domains', '--output', proposalPath, '--json']).status, 0);
|
|
const proposal = JSON.parse(readFileSync(proposalPath, 'utf8'));
|
|
proposal.review = {
|
|
status: 'approved', reviewedAt: '2026-09-29T00:00:00Z', reviewedBy: 'fixture-owner', notes: [],
|
|
};
|
|
writeFileSync(proposalPath, `${JSON.stringify(proposal, null, 2)}\n`);
|
|
const installed = run(root, ['install', '--proposal', proposalPath, '--yes', '--json']);
|
|
assert.equal(installed.status, 0, installed.stderr || installed.stdout);
|
|
execFileSync('git', ['-C', root, 'add', '.torch', 'fleet-proposal.json']);
|
|
execFileSync('git', ['-C', root, 'commit', '-m', 'install torch for queue test']);
|
|
|
|
const before = execFileSync('git', ['-C', root, 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim();
|
|
const unconfirmed = run(root, ['integrate', 'drain', '--area', 'session-manager', '--json']);
|
|
assert.equal(unconfirmed.status, 2);
|
|
assert.equal(JSON.parse(unconfirmed.stdout).error, 'APPROVAL_REQUIRED');
|
|
assert.equal(execFileSync('git', ['-C', root, 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim(), before);
|
|
|
|
const drained = run(root, ['integrate', 'drain', '--area', 'session-manager', '--yes', '--json']);
|
|
assert.equal(drained.status, 0, drained.stderr || drained.stdout);
|
|
assert.equal(JSON.parse(drained.stdout).processed, 0);
|
|
assert.equal(execFileSync('git', ['-C', root, 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim(), before);
|
|
});
|
|
|
|
test('SCN-cli-control-plane: CLI messages, acknowledgements, ownership, and status share durable state', () => {
|
|
const root = repo();
|
|
const proposalPath = join(root, 'fleet-proposal.json');
|
|
run(root, ['domains', '--output', proposalPath, '--json']);
|
|
const proposal = JSON.parse(readFileSync(proposalPath, 'utf8'));
|
|
proposal.review = {
|
|
status: 'approved', reviewedAt: '2026-09-27T00:00:00Z', reviewedBy: 'fixture-owner', notes: [],
|
|
};
|
|
writeFileSync(proposalPath, `${JSON.stringify(proposal, null, 2)}\n`);
|
|
assert.equal(run(root, ['install', '--proposal', proposalPath, '--yes', '--json']).status, 0);
|
|
const worker = proposal.domains[0].id;
|
|
|
|
const sent = run(root, [
|
|
'message', '--from', 'session-manager', '--to', worker, '--body', 'Use the CLI fallback.',
|
|
'--task', 'TASK-CLI', '--json',
|
|
]);
|
|
assert.equal(sent.status, 0, sent.stderr || sent.stdout);
|
|
const message = JSON.parse(sent.stdout);
|
|
const inbox = JSON.parse(run(root, ['inbox', '--area', worker, '--unacknowledged', '--json']).stdout);
|
|
assert.equal(inbox.messages[0].id, message.id);
|
|
assert.equal(inbox.messages[0].references.task, 'TASK-CLI');
|
|
const acknowledged = run(root, ['ack', '--area', worker, '--message', message.id, '--json']);
|
|
assert.equal(acknowledged.status, 0, acknowledged.stderr || acknowledged.stdout);
|
|
assert.equal(JSON.parse(run(root, ['inbox', '--area', worker, '--unacknowledged', '--json']).stdout).messages.length, 0);
|
|
const status = JSON.parse(run(root, [
|
|
'status', '--area', worker, '--state', 'working', '--summary', 'Running TASK-CLI',
|
|
'--runtime', 'claude', '--session', 'runtime-cli', '--json',
|
|
]).stdout);
|
|
assert.equal(status.runtimeSessionId, 'runtime-cli');
|
|
const ownership = JSON.parse(run(root, ['who-owns', '--path', 'README.md', '--json']).stdout);
|
|
assert.equal(ownership.owners.length > 0, true);
|
|
});
|
|
|
|
test('SCN-cli-owner-approval: owners can review scoped requests and must explicitly confirm decisions', () => {
|
|
const root = repo();
|
|
const proposalPath = join(root, 'approval-proposal.json');
|
|
assert.equal(run(root, ['domains', '--output', proposalPath, '--json']).status, 0);
|
|
const proposal = JSON.parse(readFileSync(proposalPath, 'utf8'));
|
|
proposal.review = { status: 'approved', reviewedAt: '2026-09-28T00:00:00Z', reviewedBy: 'fixture-owner', notes: [] };
|
|
writeFileSync(proposalPath, `${JSON.stringify(proposal, null, 2)}\n`);
|
|
assert.equal(run(root, ['install', '--proposal', proposalPath, '--yes', '--json']).status, 0);
|
|
|
|
const env = { ...process.env, XDG_DATA_HOME: join(tmpdir(), `${basename(root)}-data`) };
|
|
const control = openControlPlane({ repositoryRoot: root, env });
|
|
const request = control.requestApproval({
|
|
requester: proposal.domains[0].id, approver: 'owner', title: 'Review scope',
|
|
summary: 'The requested work crosses an approved boundary.', task: 'TASK-OWNER-APPROVAL',
|
|
});
|
|
control.close();
|
|
|
|
const listed = run(root, ['approvals', 'list', '--area', 'owner', '--json']);
|
|
assert.equal(listed.status, 0, listed.stderr || listed.stdout);
|
|
assert.equal(JSON.parse(listed.stdout).approvals[0].id, request.id);
|
|
const unconfirmed = run(root, [
|
|
'approvals', 'decide', '--id', request.id, '--by', 'owner', '--decision', 'approved', '--json',
|
|
]);
|
|
assert.equal(unconfirmed.status, 2);
|
|
assert.equal(JSON.parse(unconfirmed.stdout).error, 'APPROVAL_REQUIRED');
|
|
const confirmed = run(root, [
|
|
'approvals', 'decide', '--id', request.id, '--by', 'owner', '--decision', 'approved',
|
|
'--revision', String(request.revision), '--yes', '--json',
|
|
]);
|
|
assert.equal(confirmed.status, 0, confirmed.stderr || confirmed.stdout);
|
|
assert.equal(JSON.parse(confirmed.stdout).status, 'approved');
|
|
});
|
|
|
|
test('SCN-cli-self-host: candidate acceptance, atomic upgrade, and rollback require explicit approval', () => {
|
|
const root = repo();
|
|
const v1 = releaseCandidate(root, '1.0.0');
|
|
const v2 = releaseCandidate(root, '1.1.0');
|
|
const plan = run(root, ['candidate', 'plan', '--source', v1, '--json']);
|
|
assert.equal(plan.status, 0, plan.stderr || plan.stdout);
|
|
assert.equal(JSON.parse(plan.stdout).version, '1.0.0');
|
|
const refused = run(root, ['candidate', 'build', '--source', v1, '--json']);
|
|
assert.equal(JSON.parse(refused.stdout).error, 'APPROVAL_REQUIRED');
|
|
|
|
for (const source of [v1, v2]) {
|
|
const built = run(root, ['candidate', 'build', '--source', source, '--yes', '--json']);
|
|
assert.equal(built.status, 0, built.stderr || built.stdout);
|
|
assert.equal(JSON.parse(built.stdout).mutationPerformed, true);
|
|
}
|
|
const before = JSON.parse(run(root, ['candidate', 'status', '--json']).stdout);
|
|
assert.equal(before.activeVersion, null);
|
|
assert.deepEqual(before.installed, ['1.0.0', '1.1.0']);
|
|
|
|
assert.equal(run(root, ['upgrade', '--version', '1.0.0', '--dry-run', '--json']).status, 0);
|
|
assert.equal(JSON.parse(run(root, ['upgrade', '--version', '1.0.0', '--json']).stdout).error, 'APPROVAL_REQUIRED');
|
|
assert.equal(run(root, ['upgrade', '--version', '1.0.0', '--yes', '--json']).status, 0);
|
|
assert.equal(run(root, ['upgrade', '--version', '1.1.0', '--yes', '--json']).status, 0);
|
|
|
|
const rollbackPlan = JSON.parse(run(root, ['rollback', '--dry-run', '--json']).stdout);
|
|
assert.deepEqual({ from: rollbackPlan.from, to: rollbackPlan.to }, { from: '1.1.0', to: '1.0.0' });
|
|
assert.equal(JSON.parse(run(root, ['rollback', '--json']).stdout).error, 'APPROVAL_REQUIRED');
|
|
const rolledBack = run(root, ['rollback', '--yes', '--json']);
|
|
assert.equal(rolledBack.status, 0, rolledBack.stderr || rolledBack.stdout);
|
|
assert.equal(JSON.parse(rolledBack.stdout).activeVersion, '1.0.0');
|
|
});
|
|
|
|
test('SCN-cli-backlog: public commands create, transition, assign, and list tracked work', () => {
|
|
const root = repo();
|
|
const proposalPath = join(root, 'fleet-proposal.json');
|
|
run(root, ['domains', '--output', proposalPath, '--json']);
|
|
const proposal = JSON.parse(readFileSync(proposalPath, 'utf8'));
|
|
proposal.review = {
|
|
status: 'approved', reviewedAt: '2026-09-27T00:00:00Z', reviewedBy: 'fixture-owner', notes: [],
|
|
};
|
|
writeFileSync(proposalPath, `${JSON.stringify(proposal, null, 2)}\n`);
|
|
assert.equal(run(root, ['install', '--proposal', proposalPath, '--yes', '--json']).status, 0);
|
|
execFileSync('git', ['-C', root, 'add', '.torch', 'fleet-proposal.json']);
|
|
execFileSync('git', ['-C', root, 'commit', '-m', 'install torch']);
|
|
const worktreeParent = join(tmpdir(), `${basename(root)}-worktrees`);
|
|
const worktrees = run(root, ['worktrees', '--parent', worktreeParent, '--yes', '--json']);
|
|
assert.equal(worktrees.status, 0, worktrees.stderr || worktrees.stdout);
|
|
|
|
const created = run(root, [
|
|
'backlog', 'create', '--area', 'session-manager', '--title', 'CLI task',
|
|
'--description', 'Exercise public lifecycle.', '--accept', 'Assigned to owner',
|
|
'--domains', proposal.domains[0].id, '--feature', 'World pipeline', '--milestone', 'Alpha', '--json',
|
|
]);
|
|
assert.equal(created.status, 0, created.stderr || created.stdout);
|
|
let task = JSON.parse(created.stdout);
|
|
assert.equal(task.feature, 'World pipeline');
|
|
assert.equal(task.milestone, 'Alpha');
|
|
const classified = run(root, [
|
|
'backlog', 'classify', '--task', task.id, '--area', 'session-manager',
|
|
'--revision', String(task.revision), '--milestone', 'Beta',
|
|
'--reason', 'Resequence this work to the next checkpoint.', '--json',
|
|
]);
|
|
assert.equal(classified.status, 0, classified.stderr || classified.stdout);
|
|
task = JSON.parse(classified.stdout);
|
|
assert.equal(task.feature, 'World pipeline');
|
|
assert.equal(task.milestone, 'Beta');
|
|
for (const transition of [
|
|
['ready'],
|
|
['assigned', '--owner', proposal.domains[0].id],
|
|
]) {
|
|
const result = run(root, [
|
|
'backlog', 'transition', '--task', task.id, '--area', 'session-manager',
|
|
'--state', ...transition, '--revision', String(task.revision), '--json',
|
|
]);
|
|
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
task = JSON.parse(result.stdout);
|
|
}
|
|
assert.equal(task.state, 'assigned');
|
|
assert.equal(task.owner, proposal.domains[0].id);
|
|
const next = JSON.parse(run(root, [
|
|
'backlog', 'next', '--area', proposal.domains[0].id, '--json',
|
|
]).stdout);
|
|
assert.equal(next.disposition, 'resume');
|
|
assert.equal(next.task.id, task.id);
|
|
assert.equal(next.mutationPerformed, false);
|
|
const health = JSON.parse(run(root, [
|
|
'backlog', 'health', '--stale-days', '0', '--json',
|
|
]).stdout);
|
|
assert.equal(health.mutationPerformed, false);
|
|
assert.equal(health.findings.some((finding) => finding.code === 'BACKLOG_ASSIGNED_STALE'), true);
|
|
const listed = JSON.parse(run(root, ['backlog', 'list', '--state', 'assigned', '--json']).stdout);
|
|
assert.equal(listed.tasks[0].id, task.id);
|
|
});
|