galata. GitHub ↗

ADR-0015: Supported platforms are Linux and macOS

Context

Until this record the engine was built and tested on four legs — Linux with GCC, Linux with Clang, macOS with AppleClang, and Windows with MSVC — and every release shipped a windows-x86_64 archive beside the others.

Windows was the only failing check on the M2 branch, and it failed in the place that matters most for a platform anyone is expected to install: the installed-package consumer smoke test in scripts/check-install.py. A relocated out-of-tree consumer linking the installed galata::modeling could not parse a YAML document that galata had just written correctly. It first appeared as an access violation, 0xC0000005, which identifies nothing. Naming each invariant in the smoke test turned it into a yaml-cpp report of an unknown escape character on a pure-ASCII document with LF line breaks, whose third line is blocks: and which contains no backslash anywhere. A byte dump then established that Windows writes the same document macOS does, and that the same round trip passes throughout the Windows test suite against the build tree — only the relocated consumer fails. That narrows the defect to what the installed consumer resolves against rather than to galata's serialisation, and there it stopped. It is filed as issue 12 and was never root caused.

The price of the fourth platform is visible in the branch this record was written on, merged as pull request 11. Seven of its thirteen commits — 5dcf158, ef638a8, 5b023af, b8ba5a1, 750add8, 9d03aaa and 6066f2f — are hosted-matrix portability and diagnosis work rather than the increment they were meant to deliver, and the last two of those are a debug probe for the Windows failure and its revert. Each attempt is a round trip through a runner nobody here can attach a debugger to. That branch was squash-merged as f19c562 and then deleted, so none of those seven commits is reachable from main; they survive on the pull request, and git fetch origin refs/pull/11/head brings them into a clone.

Meanwhile the product plan already records that delivery prioritises macOS, then Linux, per user direction. Nobody uses galata on Windows. The platform with the weakest evidence was being maintained for a user who does not exist.

Withdrawing it also undoes work this repository has already paid for. Pull request 2, "Ship the Windows DLLs with the packaged binary", exists for no purpose other than making the Windows archive start on a machine that did not build it. This decision withdraws what that pull request delivered, and a record that does not say so is worth less than one that does.

Decision

Withdraw Windows as a supported platform. Supported platforms are Linux (GCC and Clang) and macOS (AppleClang).

The Windows leg leaves the engine matrix in the CI workflow and the fingerprint matrix in the determinism workflow. The windows-x86_64 packaging leg leaves the release workflow. The ci-windows configure, build and test presets leave CMakePresets.json. The MSVC developer-environment step leaves the setup-cpp composite action, which removes this repository's only use of the third-party action ilammy/msvc-dev-cmd@v1. vcpkg.json now declares support for Linux and macOS on x64 and arm64. scripts/package-release.py loses the Windows platform entry, the .exe suffix, the dependency-DLL staging block and the zip-versus-tarball choice, so every supported platform ships one archive format; scripts/check-release-evidence.py expects the two remaining platforms; and scripts/check-install.py and scripts/check-release-archive.py lose their .exe handling. The bug-report template no longer offers Windows in its Platform dropdown.

The MSVC branches in cmake/GalataDeterminism.cmake and cmake/GalataWarnings.cmake go as well. They are the easiest thing here to argue for keeping, because leaving dead compiler branches in a CMake file costs nothing to run. But ADR-0004 claims determinism is a tested property, and a floating-point mode selected for a compiler no job exercises is a determinism claim with no test behind it. Deleting it is the honest form of not testing it.

Three things deliberately do not change. The asan preset keeps its condition excluding Windows hosts: that is a refusal to configure there, not a promise about it. scripts/ci_evidence.py is untouched, because its required-job set names workflow job ids — format, governance, engine, sanitizer, static-analysis and determinism — rather than platform legs, and none of those jobs disappears. And Windows-shaped input hygiene stays: the study-file guide refuses Windows device names in output paths, and refusing CON or NUL is portable defensive validation that says nothing about which platforms are supported.

The archives already published under v0.1.0 and v0.2.0 stay where they are. Each of those releases publishes one SHA256SUMS.txt covering every asset it shipped, so deleting the Windows zip would invalidate the checksum file every other platform in that release depends on. scripts/check-release-archive.py therefore still reads zip archives on purpose, so those two remain verifiable. They are historical and unmaintained, and no future release ships one.

Alternatives considered

Root-cause the installed-consumer failure and fix it. This is the option a stranger would pick, and it has the better argument on the merits: a fault that appears only in a relocated installed package is the shape of a real defect — a mismatched runtime, two yaml-cpp copies resolved at load time, an ODR violation — and such a defect can be latent on every platform while visible on one. That branch has already been the beneficiary of exactly that effect twice, in 5b023af and b8ba5a1, where a compiler galata does not develop on found a dangling reference and an uninitialised read that the local toolchain did not. Against that: the failure reproduces only on a hosted runner, and two commits were spent merely obtaining a legible symptom, which established what the defect is not and left the question of which yaml-cpp the installed consumer binds against unanswered. The remaining work is bounded by nothing, is paid in push-and-wait iterations, and its success condition is a green check for a platform with no user. Nor is it a one-off payment: succeeding restores the state in which every later change is taxed by the same matrix. The messenger this gives up is real, and named below as a cost.

Keep the Windows leg but make it advisory. The cheapest change on this list, and it keeps evidence accruing without blocking a merge. But Windows is a leg inside the engine job, not a job of its own, and the only way to stop one leg failing the job is continue-on-error. That makes engine report success while Windows is broken — and scripts/ci_evidence.py reads job results, not matrix legs, so ADR-0009's release gate would then accept a complete, all-successful required job set that conceals a failing platform. Splitting Windows into its own advisory job avoids that, at the cost of a permanently red check nobody may block on. Neither is evidence; both are a support claim standing next to its own counterexample.

Keep Windows building but stop shipping a Windows archive. This preserves what is genuinely valuable about the platform — MSVC is a third front end, with a different standard library and a different warning set — while retiring the packaging surface. It is also the narrowest change that could make CI green. The trouble is where the failure actually lives: scripts/check-install.py tests the installed package, so this option either keeps that check, in which case the job still fails, or deletes it, in which case the surviving Windows evidence is that the tree compiles and its in-tree suite passes. A configuration that is never installed and never shipped is a configuration nobody consumes, and every MSVC portability commit would still be paid in full. That is the whole cost of a platform for part of the assurance.

Consequences

Three engine legs instead of four, and one determinism fingerprint fewer. The release path has one archive format and one dependency-staging story rather than two of each. cmake/GalataDeterminism.cmake and cmake/GalataWarnings.cmake describe only compilers that are exercised. A portability defect that appears only under MSVC no longer blocks this repository — and no longer reaches it either.

That last point is the real cost, and it comes in two parts. Losing MSVC loses a front end whose diagnostics differ from GCC's and Clang's, of the same kind that caught defects on this branch; what survives is two Linux compilers, the macOS compiler, the sanitizer job and static analysis. And determinism tier 2 drops from three platforms to two. Its published relative bound and its every-pair comparison are unchanged — with two platforms, every pair is the one pair, Linux against macOS — but the evidence behind the cross-platform claim is narrower than it was. A libm disagreement that glibc and Apple's libm happen to share is no longer contradicted by a third implementation. Tier 1, the byte-identical same-platform guarantee, is now gated on Linux and macOS and is otherwise unaffected.

Undoing this decision means restoring, specifically: the engine matrix leg in the CI workflow; the fingerprint matrix leg and the tier-2 fingerprint count in the determinism workflow; the packaging leg in the release workflow; the ci-windows configure, build and test presets in CMakePresets.json; the MSVC developer-environment step in the setup-cpp composite action, together with a fresh review of the third-party action it depends on; the MSVC branches in cmake/GalataDeterminism.cmake and cmake/GalataWarnings.cmake; the .exe suffix, dependency-DLL staging and zip selection in scripts/package-release.py, and the .exe handling in scripts/check-install.py and scripts/check-release-archive.py; the expected platform set in scripts/check-release-evidence.py; the supports expression in vcpkg.json; and the Platform entry in the bug-report template. None of that is difficult, and all of it is recoverable from the diff of f19c562. What is not recoverable that way is the reason the leg was red: restoring Windows means starting from issue 12, not from a green build.

Revisit when

Revisit when a named user requires galata on Windows, or when a Windows build becomes a precondition of a delivery commitment that has been accepted — the condition the product plan leaves open by making delivery priorities follow user direction. Reopening this record means reopening issue 12 first: the platform comes back when its installed package is explained, not when its matrix leg is pasted back in.