2026.8.6.3: the B line lands — every glvnd entry point interposed, proven by provenance - #490
Merged
Merged
Conversation
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.
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.
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.
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.
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.
`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.
`mcpplibs.xpkg@<ver> 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.
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.
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.
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.
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.
`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.
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 `<pkg>@<ver> not found`. Seven workflows; release.yml had three copies of the block.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pins libxpkg 0.0.52. Companions: openxlings/libxpkg#36, openxlings/xim-pkgindex#532.
What the pin brings
elfpatch.host_link_interposer— from a shipped empty ELF stub, patchelfproduces an object carrying the vendor's SONAME, the HOST vendor as an
absolute
DT_NEEDED, and the payload closure asDT_RPATH. The vendor'sdependencies then resolve out of our payloads along one load chain, rather
than being broadcast to the whole process through
LD_LIBRARY_PATH.A
depsshape fix —deps = { "a", "b", build = {...} }dropped thebuild deps and copied the positional entries into
build_depsin theirplace. Declared, reported installed, neither done.
Why §7.7 of the plan doc is long
Every step of this landing produced a passing result first:
interposer-stubinstalledglxinfo→NVIDIA GeForce RTX 4080/PCIe/SSE2in the subos/usr/lib— the host binary under the host loader, our payload contributing nothing. Identical output whether the B line works or never happened.interposer: yesdeps.build = {"xim:patchelf"}declaredEach was found by measuring the artifact rather than reading the log line.
Verification
xim-pkgindex/.agents/tools/graphics/verify-host-link.sh— 12/12 on areal RTX 4080 (driver 550.144.03) in an isolated home: four entry points
have the interposer shape, EGL renders a pixel, GLX renders, both go through
our interposer, the host's real vendor arrives behind it by absolute
DT_NEEDED,LD_LIBRARY_PATHis empty, and the host's driver files areuntouched.
A correction to my own earlier claim
I previously wrote that ~30 recipes using elfpatch "silently fall back to the
host's patchelf". Both halves were wrong.
_find_toolresolves payload →subos view → home bin → host and warns when it leaves the payload (that
warning is what A3 added in 0.0.51), and
tool_payload_dirscans the wholestore — so any home that has patchelf at all uses it, declared or not. The
real gap is narrow: a home that has never installed patchelf. Declaring the
build dep closes that one.