Files
stack/packages/control-board/tests/discord.test.mjs
T
jason.woltjeandClaude Opus 5.5 af4203ca92 feat(board): session attention, Discord rows, task attribution and relaunch activity (rows 18, 22, #1511, #1512)
One cumulative control-board, webui and seat state. The four rows edit the
same files (scan.mjs, page.html, README.md, app.js), so they land together,
each on its own receipt:

- Row 18, Discord connector rows on the board (#1509): R3 approved by
  Darkwing and Dewey, Gitea comment 26257, manifest 254403b8. Jason
  accepted the visual test.
- Row 22, board attention status (#1503): Filbert approved R1, comment
  26248, manifest e40b58ec; restart receipt 26249.
- #1511, task attribution (row 6 code phase): R2 approved by Filbert and
  Dewey, manifest d4c96395. docs/TOOLS.md carries the approved --by usage
  line (tools-usage.patch 86bcba3c).
- #1512, relaunch activity (row 6 pilot): R1 approved by Darkwing and
  Dewey, candidate manifest 47769fad. All seven source files match it.

Row 16, internal development bootstrap (#1510): the seven files outside
shared records match Filbert's R1 pins, receipt 26204 (agents/researcher/*,
scripts/test-darkwing-launch.mjs, the bootstrap plan).

packages/webui/src/public/app.js is committed at its #1512 R1 pin ce7d79a4.
The working copy holds Dewey's unreviewed return-flow candidate on top of
that, and it stays uncommitted.

Also: the four row briefs and Darkwing's evidence records under
agents/darkwing/work, including the 2026-09-26 tree manifest and the #1512
re-run against 21e3e908. Serial acceptance command: 397/397, three runs.
The failures that only show when tests run concurrently are in #1509 engine
tests, and they reproduce on clean HEAD.

Suites on the exact staged tree: config 24, task 90, foundation 43,
conductor 17, release 14, auth 15, discord 63; package union 397/397
(serial); test-darkwing-launch 5/5.

Shared records (BUILD-LOG, QUEUE, CURRENT, DEFERRED, SESSIONS, AGENTS.md,
agents/README.md) follow in Sage's records commit.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-26 14:54:18 -05:00

146 lines
10 KiB
JavaScript

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, mkdirSync, writeFileSync, chmodSync, symlinkSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { spawnSync } from 'node:child_process';
import { join } from 'node:path';
import { discoverDiscordAgents, inspectDiscord } from '../src/discord.mjs';
import { scan, scanAgent } from '../src/scan.mjs';
import { replyToRow, startServer } from '../src/serve.mjs';
import { identityOf } from '../../discord/src/journal.mjs';
import { envelope } from '../../discord/src/context.mjs';
import { makeRegistration, writeRegistration } from '../../seat/src/seat.mjs';
test('STOP access failure is unknown, not absence, under a non-root identity', () => {
const result = spawnSync(process.execPath, ['--input-type=module', '-e', `
import assert from 'node:assert/strict';
import { mkdtempSync, mkdirSync, writeFileSync, chmodSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { inspectDiscord } from ${JSON.stringify(new URL('../src/discord.mjs', import.meta.url).href)};
assert.notEqual(process.getuid(), 0, 'permission regression must not run as root');
const root = mkdtempSync(join(tmpdir(), 'board-stop-permission-'));
const dir = join(root, 'journal'), connector = { binding: 'fixture', journalDir: dir };
mkdirSync(dir);
try {
assert.equal(inspectDiscord(connector).braked, false, 'verified absence');
writeFileSync(join(dir, 'STOP'), 'private contents never read');
assert.equal(inspectDiscord(connector).braked, true, 'verified presence');
chmodSync(dir, 0o000);
const inaccessible = inspectDiscord(connector);
assert.equal(inaccessible.braked, null, 'inaccessible STOP is unknown');
assert.equal(inaccessible.alive, false);
chmodSync(dir, 0o700);
assert.equal(inspectDiscord(connector).braked, true, 'restored access sees STOP');
rmSync(join(dir, 'STOP'));
assert.equal(inspectDiscord(connector).braked, false, 'verified removal');
} finally { chmodSync(dir, 0o700); rmSync(root, { recursive: true, force: true }); }
`], { encoding: 'utf8', timeout: 10000, ...(process.getuid() === 0 ? { uid: 65534, gid: 65534 } : {}) });
assert.equal(result.status, 0, result.stdout + result.stderr);
});
test('connector Task never inherits Discord routing envelopes; ordinary Task still uses user text', () => {
const f = fixture();
try {
const text = envelope({ guildName: 'Synthetic guild', channelName: 'synthetic-channel', authorId: '111111111111111111', messageId: '222222222222222222', text: 'Synthetic user request' });
const sessionsDir = join(f.root, 'sessions', 'discord-demo');
writeFileSync(join(sessionsDir, 's.jsonl'), JSON.stringify({ type: 'message', message: { role: 'user', content: [{ type: 'text', text }] } }) + '\n');
const connector = scanAgent(discoverDiscordAgents(f.root).specs[0]);
assert.equal(connector.task, 'Discord connector');
assert.equal(connector.taskSource, 'connector');
for (const secret of ['111111111111111111', '222222222222222222', 'Synthetic user request']) assert.equal(connector.task.includes(secret), false);
const ordinary = scanAgent({ agent: 'native', project: 'repo', sessionsDir, tmux: {} }, { isAlive: () => true });
assert.equal(ordinary.task, text.replace(/\s+/g, ' ').trim());
assert.equal(ordinary.taskSource, 'first-user-message');
} finally { f.cleanup(); }
});
function fixture() {
const root = mkdtempSync(join(tmpdir(), 'board-discord-'));
mkdirSync(join(root, 'discord', 'demo', 'run.lock'), { recursive: true });
writeFileSync(join(root, 'discord', 'demo.json'), JSON.stringify({ name: 'demo', seat: 'researcher', tokenFile: '/never-read-secret', guildId: 'PRIVATE-ID', users: ['PRIVATE-USER'] }), { mode: 0o600 });
const sessions = join(root, 'sessions', 'discord-demo'); mkdirSync(sessions, { recursive: true });
writeFileSync(join(sessions, 's.jsonl'), JSON.stringify({ type: 'message', timestamp: '2026-09-14T00:00:00Z', message: { role: 'assistant', stopReason: 'stop', content: [{ type: 'text', text: 'done' }] } }) + '\n');
const owner = join(root, 'discord', 'demo', 'run.lock', 'owner.json');
writeFileSync(owner, JSON.stringify({ pid: process.pid, ...identityOf(process.pid) }));
return { root, owner, cleanup: () => rmSync(root, { recursive: true, force: true }) };
}
test('connector discovery keeps only safe identity; rejects modes, mismatches, links and traversal', () => {
const f = fixture();
try {
const result = discoverDiscordAgents(f.root);
assert.equal(result.specs.length, 1); assert.deepEqual(result.errors, []);
assert.equal(result.specs[0].agent, 'researcher (discord: demo)');
assert.equal(JSON.stringify(result).includes('PRIVATE'), false); assert.equal(JSON.stringify(result).includes('tokenFile'), false);
writeFileSync(join(f.root, 'discord', 'bad.json'), '{"name":"bad","seat":"../../escape","secret":"PRIVATE"}', { mode: 0o600 });
symlinkSync(join(f.root, 'discord', 'demo.json'), join(f.root, 'discord', 'linked.json'));
writeFileSync(join(f.root, 'discord', 'other.json'), '{"name":"wrong","seat":"x"}', { mode: 0o600 });
chmodSync(join(f.root, 'discord', 'demo.json'), 0o644);
const bad = discoverDiscordAgents(f.root); assert.equal(bad.specs.length, 0); assert.equal(bad.errors.length, 4);
assert.equal(JSON.stringify(bad).includes('PRIVATE'), false);
} finally { f.cleanup(); }
});
test('canonical owner identity and STOP are independent; no tmux fallback or forged registration', () => {
const f = fixture();
try {
const spec = discoverDiscordAgents(f.root).specs[0];
const row = () => scanAgent(spec, { isAlive: () => { throw Error('must not ask tmux'); }, registration: { pid: process.pid, task: 'forged', taskSetBy: 'forged-setter', tmux: { session: 'forged' } } });
assert.equal(row().alive, true); assert.equal(row().state, 'idle'); assert.equal(row().registered, null);
// #1511: the connector's fixed task borrows no attribution from a native registration.
assert.equal(row().task, 'Discord connector'); assert.equal(row().taskSource, 'connector'); assert.equal(row().taskSetBy, null);
assert.equal(JSON.stringify(row()).includes('forged-setter'), false);
writeFileSync(join(f.root, 'discord', 'demo', 'STOP'), 'PRIVATE BRAKE CONTENT');
assert.equal(row().connector.braked, true); assert.equal(row().state, 'idle');
assert.equal(JSON.stringify(row()).includes('PRIVATE'), false);
assert.equal(inspectDiscord(spec.connector, { identity: () => ({ start: '1', boot: identityOf(process.pid).boot }) }).alive, false);
assert.equal(inspectDiscord(spec.connector, { identity: () => ({ start: null, boot: null }) }).ownerState, 'unknown');
assert.equal(inspectDiscord(spec.connector, { identity: () => ({ start: identityOf(process.pid).start, boot: '00000000-0000-0000-0000-000000000000' }) }).ownerState, 'mismatch');
for (const value of ['broken PRIVATE', JSON.stringify({ pid: process.pid }), JSON.stringify({ pid: 2147483647, ...identityOf(process.pid) })]) {
writeFileSync(f.owner, value); assert.equal(row().alive, false); assert.equal(row().state, 'offline');
}
rmSync(f.owner); assert.equal(row().connector.ownerState, 'absent');
symlinkSync('/never-read-secret', f.owner); assert.equal(row().alive, false);
} finally { f.cleanup(); }
});
test('connector reply refusal precedes forged live tmux registration; ordinary agent still sends', () => {
let calls = 0; const exec = () => { calls++; return { status: 0, stdout: '', stderr: '' }; };
const r = { agent: 'researcher (discord: demo)', project: 'fleet', connector: { binding: 'demo' }, registered: { alive: true, tmux: { session: 'forged' } } };
assert.equal(replyToRow({ index: { sessions: [r] }, key: 'fleet/' + r.agent, text: 'hello', exec }).status, 409);
delete r.connector;
assert.equal(replyToRow({ index: { sessions: [r] }, key: 'fleet/' + r.agent, text: 'hello', exec }).status, 409);
assert.equal(calls, 0);
r.agent = 'ordinary'; assert.equal(replyToRow({ index: { sessions: [r] }, key: 'fleet/ordinary', text: 'hello', exec }).status, 200); assert.equal(calls, 1);
});
test('server rescans connector discovery and refuses HTTP reply without transport', async () => {
const f = fixture(); let server;
try {
const sessionsDir = join(f.root, 'sessions', 'discord-demo'), seatsDir = join(f.root, 'seats');
writeRegistration(seatsDir, makeRegistration({ resolved: { seat: 'forged', project: 'fleet', sessionsDir, seatDir: sessionsDir, launchScript: join(f.root, 'unused.sh'), layout: 'repo', defaultWorkspace: f.root }, task: 'forged', tmux: { session: 'forged', socket: null }, pid: process.pid }));
server = await startServer({ port: 0, specs: [], seatsDir, boardDir: join(f.root, 'board'), discordDataRoot: f.root, exec: () => { throw Error('transport forbidden'); } });
const base = `http://127.0.0.1:${server.address().port}`;
let response = await fetch(base + '/api/board'); const board = await response.json();
assert.equal(board.sessions[0].state, 'idle'); assert.equal(board.sessions[0].connector.alive, true);
assert.equal(board.sessions[0].registered, null); assert.notEqual(board.sessions[0].task, 'forged');
response = await fetch(base + '/api/reply', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ agent: 'fleet/researcher (discord: demo)', text: 'hello' }) });
assert.equal(response.status, 409);
rmSync(join(f.root, 'discord', 'demo.json'));
assert.equal((await (await fetch(base + '/api/board')).json()).sessions.length, 0);
} finally { if (server) await new Promise(r => server.close(r)); f.cleanup(); }
});
test('connector session links and linked directories are not read', () => {
const f = fixture();
try {
const sessions = join(f.root, 'sessions', 'discord-demo'); rmSync(sessions, { recursive: true });
symlinkSync(join(f.root, 'discord'), sessions);
const result = scan([], { boardDir: join(f.root, 'board'), discordDataRoot: f.root });
assert.equal(result.sessions[0].sessionFile, null);
rmSync(join(f.root, 'discord'), { recursive: true }); symlinkSync('/never-read-secret', join(f.root, 'discord'));
assert.equal(discoverDiscordAgents(f.root).specs.length, 0);
} finally { f.cleanup(); }
});