zenops 0.20.0

Declarative system configuration management for shell config and dotfiles.
"""Cross-platform test matrix.

Each entry is a (distro, version, shell, package_manager, tier) row.
The tier says what a row is for, not how often it runs: `run.py`
defaults to the *gate* set, which is tiers 1 and 3.

`tier=1` rows are the supported set. `tier=2` rows are accepted by host
detection but not gated: run them locally with ``--tier all`` to
spot-check that an older or newer release still works. `tier=3` rows
test distros zenops *hasn't* manually wired into the supported set:
detection must not crash, file management and shell init must still
work, and missing install-hint entries are graceful no-ops. That's a
claim worth gating, so tier 3 runs alongside tier 1.

These tiers are a test-harness bucket, not a public support promise.
The README states what we support in plain prose and does not mirror
this split. Keeping the tier-1 and tier-2 rows current is a release
chore: before a release, check the Ubuntu and Fedora end-of-life dates
on endoflife.date, add any release that has newly shipped, and drop any
row whose release has reached end of life. That stops the matrix
drifting between releases.

The Dockerfiles are version-parameterized via the ``VERSION`` build arg
so a single ``fedora.Dockerfile`` covers every Fedora row; the
``archlinux`` Dockerfile ignores the version (rolling distro).
"""
from dataclasses import dataclass


@dataclass(frozen=True)
class Entry:
    distro: str           # Dockerfile stem: "fedora", "ubuntu", "archlinux", "debian"
    version: str | None   # Build-arg value; None for rolling distros.
    shell: str            # "bash" or "zsh"
    package_manager: str  # "dnf", "apt", "pacman" — implied by distro today.
    tier: int             # 1 = supported set, 2 = opt-in extended run, 3 = unsupported-but-detected.


MATRIX: list[Entry] = [
    # Tier 1 — the supported set; runs in the gate.
    Entry("fedora",    "42",    "bash", "dnf",    tier=1),
    Entry("fedora",    "42",    "zsh",  "dnf",    tier=1),
    Entry("ubuntu",    "24.04", "bash", "apt",    tier=1),
    Entry("ubuntu",    "24.04", "zsh",  "apt",    tier=1),
    Entry("archlinux", None,    "bash", "pacman", tier=1),
    Entry("archlinux", None,    "zsh",  "pacman", tier=1),
    # Tier 2 — accepted by detection, not in CI. Bash only to keep the
    # opt-in run fast; the shell dimension is exercised in tier 1.
    Entry("fedora",    "41",    "bash", "dnf",    tier=2),
    Entry("fedora",    "40",    "bash", "dnf",    tier=2),
    Entry("ubuntu",    "22.04", "bash", "apt",    tier=2),
    Entry("ubuntu",    "24.10", "bash", "apt",    tier=2),
    # Tier 3 — runs in the gate. Distros zenops hasn't manually wired for, but
    # Platform::detect accepts them anyway. Debian is the canonical case:
    # ID=debian (not in the tier-1/-2 set) but apt is on PATH so install
    # hints fire normally, file management and shell init work, and user
    # configs can target it with `when = "debian"` without zenops needing
    # to add a code-level row.
    Entry("debian",    "12",    "bash", "apt",    tier=3),
]

DISTROS = sorted({e.distro for e in MATRIX})
SHELLS = sorted({e.shell for e in MATRIX})