Skip to content

feat(xim): one resolution, referenced everywhere (2026.8.5.3) - #485

Merged
Sunrisepeak merged 4 commits into
mainfrom
feat/resolution-single-source
Aug 5, 2026
Merged

feat(xim): one resolution, referenced everywhere (2026.8.5.3)#485
Sunrisepeak merged 4 commits into
mainfrom
feat/resolution-single-source

Conversation

@Sunrisepeak

Copy link
Copy Markdown
Member

"Which version is this dependency?" had four independent answers. They agreed while the index shipped one glibc; the second one turned the disagreement into binaries that fault before main with undefined symbol: __pointer_chk_guard, version GLIBC_PRIVATE — naming neither package nor version, because ld.so and libc.so.6 are two halves of one build.

Both directions were reproducible, and both are now regression tests (E2E-62).

  • resolved_deps is total — every runtime dep, with the resolved version, payload dir, libdirs, and why. The install_dir was already computed and thrown away by a break.
  • the convention moved to the writer — libxpkg 0.0.50 deletes the consumer-side re-derivation that was the second answerer. A net deletion, not a fifth answerer.
  • an assertion on what shipped — a provider's interpreter and its libdir must come from one payload; install aborts naming both paths rather than letting the user meet a GLIBC_PRIVATE symbol weeks later.
  • .xlings-resolution.json + xlings why — the decision as data. A decision that must be reproduced to be inspected is not traceable, and reproducing this one means recreating a store state.
  • doctor LoaderLibcSplit over existing payloads, counted toward the exit code, sharing one implementation with the installer.

Also: subos new --runtime <pkg>@<ver> now installs what it declares. Recording it and leaving the subos empty let the first package to arrive decide the real runtime.

Design and the acceptance list: .agents/docs/2026-08-05-dependency-resolution-single-source.md

"Which version is this dependency?" had four independent answers. They agreed
for as long as the index shipped one glibc; the second one turned the
disagreement into binaries that fault before `main` with

    undefined symbol: __pointer_chk_guard, version GLIBC_PRIVATE

naming neither a package nor a version, because ld.so and libc.so.6 are two
halves of one build and GLIBC_PRIVATE is the ABI between them. Both directions
were reproducible: the resolver preferring an already-active 2.39 while a scan
took 2.44, and a transaction that installed both.

What authorised four answers was one sentence — deps_exports' contract said a
missing entry means "this dep declared nothing, fall back to convention". This
changes that contract to "missing is impossible".

  * resolved_deps is TOTAL. Every runtime dep is recorded with the version the
    resolver settled on, the payload directory, the libdirs, and WHY that
    version. The install_dir was already being computed and discarded by a
    `break` for deps that declared no exports; the break moved.
  * the {lib64, lib} convention is applied by the WRITER. libxpkg 0.0.50
    deletes the consumer-side re-derivation that was the second answerer.
  * elfcheck asserts, after elfpatch has written, that a provider's
    interpreter and its libdir come from one payload — the invariant, checked
    on what shipped rather than on what was intended. Install aborts naming
    both paths, instead of the user meeting a GLIBC_PRIVATE symbol later.
  * `.xlings-resolution.json` next to each payload, and `xlings why <pkg>
    [dep]` to read it back. A decision that has to be reproduced to be
    inspected is not traceable, and reproducing this one means recreating a
    store state — the thing that varies.
  * doctor gains LoaderLibcSplit over existing payloads, counted toward the
    exit code. It shares elfcheck::scan_payload with the installer rather than
    reimplementing the rule: report and repair have drifted three times here,
    and it always looks the same from outside — doctor keeps reporting what
    --fix says it fixed.

Also lands `subos new --runtime <pkg>@<ver>` actually installing what it
declares. Recording a runtime and leaving the subos empty let the first
package to arrive decide the real one, so a subos declaring 2.39 ended up on
2.44 and the flag described an intention it did not hold. Only when asked:
without --runtime, `subos new` stays a local directory operation.

Tests: nine unit cases over payload_of/provider_of and the six edge cases of
the assertion (shared libraries and static binaries skip, absent provider
passes, repeated provider passes on any same-source entry, $ORIGIN and
non-store paths are not payloads); E2E-62 covers A1 and A2 with a two-version
fixture and asserts the record is written with its source field.

Design and the full acceptance list:
.agents/docs/2026-08-05-dependency-resolution-single-source.md
The key hashes mcpp.toml, so bumping libxpkg to 0.0.50 missed it exactly --
and `restore-keys` then fell back to the newest v2 entry, whose registry
carries the mcpp-index snapshot from before the bump. Three build-and-test
jobs reported `mcpplibs.xpkg@0.0.50 not found` while a clean home resolved it
without trouble, which is the shape a stale cache always takes: the failure
describes the package, not the cache.

Same remedy the v1 -> v2 retirement used, and for the same reason -- an
inexact restore cannot be prevented by the exact key, only by moving the
prefix out from under it.
The reference is generated from the binary's own help, and the contract script
compares the two. Adding a command without regenerating fails that comparison
— which is the check working: the reference is documentation that cannot
silently fall behind the thing it documents.
Every workflow resolved mcpp's dependencies out of whatever index snapshot the
bootstrap xlings shipped with, and never refreshed it. A freshly published
`mcpplibs.xpkg@0.0.50` was reported "not found" in the same run that
downloaded `mcpplibs:tinyhttps@0.2.9` from the same index without trouble —
older entries were there, the new one was not.

The failure names the package, so it reads as an unpublished dependency. It
took a clean home resolving the same version without difficulty to place it:
the index was fine, the snapshot was old, and nothing in the pipeline had a
reason to notice.

`|| true` on POSIX and a LASTEXITCODE reset on Windows: a refresh that cannot
reach the network must not fail a run whose dependencies are already cached.
This is a freshness step, not a gate.
@Sunrisepeak
Sunrisepeak merged commit c512679 into main Aug 5, 2026
8 of 18 checks passed
@Sunrisepeak
Sunrisepeak deleted the feat/resolution-single-source branch August 5, 2026 18:26
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