Add fleet topology epidemiology: strain plague map and mining interrupt fixes.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled

Strain nodes replace host nodes in FleetTopologyMap; server queues epidemiology_fix on auth and reports interrupts to AI and Seer.
This commit is contained in:
AetherForge
2026-06-07 09:19:48 -07:00
parent bbab38f8e1
commit c8b3b2a126
15 changed files with 673 additions and 34 deletions

View File

@@ -365,6 +365,15 @@ export const api = {
'/fleet/modules/push',
{ method: 'POST', body: JSON.stringify(body) },
),
postFleetGraft: (body: { source_agent_id: string; target_agent_id: string }) =>
fetchJSON<{
success: boolean;
source_id?: string;
target_id?: string;
graft_policy?: Record<string, unknown>;
pushed?: boolean;
error?: string;
}>('/fleet/graft', { method: 'POST', body: JSON.stringify(body) }),
listStrainCards: (agentId?: string) =>
fetchJSON<import('../types').StrainCard[]>(
agentId ? `/fleet/strain-cards?agent_id=${encodeURIComponent(agentId)}` : '/fleet/strain-cards',

View File

@@ -231,6 +231,21 @@ describe('AccessDepthPanel', () => {
expect(screen.getByText(/parent 11112222/i)).toBeInTheDocument();
});
it('shows genealogy graft note when fleet AI and roles enabled', async () => {
vi.mocked(api.getConfig).mockResolvedValueOnce({
server: { ai_control_enabled: true, fleet_roles_enabled: true },
} as Awaited<ReturnType<typeof api.getConfig>>);
renderPanel(
mockAgent({
platform: 'windows',
graft_tier: 'winrm',
graft_source_strain: '#aabbcc',
}),
);
expect(await screen.findByText(/genealogy graft pending/i)).toBeInTheDocument();
expect(screen.getByText(/winrm/)).toBeInTheDocument();
});
it('renders lineage strain card with play control', async () => {
vi.mocked(api.listStrainCards).mockResolvedValueOnce([
{

View File

@@ -99,6 +99,7 @@ describe('PathTracerPage', () => {
installIntervalHook();
useWebSocketMock.mockReturnValue(wsValue());
vi.spyOn(api, 'listAgents').mockResolvedValue([]);
vi.spyOn(api, 'getConfig').mockResolvedValue({ server: {} });
vi.spyOn(api, 'startTrace').mockResolvedValue({ session_id: 'sess-1', hops: [] });
vi.spyOn(api, 'getTraceStatus').mockResolvedValue({ session_id: 'sess-1', ready: false, hops: [] });
vi.spyOn(api, 'getTraceQR').mockResolvedValue({ config: 'wg-conf', qr_png_b64: 'abc123' });
@@ -150,6 +151,14 @@ describe('PathTracerPage', () => {
expect(screen.getByText(/Path Tracer/i)).toBeInTheDocument();
});
it('shows genealogy graft note when AI control and fleet roles enabled', async () => {
vi.mocked(api.getConfig).mockResolvedValue({
server: { ai_control_enabled: true, fleet_roles_enabled: true },
} as Awaited<ReturnType<typeof api.getConfig>>);
renderPage();
expect(await screen.findByText(/Genealogy grafting is active/i)).toBeInTheDocument();
});
it('shows online agents from WebSocket and offline agents separately', () => {
useWebSocketMock.mockReturnValue(
wsValue({ agents: [windowsAgent, linuxAgent, offlineAgent] }),