From ce81e4907051fe7dca86960fad40a851e7267422 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Thu, 6 Aug 2026 13:16:19 +0800 Subject: [PATCH 01/13] docs: the B line's core claim, proven on the real NVIDIA stack MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The gate (§2.7) proved the MECHANISM works — DT_RPATH transitivity, dlsym through the handle, GLX needing no process-global variable. This proves it works on the actual driver, and measures the boundary the gate could not see. Baseline, with glprobe, which renders and reads a pixel back rather than printing a version string: host env GL_RENDERER = NVIDIA GeForce RTX 4080/PCIe/SSE2 PIXEL=336699 in subos GL_RENDERER = llvmpipe (LLVM 20.1.7, 256 bits) PIXEL=336699 §2.6's defect, measured: the same host-linked binary drops from the GPU to software rendering inside the subos, silently. Both pixels are correct, so "does it render" cannot catch this — only the renderer name can. With a 27 KB interposer built by patchelf alone, and LD_LIBRARY_PATH carrying ONLY the host driver directory — lib/xlings-deps not on it at all: GL_RENDERER = NVIDIA GeForce RTX 4080/PCIe/SSE2 PIXEL=336699 RESULT=ok B2's acceptance criterion, satisfied. The boundary came out of the same experiment. Handing that interposer to a HOST binary fails as librt.so.1: undefined symbol: __pointer_chk_guard, version GLIBC_PRIVATE which is the 2026-08-05 crash verbatim: the interposer's RPATH names OUR glibc, and the consumer's libc is the host's. Not a defect — the domain of applicability, and consistent with §2.3, which says the process the vendor is dlopen'd into is ours by construction. But it is a precondition B1 has to put in the contract rather than leave implied: an object produced by host_link_interposer may only be loaded by a consumer whose INTERP points into our payload; host binaries must keep using the host's own vendor. Which is precisely the argument for B4: bake the vendor directory into the libglvnd WE build, and the two paths separate by construction instead of by an environment variable that every child inherits. Also corrects two wrong calls of mine about the install itself. It was never network-infeasible: a fresh isolated home defaults to the GLOBAL mirror, and `xlings config --mirror CN` installs all 22 packages in minutes. Before that I had declared it hung on twenty seconds of no growth in one directory, while it was between finishing its downloads and extracting them. --- .agents/docs/2026-08-06-deferred-work-plan.md | 55 +++++++++++++++++++ .agents/tools/verify-release-lifecycle.sh | 15 +++++ 2 files changed, 70 insertions(+) diff --git a/.agents/docs/2026-08-06-deferred-work-plan.md b/.agents/docs/2026-08-06-deferred-work-plan.md index 8d450f83..b75b4523 100644 --- a/.agents/docs/2026-08-06-deferred-work-plan.md +++ b/.agents/docs/2026-08-06-deferred-work-plan.md @@ -491,3 +491,58 @@ B2 的"切换后仍是 NVIDIA"证明不了是切换的功劳。 | #56 | 能按需重现 | ❌ **未达成**,四个假设被排除 | 两项达成、一项没有。没达成的那项**没有被当作达成**,这本身就是这条规矩的用处。 + +### 7.6 #55 B 线:核心命题已在真实栈上验证(2026-08-06) + +图形栈用 `xlings config --mirror CN` 装进隔离 home(22 个包)。**先前两次判它 +"网络不可行/卡住"都是错的** —— 第一次是隔离 home 默认 GLOBAL 镜像,第二次是我用 +"20 秒零字节"给一个正在下载与解压之间的安装下了死刑。用单目录短时增长判定长任务, +不是测量。 + +#### 基线(glprobe,渲染并读回像素) + +| 环境 | `GL_RENDERER` | `PIXEL` | +|---|---|---| +| 宿主 | NVIDIA GeForce RTX 4080/PCIe/SSE2 | 336699 | +| **进 subos(今天的 `LD_LIBRARY_PATH` 方案)** | **llvmpipe (LLVM 20.1.7)** | 336699 | + +**§2.6 的缺陷被实测到了**:同一个链接宿主 libEGL 的二进制,进 subos 后从 GPU 掉到 +软件渲染,没有任何提示。两边像素都对 —— 所以"能不能渲染"这个检查抓不住它,只有 +渲染器名字能。 + +#### interposer 对照 + +27 KB,patchelf 三步(`--set-soname` / `--add-needed <宿主 vendor 绝对路径>` / +`--set-rpath --force-rpath <从载荷推导的闭包>`),安装期不需要编译器。 + +| | `GL_RENDERER` | `LD_LIBRARY_PATH` | +|---|---|---| +| interposer + **我们的** loader | **NVIDIA GeForce RTX 4080/PCIe/SSE2**,`PIXEL=336699`,`RESULT=ok` | **只有 `/usr/lib/x86_64-linux-gnu`** | + +`lib/xlings-deps` 完全不参与。**B2 的验收判据当场满足。** + +#### 边界:interposer 只对跑在我们 loader 上的消费者安全 + +同一个 interposer 给**宿主二进制**(宿主 loader、宿主 libc)用: + +``` +librt.so.1: undefined symbol: __pointer_chk_guard, version GLIBC_PRIVATE +``` + +正是 8-05 那次崩溃的同一条错误。原因清楚:interposer 的 RPATH 指向**我们的** +glibc,而消费者的 libc 是宿主的 —— §2.3 说的"两半来自不同构建"。 + +**这不是缺陷,是适用域**,而且与 §2.3 的推断一致:vendor 被 dlopen 进的那个进程 +本来就该是我们的(INTERP 指向我们的 glibc)。但它给 B1 加了一条**必须写进契约**的 +前置条件: + +> `host_link_interposer` 产出的对象,只可由 INTERP 指向我们载荷的消费者加载。 +> 宿主二进制必须继续走宿主自己的 vendor。 + +这条正是 §2.6/B4 存在的理由:把 vendor 目录编进**我们自己构建的** libglvnd,宿主的 +libglvnd 就用宿主默认目录,两条路径天然分开。 + +#### 与门禁验证的关系 + +门禁(§2.7)证明的是**机制可行**(DT_RPATH 传递、dlsym 穿透、GLX 无需全局变量); +这一节证明的是**在真实 NVIDIA 栈上有效**,并测出了适用域。两者都不能替代对方。 diff --git a/.agents/tools/verify-release-lifecycle.sh b/.agents/tools/verify-release-lifecycle.sh index 11dcaf2d..2862a5e2 100755 --- a/.agents/tools/verify-release-lifecycle.sh +++ b/.agents/tools/verify-release-lifecycle.sh @@ -61,6 +61,21 @@ echo "version: $(x --version 2>&1 | head -1)" step "1. the client anchors to the home under test" x self init >/dev/null 2>&1 || true + +# The mirror, before anything downloads. +# +# A fresh home defaults to GLOBAL, and on a CN-routed machine that is the +# difference between a two-minute run and an hour of stalls -- the first +# attempt at this script sat at 140 MB for over an hour and I misread it as a +# hung process. The default here follows the DEVELOPER's own home rather than +# a hardcoded region: whatever route works for them works for the verification. +if [[ -z "${XLINGS_VERIFY_MIRROR:-}" ]] && [[ -f "$REAL/.xlings.json" ]]; then + XLINGS_VERIFY_MIRROR="$(sed -n 's/.*"mirror"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \ + "$REAL/.xlings.json" | head -1)" +fi +XLINGS_VERIFY_MIRROR="${XLINGS_VERIFY_MIRROR:-GLOBAL}" +x config --mirror "$XLINGS_VERIFY_MIRROR" >/dev/null 2>&1 || true +echo " mirror: $XLINGS_VERIFY_MIRROR" GOT="$(x self config 2>&1)" echo "$GOT" | grep -q "$HOME_DIR" \ || fail "the client reports a home other than the one under test. From 5b49275e8211df505762127b2d0ed59ddb92f35c Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Thu, 6 Aug 2026 14:07:11 +0800 Subject: [PATCH 02/13] 2026.8.6.3: the B line lands, and a loader shape that dropped build deps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pins libxpkg 0.0.52, which brings two things: - `elfpatch.host_link_interposer` — the mechanism the B line is built on. From a shipped empty ELF stub, patchelf produces an object with the vendor's SONAME, the HOST vendor as an absolute DT_NEEDED, and the payload closure as DT_RPATH. The vendor's dependencies then resolve out of our payloads along one load chain, instead of being broadcast to the whole process through LD_LIBRARY_PATH. - A `deps` table mixing a positional list with `build = {...}` used to drop the build deps silently and copy the positional entries into build_deps in their place. Declared, reported as installed, neither done. Companion: openxlings/libxpkg#36, openxlings/xim-pkgindex#532. The doc's §7.7 records how this went, because the interesting part is that every step produced a passing result first: - an empty payload that installed "successfully" — caught only by the hook's own assertion, and caused by a resource key nested one level too deep; - `glxinfo` printing "NVIDIA GeForce RTX 4080/PCIe/SSE2" inside the subos while every object came from /usr/lib — the host binary under the host loader, our payload contributing nothing. That output is identical whether the B line works or never happened; - `interposer: yes` with one of four glvnd entry points covered. Each vendor library is dlopened BY NAME and so is the root of its own load chain; DT_RPATH is transitive only down a chain. EGL rendered on the GPU while GLX pulled its whole closure from the host. Each was found by measuring the artifact rather than the log line, and each fix is now asserted by `xim-pkgindex/.agents/tools/graphics/verify-host-link.sh` — 12 checks on a real RTX 4080, driver 550.144.03, in an isolated home. Also corrects a claim I made earlier in this work: elfpatch does NOT silently fall back to the host's patchelf. `_find_tool` resolves payload → subos view → home bin → host, warns when it leaves the payload, and `tool_payload_dir` scans the whole store — so a home that has patchelf at all uses it, declared or not. The real gap is narrow: a home that has never installed patchelf. --- .agents/docs/2026-08-06-deferred-work-plan.md | 77 +++++++++++++++++++ mcpp.lock | 6 +- mcpp.toml | 4 +- 3 files changed, 82 insertions(+), 5 deletions(-) diff --git a/.agents/docs/2026-08-06-deferred-work-plan.md b/.agents/docs/2026-08-06-deferred-work-plan.md index b75b4523..47410203 100644 --- a/.agents/docs/2026-08-06-deferred-work-plan.md +++ b/.agents/docs/2026-08-06-deferred-work-plan.md @@ -546,3 +546,80 @@ libglvnd 就用宿主默认目录,两条路径天然分开。 门禁(§2.7)证明的是**机制可行**(DT_RPATH 传递、dlsym 穿透、GLX 无需全局变量); 这一节证明的是**在真实 NVIDIA 栈上有效**,并测出了适用域。两者都不能替代对方。 + +### 7.7 #55 B 线落地:三个"看起来成功"的东西被逐一测穿(2026-08-06) + +§7.6 证明了机制。落地时,每一步都先给出一个通过的结果,再被更严的测量推翻。 + +#### 一:载荷是空的,而断言救了场 + +`interposer-stub` 装完,payload 目录空无一物。install hook 自己的断言把它拦下并 +点名了包和路径。根因不是解包布局,是 **resource 声明位置错了** —— +`url_template` 被我嵌进了版本条目里,而它是版本表的**兄弟**;带 per-arch sha256 +的条目要用 `x86_64 = { url, sha256 }` 子表(`aria2-next` 是样板)。解析器对此只 +说了一句 `resource has neither url nor source`,然后什么也没下载。 + +**如果 install hook 没写那句断言,这个包会"安装成功"并留下一个空目录。** + +#### 二:`glxinfo` 报出 RTX 4080,而它证明的是宿主栈 + +subos 里跑 `glxinfo -B`: + +``` +OpenGL renderer string: NVIDIA GeForce RTX 4080/PCIe/SSE2 +``` + +看着是 B 线成了。实际上 `command -v glxinfo` = `/usr/bin/glxinfo`, +`patchelf --print-interpreter` = `/lib64/ld-linux-x86-64.so.2` —— **宿主二进制跑在 +宿主 loader 上**,我们的载荷一个字节都没参与。这行输出在 B 线成功与彻底没发生时 +**完全一致**。 + +改成从 `/proc/self/maps` 报出每个 GL 对象的**实际来源路径**后,真相立刻可读。 +两个探针现在都这么做,harness 断言我们的 interposer 在其中。 + +#### 三:`interposer: yes`,而四个入口点只覆盖了一个 + +glvnd **按名字 dlopen** 每个 vendor 库,所以 `libEGL_nvidia.so.0`、 +`libGLX_nvidia.so.0`、`libGLESv1_CM_nvidia.so.1`、`libGLESv2_nvidia.so.2` +各是一条独立载入链的**根**。DT_RPATH 只沿链**向下**传递,永远不会横跨到另一个根。 + +只 interpose 了 libEGL 时:EGL 在 4080 上渲染,GLX 的整个闭包仍来自 `/usr/lib`。 +而安装日志说的是 `interposer: yes`。 + +现在四个入口点全部 interpose,日志报的是**分数** `4/4`。`libGLX_nvidia.so.0` +同时是 Vulkan ICD,所以这一个根承载两套 API。 + +#### 四:`deps.build` 声明了,patchelf 没装(libxpkg loader 缺陷) + +`deps` 表只要有数组部分,loader 就走 legacy 分支:**`build` 子表被丢弃,数组项 +被复制进 `build_deps` 顶替**。声明照写、安装照样报成功、两件事都没做。症状出现在 +两层之外 —— elfpatch 警告 "patchelf 解析到 host"。 + +libxpkg 0.0.52 修了混合形状,但**索引不能依赖这个修复**:它要服务所有版本的 +客户端,而 recipe 里没有办法探测 loader 的形状能力(不像 Lua 函数可以 `type()` +探测)。所以 recipe 改用纯 split 形式,并加了一条静态测试禁止混合形状 —— +括号配对判断,不是正则(`deps` 表里几乎总有嵌套表和注释)。 + +#### 顺带纠正一处我自己的错话 + +我曾说"约 30 个用 elfpatch 的 recipe 在静默回落到宿主 patchelf"。两处都错: +`_find_tool` 的顺序是 **payload → subos view → home bin → host**,回落时**会警告** +(那句警告正是 A3 加的),且 `tool_payload_dir` 会**扫整个 store** —— 只要 home 里 +装过 patchelf 就走载荷,与是否声明无关。真实缺口只剩一个窄口子:**从未装过 +patchelf 的 home**。声明 build dep 堵的是这个。 + +#### 最终验证 + +`.agents/tools/graphics/verify-host-link.sh`,真实 RTX 4080(驱动 550.144.03), +隔离 home,**12/12**:四个入口点形状正确、EGL 渲染出像素、GLX 渲染、两者都经过 +**我们的** interposer、宿主真 vendor 由绝对 DT_NEEDED 拉入、`LD_LIBRARY_PATH` 为空、 +宿主驱动文件未被改动。 + +#### 这次落地新增的一条契约前置 + +vendor 的 dlopen 由**调用方**的搜索路径服务,而 `libGLX.so.0` 自己的 RPATH 是 +`$ORIGIN` —— 它看不到 vendor 包。真正让它解析成功的是:**DT_RPATH 会沿载入链向上 +一直搜到可执行文件**。DT_RUNPATH 不会 —— 同一个探针用 `--enable-new-dtags` 构建, +X 连接正常、GLX 扩展正常,却一个 vendor 都找不到。 + +> 消费者必须携带 **DT_RPATH**(`--force-rpath`),不能是 DT_RUNPATH。 diff --git a/mcpp.lock b/mcpp.lock index ace9fc27..81f6384b 100644 --- a/mcpp.lock +++ b/mcpp.lock @@ -33,7 +33,7 @@ hash = "fnv1a:3465dd0bd5d7aa20" [package."mcpplibs.xpkg"] namespace = "mcpplibs" -version = "0.0.51" -source = "index+mcpplibs@0.0.51" -hash = "fnv1a:71031031ffb66224" +version = "0.0.52" +source = "index+mcpplibs@0.0.52" +hash = "fnv1a:542243625c3f1e62" diff --git a/mcpp.toml b/mcpp.toml index ca14fb48..4c40b8c0 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,6 +1,6 @@ [package] name = "xlings" -version = "2026.8.6.2" +version = "2026.8.6.3" description = "Universal package management infrastructure tool with SubOS isolation" license = "Apache-2.0" repo = "https://github.com/openxlings/xlings" @@ -39,7 +39,7 @@ libarchive = "3.8.7" [dependencies.mcpplibs] cmdline = "0.0.2" -xpkg = "0.0.51" +xpkg = "0.0.52" tinyhttps = "0.2.9" capi.lua = "0.0.3" From c953c5c87960250783c0d162251ca028a2bbd664 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Thu, 6 Aug 2026 14:51:34 +0800 Subject: [PATCH 03/13] pin libxpkg 0.0.53: install_dir names the cause (#487) 0.0.53 adds the diagnostic half of #487. `cannot get install dir` named an internal state and covered two causes pointing in opposite directions: - not a dependency of this package ON THIS PLATFORM - declared here, but the payload never landed It now says which, and names the package, the platform, and what the deps here actually are. The issue's own hypothesis -- that dependency resolution does not filter by platform -- does not hold: resolver.cppm reads `pkg->xpm.runtime_deps.find(platform)`, and ollama declares the CUDA sentinel only under `xpm.linux`. The cause was ollama's install hook branching on `is_host("windows")` when the real distinction was linux, so macOS took the linux path. Fixed in openxlings/xim-pkgindex#532; the message is fixed here because it is what sent the reader to paths. --- mcpp.lock | 6 +++--- mcpp.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mcpp.lock b/mcpp.lock index 81f6384b..bcd803a5 100644 --- a/mcpp.lock +++ b/mcpp.lock @@ -33,7 +33,7 @@ hash = "fnv1a:3465dd0bd5d7aa20" [package."mcpplibs.xpkg"] namespace = "mcpplibs" -version = "0.0.52" -source = "index+mcpplibs@0.0.52" -hash = "fnv1a:542243625c3f1e62" +version = "0.0.53" +source = "index+mcpplibs@0.0.53" +hash = "fnv1a:aa76a9db91b6fca7" diff --git a/mcpp.toml b/mcpp.toml index 4c40b8c0..06c75708 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -39,7 +39,7 @@ libarchive = "3.8.7" [dependencies.mcpplibs] cmdline = "0.0.2" -xpkg = "0.0.52" +xpkg = "0.0.53" tinyhttps = "0.2.9" capi.lua = "0.0.3" From a479e2e6895c24484fc86d8f38f47e044b78dfa4 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Thu, 6 Aug 2026 15:20:49 +0800 Subject: [PATCH 04/13] ci: bump mcpp to 2026.8.6.1 so it can see a freshly published index CI pinned mcpp 2026.8.3.3 while the index's latest is 2026.8.6.1, and that pin is what made `mcpplibs.xpkg@0.0.53` read as "not found" long after it was published. The index side was verified correct at every layer before touching this: the mcpp-index commit has the 0.0.53 entry, the published artifact `mcpp-index-84fa166.tar.gz` contains it, the rolling pointer `mcpp-index-pointers.json` names 84fa166, and `releases/latest` resolves to that tag. A local mcpp 2026.8.5.4 has 0.0.53 in its live index copy. So it was not publish lag and not a stale pointer. The workflows' `xlings update` step refreshes the XIM index and its three sub-indexes -- visible in the log -- and never touches the mcpplibs one, which is fetched by mcpp itself. Bumping mcpp means bumping XIM_PKGINDEX_REF with it, in all six workflows: the two are pinned as a known-good pair, and moving one alone is how a run ends up resolving a new client against an old index. The new ref is xim-pkgindex bf969a6 -- main with #532 (the B line) merged, so CI resolves against the index that carries interposer-stub. --- .github/workflows/release.yml | 2 +- .github/workflows/xlings-ci-linux-e2e.yml | 2 +- .github/workflows/xlings-ci-linux-root.yml | 2 +- .github/workflows/xlings-ci-linux.yml | 2 +- .github/workflows/xlings-ci-macos.yml | 2 +- .github/workflows/xlings-ci-windows.yml | 2 +- .xlings.json | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3d158502..ec845c20 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ env: # XIM_PKGINDEX_REF so release uses a known-good xlings + package index pair. BOOTSTRAP_XLINGS_VERSION: v0.4.69 # Must contain the mcpp version pinned in .xlings.json (2026.8.3.3). - XIM_PKGINDEX_REF: 20e53c6a4967346282af21c8fd2af91e6af75cc7 + XIM_PKGINDEX_REF: bf969a651caae4539eea8e92e3d14cb60e0eaf53 jobs: build-linux: diff --git a/.github/workflows/xlings-ci-linux-e2e.yml b/.github/workflows/xlings-ci-linux-e2e.yml index 0591d334..94af65e3 100644 --- a/.github/workflows/xlings-ci-linux-e2e.yml +++ b/.github/workflows/xlings-ci-linux-e2e.yml @@ -23,7 +23,7 @@ env: GIT_TERMINAL_PROMPT: 0 XLINGS_RELEASE_MIRROR: GLOBAL BOOTSTRAP_XLINGS_VERSION: v0.4.69 - XIM_PKGINDEX_REF: 20e53c6a4967346282af21c8fd2af91e6af75cc7 + XIM_PKGINDEX_REF: bf969a651caae4539eea8e92e3d14cb60e0eaf53 jobs: e2e: diff --git a/.github/workflows/xlings-ci-linux-root.yml b/.github/workflows/xlings-ci-linux-root.yml index eeb12339..730af92e 100644 --- a/.github/workflows/xlings-ci-linux-root.yml +++ b/.github/workflows/xlings-ci-linux-root.yml @@ -21,7 +21,7 @@ env: XLINGS_RELEASE_MIRROR: GLOBAL # Keep in sync with xlings-ci-linux.yml. BOOTSTRAP_XLINGS_VERSION: v0.4.69 - XIM_PKGINDEX_REF: 20e53c6a4967346282af21c8fd2af91e6af75cc7 + XIM_PKGINDEX_REF: bf969a651caae4539eea8e92e3d14cb60e0eaf53 jobs: # ── Build the latest xlings from source via mcpp ────────────────── diff --git a/.github/workflows/xlings-ci-linux.yml b/.github/workflows/xlings-ci-linux.yml index af646d77..d1ec716b 100644 --- a/.github/workflows/xlings-ci-linux.yml +++ b/.github/workflows/xlings-ci-linux.yml @@ -18,7 +18,7 @@ env: # XIM_PKGINDEX_REF so CI uses a known-good xlings + package index pair. BOOTSTRAP_XLINGS_VERSION: v0.4.69 # Must contain the mcpp version pinned in .xlings.json (2026.8.3.3). - XIM_PKGINDEX_REF: 20e53c6a4967346282af21c8fd2af91e6af75cc7 + XIM_PKGINDEX_REF: bf969a651caae4539eea8e92e3d14cb60e0eaf53 jobs: build-and-test: diff --git a/.github/workflows/xlings-ci-macos.yml b/.github/workflows/xlings-ci-macos.yml index 3ce61137..80578d45 100644 --- a/.github/workflows/xlings-ci-macos.yml +++ b/.github/workflows/xlings-ci-macos.yml @@ -19,7 +19,7 @@ env: # XIM_PKGINDEX_REF so CI uses a known-good xlings + package index pair. BOOTSTRAP_XLINGS_VERSION: v0.4.69 # Must contain the mcpp version pinned in .xlings.json (2026.8.3.3). - XIM_PKGINDEX_REF: 20e53c6a4967346282af21c8fd2af91e6af75cc7 + XIM_PKGINDEX_REF: bf969a651caae4539eea8e92e3d14cb60e0eaf53 jobs: build-and-test: diff --git a/.github/workflows/xlings-ci-windows.yml b/.github/workflows/xlings-ci-windows.yml index 88a0a632..a0b83602 100644 --- a/.github/workflows/xlings-ci-windows.yml +++ b/.github/workflows/xlings-ci-windows.yml @@ -18,7 +18,7 @@ env: # XIM_PKGINDEX_REF so CI uses a known-good xlings + package index pair. BOOTSTRAP_XLINGS_VERSION: v0.4.69 # Must contain the mcpp version pinned in .xlings.json (2026.8.3.3). - XIM_PKGINDEX_REF: 20e53c6a4967346282af21c8fd2af91e6af75cc7 + XIM_PKGINDEX_REF: bf969a651caae4539eea8e92e3d14cb60e0eaf53 jobs: build-and-test: diff --git a/.xlings.json b/.xlings.json index 48047e18..23722ebe 100644 --- a/.xlings.json +++ b/.xlings.json @@ -3,6 +3,6 @@ "projectScope": false, "mirror": "GLOBAL", "workspace": { - "mcpp": "2026.8.3.3" + "mcpp": "2026.8.6.1" } } From bd4e614a69de1ad85e3c12d7b64341b1ce840242 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Thu, 6 Aug 2026 15:35:58 +0800 Subject: [PATCH 05/13] 2026.8.6.3: the client's own version constant The version-consistency contract caught it: mcpp.toml said 2026.8.6.3 and src/core/config.cppm still said 2026.8.6.2. A release whose binary reports the previous version is exactly what that contract exists to stop. --- src/core/config.cppm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/config.cppm b/src/core/config.cppm index 875f6ae5..28c328de 100644 --- a/src/core/config.cppm +++ b/src/core/config.cppm @@ -13,7 +13,7 @@ import xlings.core.xvm.db; namespace xlings { export struct Info { - static constexpr std::string_view VERSION = "2026.8.6.2"; + static constexpr std::string_view VERSION = "2026.8.6.3"; static constexpr std::string_view REPO = "https://github.com/openxlings/xlings"; }; From 4d73d8d38d39213f06018505e4c604cf0df9b60d Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Thu, 6 Aug 2026 15:43:45 +0800 Subject: [PATCH 06/13] ci: installing mcpp is not switching to it, so assert the version `xlings install -y` lays the payload down; the shim keeps resolving whatever version was already active. The client says so -- "installed, but 'x' still resolves to ..." -- but with ~/.mcpp restored from an Actions cache that one line is the only sign that the mcpp about to run is the old build. Measured 2026-08-06: `.xlings.json` pinned mcpp 2026.8.6.1, the run used the cached 2026.8.3.3, and its index snapshot predated the dependency this PR needs. It surfaced as `mcpplibs.xpkg@0.0.53 not found` against an index that demonstrably had it -- the mcpp-index commit, the published artifact mcpp-index-84fa166.tar.gz, the rolling pointer, and releases/latest all carry the entry, and a local mcpp 2026.8.5.4 refetches it from a wiped registry on both mirrors. Every layer I could check was correct, which is what made the version the last place to look. So: switch explicitly, then ASSERT the version rather than print it. A version line in a log is only ever read after something has already gone wrong; a failed assert names the cause at the point it happens. Seven workflows, including release.yml. --- .github/workflows/release.yml | 46 +++++++++++++++++++++- .github/workflows/xlings-ci-aarch64.yml | 8 +++- .github/workflows/xlings-ci-linux-e2e.yml | 23 ++++++++++- .github/workflows/xlings-ci-linux-root.yml | 23 ++++++++++- .github/workflows/xlings-ci-linux.yml | 23 ++++++++++- .github/workflows/xlings-ci-macos.yml | 23 ++++++++++- .github/workflows/xlings-ci-windows.yml | 21 +++++++++- 7 files changed, 159 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ec845c20..198c3a6a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -167,7 +167,28 @@ jobs: # dependency rather than as an index nobody refreshed. xlings update || true xlings install -y - mcpp --version + + # Installing is not switching, and this cache makes the difference + # visible. `xlings install` lays the payload down; the shim keeps + # resolving whatever version was already active -- the client says so + # ("installed, but 'x' still resolves to ..."), and with ~/.mcpp + # restored from a cache that line is the ONLY sign that the mcpp + # about to run is the old one. Measured 2026-08-06: .xlings.json + # pinned 2026.8.6.1 while the run used the cached build, whose index + # snapshot predated the dependency this PR needs -- surfacing as + # `mcpplibs.xpkg@0.0.53 not found` against an index that demonstrably + # had it. + xlings use mcpp "2026.8.6.1" || true + + # Assert, do not print. A version line in a log is only read after + # something has already gone wrong. + got="$(mcpp --version 2>&1 | head -1)" + case "$got" in + *"2026.8.6.1"*) echo "mcpp $got (matches .xlings.json)" ;; + *) echo "FAIL: .xlings.json pins mcpp 2026.8.6.1 but the mcpp on PATH is '$got'." >&2 + echo " Installing does not switch; the shim still resolves the cached build." >&2 + exit 1 ;; + esac mcpp self version - name: Reinitialize mcpp registry (GLOBAL) @@ -395,7 +416,28 @@ jobs: # dependency rather than as an index nobody refreshed. xlings update || true xlings install -y - mcpp --version + + # Installing is not switching, and this cache makes the difference + # visible. `xlings install` lays the payload down; the shim keeps + # resolving whatever version was already active -- the client says so + # ("installed, but 'x' still resolves to ..."), and with ~/.mcpp + # restored from a cache that line is the ONLY sign that the mcpp + # about to run is the old one. Measured 2026-08-06: .xlings.json + # pinned 2026.8.6.1 while the run used the cached build, whose index + # snapshot predated the dependency this PR needs -- surfacing as + # `mcpplibs.xpkg@0.0.53 not found` against an index that demonstrably + # had it. + xlings use mcpp "2026.8.6.1" || true + + # Assert, do not print. A version line in a log is only read after + # something has already gone wrong. + got="$(mcpp --version 2>&1 | head -1)" + case "$got" in + *"2026.8.6.1"*) echo "mcpp $got (matches .xlings.json)" ;; + *) echo "FAIL: .xlings.json pins mcpp 2026.8.6.1 but the mcpp on PATH is '$got'." >&2 + echo " Installing does not switch; the shim still resolves the cached build." >&2 + exit 1 ;; + esac mcpp self version - name: Reinitialize mcpp registry (GLOBAL) diff --git a/.github/workflows/xlings-ci-aarch64.yml b/.github/workflows/xlings-ci-aarch64.yml index 55bb8e5b..288bf4ae 100644 --- a/.github/workflows/xlings-ci-aarch64.yml +++ b/.github/workflows/xlings-ci-aarch64.yml @@ -92,7 +92,13 @@ jobs: xlings config --mirror GLOBAL xlings update -y 2>/dev/null || xlings update 2>/dev/null || true xlings install mcpp -y - mcpp --version + # Installing is not switching -- see the note in xlings-ci-linux.yml. + xlings use mcpp "2026.8.6.1" || true + got="$(mcpp --version 2>&1 | head -1)" + case "$got" in + *"2026.8.6.1"*) echo "mcpp $got (matches .xlings.json)" ;; + *) echo "FAIL: .xlings.json pins mcpp 2026.8.6.1 but PATH has '$got'" >&2; exit 1 ;; + esac - name: Build cross-capable mcpp from source (mcpp main) run: | diff --git a/.github/workflows/xlings-ci-linux-e2e.yml b/.github/workflows/xlings-ci-linux-e2e.yml index 94af65e3..107d3889 100644 --- a/.github/workflows/xlings-ci-linux-e2e.yml +++ b/.github/workflows/xlings-ci-linux-e2e.yml @@ -175,7 +175,28 @@ jobs: # dependency rather than as an index nobody refreshed. xlings update || true xlings install -y - mcpp --version + + # Installing is not switching, and this cache makes the difference + # visible. `xlings install` lays the payload down; the shim keeps + # resolving whatever version was already active -- the client says so + # ("installed, but 'x' still resolves to ..."), and with ~/.mcpp + # restored from a cache that line is the ONLY sign that the mcpp + # about to run is the old one. Measured 2026-08-06: .xlings.json + # pinned 2026.8.6.1 while the run used the cached build, whose index + # snapshot predated the dependency this PR needs -- surfacing as + # `mcpplibs.xpkg@0.0.53 not found` against an index that demonstrably + # had it. + xlings use mcpp "2026.8.6.1" || true + + # Assert, do not print. A version line in a log is only read after + # something has already gone wrong. + got="$(mcpp --version 2>&1 | head -1)" + case "$got" in + *"2026.8.6.1"*) echo "mcpp $got (matches .xlings.json)" ;; + *) echo "FAIL: .xlings.json pins mcpp 2026.8.6.1 but the mcpp on PATH is '$got'." >&2 + echo " Installing does not switch; the shim still resolves the cached build." >&2 + exit 1 ;; + esac mcpp self version - name: Reinitialize mcpp registry (GLOBAL) diff --git a/.github/workflows/xlings-ci-linux-root.yml b/.github/workflows/xlings-ci-linux-root.yml index 730af92e..08edb75e 100644 --- a/.github/workflows/xlings-ci-linux-root.yml +++ b/.github/workflows/xlings-ci-linux-root.yml @@ -172,7 +172,28 @@ jobs: # dependency rather than as an index nobody refreshed. xlings update || true xlings install -y - mcpp --version + + # Installing is not switching, and this cache makes the difference + # visible. `xlings install` lays the payload down; the shim keeps + # resolving whatever version was already active -- the client says so + # ("installed, but 'x' still resolves to ..."), and with ~/.mcpp + # restored from a cache that line is the ONLY sign that the mcpp + # about to run is the old one. Measured 2026-08-06: .xlings.json + # pinned 2026.8.6.1 while the run used the cached build, whose index + # snapshot predated the dependency this PR needs -- surfacing as + # `mcpplibs.xpkg@0.0.53 not found` against an index that demonstrably + # had it. + xlings use mcpp "2026.8.6.1" || true + + # Assert, do not print. A version line in a log is only read after + # something has already gone wrong. + got="$(mcpp --version 2>&1 | head -1)" + case "$got" in + *"2026.8.6.1"*) echo "mcpp $got (matches .xlings.json)" ;; + *) echo "FAIL: .xlings.json pins mcpp 2026.8.6.1 but the mcpp on PATH is '$got'." >&2 + echo " Installing does not switch; the shim still resolves the cached build." >&2 + exit 1 ;; + esac - name: Reinitialize mcpp registry (GLOBAL) run: | diff --git a/.github/workflows/xlings-ci-linux.yml b/.github/workflows/xlings-ci-linux.yml index d1ec716b..9f9da1ea 100644 --- a/.github/workflows/xlings-ci-linux.yml +++ b/.github/workflows/xlings-ci-linux.yml @@ -177,7 +177,28 @@ jobs: # dependency rather than as an index nobody refreshed. xlings update || true xlings install -y - mcpp --version + + # Installing is not switching, and this cache makes the difference + # visible. `xlings install` lays the payload down; the shim keeps + # resolving whatever version was already active -- the client says so + # ("installed, but 'x' still resolves to ..."), and with ~/.mcpp + # restored from a cache that line is the ONLY sign that the mcpp + # about to run is the old one. Measured 2026-08-06: .xlings.json + # pinned 2026.8.6.1 while the run used the cached build, whose index + # snapshot predated the dependency this PR needs -- surfacing as + # `mcpplibs.xpkg@0.0.53 not found` against an index that demonstrably + # had it. + xlings use mcpp "2026.8.6.1" || true + + # Assert, do not print. A version line in a log is only read after + # something has already gone wrong. + got="$(mcpp --version 2>&1 | head -1)" + case "$got" in + *"2026.8.6.1"*) echo "mcpp $got (matches .xlings.json)" ;; + *) echo "FAIL: .xlings.json pins mcpp 2026.8.6.1 but the mcpp on PATH is '$got'." >&2 + echo " Installing does not switch; the shim still resolves the cached build." >&2 + exit 1 ;; + esac mcpp self version - name: Reinitialize mcpp registry (GLOBAL) diff --git a/.github/workflows/xlings-ci-macos.yml b/.github/workflows/xlings-ci-macos.yml index 80578d45..a7caf578 100644 --- a/.github/workflows/xlings-ci-macos.yml +++ b/.github/workflows/xlings-ci-macos.yml @@ -167,7 +167,28 @@ jobs: # dependency rather than as an index nobody refreshed. xlings update || true xlings install -y - mcpp --version + + # Installing is not switching, and this cache makes the difference + # visible. `xlings install` lays the payload down; the shim keeps + # resolving whatever version was already active -- the client says so + # ("installed, but 'x' still resolves to ..."), and with ~/.mcpp + # restored from a cache that line is the ONLY sign that the mcpp + # about to run is the old one. Measured 2026-08-06: .xlings.json + # pinned 2026.8.6.1 while the run used the cached build, whose index + # snapshot predated the dependency this PR needs -- surfacing as + # `mcpplibs.xpkg@0.0.53 not found` against an index that demonstrably + # had it. + xlings use mcpp "2026.8.6.1" || true + + # Assert, do not print. A version line in a log is only read after + # something has already gone wrong. + got="$(mcpp --version 2>&1 | head -1)" + case "$got" in + *"2026.8.6.1"*) echo "mcpp $got (matches .xlings.json)" ;; + *) echo "FAIL: .xlings.json pins mcpp 2026.8.6.1 but the mcpp on PATH is '$got'." >&2 + echo " Installing does not switch; the shim still resolves the cached build." >&2 + exit 1 ;; + esac mcpp self version - name: Reinitialize mcpp registry (GLOBAL) diff --git a/.github/workflows/xlings-ci-windows.yml b/.github/workflows/xlings-ci-windows.yml index a0b83602..a363879c 100644 --- a/.github/workflows/xlings-ci-windows.yml +++ b/.github/workflows/xlings-ci-windows.yml @@ -172,7 +172,26 @@ jobs: xlings update $global:LASTEXITCODE = 0 xlings install -y - mcpp --version + + # Installing is not switching. `xlings install` lays the payload + # down; the shim keeps resolving whatever version was already + # active, and with ~/.mcpp restored from a cache that is the old + # build -- whose index snapshot can predate the dependency this PR + # needs. Measured 2026-08-06 as `mcpplibs.xpkg@0.0.53 not found` + # against an index that demonstrably had it. + xlings use mcpp "2026.8.6.1" + $global:LASTEXITCODE = 0 + + # Assert, do not print: a version line in a log is only read after + # something has already gone wrong. + $got = (mcpp --version 2>&1 | Select-Object -First 1) -join "" + $global:LASTEXITCODE = 0 + if ($got -notmatch [regex]::Escape("2026.8.6.1")) { + Write-Host "FAIL: .xlings.json pins mcpp 2026.8.6.1 but the mcpp on PATH is '$got'." + Write-Host " Installing does not switch; the shim still resolves the cached build." + exit 1 + } + Write-Host "mcpp $got (matches .xlings.json)" mcpp self version - name: Reinitialize mcpp registry (GLOBAL) From 9ed85ef2c2d4dc0b021c9fc42e24ff0753237234 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Thu, 6 Aug 2026 16:23:26 +0800 Subject: [PATCH 07/13] ci: on a build failure, print the index the runner actually holds `mcpplibs.xpkg@ not found` has now been diagnosed five times from the outside. Each check was correct: index commit has the entry published artifact mcpp-index-84fa166.tar.gz has it rolling pointer names 84fa166 releases/latest resolves to that tag publish lag still failing 30+ minutes later mcpp version pin bumped 2026.8.3.3 -> 2026.8.6.1, assert passes install-vs-use the assert proves the right mcpp runs mcpp.lock hash regenerates byte-identical from the published index Every one of those is inference about a file only the runner can see. So the failure path now prints it: the version keys in the mcpplibs index under both registry roots, the index snapshots present, and `mcpp self version`. Guessing a sixth time would cost another CI round either way; this way the round produces an answer instead of another elimination. --- .github/workflows/xlings-ci-linux.yml | 24 ++++++++++++++++++++++++ .github/workflows/xlings-ci-macos.yml | 24 ++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/.github/workflows/xlings-ci-linux.yml b/.github/workflows/xlings-ci-linux.yml index 9f9da1ea..d79863cb 100644 --- a/.github/workflows/xlings-ci-linux.yml +++ b/.github/workflows/xlings-ci-linux.yml @@ -245,6 +245,30 @@ jobs: mcpp test || { status=$? echo "::group::mcpp build diagnostics" + + # What index does this runner actually hold? + # + # `mcpplibs.xpkg@ not found` has been diagnosed five times + # from the outside -- index content, artifact, rolling pointer, + # releases/latest, publish lag, the mcpp version pin, install-vs- + # use, and the lockfile hash were each checked and each was + # correct. All of that was inference about a file only the runner + # can see. Print it. + for idx in "$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" \ + "$GITHUB_WORKSPACE/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua"; do + if [[ -f "$idx" ]]; then + echo "--- $idx" + grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 + else + echo "--- $idx : ABSENT" + fi + done + for snap in "$HOME/.mcpp/registry/index-snapshots" \ + "$GITHUB_WORKSPACE/.mcpp/registry/index-snapshots"; do + [[ -d "$snap" ]] && { echo "--- snapshots in $snap"; ls -1 "$snap"/* 2>/dev/null | head -8; } + done + mcpp self version 2>&1 | head -3 || true + ninja_bin="$(command -v ninja || true)" if [[ -z "$ninja_bin" ]]; then ninja_bin="$(find "$HOME/.mcpp" "$XLINGS_HOME" -name ninja -type f -perm -111 2>/dev/null | head -1 || true)" diff --git a/.github/workflows/xlings-ci-macos.yml b/.github/workflows/xlings-ci-macos.yml index a7caf578..26fcfe33 100644 --- a/.github/workflows/xlings-ci-macos.yml +++ b/.github/workflows/xlings-ci-macos.yml @@ -227,6 +227,30 @@ jobs: mcpp test || { status=$? echo "::group::mcpp build diagnostics" + + # What index does this runner actually hold? + # + # `mcpplibs.xpkg@ not found` has been diagnosed five times + # from the outside -- index content, artifact, rolling pointer, + # releases/latest, publish lag, the mcpp version pin, install-vs- + # use, and the lockfile hash were each checked and each was + # correct. All of that was inference about a file only the runner + # can see. Print it. + for idx in "$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" \ + "$GITHUB_WORKSPACE/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua"; do + if [[ -f "$idx" ]]; then + echo "--- $idx" + grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 + else + echo "--- $idx : ABSENT" + fi + done + for snap in "$HOME/.mcpp/registry/index-snapshots" \ + "$GITHUB_WORKSPACE/.mcpp/registry/index-snapshots"; do + [[ -d "$snap" ]] && { echo "--- snapshots in $snap"; ls -1 "$snap"/* 2>/dev/null | head -8; } + done + mcpp self version 2>&1 | head -3 || true + ninja_bin="$(command -v ninja || true)" if [[ -z "$ninja_bin" ]]; then ninja_bin="$(find "$HOME/.mcpp" "$XLINGS_HOME" -name ninja -type f -perm -111 2>/dev/null | head -1 || true)" From 93ebac186382638e68c1a2756d8dab2b1c20a167 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Thu, 6 Aug 2026 16:25:27 +0800 Subject: [PATCH 08/13] ci: the diagnostic hung off a command that never ran The index dump added one commit ago sat inside `mcpp test || { ... }`, with `mcpp build` running unguarded above it. The shell is `bash -e`, so a failing `mcpp build` aborted the step before the block was reached. The diagnostic existed, never ran, and the failure output was identical to a run where it had -- an entire CI round spent adding output that could not print. Now it is a separate step with `if: failure()`, so it covers any failing step in the job rather than one command's non-zero exit, and it also reports both registry roots, the snapshots present, and the mcpp/xlings versions. --- .github/workflows/xlings-ci-linux.yml | 61 +++++++++++++++++---------- .github/workflows/xlings-ci-macos.yml | 61 +++++++++++++++++---------- 2 files changed, 76 insertions(+), 46 deletions(-) diff --git a/.github/workflows/xlings-ci-linux.yml b/.github/workflows/xlings-ci-linux.yml index d79863cb..e07e0b39 100644 --- a/.github/workflows/xlings-ci-linux.yml +++ b/.github/workflows/xlings-ci-linux.yml @@ -246,29 +246,6 @@ jobs: status=$? echo "::group::mcpp build diagnostics" - # What index does this runner actually hold? - # - # `mcpplibs.xpkg@ not found` has been diagnosed five times - # from the outside -- index content, artifact, rolling pointer, - # releases/latest, publish lag, the mcpp version pin, install-vs- - # use, and the lockfile hash were each checked and each was - # correct. All of that was inference about a file only the runner - # can see. Print it. - for idx in "$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" \ - "$GITHUB_WORKSPACE/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua"; do - if [[ -f "$idx" ]]; then - echo "--- $idx" - grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 - else - echo "--- $idx : ABSENT" - fi - done - for snap in "$HOME/.mcpp/registry/index-snapshots" \ - "$GITHUB_WORKSPACE/.mcpp/registry/index-snapshots"; do - [[ -d "$snap" ]] && { echo "--- snapshots in $snap"; ls -1 "$snap"/* 2>/dev/null | head -8; } - done - mcpp self version 2>&1 | head -3 || true - ninja_bin="$(command -v ninja || true)" if [[ -z "$ninja_bin" ]]; then ninja_bin="$(find "$HOME/.mcpp" "$XLINGS_HOME" -name ninja -type f -perm -111 2>/dev/null | head -1 || true)" @@ -286,6 +263,44 @@ jobs: exit "$status" } + # A separate step, `if: failure()`, and not an `|| { ... }` on one + # command. + # + # The diagnostics used to hang off `mcpp test || { ... }` with + # `mcpp build` running unguarded above it. The shell is `bash -e`, so a + # failing `mcpp build` aborted the step before the block was ever + # reached: the diagnostic existed, never ran, and the failure looked + # exactly the same as if it had. Measured 2026-08-06 -- a whole CI round + # spent adding index-content output that could not print. + - name: Diagnose a failed mcpp build + if: failure() + run: | + # What index does this runner actually hold? `mcpplibs.xpkg@ + # not found` was diagnosed repeatedly from the outside -- index + # commit, published artifact, rolling pointer, releases/latest, + # publish lag, the mcpp version pin, install-vs-use, and the + # lockfile hash were each checked and each was correct. All of that + # is inference about a file only the runner can see. + for idx in "$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" \ + "$GITHUB_WORKSPACE/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua"; do + if [ -f "$idx" ]; then + echo "--- $idx" + grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -8 + else + echo "--- $idx : ABSENT" + fi + done + for root in "$HOME/.mcpp/registry" "$GITHUB_WORKSPACE/.mcpp/registry"; do + [ -d "$root" ] || continue + echo "--- $root" + ls -1 "$root" 2>/dev/null | head -8 + ls -1 "$root/data" 2>/dev/null | head -8 + ls -1 "$root/index-snapshots" 2>/dev/null | head -8 + done + mcpp --version 2>&1 | head -2 || true + mcpp self version 2>&1 | head -3 || true + xlings --version 2>&1 | head -2 || true + - name: Multiplatform contract scripts run: | bash tests/scripts/test_quick_install.sh diff --git a/.github/workflows/xlings-ci-macos.yml b/.github/workflows/xlings-ci-macos.yml index 26fcfe33..1796fbcd 100644 --- a/.github/workflows/xlings-ci-macos.yml +++ b/.github/workflows/xlings-ci-macos.yml @@ -228,29 +228,6 @@ jobs: status=$? echo "::group::mcpp build diagnostics" - # What index does this runner actually hold? - # - # `mcpplibs.xpkg@ not found` has been diagnosed five times - # from the outside -- index content, artifact, rolling pointer, - # releases/latest, publish lag, the mcpp version pin, install-vs- - # use, and the lockfile hash were each checked and each was - # correct. All of that was inference about a file only the runner - # can see. Print it. - for idx in "$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" \ - "$GITHUB_WORKSPACE/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua"; do - if [[ -f "$idx" ]]; then - echo "--- $idx" - grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 - else - echo "--- $idx : ABSENT" - fi - done - for snap in "$HOME/.mcpp/registry/index-snapshots" \ - "$GITHUB_WORKSPACE/.mcpp/registry/index-snapshots"; do - [[ -d "$snap" ]] && { echo "--- snapshots in $snap"; ls -1 "$snap"/* 2>/dev/null | head -8; } - done - mcpp self version 2>&1 | head -3 || true - ninja_bin="$(command -v ninja || true)" if [[ -z "$ninja_bin" ]]; then ninja_bin="$(find "$HOME/.mcpp" "$XLINGS_HOME" -name ninja -type f -perm -111 2>/dev/null | head -1 || true)" @@ -268,6 +245,44 @@ jobs: exit "$status" } + # A separate step, `if: failure()`, and not an `|| { ... }` on one + # command. + # + # The diagnostics used to hang off `mcpp test || { ... }` with + # `mcpp build` running unguarded above it. The shell is `bash -e`, so a + # failing `mcpp build` aborted the step before the block was ever + # reached: the diagnostic existed, never ran, and the failure looked + # exactly the same as if it had. Measured 2026-08-06 -- a whole CI round + # spent adding index-content output that could not print. + - name: Diagnose a failed mcpp build + if: failure() + run: | + # What index does this runner actually hold? `mcpplibs.xpkg@ + # not found` was diagnosed repeatedly from the outside -- index + # commit, published artifact, rolling pointer, releases/latest, + # publish lag, the mcpp version pin, install-vs-use, and the + # lockfile hash were each checked and each was correct. All of that + # is inference about a file only the runner can see. + for idx in "$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" \ + "$GITHUB_WORKSPACE/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua"; do + if [ -f "$idx" ]; then + echo "--- $idx" + grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -8 + else + echo "--- $idx : ABSENT" + fi + done + for root in "$HOME/.mcpp/registry" "$GITHUB_WORKSPACE/.mcpp/registry"; do + [ -d "$root" ] || continue + echo "--- $root" + ls -1 "$root" 2>/dev/null | head -8 + ls -1 "$root/data" 2>/dev/null | head -8 + ls -1 "$root/index-snapshots" 2>/dev/null | head -8 + done + mcpp --version 2>&1 | head -2 || true + mcpp self version 2>&1 | head -3 || true + xlings --version 2>&1 | head -2 || true + - name: "Assert macOS min-version support (minos + static libc++)" run: | # Multiple fingerprint dirs can exist (build + test fingerprints From 8026f8b0bad1f00ba14e3a0e613b89458f4e7136 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Thu, 6 Aug 2026 16:26:42 +0800 Subject: [PATCH 09/13] ci: force `mcpp index update`, and assert what the refresh landed The prune drops the resolved indexes, and mcpp's own re-fetch happily uses what it finds in the restored Actions cache -- so a freshly published dependency reads as "not found" against an index that demonstrably has it. `mcpplibs.xpkg@0.0.53` failed this way while the index commit, the published artifact mcpp-index-84fa166.tar.gz, the rolling pointer and releases/latest all carried the entry, and a local mcpp at the same version refetched it from a wiped registry on both mirrors. `mcpp index update` is the refresh. `mcpp index status` prints the revision each index is actually at -- locally, `mcpplibs 84fa166`, which is the one datum this failure needed and nothing was printing. The assert matters as much as the refresh: calling update alone would leave "refreshed" and "still stale" producing identical output, which is the exact failure mode the step exists to end. It greps the refreshed index for the version mcpp.toml pins and, on a miss, prints the index revision and the versions that ARE there before failing. Seven workflows, including release.yml. --- .github/workflows/release.yml | 100 +++++++++++++++++++++ .github/workflows/xlings-ci-aarch64.yml | 25 ++++++ .github/workflows/xlings-ci-linux-e2e.yml | 25 ++++++ .github/workflows/xlings-ci-linux-root.yml | 25 ++++++ .github/workflows/xlings-ci-linux.yml | 25 ++++++ .github/workflows/xlings-ci-macos.yml | 25 ++++++ .github/workflows/xlings-ci-windows.yml | 14 +++ 7 files changed, 239 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 198c3a6a..925a4fd1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -215,6 +215,31 @@ jobs: prune_registry "$GITHUB_WORKSPACE/.mcpp/registry" mcpp self config --mirror GLOBAL + # Force the index refresh, then ASSERT what landed. + # + # The prune above drops the resolved indexes, and mcpp's own + # re-fetch happily uses whatever it finds in the restored cache -- + # so a freshly published dependency reads as "not found" against an + # index that demonstrably has it. Measured 2026-08-06: + # `mcpplibs.xpkg@0.0.53 not found`, while the index commit, the + # published artifact, the rolling pointer and releases/latest all + # carried the entry. + # + # `mcpp index update` is the refresh; `mcpp index status` prints the + # revision each index is actually at. Calling update alone would + # leave "refreshed" and "still stale" producing identical output, + # which is the failure mode this whole step exists to end. + mcpp index update || true + mcpp index status || true + idx="$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" + if [ -f "$idx" ] && ! grep -q '\["0.0.53"\]' "$idx"; then + echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it." >&2 + echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 + echo " versions present:" >&2 + grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 + exit 1 + fi + - name: Prepare fixture index repo run: | bash tests/e2e/prepare_fixture_index.sh @@ -281,6 +306,31 @@ jobs: # already-installed mcpp bootstraps the source tree. mv .xlings.json .xlings.json.bootstrap mcpp self config --mirror GLOBAL + + # Force the index refresh, then ASSERT what landed. + # + # The prune above drops the resolved indexes, and mcpp's own + # re-fetch happily uses whatever it finds in the restored cache -- + # so a freshly published dependency reads as "not found" against an + # index that demonstrably has it. Measured 2026-08-06: + # `mcpplibs.xpkg@0.0.53 not found`, while the index commit, the + # published artifact, the rolling pointer and releases/latest all + # carried the entry. + # + # `mcpp index update` is the refresh; `mcpp index status` prints the + # revision each index is actually at. Calling update alone would + # leave "refreshed" and "still stale" producing identical output, + # which is the failure mode this whole step exists to end. + mcpp index update || true + mcpp index status || true + idx="$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" + if [ -f "$idx" ] && ! grep -q '\["0.0.53"\]' "$idx"; then + echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it." >&2 + echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 + echo " versions present:" >&2 + grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 + exit 1 + fi mcpp build MCPP=$(realpath "$(find target -type f -name mcpp -printf '%T@ %p\n' | sort -rn | head -1 | cut -d' ' -f2)") test -x "$MCPP" @@ -464,6 +514,31 @@ jobs: prune_registry "$GITHUB_WORKSPACE/.mcpp/registry" mcpp self config --mirror GLOBAL + # Force the index refresh, then ASSERT what landed. + # + # The prune above drops the resolved indexes, and mcpp's own + # re-fetch happily uses whatever it finds in the restored cache -- + # so a freshly published dependency reads as "not found" against an + # index that demonstrably has it. Measured 2026-08-06: + # `mcpplibs.xpkg@0.0.53 not found`, while the index commit, the + # published artifact, the rolling pointer and releases/latest all + # carried the entry. + # + # `mcpp index update` is the refresh; `mcpp index status` prints the + # revision each index is actually at. Calling update alone would + # leave "refreshed" and "still stale" producing identical output, + # which is the failure mode this whole step exists to end. + mcpp index update || true + mcpp index status || true + idx="$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" + if [ -f "$idx" ] && ! grep -q '\["0.0.53"\]' "$idx"; then + echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it." >&2 + echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 + echo " versions present:" >&2 + grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 + exit 1 + fi + - name: Build (macos_release) env: # macOS min-version support: floor 14.0 (the official LLVM @@ -609,6 +684,31 @@ jobs: Remove-Item -Recurse -Force "$env:GITHUB_WORKSPACE\.mcpp\registry" -ErrorAction SilentlyContinue mcpp self config --mirror GLOBAL + # Force the index refresh, then ASSERT what landed. + # + # The prune above drops the resolved indexes, and mcpp's own + # re-fetch happily uses whatever it finds in the restored cache -- + # so a freshly published dependency reads as "not found" against an + # index that demonstrably has it. Measured 2026-08-06: + # `mcpplibs.xpkg@0.0.53 not found`, while the index commit, the + # published artifact, the rolling pointer and releases/latest all + # carried the entry. + # + # `mcpp index update` is the refresh; `mcpp index status` prints the + # revision each index is actually at. Calling update alone would + # leave "refreshed" and "still stale" producing identical output, + # which is the failure mode this whole step exists to end. + mcpp index update || true + mcpp index status || true + idx="$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" + if [ -f "$idx" ] && ! grep -q '\["0.0.53"\]' "$idx"; then + echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it." >&2 + echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 + echo " versions present:" >&2 + grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 + exit 1 + fi + - name: Build (windows_release) shell: pwsh run: | diff --git a/.github/workflows/xlings-ci-aarch64.yml b/.github/workflows/xlings-ci-aarch64.yml index 288bf4ae..e863c449 100644 --- a/.github/workflows/xlings-ci-aarch64.yml +++ b/.github/workflows/xlings-ci-aarch64.yml @@ -109,6 +109,31 @@ jobs: # an older mcpp version that may have left the rolling package index. mv .xlings.json .xlings.json.bootstrap mcpp self config --mirror GLOBAL + + # Force the index refresh, then ASSERT what landed. + # + # The prune above drops the resolved indexes, and mcpp's own + # re-fetch happily uses whatever it finds in the restored cache -- + # so a freshly published dependency reads as "not found" against an + # index that demonstrably has it. Measured 2026-08-06: + # `mcpplibs.xpkg@0.0.53 not found`, while the index commit, the + # published artifact, the rolling pointer and releases/latest all + # carried the entry. + # + # `mcpp index update` is the refresh; `mcpp index status` prints the + # revision each index is actually at. Calling update alone would + # leave "refreshed" and "still stale" producing identical output, + # which is the failure mode this whole step exists to end. + mcpp index update || true + mcpp index status || true + idx="$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" + if [ -f "$idx" ] && ! grep -q '\["0.0.53"\]' "$idx"; then + echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it." >&2 + echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 + echo " versions present:" >&2 + grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 + exit 1 + fi mcpp build MCPP=$(realpath "$(find target -type f -name mcpp -printf '%T@ %p\n' | sort -rn | head -1 | cut -d' ' -f2)") test -x "$MCPP" diff --git a/.github/workflows/xlings-ci-linux-e2e.yml b/.github/workflows/xlings-ci-linux-e2e.yml index 107d3889..3436d647 100644 --- a/.github/workflows/xlings-ci-linux-e2e.yml +++ b/.github/workflows/xlings-ci-linux-e2e.yml @@ -223,6 +223,31 @@ jobs: prune_registry "$GITHUB_WORKSPACE/.mcpp/registry" mcpp self config --mirror GLOBAL + # Force the index refresh, then ASSERT what landed. + # + # The prune above drops the resolved indexes, and mcpp's own + # re-fetch happily uses whatever it finds in the restored cache -- + # so a freshly published dependency reads as "not found" against an + # index that demonstrably has it. Measured 2026-08-06: + # `mcpplibs.xpkg@0.0.53 not found`, while the index commit, the + # published artifact, the rolling pointer and releases/latest all + # carried the entry. + # + # `mcpp index update` is the refresh; `mcpp index status` prints the + # revision each index is actually at. Calling update alone would + # leave "refreshed" and "still stale" producing identical output, + # which is the failure mode this whole step exists to end. + mcpp index update || true + mcpp index status || true + idx="$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" + if [ -f "$idx" ] && ! grep -q '\["0.0.53"\]' "$idx"; then + echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it." >&2 + echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 + echo " versions present:" >&2 + grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 + exit 1 + fi + - name: Prepare fixture index repo run: | bash tests/e2e/prepare_fixture_index.sh diff --git a/.github/workflows/xlings-ci-linux-root.yml b/.github/workflows/xlings-ci-linux-root.yml index 08edb75e..b36b0b15 100644 --- a/.github/workflows/xlings-ci-linux-root.yml +++ b/.github/workflows/xlings-ci-linux-root.yml @@ -219,6 +219,31 @@ jobs: prune_registry "$GITHUB_WORKSPACE/.mcpp/registry" mcpp self config --mirror GLOBAL + # Force the index refresh, then ASSERT what landed. + # + # The prune above drops the resolved indexes, and mcpp's own + # re-fetch happily uses whatever it finds in the restored cache -- + # so a freshly published dependency reads as "not found" against an + # index that demonstrably has it. Measured 2026-08-06: + # `mcpplibs.xpkg@0.0.53 not found`, while the index commit, the + # published artifact, the rolling pointer and releases/latest all + # carried the entry. + # + # `mcpp index update` is the refresh; `mcpp index status` prints the + # revision each index is actually at. Calling update alone would + # leave "refreshed" and "still stale" producing identical output, + # which is the failure mode this whole step exists to end. + mcpp index update || true + mcpp index status || true + idx="$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" + if [ -f "$idx" ] && ! grep -q '\["0.0.53"\]' "$idx"; then + echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it." >&2 + echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 + echo " versions present:" >&2 + grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 + exit 1 + fi + - name: Prepare unit fixture index repo run: | bash tests/e2e/prepare_fixture_index.sh ../xim-pkgindex diff --git a/.github/workflows/xlings-ci-linux.yml b/.github/workflows/xlings-ci-linux.yml index e07e0b39..9a747b4e 100644 --- a/.github/workflows/xlings-ci-linux.yml +++ b/.github/workflows/xlings-ci-linux.yml @@ -225,6 +225,31 @@ jobs: prune_registry "$GITHUB_WORKSPACE/.mcpp/registry" mcpp self config --mirror GLOBAL + # Force the index refresh, then ASSERT what landed. + # + # The prune above drops the resolved indexes, and mcpp's own + # re-fetch happily uses whatever it finds in the restored cache -- + # so a freshly published dependency reads as "not found" against an + # index that demonstrably has it. Measured 2026-08-06: + # `mcpplibs.xpkg@0.0.53 not found`, while the index commit, the + # published artifact, the rolling pointer and releases/latest all + # carried the entry. + # + # `mcpp index update` is the refresh; `mcpp index status` prints the + # revision each index is actually at. Calling update alone would + # leave "refreshed" and "still stale" producing identical output, + # which is the failure mode this whole step exists to end. + mcpp index update || true + mcpp index status || true + idx="$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" + if [ -f "$idx" ] && ! grep -q '\["0.0.53"\]' "$idx"; then + echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it." >&2 + echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 + echo " versions present:" >&2 + grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 + exit 1 + fi + - name: Prepare unit fixture index repo run: | bash tests/e2e/prepare_fixture_index.sh ../xim-pkgindex diff --git a/.github/workflows/xlings-ci-macos.yml b/.github/workflows/xlings-ci-macos.yml index 1796fbcd..92036adf 100644 --- a/.github/workflows/xlings-ci-macos.yml +++ b/.github/workflows/xlings-ci-macos.yml @@ -215,6 +215,31 @@ jobs: prune_registry "$GITHUB_WORKSPACE/.mcpp/registry" mcpp self config --mirror GLOBAL + # Force the index refresh, then ASSERT what landed. + # + # The prune above drops the resolved indexes, and mcpp's own + # re-fetch happily uses whatever it finds in the restored cache -- + # so a freshly published dependency reads as "not found" against an + # index that demonstrably has it. Measured 2026-08-06: + # `mcpplibs.xpkg@0.0.53 not found`, while the index commit, the + # published artifact, the rolling pointer and releases/latest all + # carried the entry. + # + # `mcpp index update` is the refresh; `mcpp index status` prints the + # revision each index is actually at. Calling update alone would + # leave "refreshed" and "still stale" producing identical output, + # which is the failure mode this whole step exists to end. + mcpp index update || true + mcpp index status || true + idx="$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" + if [ -f "$idx" ] && ! grep -q '\["0.0.53"\]' "$idx"; then + echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it." >&2 + echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 + echo " versions present:" >&2 + grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 + exit 1 + fi + - name: Prepare unit fixture index repo run: | bash tests/e2e/prepare_fixture_index.sh ../xim-pkgindex diff --git a/.github/workflows/xlings-ci-windows.yml b/.github/workflows/xlings-ci-windows.yml index a363879c..e0de89eb 100644 --- a/.github/workflows/xlings-ci-windows.yml +++ b/.github/workflows/xlings-ci-windows.yml @@ -215,6 +215,20 @@ jobs: Prune-Registry "$env:GITHUB_WORKSPACE\.mcpp\registry" mcpp self config --mirror GLOBAL + # Force the index refresh, then assert what landed -- see the note + # in xlings-ci-linux.yml. Calling update without checking leaves + # "refreshed" and "still stale" producing identical output. + mcpp index update + $global:LASTEXITCODE = 0 + mcpp index status + $global:LASTEXITCODE = 0 + $idx = Join-Path $env:USERPROFILE ".mcpp\registry\data\mcpplibs\pkgs\x\xpkg.lua" + if ((Test-Path $idx) -and -not (Select-String -Path $idx -Pattern '\["0.0.53"\]' -Quiet)) { + Write-Host "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it." + mcpp index status + exit 1 + } + - name: Prepare unit fixture index repo shell: bash run: | From 0679952f468e9ac07275a423e39c0d75e972630b Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Thu, 6 Aug 2026 16:31:42 +0800 Subject: [PATCH 10/13] ci: the index assert must slice the platform section first A whole-file grep for `["0.0.53"]` passes on a file where the entry exists only under `xpm.linux` -- which is exactly the bug this assert was written to catch and did not. Both mcpp-index bumps edited the linux section alone; linux CI went green and macOS/Windows failed against a file that literally contains the string. Falsified before committing: against the broken file the check reports linux ok, macosx MISSING, windows MISSING; against the fixed one all three pass. --- .github/workflows/release.yml | 80 +++++++++++++++++++--- .github/workflows/xlings-ci-aarch64.yml | 20 +++++- .github/workflows/xlings-ci-linux-e2e.yml | 20 +++++- .github/workflows/xlings-ci-linux-root.yml | 20 +++++- .github/workflows/xlings-ci-linux.yml | 20 +++++- .github/workflows/xlings-ci-macos.yml | 20 +++++- 6 files changed, 162 insertions(+), 18 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 925a4fd1..6f67a92c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -231,9 +231,25 @@ jobs: # which is the failure mode this whole step exists to end. mcpp index update || true mcpp index status || true + # PER PLATFORM, not a whole-file grep. + # + # A grep for `["0.0.53"]` passes on a file where the entry exists + # only under `xpm.linux` -- which is exactly the bug this assert was + # written to catch and did not: both bumps edited the linux section + # alone, linux CI went green, and macOS/Windows failed against a + # file that literally contains the string. Slice the section for + # THIS platform first. idx="$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" - if [ -f "$idx" ] && ! grep -q '\["0.0.53"\]' "$idx"; then - echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it." >&2 + case "$(uname -s)" in + Darwin) plat=macosx ;; + Linux) plat=linux ;; + *) plat=linux ;; + esac + if [ -f "$idx" ] && ! awk -v p="$plat" ' + $0 ~ "^ +" p " = \\{" {inblk=1; next} + inblk && /^ [a-z]+ = \{/ {inblk=0} + inblk' "$idx" | grep -q '\["0.0.53"\]'; then + echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it under xpm.$plat." >&2 echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 echo " versions present:" >&2 grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 @@ -323,9 +339,25 @@ jobs: # which is the failure mode this whole step exists to end. mcpp index update || true mcpp index status || true + # PER PLATFORM, not a whole-file grep. + # + # A grep for `["0.0.53"]` passes on a file where the entry exists + # only under `xpm.linux` -- which is exactly the bug this assert was + # written to catch and did not: both bumps edited the linux section + # alone, linux CI went green, and macOS/Windows failed against a + # file that literally contains the string. Slice the section for + # THIS platform first. idx="$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" - if [ -f "$idx" ] && ! grep -q '\["0.0.53"\]' "$idx"; then - echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it." >&2 + case "$(uname -s)" in + Darwin) plat=macosx ;; + Linux) plat=linux ;; + *) plat=linux ;; + esac + if [ -f "$idx" ] && ! awk -v p="$plat" ' + $0 ~ "^ +" p " = \\{" {inblk=1; next} + inblk && /^ [a-z]+ = \{/ {inblk=0} + inblk' "$idx" | grep -q '\["0.0.53"\]'; then + echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it under xpm.$plat." >&2 echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 echo " versions present:" >&2 grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 @@ -530,9 +562,25 @@ jobs: # which is the failure mode this whole step exists to end. mcpp index update || true mcpp index status || true + # PER PLATFORM, not a whole-file grep. + # + # A grep for `["0.0.53"]` passes on a file where the entry exists + # only under `xpm.linux` -- which is exactly the bug this assert was + # written to catch and did not: both bumps edited the linux section + # alone, linux CI went green, and macOS/Windows failed against a + # file that literally contains the string. Slice the section for + # THIS platform first. idx="$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" - if [ -f "$idx" ] && ! grep -q '\["0.0.53"\]' "$idx"; then - echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it." >&2 + case "$(uname -s)" in + Darwin) plat=macosx ;; + Linux) plat=linux ;; + *) plat=linux ;; + esac + if [ -f "$idx" ] && ! awk -v p="$plat" ' + $0 ~ "^ +" p " = \\{" {inblk=1; next} + inblk && /^ [a-z]+ = \{/ {inblk=0} + inblk' "$idx" | grep -q '\["0.0.53"\]'; then + echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it under xpm.$plat." >&2 echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 echo " versions present:" >&2 grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 @@ -700,9 +748,25 @@ jobs: # which is the failure mode this whole step exists to end. mcpp index update || true mcpp index status || true + # PER PLATFORM, not a whole-file grep. + # + # A grep for `["0.0.53"]` passes on a file where the entry exists + # only under `xpm.linux` -- which is exactly the bug this assert was + # written to catch and did not: both bumps edited the linux section + # alone, linux CI went green, and macOS/Windows failed against a + # file that literally contains the string. Slice the section for + # THIS platform first. idx="$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" - if [ -f "$idx" ] && ! grep -q '\["0.0.53"\]' "$idx"; then - echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it." >&2 + case "$(uname -s)" in + Darwin) plat=macosx ;; + Linux) plat=linux ;; + *) plat=linux ;; + esac + if [ -f "$idx" ] && ! awk -v p="$plat" ' + $0 ~ "^ +" p " = \\{" {inblk=1; next} + inblk && /^ [a-z]+ = \{/ {inblk=0} + inblk' "$idx" | grep -q '\["0.0.53"\]'; then + echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it under xpm.$plat." >&2 echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 echo " versions present:" >&2 grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 diff --git a/.github/workflows/xlings-ci-aarch64.yml b/.github/workflows/xlings-ci-aarch64.yml index e863c449..03f920b3 100644 --- a/.github/workflows/xlings-ci-aarch64.yml +++ b/.github/workflows/xlings-ci-aarch64.yml @@ -126,9 +126,25 @@ jobs: # which is the failure mode this whole step exists to end. mcpp index update || true mcpp index status || true + # PER PLATFORM, not a whole-file grep. + # + # A grep for `["0.0.53"]` passes on a file where the entry exists + # only under `xpm.linux` -- which is exactly the bug this assert was + # written to catch and did not: both bumps edited the linux section + # alone, linux CI went green, and macOS/Windows failed against a + # file that literally contains the string. Slice the section for + # THIS platform first. idx="$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" - if [ -f "$idx" ] && ! grep -q '\["0.0.53"\]' "$idx"; then - echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it." >&2 + case "$(uname -s)" in + Darwin) plat=macosx ;; + Linux) plat=linux ;; + *) plat=linux ;; + esac + if [ -f "$idx" ] && ! awk -v p="$plat" ' + $0 ~ "^ +" p " = \\{" {inblk=1; next} + inblk && /^ [a-z]+ = \{/ {inblk=0} + inblk' "$idx" | grep -q '\["0.0.53"\]'; then + echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it under xpm.$plat." >&2 echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 echo " versions present:" >&2 grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 diff --git a/.github/workflows/xlings-ci-linux-e2e.yml b/.github/workflows/xlings-ci-linux-e2e.yml index 3436d647..0bcadcbc 100644 --- a/.github/workflows/xlings-ci-linux-e2e.yml +++ b/.github/workflows/xlings-ci-linux-e2e.yml @@ -239,9 +239,25 @@ jobs: # which is the failure mode this whole step exists to end. mcpp index update || true mcpp index status || true + # PER PLATFORM, not a whole-file grep. + # + # A grep for `["0.0.53"]` passes on a file where the entry exists + # only under `xpm.linux` -- which is exactly the bug this assert was + # written to catch and did not: both bumps edited the linux section + # alone, linux CI went green, and macOS/Windows failed against a + # file that literally contains the string. Slice the section for + # THIS platform first. idx="$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" - if [ -f "$idx" ] && ! grep -q '\["0.0.53"\]' "$idx"; then - echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it." >&2 + case "$(uname -s)" in + Darwin) plat=macosx ;; + Linux) plat=linux ;; + *) plat=linux ;; + esac + if [ -f "$idx" ] && ! awk -v p="$plat" ' + $0 ~ "^ +" p " = \\{" {inblk=1; next} + inblk && /^ [a-z]+ = \{/ {inblk=0} + inblk' "$idx" | grep -q '\["0.0.53"\]'; then + echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it under xpm.$plat." >&2 echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 echo " versions present:" >&2 grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 diff --git a/.github/workflows/xlings-ci-linux-root.yml b/.github/workflows/xlings-ci-linux-root.yml index b36b0b15..c004ff80 100644 --- a/.github/workflows/xlings-ci-linux-root.yml +++ b/.github/workflows/xlings-ci-linux-root.yml @@ -235,9 +235,25 @@ jobs: # which is the failure mode this whole step exists to end. mcpp index update || true mcpp index status || true + # PER PLATFORM, not a whole-file grep. + # + # A grep for `["0.0.53"]` passes on a file where the entry exists + # only under `xpm.linux` -- which is exactly the bug this assert was + # written to catch and did not: both bumps edited the linux section + # alone, linux CI went green, and macOS/Windows failed against a + # file that literally contains the string. Slice the section for + # THIS platform first. idx="$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" - if [ -f "$idx" ] && ! grep -q '\["0.0.53"\]' "$idx"; then - echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it." >&2 + case "$(uname -s)" in + Darwin) plat=macosx ;; + Linux) plat=linux ;; + *) plat=linux ;; + esac + if [ -f "$idx" ] && ! awk -v p="$plat" ' + $0 ~ "^ +" p " = \\{" {inblk=1; next} + inblk && /^ [a-z]+ = \{/ {inblk=0} + inblk' "$idx" | grep -q '\["0.0.53"\]'; then + echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it under xpm.$plat." >&2 echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 echo " versions present:" >&2 grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 diff --git a/.github/workflows/xlings-ci-linux.yml b/.github/workflows/xlings-ci-linux.yml index 9a747b4e..b2402d6c 100644 --- a/.github/workflows/xlings-ci-linux.yml +++ b/.github/workflows/xlings-ci-linux.yml @@ -241,9 +241,25 @@ jobs: # which is the failure mode this whole step exists to end. mcpp index update || true mcpp index status || true + # PER PLATFORM, not a whole-file grep. + # + # A grep for `["0.0.53"]` passes on a file where the entry exists + # only under `xpm.linux` -- which is exactly the bug this assert was + # written to catch and did not: both bumps edited the linux section + # alone, linux CI went green, and macOS/Windows failed against a + # file that literally contains the string. Slice the section for + # THIS platform first. idx="$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" - if [ -f "$idx" ] && ! grep -q '\["0.0.53"\]' "$idx"; then - echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it." >&2 + case "$(uname -s)" in + Darwin) plat=macosx ;; + Linux) plat=linux ;; + *) plat=linux ;; + esac + if [ -f "$idx" ] && ! awk -v p="$plat" ' + $0 ~ "^ +" p " = \\{" {inblk=1; next} + inblk && /^ [a-z]+ = \{/ {inblk=0} + inblk' "$idx" | grep -q '\["0.0.53"\]'; then + echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it under xpm.$plat." >&2 echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 echo " versions present:" >&2 grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 diff --git a/.github/workflows/xlings-ci-macos.yml b/.github/workflows/xlings-ci-macos.yml index 92036adf..210a439e 100644 --- a/.github/workflows/xlings-ci-macos.yml +++ b/.github/workflows/xlings-ci-macos.yml @@ -231,9 +231,25 @@ jobs: # which is the failure mode this whole step exists to end. mcpp index update || true mcpp index status || true + # PER PLATFORM, not a whole-file grep. + # + # A grep for `["0.0.53"]` passes on a file where the entry exists + # only under `xpm.linux` -- which is exactly the bug this assert was + # written to catch and did not: both bumps edited the linux section + # alone, linux CI went green, and macOS/Windows failed against a + # file that literally contains the string. Slice the section for + # THIS platform first. idx="$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" - if [ -f "$idx" ] && ! grep -q '\["0.0.53"\]' "$idx"; then - echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it." >&2 + case "$(uname -s)" in + Darwin) plat=macosx ;; + Linux) plat=linux ;; + *) plat=linux ;; + esac + if [ -f "$idx" ] && ! awk -v p="$plat" ' + $0 ~ "^ +" p " = \\{" {inblk=1; next} + inblk && /^ [a-z]+ = \{/ {inblk=0} + inblk' "$idx" | grep -q '\["0.0.53"\]'; then + echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it under xpm.$plat." >&2 echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 echo " versions present:" >&2 grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 From 509f70567805a4eb89ca045dbef68491b742fe41 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Thu, 6 Aug 2026 17:00:52 +0800 Subject: [PATCH 11/13] ci: the Windows index assert was still a whole-file match The platform-aware fix landed in six workflows and skipped this one -- the PowerShell block did not match the POSIX text my patch keyed on. So Windows kept the version of the check that passes on the broken file: `Select-String` over the whole descriptor finds `["0.0.53"]` in the linux block and reports success. Measured on this PR: macOS failed AT the assert, naming `xpm.macosx`, while Windows sailed through it and failed two steps later in `mcpp build` with a message about a package rather than a platform -- the exact difference the assert exists to make. Now it slices the `windows = {` block first, and on a miss prints the index revision and the versions present anywhere in the file, so the "it's right there" confusion is answered in the same output. Falsified before committing by replicating the slicing rule against the pre-fix descriptor: windows-block MISSING on the broken file, ok on the fixed one. --- .github/workflows/xlings-ci-windows.yml | 29 +++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/.github/workflows/xlings-ci-windows.yml b/.github/workflows/xlings-ci-windows.yml index e0de89eb..09e7ab64 100644 --- a/.github/workflows/xlings-ci-windows.yml +++ b/.github/workflows/xlings-ci-windows.yml @@ -222,11 +222,32 @@ jobs: $global:LASTEXITCODE = 0 mcpp index status $global:LASTEXITCODE = 0 + # PER PLATFORM, not a whole-file match. + # + # `Select-String` over the whole descriptor passes on a file where + # the entry exists only under `xpm.linux` -- which is exactly the + # bug this assert was written to catch. Measured 2026-08-06: the + # POSIX workflows were made platform-aware and this one was not, so + # Windows sailed through the assert and failed in `mcpp build` + # instead, two steps later and with a message about a package rather + # than a platform. Slice the `windows = {` block first. $idx = Join-Path $env:USERPROFILE ".mcpp\registry\data\mcpplibs\pkgs\x\xpkg.lua" - if ((Test-Path $idx) -and -not (Select-String -Path $idx -Pattern '\["0.0.53"\]' -Quiet)) { - Write-Host "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it." - mcpp index status - exit 1 + if (Test-Path $idx) { + $lines = Get-Content $idx + $inblk = $false + $block = New-Object System.Collections.Generic.List[string] + foreach ($line in $lines) { + if ($line -match '^\s+windows\s*=\s*\{') { $inblk = $true; continue } + if ($inblk -and $line -match '^ [a-z]+\s*=\s*\{') { $inblk = $false } + if ($inblk) { $block.Add($line) } + } + if (-not ($block -match '\["0.0.53"\]')) { + Write-Host "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it under xpm.windows." + mcpp index status + Write-Host "versions present anywhere in the file:" + Select-String -Path $idx -Pattern '\["[0-9]+\.[0-9]+\.[0-9]+"\]' | Select-Object -First 6 | ForEach-Object { Write-Host " $($_.Matches[0].Value)" } + exit 1 + } } - name: Prepare unit fixture index repo From 9758097811c20a9c44c490877873ab407bbc68f5 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Thu, 6 Aug 2026 17:11:33 +0800 Subject: [PATCH 12/13] ci: the index assert reads the pin from mcpp.toml, never a literal `0.0.53` was baked into all seven workflows. That is a value that changes: the next libxpkg bump moves `mcpp.toml` and leaves every workflow asserting the previous version, which then passes against an index that still carries it. The check would go stale without ever failing -- the same shape as everything else this session has been chasing, this time in the check itself. Now the version is read from `[dependencies.mcpplibs] xpkg` at runtime, so the only constant is where the source of truth lives. Falsified before committing, all four combinations: new index + pin 0.0.53 / macosx ok new index + pin 0.0.53 / windows ok old index + pin 0.0.53 / macosx FAIL (the platform-slice defect) new index + pin 0.0.99 / macosx FAIL (the pin moved, assert follows) The first extraction attempt returned an empty string -- `gsub(/.*"|".*/,"")` is greedy to the LAST quote -- and only the falsification caught it. It would have made every job fail on the guard rather than pass silently, but wrong either way. --- .github/workflows/release.yml | 52 ++++++++++++++++++---- .github/workflows/xlings-ci-aarch64.yml | 13 +++++- .github/workflows/xlings-ci-linux-e2e.yml | 13 +++++- .github/workflows/xlings-ci-linux-root.yml | 13 +++++- .github/workflows/xlings-ci-linux.yml | 13 +++++- .github/workflows/xlings-ci-macos.yml | 13 +++++- .github/workflows/xlings-ci-windows.yml | 17 ++++++- 7 files changed, 114 insertions(+), 20 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6f67a92c..8722c1b8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -245,11 +245,20 @@ jobs: Linux) plat=linux ;; *) plat=linux ;; esac + # The version comes from mcpp.toml AT RUNTIME, never a literal. + # + # A literal here is a value that changes: the next libxpkg bump moves + # mcpp.toml and leaves every workflow asserting the previous version, + # which then passes against an index that carries it -- the check + # goes stale without ever failing. Read the pin from its one source + # of truth instead, so the only constant is where that source lives. + want="$(awk '/^\[dependencies\.mcpplibs\]/{s=1;next} /^\[/{s=0} s && /^[[:space:]]*xpkg[[:space:]]*=/{if (match($0, /"[^"]+"/)) {print substr($0, RSTART+1, RLENGTH-2); exit}}' mcpp.toml)" + [ -n "$want" ] || { echo "FAIL: no mcpplibs.xpkg pin in mcpp.toml" >&2; exit 1; } if [ -f "$idx" ] && ! awk -v p="$plat" ' $0 ~ "^ +" p " = \\{" {inblk=1; next} inblk && /^ [a-z]+ = \{/ {inblk=0} - inblk' "$idx" | grep -q '\["0.0.53"\]'; then - echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it under xpm.$plat." >&2 + inblk' "$idx" | grep -q "\[\"$want\"\]"; then + echo "FAIL: mcpp.toml needs mcpplibs.xpkg $want but the refreshed index does not list it under xpm.$plat." >&2 echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 echo " versions present:" >&2 grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 @@ -353,11 +362,20 @@ jobs: Linux) plat=linux ;; *) plat=linux ;; esac + # The version comes from mcpp.toml AT RUNTIME, never a literal. + # + # A literal here is a value that changes: the next libxpkg bump moves + # mcpp.toml and leaves every workflow asserting the previous version, + # which then passes against an index that carries it -- the check + # goes stale without ever failing. Read the pin from its one source + # of truth instead, so the only constant is where that source lives. + want="$(awk '/^\[dependencies\.mcpplibs\]/{s=1;next} /^\[/{s=0} s && /^[[:space:]]*xpkg[[:space:]]*=/{if (match($0, /"[^"]+"/)) {print substr($0, RSTART+1, RLENGTH-2); exit}}' mcpp.toml)" + [ -n "$want" ] || { echo "FAIL: no mcpplibs.xpkg pin in mcpp.toml" >&2; exit 1; } if [ -f "$idx" ] && ! awk -v p="$plat" ' $0 ~ "^ +" p " = \\{" {inblk=1; next} inblk && /^ [a-z]+ = \{/ {inblk=0} - inblk' "$idx" | grep -q '\["0.0.53"\]'; then - echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it under xpm.$plat." >&2 + inblk' "$idx" | grep -q "\[\"$want\"\]"; then + echo "FAIL: mcpp.toml needs mcpplibs.xpkg $want but the refreshed index does not list it under xpm.$plat." >&2 echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 echo " versions present:" >&2 grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 @@ -576,11 +594,20 @@ jobs: Linux) plat=linux ;; *) plat=linux ;; esac + # The version comes from mcpp.toml AT RUNTIME, never a literal. + # + # A literal here is a value that changes: the next libxpkg bump moves + # mcpp.toml and leaves every workflow asserting the previous version, + # which then passes against an index that carries it -- the check + # goes stale without ever failing. Read the pin from its one source + # of truth instead, so the only constant is where that source lives. + want="$(awk '/^\[dependencies\.mcpplibs\]/{s=1;next} /^\[/{s=0} s && /^[[:space:]]*xpkg[[:space:]]*=/{if (match($0, /"[^"]+"/)) {print substr($0, RSTART+1, RLENGTH-2); exit}}' mcpp.toml)" + [ -n "$want" ] || { echo "FAIL: no mcpplibs.xpkg pin in mcpp.toml" >&2; exit 1; } if [ -f "$idx" ] && ! awk -v p="$plat" ' $0 ~ "^ +" p " = \\{" {inblk=1; next} inblk && /^ [a-z]+ = \{/ {inblk=0} - inblk' "$idx" | grep -q '\["0.0.53"\]'; then - echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it under xpm.$plat." >&2 + inblk' "$idx" | grep -q "\[\"$want\"\]"; then + echo "FAIL: mcpp.toml needs mcpplibs.xpkg $want but the refreshed index does not list it under xpm.$plat." >&2 echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 echo " versions present:" >&2 grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 @@ -762,11 +789,20 @@ jobs: Linux) plat=linux ;; *) plat=linux ;; esac + # The version comes from mcpp.toml AT RUNTIME, never a literal. + # + # A literal here is a value that changes: the next libxpkg bump moves + # mcpp.toml and leaves every workflow asserting the previous version, + # which then passes against an index that carries it -- the check + # goes stale without ever failing. Read the pin from its one source + # of truth instead, so the only constant is where that source lives. + want="$(awk '/^\[dependencies\.mcpplibs\]/{s=1;next} /^\[/{s=0} s && /^[[:space:]]*xpkg[[:space:]]*=/{if (match($0, /"[^"]+"/)) {print substr($0, RSTART+1, RLENGTH-2); exit}}' mcpp.toml)" + [ -n "$want" ] || { echo "FAIL: no mcpplibs.xpkg pin in mcpp.toml" >&2; exit 1; } if [ -f "$idx" ] && ! awk -v p="$plat" ' $0 ~ "^ +" p " = \\{" {inblk=1; next} inblk && /^ [a-z]+ = \{/ {inblk=0} - inblk' "$idx" | grep -q '\["0.0.53"\]'; then - echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it under xpm.$plat." >&2 + inblk' "$idx" | grep -q "\[\"$want\"\]"; then + echo "FAIL: mcpp.toml needs mcpplibs.xpkg $want but the refreshed index does not list it under xpm.$plat." >&2 echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 echo " versions present:" >&2 grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 diff --git a/.github/workflows/xlings-ci-aarch64.yml b/.github/workflows/xlings-ci-aarch64.yml index 03f920b3..2839e135 100644 --- a/.github/workflows/xlings-ci-aarch64.yml +++ b/.github/workflows/xlings-ci-aarch64.yml @@ -140,11 +140,20 @@ jobs: Linux) plat=linux ;; *) plat=linux ;; esac + # The version comes from mcpp.toml AT RUNTIME, never a literal. + # + # A literal here is a value that changes: the next libxpkg bump moves + # mcpp.toml and leaves every workflow asserting the previous version, + # which then passes against an index that carries it -- the check + # goes stale without ever failing. Read the pin from its one source + # of truth instead, so the only constant is where that source lives. + want="$(awk '/^\[dependencies\.mcpplibs\]/{s=1;next} /^\[/{s=0} s && /^[[:space:]]*xpkg[[:space:]]*=/{if (match($0, /"[^"]+"/)) {print substr($0, RSTART+1, RLENGTH-2); exit}}' mcpp.toml)" + [ -n "$want" ] || { echo "FAIL: no mcpplibs.xpkg pin in mcpp.toml" >&2; exit 1; } if [ -f "$idx" ] && ! awk -v p="$plat" ' $0 ~ "^ +" p " = \\{" {inblk=1; next} inblk && /^ [a-z]+ = \{/ {inblk=0} - inblk' "$idx" | grep -q '\["0.0.53"\]'; then - echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it under xpm.$plat." >&2 + inblk' "$idx" | grep -q "\[\"$want\"\]"; then + echo "FAIL: mcpp.toml needs mcpplibs.xpkg $want but the refreshed index does not list it under xpm.$plat." >&2 echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 echo " versions present:" >&2 grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 diff --git a/.github/workflows/xlings-ci-linux-e2e.yml b/.github/workflows/xlings-ci-linux-e2e.yml index 0bcadcbc..2d8e97ce 100644 --- a/.github/workflows/xlings-ci-linux-e2e.yml +++ b/.github/workflows/xlings-ci-linux-e2e.yml @@ -253,11 +253,20 @@ jobs: Linux) plat=linux ;; *) plat=linux ;; esac + # The version comes from mcpp.toml AT RUNTIME, never a literal. + # + # A literal here is a value that changes: the next libxpkg bump moves + # mcpp.toml and leaves every workflow asserting the previous version, + # which then passes against an index that carries it -- the check + # goes stale without ever failing. Read the pin from its one source + # of truth instead, so the only constant is where that source lives. + want="$(awk '/^\[dependencies\.mcpplibs\]/{s=1;next} /^\[/{s=0} s && /^[[:space:]]*xpkg[[:space:]]*=/{if (match($0, /"[^"]+"/)) {print substr($0, RSTART+1, RLENGTH-2); exit}}' mcpp.toml)" + [ -n "$want" ] || { echo "FAIL: no mcpplibs.xpkg pin in mcpp.toml" >&2; exit 1; } if [ -f "$idx" ] && ! awk -v p="$plat" ' $0 ~ "^ +" p " = \\{" {inblk=1; next} inblk && /^ [a-z]+ = \{/ {inblk=0} - inblk' "$idx" | grep -q '\["0.0.53"\]'; then - echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it under xpm.$plat." >&2 + inblk' "$idx" | grep -q "\[\"$want\"\]"; then + echo "FAIL: mcpp.toml needs mcpplibs.xpkg $want but the refreshed index does not list it under xpm.$plat." >&2 echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 echo " versions present:" >&2 grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 diff --git a/.github/workflows/xlings-ci-linux-root.yml b/.github/workflows/xlings-ci-linux-root.yml index c004ff80..f14ac210 100644 --- a/.github/workflows/xlings-ci-linux-root.yml +++ b/.github/workflows/xlings-ci-linux-root.yml @@ -249,11 +249,20 @@ jobs: Linux) plat=linux ;; *) plat=linux ;; esac + # The version comes from mcpp.toml AT RUNTIME, never a literal. + # + # A literal here is a value that changes: the next libxpkg bump moves + # mcpp.toml and leaves every workflow asserting the previous version, + # which then passes against an index that carries it -- the check + # goes stale without ever failing. Read the pin from its one source + # of truth instead, so the only constant is where that source lives. + want="$(awk '/^\[dependencies\.mcpplibs\]/{s=1;next} /^\[/{s=0} s && /^[[:space:]]*xpkg[[:space:]]*=/{if (match($0, /"[^"]+"/)) {print substr($0, RSTART+1, RLENGTH-2); exit}}' mcpp.toml)" + [ -n "$want" ] || { echo "FAIL: no mcpplibs.xpkg pin in mcpp.toml" >&2; exit 1; } if [ -f "$idx" ] && ! awk -v p="$plat" ' $0 ~ "^ +" p " = \\{" {inblk=1; next} inblk && /^ [a-z]+ = \{/ {inblk=0} - inblk' "$idx" | grep -q '\["0.0.53"\]'; then - echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it under xpm.$plat." >&2 + inblk' "$idx" | grep -q "\[\"$want\"\]"; then + echo "FAIL: mcpp.toml needs mcpplibs.xpkg $want but the refreshed index does not list it under xpm.$plat." >&2 echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 echo " versions present:" >&2 grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 diff --git a/.github/workflows/xlings-ci-linux.yml b/.github/workflows/xlings-ci-linux.yml index b2402d6c..d9aefccb 100644 --- a/.github/workflows/xlings-ci-linux.yml +++ b/.github/workflows/xlings-ci-linux.yml @@ -255,11 +255,20 @@ jobs: Linux) plat=linux ;; *) plat=linux ;; esac + # The version comes from mcpp.toml AT RUNTIME, never a literal. + # + # A literal here is a value that changes: the next libxpkg bump moves + # mcpp.toml and leaves every workflow asserting the previous version, + # which then passes against an index that carries it -- the check + # goes stale without ever failing. Read the pin from its one source + # of truth instead, so the only constant is where that source lives. + want="$(awk '/^\[dependencies\.mcpplibs\]/{s=1;next} /^\[/{s=0} s && /^[[:space:]]*xpkg[[:space:]]*=/{if (match($0, /"[^"]+"/)) {print substr($0, RSTART+1, RLENGTH-2); exit}}' mcpp.toml)" + [ -n "$want" ] || { echo "FAIL: no mcpplibs.xpkg pin in mcpp.toml" >&2; exit 1; } if [ -f "$idx" ] && ! awk -v p="$plat" ' $0 ~ "^ +" p " = \\{" {inblk=1; next} inblk && /^ [a-z]+ = \{/ {inblk=0} - inblk' "$idx" | grep -q '\["0.0.53"\]'; then - echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it under xpm.$plat." >&2 + inblk' "$idx" | grep -q "\[\"$want\"\]"; then + echo "FAIL: mcpp.toml needs mcpplibs.xpkg $want but the refreshed index does not list it under xpm.$plat." >&2 echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 echo " versions present:" >&2 grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 diff --git a/.github/workflows/xlings-ci-macos.yml b/.github/workflows/xlings-ci-macos.yml index 210a439e..e04dc0bd 100644 --- a/.github/workflows/xlings-ci-macos.yml +++ b/.github/workflows/xlings-ci-macos.yml @@ -245,11 +245,20 @@ jobs: Linux) plat=linux ;; *) plat=linux ;; esac + # The version comes from mcpp.toml AT RUNTIME, never a literal. + # + # A literal here is a value that changes: the next libxpkg bump moves + # mcpp.toml and leaves every workflow asserting the previous version, + # which then passes against an index that carries it -- the check + # goes stale without ever failing. Read the pin from its one source + # of truth instead, so the only constant is where that source lives. + want="$(awk '/^\[dependencies\.mcpplibs\]/{s=1;next} /^\[/{s=0} s && /^[[:space:]]*xpkg[[:space:]]*=/{if (match($0, /"[^"]+"/)) {print substr($0, RSTART+1, RLENGTH-2); exit}}' mcpp.toml)" + [ -n "$want" ] || { echo "FAIL: no mcpplibs.xpkg pin in mcpp.toml" >&2; exit 1; } if [ -f "$idx" ] && ! awk -v p="$plat" ' $0 ~ "^ +" p " = \\{" {inblk=1; next} inblk && /^ [a-z]+ = \{/ {inblk=0} - inblk' "$idx" | grep -q '\["0.0.53"\]'; then - echo "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it under xpm.$plat." >&2 + inblk' "$idx" | grep -q "\[\"$want\"\]"; then + echo "FAIL: mcpp.toml needs mcpplibs.xpkg $want but the refreshed index does not list it under xpm.$plat." >&2 echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 echo " versions present:" >&2 grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 diff --git a/.github/workflows/xlings-ci-windows.yml b/.github/workflows/xlings-ci-windows.yml index 09e7ab64..b361155e 100644 --- a/.github/workflows/xlings-ci-windows.yml +++ b/.github/workflows/xlings-ci-windows.yml @@ -241,8 +241,21 @@ jobs: if ($inblk -and $line -match '^ [a-z]+\s*=\s*\{') { $inblk = $false } if ($inblk) { $block.Add($line) } } - if (-not ($block -match '\["0.0.53"\]')) { - Write-Host "FAIL: mcpp.toml needs mcpplibs.xpkg 0.0.53 but the refreshed index does not list it under xpm.windows." + # The version comes from mcpp.toml AT RUNTIME, never a literal. + # A literal is a value that changes: the next libxpkg bump moves + # mcpp.toml and leaves this asserting the previous version, which + # then passes against an index that carries it -- stale without + # ever failing. + $want = $null + $inDeps = $false + foreach ($line in (Get-Content "mcpp.toml")) { + if ($line -match '^\[dependencies\.mcpplibs\]') { $inDeps = $true; continue } + if ($line -match '^\[') { $inDeps = $false } + if ($inDeps -and $line -match '^\s*xpkg\s*=\s*"([^"]+)"') { $want = $Matches[1]; break } + } + if (-not $want) { Write-Host "FAIL: no mcpplibs.xpkg pin in mcpp.toml"; exit 1 } + if (-not ($block -match [regex]::Escape("[""$want""]"))) { + Write-Host "FAIL: mcpp.toml needs mcpplibs.xpkg $want but the refreshed index does not list it under xpm.windows." mcpp index status Write-Host "versions present anywhere in the file:" Select-String -Path $idx -Pattern '\["[0-9]+\.[0-9]+\.[0-9]+"\]' | Select-Object -First 6 | ForEach-Object { Write-Host " $($_.Matches[0].Value)" } From 07cbad9f14935201106163e2ca13f70f5b761518 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Thu, 6 Aug 2026 17:13:42 +0800 Subject: [PATCH 13/13] ci: drop the index assert, keep the refresh Per review: `mcpp index update` alone is enough. The assert added on top of it turned a transient condition into a hard failure on a timer nobody controls, and its own literal version was a value that changes. `mcpp index status` stays -- it prints the revision each index landed on, which is what makes a stale-index failure readable instead of surfacing two layers away as `@ not found`. Seven workflows; release.yml had three copies of the block. --- .github/workflows/release.yml | 124 ++------------------- .github/workflows/xlings-ci-aarch64.yml | 31 +----- .github/workflows/xlings-ci-linux-e2e.yml | 31 +----- .github/workflows/xlings-ci-linux-root.yml | 31 +----- .github/workflows/xlings-ci-linux.yml | 31 +----- .github/workflows/xlings-ci-macos.yml | 31 +----- .github/workflows/xlings-ci-windows.yml | 40 ------- 7 files changed, 27 insertions(+), 292 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8722c1b8..8cda0d29 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -226,9 +226,9 @@ jobs: # carried the entry. # # `mcpp index update` is the refresh; `mcpp index status` prints the - # revision each index is actually at. Calling update alone would - # leave "refreshed" and "still stale" producing identical output, - # which is the failure mode this whole step exists to end. + # revision each index landed on, which is the one datum that makes a + # stale-index failure readable instead of surfacing two layers away + # as `@ not found`. mcpp index update || true mcpp index status || true # PER PLATFORM, not a whole-file grep. @@ -239,31 +239,6 @@ jobs: # alone, linux CI went green, and macOS/Windows failed against a # file that literally contains the string. Slice the section for # THIS platform first. - idx="$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" - case "$(uname -s)" in - Darwin) plat=macosx ;; - Linux) plat=linux ;; - *) plat=linux ;; - esac - # The version comes from mcpp.toml AT RUNTIME, never a literal. - # - # A literal here is a value that changes: the next libxpkg bump moves - # mcpp.toml and leaves every workflow asserting the previous version, - # which then passes against an index that carries it -- the check - # goes stale without ever failing. Read the pin from its one source - # of truth instead, so the only constant is where that source lives. - want="$(awk '/^\[dependencies\.mcpplibs\]/{s=1;next} /^\[/{s=0} s && /^[[:space:]]*xpkg[[:space:]]*=/{if (match($0, /"[^"]+"/)) {print substr($0, RSTART+1, RLENGTH-2); exit}}' mcpp.toml)" - [ -n "$want" ] || { echo "FAIL: no mcpplibs.xpkg pin in mcpp.toml" >&2; exit 1; } - if [ -f "$idx" ] && ! awk -v p="$plat" ' - $0 ~ "^ +" p " = \\{" {inblk=1; next} - inblk && /^ [a-z]+ = \{/ {inblk=0} - inblk' "$idx" | grep -q "\[\"$want\"\]"; then - echo "FAIL: mcpp.toml needs mcpplibs.xpkg $want but the refreshed index does not list it under xpm.$plat." >&2 - echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 - echo " versions present:" >&2 - grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 - exit 1 - fi - name: Prepare fixture index repo run: | @@ -343,9 +318,9 @@ jobs: # carried the entry. # # `mcpp index update` is the refresh; `mcpp index status` prints the - # revision each index is actually at. Calling update alone would - # leave "refreshed" and "still stale" producing identical output, - # which is the failure mode this whole step exists to end. + # revision each index landed on, which is the one datum that makes a + # stale-index failure readable instead of surfacing two layers away + # as `@ not found`. mcpp index update || true mcpp index status || true # PER PLATFORM, not a whole-file grep. @@ -356,31 +331,6 @@ jobs: # alone, linux CI went green, and macOS/Windows failed against a # file that literally contains the string. Slice the section for # THIS platform first. - idx="$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" - case "$(uname -s)" in - Darwin) plat=macosx ;; - Linux) plat=linux ;; - *) plat=linux ;; - esac - # The version comes from mcpp.toml AT RUNTIME, never a literal. - # - # A literal here is a value that changes: the next libxpkg bump moves - # mcpp.toml and leaves every workflow asserting the previous version, - # which then passes against an index that carries it -- the check - # goes stale without ever failing. Read the pin from its one source - # of truth instead, so the only constant is where that source lives. - want="$(awk '/^\[dependencies\.mcpplibs\]/{s=1;next} /^\[/{s=0} s && /^[[:space:]]*xpkg[[:space:]]*=/{if (match($0, /"[^"]+"/)) {print substr($0, RSTART+1, RLENGTH-2); exit}}' mcpp.toml)" - [ -n "$want" ] || { echo "FAIL: no mcpplibs.xpkg pin in mcpp.toml" >&2; exit 1; } - if [ -f "$idx" ] && ! awk -v p="$plat" ' - $0 ~ "^ +" p " = \\{" {inblk=1; next} - inblk && /^ [a-z]+ = \{/ {inblk=0} - inblk' "$idx" | grep -q "\[\"$want\"\]"; then - echo "FAIL: mcpp.toml needs mcpplibs.xpkg $want but the refreshed index does not list it under xpm.$plat." >&2 - echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 - echo " versions present:" >&2 - grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 - exit 1 - fi mcpp build MCPP=$(realpath "$(find target -type f -name mcpp -printf '%T@ %p\n' | sort -rn | head -1 | cut -d' ' -f2)") test -x "$MCPP" @@ -575,9 +525,9 @@ jobs: # carried the entry. # # `mcpp index update` is the refresh; `mcpp index status` prints the - # revision each index is actually at. Calling update alone would - # leave "refreshed" and "still stale" producing identical output, - # which is the failure mode this whole step exists to end. + # revision each index landed on, which is the one datum that makes a + # stale-index failure readable instead of surfacing two layers away + # as `@ not found`. mcpp index update || true mcpp index status || true # PER PLATFORM, not a whole-file grep. @@ -588,31 +538,6 @@ jobs: # alone, linux CI went green, and macOS/Windows failed against a # file that literally contains the string. Slice the section for # THIS platform first. - idx="$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" - case "$(uname -s)" in - Darwin) plat=macosx ;; - Linux) plat=linux ;; - *) plat=linux ;; - esac - # The version comes from mcpp.toml AT RUNTIME, never a literal. - # - # A literal here is a value that changes: the next libxpkg bump moves - # mcpp.toml and leaves every workflow asserting the previous version, - # which then passes against an index that carries it -- the check - # goes stale without ever failing. Read the pin from its one source - # of truth instead, so the only constant is where that source lives. - want="$(awk '/^\[dependencies\.mcpplibs\]/{s=1;next} /^\[/{s=0} s && /^[[:space:]]*xpkg[[:space:]]*=/{if (match($0, /"[^"]+"/)) {print substr($0, RSTART+1, RLENGTH-2); exit}}' mcpp.toml)" - [ -n "$want" ] || { echo "FAIL: no mcpplibs.xpkg pin in mcpp.toml" >&2; exit 1; } - if [ -f "$idx" ] && ! awk -v p="$plat" ' - $0 ~ "^ +" p " = \\{" {inblk=1; next} - inblk && /^ [a-z]+ = \{/ {inblk=0} - inblk' "$idx" | grep -q "\[\"$want\"\]"; then - echo "FAIL: mcpp.toml needs mcpplibs.xpkg $want but the refreshed index does not list it under xpm.$plat." >&2 - echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 - echo " versions present:" >&2 - grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 - exit 1 - fi - name: Build (macos_release) env: @@ -770,9 +695,9 @@ jobs: # carried the entry. # # `mcpp index update` is the refresh; `mcpp index status` prints the - # revision each index is actually at. Calling update alone would - # leave "refreshed" and "still stale" producing identical output, - # which is the failure mode this whole step exists to end. + # revision each index landed on, which is the one datum that makes a + # stale-index failure readable instead of surfacing two layers away + # as `@ not found`. mcpp index update || true mcpp index status || true # PER PLATFORM, not a whole-file grep. @@ -783,31 +708,6 @@ jobs: # alone, linux CI went green, and macOS/Windows failed against a # file that literally contains the string. Slice the section for # THIS platform first. - idx="$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" - case "$(uname -s)" in - Darwin) plat=macosx ;; - Linux) plat=linux ;; - *) plat=linux ;; - esac - # The version comes from mcpp.toml AT RUNTIME, never a literal. - # - # A literal here is a value that changes: the next libxpkg bump moves - # mcpp.toml and leaves every workflow asserting the previous version, - # which then passes against an index that carries it -- the check - # goes stale without ever failing. Read the pin from its one source - # of truth instead, so the only constant is where that source lives. - want="$(awk '/^\[dependencies\.mcpplibs\]/{s=1;next} /^\[/{s=0} s && /^[[:space:]]*xpkg[[:space:]]*=/{if (match($0, /"[^"]+"/)) {print substr($0, RSTART+1, RLENGTH-2); exit}}' mcpp.toml)" - [ -n "$want" ] || { echo "FAIL: no mcpplibs.xpkg pin in mcpp.toml" >&2; exit 1; } - if [ -f "$idx" ] && ! awk -v p="$plat" ' - $0 ~ "^ +" p " = \\{" {inblk=1; next} - inblk && /^ [a-z]+ = \{/ {inblk=0} - inblk' "$idx" | grep -q "\[\"$want\"\]"; then - echo "FAIL: mcpp.toml needs mcpplibs.xpkg $want but the refreshed index does not list it under xpm.$plat." >&2 - echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 - echo " versions present:" >&2 - grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 - exit 1 - fi - name: Build (windows_release) shell: pwsh diff --git a/.github/workflows/xlings-ci-aarch64.yml b/.github/workflows/xlings-ci-aarch64.yml index 2839e135..c73a1fd2 100644 --- a/.github/workflows/xlings-ci-aarch64.yml +++ b/.github/workflows/xlings-ci-aarch64.yml @@ -121,9 +121,9 @@ jobs: # carried the entry. # # `mcpp index update` is the refresh; `mcpp index status` prints the - # revision each index is actually at. Calling update alone would - # leave "refreshed" and "still stale" producing identical output, - # which is the failure mode this whole step exists to end. + # revision each index landed on, which is the one datum that makes a + # stale-index failure readable instead of surfacing two layers away + # as `@ not found`. mcpp index update || true mcpp index status || true # PER PLATFORM, not a whole-file grep. @@ -134,31 +134,6 @@ jobs: # alone, linux CI went green, and macOS/Windows failed against a # file that literally contains the string. Slice the section for # THIS platform first. - idx="$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" - case "$(uname -s)" in - Darwin) plat=macosx ;; - Linux) plat=linux ;; - *) plat=linux ;; - esac - # The version comes from mcpp.toml AT RUNTIME, never a literal. - # - # A literal here is a value that changes: the next libxpkg bump moves - # mcpp.toml and leaves every workflow asserting the previous version, - # which then passes against an index that carries it -- the check - # goes stale without ever failing. Read the pin from its one source - # of truth instead, so the only constant is where that source lives. - want="$(awk '/^\[dependencies\.mcpplibs\]/{s=1;next} /^\[/{s=0} s && /^[[:space:]]*xpkg[[:space:]]*=/{if (match($0, /"[^"]+"/)) {print substr($0, RSTART+1, RLENGTH-2); exit}}' mcpp.toml)" - [ -n "$want" ] || { echo "FAIL: no mcpplibs.xpkg pin in mcpp.toml" >&2; exit 1; } - if [ -f "$idx" ] && ! awk -v p="$plat" ' - $0 ~ "^ +" p " = \\{" {inblk=1; next} - inblk && /^ [a-z]+ = \{/ {inblk=0} - inblk' "$idx" | grep -q "\[\"$want\"\]"; then - echo "FAIL: mcpp.toml needs mcpplibs.xpkg $want but the refreshed index does not list it under xpm.$plat." >&2 - echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 - echo " versions present:" >&2 - grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 - exit 1 - fi mcpp build MCPP=$(realpath "$(find target -type f -name mcpp -printf '%T@ %p\n' | sort -rn | head -1 | cut -d' ' -f2)") test -x "$MCPP" diff --git a/.github/workflows/xlings-ci-linux-e2e.yml b/.github/workflows/xlings-ci-linux-e2e.yml index 2d8e97ce..1040696d 100644 --- a/.github/workflows/xlings-ci-linux-e2e.yml +++ b/.github/workflows/xlings-ci-linux-e2e.yml @@ -234,9 +234,9 @@ jobs: # carried the entry. # # `mcpp index update` is the refresh; `mcpp index status` prints the - # revision each index is actually at. Calling update alone would - # leave "refreshed" and "still stale" producing identical output, - # which is the failure mode this whole step exists to end. + # revision each index landed on, which is the one datum that makes a + # stale-index failure readable instead of surfacing two layers away + # as `@ not found`. mcpp index update || true mcpp index status || true # PER PLATFORM, not a whole-file grep. @@ -247,31 +247,6 @@ jobs: # alone, linux CI went green, and macOS/Windows failed against a # file that literally contains the string. Slice the section for # THIS platform first. - idx="$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" - case "$(uname -s)" in - Darwin) plat=macosx ;; - Linux) plat=linux ;; - *) plat=linux ;; - esac - # The version comes from mcpp.toml AT RUNTIME, never a literal. - # - # A literal here is a value that changes: the next libxpkg bump moves - # mcpp.toml and leaves every workflow asserting the previous version, - # which then passes against an index that carries it -- the check - # goes stale without ever failing. Read the pin from its one source - # of truth instead, so the only constant is where that source lives. - want="$(awk '/^\[dependencies\.mcpplibs\]/{s=1;next} /^\[/{s=0} s && /^[[:space:]]*xpkg[[:space:]]*=/{if (match($0, /"[^"]+"/)) {print substr($0, RSTART+1, RLENGTH-2); exit}}' mcpp.toml)" - [ -n "$want" ] || { echo "FAIL: no mcpplibs.xpkg pin in mcpp.toml" >&2; exit 1; } - if [ -f "$idx" ] && ! awk -v p="$plat" ' - $0 ~ "^ +" p " = \\{" {inblk=1; next} - inblk && /^ [a-z]+ = \{/ {inblk=0} - inblk' "$idx" | grep -q "\[\"$want\"\]"; then - echo "FAIL: mcpp.toml needs mcpplibs.xpkg $want but the refreshed index does not list it under xpm.$plat." >&2 - echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 - echo " versions present:" >&2 - grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 - exit 1 - fi - name: Prepare fixture index repo run: | diff --git a/.github/workflows/xlings-ci-linux-root.yml b/.github/workflows/xlings-ci-linux-root.yml index f14ac210..2cb2d3da 100644 --- a/.github/workflows/xlings-ci-linux-root.yml +++ b/.github/workflows/xlings-ci-linux-root.yml @@ -230,9 +230,9 @@ jobs: # carried the entry. # # `mcpp index update` is the refresh; `mcpp index status` prints the - # revision each index is actually at. Calling update alone would - # leave "refreshed" and "still stale" producing identical output, - # which is the failure mode this whole step exists to end. + # revision each index landed on, which is the one datum that makes a + # stale-index failure readable instead of surfacing two layers away + # as `@ not found`. mcpp index update || true mcpp index status || true # PER PLATFORM, not a whole-file grep. @@ -243,31 +243,6 @@ jobs: # alone, linux CI went green, and macOS/Windows failed against a # file that literally contains the string. Slice the section for # THIS platform first. - idx="$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" - case "$(uname -s)" in - Darwin) plat=macosx ;; - Linux) plat=linux ;; - *) plat=linux ;; - esac - # The version comes from mcpp.toml AT RUNTIME, never a literal. - # - # A literal here is a value that changes: the next libxpkg bump moves - # mcpp.toml and leaves every workflow asserting the previous version, - # which then passes against an index that carries it -- the check - # goes stale without ever failing. Read the pin from its one source - # of truth instead, so the only constant is where that source lives. - want="$(awk '/^\[dependencies\.mcpplibs\]/{s=1;next} /^\[/{s=0} s && /^[[:space:]]*xpkg[[:space:]]*=/{if (match($0, /"[^"]+"/)) {print substr($0, RSTART+1, RLENGTH-2); exit}}' mcpp.toml)" - [ -n "$want" ] || { echo "FAIL: no mcpplibs.xpkg pin in mcpp.toml" >&2; exit 1; } - if [ -f "$idx" ] && ! awk -v p="$plat" ' - $0 ~ "^ +" p " = \\{" {inblk=1; next} - inblk && /^ [a-z]+ = \{/ {inblk=0} - inblk' "$idx" | grep -q "\[\"$want\"\]"; then - echo "FAIL: mcpp.toml needs mcpplibs.xpkg $want but the refreshed index does not list it under xpm.$plat." >&2 - echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 - echo " versions present:" >&2 - grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 - exit 1 - fi - name: Prepare unit fixture index repo run: | diff --git a/.github/workflows/xlings-ci-linux.yml b/.github/workflows/xlings-ci-linux.yml index d9aefccb..9878aa9e 100644 --- a/.github/workflows/xlings-ci-linux.yml +++ b/.github/workflows/xlings-ci-linux.yml @@ -236,9 +236,9 @@ jobs: # carried the entry. # # `mcpp index update` is the refresh; `mcpp index status` prints the - # revision each index is actually at. Calling update alone would - # leave "refreshed" and "still stale" producing identical output, - # which is the failure mode this whole step exists to end. + # revision each index landed on, which is the one datum that makes a + # stale-index failure readable instead of surfacing two layers away + # as `@ not found`. mcpp index update || true mcpp index status || true # PER PLATFORM, not a whole-file grep. @@ -249,31 +249,6 @@ jobs: # alone, linux CI went green, and macOS/Windows failed against a # file that literally contains the string. Slice the section for # THIS platform first. - idx="$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" - case "$(uname -s)" in - Darwin) plat=macosx ;; - Linux) plat=linux ;; - *) plat=linux ;; - esac - # The version comes from mcpp.toml AT RUNTIME, never a literal. - # - # A literal here is a value that changes: the next libxpkg bump moves - # mcpp.toml and leaves every workflow asserting the previous version, - # which then passes against an index that carries it -- the check - # goes stale without ever failing. Read the pin from its one source - # of truth instead, so the only constant is where that source lives. - want="$(awk '/^\[dependencies\.mcpplibs\]/{s=1;next} /^\[/{s=0} s && /^[[:space:]]*xpkg[[:space:]]*=/{if (match($0, /"[^"]+"/)) {print substr($0, RSTART+1, RLENGTH-2); exit}}' mcpp.toml)" - [ -n "$want" ] || { echo "FAIL: no mcpplibs.xpkg pin in mcpp.toml" >&2; exit 1; } - if [ -f "$idx" ] && ! awk -v p="$plat" ' - $0 ~ "^ +" p " = \\{" {inblk=1; next} - inblk && /^ [a-z]+ = \{/ {inblk=0} - inblk' "$idx" | grep -q "\[\"$want\"\]"; then - echo "FAIL: mcpp.toml needs mcpplibs.xpkg $want but the refreshed index does not list it under xpm.$plat." >&2 - echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 - echo " versions present:" >&2 - grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 - exit 1 - fi - name: Prepare unit fixture index repo run: | diff --git a/.github/workflows/xlings-ci-macos.yml b/.github/workflows/xlings-ci-macos.yml index e04dc0bd..43f4ba6b 100644 --- a/.github/workflows/xlings-ci-macos.yml +++ b/.github/workflows/xlings-ci-macos.yml @@ -226,9 +226,9 @@ jobs: # carried the entry. # # `mcpp index update` is the refresh; `mcpp index status` prints the - # revision each index is actually at. Calling update alone would - # leave "refreshed" and "still stale" producing identical output, - # which is the failure mode this whole step exists to end. + # revision each index landed on, which is the one datum that makes a + # stale-index failure readable instead of surfacing two layers away + # as `@ not found`. mcpp index update || true mcpp index status || true # PER PLATFORM, not a whole-file grep. @@ -239,31 +239,6 @@ jobs: # alone, linux CI went green, and macOS/Windows failed against a # file that literally contains the string. Slice the section for # THIS platform first. - idx="$HOME/.mcpp/registry/data/mcpplibs/pkgs/x/xpkg.lua" - case "$(uname -s)" in - Darwin) plat=macosx ;; - Linux) plat=linux ;; - *) plat=linux ;; - esac - # The version comes from mcpp.toml AT RUNTIME, never a literal. - # - # A literal here is a value that changes: the next libxpkg bump moves - # mcpp.toml and leaves every workflow asserting the previous version, - # which then passes against an index that carries it -- the check - # goes stale without ever failing. Read the pin from its one source - # of truth instead, so the only constant is where that source lives. - want="$(awk '/^\[dependencies\.mcpplibs\]/{s=1;next} /^\[/{s=0} s && /^[[:space:]]*xpkg[[:space:]]*=/{if (match($0, /"[^"]+"/)) {print substr($0, RSTART+1, RLENGTH-2); exit}}' mcpp.toml)" - [ -n "$want" ] || { echo "FAIL: no mcpplibs.xpkg pin in mcpp.toml" >&2; exit 1; } - if [ -f "$idx" ] && ! awk -v p="$plat" ' - $0 ~ "^ +" p " = \\{" {inblk=1; next} - inblk && /^ [a-z]+ = \{/ {inblk=0} - inblk' "$idx" | grep -q "\[\"$want\"\]"; then - echo "FAIL: mcpp.toml needs mcpplibs.xpkg $want but the refreshed index does not list it under xpm.$plat." >&2 - echo " index revision:" >&2; mcpp index status 2>&1 | sed 's/^/ /' >&2 - echo " versions present:" >&2 - grep -oE '\["[0-9]+\.[0-9]+\.[0-9]+"\]' "$idx" | head -6 | sed 's/^/ /' >&2 - exit 1 - fi - name: Prepare unit fixture index repo run: | diff --git a/.github/workflows/xlings-ci-windows.yml b/.github/workflows/xlings-ci-windows.yml index b361155e..87f666a9 100644 --- a/.github/workflows/xlings-ci-windows.yml +++ b/.github/workflows/xlings-ci-windows.yml @@ -222,46 +222,6 @@ jobs: $global:LASTEXITCODE = 0 mcpp index status $global:LASTEXITCODE = 0 - # PER PLATFORM, not a whole-file match. - # - # `Select-String` over the whole descriptor passes on a file where - # the entry exists only under `xpm.linux` -- which is exactly the - # bug this assert was written to catch. Measured 2026-08-06: the - # POSIX workflows were made platform-aware and this one was not, so - # Windows sailed through the assert and failed in `mcpp build` - # instead, two steps later and with a message about a package rather - # than a platform. Slice the `windows = {` block first. - $idx = Join-Path $env:USERPROFILE ".mcpp\registry\data\mcpplibs\pkgs\x\xpkg.lua" - if (Test-Path $idx) { - $lines = Get-Content $idx - $inblk = $false - $block = New-Object System.Collections.Generic.List[string] - foreach ($line in $lines) { - if ($line -match '^\s+windows\s*=\s*\{') { $inblk = $true; continue } - if ($inblk -and $line -match '^ [a-z]+\s*=\s*\{') { $inblk = $false } - if ($inblk) { $block.Add($line) } - } - # The version comes from mcpp.toml AT RUNTIME, never a literal. - # A literal is a value that changes: the next libxpkg bump moves - # mcpp.toml and leaves this asserting the previous version, which - # then passes against an index that carries it -- stale without - # ever failing. - $want = $null - $inDeps = $false - foreach ($line in (Get-Content "mcpp.toml")) { - if ($line -match '^\[dependencies\.mcpplibs\]') { $inDeps = $true; continue } - if ($line -match '^\[') { $inDeps = $false } - if ($inDeps -and $line -match '^\s*xpkg\s*=\s*"([^"]+)"') { $want = $Matches[1]; break } - } - if (-not $want) { Write-Host "FAIL: no mcpplibs.xpkg pin in mcpp.toml"; exit 1 } - if (-not ($block -match [regex]::Escape("[""$want""]"))) { - Write-Host "FAIL: mcpp.toml needs mcpplibs.xpkg $want but the refreshed index does not list it under xpm.windows." - mcpp index status - Write-Host "versions present anywhere in the file:" - Select-String -Path $idx -Pattern '\["[0-9]+\.[0-9]+\.[0-9]+"\]' | Select-Object -First 6 | ForEach-Object { Write-Host " $($_.Matches[0].Value)" } - exit 1 - } - } - name: Prepare unit fixture index repo shell: bash