stygian_charon/playbooks/mod.rs
1//! Target-class playbook schema (T85).
2//!
3//! A [`crate::playbooks::Playbook`] is the **codified, opinionated
4//! strategy** for one
5//! anti-bot tier. It bundles four operator-facing knobs into a single
6//! document:
7//!
8//! 1. [`crate::playbooks::AcquisitionDefaults`] — the acquisition mode /
9//! execution mode /
10//! session mode / retry budget the runner should start with.
11//! 2. [`crate::playbooks::ProxyPreference`] — which proxy flavour
12//! (datacenter /
13//! residential / mobile / SOCKS5) and which sticky-session
14//! constraints the proxy manager should honour.
15//! 3. [`crate::playbooks::PacingProfile`] — pacing rate, jitter, and
16//! minimum
17//! inter-request interval.
18//! 4. [`crate::playbooks::EscalationStrategy`] — the deterministic
19//! ladder the runner
20//! should climb when the current stage fails.
21//!
22//! Playbooks live on disk as TOML data files in
23//! `crates/stygian-charon/data/playbooks/`. The schema is
24//! serde-deserialisable so the same TOML files double as the
25//! operator-facing configuration surface.
26//!
27//! # Validation
28//!
29//! Every public mutator or loader path calls
30//! [`crate::playbooks::Playbook::validate`] to
31//! ensure the four knobs are internally consistent. Validation errors
32//! are reported as [`crate::playbooks::ValidationError`] variants that
33//! include the
34//! **field path** (`pacing.rate_limit_rps`) and the **bad value**
35//! (e.g. `"-0.5"`) so operators can locate the offending line in the
36//! TOML without having to re-run the loader.
37//!
38//! # Example
39//!
40//! ```
41//! use stygian_charon::playbooks::{AcquisitionDefaults, EscalationStrategy, PacingProfile, Playbook, ProxyPreference};
42//! use stygian_charon::acquisition::AcquisitionModeHint;
43//! use stygian_charon::types::{ExecutionMode, SessionMode, TargetClass, TelemetryLevel};
44//!
45//! let pb = Playbook {
46//! id: "tier1-static".to_string(),
47//! target_class: TargetClass::ContentSite,
48//! description: "Static content sites with no JavaScript challenge".to_string(),
49//! acquisition: AcquisitionDefaults {
50//! mode: AcquisitionModeHint::Fast,
51//! execution_mode: ExecutionMode::Http,
52//! session_mode: SessionMode::Stateless,
53//! telemetry_level: TelemetryLevel::Basic,
54//! sticky_session_ttl_secs: None,
55//! enable_warmup: false,
56//! retry_budget: 2,
57//! backoff_base_ms: 250,
58//! },
59//! proxy_preference: ProxyPreference {
60//! preferred_protocol: "https".to_string(),
61//! require_sticky: false,
62//! require_residential: false,
63//! max_latency_ms: None,
64//! },
65//! pacing: PacingProfile {
66//! rate_limit_rps: 3.0,
67//! jitter_pct: 0.10,
68//! min_request_interval_ms: 250,
69//! },
70//! escalation: EscalationStrategy::Capped { ceiling: AcquisitionModeHint::Resilient },
71//! };
72//! assert!(pb.validate().is_ok());
73//! ```
74
75mod builtin;
76mod error;
77mod resolver;
78mod schema;
79
80pub use error::ValidationError;
81pub use resolver::{PlaybookOverrides, PlaybookResolver, ResolvedPlaybook};
82pub use schema::{
83 AcquisitionDefaults, AcquisitionOverrides, EscalationStrategy, PacingProfile, Playbook,
84 ProxyPreference, ResolutionSource,
85};