Add fleet phenotype cloning for sibling machines.

Publish winning tier paths per host fingerprint on hashrate success, inherit on auth before adaptive strategy, and surface clone badges in LOTL Timeline and Access Depth.
This commit is contained in:
AetherForge
2026-06-07 02:28:00 -07:00
parent bb48515b2b
commit 4204dd6b6d
25 changed files with 1421 additions and 38 deletions

View File

@@ -69,6 +69,12 @@
font-style: italic;
}
.access-depth-phenotype {
margin-top: 0.45rem;
font-size: 0.72rem;
color: var(--text-muted);
}
.access-depth-join {
display: flex;
flex-wrap: wrap;
@@ -192,6 +198,21 @@
border: 1px solid rgba(255, 180, 60, 0.3);
}
.access-depth-atlas-list {
margin: 0;
padding-left: 1.1rem;
font-size: 0.78rem;
color: var(--text-muted);
}
.access-depth-atlas-row {
margin: 0.2rem 0;
}
.access-depth-onion-item--skipped_by_atlas .access-depth-onion-label {
opacity: 0.7;
}
.access-depth-source {
margin-left: 0.35rem;
color: var(--text-muted);
@@ -220,3 +241,35 @@
.access-depth-calibrate-link:hover {
text-decoration: underline;
}
.access-depth-clearance-badge {
font-family: var(--font-tech);
font-size: 0.68rem;
font-weight: 700;
letter-spacing: 0.08em;
color: var(--neon-cyan);
border: 1px solid rgba(0, 245, 255, 0.45);
border-radius: 3px;
padding: 1px 6px;
cursor: help;
}
.access-depth-clearance-flash {
font-size: 0.68rem;
color: var(--neon-amber, #ffb347);
background: rgba(255, 180, 60, 0.12);
border: 1px solid rgba(255, 180, 60, 0.45);
border-radius: 3px;
padding: 2px 8px;
animation: access-depth-clearance-flash 1.2s ease-in-out 2;
}
@keyframes access-depth-clearance-flash {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.45;
}
}

View File

@@ -12,6 +12,10 @@ import {
parseAccessDepthServerPolicy,
} from '../../help/accessDepth';
vi.mock('../../hooks/useWebSocket', () => ({
useWebSocket: () => ({ latestMessage: null }),
}));
vi.mock('../../api/client', () => ({
api: {
getConfig: vi.fn().mockResolvedValue({
@@ -155,6 +159,22 @@ describe('AccessDepthPanel', () => {
expect(await screen.findByText('WinRM')).toBeInTheDocument();
});
it('shows phenotype source when inherited phenotype present', async () => {
renderPanel(
mockAgent({
platform: 'windows',
inherited_phenotype: {
source_agent_name: 'worker-07',
spread_lane: 'winrm',
tier_order: ['container', 'wsl', 'cpu_inprocess'],
},
}),
);
expect(await screen.findByText(/phenotype cloned from/i)).toBeInTheDocument();
expect(screen.getByText('worker-07')).toBeInTheDocument();
expect(await screen.findByText('WinRM')).toBeInTheDocument();
});
it('renders adaptive strategy reasoning fixtures', () => {
renderPanel(
mockAgent({ platform: 'windows', status: 'online' }),
@@ -180,6 +200,13 @@ describe('AccessDepthPanel', () => {
expect(screen.getByText(/confidence 72%/i)).toBeInTheDocument();
});
it('renders clearance badge L0L4 with tooltip permissions', () => {
renderPanel(mockAgent({ clearance_level: 2 }));
const badge = screen.getByLabelText(/Clearance L2/i);
expect(badge).toHaveTextContent('L2');
expect(badge.getAttribute('title')).toMatch(/spread/i);
});
it('shows pending chain when tiers not yet attempted', () => {
renderPanel(
mockAgent({

View File

@@ -1,11 +1,18 @@
import { useEffect, useMemo, useState } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { Link } from 'react-router-dom';
import { api } from '../../api/client';
import {
atlasSkipDisplayLabel,
buildAccessDepthModel,
parseAccessDepthServerPolicy,
type AccessDepthDiagnostics,
} from '../../help/accessDepth';
import {
clearanceLabel,
clearancePermissions,
formatClearanceElevation,
} from '../../help/clearance';
import { useWebSocket } from '../../hooks/useWebSocket';
import type { Agent } from '../../types';
import { HelpTip } from '../HelpTip';
import JoinLaneBadge from './JoinLaneBadge';
@@ -31,6 +38,9 @@ function OnionList({ rows, empty }: { rows: ReturnType<typeof buildAccessDepthMo
<span className="access-depth-onion-label">{row.label}</span>
{row.status === 'active' && <span className="access-depth-tag access-depth-tag--active">active</span>}
{row.status === 'skipped' && <span className="access-depth-tag access-depth-tag--skip">skipped</span>}
{row.status === 'skipped_by_atlas' && (
<span className="access-depth-tag access-depth-tag--skip">atlas skip</span>
)}
{row.status === 'done' && <span className="access-depth-tag access-depth-tag--ok">ok</span>}
{row.status === 'failed' && <span className="access-depth-tag access-depth-tag--fail">fail</span>}
{row.status === 'pending' && <span className="access-depth-tag access-depth-tag--pending">pending</span>}
@@ -63,8 +73,38 @@ function AttemptMiniList({
}
export default function AccessDepthPanel({ agent, diagnostics }: Props) {
const { latestMessage } = useWebSocket();
const [policyLoaded, setPolicyLoaded] = useState(false);
const [serverPolicy, setServerPolicy] = useState(parseAccessDepthServerPolicy({}));
const [elevationFlash, setElevationFlash] = useState<string | null>(null);
const flashTimerRef = useRef<number | null>(null);
const clearanceLevel = agent.clearance_level ?? 1;
useEffect(() => {
if (!latestMessage || latestMessage.type !== 'clearance_elevated') return;
const p = latestMessage.payload as {
agent_id?: string;
to_level?: number;
source?: string;
reason?: string;
};
if (p.agent_id !== agent.id || typeof p.to_level !== 'number') return;
const text = formatClearanceElevation({
to_level: p.to_level,
source: p.source,
reason: p.reason,
});
setElevationFlash(text);
if (flashTimerRef.current != null) window.clearTimeout(flashTimerRef.current);
flashTimerRef.current = window.setTimeout(() => setElevationFlash(null), 6000);
}, [latestMessage, agent.id]);
useEffect(() => {
return () => {
if (flashTimerRef.current != null) window.clearTimeout(flashTimerRef.current);
};
}, []);
useEffect(() => {
let cancelled = false;
@@ -95,6 +135,18 @@ export default function AccessDepthPanel({ agent, diagnostics }: Props) {
<span className="lotl-attempts-title">
ACCESS DEPTH <HelpTip field="crucible_access_depth" />
</span>
<span
className="access-depth-clearance-badge"
title={clearancePermissions(clearanceLevel)}
aria-label={`Clearance ${clearanceLabel(clearanceLevel)}: ${clearancePermissions(clearanceLevel)}`}
>
{clearanceLabel(clearanceLevel)}
</span>
{elevationFlash && (
<span className="access-depth-clearance-flash" role="status">
{elevationFlash}
</span>
)}
{!policyLoaded && <span className="access-depth-muted">loading policy</span>}
</div>
@@ -137,6 +189,17 @@ export default function AccessDepthPanel({ agent, diagnostics }: Props) {
) : (
<div className="access-depth-muted">No join lane yet</div>
)}
{model.phenotypeSource && (
<div className="access-depth-phenotype">
phenotype cloned from <strong>{model.phenotypeSource}</strong>
{model.phenotypeSpreadLane ? (
<>
{' '}
· spread <JoinLaneBadge lane={model.phenotypeSpreadLane} />
</>
) : null}
</div>
)}
</div>
<div className="access-depth-section">
@@ -161,6 +224,19 @@ export default function AccessDepthPanel({ agent, diagnostics }: Props) {
</div>
</div>
{model.atlasSkips.length > 0 && (
<div className="access-depth-section access-depth-atlas-block">
<div className="access-depth-section-title">Atlas skips</div>
<ul className="access-depth-atlas-list">
{model.atlasSkips.map((skip) => (
<li key={`${skip.tier}-${skip.condition}`} className="access-depth-atlas-row">
{atlasSkipDisplayLabel(skip)}
</li>
))}
</ul>
</div>
)}
{model.strategyReasoning.length > 0 && (
<div className="access-depth-section access-depth-strategy-block">
<div className="access-depth-section-title">