pub enum ChallengeOutcome {
Pass,
SoftChallenge,
HardChallenge,
Blocked,
Captcha,
}Expand description
Normalised label for the outcome of a single acquisition attempt.
The taxonomy is intentionally small and stable so that policy
planning, vendor classification (T89), and change-detection
feeds (T88) can all agree on a shared vocabulary. Each variant
carries a stable snake_case wire label and a per-outcome
risk_delta that the feedback loop adds to
the next runtime policy’s risk score (subject to the documented
MAX_RISK_DELTA clamp).
§Example
use stygian_charon::challenge_feedback::ChallengeOutcome;
let outcome = ChallengeOutcome::HardChallenge;
assert_eq!(outcome.label(), "hard_challenge");
assert!(outcome.risk_delta() > 0.0);Variants§
Pass
The request returned successfully (2xx) with no challenge artefact in the response body or headers.
SoftChallenge
The request returned a soft challenge (e.g. Cloudflare
“Just a moment…” interstitial, 403 with a JS challenge
script, or a slow-down page) that the runner eventually
solved without raising execution mode.
HardChallenge
The request returned a hard challenge (e.g. a cf-chl-bypass
token, a DataDome interstitial, an Akamai Bot Manager
challenge page) that required a browser-stealth strategy.
Blocked
The request was blocked outright (e.g. 403/429 with no
challenge artefact — IP-level or fingerprint-level
rejection).
Captcha
The request was served a CAPTCHA (reCAPTCHA, hCaptcha,
DataDome captcha-delivery, etc.) that could not be
solved automatically.
Implementations§
Source§impl ChallengeOutcome
impl ChallengeOutcome
Sourcepub const fn label(self) -> &'static str
pub const fn label(self) -> &'static str
Stable, human-readable label for telemetry / JSON output.
§Example
use stygian_charon::challenge_feedback::ChallengeOutcome;
assert_eq!(ChallengeOutcome::Pass.label(), "pass");
assert_eq!(ChallengeOutcome::SoftChallenge.label(), "soft_challenge");
assert_eq!(ChallengeOutcome::HardChallenge.label(), "hard_challenge");
assert_eq!(ChallengeOutcome::Blocked.label(), "blocked");
assert_eq!(ChallengeOutcome::Captcha.label(), "captcha");Sourcepub const fn risk_delta(self) -> f64
pub const fn risk_delta(self) -> f64
Per-outcome risk-score contribution before clamping.
The values are bounded by
MAX_RISK_DELTA
so a single entry never overshoots the documented ceiling
on its own. Pass carries a small negative
contribution to gently de-escalate after clean runs; every
other outcome contributes a non-negative amount.
§Example
use stygian_charon::challenge_feedback::ChallengeOutcome;
assert!(ChallengeOutcome::Pass.risk_delta() < 0.0);
assert!(ChallengeOutcome::SoftChallenge.risk_delta() > 0.0);
assert!(ChallengeOutcome::HardChallenge.risk_delta() > 0.0);
assert!(ChallengeOutcome::Blocked.risk_delta() > 0.0);
assert!(ChallengeOutcome::Captcha.risk_delta() > 0.0);Sourcepub const fn risk_delta_for(self, target_class: TargetClass) -> f64
pub const fn risk_delta_for(self, target_class: TargetClass) -> f64
Per-(outcome, target_class) risk-score contribution.
Different target classes interpret the same outcome
differently: an API hitting a Captcha is anomalous (the
runner should back off — sign flips to negative), while the
same Captcha on a Content site is a normal anti-bot posture
that warrants a higher risk score. The
TargetClass::Unknown fallback returns 0.0 because the
runner has no signal to act on.
§Example
use stygian_charon::challenge_feedback::ChallengeOutcome;
use stygian_charon::types::TargetClass;
// API + Captcha is anomalous — sign flips to negative.
let api_captcha = ChallengeOutcome::Captcha.risk_delta_for(TargetClass::Api);
assert!(api_captcha < 0.0);
// ContentSite + Captcha is a normal anti-bot posture — positive.
let content_captcha = ChallengeOutcome::Captcha.risk_delta_for(TargetClass::ContentSite);
assert!(content_captcha > 0.0);
// Unknown class has no signal.
let unknown = ChallengeOutcome::Captcha.risk_delta_for(TargetClass::Unknown);
assert!(unknown == 0.0);Trait Implementations§
Source§impl Clone for ChallengeOutcome
impl Clone for ChallengeOutcome
Source§fn clone(&self) -> ChallengeOutcome
fn clone(&self) -> ChallengeOutcome
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreimpl Copy for ChallengeOutcome
Source§impl Debug for ChallengeOutcome
impl Debug for ChallengeOutcome
Source§impl<'de> Deserialize<'de> for ChallengeOutcome
impl<'de> Deserialize<'de> for ChallengeOutcome
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
impl Eq for ChallengeOutcome
Source§impl Hash for ChallengeOutcome
impl Hash for ChallengeOutcome
Source§impl PartialEq for ChallengeOutcome
impl PartialEq for ChallengeOutcome
Source§fn eq(&self, other: &ChallengeOutcome) -> bool
fn eq(&self, other: &ChallengeOutcome) -> bool
self and other values to be equal, and is used by ==.Source§impl Serialize for ChallengeOutcome
impl Serialize for ChallengeOutcome
impl StructuralPartialEq for ChallengeOutcome
Auto Trait Implementations§
impl Freeze for ChallengeOutcome
impl RefUnwindSafe for ChallengeOutcome
impl Send for ChallengeOutcome
impl Sync for ChallengeOutcome
impl Unpin for ChallengeOutcome
impl UnsafeUnpin for ChallengeOutcome
impl UnwindSafe for ChallengeOutcome
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.