diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index fe75617..abeffcd 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -200,6 +200,22 @@ jobs: # because it needs the full set of declared identities. - name: Lint cross-package references run: lua5.4 tests/check_cross_package_refs.lua pkgs/*/*.lua + # ── Partial version bumps ──────────────────────────────────────── + # A bump is a one-line-looking edit that has to land in N platform + # sections. Editing `xpm.linux` and reading the file back gives a file + # that CONTAINS the new version, so the author -- and any whole-file + # grep -- sees success, while the other platforms are left behind. The + # failure then surfaces as `@ not found` on a platform, + # against a file that literally contains that version string. + # + # Measured 2026-08-06: xpkg 0.0.52 and 0.0.53 were both added to + # `xpm.linux` alone; linux CI went green twice while macOS and Windows + # failed, and eight checks made from the outside all came back correct + # because each asked "is it in the index?" instead of "is it in THIS + # platform's section?". Whole-repo, because a partial bump is only + # visible by comparing sections against each other. + - name: Lint platform version parity + run: lua5.4 tests/check_platform_version_parity.lua pkgs/*/*.lua # ── Single-source-of-truth grammar check ───────────────────────── # `mcpp xpkg parse` uses EXACTLY the resolver's parser, so what # passes here is what builds for users of the pinned MCPP_VERSION. diff --git a/pkgs/x/xpkg.lua b/pkgs/x/xpkg.lua index 7fdf042..5e1a42c 100644 --- a/pkgs/x/xpkg.lua +++ b/pkgs/x/xpkg.lua @@ -112,6 +112,20 @@ package = { }, }, macosx = { + ["0.0.53"] = { + url = { + GLOBAL = "https://github.com/openxlings/libxpkg/archive/refs/tags/0.0.53.tar.gz", + CN = "https://gitcode.com/mcpp-res/xpkg/releases/download/0.0.53/xpkg-0.0.53.tar.gz", + }, + sha256 = "158da808846c09cb8758322828a6175a636d74f570f4ac47575c844f93c20cf8", + }, + ["0.0.52"] = { + url = { + GLOBAL = "https://github.com/openxlings/libxpkg/archive/refs/tags/0.0.52.tar.gz", + CN = "https://gitcode.com/mcpp-res/xpkg/releases/download/0.0.52/xpkg-0.0.52.tar.gz", + }, + sha256 = "5cfa70af911b31a3c142a0a8e7faee9c56b1655baa5963d3153b1721e1f9c60c", + }, ["0.0.51"] = { url = { GLOBAL = "https://github.com/openxlings/libxpkg/archive/refs/tags/0.0.51.tar.gz", @@ -198,6 +212,20 @@ package = { }, }, windows = { + ["0.0.53"] = { + url = { + GLOBAL = "https://github.com/openxlings/libxpkg/archive/refs/tags/0.0.53.tar.gz", + CN = "https://gitcode.com/mcpp-res/xpkg/releases/download/0.0.53/xpkg-0.0.53.tar.gz", + }, + sha256 = "158da808846c09cb8758322828a6175a636d74f570f4ac47575c844f93c20cf8", + }, + ["0.0.52"] = { + url = { + GLOBAL = "https://github.com/openxlings/libxpkg/archive/refs/tags/0.0.52.tar.gz", + CN = "https://gitcode.com/mcpp-res/xpkg/releases/download/0.0.52/xpkg-0.0.52.tar.gz", + }, + sha256 = "5cfa70af911b31a3c142a0a8e7faee9c56b1655baa5963d3153b1721e1f9c60c", + }, ["0.0.51"] = { url = { GLOBAL = "https://github.com/openxlings/libxpkg/archive/refs/tags/0.0.51.tar.gz", diff --git a/tests/check_platform_version_parity.lua b/tests/check_platform_version_parity.lua new file mode 100644 index 0000000..9c18fa7 --- /dev/null +++ b/tests/check_platform_version_parity.lua @@ -0,0 +1,124 @@ +-- Every platform section of a package must carry the same set of versions. +-- +-- WHY THIS EXISTS +-- +-- A version bump is a one-line-looking edit that has to land in N places. +-- Editing `xpm.linux` and reading the file back gives a file that contains +-- the new version -- so the author, and any whole-file grep, sees success. +-- The other platforms are simply left behind, and the failure surfaces +-- somewhere else entirely: `mcpplibs.xpkg@0.0.53 not found` on macOS, +-- against a file that literally contains `["0.0.53"]`. +-- +-- Measured 2026-08-06: xpkg 0.0.52 and 0.0.53 were both added to `xpm.linux` +-- alone. Linux CI went green twice. macOS and Windows failed, and the cause +-- was diagnosed eight times from the outside -- index commit, published +-- artifact, rolling pointer, releases/latest, publish lag, the client version +-- pin, install-vs-use, the lockfile hash -- each of which was correct, +-- because each asked "is 0.0.53 in the index?" rather than "is it in THIS +-- PLATFORM's section?". +-- +-- THE RULE +-- +-- Within one descriptor, every platform section that carries any version +-- entry must carry the same set of version entries. +-- +-- Packages that genuinely diverge (a platform that stopped at an older +-- release, or ships versions the others never had) opt out explicitly: +-- +-- package = { +-- ... +-- -- +-- platform_versions_diverge = true, +-- } +-- +-- The opt-out is a claim someone made on purpose, which is the difference +-- between a divergence and an omission. Surveyed at the time this was +-- written: 80 descriptors, 0 divergences -- so the rule costs nothing today +-- and only fires on a partial bump. +-- +-- Usage: lua5.4 tests/check_platform_version_parity.lua [...] +-- Exits non-zero, with ::error lines, listing what each platform is missing. + +function import(...) + return setmetatable({}, {__index = function() return function() end end}) +end + +local fail = 0 + +local function err(file, msg) + io.stderr:write(string.format("::error file=%s::%s\n", file, msg)) + fail = 1 +end + +local function version_keys(tbl) + local set, n = {}, 0 + for k in pairs(tbl) do + -- A version key, not `latest` / `deps` / `exports` / ... + if type(k) == "string" and k:match("^%d[%d%.]*$") then + set[k] = true + n = n + 1 + end + end + return set, n +end + +local function sorted(set) + local out = {} + for k in pairs(set) do table.insert(out, k) end + table.sort(out) + return out +end + +local function check(file) + package = nil + local chunk = loadfile(file, "t") + if not chunk then return end -- parse errors: other checks + if not pcall(chunk) then return end + local p = package + if type(p) ~= "table" or type(p.xpm) ~= "table" then return end + if p.platform_versions_diverge then return end + + -- Only platforms that carry versions at all. A section that is purely + -- `inherits` or `deps` is not an omission. + local plats, sets = {}, {} + for plat, pt in pairs(p.xpm) do + if type(pt) == "table" then + local s, n = version_keys(pt) + if n > 0 then + table.insert(plats, plat) + sets[plat] = s + end + end + end + if #plats < 2 then return end + table.sort(plats) + + local union = {} + for _, plat in ipairs(plats) do + for v in pairs(sets[plat]) do union[v] = true end + end + + for _, plat in ipairs(plats) do + local missing = {} + for _, v in ipairs(sorted(union)) do + if not sets[plat][v] then table.insert(missing, v) end + end + if #missing > 0 then + err(file, string.format( + "xpm.%s is missing %s -- present in another platform's section. " + .. "A version bump has to land in every platform block; editing " + .. "one leaves a file that still contains the version, so the " + .. "omission reads as 'not found' on the platforms that lack it. " + .. "If the platforms genuinely differ, set " + .. "`platform_versions_diverge = true` and say why.", + plat, table.concat(missing, ", "))) + end + end +end + +if #arg == 0 then + io.stderr:write("usage: check_platform_version_parity.lua [...]\n") + os.exit(2) +end +for _, file in ipairs(arg) do check(file) end +os.exit(fail)