- Status: accepted (the per-push Windows CI lane element is superseded by ADR 0033; the toolchain and pinning elements stand)
- Date: 2026-09-16
- Governs: the declared Elixir range, the supported Erlang/OTP major set, their enforcement point, and the platform portability bar for the library contract
Context
Before this ADR, toolchain identity lived outside the repository. The declared Elixir
range (~> 1.18) carried no rationale binding it to anything; the CI compatibility
lanes were hand-picked with nothing tying them to a discovered set; and nothing in
the repository refused a build on an unsupported Erlang/OTP major — a host PATH, a
personal install, or an out-of-band version manager alone could drive a build.
Two probes settle the supported set for the declared line (Elixir 1.18/1.19/1.20):
- Precompiled build availability (asdf
list all, the officiallibrary/elixirdocker tags, both channels agreeing): Elixir 1.18 has OTP 25–28 builds, 1.19 has 26–28, 1.20 has 27–29. Build availability alone would admit {25..29}. - The package's own stdlib floor:
lib/bounded_authority_protocol/v1/json.exdecodes through the:jsonmodule, which enters the Erlang stdlib in OTP 27 (there is no JSON fallback dependency). OTP 25 and 26 cannot run the codecs even though images exist for them; the siblingbounded_authority_report_adapteraudit probed the same wall independently from the consumed Hex package (:json.decode/1UndefinedFunctionError on OTP 25/26).
Separately, the owner ruled on 2026-09-16 that the library contract — git clone,
deps.get, compile, test — must hold on macOS, Linux, and Windows ("if this
cannot be cloned on a Windows machine it is a failed project"). A default
Git-for-Windows clone (core.autocrlf=true) would have checked every tracked text
file out with CRLF — byte-mangling the certified corpora whose index SHA-256 is
pinned at load — and the test path carried POSIX-only mechanisms (shell-shim
System.cmd targets, /tmp literals, mktemp, sh -c, chmod-based failure
injection).
Decision
- The supported-OTP set is {27, 28, 29}: the probe-derived build set for the
declared line, intersected with the package's own OTP 27 stdlib floor. This is
the set's rationale, not a change to the declared Elixir range, which stays
~> 1.18. config/config.exsraises at config-load time — before any compilation, for every Mix invocation in this repository — when the running OTP major is outside the set. The file is not shipped:files:in mix.exs excludesconfig/, so consumers of the published package never inherit this assert; each consuming repository enforces its own toolchain.- Lockstep rule: the mix.exs Elixir range, the config supported-OTP set,
.tool-versions, and the CI compatibility lanes move together in one commit. A supported major without a CI lane is a defect; a lane outside the set is a defect. - Tri-platform build bar: the library contract must hold on macOS, Linux, and
Windows.
.gitattributesforces LF checkout everywhere (* text=auto eol=lf) and marks the byte-exact conformance trees-text. The test path contains no POSIX-only mechanism:mix/elixir/escriptsubprocesses route through one portable wrapper (test_support/portable.ex,cmd /con win32), scratch paths useSystem.tmp_dir!(), and the two mechanisms with no Windows equivalent (chmod-based unreadability, the POSIX-shell publish guard) carry compile-time carve-outs naming the POSIX lanes that still cover them. Awindows-2025CI lane proves the contract continuously. The fullmix qualitybattery remains POSIX-only tooling (shell gates, Gitleaks, ProVerif, kramdown) and runs on the Linux lanes or WSL.
Consequences
- Refusals are value-free and pre-compilation. Proof legs (throwaway source copy,
official docker images): Elixir 1.17.3/OTP 27 is refused by Mix with the declared
range error; Elixir 1.19.6/OTP 26 and Elixir 1.18.4/OTP 25 are refused at config
load with the assert message (
supports Erlang/OTP 27/28/29; running 26/25); Elixir 1.18.4/OTP 27 compiles green underMIX_ENV=test. The dev lane (.tool-versions, Elixir 1.20.2-otp-29 / Erlang 29.0.3) compiles green. - The Windows surface is gated and observed green: the
windows-2025lane proved checkout,deps.get,compile --warnings-as-errors, andmix test(445/445) on its first completed run (2026-09-17) and re-proves it on every push. The checkout-bytes claim is enforced by the eol policy (a defaultcore.autocrlfclone would otherwise CRLF-convert the digest-pinned corpora); the POSIX-only test mechanisms (chmod-based unreadability, symlink-privilege cases, Win32-unrepresentable path components, the POSIX-shell publish guard) are carved out with the POSIX lanes named as their coverage at each site. - Hex consumers see no new behavior: the assert never ships, and published-package bytes do not change.