## Upgrade notes

    - **Start from a fresh database.** The schema changed a lot (scoring criteria, `Raw_Scores` in place of `Team_Score_Current`, validation runs, dry-run rounds, the IR tables dropped) and there are no migrations.
    - **Re-check old settings exports before importing.** A v0.15.x export won't import unchanged:
      - `ir.toml` is no longer read.
      - `settings.toml` now carries the scoring criteria.
      - Every inject in `inject.toml` needs a `scoring_target` (a criterion name).
      - `round_interval` must be at least 15.

      Export from a fresh v0.16.0 install and use it as the reference format.
    - **Traefik certificates are now stored on the host.** Run `just setup-environment` before `just reset-swarm` or a fresh deploy. It creates `/opt/comp/hermes/traefik`, the deploy refuses to start without it, and `reset-swarm` now leaves it alone. Let's Encrypt only issues five certificates a week per domain, and the old named volume was wiped on every reset.
    - **New minion env var `MINION_MAX_PARALLEL_VALIDATION`.** It sets the size of a separate pool of validation slots and defaults to 2. The prod compose file sets 8, and `just setup-environment` writes 16 for dev.
    - **New RabbitMQ exchange `validation`**, with queues `validation.execute` and `validation.results`. `EnsureTopology` declares them at startup, so nothing has to be done by hand.
    - **The minimum round interval is 15 seconds (was 5).** Every round keeps its last 10 seconds for minions to report back and for saving results, so at 15 s checks get 5 s to run.

  ## Added

    - **Scoring criteria.** A team's score is now a weighted percentage over the enabled criteria. Each criterion has a kind (Uptime, Injects, KOTH or Custom), a weight (all weights add up to 1) and a total possible.
      - A criterion's score is weight × (earned + adjustments) / total possible.
      - Staff manage criteria on a new Scores subpage.
      - Disabling a criterion locks every write to what it scores, staff included, and shows a banner on the scoreboard and team pages. Disabling Uptime also pauses the Uptime Engine.
    - **IR reports are now injects.** An inject template targets a scoring criterion, so IR is an Injects criterion its templates score into.
    - **Dry-run mode** (Settings). Rounds keep running against real targets and their results show on the scoreboard and team pages, but no points are awarded. The scoreboard shows "Dry run". Results from dry-run rounds stop showing once the mode is turned off. A change takes effect from the next round.
    - **Validation tab** (Checks → Validation, black team and admin). Runs selected checks, or every active check, against one team or all teams right now, outside any round and without scoring.
      - A check you pick runs even if it's inactive.
      - Results are grouped by check, with pass/down/engine-error counts, timings, a filter (failures, no response) and per-step output for chain checks.
      - Runs are stored. The tab opens on the newest run with its time, and a paged list of past runs lets you reopen any of them.
      - The check editor has a Validate panel that runs the saved check.
      - Validation has its own RabbitMQ exchange and its own minion slots, so it never takes a scoring slot or gets purged at round start. Each validation check is capped at 50 seconds.
      - API: `POST /api/validation-runs`, `GET /api/validation-runs` (paged), `GET /api/validation-runs/:run_id`.
    - **Variables can be renamed.** Renaming a variable rewrites the `{vars.<name>}` references in its check, and team overrides stay attached.
    - **Staff can view any team's check page**, with results, stats and variables, from the scoreboard (white team and up).
    - **Team check stats graphs** have a set range.
    - **The LDAP check can search, not just bind.** Pass on bind, on an entry existing, on an entry count, or on an attribute value (equals, contains, regex). It supports LDAPS, base DN, scope, a filter with a substituted value, and a returned attribute.
    - **The settings page** shows how many seconds checks get per round and warns at the minimum interval.

  ## Changed

    - "Engine" is now **Uptime Engine** everywhere, and the engine reset is now **Reset Competition**.
    - White team and above accounts no longer need a team number.
    - Team check pages refetch when the scoreboard announces a new round, so the two pages move together. A round only appears on the team page once its end time has passed, as on the scoreboard.
    - The telnet check works with Windows telnet servers: CMD and PowerShell prompts, and KpyM's ANSI screen updates.
    - ICMP execution times are more accurate.
    - Check variables are readable by white team (editing still needs black team).
    - A check that times out now reports "check exceeded its deadline" (was "…round deadline"), which is also correct for validation runs.
    - `just update-swarm` passes `--force`, so redeploying the same tag restarts the services.

  ## Removed

    - The old IR system: its pages, the `/api/ir/*` routes, and `ir.toml` in settings export and import. IR is now an inject criterion (see Added).

  ## Fixed

    - Minion heartbeats and registrations were being dropped. The round-start purge now clears only the check queues, not the minion queues.
    - Down targets held minion slots until the round deadline. Connect timeouts were added to the FTP, HTTP, LDAP, MongoDB, MSSQL, MySQL, SMB, telnet and WinRM checks.
    - Missing logging was added across the minion checks.
    - NULL bytes are stripped from check output before it is stored.
    - The ipvlan network setup no longer gives a minion an address ending in `.0` or `.255`.
    - Scoring rounds survive switching dry-run mode on or off.

  ## Internal

    - Frontend tests moved from Jest to Vitest, and several were simplified.
    - The CI dind services stream their logs into the job (`CI_DEBUG_SERVICES`) and have a TCP health check on 2376. That check only waits for the port to open, not for dockerd to serve, so integration jobs can still fail on a slow runner node.
    - `build_ci_tools_image` no longer runs on tag pipelines.
    - Table migrations in `setup_db.go` were removed.