# ND-300 Network Diagnostic

[![Build Status](https://github.com/QubeTX/qube-network-diagnostics/actions/workflows/release.yml/badge.svg)](https://github.com/QubeTX/qube-network-diagnostics/actions/workflows/release.yml)
[![License: PolyForm Noncommercial](https://img.shields.io/badge/license-PolyForm%20Noncommercial-blue)](LICENSE)

Cross-platform network diagnostic tool for Windows, macOS, and Linux. Includes **SpeedQX**, a standalone eight-provider speed test (SpeedQX Methodology v4).

## Features

- **Two operating modes**: User mode (clean summary) and Technician mode (deep diagnostics)
- **8 core diagnostics**: adapters, interfaces, gateway, DNS, public IP, latency, speed test, port connectivity
- **25 deep diagnostics** (technician mode): ARP (+ gateway health), routing, connections, listening ports, DHCP, protocol stats, adapter hardware, proxy (+ PAC/WPAD), VPN, firewall, DNS cache, IPv6 (+ real v6 fetch), MTU (+ path-MTU probe), connection states, real bufferbloat (loaded latency during saturation), reverse DNS, TLS inspection, traffic counters, route path (traceroute analysis), sustained packet loss, NAT/CGNAT analysis, Wi-Fi link quality, DNS resolver benchmark (+ hijack/DNSSEC checks), captive portal detection, clock sync (NTP)
- **Diagnostic-driven `nd300 fix`** — runs the diagnostics, identifies which checks failed, and applies only the recovery actions that target those specific failures. Clean and latency-only networks are advisory/no-op, DNS repair is staged from safest to most invasive, and medium/high-risk steps require confirmation. Re-tests after each step and repeats until everything passes or no further actions remain.
- **Six-provider speed test** — Cloudflare + M-Lab NDT7 + LibreSpeed + fast.com (Netflix) + M-Lab MSAK (multi-stream) + Apple networkQuality, with bounded N-provider inverse-variance aggregation, minimum-sample floors, modified trimean (Ookla-style), RFC 3550 jitter, and bootstrap confidence intervals ("941 Mbps ±12 Mbps") for technician-grade accuracy. Measures ping, jitter, download, upload, packet loss, stability, and provider divergence.
- **Resilient self-update** — `nd300 update` / `speedqx update` checks GitHub for the latest release and runs a probe-and-retry chain: cargo first when available, cargo-dist installer as universal fallback (curl → wget on macOS/Linux, PowerShell → pwsh on Windows). On Windows it can also upgrade in place via the matching first-class installer (MSI/EXE × Global/Corporate), chosen from an install marker, with SHA-256 verification of the download (refuse-on-mismatch). It cleans up shadowing non-Cargo ND300 installs when migrating to `cargo install nd300`, verifies the new version actually landed, and surfaces per-strategy failures with specific reasons.
- **SpeedQX** standalone speed test binary — all 8 providers (Methodology v4: capacity + consensus merge, I² agreement, PDV jitter, RPM) with per-provider breakdown and real-time progress; `--fast` for the quick 3-provider early-stopping run, `--skip-msak`/`--skip-apple` to trim the full run
- **Bufferbloat detection** with grade scoring (A+ through F)
- **JSON output** for scripting and automation
- **Unicode box-drawing** table rendering with ASCII fallback
- **Color-coded** status indicators (OK/Warn/Fail/Skip)
- **Cross-platform** — native support for Windows, macOS, and Linux

## Installation

### Shell (macOS/Linux)

```sh
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/QubeTX/qube-network-diagnostics/releases/latest/download/nd300-installer.sh | sh
```

### PowerShell (Windows)

```powershell
powershell -ExecutionPolicy Bypass -c "irm https://github.com/QubeTX/qube-network-diagnostics/releases/latest/download/nd300-installer.ps1 | iex"
```

### Windows installers (MSI + EXE, Global + Corporate)

Four first-class Windows installers are attached to every [release](https://github.com/QubeTX/qube-network-diagnostics/releases/latest). All four install **both** `nd300.exe` and `speedqx.exe` and add the install dir to your `PATH`. Pick **one format per edition** (installing two formats of the same edition leaves duplicate Add/Remove Programs entries).

| Edition | Scope | Admin (UAC)? | Installs to | Download |
|---|---|---|---|---|
| **Global** MSI | Per-machine | Yes | `C:\Program Files\nd300\bin\` | `nd300-x86_64-pc-windows-msvc.msi` |
| **Corporate** MSI | Per-user | No | `%LocalAppData%\Programs\nd300\bin\` | `nd300-x86_64-pc-windows-msvc-corporate.msi` |
| **Global** EXE | Per-machine | Yes | `C:\Program Files\nd300\bin\` | `nd300-x86_64-pc-windows-msvc-setup.exe` |
| **Corporate** EXE | Per-user | No | `%LocalAppData%\Programs\nd300\bin\` | `nd300-x86_64-pc-windows-msvc-corporate-setup.exe` |

- **Corporate** editions install per-user with **no admin prompt** — ideal for locked-down corporate machines where you can't elevate.
- Each installer writes a small `HKCU\Software\ND300\InstallSource` marker so `nd300 update` knows which installer to re-download for an in-place upgrade (see [Self-Update](#self-update)).
- Each installer has a `.sha256` sidecar; `nd300 update` verifies it before running the downloaded installer (refuse-on-mismatch).
- The cargo-dist PowerShell installer above remains available and installs into Cargo's bin directory.

**One install at a time — automatic consolidation.** Each installer offers two
clean-up checkboxes, **both checked by default**:

- **Remove an older Cargo-installed copy** — deletes a shadowing `nd300`/`speedqx`
  in your `~\.cargo\bin` (a prior `cargo install` copy usually wins on `PATH`
  otherwise). Your Rust toolchain is never touched — `cargo.exe`, `rustup.exe`,
  and the `.cargo\bin` `PATH` entry are left exactly as they were.
- **Also remove the other edition** — deletes the other Windows edition
  (Global ↔ Corporate) so only one edition remains.

The clean-up only ever removes `nd300.exe`/`speedqx.exe`, never the copy you're
running, and never anything in your Downloads folder. If removing the other
edition needs admin rights the installer doesn't have (e.g. a per-user install
trying to remove a per-machine one), it simply skips that step and reports it —
it never blocks or fails the install. **This consolidation also runs on a silent
self-update**, so a routine `nd300 update` quietly leaves you with a single,
current install. (Under the hood this is the hidden `nd300 migrate-cleanup`
command, invoked by the installers; you never need to run it by hand.)

### Cargo

```sh
cargo install nd300
```

Older installed copies that still request `nd-300-installer.sh` or `nd-300-installer.ps1` are supported by compatibility aliases on every new GitHub release, so the legacy updater path can still reach the current installers.

Cargo itself cannot run ND300-specific uninstall hooks for unrelated old install locations. If an older installer-managed `nd300` appears before Cargo's bin directory on your `PATH`, run `nd300 update` first, or run `nd300 uninstall` before `cargo install nd300`; the updater knows how to remove the old ND300 layout and complete the Cargo install.

### From Source

```sh
git clone https://github.com/QubeTX/qube-network-diagnostics.git
cd qube-network-diagnostics
cargo build --release
```

## Usage

### nd300 — Network Diagnostic

As of v3.0.0, every action command is available **either as a subcommand or as a flag** — both forms are first-class and produce identical behavior. Pick whichever you prefer.

```sh
# Default user mode — clean summary
nd300

# Technician mode — full deep diagnostics
nd300 -t

# Change DNS configuration (interactive)
nd300 dns          # subcommand form (recommended); on success continues to diagnostics
nd300 -d           # legacy flag form (still supported); identical behavior

# Diagnostic-driven fix loop (subcommand form, recommended)
nd300 fix

# Same thing — legacy flag forms (still supported)
nd300 -f
nd300 --fix

# Auto-confirm medium-risk prompts (does NOT bypass high-risk Y/N)
nd300 fix --yes
nd300 -f -y

# Skip the speed test for faster execution
nd300 --fast
nd300 fix --fast

# JSON output for scripting
nd300 --json
nd300 fix --json    # JSON works after subcommands too (global flag)

# ASCII characters instead of Unicode
nd300 --ascii

# Disable colored output
nd300 --no-color

# Custom report title
nd300 -T "Office Network Check"

# Custom speed test duration per provider (seconds)
nd300 --speed-duration 20

# Self-update — both forms work
nd300 update
nd300 --update

# Reset DNS cache — both forms work
nd300 clear-dns
nd300 --clear-dns
nd300 -c

# Uninstall — both forms work
nd300 uninstall
nd300 --uninstall

# Show help
nd300 --help
nd300 fix --help    # subcommand-specific help
```

### speedqx — Standalone Speed Test

```sh
# Full eight-provider speed test (Cloudflare + NDT7 + MSAK + LibreSpeed + fast.com + CacheFly + Vultr + Apple)
speedqx

# Custom duration per direction (60s download + 60s upload per provider)
speedqx --duration 60

# Override fast.com duration (defaults to "auto")
speedqx --fastcom-duration 30

# Classic 4-provider run
speedqx --skip-msak --skip-apple

# JSON output
speedqx --json

# Quick test with shorter duration
speedqx --duration 10 --latency-probes 5
```

## Example Output

```
╔══════════════════════════════════════════════════════════════╗
║                   ND-300 Network Diagnostic                 ║
╠══════════════════════════════════════════════════════════════╣
║ Category         │ Status │ Details                         ║
╠══════════════════╪════════╪═════════════════════════════════╣
║ Network Adapters │  OK    │ Wi-Fi (connected)               ║
║ IP Configuration │  OK    │ 192.168.1.42/24                 ║
║ Default Gateway  │  OK    │ 192.168.1.1                     ║
║ DNS Resolution   │  OK    │ 1.1.1.1, 8.8.8.8               ║
║ Public IP        │  OK    │ 203.0.113.45 (Cloudflare)       ║
║ Latency          │  OK    │ 12ms avg (1.1.1.1)             ║
║ Speed Test       │  OK    │ ↓ 245 Mbps  ↑ 48 Mbps          ║
║ Port Check       │  OK    │ 8/8 reachable                   ║
╚══════════════════╧════════╧═════════════════════════════════╝
```

## Command Line Options

| Flag | Description |
|------|-------------|
| `-t, --tech` | Technician mode — show full technical report with deep diagnostics |
| `-T, --title <TITLE>` | Custom title for the report header |
| `--json` | Output results as JSON |
| `--ascii` | Use ASCII characters instead of Unicode box-drawing |
| `--no-color` | Disable colored output |
| `--fast` | Skip the speed test (faster execution) |
| `--speed-duration <SECS>` | Speed test duration in seconds, per direction per provider (default: 15, min: 4) |
| `--verbose` | Show additional debug/trace information |
| `-d, --dns` | Change DNS servers and verify connectivity (requires elevated privileges) |
| `-f, --fix` | Run the diagnostic-driven triage / fix loop (requires elevated privileges). Equivalent to `nd300 fix`. |
| `-c, --clear-dns` | Flush the system DNS cache. Equivalent to `nd300 clear-dns`. |
| `--uninstall` | Remove nd300 from the system. Equivalent to `nd300 uninstall`. |
| `--update` | Check for updates and install the latest version. Equivalent to `nd300 update`. |
| `-y, --yes` | Auto-confirm medium-risk prompts when running the fix flow. Does **not** bypass high-risk Y/N. |
| `-h, --help` | Print help |
| `-v, --version` | Print version |

### Subcommands

| Subcommand | Equivalent flag | Description |
|------------|-----------------|-------------|
| `nd300 dns` | `nd300 -d` / `nd300 --dns` | Change DNS servers and verify connectivity (semi-exit-early: exits on failure, continues to diagnostics on success) |
| `nd300 fix [-y]` | `nd300 -f` / `nd300 --fix` | Diagnostic-driven triage and recovery loop |
| `nd300 update` | `nd300 --update` | Self-update to the latest release |
| `nd300 clear-dns` | `nd300 -c` / `nd300 --clear-dns` | Flush the DNS cache and exit |
| `nd300 uninstall` | `nd300 --uninstall` | Uninstall nd300 from this system |

## SpeedQX Options

| Flag | Description |
|------|-------------|
| `--json` | Output results as JSON |
| `--ascii` | Use ASCII characters instead of Unicode box-drawing |
| `--no-color` | Disable colored output |
| `--duration <VALUE>` | Test duration per direction for CF/NDT7/LS/MSAK/Apple: seconds or "auto" (default: 30) |
| `--fastcom-duration <VALUE>` | Test duration per direction for fast.com: seconds or "auto" (default: auto) |
| `--latency-probes <N>` | Number of latency probes (default: 20) |
| `--skip-msak` | Skip the M-Lab MSAK multi-stream provider |
| `--skip-apple` | Skip the Apple networkQuality provider |
| `--update` | Check for updates and install the latest version |
| `-v, --version` | Print version |
| `-h, --help` | Print help |

## User Mode vs Technician Mode

**User mode** (default) runs 8 core diagnostics and presents a clean summary table. Ideal for quick network health checks.

**Technician mode** (`-t`) runs all 8 core diagnostics plus 25 additional deep diagnostic modules. Produces a detailed technical report with per-module breakdowns, suitable for troubleshooting and support workflows. A full technician run takes ~2–4 minutes — the long probes (traceroute, sustained packet loss, loaded bufferbloat) are doing real work.

## Diagnostics Covered

### Core Diagnostics (both modes)
1. Network Adapters
2. IP Configuration / Interfaces
3. Default Gateway
4. DNS Resolution
5. Public IP
6. Latency
7. Speed Test (Cloudflare + M-Lab NDT7 in nd300; all 8 providers in SpeedQX)
8. Port Connectivity

### Deep Diagnostics (technician mode only)
9. ARP Table + Gateway Health
10. Routing Table
11. Active Connections
12. Listening Ports
13. DHCP Lease Info
14. Protocol Statistics
15. Adapter Hardware Details
16. Proxy Detection (+ PAC validation, WPAD discovery)
17. VPN Detection
18. Firewall Status
19. DNS Cache
20. IPv6 Connectivity (+ real HTTPS-over-v6 fetch, v4-vs-v6 comparison)
21. MTU per Interface + Path-MTU Discovery
22. Connection State Summary
23. Bufferbloat (real loaded-latency measurement, per-direction grades)
24. Reverse DNS
25. TLS Inspection
26. Traffic Counters
27. Route Path (traceroute with LAN/ISP/backbone analysis)
28. Sustained Packet Loss (30 probes × 3 targets)
29. NAT Analysis (double-NAT, CGNAT detection)
30. Wi-Fi Link Quality (signal, channel, band, PHY, security)
31. DNS Resolver Benchmark (+ NXDOMAIN-hijack and DNSSEC checks)
32. Captive Portal Detection
33. Clock Sync (SNTP offset measurement)

## Fix Flow (`nd300 fix` / `nd300 -f`)

The fix flow runs a **diagnostic-driven triage loop**: it tests the network, looks at what actually failed, applies only the recovery actions that target those specific failures, re-tests, and repeats. Requires elevated privileges (`sudo` on macOS/Linux, Administrator on Windows).

A clean network completes `nd300 fix` in under 8 seconds and applies zero actions. Latency-only findings are reported as advisory because ICMP can be rate-limited or deprioritized by healthy networks. A real failure usually clears in 1–2 iterations.

### How it works

```text
1. Run baseline diagnostics
2. If everything passes → done.
3. Look at which specific diagnostics failed.
4. Group them by root cause (e.g. interface-down ⇒ skip DNS/gateway/IP — they cascade).
5. Pick the safest justified action that targets a remaining failure and apply it.
6. Wait for the system to stabilize.
7. Re-run the diagnostics.
8. Repeat until everything passes, or no further actions are available.
```

The loop is bounded by **three independent caps** so it always terminates:

- ≤ 6 iterations
- ≤ 4 minutes wall clock
- Per-action attempt cap (typically 1 — failed actions are not retried)

### Action ladder

Actions are tried by evidence and risk: cheap first, reversible first, and only when the current failure set justifies the action. DNS repair is intentionally staged so the tool flushes caches and restores router-provided DNS before considering a public DNS change.

| Cost / risk | Action | Targets |
|---|---|---|
| Cheap / Low | Flush DNS cache | DNS |
| Cheap / Low | Reset DNS to router defaults | DNS |
| Cheap / Low | Switch DNS to Cloudflare 1.1.1.1 | DNS, only after safer DNS-specific fixes fail |
| Cheap / Low | Flush ARP cache | Gateway |
| Medium / Low | Restart networking services | DNS, gateway, public IP |
| Medium / Low | Renew DHCP lease | Gateway, public IP, adapters, interfaces |
| Medium / Medium | Temporarily disable consumer VPNs | Public IP, DNS; interactive confirmation required |
| Expensive / Medium | Restart the network adapter | Adapters, interfaces, gateway, DNS, public IP |
| Expensive / **High** | Deep stack reset (Winsock / TCP-IP / IPv6 on Windows; recreate network service on macOS; recreate NetworkManager profile on Linux) | Last-resort recovery |

Enterprise VPNs (Cisco AnyConnect, Zscaler, Palo Alto / GlobalProtect, F5, Check Point, Juniper) are **never** auto-disabled. Consumer VPN disable is also skipped in JSON or non-interactive mode, even with `--yes`, because the tool cannot safely guide re-enable/recovery steps there. Other medium-risk actions such as DHCP reset, service restart, adapter restart, and public-DNS changes are skipped in JSON or non-interactive mode unless `--yes` can safely auto-confirm the medium-risk prompt. High-risk actions are never auto-confirmed.

### Confirmation prompts

Medium-risk actions explain what they will change and require confirmation unless `--yes` is provided. The deep stack reset is high-risk and is always gated behind a structured plain-language prompt:

```text
┌─ Escalating: Reset Windows networking stack ──────────────────────────────
Why I want to do this:
  Gateway and DNS are still failing after DHCP renew and ARP flush.
  Resetting the networking stack rebuilds Windows' TCP/IP, Winsock,
  and IPv6 catalogs from scratch — the standard fix when simpler
  steps haven't recovered the connection.

What will happen:
  • You will lose internet for ~10–15 seconds.
  • Open VPN sessions and SSH connections will drop.
  • A reboot is recommended afterward; nd300 will remind you at the end.

Reversible: requires reboot to fully revert
Typical duration: 10–15 seconds

Continue? Type 'y' to proceed, anything else to skip:
```

`--yes` does **not** bypass high-risk prompts — they always require an explicit `y`. Anything else (including a blank Enter) is treated as N. In `--json` / non-interactive contexts, high-risk actions are skipped, not auto-applied, with a clear marker in the report.

### Hard-block detection

Some failure shapes can't be auto-fixed. The loop short-circuits cleanly with guidance instead of thrashing:

- **No physical link** — no cable / Wi-Fi.
- **ISP outage shape** — local network healthy, public internet failing.
- **Enterprise VPN active** — diagnostics shaped by a managed VPN we won't disable.

### Fix Report

Every run produces a Markdown report at `~/Downloads/nd300-fix-report-YYYYMMDD-HHMMSS.md`:

- **Verdict** — Fixed, Partially fixed, Couldn't fix, Cannot fix from here, Timed out, or Stopped at your request.
- **Baseline diagnostics** — full table snapshot before any action ran.
- **Iteration timeline** — for each loop pass: which actions ran, captured stdout/stderr/exit/duration, and the diagnostic delta after.
- **Final diagnostics** — same table as baseline so you can see what changed.
- **Environment** — OS, version, elevation status, VPNs detected.
- **What to try next** — concrete suggestions when the fix didn't fully succeed (restart router, contact ISP, check for driver updates, etc.).

In JSON mode (`nd300 fix --json`), the schema reports the same data as `outcome`, `iterations`, `applied_actions[]`, `remaining_failures[]`, `hard_block`, and `report_path`.

## DNS Configuration (`nd300 dns`)

`nd300 dns` (or the legacy flag `nd300 -d` / `--dns`) provides a standalone way to change your DNS configuration. Requires elevated privileges. The subcommand and flag are identical: both are semi-exit-early — they exit on failure, and on success fall through to running standard diagnostics so you can immediately confirm the change improved connectivity.

**Providers:**
- **Cloudflare** (recommended) — 1.1.1.1 (privacy-focused)
- **Google** — 8.8.8.8 (reliability)
- **NextDNS** — Encrypted DNS with filtering (requires config ID)
- **Automatic** — Reset to system default (DHCP)
- **Hybrid** (not recommended) — Cloudflare + Google

**NextDNS encryption by platform:**
- **Windows** — DNS-over-HTTPS via `netsh` DoH template registration
- **Linux** — DNS-over-TLS via `systemd-resolved` (falls back to plain IPs via `nmcli`)
- **macOS** — NextDNS CLI client (`nextdns install/activate`), plain IPs if CLI not installed

After setting DNS, the tool verifies both DNS resolution and HTTP connectivity. If verification fails, it automatically reverts to DHCP and reports the result. On success, full diagnostics run to confirm network health.

## Man Pages (Linux/macOS)

Man pages are generated at build time and included in release archives. To install after building from source:

```sh
sudo cp man/nd300.1 /usr/share/man/man1/
sudo cp man/speedqx.1 /usr/share/man/man1/
sudo mandb
```

Then use `man nd300` or `man speedqx` to view documentation.

## Building from Source

```sh
git clone https://github.com/QubeTX/qube-network-diagnostics.git
cd qube-network-diagnostics
cargo build --release
```

Binaries will be at `target/release/nd300` and `target/release/speedqx` (or `.exe` on Windows).

## Self-Update

Both binaries support self-updating to the latest release. Either form works:

```sh
nd300 update          # subcommand form (recommended)
nd300 --update        # legacy flag form (still supported)
speedqx update
speedqx --update
```

The updater runs a **probe-and-retry chain** so missing tools don't block the update:

1. Checks the latest release on GitHub. If you're already on it, exits 0 with no action.
2. Tries `cargo install nd300 --force` first when `cargo --version` succeeds on your system.
3. If Cargo succeeds while the currently running ND300 copy lives outside Cargo's bin directory, removes the old installer-managed `nd300`/`speedqx` layout so the new Cargo install is not shadowed on `PATH`.
4. If Cargo reports that an existing `nd300` or `speedqx` binary is blocking installation, runs ND300's uninstall cleanup for the current install and retries `cargo install nd300 --force`.
5. After a successful `cargo install`, re-runs `nd300 --version` to confirm the new version actually landed. crates.io can briefly serve the old version right after a release (publish lag), or a different `nd300` may be earlier on your `PATH`; if the running binary didn't change, the updater falls through to the prebuilt installer (which always carries the latest version) instead of looping "update available" forever.
6. If cargo isn't available (or still fails), falls through to the cargo-dist installer for your platform — `curl | sh` then `wget | sh` on macOS/Linux, `powershell.exe` then `pwsh.exe` on Windows. The installer URL uses GitHub's `releases/latest` redirect, so it always resolves to the newest release.
7. Whichever strategy succeeds first wins; the chain stops there.
8. If every strategy fails, both pretty and `--json` output show a per-attempt diagnostic block listing what was tried and why each failed, so you can fix the environment manually.

### Windows installer-aware self-update

If you installed via one of the four first-class Windows installers (MSI/EXE × Global/Corporate — see [Installation](#windows-installers-msi--exe-global--corporate)), `nd300 update` reads the `HKCU\Software\ND300\InstallSource` marker that installer wrote and downloads the **matching** installer for an in-place upgrade — rather than switching you to a different installer/format (which would leave duplicate Add/Remove Programs entries). If no marker is found, it falls back to detecting the install from the binary's path; cargo / PowerShell-installer users get the cargo-first chain above.

Before running a downloaded MSI/EXE, the updater fetches the asset's `.sha256` sidecar and verifies the download against it (refuse-on-mismatch) — defending against a corrupted download or a network MITM (corporate TLS-interception proxies, hostile WiFi). After the installer exits, it re-runs `--version` to confirm the file replacement actually took effect (and surfaces the reboot-required case honestly if Windows scheduled a deferred replace).

**Consolidation runs on a silent self-update too.** The Windows installers re-run by `nd300 update` carry the same two clean-up options (remove an older Cargo copy, remove the other edition) — both **default on**, and the silent self-update path (`msiexec /passive`, Inno `/SILENT`) keeps them on, so a routine update leaves you with exactly one current install/edition. Anything that would need admin rights the update doesn't have is skipped and reported, never failing the update.

Versioning is prerelease-aware: a prerelease of an upcoming version is treated as newer than the previous stable patch, and a stable release is newer than its own prerelease. GitHub's unauthenticated rate-limit case (60 requests/hour per IP) is named explicitly so you know to just wait.

Current releases publish the canonical `nd300-installer.sh` and `nd300-installer.ps1` assets plus legacy `nd-300-installer.*` aliases for older installed copies.

### Update JSON output

In `--json` mode, the response includes `"strategy"` (the precise variant that ran or was attempted) and, on failure, an `"attempts"` array. The legacy `"method"` field still maps to `"cargo"` or `"installer"` for backward compatibility. On **Windows**, every update payload also carries a top-level `"install_origin"` field — one of `msi-global`, `msi-corporate`, `exe-global`, `exe-corporate`, `cargo-or-installer`, or `unknown` (the field is omitted on macOS/Linux, where install origin doesn't vary).

```json
{
  "action": "update",
  "success": true,
  "current_version": "3.0.11",
  "latest_version": "3.1.0",
  "update_available": true,
  "method": "installer",
  "strategy": "msi_corporate",
  "install_origin": "msi-corporate"
}
```

The precise `"strategy"` values are `cargo`, `installer_curl`, `installer_wget`, `installer_powershell`, `installer_pwsh`, `msi_global`, `msi_corporate`, `exe_global`, and `exe_corporate`.

## Speed Test Methodology

The speed test uses a technician-grade accuracy pipeline matching the [SpeedQX web speed test](https://speedqx.com/how-it-works):

- **Modified trimean** (Ookla-style): `(P10 + 8*P50 + P90) / 10` for robust central tendency
- **30% slow-start discard**: eliminates TCP ramp-up contamination
- **IQR outlier filtering**: removes transient congestion and measurement artifacts
- **Winsorized cross-validation**: catches edge cases where IQR filtering is too aggressive
- **Upload-specific pipeline**: keeps fastest 50% of post-warmup samples (following Speedtest.net methodology)
- **RFC 3550 jitter**: exponentially weighted moving average, the standard used by VoIP and real-time media
- **Bounded inverse-variance weighted aggregation**: combines every successful provider while preventing any single provider from dominating the result
- **Provider divergence detection**: flags when the full provider spread differs by >30%, indicating possible throttling or QoS

For a full technical breakdown, see the [SpeedQX Technical Report](https://speedqx.com/how-it-works).

## License

[PolyForm Noncommercial 1.0.0](https://polyformproject.org/licenses/noncommercial/1.0.0/)
