pub struct EvolutionEngine {Show 18 fields
pub gene_pool: GenePool,
pub rng: StdRng,
pub cache: LruCache<String, OracleVerdict>,
pub budget: Budget,
pub in_flight: HashMap<u64, (u64, Chromosome, Instant)>,
pub stats: SearchStats,
pub target_health: TargetHealthMonitor,
pub checkpoint_path: Option<PathBuf>,
pub request_count: usize,
pub gene_stats: Vec<(String, String, u32, u32)>,
pub fitness_history: VecDeque<f64>,
pub stagnation_counter: u32,
pub corpus: BypassCorpus,
pub rule_coverage: RuleCoverage,
pub booster: WafBoosterScorer,
pub no_booster: bool,
pub exploration_boost_remaining: u32,
pub exploration_boost_factor: f64,
/* private fields */
}Expand description
The evolutionary engine that maintains a population and evolves it.
Fields§
§gene_pool: GenePoolGene pool for creating/mutating chromosomes.
rng: StdRngSeeded random number generator.
cache: LruCache<String, OracleVerdict>Payload→verdict LRU cache.
budget: BudgetHard budget limits.
in_flight: HashMap<u64, (u64, Chromosome, Instant)>Candidates currently being evaluated:
engine_eval_id → (algorithm_candidate_id, Chromosome, sent_at).
algorithm_candidate_id is the ID the search algorithm
originally minted in request_evaluations and the same ID it
expects to see back in submit_evaluations. Population-based
algorithms (MapElites, NoveltySearch) keep their own private
in_flight keyed by that ID — if we forwarded the engine’s
eval_id instead, their lookup misses and the evaluation is
silently dropped (the grid / archive never gets updated).
stats: SearchStatsSearch statistics.
target_health: TargetHealthMonitorTarget health monitor.
checkpoint_path: Option<PathBuf>Optional path for automatic checkpointing.
request_count: usizeTotal oracle requests issued.
gene_stats: Vec<(String, String, u32, u32)>Per-gene success tracking: (gene_name, gene_value, successes, attempts).
fitness_history: VecDeque<f64>Fitness history: average fitness per generation (sliding window).
stagnation_counter: u32Number of consecutive generations with no improvement.
corpus: BypassCorpusSaved bypass corpus.
rule_coverage: RuleCoverageWAF rule-coverage accumulator. Tracks (payload_class × rule_id) cells
observed during the run. Populated by submit_batch when the oracle
result carries a rule_id (via OracleVerdict::rule_id).
booster: WafBoosterScorerWAFBooster importance scorer. Updated on every oracle result and used to re-rank mutation candidates so pass-likely payloads are tried first.
no_booster: boolWhen true, the booster is disabled and candidate selection falls back
to the underlying algorithm’s FIFO/UCB1 ordering.
exploration_boost_remaining: u32Remaining rounds of elevated exploration weight following a
change-point alarm (C-11). When > 0, on_change_point has
signalled that a WAF rule update was detected and the engine
should explore more aggressively (higher UCB1 exploration bias,
stagnation counter reset). Decrements by 1 each call to evolve.
exploration_boost_factor: f64Exploration-weight multiplier applied while exploration_boost_remaining > 0.
A value of 2.0 doubles the effective UCB1 exploration constant.
Reset to 1.0 when the boost expires. Default 1.0 (no boost).
Implementations§
Source§impl EvolutionEngine
impl EvolutionEngine
Move this engine behind the canonical SharedEngine pointer.
Equivalent to Arc::new(RwLock::new(self)) — exists so the
shared-access pattern is discoverable on the type itself
rather than buried in module-level docs.
Source§impl EvolutionEngine
impl EvolutionEngine
Sourcepub fn new(population_size: usize) -> Self
pub fn new(population_size: usize) -> Self
Create a new engine with the given algorithm and population size.
Sourcepub fn new_seeded(population_size: usize, seed: u64) -> Self
pub fn new_seeded(population_size: usize, seed: u64) -> Self
Create a new engine with a seeded RNG.
population_size is clamped to the inclusive range [1, 10_000]:
0 would leave the selection helpers (tournament/roulette) with
nothing to index — a contract violation that used to panic.
10_000 caps memory at construction so a misconfigured caller
can’t OOM the process by passing usize::MAX.
Sourcepub fn with_algorithm(
algorithm_name: &str,
gene_pool: GenePool,
rng: StdRng,
budget: Budget,
) -> Result<Self, EvolutionError>
pub fn with_algorithm( algorithm_name: &str, gene_pool: GenePool, rng: StdRng, budget: Budget, ) -> Result<Self, EvolutionError>
Create an engine with a specific algorithm by name.
Sourcepub fn next_id(&self) -> u64
pub fn next_id(&self) -> u64
Read-only view of the engine’s next eval-id counter. Exposed so checkpoint round-trip tests can verify the counter is preserved across save/load. The field itself stays private so external callers can’t desync it.
Sourcepub fn next_candidate(&mut self) -> Option<(usize, &Chromosome)>
pub fn next_candidate(&mut self) -> Option<(usize, &Chromosome)>
Get the next candidate to try (legacy sequential API).
Returns a synthetic index and a reference to the stored candidate.
Sourcepub fn batch_candidates(&mut self, n: usize) -> Vec<(usize, Chromosome)>
pub fn batch_candidates(&mut self, n: usize) -> Vec<(usize, Chromosome)>
Request a batch of up to n candidates for parallel evaluation.
Checks cache, budget, and target health before returning candidates.
n is also clamped to the remaining budget.max_requests headroom
so a single batch call can never overshoot the hard request budget
(the underlying algorithm is free to request whatever it likes
internally; the engine bounds the request count it actually
surfaces).
When the booster is enabled (no_booster == false), the result
batch is re-ordered by ascending booster score so that pass-likely
candidates are tried first. The in-flight map and cache logic are
unaffected by the reordering.
Sourcepub fn prune_stale_in_flight(&mut self, max_age: Duration) -> usize
pub fn prune_stale_in_flight(&mut self, max_age: Duration) -> usize
Drop entries from the in-flight map that have been outstanding
longer than max_age. The proxy / scan loop should call this
periodically (or when a Worker pool is reaped) so a dropped
evaluation doesn’t permanently consume a budget slot.
Audit (2026-05-10): pre-fix in_flight grew without any TTL —
every dropped eval permanently consumed a max_requests slot,
so a long scan with even moderate eval-loss would terminate
prematurely with budget exhausted while the in-flight map
silently accumulated. Returns the number of pruned entries.
Sourcepub fn submit_batch(
&mut self,
results: Vec<(usize, OracleVerdict)>,
) -> Result<(), EvolutionError>
pub fn submit_batch( &mut self, results: Vec<(usize, OracleVerdict)>, ) -> Result<(), EvolutionError>
Submit a batch of evaluation results.
§Errors
Returns an error if an evaluation ID is not in the in-flight set.
Sourcepub fn record_feedback(
&mut self,
chromosome_index: usize,
passed: bool,
) -> Result<(), EvolutionError>
pub fn record_feedback( &mut self, chromosome_index: usize, passed: bool, ) -> Result<(), EvolutionError>
Record legacy boolean feedback for a candidate.
Sourcepub fn record_verdict(
&mut self,
chromosome_index: usize,
verdict: &OracleVerdict,
) -> Result<(), EvolutionError>
pub fn record_verdict( &mut self, chromosome_index: usize, verdict: &OracleVerdict, ) -> Result<(), EvolutionError>
Record rich oracle verdict feedback.
Sourcepub fn record_target_error(
&mut self,
error: String,
) -> Result<(), EvolutionError>
pub fn record_target_error( &mut self, error: String, ) -> Result<(), EvolutionError>
Record target-error feedback.
Sourcepub fn on_change_point(&mut self, boost_rounds: u32, factor: f64)
pub fn on_change_point(&mut self, boost_rounds: u32, factor: f64)
Signal that the online CUSUM bypass-rate monitor detected a change-point (C-11) — i.e. the WAF vendor likely pushed a rule update.
The engine responds by:
- Resetting
stagnation_counterto 0 — prevents premature termination that would otherwise fire because the bypass rate collapsed (high stagnation from many blocked attempts). - Setting
exploration_boost_remainingtoboost_rounds— for the nextboost_roundscalls toevolve, the booster score for candidates is discounted so the engine explores more broadly rather than exploiting the (now-broken) learned strategy. - Setting
exploration_boost_factortofactor— the multiplier applied to candidate selection diversity during boost rounds.
§Parameters
boost_rounds: how manyevolvecalls the boost lasts (default 10).factor: exploration multiplier > 1.0 (default 2.0).
§Example
use wafrift_evolution::evolution::EvolutionEngine;
let mut engine = EvolutionEngine::new(10);
assert_eq!(engine.exploration_boost_remaining, 0);
engine.on_change_point(10, 2.0);
assert_eq!(engine.exploration_boost_remaining, 10);
assert_eq!(engine.stagnation_counter, 0);Sourcepub fn should_terminate(&self) -> bool
pub fn should_terminate(&self) -> bool
Check if evolution should terminate.
Sourcepub fn best(&self) -> Option<&Chromosome>
pub fn best(&self) -> Option<&Chromosome>
Get the best-performing chromosome.
Sourcepub fn algorithm_name(&self) -> &str
pub fn algorithm_name(&self) -> &str
Return the name of the active search algorithm (e.g. "ast_mcts").
Sourcepub fn save_checkpoint(&self, path: &Path) -> Result<(), EvolutionError>
pub fn save_checkpoint(&self, path: &Path) -> Result<(), EvolutionError>
Save engine state to disk.
Sourcepub fn load_checkpoint(&mut self, path: &Path) -> Result<(), EvolutionError>
pub fn load_checkpoint(&mut self, path: &Path) -> Result<(), EvolutionError>
Load engine state from disk.
Sourcepub fn learned_summary(&self) -> String
pub fn learned_summary(&self) -> String
Get a human-readable summary.
Sourcepub fn seed_population(&mut self, population: Vec<Chromosome>)
pub fn seed_population(&mut self, population: Vec<Chromosome>)
Seed the underlying algorithm with an explicit population — the public path callers use to warm-start search from a known good corpus (or to inject a synthetic population from tests).
Previously this method cloned self.rng before passing it to
initialize, so the engine’s owned RNG was never advanced. Any
random draws made by initialize (e.g. MapElites grid placement,
initial mutation in SimulatedAnnealing) were “used up” in the
clone and the engine remained at the same RNG state — making two
successive seed_population calls (or a seed_population + an
evolve) produce identical random sequences and identical
chromosomes.
Sourcepub fn population_snapshot(&self) -> Vec<Chromosome>
pub fn population_snapshot(&self) -> Vec<Chromosome>
Snapshot the algorithm’s live population (test/diagnostic surface). Population-based algorithms return their full pool; single-state algorithms return the singleton current/best.
Sourcepub fn diversity_score(&self) -> f64
pub fn diversity_score(&self) -> f64
Population diversity in [0.0, 1.0] — drives adaptive mutation
pressure (see crossover::diversity::adaptive_mutation_rate).
Strategy:
- Snapshot the algorithm’s live population and union it with
the engine’s
in_flightcandidates. - If
len() >= 2, return mean pairwise gene-mismatch ratio viacrossover::diversity::diversity_score. - Otherwise (single-state algorithm with nothing in-flight),
fall back to gene-pool exploration entropy from
Self::gene_stats_diversity— measures how broadly the engine has explored the gene space rather than how varied the current population is. With no exploration history either, return 1.0 (max-safe default — keeps mutation pressure conservative on a fresh engine).
Sourcepub fn gene_stats_diversity(&self) -> f64
pub fn gene_stats_diversity(&self) -> f64
Shannon-entropy style diversity over the engine’s per-gene exploration history.
For each unique gene name in gene_stats, computes the
normalised entropy of its value distribution weighted by
attempts. The per-gene entropies are averaged. Range
[0.0, 1.0]: 0.0 means we tried only one value for every
gene (no exploration), 1.0 means a uniform distribution
across the maximum-cardinality gene’s value space.
Useful as a fallback signal when the active search algorithm is single-state (e.g. simulated annealing) and the population snapshot is too small to give meaningful pairwise distance.