fix(build): declare compatibility packages exactly - #521
Draft
Sunrisepeak wants to merge 1 commit into
Draft
Conversation
Move ftxui and the gtest development dependency under the compat namespace, and refresh the lockfile so ftxui and the transitive Lua C library retain their canonical identities.\n\nRefs #520
Member
Author
Draft checkpoint: normalized full test gateThe full suite now passes with terminal policy normalized for this non-interactive agent shell (
The earlier single failure was reproduced as the shell policy ( |
Member
Author
|
Cross-repository checkpoint
|
Collaborator
中文状态同步 checkpointPR 正文已由
本 PR 继续保持 Draft/REVIEW_REQUIRED,等待用户明确 review;不会使用 admin/bypass/squash 或改写历史。 |
Sunrisepeak
pushed a commit
to mcpp-community/mcpp
that referenced
this pull request
Aug 9, 2026
Two independent bumps that happen to land on the same date string. The xlings pin moves from 2026.8.9.2 to the current stable 2026.8.10.1 across all 16 pin sites; check_version_pins.sh verifies them against src/xlings.cppm rather than trusting a comment. Note that 2026.8.10.1 still declares a bare `ftxui`, so the four cross-repo CI failures on this branch are resolved by the bare-name migration window in this PR, not by the pin — openxlings/xlings#521 remains the correct upstream fix and is no longer on the critical path. The release version moves from 2026.8.9.1 to 2026.8.10.1 because the version is a DATE and the date changed while this branch was in review. 2026.8.9.1 was never tagged, so nothing depends on the old string. The release-manifest and AUR fixtures derive "some other version" from VERSION; both halves move so they stay distinct instead of collapsing onto the release version and silently asserting nothing. Bootstrap pin stays at the released 2026.8.8.4 — it is the self-hosting starting point, not a function of what this release will be.
Sunrisepeak
pushed a commit
to mcpp-community/mcpp
that referenced
this pull request
Aug 9, 2026
The design that drove this round of work, plus an appendix of outcomes written after the fact — including the two places reality diverged from the plan. D2 resolved as "proven, keep", but for a different reason than the commit it was defending claimed: not a Windows short-name alias, but that anchoring must not relocate an index out of the tree the workspace author addressed. D5's invariant turned out to already hold; it simply had no test. Two things arrived unplanned: #401, and the discovery that every identity/index-routing e2e was gated behind a capability Windows does not have — so the platform where path semantics differ was the one platform never asserting them. And main line C's bottleneck moved. The migration window took openxlings/xlings#521 off the critical path as intended, but pinning to the newest xlings put a harder one in its place: a cold home cannot install gcc at all (openxlings/xlings#524). Recorded with the A/B, because the lesson generalizes — "pin to latest" is an action that needs verifying, not a text substitution, and it only surfaced here because changing the pin also changed the CI cache key.
speak-agent
added a commit
to mcpp-community/mcpp
that referenced
this pull request
Aug 9, 2026
#400) * docs: plan focused ecosystem convergence * feat: make package selectors exact * feat: make template selection exact * feat: make project scaffolding transactional * feat: make runtime selection root-local * feat: validate Linux runtime closure * feat: carry provider-neutral runtime provenance * feat: publish immutable release manifests * feat: reconcile mcpp-bin releases * fix: reconcile runtime bindings with SubOS views * chore: prepare 2026.8.9.1 release * test: recognize Python E2E capability * test: keep ELF parser fixtures on Linux * fix(pm): inherit declared index namespaces exactly * fix(pm): retain index ownership in dependency selection * fix(runtime): model ELF SONAME reuse * test(e2e): align fixtures with exact identities * test(e2e): preserve runtime contracts in fake xlings * fix(index): retain floor cause on exact misses * fix(pm): surface malformed exact descriptors * fix(pm): derive mangling from authored modules * test(e2e): scope BMI settling to its edge * test(scaffold): close fixtures before cleanup * docs: make Draft PR trace privacy-safe * test(e2e): honor isolated mcpp home in libc poison * fix(workspace): anchor inherited indices lexically * docs: add Chinese PR handoff * docs: refresh PR validation handoff * fix(aur): use privacy-safe release identity * docs: record privacy-safe AUR checkpoint * fix(pm): explain exact index route misses * docs: record Windows index route evidence * test(e2e): write fixture paths in host spelling, not shell spelling The Windows E2E 2/2 failure is not the workspace index inheritance the last three commits were aimed at. It is step (15) of 12_add_command.sh, added by this branch, which writes an MSYS path into a manifest: TMP=$(mktemp -d) # Git Bash -> /tmp/tmp.XXXXXXXX acme = { path = "$TMP/myapp/index" } # written as FILE CONTENT MSYS converts POSIX paths on the way into argv and the environment; it does not convert file content. A native mcpp.exe reads the literal leading `/` as "root of the current drive", so the fixture index resolves to C:\tmp\... and is absent. That is exactly what the route diagnostic reported: `local index 'acme': root absent, pkgs absent`. Four independent signals place the failure at step (15), not (14): exit code 2 is mcpp's own status forwarded by `set -e`, while step (14) is wrapped in `|| { …; exit 1; }`; step (14)'s failure banner never appears; the last uncaptured output before it belongs to step (12); and step (14) predates this branch, on which the Windows suite was green. The Wine replay that "reproduced it as passing" was a false negative: Wine maps Z: to /, so \tmp\... lands back on the real /tmp. Wine can show a PE runs. It cannot show anything about path semantics. Fixed as a class rather than a line. `_host_path.sh` provides `host_path` (cygpath -m on MSYS, identity elsewhere, relative paths untouched), 00_fixture_path_hygiene.sh fails any fixture that interpolates a non-`*_HOST` variable into a TOML `path =` value, and the 40 pre-existing sites across 24 files are migrated. Most were masked only because those tests skip on Windows for a missing capability. * test(pm): pin why an inherited index is anchored lexically `inherit_workspace_indices` was switched from `weakly_canonical` to `lexically_normal` in ed4cf64 to fix the red Windows suite. It did not: the same failure appeared identically before (run 31318089536) and after (run 31321961040), and the actual cause was a fixture writing an MSYS path into mcpp.toml. So the change was landed against a misdiagnosis, against a behaviour that was green on main, with no test able to tell the two spellings apart. It turns out to be right for a different reason, and now it says so. Anchoring answers "which directory did the workspace author mean", and the answer must stay inside the tree the author addressed. `weakly_canonical` answers "what is this path once every symlink is resolved", so a workspace reached through a symlinked parent gets relocated into a tree the author never wrote — observable, because prepare reports a missing descriptor as "not found in local index at '<path>'". Verified as a real RED: restoring `weakly_canonical` fails `unit/test_pm_index_route` (71 passed, 1 failed). The existing test also stops asserting a path SPELLING. Comparing the stored path against `(root / "index").lexically_normal()` passes for any rule that happens to produce that string and says nothing about whether the index can be read — which is how it stayed green through a red Windows suite. It now asserts the capability: route description, then a descriptor lookup that actually resolves. * feat(pm): give namespace omission a one-release exit ramp Namespace omission means exactly `mcpplibs`. But every published `compat.*` package and every manifest written before exact identity spells its dependency bare — `gtest = "1.15.2"`, `ftxui = "6.1.9"` — so turning that into an immediate hard error makes an mcpp upgrade break builds against data that is already published and cannot be edited retroactively. Four CI jobs on this branch are that breakage, and this repository's own mcpp.toml had to move gtest under [dev-dependencies.compat] to keep bootstrapping: a change that breaks its own project is breaking everyone's. It is the mirror of the rule that keeps a raised index floor from bricking older clients. Published data must not break the program; a published program must not break existing data either. Both degrade. The #278 defect was the SILENCE, not the reach: mcpp continued with a namespace the user never wrote and never said so. So the rungs come back, and cannot be quiet. After the exact coordinate misses, and only for a selector whose namespace was OMITTED, `(compat, name)` and the namespace-less discovery rung are tried; a hit is announced with the canonical selector and the exact manifest edit, and the canonical identity — not the ambiguous spelling — is what reaches the lock, the install layer and the cache label. `mcpp add` goes further and performs the edit, so touching a dependency migrates it. `mcpplibs.gtest` states an identity and still misses. A third-party namespace is still unreachable by a bare name. Removed in 2026.9, named once as kBareNameFallbackRemovedIn so the warning, the docs and the removal cannot drift apart. Test 162 now asserts both branches and that an already-exact selector is not nagged, and it serves the compat fixture from its own index instead of whatever gtest happened to be installed. * fix(runtime): keep the private libc out of the process environment (#401) `mcpp run` put the private glibc directory on LD_LIBRARY_PATH. That variable is inherited by every process the program ever spawns, and a child like /bin/sh is loaded by the HOST loader — PT_INTERP is baked in and no environment variable overrides it. glibc's libc.so.6 and its ld.so are version-locked through GLIBC_PRIVATE, so the child dies during relocation, before main: sh: symbol lookup error: …/xim-x-glibc/2.44/lib64/libc.so.6: undefined symbol: __pointer_chk_guard, version GLIBC_PRIVATE The reporter hit it through an application probing the desktop theme with gsettings: popen() returned nothing, the app silently used the wrong theme, and mcpp said nothing. Verified locally — the 2.44 payload kills /bin/sh, the 2.39 payload does not, which is why this survived as long as it did and why "it works here" proved nothing. The directory was published to serve a dlopen() the executable's DT_NEEDED closure does not cover, and the artifact's RUNPATH already covers exactly that: the link model emits -Wl,-rpath,<glibc> next to --dynamic-linker, and the built binary's RUNPATH is byte-identical before and after this change. So the environment entry bought nothing and cost every child process. It is gone. Measured against the released mcpp with the same fixture: before LDLP=[…/runtime:…/xim-x-glibc/2.39/lib64] after LDLP=[…/runtime] The decision lives in mcpp.platform.runtime_env_contract as a scope, not a condition: no build-level predicate can make an inherited variable safe for a process mcpp did not launch and cannot see. mcpp#291 was the same mistake one hop closer in. 208 pins all three halves — the dir is in the artifact RUNPATH, it is not in the program's environment, and both the /bin/sh child and the dlopen still work. The RUNPATH assertion deliberately checks coverage mcpp does not itself emit, so losing it is a red test rather than a silent loss of resolution. * test(runtime): a proven mismatch must not go green on a re-run The Linux closure verdict is cached against the artifact stat and the RuntimeBinding contract hash, and caching a FAILURE is the half worth pinning: an unchanged artifact that was proven wrong is still wrong, and reporting success the second time tells the user a problem went away when nothing about the program changed. It is easy to lose by accident, because the project fast path exists to skip work when nothing changed — including, unless something stops it, the check that would have failed. `validated_artifact_snapshot` stops it by refusing to engage unless every stored verdict is a PASS; that rule had no test. 209 poisons one stored verdict without touching a source file and requires the next build to fail with the recorded diagnostics, requires `mcpp self doctor` to explain it from storage, and then requires a rebuilt artifact to be re-validated — a mismatch is cached, not a sentence, or the first real failure would wedge the project until someone deleted target/. * ci(aur): require arming before an unattended publish `schedule` fires every six hours off the default branch and set publish=true unconditionally. Merging this workflow was therefore enough to make mcpp start writing to the AUR on its own, within six hours, on a path that had never completed a real push — only a makepkg --verifysource dry-run. Merging is a decision about code; publishing to a third-party service is a decision about the outside world, and they should not be the same act. Both automatic triggers now plan and report but withhold the push unless the repository variable AUR_AUTOPUBLISH is exactly "true", so a typo fails closed. Dry runs still validate payloads, render .SRCINFO, query the AUR and print the diff — only the push is withheld, so nothing is lost but the surprise. workflow_dispatch keeps its explicit per-run switch, which is how the first watched publish is meant to happen, and unsetting the variable is a kill switch that needs no revert. The contract test runs the workflow's own decision shell rather than a paraphrase of it, so the guarantee cannot drift from the YAML. * test(e2e): assert local-index addressing on every host Every test covering local-index addressing needed `gcc` or `fresh-sandbox`, and Windows has neither capability. So the one platform where path semantics actually differ was the one platform never asserting them — which is why a fixture could write an MSYS path into mcpp.toml and the failure surfaced four steps away, as "package not found in any configured index", and was chased through the workspace inheritance code that was not involved. 210 needs no compiler, no sandbox bootstrap and no network: it asserts at the resolution layer, which is the layer that was broken. An absolute [indices] path resolves; a workspace member inherits a root-relative one and reads it; a miss in both reports the index as a READABLE root, since "root absent" is the fingerprint of a path written in the wrong spelling; and the route diagnostic that makes any of this findable stays free of filesystem paths. * chore(release): 2026.8.10.1 on xlings 2026.8.10.1 Two independent bumps that happen to land on the same date string. The xlings pin moves from 2026.8.9.2 to the current stable 2026.8.10.1 across all 16 pin sites; check_version_pins.sh verifies them against src/xlings.cppm rather than trusting a comment. Note that 2026.8.10.1 still declares a bare `ftxui`, so the four cross-repo CI failures on this branch are resolved by the bare-name migration window in this PR, not by the pin — openxlings/xlings#521 remains the correct upstream fix and is no longer on the critical path. The release version moves from 2026.8.9.1 to 2026.8.10.1 because the version is a DATE and the date changed while this branch was in review. 2026.8.9.1 was never tagged, so nothing depends on the old string. The release-manifest and AUR fixtures derive "some other version" from VERSION; both halves move so they stay distinct instead of collapsing onto the release version and silently asserting nothing. Bootstrap pin stays at the released 2026.8.8.4 — it is the self-hosting starting point, not a function of what this release will be. * docs: the bare-name window, the runtime env scope, and AUR arming Three things a user or a maintainer cannot discover from the code: - The one-release exit ramp for namespace-omitted selectors: what still resolves, what the warning says, that the canonical identity is what reaches the lock, that `mcpp add` performs the migration, and that a stated namespace is not eligible. Written with the reason attached — every published `compat.*` package spells its dependency bare, so failing them outright would make a program release invalidate data that is already published and cannot be edited. - Why the private libc directory is binary-scoped and never environment-scoped, as a table of blast radius rather than a rule to memorize: DT_RUNPATH reaches one object, LD_LIBRARY_PATH reaches every descendant process forever, and a GLIBC_PRIVATE-coupled libc handed to the host loader kills /bin/sh before main (#401). - How to arm the AUR automatic triggers, including that the kill switch is unsetting a repository variable rather than reverting a commit. English and Chinese both, plus the changelog entry for 2026.8.10.1. * chore(xlings): hold the pin at 2026.8.9.2 until openxlings/xlings#524 Pinning to the current stable 2026.8.10.1 turned every Linux job red at the same place: a COLD home cannot install xim:gcc@16.1.0 at all. Downloading xim:glibc@2.44 … integrity: cleaning incomplete install: …/xpkgs/xim-x-gcc/16.1.0 [gcc] failed: config hook failed [xim:xpkg]: glibc payload not found, but gcc needs it to rewrite its ELF interpreter away from the build machine's path. install it first: xlings install xim:glibc@2.39 The version it installs and the version the hook asks for disagree, and gcc.lua declares `xim:glibc@>=2.39` so resolving 2.44 is legal — the failure is that the consumer's config hook cannot find the dependency payload that was just fetched. A/B on the same workflow, same runner image, both with a confirmed cache miss, so this is not a long-standing cold-start bug that warm caches were hiding: 2026.8.9.2 cold → gcc installs (run 31317627461) 2026.8.10.1 cold → the failure above (run 31335075557, 4/4 Linux jobs) Nothing in mcpp can route around it: it is upstream install ordering or store-root lookup, and it breaks every new machine, every cold CI cache and every fresh install. Reported with the evidence as openxlings/xlings#524; the pin moves once that ships. The release version stays 2026.8.10.1 — it is a date, and this is a different project's version that happens to share the string. * docs: record the completion design and what executing it actually found The design that drove this round of work, plus an appendix of outcomes written after the fact — including the two places reality diverged from the plan. D2 resolved as "proven, keep", but for a different reason than the commit it was defending claimed: not a Windows short-name alias, but that anchoring must not relocate an index out of the tree the workspace author addressed. D5's invariant turned out to already hold; it simply had no test. Two things arrived unplanned: #401, and the discovery that every identity/index-routing e2e was gated behind a capability Windows does not have — so the platform where path semantics differ was the one platform never asserting them. And main line C's bottleneck moved. The migration window took openxlings/xlings#521 off the critical path as intended, but pinning to the newest xlings put a harder one in its place: a cold home cannot install gcc at all (openxlings/xlings#524). Recorded with the A/B, because the lesson generalizes — "pin to latest" is an action that needs verifying, not a text substitution, and it only surfaced here because changing the pin also changed the CI cache key. * test(e2e): 166 asserted the mechanism, not the capability 166 exists for mcpp#291 and had two halves. The negative half — a plain binary must not be handed the private glibc — is still exactly right and is unchanged. The positive half required the opposite in the presence of a dlopen-reachable dependency: the payload dir HAD to be on LD_LIBRARY_PATH, "because dlopen'd libraries do not consult the executable's RUNPATH". The guard was right to exist. It stopped anyone from "fixing" #291 by deleting the entry and quietly breaking dlopen. But it pinned the mechanism rather than the capability, and the mechanism's premise was false: a dlopen() performed by the executable does consult the executable's DT_RUNPATH, and the link model already puts the payload glibc there — the artifact's RUNPATH is byte-identical with and without the environment entry. So the entry bought nothing while reaching every descendant process, which is how it became mcpp#401. The capability it was protecting now lives in 208, which asserts the dlopen actually resolves. 166 keeps the rule it is named for and now applies it to both shapes, plus one new assertion: the project's own [runtime] library_dirs entry must still be there. Only the private libc is binary-scoped; dropping ordinary runtime dirs too would be a different regression that the negative assertion alone would not catch. Full local Linux e2e on this HEAD: 202 passed, 0 failed, 8 skipped. --------- Co-authored-by: speak-agent <x.d2learn.org@gmail.com> Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>
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.
概要
ftxui@6.1.9精确声明为compat.ftxuigtest@1.15.2精确声明为compat.gtestmcpp.lock,使 FTXUI 和传递 Lua C 库保留 canonical package identity原因
mcpp-community/mcpp#400 的 exact-selector 契约明确规定:省略命名空间只等于
mcpplibs,不再按短名称猜测另一个命名空间。xlings 仍使用裸ftxui和gtest,因此新 mcpp 会正确尝试(mcpplibs, ftxui)/(mcpplibs, gtest),并在进入平台专属验证前停止。不可变
mcpplibs.capi.lua@0.0.3中的传递裸 Lua 依赖已经由 mcpplibs/mcpp-index#197 用 index-side Form-B bridge 精确声明为compat.lua@5.4.7;该 PR 已正常合并,10/10 CI 通过。验证
compat.ftxui、compat.gtest、mcpplibs.capi.lua和精确compat.luaDraft 边界
2026.8.9.2不包含本修复;合并后需要独立 version bump、全平台 release CI 和新稳定 releaseFixes #520
Related: mcpp-community/mcpp#400, mcpplibs/mcpp-index#197