captchaforge 0.2.36

[DO NOT USE — UNDER ACTIVE DEVELOPMENT, NOT PRODUCTION-READY] Captcha solver scaffolding for chromiumoxide-driven browsers. The architecture is in place (vendor solvers, retry-loop iframe walking, VLM provider abstraction, real-WAF bench harness) but the live-vendor success rate is still 0% — Cloudflare Turnstile / hCaptcha / reCAPTCHA detect us at a TLS / CDP fingerprint layer that no flag-based stealth has cleared. Watch the repo; do not depend on this for any real workload.
Documentation
//! Lock the training pipeline file set so future refactors can't
//! drop a script + leave RUNBOOK.md pointing at a missing file.

use std::path::PathBuf;

fn training_root() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("training")
}

#[test]
fn pipeline_scripts_all_present() {
    for script in [
        "scripts/auto_label.py",
        "scripts/train_lora.py",
        "scripts/distill.py",
        "scripts/bench_compare.py",
        "scripts/promote_to_ollama.sh",
    ] {
        let p = training_root().join(script);
        assert!(
            p.is_file(),
            "training pipeline script missing: {}",
            p.display()
        );
    }
}

#[test]
fn dockerfile_and_requirements_present() {
    assert!(training_root().join("Dockerfile").is_file());
    assert!(training_root().join("requirements.txt").is_file());
}

#[test]
fn runbook_references_existing_scripts_only() {
    let runbook = std::fs::read_to_string(training_root().join("RUNBOOK.md")).unwrap();
    // Every `training/scripts/<name>` mention in the runbook must
    // resolve to a real file. Plain-text scan to avoid pulling a
    // regex dep just for one test.
    const NEEDLE: &str = "training/scripts/";
    let mut missing: Vec<String> = Vec::new();
    let mut search_from = 0;
    while let Some(start) = runbook[search_from..].find(NEEDLE) {
        let abs_start = search_from + start;
        let rest = &runbook[abs_start..];
        // Take chars allowed in a script path.
        let end = rest
            .char_indices()
            .find(|(_, c)| {
                !(c.is_ascii_alphanumeric() || matches!(c, '/' | '.' | '_' | '-'))
            })
            .map(|(i, _)| i)
            .unwrap_or(rest.len());
        let mut rel = &rest[..end];
        // Trim trailing punctuation like `.` from a sentence end.
        while rel.ends_with('.') {
            rel = &rel[..rel.len() - 1];
        }
        let p = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(rel);
        if !p.is_file() && !p.is_dir() {
            missing.push(rel.to_string());
        }
        search_from = abs_start + end;
    }
    assert!(
        missing.is_empty(),
        "RUNBOOK references missing paths: {missing:?}"
    );
}