# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.8.0] - 2026-05-09
### Changed (breaking)
- **`ScatterProxyRouter::new()`** now accepts per-host `(host, ScatterProxyConfig)` tuples
instead of the previous `(hosts, shared_config, classifier)` signature. Each host gets
its own independent configuration, enabling per-host tuning of sources, concurrency,
timeouts, and log prefix.
```rust
let router = ScatterProxyRouter::new(
["szse.cn", "sse.com.cn"],
ScatterProxyConfig::default(),
DefaultClassifier,
).await?;
let router = ScatterProxyRouter::new(
[
("szse.cn", ScatterProxyConfig { max_inflight: 20, ..Default::default() }),
("sse.com.cn", ScatterProxyConfig { max_inflight: 10, ..Default::default() }),
],
DefaultClassifier,
).await?;
```
- **`ProxyManager`**: Removed remaining `DIRECT` (`direct://localhost`) code — the const,
the `set_state` guard that prevented eviction, and the `get_client` direct-connection
branch. All connections unconditionally route through SOCKS5 proxies.
### Added
- **Task-completed log** — `debug!` emitted on successful task completion with `host`,
`proxy`, `attempt`, and `latency_ms` fields; symmetric to the existing `"task requeued"` log.
- **Host field in proxy-dead log** — `"proxy dead | global success_rate=0%"` now includes
`host = %host` so the triggering host is identifiable in multi-host deployments.
## [0.7.0] - 2026-05-09
### Added
- **`ScatterProxyRouter`** — routes proxied requests to per-host [`ScatterProxy`] instances.
Each registered host gets its own independent proxy pool, health tracker, scheduler, and
metrics view. A struggling host (all proxies in cooldown, zero success) can no longer
starve tasks for other hosts, and proxy eviction decisions are scoped per-host.
- **`ScatterProxyConfig::name: Option<String>`** — optional label prepended to every
periodic metrics log line as `[name]`. `ScatterProxyRouter` automatically sets this to
the host string for each pool, so multi-host log output is immediately identifiable.
- **`ScatterProxyError::UnknownHost(String)`** — returned by `ScatterProxyRouter::submit`
and `try_submit` when the request targets a host not registered in the router.
- **`ScatterProxyConfig: Clone`** and **`RateLimitConfig: Clone`** — required to share a
single config across router-managed per-host pools.
## [0.6.0] - 2026-05-07
### Changed
- Repository hosting migrated from GitHub to Codeberg.
- CI/CD migrated from GitHub Actions to Woodpecker CI.
- Project website/docs deployment migrated from GitHub Pages to Codeberg Pages.
- Default first-party proxy source switched to the Codeberg Pages URL.
### Infrastructure
- Added `.woodpecker.yml` with CI checks, docs publish pipeline, scheduled proxy list refresh, and tag-based crates.io publish.
- Removed legacy `.github/workflows/*` GitHub Actions pipelines.
## [0.5.0] - 2026-04-23
### Changed (breaking)
- Removed the implicit DIRECT proxy candidate (`direct://localhost`). Proxy mode now uses only fetched/configured proxies; direct traffic is no longer auto-inserted into the race.
- Removed the circuit breaker subsystem entirely. Temporary lack of runnable proxies is now handled by the delayed retry queue instead of host-level breaker state.
- Reworked scheduling to use multiple scheduler workers plus a ready queue and delayed heap-backed retry queue.
- Expanded metrics to report delayed tasks, requeues, zero-available events, dispatch count, and skip reasons.
### Fixed
- Reduced hot-loop requeue behavior by delaying tasks until their next retry window.
- Improved fairness by keeping ready tasks in FIFO order while delayed tasks are promoted by `ready_at`.
## [0.3.0] - 2026-04-22
### Changed (breaking)
- **`ScatterProxy::submit`** is now `async` and returns `TaskHandle` directly instead of `Result<TaskHandle, ScatterProxyError>`. The scheduler retries internally forever; no error is ever returned from the submit path or the handle's `.await`.
- **`ScatterProxy::submit_batch`** is now `async` and returns `Vec<TaskHandle>` directly.
- **`ScatterProxyConfig`**: removed `task_timeout` and `max_attempts` fields; the scheduler no longer imposes per-task attempt limits or overall timeouts.
### Added
- **`ScatterProxy::try_submit`** — non-blocking submit; returns `Err(PoolFull)` immediately when the pool is at capacity.
- **`ScatterProxy::try_submit_batch`** — atomic non-blocking batch submit; rejects the entire batch if capacity is insufficient.
- **`ScatterProxy::submit_timeout`** — blocks up to a deadline waiting for a pool slot; returns `Err(Timeout)` if the deadline elapses before capacity is available.
- **`TaskHandle::with_timeout(Duration)`** — wraps the handle with a caller-side deadline; returns `Err(Timeout)` if the task hasn't resolved in time. The underlying task remains in the scheduler and continues to retry. `tokio::time::timeout(dur, handle).await` also works since `TaskHandle: Future`.
- **Cold-start fast-fail** — when any available proxy has not yet been health-tested, the fan-out *K* is boosted to `max(5, max_concurrent_per_request)` to collect health data quickly.
### Fixed
- Scheduler no longer sends terminal errors to task handles for IO failures, proxy timeouts, or max-attempt exhaustion. All such tasks are silently requeued and retried.
## [0.1.0] - 2025-07-13
### Added
- `ScatterProxy` — main entry point with `new()`, `submit()`, `submit_batch()`, `metrics()`, `shutdown()`
- Multi-path race scheduling: fan-out K proxies per task, first success wins
- Adaptive fan-out: K auto-adjusts based on success rate and available proxy count
- Per-(proxy, host) rate limiting with configurable intervals and host overrides
- Health tracking with sliding-window success rates and latency stats
- Exponential-backoff cooldown for consecutively-failing (proxy, host) pairs
- Per-host circuit breaker with Open → HalfOpen → Closed state machine
- Proxy eviction: mark dead if global success rate = 0% after sufficient samples
- `BodyClassifier` trait + `DefaultClassifier` for response classification
- `ScatterResponse` with status, headers, and body bytes
- JSON state persistence with atomic writes and hot-restart restore
- Automatic proxy source refresh from URLs
- Structured logging via `tracing` with periodic metrics summaries
- `socks5h://` by default for remote DNS resolution
- `PoolMetrics` for real-time observability
- 240+ unit tests across all modules
- Integration tests with real free SOCKS5 proxy sources