agent-ecosystem/agent-teams-controller/test/cascadeGuard.test.js
iliya c3eea4d6eb feat: add cross-team communication orchestrator
Autonomous message routing between Agent Teams via MCP with cascade
protection. Inbox-first canonical delivery with cross-process file
lock (O_CREAT|O_EXCL) and best-effort relay for online teams.

- CascadeGuard: rate limit (10/min), chain depth (max 5), pair cooldown (3s)
- FileLock: cross-process safe via kernel-level atomic lock files
- CrossTeamService: validate → cascade → file-lock → inbox write → relay → outbox
- Unified lead resolver with members.meta.json normalization (trim+dedup)
- 3 MCP tools: cross_team_send, cross_team_list_targets, cross_team_get_outbox
- Controller module with sync file lock, same protocol as main
- IPC adapter with 3 Electron handlers
- 64 new tests across 8 test files
2026-03-09 18:45:15 +02:00

58 lines
1.7 KiB
JavaScript

const cascadeGuard = require('../src/internal/cascadeGuard.js');
describe('cascadeGuard', () => {
beforeEach(() => {
cascadeGuard.reset();
});
describe('rate limit', () => {
it('allows up to 10 messages per minute', () => {
for (let i = 0; i < 10; i++) {
cascadeGuard.check('team-a', `team-${i}`, 0);
cascadeGuard.record('team-a', `team-${i}`);
}
expect(() => cascadeGuard.check('team-a', 'team-x', 0)).toThrow('rate limit');
});
});
describe('chain depth', () => {
it('allows depth 0 through 4', () => {
for (let d = 0; d < 5; d++) {
expect(() => cascadeGuard.check('team-a', 'team-b', d)).not.toThrow();
}
});
it('rejects depth >= 5', () => {
expect(() => cascadeGuard.check('team-a', 'team-b', 5)).toThrow('chain depth');
});
});
describe('pair cooldown', () => {
it('rejects same pair within 3s', () => {
cascadeGuard.check('team-a', 'team-b', 0);
cascadeGuard.record('team-a', 'team-b');
expect(() => cascadeGuard.check('team-a', 'team-b', 0)).toThrow('cooldown');
});
it('allows different pairs simultaneously', () => {
cascadeGuard.check('team-a', 'team-b', 0);
cascadeGuard.record('team-a', 'team-b');
expect(() => cascadeGuard.check('team-a', 'team-c', 0)).not.toThrow();
});
});
describe('reset', () => {
it('clears all state', () => {
for (let i = 0; i < 10; i++) {
cascadeGuard.check('team-a', `team-${i}`, 0);
cascadeGuard.record('team-a', `team-${i}`);
}
cascadeGuard.reset();
expect(() => cascadeGuard.check('team-a', 'team-0', 0)).not.toThrow();
});
});
});