Skip to content

2026.8.6.3: the B line lands — every glvnd entry point interposed, proven by provenance - #490

Merged
Sunrisepeak merged 13 commits into
mainfrom
fix/bline-interposer-verified
Aug 6, 2026
Merged

2026.8.6.3: the B line lands — every glvnd entry point interposed, proven by provenance#490
Sunrisepeak merged 13 commits into
mainfrom
fix/bline-interposer-verified

Conversation

@Sunrisepeak

Copy link
Copy Markdown
Member

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, patchelf
produces an object carrying 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, rather
than being broadcast to the whole process through LD_LIBRARY_PATH.

A deps shape fixdeps = { "a", "b", build = {...} } dropped the
build deps and copied the positional entries into build_deps in their
place. Declared, reported installed, neither done.

Why §7.7 of the plan doc is long

Every step of this landing produced a passing result first:

what it said what was true
interposer-stub installed payload directory empty — a resource key nested one level too deep meant nothing was ever downloaded. Caught only by the hook's own assertion.
glxinfoNVIDIA GeForce RTX 4080/PCIe/SSE2 in the subos every object came from /usr/lib — the host binary under the host loader, our payload contributing nothing. Identical output whether the B line works or never happened.
interposer: yes one of four glvnd entry points covered. EGL rendered on the GPU; GLX pulled its whole closure from the host.
deps.build = {"xim:patchelf"} declared no patchelf installed; the host's ran, and the symptom surfaced two layers away as an elfpatch warning.

Each was found by measuring the artifact rather than reading the log line.

Verification

xim-pkgindex/.agents/tools/graphics/verify-host-link.sh12/12 on a
real 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_PATH is empty, and the host's driver files are
untouched.

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_tool resolves 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_dir scans the whole
store — 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.

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.
@Sunrisepeak
Sunrisepeak merged commit 2cfdc16 into main Aug 6, 2026
8 checks passed
@Sunrisepeak
Sunrisepeak deleted the fix/bline-interposer-verified branch August 6, 2026 09:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant