feat(tess): add roster-bound tmux fleet provider (#724)
This commit was merged in pull request #724.
This commit is contained in:
261
packages/agent/src/tmux-fleet-runtime-provider.test.ts
Normal file
261
packages/agent/src/tmux-fleet-runtime-provider.test.ts
Normal file
@@ -0,0 +1,261 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import type { RuntimeScope } from '@mosaicstack/types';
|
||||
import {
|
||||
type FleetReadAuthority,
|
||||
type FleetWriteAuthority,
|
||||
TmuxFleetRuntimeProvider,
|
||||
} from './tmux-fleet-runtime-provider.js';
|
||||
import type {
|
||||
FleetRuntimeProviderError,
|
||||
FleetRuntimeTarget,
|
||||
FleetRuntimeTransport,
|
||||
} from './tmux-fleet-runtime-provider.js';
|
||||
|
||||
const scope: RuntimeScope = {
|
||||
actorId: 'operator-1',
|
||||
tenantId: 'tenant-a',
|
||||
channelId: 'discord-1',
|
||||
correlationId: 'corr-1',
|
||||
};
|
||||
|
||||
const target: FleetRuntimeTarget = {
|
||||
id: 'coder0',
|
||||
runtimeId: 'codex',
|
||||
socketName: 'tess-fleet',
|
||||
};
|
||||
|
||||
function transport(): FleetRuntimeTransport {
|
||||
return {
|
||||
verifySession: vi.fn(async (): Promise<FleetRuntimeTarget> => target),
|
||||
listSessions: vi.fn(async (): Promise<FleetRuntimeTarget[]> => [target]),
|
||||
sendMessage: vi.fn(async (): Promise<void> => undefined),
|
||||
terminate: vi.fn(async (): Promise<void> => undefined),
|
||||
};
|
||||
}
|
||||
|
||||
function readAuthority(): FleetReadAuthority {
|
||||
return { canRead: vi.fn(async (): Promise<boolean> => true) };
|
||||
}
|
||||
|
||||
describe('TmuxFleetRuntimeProvider security policy', (): void => {
|
||||
it('advertises only fleet operations it can safely implement', async (): Promise<void> => {
|
||||
const provider = new TmuxFleetRuntimeProvider({ transport: transport() });
|
||||
|
||||
await expect(provider.capabilities(scope)).resolves.toEqual({
|
||||
supported: [
|
||||
'session.list',
|
||||
'session.tree',
|
||||
'session.send',
|
||||
'session.attach',
|
||||
'session.terminate',
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects control attach without consulting the tmux transport', async (): Promise<void> => {
|
||||
const fleet = transport();
|
||||
const provider = new TmuxFleetRuntimeProvider({ transport: fleet });
|
||||
|
||||
await expect(provider.attach('coder0', 'control', scope)).rejects.toMatchObject({
|
||||
code: 'forbidden',
|
||||
} satisfies Partial<FleetRuntimeProviderError>);
|
||||
expect(fleet.verifySession).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('denies fleet listing without an exact-scope read authority decision', async (): Promise<void> => {
|
||||
const fleet = transport();
|
||||
const provider = new TmuxFleetRuntimeProvider({ transport: fleet });
|
||||
|
||||
await expect(provider.listSessions(scope)).rejects.toMatchObject({
|
||||
code: 'forbidden',
|
||||
} satisfies Partial<FleetRuntimeProviderError>);
|
||||
expect(fleet.listSessions).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('denies read attachment without an exact-scope read authority decision', async (): Promise<void> => {
|
||||
const fleet = transport();
|
||||
const provider = new TmuxFleetRuntimeProvider({ transport: fleet });
|
||||
|
||||
await expect(provider.attach('coder0', 'read', scope)).rejects.toMatchObject({
|
||||
code: 'forbidden',
|
||||
} satisfies Partial<FleetRuntimeProviderError>);
|
||||
expect(fleet.verifySession).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('creates a read-only attachment only after exact target verification', async (): Promise<void> => {
|
||||
const fleet = transport();
|
||||
const provider = new TmuxFleetRuntimeProvider({
|
||||
transport: fleet,
|
||||
readAuthority: readAuthority(),
|
||||
attachmentIdFactory: (): string => 'attachment-1',
|
||||
now: (): Date => new Date('2026-07-12T00:00:00.000Z'),
|
||||
});
|
||||
|
||||
await expect(provider.attach('coder0', 'read', scope)).resolves.toEqual({
|
||||
attachmentId: 'attachment-1',
|
||||
sessionId: 'coder0',
|
||||
mode: 'read',
|
||||
expiresAt: '2026-07-12T00:05:00.000Z',
|
||||
});
|
||||
expect(fleet.verifySession).toHaveBeenCalledWith('coder0');
|
||||
});
|
||||
|
||||
it('denies attachment-handle replay from another immutable actor scope', async (): Promise<void> => {
|
||||
const fleet = transport();
|
||||
const provider = new TmuxFleetRuntimeProvider({
|
||||
transport: fleet,
|
||||
readAuthority: readAuthority(),
|
||||
attachmentIdFactory: (): string => 'attachment-1',
|
||||
now: (): Date => new Date('2026-07-12T00:00:00.000Z'),
|
||||
});
|
||||
await provider.attach('coder0', 'read', scope);
|
||||
|
||||
await expect(
|
||||
provider.detach('attachment-1', { ...scope, actorId: 'operator-2' }),
|
||||
).rejects.toMatchObject({
|
||||
code: 'forbidden',
|
||||
} satisfies Partial<FleetRuntimeProviderError>);
|
||||
});
|
||||
|
||||
it('keeps attachment scope immutable after caller-side scope mutation', async (): Promise<void> => {
|
||||
const mutableScope = { ...scope };
|
||||
const provider = new TmuxFleetRuntimeProvider({
|
||||
transport: transport(),
|
||||
readAuthority: readAuthority(),
|
||||
attachmentIdFactory: (): string => 'attachment-1',
|
||||
now: (): Date => new Date('2026-07-12T00:00:00.000Z'),
|
||||
});
|
||||
await provider.attach('coder0', 'read', mutableScope);
|
||||
mutableScope.actorId = 'operator-2';
|
||||
|
||||
await expect(provider.detach('attachment-1', scope)).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('denies expired attachment handles and removes them', async (): Promise<void> => {
|
||||
let now = new Date('2026-07-12T00:00:00.000Z');
|
||||
const provider = new TmuxFleetRuntimeProvider({
|
||||
transport: transport(),
|
||||
readAuthority: readAuthority(),
|
||||
attachmentIdFactory: (): string => 'attachment-1',
|
||||
now: (): Date => now,
|
||||
});
|
||||
await provider.attach('coder0', 'read', scope);
|
||||
now = new Date('2026-07-12T00:05:00.001Z');
|
||||
|
||||
await expect(provider.detach('attachment-1', scope)).rejects.toMatchObject({
|
||||
code: 'forbidden',
|
||||
} satisfies Partial<FleetRuntimeProviderError>);
|
||||
await expect(provider.detach('attachment-1', scope)).rejects.toMatchObject({
|
||||
code: 'not_found',
|
||||
} satisfies Partial<FleetRuntimeProviderError>);
|
||||
});
|
||||
|
||||
it('prunes expired attachment handles before creating a new handle', async (): Promise<void> => {
|
||||
let now = new Date('2026-07-12T00:00:00.000Z');
|
||||
let attachmentSequence = 0;
|
||||
const provider = new TmuxFleetRuntimeProvider({
|
||||
transport: transport(),
|
||||
readAuthority: readAuthority(),
|
||||
attachmentIdFactory: (): string => `attachment-${++attachmentSequence}`,
|
||||
now: (): Date => now,
|
||||
});
|
||||
await provider.attach('coder0', 'read', scope);
|
||||
now = new Date('2026-07-12T00:05:00.001Z');
|
||||
await provider.attach('coder0', 'read', scope);
|
||||
|
||||
await expect(provider.detach('attachment-1', scope)).rejects.toMatchObject({
|
||||
code: 'not_found',
|
||||
} satisfies Partial<FleetRuntimeProviderError>);
|
||||
});
|
||||
|
||||
it('rejects an empty message before consulting write authority or tmux', async (): Promise<void> => {
|
||||
const fleet = transport();
|
||||
const writeAuthority: FleetWriteAuthority = {
|
||||
canWrite: vi.fn(async (): Promise<boolean> => true),
|
||||
assertAuthorized: vi.fn(async (): Promise<void> => undefined),
|
||||
};
|
||||
const provider = new TmuxFleetRuntimeProvider({ transport: fleet, writeAuthority });
|
||||
|
||||
await expect(
|
||||
provider.sendMessage('coder0', { content: '', idempotencyKey: 'message-1' }, scope),
|
||||
).rejects.toMatchObject({
|
||||
code: 'invalid_request',
|
||||
} satisfies Partial<FleetRuntimeProviderError>);
|
||||
expect(writeAuthority.canWrite).not.toHaveBeenCalled();
|
||||
expect(writeAuthority.assertAuthorized).not.toHaveBeenCalled();
|
||||
expect(fleet.verifySession).not.toHaveBeenCalled();
|
||||
expect(fleet.sendMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('denies fleet writes by default before probing the tmux transport', async (): Promise<void> => {
|
||||
const fleet = transport();
|
||||
const provider = new TmuxFleetRuntimeProvider({ transport: fleet });
|
||||
|
||||
await expect(
|
||||
provider.sendMessage('coder0', { content: 'hello', idempotencyKey: 'message-1' }, scope),
|
||||
).rejects.toMatchObject({ code: 'forbidden' } satisfies Partial<FleetRuntimeProviderError>);
|
||||
await expect(provider.terminate('coder0', 'approval-1', scope)).rejects.toMatchObject({
|
||||
code: 'forbidden',
|
||||
} satisfies Partial<FleetRuntimeProviderError>);
|
||||
expect(fleet.verifySession).not.toHaveBeenCalled();
|
||||
expect(fleet.sendMessage).not.toHaveBeenCalled();
|
||||
expect(fleet.terminate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('rejects an unverified target before consulting Mos write authority', async (): Promise<void> => {
|
||||
const fleet = transport();
|
||||
fleet.verifySession = vi.fn(async (): Promise<FleetRuntimeTarget> => {
|
||||
throw new Error('target identity mismatch');
|
||||
});
|
||||
const writeAuthority: FleetWriteAuthority = {
|
||||
canWrite: vi.fn(async (): Promise<boolean> => true),
|
||||
assertAuthorized: vi.fn(async (): Promise<void> => undefined),
|
||||
};
|
||||
const provider = new TmuxFleetRuntimeProvider({ transport: fleet, writeAuthority });
|
||||
|
||||
await expect(
|
||||
provider.sendMessage('coder', { content: 'hello', idempotencyKey: 'message-1' }, scope),
|
||||
).rejects.toThrow('target identity mismatch');
|
||||
expect(writeAuthority.assertAuthorized).not.toHaveBeenCalled();
|
||||
expect(fleet.sendMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('passes an exact session ID to the fleet transport only through authorized Mos writes', async (): Promise<void> => {
|
||||
const fleet = transport();
|
||||
const writeAuthority: FleetWriteAuthority = {
|
||||
canWrite: vi.fn(async (): Promise<boolean> => true),
|
||||
assertAuthorized: vi.fn(async (): Promise<void> => undefined),
|
||||
};
|
||||
const provider = new TmuxFleetRuntimeProvider({
|
||||
transport: fleet,
|
||||
sourceLabel: 'tess',
|
||||
writeAuthority,
|
||||
});
|
||||
|
||||
await provider.sendMessage('coder0', { content: 'hello', idempotencyKey: 'message-1' }, scope);
|
||||
await provider.terminate('coder0', 'approval-1', scope);
|
||||
|
||||
expect(writeAuthority.assertAuthorized).toHaveBeenCalledWith({
|
||||
operation: 'session.send',
|
||||
sessionId: 'coder0',
|
||||
scope,
|
||||
});
|
||||
expect(writeAuthority.assertAuthorized).toHaveBeenCalledWith({
|
||||
operation: 'session.terminate',
|
||||
sessionId: 'coder0',
|
||||
scope,
|
||||
approvalRef: 'approval-1',
|
||||
});
|
||||
expect(fleet.sendMessage).toHaveBeenCalledWith('coder0', 'hello', 'tess');
|
||||
expect(fleet.terminate).toHaveBeenCalledWith('coder0');
|
||||
});
|
||||
|
||||
it('fails closed when consumers ask for session streaming', async (): Promise<void> => {
|
||||
const provider = new TmuxFleetRuntimeProvider({ transport: transport() });
|
||||
const stream = provider.streamSession('coder0', undefined, scope)[Symbol.asyncIterator]();
|
||||
|
||||
await expect(stream.next()).rejects.toMatchObject({
|
||||
code: 'capability_unsupported',
|
||||
} satisfies Partial<FleetRuntimeProviderError>);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user