Skip to main content

EvolutionEngine

Struct EvolutionEngine 

Source
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: GenePool

Gene pool for creating/mutating chromosomes.

§rng: StdRng

Seeded random number generator.

§cache: LruCache<String, OracleVerdict>

Payload→verdict LRU cache.

§budget: Budget

Hard 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: SearchStats

Search statistics.

§target_health: TargetHealthMonitor

Target health monitor.

§checkpoint_path: Option<PathBuf>

Optional path for automatic checkpointing.

§request_count: usize

Total 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: u32

Number of consecutive generations with no improvement.

§corpus: BypassCorpus

Saved bypass corpus.

§rule_coverage: RuleCoverage

WAF 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: WafBoosterScorer

WAFBooster importance scorer. Updated on every oracle result and used to re-rank mutation candidates so pass-likely payloads are tried first.

§no_booster: bool

When true, the booster is disabled and candidate selection falls back to the underlying algorithm’s FIFO/UCB1 ordering.

§exploration_boost_remaining: u32

Remaining 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: f64

Exploration-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

Source

pub fn into_shared(self) -> SharedEngine

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

Source

pub fn new(population_size: usize) -> Self

Create a new engine with the given algorithm and population size.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn record_feedback( &mut self, chromosome_index: usize, passed: bool, ) -> Result<(), EvolutionError>

Record legacy boolean feedback for a candidate.

Source

pub fn record_verdict( &mut self, chromosome_index: usize, verdict: &OracleVerdict, ) -> Result<(), EvolutionError>

Record rich oracle verdict feedback.

Source

pub fn record_target_error( &mut self, error: String, ) -> Result<(), EvolutionError>

Record target-error feedback.

Source

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:

  1. Resetting stagnation_counter to 0 — prevents premature termination that would otherwise fire because the bypass rate collapsed (high stagnation from many blocked attempts).
  2. Setting exploration_boost_remaining to boost_rounds — for the next boost_rounds calls to evolve, the booster score for candidates is discounted so the engine explores more broadly rather than exploiting the (now-broken) learned strategy.
  3. Setting exploration_boost_factor to factor — the multiplier applied to candidate selection diversity during boost rounds.
§Parameters
  • boost_rounds: how many evolve calls 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);
Source

pub fn evolve(&mut self)

Evolve the population to the next generation.

Source

pub fn should_terminate(&self) -> bool

Check if evolution should terminate.

Source

pub fn best(&self) -> Option<&Chromosome>

Get the best-performing chromosome.

Source

pub fn algorithm_name(&self) -> &str

Return the name of the active search algorithm (e.g. "ast_mcts").

Source

pub fn save_checkpoint(&self, path: &Path) -> Result<(), EvolutionError>

Save engine state to disk.

Source

pub fn load_checkpoint(&mut self, path: &Path) -> Result<(), EvolutionError>

Load engine state from disk.

Source

pub fn gene_success_rates(&self) -> Vec<(&str, &str, f64)>

Get per-gene success rates.

Source

pub fn learned_summary(&self) -> String

Get a human-readable summary.

Source

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.

Source

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.

Source

pub fn diversity_score(&self) -> f64

Population diversity in [0.0, 1.0] — drives adaptive mutation pressure (see crossover::diversity::adaptive_mutation_rate).

Strategy:

  1. Snapshot the algorithm’s live population and union it with the engine’s in_flight candidates.
  2. If len() >= 2, return mean pairwise gene-mismatch ratio via crossover::diversity::diversity_score.
  3. 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).
Source

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.

Trait Implementations§

Source§

impl Clone for EvolutionEngine

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for EvolutionEngine

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more