Add browser deploy recon backend with port scan, web crawl, and deploy lane recommendations.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled

POST /api/v1/recon/scan probes fleet ports from the server host, crawls owned HTTP targets, maps findings to spread lanes, and records optional oath ledger rows.
This commit is contained in:
AetherForge
2026-06-07 11:23:10 -07:00
parent 483eafa9dd
commit f3e5a9a07d
40 changed files with 3091 additions and 4 deletions

View File

@@ -0,0 +1,51 @@
import { expect, test } from '@playwright/test';
import { loginToDashboard } from './fixtures';
const MOCK_REPORT = {
host: 'e2e-recon.lab',
scanned_at: '2026-06-07T12:00:00Z',
ports: [
{ port: 22, open: false },
{ port: 80, open: true },
{ port: 445, open: true },
{ port: 5985, open: false },
],
crawl: {
pages_fetched: 1,
ssrf_score: 35,
url_fields: [{ page_url: 'http://e2e-recon.lab/', name: 'fetch_url', hint: 'url' }],
cms_fingerprints: ['wordpress'],
multipart_forms: [],
},
recommendations: [{ lane: 'spread_smb_unc', reason: 'SMB open', priority: 50 }],
};
test.describe('Deploy Recon smoke', () => {
test.beforeEach(async ({ page }) => {
await page.route('**/api/v1/recon/scan', async (route) => {
await route.fulfill({ json: MOCK_REPORT });
});
await loginToDashboard(page);
await page.goto('/deploy-recon');
await expect(page.getByRole('heading', { level: 1, name: /Deploy Recon/i })).toBeVisible({
timeout: 10_000,
});
});
test('sidebar link and scan results render', async ({ page }) => {
await page.getByTestId('dr-host-input').fill('e2e-recon.lab');
await page.getByTestId('dr-scan-btn').click();
await expect(page.getByTestId('dr-results')).toBeVisible({ timeout: 10_000 });
await expect(page.getByTestId('dr-port-matrix')).toBeVisible();
await expect(page.getByTestId('dr-finding-ssrf')).toBeVisible();
await expect(page.getByRole('link', { name: /Fleet spread to e2e-recon\.lab/i })).toBeVisible();
});
test('/browser-spread alias redirects to deploy recon', async ({ page }) => {
await page.goto('/browser-spread');
await expect(page).toHaveURL(/\/deploy-recon/);
await expect(page.getByRole('heading', { level: 1, name: /Deploy Recon/i })).toBeVisible({
timeout: 10_000,
});
});
});

View File

@@ -78,15 +78,22 @@ export async function expectOnlineCrucibleCard(page: Page, hostname: string): Pr
export async function loginToDashboard(page: Page): Promise<void> {
const commandDeck = page.getByRole('heading', { name: 'Command Deck' });
const loginHeading = page.getByRole('heading', { name: 'AetherForge' });
for (let attempt = 0; attempt < 3; attempt++) {
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30_000 });
await page.goto('/', { waitUntil: 'load', timeout: 30_000 });
if (await page.getByText('No frontend configured').isVisible().catch(() => false)) {
throw new Error(
'Dashboard SPA missing — run `npm run build` in server/web (webroot must exist before phase 8)',
);
}
await page.locator('#root').waitFor({ state: 'attached', timeout: 15_000 }).catch(() => {});
if (await commandDeck.isVisible().catch(() => false)) {
return;
}
const loginHeading = page.getByRole('heading', { name: 'AetherForge' });
try {
await expect(loginHeading).toBeVisible({ timeout: 25_000 });
} catch (err) {

View File

@@ -48,4 +48,43 @@ test.describe('Page smoke', () => {
await expect(page.getByRole('heading', { name: 'Quick Forge' })).toBeVisible();
await expect(page.locator('.forge-mode-toggle').getByRole('button', { name: 'Simple', exact: true }).first()).toBeVisible();
});
test('Deploy Recon renders scan form', async ({ page }) => {
await page.goto('/deploy-recon');
await expect(page.getByRole('heading', { level: 1, name: /Deploy Recon/i })).toBeVisible({
timeout: 10_000,
});
await expect(page.getByTestId('dr-scan-btn')).toBeVisible();
});
test('Emberwake Cloud Ecosystem panel expands', async ({ page }) => {
await page.goto('/emberwake');
await expect(page.getByRole('heading', { level: 1, name: /Emberwake/i })).toBeVisible({ timeout: 10_000 });
await page.getByText('Cloud ecosystem').click();
await expect(page.getByTestId('cloud-spread-panel')).toBeVisible({ timeout: 10_000 });
await expect(page.getByTestId('cloud-method-s3-cloudfront')).toBeVisible();
await expect(page.getByTestId('cloud-method-minio')).toBeVisible();
await expect(page.getByText('AWS ecosystem')).toBeVisible();
});
test('Seer page is read-only stream', async ({ page }) => {
await page.goto('/seer');
await expect(page.getByRole('heading', { name: /The Seer/i })).toBeVisible({ timeout: 10_000 });
await expect(page.getByText(/FLEET AI STREAM/i)).toBeVisible();
await expect(page.getByText(/SEER NOTES/i)).toBeVisible();
await expect(page.getByRole('link', { name: /Oath Ledger/i })).toBeVisible();
await expect(page.getByRole('textbox')).toHaveCount(0);
await expect(page.getByRole('searchbox')).toHaveCount(0);
});
test('Oath ledger renders accountability table', async ({ page }) => {
await page.goto('/oath');
await expect(page.getByRole('heading', { name: /Operator Oath Ledger/i })).toBeVisible({
timeout: 10_000,
});
await expect(page.getByRole('columnheader', { name: 'Time' })).toBeVisible();
await expect(page.getByRole('columnheader', { name: 'Action' })).toBeVisible();
await expect(page.getByRole('link', { name: /Seer reasoning/i })).toBeVisible();
await expect(page.getByText(/No oath rows yet|Loading…/)).toBeVisible({ timeout: 15_000 });
});
});