feat(account): auto-select best account per service

Merged
MP
6mo ago
+1123-12

Summary

Implements AccountSelector application service to rank multiple accounts for the same hoster/debrid service deterministically. Supports three configurable strategies (BestTraffic, RoundRobin, Manual) read from live config. Unblocks task 25 (rotation auto sur quota).

Why

Task 24 from PRD §6.4: plugins need deterministic account dispatch to handle quota rotation and fallback chains. BestTraffic ranks by traffic availability with tie-breakers for reproducibility. RoundRobin distributes load across eligible accounts. Manual reserved for future pinning UI.

Changes

  • Add AccountSelector service in src-tauri/src/application/services/account_selector.rs (~685 lines)

    • Three strategies: BestTraffic (enabled → non-expired → most traffic → most recent validation → smallest id), RoundRobin (per-service cursor), Manual (alias of BestTraffic + regression test)
    • Per-service candidate cache invalidated on account-touching events
    • Domain events: NoAccountAvailable { service_name } and AccountSelected { id, service_name, strategy }
  • Extend domain models

    • AccountSelectionStrategy enum in domain/model/account.rs with Display/FromStr (snake_case serialization)
    • account_selection_strategy field on AppConfig and ConfigPatch with apply_patch() support
  • Update adapters

    • Tauri bridge: serialize no-account-available and account-selected events to frontend
    • TOML store: persist strategy to config.toml (snake_case: best_traffic, round_robin, manual)
    • IPC DTOs: add account_selection_strategy field (camelCase) for settings/patch endpoints
  • Extend CommandBus with account_selector() accessor and resolve_account_for(service_name) convenience method

  • Flag planned hook in resolve_links.rs (comment only; actual per-URL calls deferred to avoid emitting AccountSelected once per probed link)

  • Update CHANGELOG.md with full feature description

Testing

✅ All 12 unit tests pass covering four PRD acceptance criteria:

  • 3-account scenario (full-traffic account wins over expired/low-traffic)
  • All-expired returns None + NoAccountAvailable event
  • Comparative ranking table (enabled → non-expired → traffic → validation → id)
  • RoundRobin alternance with cursor persistence across cache invalidation

✅ Comprehensive edge cases: cache invalidation, case-mismatch behavior, tie-breaker correctness, config strategy reading

✅ Integration: config patch apply, event emission, Tauri IPC serialization

Command output:

cargo test --package vortex-app --lib account_selector
# 12 passed
cargo clippy --workspace -- -D warnings
# clean
cargo fmt --check
# ok

Related Issues

  • Refs: PRD §6.4 (Auto-select meilleur compte)
  • Refs: PRD-v2 §P1.5 (task 24)
  • Unblocks: task 25 (rotation auto sur quota)

Notes for Reviewer

  • Traffic ranking: Unlimited enum variant ranks above any Finite(u64) via Ord derivation, ensuring unlimited plans always win
  • Cache survival: per-service cursors in RoundRobin survive bulk cache invalidation so hot-reload of account set does not reset rotation
  • Weak<Mutex> pattern prevents Arc cycle between EventBus subscriber and AccountSelector while allowing events to invalidate cache
  • Config override: resolve_account_for() reads live strategy from AppConfig each call, honoring runtime changes without restart
  • Deterministic tie-breaking: enabled → not expired → traffic_left → last_validated_at → id ensures reproducible selection for same candidate set

Checklist

  • Tests added and passing (12 unit tests, 4 acceptance criteria verified)
  • Docs updated (CHANGELOG.md, inline doc comments, task frontmatter)
  • No secrets, debug prints, or allow-directives
  • Self-reviewed diff
  • Cargo clippy + fmt clean

Summary by cubic

Auto-select the best account per service with a configurable strategy. Deterministic selection from live repo state, a single entry point for plugins/downloads, and backward-compatible config loading; implements PRD §6.4 (Linear task 24).

  • New Features

    • Added AccountSelector with three strategies: BestTraffic (default), RoundRobin (per-service cursor), and Manual (alias of BestTraffic for now).
    • New events: NoAccountAvailable and AccountSelected; forwarded via Tauri as no-account-available and account-selected.
    • CommandBus::resolve_account_for(service_name) uses the live AppConfig strategy.
    • Config: account_selection_strategy on AppConfig/ConfigPatch, persisted to TOML and exposed via IPC ("best_traffic" | "round_robin" | "manual").
  • Bug Fixes

    • Removed selector caching to fix a race that could serve stale rows.
    • RoundRobin: a poisoned cursor now surfaces as a validation error instead of returning None.
    • Strict validation of account_selection_strategy in IPC and TOML; settings_update surfaces errors. Legacy config.toml without the field now loads with the default (BestTraffic), while unknown non-empty values fail fast.
    • resolve_account_for propagates ConfigStore::get_config() failures; tests cover repo-fresh selection, case sensitivity, validation, poisoned-cursor handling, and legacy-config compatibility.

Summary by CodeRabbit

  • New Features

    • Configurable account selection strategies in Settings: Best Traffic, Round Robin, Manual.
    • Account resolution API for operations needing credentials.
    • Accounts management UI with add/update/delete/validate/import/export and persisted account storage.
  • Bug Fixes

    • Invalid/unknown strategy values are rejected and surface as errors (no silent fallback).
  • Behavior

    • Account lifecycle events forwarded to the frontend: account selected, no account available.