Add Fleet Torrent erasure extension with shard DHT and gossip.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Content-addressed shard DHT on seeder agents with subnet_primary_seeder election, cross-subnet fleet_torrent_gossip, BGP swarm magnets, C2 torrent manifest, and k-of-n peer fetch with C2 fallback.
This commit is contained in:
@@ -25,6 +25,7 @@ const LotlTimelinePage = lazy(() => import('./pages/LotlTimelinePage'));
|
||||
const ROIPage = lazy(() => import('./pages/ROIPage'));
|
||||
const ActivityFeedPage = lazy(() => import('./pages/ActivityFeedPage'));
|
||||
const SeerPage = lazy(() => import('./pages/SeerPage'));
|
||||
const OathLedgerPage = lazy(() => import('./pages/OathLedgerPage'));
|
||||
|
||||
export function PageFallback() {
|
||||
return (
|
||||
@@ -68,6 +69,7 @@ function App() {
|
||||
<Route path="/roi" element={<ROIPage />} />
|
||||
<Route path="/activity" element={<ActivityFeedPage />} />
|
||||
<Route path="/seer" element={<SeerPage />} />
|
||||
<Route path="/oath" element={<OathLedgerPage />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
</Layout>
|
||||
|
||||
@@ -389,6 +389,8 @@ export const api = {
|
||||
queued?: boolean;
|
||||
error?: string;
|
||||
}>('/fleet/play-strain-card', { method: 'POST', body: JSON.stringify(body) }),
|
||||
listOathLedger: (limit = 100) =>
|
||||
fetchJSON<import('../types').OathLedgerEntry[]>(`/fleet/oath-ledger?limit=${limit}`),
|
||||
|
||||
// Public builds (unauthenticated — used on login page)
|
||||
listPublicBuilds: async (): Promise<PublicBuildsResponse> => {
|
||||
|
||||
@@ -195,6 +195,17 @@ export default function CalibrationAIControl({ server, onUpdate }: Props) {
|
||||
<span>Enable adaptive strategy engine <HelpTip field="adaptive_strategy" /></span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="form-group checkbox-group" style={{ marginTop: '0.5rem' }}>
|
||||
<label className="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="checkbox"
|
||||
checked={server.fleet_torrent_enabled === true}
|
||||
onChange={(e) => onUpdate('server.fleet_torrent_enabled', e.target.checked)}
|
||||
/>
|
||||
<span>Fleet Torrent (shard DHT + gossip) <HelpTip field="fleet_torrent" /></span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="form-group checkbox-group" style={{ marginTop: '0.5rem' }}>
|
||||
<label className="checkbox-label">
|
||||
<input
|
||||
|
||||
@@ -10,6 +10,19 @@
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.access-depth-oath-link {
|
||||
margin-left: auto;
|
||||
font-size: 0.68rem;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-muted, #8899aa);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.access-depth-oath-link:hover {
|
||||
color: var(--neon-cyan, #00e8f5);
|
||||
}
|
||||
|
||||
.access-depth-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
|
||||
|
||||
@@ -188,6 +188,9 @@ export default function AccessDepthPanel({ agent, diagnostics }: Props) {
|
||||
</span>
|
||||
)}
|
||||
{!policyLoaded && <span className="access-depth-muted">loading policy…</span>}
|
||||
<Link to="/oath" className="access-depth-oath-link" title="Immutable operator accountability ledger">
|
||||
Oath
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="access-depth-grid">
|
||||
|
||||
@@ -108,6 +108,7 @@ describe('FIELD_HELP', () => {
|
||||
'fleet_phenotype',
|
||||
'failure_atlas',
|
||||
'erasure_lanes',
|
||||
'fleet_torrent',
|
||||
'ai_court_session',
|
||||
'ai_persona',
|
||||
'ai_persona_aggressive',
|
||||
@@ -193,6 +194,12 @@ describe('FIELD_HELP', () => {
|
||||
expect(FIELD_HELP.erasure_lanes).toMatch(/Foundation only/i);
|
||||
});
|
||||
|
||||
it('fleet_torrent describes shard DHT and cross-subnet gossip', () => {
|
||||
expect(FIELD_HELP.fleet_torrent).toMatch(/Fleet Torrent/i);
|
||||
expect(FIELD_HELP.fleet_torrent).toContain('subnet_primary_seeder');
|
||||
expect(FIELD_HELP.fleet_torrent).toMatch(/swarm_magnet|cross-subnet/i);
|
||||
});
|
||||
|
||||
it('documents honest AV limits — no invisible mining', () => {
|
||||
expect(FIELD_HELP.av_limits).toMatch(/100% invisible|not 100% invisible/i);
|
||||
expect(FIELD_HELP.av_limits).toMatch(/in-process RandomX/i);
|
||||
|
||||
105
server/web/src/pages/OathLedgerPage.css
Normal file
105
server/web/src/pages/OathLedgerPage.css
Normal file
@@ -0,0 +1,105 @@
|
||||
.oath-ledger-page {
|
||||
padding: 1.25rem 1.5rem 2rem;
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
.oath-ledger-header {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.oath-ledger-title {
|
||||
margin: 0 0 0.35rem;
|
||||
font-size: 1.35rem;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.oath-ledger-subtitle {
|
||||
margin: 0;
|
||||
color: var(--text-muted, #8899aa);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.oath-ledger-link {
|
||||
color: var(--accent-cyan, #00e8f5);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.oath-ledger-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.oath-ledger-error {
|
||||
margin-bottom: 0.75rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border: 1px solid rgba(255, 68, 68, 0.35);
|
||||
color: #ff8888;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.oath-ledger-table-wrap {
|
||||
overflow-x: auto;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 4px;
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.oath-ledger-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.78rem;
|
||||
font-family: var(--font-tech, monospace);
|
||||
}
|
||||
|
||||
.oath-ledger-table th,
|
||||
.oath-ledger-table td {
|
||||
padding: 0.45rem 0.6rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.oath-ledger-table th {
|
||||
color: var(--text-muted, #8899aa);
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
font-size: 0.68rem;
|
||||
}
|
||||
|
||||
.oath-ledger-table tbody tr:hover {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
.oath-ledger-empty {
|
||||
text-align: center;
|
||||
color: var(--text-muted, #8899aa);
|
||||
padding: 1.25rem !important;
|
||||
}
|
||||
|
||||
.oath-ledger-hash {
|
||||
font-family: var(--font-mono, monospace);
|
||||
color: var(--text-muted, #8899aa);
|
||||
}
|
||||
|
||||
.oath-ledger-outcome {
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
.oath-ledger-table tr[data-outcome='success'] .oath-ledger-outcome {
|
||||
color: #7fd87f;
|
||||
}
|
||||
|
||||
.oath-ledger-table tr[data-outcome='fail'] .oath-ledger-outcome {
|
||||
color: #ff8888;
|
||||
}
|
||||
|
||||
.oath-ledger-table tr[data-outcome='pending'] .oath-ledger-outcome {
|
||||
color: #d4b86a;
|
||||
}
|
||||
|
||||
.oath-ledger-strain-swatch {
|
||||
display: inline-block;
|
||||
width: 0.75rem;
|
||||
height: 0.75rem;
|
||||
border-radius: 2px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
46
server/web/src/pages/OathLedgerPage.test.tsx
Normal file
46
server/web/src/pages/OathLedgerPage.test.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
||||
import OathLedgerPage from './OathLedgerPage';
|
||||
import { api } from '../api/client';
|
||||
import { WebSocketProvider } from '../context/WebSocketProvider';
|
||||
|
||||
vi.mock('../api/client', () => ({
|
||||
api: {
|
||||
listOathLedger: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('OathLedgerPage', () => {
|
||||
beforeEach(() => {
|
||||
vi.mocked(api.listOathLedger).mockResolvedValue([
|
||||
{
|
||||
id: 1,
|
||||
timestamp: '2026-06-07T12:00:00Z',
|
||||
actor: 'comrade',
|
||||
action_type: 'graft',
|
||||
agent_id: 'agent-abc-123',
|
||||
strain: '#aabbcc',
|
||||
why_hash: 'abc123def456',
|
||||
outcome: 'success',
|
||||
payload_json: { graft_tier: 'dns_txt' },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('renders read-only oath table from API', async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<WebSocketProvider>
|
||||
<OathLedgerPage />
|
||||
</WebSocketProvider>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
expect(await screen.findByText('Operator Oath Ledger')).toBeInTheDocument();
|
||||
await waitFor(() => expect(api.listOathLedger).toHaveBeenCalledWith(100));
|
||||
expect(screen.getByText('comrade')).toBeInTheDocument();
|
||||
expect(screen.getByText('graft')).toBeInTheDocument();
|
||||
expect(screen.getByText('success')).toBeInTheDocument();
|
||||
expect(screen.getByRole('link', { name: /Seer reasoning/i })).toHaveAttribute('href', '/seer');
|
||||
});
|
||||
});
|
||||
124
server/web/src/pages/OathLedgerPage.tsx
Normal file
124
server/web/src/pages/OathLedgerPage.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { api } from '../api/client';
|
||||
import { useWebSocket } from '../hooks/useWebSocket';
|
||||
import type { OathLedgerEntry } from '../types';
|
||||
import './OathLedgerPage.css';
|
||||
|
||||
function fmtTs(ts?: string): string {
|
||||
if (!ts) return '—';
|
||||
const d = new Date(ts);
|
||||
if (Number.isNaN(d.getTime())) return ts;
|
||||
return d.toLocaleString([], {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
});
|
||||
}
|
||||
|
||||
function actionLabel(action: string): string {
|
||||
return action.replace(/_/g, ' ');
|
||||
}
|
||||
|
||||
export default function OathLedgerPage() {
|
||||
const { latestMessage } = useWebSocket();
|
||||
const [rows, setRows] = useState<OathLedgerEntry[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
const load = useCallback(async () => {
|
||||
try {
|
||||
const data = await api.listOathLedger(100);
|
||||
setRows(data ?? []);
|
||||
setError('');
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : 'Failed to load oath ledger');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
void load();
|
||||
}, [load]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!latestMessage || latestMessage.type !== 'oath_ledger_event') return;
|
||||
const entry = latestMessage.payload as OathLedgerEntry;
|
||||
if (!entry || typeof entry !== 'object' || !entry.id) return;
|
||||
setRows((prev) => {
|
||||
if (prev.some((r) => r.id === entry.id)) return prev;
|
||||
return [entry, ...prev].slice(0, 100);
|
||||
});
|
||||
}, [latestMessage]);
|
||||
|
||||
return (
|
||||
<div className="page oath-ledger-page">
|
||||
<header className="oath-ledger-header">
|
||||
<h1 className="oath-ledger-title font-display">Operator Oath Ledger</h1>
|
||||
<p className="oath-ledger-subtitle font-tech">
|
||||
Immutable accountability — separate from{' '}
|
||||
<Link to="/seer" className="oath-ledger-link">
|
||||
Seer reasoning
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
{error && <div className="oath-ledger-error">{error}</div>}
|
||||
|
||||
<div className="oath-ledger-table-wrap">
|
||||
<table className="oath-ledger-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Time</th>
|
||||
<th>Actor</th>
|
||||
<th>Action</th>
|
||||
<th>Agent</th>
|
||||
<th>Strain</th>
|
||||
<th>Why hash</th>
|
||||
<th>Outcome</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{loading && (
|
||||
<tr>
|
||||
<td colSpan={7} className="oath-ledger-empty">
|
||||
Loading…
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{!loading && rows.length === 0 && (
|
||||
<tr>
|
||||
<td colSpan={7} className="oath-ledger-empty">
|
||||
No oath rows yet — graft, strain plays, court L4, fork/merge, and LOTL dispatch record here.
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{rows.map((row) => (
|
||||
<tr key={row.id} data-outcome={row.outcome}>
|
||||
<td className="oath-ledger-ts">{fmtTs(row.timestamp)}</td>
|
||||
<td className="oath-ledger-actor">{row.actor || '—'}</td>
|
||||
<td className="oath-ledger-action">{actionLabel(row.action_type)}</td>
|
||||
<td className="oath-ledger-agent">{row.agent_id ? row.agent_id.slice(0, 8) : '—'}</td>
|
||||
<td className="oath-ledger-strain">
|
||||
{row.strain ? (
|
||||
<span className="oath-ledger-strain-swatch" style={{ backgroundColor: row.strain }} title={row.strain} />
|
||||
) : (
|
||||
'—'
|
||||
)}
|
||||
</td>
|
||||
<td className="oath-ledger-hash" title={row.why_hash}>
|
||||
{row.why_hash ? `${row.why_hash.slice(0, 10)}…` : '—'}
|
||||
</td>
|
||||
<td className="oath-ledger-outcome">{row.outcome}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -20,6 +20,21 @@
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
|
||||
.seer-oath-strip {
|
||||
margin: 0.35rem 0 0;
|
||||
font-size: 0.78rem;
|
||||
color: var(--text-muted, #8899aa);
|
||||
}
|
||||
|
||||
.seer-oath-link {
|
||||
color: var(--accent-cyan, #00e8f5);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.seer-oath-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.seer-subtitle {
|
||||
margin: 0.35rem 0 0;
|
||||
font-size: 0.72rem;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useWebSocket } from '../hooks/useWebSocket';
|
||||
import { HelpTip } from '../components/HelpTip';
|
||||
import { api } from '../api/client';
|
||||
@@ -150,6 +151,13 @@ export default function SeerPage() {
|
||||
The Seer <HelpTip field="seer_overview" />
|
||||
</h1>
|
||||
<p className="seer-subtitle font-tech">{statusLine}</p>
|
||||
<p className="seer-oath-strip font-tech">
|
||||
Accountability rows live in{' '}
|
||||
<Link to="/oath" className="seer-oath-link">
|
||||
Oath Ledger
|
||||
</Link>{' '}
|
||||
— not mixed with this reasoning stream.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
{error && <div className="seer-error">{error}</div>}
|
||||
|
||||
@@ -811,6 +811,18 @@ export interface AuditEntry {
|
||||
detail?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface OathLedgerEntry {
|
||||
id: number;
|
||||
timestamp: string;
|
||||
actor: string;
|
||||
action_type: string;
|
||||
agent_id: string;
|
||||
strain: string;
|
||||
why_hash: string;
|
||||
outcome: 'success' | 'fail' | 'pending' | string;
|
||||
payload_json?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface FleetModuleManifest {
|
||||
name: string;
|
||||
version: string;
|
||||
|
||||
Reference in New Issue
Block a user