import { describe, it, expect, beforeEach, afterEach } from 'vitest'; 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 { buildFleetCommsBlock, renderPeerReach, readFleetCommsBlock, resolveCommsBlock, 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', ' host: w-jarvis', ' - name: coder0-0', ' runtime: claude', ' class: implementer', ' host: 10.1.10.37', ' ssh: jwoltje@10.1.10.37', '', ].join('\n'); 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', }); }); it('rejects unknown fields instead of leniently constructing a second roster view', () => { expect(() => parseFleetRosterV1(`${ROSTER}\nunknown: value\n`, 'yaml')).toThrow( /unknown field/i, ); }); 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', ), ), ).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 = { version: 1, transport: 'tmux', tmux: {}, defaults: {}, runtimes: { claude: {} }, agents: [{ name: 'a', runtime: 'claude', class: 'worker' }], }; const assign = ( root: Record, path: readonly (string | number)[], value: unknown, ) => { let cursor: unknown = root; for (const segment of path.slice(0, -1)) { cursor = (cursor as Record)[segment]; } (cursor as Record)[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 — 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 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('uses only the explicit roster ssh target for a cross-host peer', () => { const peer: FleetAgent = { ...base, name: 'coder0-0', host: '10.1.10.37', ssh: 'jwoltje@10.1.10.37', }; 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'; it('renders authoritative identity, exact rows, generation, and no operational metavariables', () => { const block = buildFleetCommsBlock({ selfName: 'enhancer', roster: roster(), localHost: 'ignored-process-host', agentSendPath: send, }); expect(block).toContain('# Fleet Comms'); 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*\|/); 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('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: 'enhancer', roster: bad, localHost: 'w-jarvis', agentSendPath: send, }), ).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('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('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('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('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\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, '\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\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\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\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 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 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', () => { const noRoster = mkdtempSync(join(tmpdir(), 'mosaic-noroster-')); 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 result = resolveCommsBlock(home, undefined); expect(result.ok).toBe(false); expect(result.error).toContain('requires'); }); });