Skip to content

Commit 38f6251

Browse files
authored
ci: a published version names one set of bytes (#255)
Every package here is published as an archive of a repository at a tag, so any change to that repository changes the artefact. A release needs the version in `mcpp.toml` bumped, and nothing checked that it was: a package's own CI validates the tree in front of it and has no opinion about which numbers are already taken. Measured 2026-08-25, following openkal 0.7.0 through the ecosystem: six of the eight repositories had a branch ready to merge whose version equalled the version on `main`, which is the version already in this index. Two had gained a whole interface implementation. All were green. What follows is quiet rather than loud. `git tag 0.5.3` finds the tag present and succeeds; the archive fetched from it is the old content; both mirror legs then agree with each other and with the source archive because all three are the same old bytes. The release verifies perfectly and publishes nothing, and the only surviving trace is a second `["0.5.3"]` entry here — which Lua accepts, keeps the last of, and says nothing about. The check is textual for that reason: loading the descriptor is exactly what cannot see it. Measured against the tree — 128 descriptors, no duplicates — and against a real repeat built by inserting openkal's 0.7.0 entry twice, which it names by file, line and platform section while `loadfile` on the same file succeeds.
1 parent 3b14cdf commit 38f6251

2 files changed

Lines changed: 85 additions & 0 deletions

File tree

.github/workflows/validate.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,13 @@ jobs:
252252
# visible by comparing sections against each other.
253253
- name: Lint platform version parity
254254
run: lua5.4 tests/check_platform_version_parity.lua pkgs/*/*.lua
255+
256+
# A published version names one set of bytes. Nothing upstream checks
257+
# that a package changing its content also bumps its number, and a
258+
# repeated key here is accepted by Lua and silently collapsed --- so a
259+
# release that republished an old tag would leave no other trace.
260+
- name: Lint duplicate versions
261+
run: lua5.4 tests/check_duplicate_versions.lua pkgs/*/*.lua
255262
# ── Single-source-of-truth grammar check ─────────────────────────
256263
# `mcpp xpkg parse` uses EXACTLY the resolver's parser, so what
257264
# passes here is what builds for users of the pinned MCPP_VERSION.

tests/check_duplicate_versions.lua

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
-- A version number, once published, names one set of bytes forever.
2+
--
3+
-- WHY THIS EXISTS
4+
--
5+
-- Every package in this index is published as an archive of a repository at a
6+
-- tag, so ANY change to that repository changes the artefact. A release
7+
-- therefore needs the version in `mcpp.toml` bumped, and nothing checks that
8+
-- it was: a package's own CI validates the tree in front of it and has no
9+
-- opinion about which numbers are already taken.
10+
--
11+
-- Measured 2026-08-25, following openkal 0.7.0 through the ecosystem: SIX of
12+
-- the eight repositories had a branch ready to merge whose version equalled
13+
-- the version on `main`, which is the version already in this index. Two of
14+
-- them had gained a whole interface implementation. Every one of those
15+
-- packages was green.
16+
--
17+
-- What happens next is quiet rather than loud. `git tag 0.5.3` finds the tag
18+
-- present and succeeds; the archive fetched from that tag is the OLD content;
19+
-- both mirror legs then agree with each other and with the source archive,
20+
-- because all three are the same old bytes. The release verifies perfectly
21+
-- and publishes nothing. The only surviving trace is a second `["0.5.3"]`
22+
-- entry here -- and Lua accepts a repeated key in a table constructor, keeps
23+
-- the last one, and reports nothing.
24+
--
25+
-- THE RULE
26+
--
27+
-- Within one platform section, a version key appears at most once.
28+
--
29+
-- The check is textual and deliberately so. Loading the descriptor is exactly
30+
-- what cannot see this: by the time the table exists, the duplicate has
31+
-- already collapsed into the survivor.
32+
--
33+
-- Usage: lua5.4 tests/check_duplicate_versions.lua <file.lua> [...]
34+
-- Exits non-zero, with ::error lines naming the file, the line, and the
35+
-- platform section the repeat is in.
36+
37+
local failed = false
38+
39+
-- A platform section opens with `<name> = {` at the indentation the
40+
-- descriptors use inside `xpm`, and a version key is `["<digits and dots>"]`.
41+
-- Anything else -- `url`, `sha256`, nested tables -- is passed over.
42+
local function check(path)
43+
local handle = io.open(path, "r")
44+
if not handle then
45+
io.stderr:write(("::error file=%s::cannot be read\n"):format(path))
46+
failed = true
47+
return
48+
end
49+
50+
local section, seen, line_no = nil, {}, 0
51+
for line in handle:lines() do
52+
line_no = line_no + 1
53+
54+
local platform = line:match("^%s%s%s%s%s%s%s%s([%w_]+)%s*=%s*{%s*$")
55+
if platform then
56+
section, seen = platform, {}
57+
end
58+
59+
local version = line:match('^%s*%["([%d][%d%.]*)"%]%s*=')
60+
if version and section then
61+
if seen[version] then
62+
io.stderr:write(("::error file=%s,line=%d::%s appears twice in the %s section (first at line %d). A published version names one set of bytes; bump it instead.\n")
63+
:format(path, line_no, version, section, seen[version]))
64+
failed = true
65+
else
66+
seen[version] = line_no
67+
end
68+
end
69+
end
70+
handle:close()
71+
end
72+
73+
if #arg == 0 then
74+
io.stderr:write("usage: check_duplicate_versions.lua <file.lua> [...]\n")
75+
os.exit(2)
76+
end
77+
for _, path in ipairs(arg) do check(path) end
78+
os.exit(failed and 1 or 0)

0 commit comments

Comments
 (0)