fix(fleet): enforce exact comms authority (#787)
This commit was merged in pull request #787.
This commit is contained in:
@@ -1,30 +1,43 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
||||
import {
|
||||
chmodSync,
|
||||
mkdtempSync,
|
||||
mkdirSync,
|
||||
writeFileSync,
|
||||
rmSync,
|
||||
readFileSync,
|
||||
symlinkSync,
|
||||
} from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { parseFleetRosterV1, type FleetRoster, type FleetAgent } from './fleet-roster-v1.js';
|
||||
import {
|
||||
parseRosterAgents,
|
||||
buildFleetCommsBlock,
|
||||
renderPeerReach,
|
||||
readFleetCommsBlock,
|
||||
resolveCommsBlock,
|
||||
type CommsPeer,
|
||||
resolvePeerCommand,
|
||||
renderToolsContractStatus,
|
||||
} from './comms-onboarding.js';
|
||||
|
||||
const ROSTER = [
|
||||
'version: 1',
|
||||
'transport: tmux',
|
||||
'tmux:',
|
||||
' socket_name: mosaic-fleet',
|
||||
'agents:',
|
||||
' - name: orchestrator',
|
||||
' runtime: claude',
|
||||
' class: orchestrator',
|
||||
' host: w-jarvis',
|
||||
' - name: enhancer',
|
||||
' runtime: claude',
|
||||
' class: enhancer',
|
||||
' host: w-jarvis',
|
||||
' - name: coder0',
|
||||
' runtime: pi',
|
||||
' class: implementer',
|
||||
' # a manually-listed cross-host peer (pre-federation stopgap)',
|
||||
' host: w-jarvis',
|
||||
' - name: coder0-0',
|
||||
' runtime: claude',
|
||||
' class: implementer',
|
||||
@@ -33,206 +46,687 @@ const ROSTER = [
|
||||
'',
|
||||
].join('\n');
|
||||
|
||||
describe('parseRosterAgents', () => {
|
||||
it('parses name + class + optional host/ssh', () => {
|
||||
const peers = parseRosterAgents(ROSTER);
|
||||
expect(peers.map((p) => p.name)).toEqual(['orchestrator', 'enhancer', 'coder0', 'coder0-0']);
|
||||
expect(peers.find((p) => p.name === 'coder0')).toMatchObject({ className: 'implementer' });
|
||||
expect(peers.find((p) => p.name === 'coder0-0')).toMatchObject({
|
||||
className: 'implementer',
|
||||
function roster(source = ROSTER): FleetRoster {
|
||||
return parseFleetRosterV1(source, 'yaml');
|
||||
}
|
||||
|
||||
describe('shared fleet roster v1 resolver', () => {
|
||||
it('resolves comms fields and the global socket through the canonical roster contract', () => {
|
||||
const resolved = roster();
|
||||
expect(resolved.tmux.socketName).toBe('mosaic-fleet');
|
||||
expect(resolved.agents.find((agent) => agent.name === 'coder0-0')).toMatchObject({
|
||||
className: 'code',
|
||||
host: '10.1.10.37',
|
||||
ssh: 'jwoltje@10.1.10.37',
|
||||
});
|
||||
// local agents have no host/ssh
|
||||
expect(peers.find((p) => p.name === 'orchestrator')!.host).toBeUndefined();
|
||||
});
|
||||
|
||||
it('parses an optional per-agent socket', () => {
|
||||
const peers = parseRosterAgents(
|
||||
['agents:', ' - name: a', ' class: worker', ' socket: mosaic-fleet'].join('\n'),
|
||||
it('rejects unknown fields instead of leniently constructing a second roster view', () => {
|
||||
expect(() => parseFleetRosterV1(`${ROSTER}\nunknown: value\n`, 'yaml')).toThrow(
|
||||
/unknown field/i,
|
||||
);
|
||||
expect(peers[0]).toMatchObject({ name: 'a', socket: 'mosaic-fleet' });
|
||||
});
|
||||
|
||||
it('stops at the next top-level key', () => {
|
||||
const peers = parseRosterAgents(
|
||||
['agents:', ' - name: a', ' class: worker', 'defaults:', ' working_directory: ~'].join(
|
||||
'\n',
|
||||
it('rejects an unsupported independent per-agent socket instead of targeting a nonexistent session', () => {
|
||||
expect(() =>
|
||||
roster(
|
||||
ROSTER.replace(
|
||||
' host: w-jarvis\n - name: coder0',
|
||||
' host: w-jarvis\n socket: other-socket\n - name: coder0',
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(peers.map((p) => p.name)).toEqual(['a']);
|
||||
).toThrow(/independent per-agent sockets are not supported/i);
|
||||
});
|
||||
|
||||
it('rejects unsafe operational targeting values', () => {
|
||||
expect(() =>
|
||||
roster(ROSTER.replace(' ssh: jwoltje@10.1.10.37', ' ssh: host;touch-owned')),
|
||||
).toThrow(/unsupported targeting characters/i);
|
||||
});
|
||||
|
||||
it('normalizes matching connector settings for YAML and JSON rosters', () => {
|
||||
const yamlSource = `${ROSTER}connector:\n kind: discord\n discord:\n channel_id: "123"\n`;
|
||||
const jsonSource = JSON.stringify({
|
||||
version: 1,
|
||||
transport: 'tmux',
|
||||
agents: [{ name: 'a', runtime: 'claude', class: 'worker' }],
|
||||
connector: {
|
||||
kind: 'matrix',
|
||||
matrix: {
|
||||
homeserver_url: 'https://matrix.example',
|
||||
user_id: '@a:example',
|
||||
room_id: '!room:example',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(parseFleetRosterV1(yamlSource, 'yaml').connector).toEqual({
|
||||
kind: 'discord',
|
||||
discord: { channelId: '123' },
|
||||
});
|
||||
expect(parseFleetRosterV1(jsonSource, 'json').connector).toEqual({
|
||||
kind: 'matrix',
|
||||
matrix: {
|
||||
homeserverUrl: 'https://matrix.example',
|
||||
userId: '@a:example',
|
||||
roomId: '!room:example',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
['discord channel_id', { kind: 'discord', discord: { channel_id: '' } }],
|
||||
['discord channel_id whitespace', { kind: 'discord', discord: { channel_id: ' ' } }],
|
||||
[
|
||||
'matrix homeserver_url',
|
||||
{
|
||||
kind: 'matrix',
|
||||
matrix: { homeserver_url: '', user_id: '@a:example', room_id: '!room:example' },
|
||||
},
|
||||
],
|
||||
[
|
||||
'matrix homeserver_url whitespace',
|
||||
{
|
||||
kind: 'matrix',
|
||||
matrix: { homeserver_url: '\t', user_id: '@a:example', room_id: '!room:example' },
|
||||
},
|
||||
],
|
||||
[
|
||||
'matrix user_id',
|
||||
{
|
||||
kind: 'matrix',
|
||||
matrix: {
|
||||
homeserver_url: 'https://matrix.example',
|
||||
user_id: '',
|
||||
room_id: '!room:example',
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
'matrix user_id whitespace',
|
||||
{
|
||||
kind: 'matrix',
|
||||
matrix: {
|
||||
homeserver_url: 'https://matrix.example',
|
||||
user_id: ' ',
|
||||
room_id: '!room:example',
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
'matrix room_id',
|
||||
{
|
||||
kind: 'matrix',
|
||||
matrix: {
|
||||
homeserver_url: 'https://matrix.example',
|
||||
user_id: '@a:example',
|
||||
room_id: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
'matrix room_id whitespace',
|
||||
{
|
||||
kind: 'matrix',
|
||||
matrix: {
|
||||
homeserver_url: 'https://matrix.example',
|
||||
user_id: '@a:example',
|
||||
room_id: '\n',
|
||||
},
|
||||
},
|
||||
],
|
||||
])(
|
||||
'rejects empty or whitespace-only parser-required connector string: %s',
|
||||
(_label, connector) => {
|
||||
expect(() =>
|
||||
parseFleetRosterV1(
|
||||
JSON.stringify({
|
||||
version: 1,
|
||||
transport: 'tmux',
|
||||
agents: [{ name: 'a', runtime: 'claude', class: 'worker' }],
|
||||
connector,
|
||||
}),
|
||||
'json',
|
||||
),
|
||||
).toThrow(/required/i);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
['tmux with discord settings', { kind: 'tmux', discord: { channel_id: '123' } }],
|
||||
['discord without discord settings', { kind: 'discord' }],
|
||||
[
|
||||
'discord with matrix settings',
|
||||
{ kind: 'discord', discord: { channel_id: '123' }, matrix: {} },
|
||||
],
|
||||
['matrix without matrix settings', { kind: 'matrix' }],
|
||||
[
|
||||
'matrix with discord settings',
|
||||
{
|
||||
kind: 'matrix',
|
||||
matrix: {
|
||||
homeserver_url: 'https://matrix.example',
|
||||
user_id: '@a:example',
|
||||
room_id: '!room:example',
|
||||
},
|
||||
discord: { channel_id: '123' },
|
||||
},
|
||||
],
|
||||
])('rejects connector kind/settings mismatch: %s', (_label, connector) => {
|
||||
expect(() =>
|
||||
parseFleetRosterV1(
|
||||
JSON.stringify({
|
||||
version: 1,
|
||||
transport: 'tmux',
|
||||
agents: [{ name: 'a', runtime: 'claude', class: 'worker' }],
|
||||
connector,
|
||||
}),
|
||||
'json',
|
||||
),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it.each([
|
||||
['tmux socket', ['tmux', 'socket_name'], ['tmux', 'socketName'], 'same', 'different'],
|
||||
['tmux holder', ['tmux', 'holder_session'], ['tmux', 'holderSession'], 'same', 'different'],
|
||||
[
|
||||
'defaults working directory',
|
||||
['defaults', 'working_directory'],
|
||||
['defaults', 'workingDirectory'],
|
||||
'same',
|
||||
'different',
|
||||
],
|
||||
[
|
||||
'runtime reset command',
|
||||
['runtimes', 'claude', 'reset_command'],
|
||||
['runtimes', 'claude', 'resetCommand'],
|
||||
'same',
|
||||
'different',
|
||||
],
|
||||
[
|
||||
'agent working directory',
|
||||
['agents', 0, 'working_directory'],
|
||||
['agents', 0, 'workingDirectory'],
|
||||
'same',
|
||||
'different',
|
||||
],
|
||||
[
|
||||
'agent model hint',
|
||||
['agents', 0, 'model_hint'],
|
||||
['agents', 0, 'modelHint'],
|
||||
'same',
|
||||
'different',
|
||||
],
|
||||
[
|
||||
'agent reasoning level',
|
||||
['agents', 0, 'reasoning_level'],
|
||||
['agents', 0, 'reasoningLevel'],
|
||||
'same',
|
||||
'different',
|
||||
],
|
||||
[
|
||||
'agent tool policy',
|
||||
['agents', 0, 'tool_policy'],
|
||||
['agents', 0, 'toolPolicy'],
|
||||
'same',
|
||||
'different',
|
||||
],
|
||||
[
|
||||
'agent persistent persona',
|
||||
['agents', 0, 'persistent_persona'],
|
||||
['agents', 0, 'persistentPersona'],
|
||||
true,
|
||||
false,
|
||||
],
|
||||
[
|
||||
'agent reset between tasks',
|
||||
['agents', 0, 'reset_between_tasks'],
|
||||
['agents', 0, 'resetBetweenTasks'],
|
||||
true,
|
||||
false,
|
||||
],
|
||||
[
|
||||
'agent kickstart template',
|
||||
['agents', 0, 'kickstart_template'],
|
||||
['agents', 0, 'kickstartTemplate'],
|
||||
'same',
|
||||
'different',
|
||||
],
|
||||
] as const)(
|
||||
'rejects conflicting %s aliases and accepts identical aliases',
|
||||
(_label, snake, camel, same, different) => {
|
||||
const base: Record<string, unknown> = {
|
||||
version: 1,
|
||||
transport: 'tmux',
|
||||
tmux: {},
|
||||
defaults: {},
|
||||
runtimes: { claude: {} },
|
||||
agents: [{ name: 'a', runtime: 'claude', class: 'worker' }],
|
||||
};
|
||||
const assign = (
|
||||
root: Record<string, unknown>,
|
||||
path: readonly (string | number)[],
|
||||
value: unknown,
|
||||
) => {
|
||||
let cursor: unknown = root;
|
||||
for (const segment of path.slice(0, -1)) {
|
||||
cursor = (cursor as Record<string | number, unknown>)[segment];
|
||||
}
|
||||
(cursor as Record<string | number, unknown>)[path.at(-1)!] = value;
|
||||
};
|
||||
assign(base, snake, same);
|
||||
assign(base, camel, different);
|
||||
expect(() => parseFleetRosterV1(JSON.stringify(base), 'json')).toThrow(
|
||||
/aliases .* conflict/i,
|
||||
);
|
||||
assign(base, camel, same);
|
||||
expect(() => parseFleetRosterV1(JSON.stringify(base), 'json')).not.toThrow();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('renderPeerReach — same-host vs cross-host', () => {
|
||||
describe('renderPeerReach — exact same-host/cross-host/socket targeting', () => {
|
||||
const send = '/home/u/.config/mosaic/tools/tmux/agent-send.sh';
|
||||
const base: FleetAgent = {
|
||||
name: 'peer',
|
||||
runtime: 'claude',
|
||||
className: 'worker',
|
||||
};
|
||||
|
||||
it('renders the short form for a same-host peer', () => {
|
||||
const peer: CommsPeer = { name: 'enhancer', className: 'enhancer' };
|
||||
expect(renderPeerReach(peer, 'w-jarvis', send)).toBe(`${send} -s enhancer -m "…"`);
|
||||
});
|
||||
|
||||
it('renders the -H form for a cross-host peer using ssh', () => {
|
||||
const peer: CommsPeer = {
|
||||
name: 'coder0-0',
|
||||
className: 'implementer',
|
||||
host: '10.1.10.37',
|
||||
ssh: 'jwoltje@10.1.10.37',
|
||||
};
|
||||
expect(renderPeerReach(peer, 'w-jarvis', send)).toBe(
|
||||
`${send} -H jwoltje@10.1.10.37 -s coder0-0 -m "…"`,
|
||||
it('renders the global named socket and omits -H for a same-host peer', () => {
|
||||
expect(renderPeerReach(base, 'w-jarvis', 'w-jarvis', 'mosaic-fleet', send)).toBe(
|
||||
`${send} -L mosaic-fleet -s peer -m "…"`,
|
||||
);
|
||||
});
|
||||
|
||||
it('falls back to host when a cross-host peer has no ssh', () => {
|
||||
const peer: CommsPeer = { name: 'x', className: 'worker', host: '10.0.0.9' };
|
||||
expect(renderPeerReach(peer, 'w-jarvis', send)).toBe(`${send} -H 10.0.0.9 -s x -m "…"`);
|
||||
});
|
||||
|
||||
it('treats a peer whose host equals the fleet host as same-host', () => {
|
||||
const peer: CommsPeer = { name: 'y', className: 'worker', host: 'w-jarvis' };
|
||||
expect(renderPeerReach(peer, 'w-jarvis', send)).toBe(`${send} -s y -m "…"`);
|
||||
});
|
||||
|
||||
it('emits NO -L for an unset/default socket', () => {
|
||||
const peer: CommsPeer = { name: 'lead', className: 'orchestrator' };
|
||||
expect(renderPeerReach(peer, 'w-jarvis', send)).toBe(`${send} -s lead -m "…"`);
|
||||
});
|
||||
|
||||
it('emits -L <socket> for a named socket', () => {
|
||||
const peer: CommsPeer = { name: 'coder0', className: 'implementer', socket: 'mosaic-fleet' };
|
||||
expect(renderPeerReach(peer, 'w-jarvis', send)).toBe(
|
||||
`${send} -L mosaic-fleet -s coder0 -m "…"`,
|
||||
);
|
||||
});
|
||||
|
||||
it('combines -L (named socket) and -H (cross-host) in order', () => {
|
||||
const peer: CommsPeer = {
|
||||
it('uses only the explicit roster ssh target for a cross-host peer', () => {
|
||||
const peer: FleetAgent = {
|
||||
...base,
|
||||
name: 'coder0-0',
|
||||
className: 'implementer',
|
||||
host: '10.1.10.37',
|
||||
ssh: 'jwoltje@10.1.10.37',
|
||||
socket: 'mosaic-fleet',
|
||||
};
|
||||
expect(renderPeerReach(peer, 'w-jarvis', send)).toBe(
|
||||
expect(renderPeerReach(peer, 'w-jarvis', 'w-jarvis', 'mosaic-fleet', send)).toBe(
|
||||
`${send} -L mosaic-fleet -H jwoltje@10.1.10.37 -s coder0-0 -m "…"`,
|
||||
);
|
||||
});
|
||||
|
||||
it('fails closed when a cross-host peer has no explicit roster ssh target', () => {
|
||||
const peer: FleetAgent = { ...base, name: 'x', host: '10.0.0.9' };
|
||||
expect(() => renderPeerReach(peer, 'w-jarvis', 'w-jarvis', 'mosaic-fleet', send)).toThrow(
|
||||
/explicit roster ssh target/i,
|
||||
);
|
||||
});
|
||||
|
||||
it('renders only the fleet-wide supported socket', () => {
|
||||
const peer: FleetAgent = { ...base, socket: 'mosaic-fleet' };
|
||||
expect(renderPeerReach(peer, 'w-jarvis', 'w-jarvis', 'mosaic-fleet', send)).toBe(
|
||||
`${send} -L mosaic-fleet -s peer -m "…"`,
|
||||
);
|
||||
});
|
||||
|
||||
it('resolves hostless peers against the stable fleet-host baseline, not the viewer host', () => {
|
||||
const peer: FleetAgent = { ...base, ssh: 'fleet-user@w-jarvis' };
|
||||
expect(renderPeerReach(peer, 'remote-host', 'w-jarvis', 'mosaic-fleet', send)).toBe(
|
||||
`${send} -L mosaic-fleet -H fleet-user@w-jarvis -s peer -m "…"`,
|
||||
);
|
||||
});
|
||||
|
||||
it('shell-quotes an exact helper path that contains spaces', () => {
|
||||
expect(
|
||||
renderPeerReach(base, 'w-jarvis', 'w-jarvis', 'mosaic-fleet', '/home/test user/send.sh'),
|
||||
).toBe(`'/home/test user/send.sh' -L mosaic-fleet -s peer -m "…"`);
|
||||
});
|
||||
|
||||
it('omits -L only for the literal default socket', () => {
|
||||
expect(renderPeerReach(base, 'w-jarvis', 'w-jarvis', '', send)).toBe(`${send} -s peer -m "…"`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildFleetCommsBlock', () => {
|
||||
const send = '/h/.config/mosaic/tools/tmux/agent-send.sh';
|
||||
const agents = parseRosterAgents(ROSTER);
|
||||
|
||||
it('excludes self, lists peers, flags the orchestrator, and emits both address forms', () => {
|
||||
it('renders authoritative identity, exact rows, generation, and no operational metavariables', () => {
|
||||
const block = buildFleetCommsBlock({
|
||||
selfName: 'enhancer',
|
||||
agents,
|
||||
fleetHost: 'w-jarvis',
|
||||
roster: roster(),
|
||||
localHost: 'ignored-process-host',
|
||||
agentSendPath: send,
|
||||
});
|
||||
|
||||
expect(block).toContain('# Fleet Comms');
|
||||
expect(block).toContain('You are **enhancer**');
|
||||
// criterion 1: agent's own [host:session] identity
|
||||
expect(block).toContain('`[w-jarvis:enhancer]`');
|
||||
// self excluded
|
||||
expect(block).toContain('Host: `w-jarvis`');
|
||||
expect(block).toContain('Agent/session: `enhancer`');
|
||||
expect(block).toContain('tmux socket: `mosaic-fleet`');
|
||||
expect(block).toContain(`Helper: \`${send}\``);
|
||||
expect(block).toMatch(/Comms generation: `[a-f0-9]{64}`/);
|
||||
expect(block).not.toMatch(/\|\s*enhancer\s*\|/);
|
||||
// peers present
|
||||
expect(block).toContain('| orchestrator |');
|
||||
expect(block).toContain('point of contact');
|
||||
// same-host peer short form
|
||||
expect(block).toContain(`${send} -s coder0 -m "…"`);
|
||||
// cross-host peer -H form + host annotation
|
||||
expect(block).toContain(`${send} -H jwoltje@10.1.10.37 -s coder0-0 -m "…"`);
|
||||
expect(block).toContain('host `10.1.10.37`');
|
||||
// conventions
|
||||
expect(block).toContain('FLIP the preamble');
|
||||
expect(block).toContain('ACCEPTED');
|
||||
expect(block).toContain(`${send} -L mosaic-fleet -s orchestrator -m "…"`);
|
||||
expect(block).toContain(`${send} -L mosaic-fleet -H jwoltje@10.1.10.37 -s coder0-0 -m "…"`);
|
||||
expect(block).toContain(`mosaic agent comms-block enhancer`);
|
||||
expect(block).toMatch(/Never invent, substitute, or fuzzy-match/i);
|
||||
expect(block).not.toMatch(
|
||||
/<(?:user@host|src_host|src_session|dst_host|dst_session|target-session)>/,
|
||||
);
|
||||
expect(block).not.toContain('FLIP the preamble');
|
||||
});
|
||||
|
||||
it('returns empty when the agent has no peers', () => {
|
||||
expect(
|
||||
it('changes the generation when a rendered peer role changes', () => {
|
||||
const generation = (block: string) => block.match(/Comms generation: `([a-f0-9]{64})`/)?.[1];
|
||||
const before = buildFleetCommsBlock({
|
||||
selfName: 'enhancer',
|
||||
roster: roster(),
|
||||
localHost: 'w-jarvis',
|
||||
agentSendPath: send,
|
||||
});
|
||||
const changedRoster = roster(ROSTER.replace('class: implementer', 'class: reviewer'));
|
||||
const after = buildFleetCommsBlock({
|
||||
selfName: 'enhancer',
|
||||
roster: changedRoster,
|
||||
localHost: 'w-jarvis',
|
||||
agentSendPath: send,
|
||||
});
|
||||
expect(generation(before)).toMatch(/^[a-f0-9]{64}$/);
|
||||
expect(generation(after)).not.toBe(generation(before));
|
||||
});
|
||||
|
||||
it('fails closed when any rendered cross-host row lacks ssh', () => {
|
||||
const bad = roster(ROSTER.replace(' ssh: jwoltje@10.1.10.37\n', ''));
|
||||
expect(() =>
|
||||
buildFleetCommsBlock({
|
||||
selfName: 'solo',
|
||||
agents: [{ name: 'solo', className: 'orchestrator' }],
|
||||
fleetHost: 'h',
|
||||
selfName: 'enhancer',
|
||||
roster: bad,
|
||||
localHost: 'w-jarvis',
|
||||
agentSendPath: send,
|
||||
}),
|
||||
).toBe('');
|
||||
).toThrow(/explicit roster ssh target/i);
|
||||
});
|
||||
|
||||
it('still renders authoritative local identity when the agent has no peers', () => {
|
||||
const solo = roster(
|
||||
[
|
||||
'version: 1',
|
||||
'transport: tmux',
|
||||
'agents:',
|
||||
' - name: solo',
|
||||
' runtime: claude',
|
||||
' class: orchestrator',
|
||||
].join('\n'),
|
||||
);
|
||||
const block = buildFleetCommsBlock({
|
||||
selfName: 'solo',
|
||||
roster: solo,
|
||||
localHost: 'h',
|
||||
agentSendPath: send,
|
||||
});
|
||||
expect(block).toContain('Host: `h`');
|
||||
expect(block).toContain('Agent/session: `solo`');
|
||||
expect(block).toContain('Role/class: `orchestrator`');
|
||||
expect(block).toMatch(/Comms generation: `[a-f0-9]{64}`/);
|
||||
expect(block).toContain('This roster has no peers');
|
||||
expect(block).toContain('## Solo authority boundaries');
|
||||
expect(block).toContain('no peer, orchestrator, or remote communication authority');
|
||||
expect(block).toContain('Do not send, infer a target, or claim fleet coordination');
|
||||
});
|
||||
});
|
||||
|
||||
describe('readFleetCommsBlock — situational (the context a spawned agent gets)', () => {
|
||||
describe('resolvePeerCommand', () => {
|
||||
const send = '/h/.config/mosaic/tools/tmux/agent-send.sh';
|
||||
|
||||
it('returns one exact known-peer row', () => {
|
||||
const result = resolvePeerCommand(roster(), 'enhancer', 'coder0-0', 'w-jarvis', send);
|
||||
expect(result.ok).toBe(true);
|
||||
expect(result.command).toContain('-H jwoltje@10.1.10.37 -s coder0-0');
|
||||
});
|
||||
|
||||
it('fails closed for an unknown peer with exact-name discovery guidance', () => {
|
||||
const result = resolvePeerCommand(roster(), 'enhancer', 'invented-host', 'w-jarvis', send);
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.command).toBe('');
|
||||
expect(result.error).toContain('invented-host');
|
||||
expect(result.error).toContain('orchestrator, coder0, coder0-0');
|
||||
expect(result.error).toContain('mosaic agent comms-block enhancer');
|
||||
expect(result.error).not.toContain('tmux ls');
|
||||
});
|
||||
});
|
||||
|
||||
describe('readFleetCommsBlock — spawned-agent context', () => {
|
||||
let home: string;
|
||||
beforeEach(() => {
|
||||
home = mkdtempSync(join(tmpdir(), 'mosaic-comms-'));
|
||||
mkdirSync(join(home, 'fleet'), { recursive: true });
|
||||
mkdirSync(join(home, 'tools', 'tmux'), { recursive: true });
|
||||
writeFileSync(join(home, 'fleet', 'roster.yaml'), ROSTER);
|
||||
const helper = join(home, 'tools', 'tmux', 'agent-send.sh');
|
||||
writeFileSync(helper, '#!/bin/sh\n');
|
||||
chmodSync(helper, 0o755);
|
||||
});
|
||||
afterEach(() => rmSync(home, { recursive: true, force: true }));
|
||||
|
||||
it('builds the cheat-sheet with correct peer addresses for a fleet member', () => {
|
||||
const block = readFleetCommsBlock(home, 'orchestrator', 'w-jarvis');
|
||||
expect(block).toContain('# Fleet Comms');
|
||||
expect(block).toContain('| enhancer |');
|
||||
expect(block).toContain(`${join(home, 'tools', 'tmux', 'agent-send.sh')} -s coder0 -m "…"`);
|
||||
expect(block).toContain('-H jwoltje@10.1.10.37 -s coder0-0');
|
||||
expect(block).not.toMatch(/\|\s*orchestrator\s*\|/); // self excluded
|
||||
it('uses the authoritative self host and global socket from the shared roster resolver', () => {
|
||||
const result = readFleetCommsBlock(home, 'enhancer', 'process-host-must-not-win');
|
||||
expect(result.ok).toBe(true);
|
||||
expect(result.output).toContain('Host: `w-jarvis`');
|
||||
expect(result.output).toContain('tmux socket: `mosaic-fleet`');
|
||||
expect(result.output).toContain('-L mosaic-fleet -s orchestrator');
|
||||
});
|
||||
|
||||
it('returns empty when MOSAIC_AGENT_NAME is unset, no roster, or agent not a member', () => {
|
||||
expect(readFleetCommsBlock(home, undefined, 'w-jarvis')).toBe('');
|
||||
expect(readFleetCommsBlock(home, 'stranger', 'w-jarvis')).toBe('');
|
||||
expect(readFleetCommsBlock(mkdtempSync(join(tmpdir(), 'noroster-')), 'orchestrator')).toBe('');
|
||||
it('fails closed for a requested fleet identity that is absent', () => {
|
||||
const result = readFleetCommsBlock(home, 'stranger', 'w-jarvis');
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.output).toBe('');
|
||||
expect(result.error).toContain('Known exact names');
|
||||
});
|
||||
|
||||
it('resolves a supported JSON-only installed roster', () => {
|
||||
rmSync(join(home, 'fleet', 'roster.yaml'));
|
||||
writeFileSync(
|
||||
join(home, 'fleet', 'roster.json'),
|
||||
JSON.stringify({
|
||||
version: 1,
|
||||
transport: 'tmux',
|
||||
tmux: { socket_name: 'mosaic-fleet' },
|
||||
agents: [
|
||||
{ name: 'enhancer', runtime: 'claude', class: 'enhancer', host: 'w-jarvis' },
|
||||
{ name: 'orchestrator', runtime: 'claude', class: 'orchestrator', host: 'w-jarvis' },
|
||||
],
|
||||
}),
|
||||
);
|
||||
const result = readFleetCommsBlock(home, 'enhancer', 'process-host-must-not-win');
|
||||
expect(result.ok).toBe(true);
|
||||
expect(result.output).toContain('-L mosaic-fleet -s orchestrator');
|
||||
});
|
||||
|
||||
it('fails closed on a YAML I/O error instead of falling back to JSON', () => {
|
||||
rmSync(join(home, 'fleet', 'roster.yaml'));
|
||||
mkdirSync(join(home, 'fleet', 'roster.yaml'));
|
||||
writeFileSync(
|
||||
join(home, 'fleet', 'roster.json'),
|
||||
JSON.stringify({
|
||||
version: 1,
|
||||
transport: 'tmux',
|
||||
agents: [{ name: 'enhancer', runtime: 'claude', class: 'enhancer' }],
|
||||
}),
|
||||
);
|
||||
const result = readFleetCommsBlock(home, 'enhancer', 'w-jarvis');
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.error).toContain('invalid fleet roster at');
|
||||
expect(result.error).toContain('roster.yaml');
|
||||
});
|
||||
|
||||
it.each([
|
||||
['missing', () => rmSync(join(home, 'tools', 'tmux', 'agent-send.sh'))],
|
||||
[
|
||||
'directory',
|
||||
() => {
|
||||
rmSync(join(home, 'tools', 'tmux', 'agent-send.sh'));
|
||||
mkdirSync(join(home, 'tools', 'tmux', 'agent-send.sh'));
|
||||
},
|
||||
],
|
||||
[
|
||||
'symlink',
|
||||
() => {
|
||||
const helper = join(home, 'tools', 'tmux', 'agent-send.sh');
|
||||
rmSync(helper);
|
||||
writeFileSync(join(home, 'real-send.sh'), '#!/bin/sh\n');
|
||||
symlinkSync(join(home, 'real-send.sh'), helper);
|
||||
},
|
||||
],
|
||||
['non-executable', () => chmodSync(join(home, 'tools', 'tmux', 'agent-send.sh'), 0o644)],
|
||||
])('fails closed for a %s helper with deterministic repair guidance', (_case, mutate) => {
|
||||
mutate();
|
||||
const result = readFleetCommsBlock(home, 'enhancer', 'w-jarvis');
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.output).toBe('');
|
||||
expect(result.error).toContain('mosaic update --repair-tools');
|
||||
expect(result.error).toContain('no active context or session was rewritten');
|
||||
});
|
||||
|
||||
it('does not rewrite the roster while resolving context', () => {
|
||||
const path = join(home, 'fleet', 'roster.yaml');
|
||||
const before = readFileSync(path, 'utf8');
|
||||
readFleetCommsBlock(home, 'enhancer', 'w-jarvis');
|
||||
expect(readFileSync(path, 'utf8')).toBe(before);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveCommsBlock — `mosaic fleet comms-block <role>` emitter semantics', () => {
|
||||
// The emitter wraps readFleetCommsBlock but must NEVER print an empty string silently:
|
||||
// an unknown role / missing roster has to fail loud (caller maps !ok → stderr + exit 1)
|
||||
// so `mosaic fleet comms-block bogus` is a visible error, not a confusing no-op. The
|
||||
// success path returns the block verbatim for `mosaic fleet comms-block <peer>` previews.
|
||||
describe('renderToolsContractStatus — non-mutating install drift', () => {
|
||||
let home: string;
|
||||
|
||||
beforeEach(() => {
|
||||
home = mkdtempSync(join(tmpdir(), 'mosaic-tools-status-'));
|
||||
mkdirSync(join(home, 'defaults'), { recursive: true });
|
||||
writeFileSync(
|
||||
join(home, 'defaults', 'TOOLS.md'),
|
||||
'# authoritative tools\n<!-- fleet-comms-contract: 1 -->\n',
|
||||
);
|
||||
});
|
||||
afterEach(() => rmSync(home, { recursive: true, force: true }));
|
||||
|
||||
it('uses the supported repair command when installed TOOLS.md is missing', () => {
|
||||
const status = renderToolsContractStatus(home);
|
||||
expect(status).toContain('mosaic update --repair-tools');
|
||||
expect(status).not.toContain('--reseed');
|
||||
expect(status).toContain('authorized operator');
|
||||
});
|
||||
|
||||
it('reports stale preserved content without rewriting it', () => {
|
||||
const path = join(home, 'TOOLS.md');
|
||||
const stale = '# customized tools\n';
|
||||
writeFileSync(path, stale);
|
||||
const status = renderToolsContractStatus(home);
|
||||
expect(status).toContain('fleet-comms-contract: 1');
|
||||
expect(status).toContain('digest-qualified backup');
|
||||
expect(status).toContain('mosaic update --repair-tools');
|
||||
expect(status).toContain('active context was not rewritten');
|
||||
expect(readFileSync(path, 'utf8')).toBe(stale);
|
||||
});
|
||||
|
||||
it('does not accept marker-only customized content as current', () => {
|
||||
const path = join(home, 'TOOLS.md');
|
||||
writeFileSync(path, '<!-- fleet-comms-contract: 1 -->\ncorrupt\n');
|
||||
expect(renderToolsContractStatus(home)).toContain('does not byte-match');
|
||||
});
|
||||
|
||||
it('rejects markerless byte-equal source and installed content', () => {
|
||||
const content = '# markerless but equal\n';
|
||||
writeFileSync(join(home, 'defaults', 'TOOLS.md'), content);
|
||||
writeFileSync(join(home, 'TOOLS.md'), content);
|
||||
const status = renderToolsContractStatus(home);
|
||||
expect(status).toContain('source contract');
|
||||
expect(status).toContain('does not declare the expected');
|
||||
});
|
||||
|
||||
it.each([
|
||||
['source', join('defaults', 'TOOLS.md')],
|
||||
['installed', 'TOOLS.md'],
|
||||
])('rejects a wrong contract version in %s content', (_case, relativePath) => {
|
||||
const current = '# authoritative tools\n<!-- fleet-comms-contract: 1 -->\n';
|
||||
writeFileSync(join(home, 'TOOLS.md'), current);
|
||||
writeFileSync(join(home, 'defaults', 'TOOLS.md'), current);
|
||||
writeFileSync(join(home, relativePath), current.replace('contract: 1', 'contract: 2'));
|
||||
expect(renderToolsContractStatus(home)).not.toBe('');
|
||||
});
|
||||
|
||||
it('treats installed TOOLS.md symlinks as stale without following or rewriting them', () => {
|
||||
const external = join(home, 'external-tools.md');
|
||||
const externalContent = '# external\n<!-- fleet-comms-contract: 1 -->\n';
|
||||
writeFileSync(external, externalContent);
|
||||
symlinkSync(external, join(home, 'TOOLS.md'));
|
||||
|
||||
const status = renderToolsContractStatus(home);
|
||||
|
||||
expect(status).toContain('unavailable');
|
||||
expect(status).toContain('mosaic update --repair-tools');
|
||||
expect(readFileSync(external, 'utf8')).toBe(externalContent);
|
||||
});
|
||||
|
||||
it('treats source TOOLS.md symlinks as unavailable without following them', () => {
|
||||
const external = join(home, 'external-source.md');
|
||||
const content = '# authoritative tools\n<!-- fleet-comms-contract: 1 -->\n';
|
||||
writeFileSync(external, content);
|
||||
rmSync(join(home, 'defaults', 'TOOLS.md'));
|
||||
symlinkSync(external, join(home, 'defaults', 'TOOLS.md'));
|
||||
writeFileSync(join(home, 'TOOLS.md'), content);
|
||||
|
||||
const status = renderToolsContractStatus(home);
|
||||
|
||||
expect(status).toContain('source contract');
|
||||
expect(status).toContain('unavailable');
|
||||
expect(readFileSync(external, 'utf8')).toBe(content);
|
||||
});
|
||||
|
||||
it('accepts byte-equal bounded source and installed contracts', () => {
|
||||
const source = readFileSync(join(home, 'defaults', 'TOOLS.md'), 'utf8');
|
||||
writeFileSync(join(home, 'TOOLS.md'), source);
|
||||
expect(renderToolsContractStatus(home)).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveCommsBlock — mosaic agent comms-block', () => {
|
||||
let home: string;
|
||||
beforeEach(() => {
|
||||
home = mkdtempSync(join(tmpdir(), 'mosaic-commsblk-'));
|
||||
mkdirSync(join(home, 'fleet'), { recursive: true });
|
||||
mkdirSync(join(home, 'tools', 'tmux'), { recursive: true });
|
||||
writeFileSync(join(home, 'fleet', 'roster.yaml'), ROSTER);
|
||||
const helper = join(home, 'tools', 'tmux', 'agent-send.sh');
|
||||
writeFileSync(helper, '#!/bin/sh\n');
|
||||
chmodSync(helper, 0o755);
|
||||
});
|
||||
afterEach(() => rmSync(home, { recursive: true, force: true }));
|
||||
|
||||
it('returns ok + the cheat-sheet for a roster member', () => {
|
||||
const res = resolveCommsBlock(home, 'orchestrator', 'w-jarvis');
|
||||
expect(res.ok).toBe(true);
|
||||
expect(res.output).toContain('# Fleet Comms');
|
||||
expect(res.output).toContain('| enhancer |');
|
||||
expect(res.error).toBeUndefined();
|
||||
it('returns the exact contract for a roster member', () => {
|
||||
const result = resolveCommsBlock(home, 'enhancer');
|
||||
expect(result.ok).toBe(true);
|
||||
expect(result.output).toContain('Host: `w-jarvis`');
|
||||
expect(result.error).toBeUndefined();
|
||||
});
|
||||
|
||||
it('fails loud (not ok + error naming the role) for a non-member — never silently empty', () => {
|
||||
const res = resolveCommsBlock(home, 'stranger', 'w-jarvis');
|
||||
expect(res.ok).toBe(false);
|
||||
expect(res.output).toBe('');
|
||||
expect(res.error).toContain('stranger');
|
||||
it('fails loud and lists known exact names for a non-member', () => {
|
||||
const result = resolveCommsBlock(home, 'stranger');
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.output).toBe('');
|
||||
expect(result.error).toContain('stranger');
|
||||
expect(result.error).toContain('orchestrator');
|
||||
expect(result.error).toContain('enhancer');
|
||||
});
|
||||
|
||||
it('fails loud when no roster exists at the mosaic home', () => {
|
||||
it('fails loud when no roster exists', () => {
|
||||
const noRoster = mkdtempSync(join(tmpdir(), 'mosaic-noroster-'));
|
||||
const res = resolveCommsBlock(noRoster, 'orchestrator', 'w-jarvis');
|
||||
expect(res.ok).toBe(false);
|
||||
expect(res.error).toBeTruthy();
|
||||
mkdirSync(join(noRoster, 'tools', 'tmux'), { recursive: true });
|
||||
const helper = join(noRoster, 'tools', 'tmux', 'agent-send.sh');
|
||||
writeFileSync(helper, '#!/bin/sh\n');
|
||||
chmodSync(helper, 0o755);
|
||||
const result = resolveCommsBlock(noRoster, 'orchestrator');
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.error).toContain('no fleet roster');
|
||||
rmSync(noRoster, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('fails loud for a missing role argument', () => {
|
||||
const res = resolveCommsBlock(home, undefined, 'w-jarvis');
|
||||
expect(res.ok).toBe(false);
|
||||
expect(res.error).toBeTruthy();
|
||||
});
|
||||
|
||||
it('honors a host override so a peer can preview its own cross-host view', () => {
|
||||
// coder0-0 viewing with its own host → its self-identity line uses that host.
|
||||
const res = resolveCommsBlock(home, 'coder0-0', '10.1.10.37');
|
||||
expect(res.ok).toBe(true);
|
||||
expect(res.output).toContain('`[10.1.10.37:coder0-0]`');
|
||||
const result = resolveCommsBlock(home, undefined);
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.error).toContain('requires');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user