From 4859c7ebc02e1d98db1f06b63a3e12072baee100 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Sat, 17 Oct 2026 12:15:20 +0900 Subject: [PATCH 1/4] 2026.10.17.1: an upgrade does not replace installed payloads inside a build; a dangling record does not fail every build; doctor reads, it does not change MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What changes - **Intent (D3).** `Fetcher::resolve_xpkg_path` takes a `PayloadIntent`: `Use` (build, run, test, pack, the runtime, host tools) takes a payload the provider calls usable and installs or replaces only one that is not; `Provision` (`mcpp toolchain install`) takes the index's current packaging; `Locate` never installs. A replacement says why (`Updating llvm@23.1.3 packaging revision 0 → 1 (revision 0 is below the index's floor 1)`). - **Failure (D5).** By the provider's error code: a deterministic refusal is neither retried through the command line nor followed by an index refresh, and its hint does not say retry; only `E_NOT_FOUND` refreshes, with a reason that is true (`could not be installed; the index may predate it`). A failed replacement of a usable payload names the revision still in use. - **Currency where a person looks (D6).** `mcpp index update` lists installed payloads the refreshed index packages anew; `mcpp toolchain list` marks them (JSON `installedRevision` / `availableRevision`). - **doctor (D7).** Read-only: one offline `prepare_build`; the toolchain it reports is what a build here uses (the configured default outside a package) with its payload's revision; a build that would install first is reported as that; mcpp's package home is summarized from xlings `binding_state`. - **Identity (D8).** A toolchain payload's revision above 0 enters the driver identity (`payload-revision: `). - **xlings 2026.10.17.1** (openxlings/xlings#678): `payload_status` reports `complete` / `usable` / `current` apart; `unmet()` requires `payload_currency` and `binding_state`; `scope_repaired` events are shown (`Repaired c++@16.1.0 in mcpp's package home had no registration; deactivated`). - **CI (D9).** `ci-upgrade.yml`: the previous release (read from GitHub at run time) builds a home; this commit's mcpp, laid out as a release, runs in it on Linux x86_64/aarch64, macOS and Windows; Linux adds an old-index variant (xim-pkgindex of 2026-10-07) and a dangling-record variant. ## Why After `xlings self update && xlings update mcpp -y`, a user's every `mcpp new && mcpp run` failed: `install of 'xim:llvm@23.1.3' failed ... sysroot update refused before metadata changed: c++@16.1.0: installed asset registration missing`. 2026.10.16.1 folded "is the packaging revision the index's" into "is the payload complete", and mcpp treated "not complete" as "not installed" inside the build; the replacement then met a dangling record of another package and failed on every run, while `mcpp self doctor` started the same replacement and reported `ok`. Measured on probe PR #814: a home of the previous release downloads gcc (101.7 MB), glibc (36.7 MB) and llvm on its first run after the upgrade, and the reported home fails with rc 101 on every run on x86_64 and aarch64. Design: .agents/docs/2026-10-17-upgrade-continuity-design.md. ## Tests - unit: `PmPayloadIntent.*` (the intent × usable × current matrix), `PmInstallFailurePolicy.*` (error code → refresh / retry). - e2e 918: a toolchain payload re-recorded at another revision builds with no install call, `toolchain list` offers the other packaging, doctor installs nothing. Fails on 2026.10.16.3 (it downloads glibc, binutils, linux-headers and fails). - ci-upgrade lane, run locally before push: orphan (the reported home) passes -- first run no download, `toolchain install` replaces llvm and reports the repaired record; snapshot passes -- `index update` names llvm 0 → 1 and glibc 2 → 4, no run downloads. Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com> --- .../2026-10-17-upgrade-continuity-design.md | 266 +++++++++++++++++ .agents/docs/README.md | 4 +- .github/actions/bootstrap-mcpp/action.yml | 2 +- .github/actions/setup-macos-llvm/action.yml | 2 +- .github/tools/upgrade_lane.sh | 197 +++++++++++++ .github/workflows/bootstrap-macos.yml | 2 +- .github/workflows/ci-bootstrap.yml | 6 +- .github/workflows/ci-fresh-install.yml | 6 +- .github/workflows/ci-linux-e2e.yml | 2 +- .github/workflows/ci-upgrade.yml | 64 +++++ .github/workflows/ci.yml | 6 + .github/workflows/cross-build-test.yml | 4 +- .github/workflows/release.yml | 14 +- CHANGELOG.md | 50 ++++ docs/09-commands-by-scenario.md | 12 +- docs/20-toolchains.md | 25 +- docs/specs/package-identity.md | 33 ++- docs/specs/toolchain-management.md | 18 +- docs/zh/09-commands-by-scenario.md | 9 +- docs/zh/20-toolchains.md | 19 +- mcpp.toml | 2 +- modules/versioning/src/version.cppm | 2 +- src/build/prepare/plan.cpp | 2 +- src/build/prepare/toolchain.cpp | 12 +- src/build/prepare/toolchain_env.cpp | 2 +- src/build/prepare/xlings.cpp | 2 +- src/doctor.cppm | 138 ++++++++- src/fetcher.cppm | 1 + src/fetcher/progress.cppm | 15 + src/pm/index_management.cppm | 12 + src/pm/package_fetcher.cppm | 267 ++++++++++++------ src/pm/refresh_policy.cppm | 22 +- src/provider/provider.cppm | 16 +- src/provider/select.cppm | 24 ++ src/provider/xlings/provider.cppm | 3 + src/provider/xlings/xlings.cppm | 16 +- src/toolchain/detect.cppm | 10 + src/toolchain/lifecycle.cppm | 50 +++- tests/e2e/52_local_path_namespaced_index.sh | 2 +- .../e2e/58_preinstall_mcpp_deps_for_hooks.sh | 2 +- tests/e2e/60_stale_xpkg_cache_reinstall.sh | 2 +- ...732_xlings_refresh_is_bounded_and_owned.sh | 2 +- ...nvelope_reports_observed_network_access.sh | 2 +- ..._refresh_false_governs_first_index_sync.sh | 2 +- .../812_an_index_floor_is_a_closing_tip.sh | 2 +- ...s_no_xlings_json_under_private_work_dir.sh | 2 +- .../916_a_home_from_before_keeps_working.sh | 8 +- ...an_older_packaging_is_used_not_replaced.sh | 101 +++++++ tests/unit/test_pm_package_fetcher.cpp | 63 +++++ upgrade-lane-logs/new-doctor.log | 34 +++ upgrade-lane-logs/new-index-update.log | 15 + upgrade-lane-logs/new-provision.log | 10 + upgrade-lane-logs/new-run-after-provision.log | 13 + upgrade-lane-logs/new-run-old-project.log | 4 + upgrade-lane-logs/new-run-on-old-index.log | 14 + upgrade-lane-logs/new-run1.log | 12 + upgrade-lane-logs/new-run2.log | 4 + upgrade-lane-logs/new-toolchain-list.log | 33 +++ upgrade-lane-logs/old-env.log | 21 ++ upgrade-lane-logs/old-run.log | 12 + upgrade-lane-logs/old-tc-gcc.log | 6 + upgrade-lane-logs/old-tc.log | 5 + 62 files changed, 1546 insertions(+), 162 deletions(-) create mode 100644 .agents/docs/2026-10-17-upgrade-continuity-design.md create mode 100644 .github/tools/upgrade_lane.sh create mode 100644 .github/workflows/ci-upgrade.yml create mode 100755 tests/e2e/918_an_older_packaging_is_used_not_replaced.sh create mode 100644 upgrade-lane-logs/new-doctor.log create mode 100644 upgrade-lane-logs/new-index-update.log create mode 100644 upgrade-lane-logs/new-provision.log create mode 100644 upgrade-lane-logs/new-run-after-provision.log create mode 100644 upgrade-lane-logs/new-run-old-project.log create mode 100644 upgrade-lane-logs/new-run-on-old-index.log create mode 100644 upgrade-lane-logs/new-run1.log create mode 100644 upgrade-lane-logs/new-run2.log create mode 100644 upgrade-lane-logs/new-toolchain-list.log create mode 100644 upgrade-lane-logs/old-env.log create mode 100644 upgrade-lane-logs/old-run.log create mode 100644 upgrade-lane-logs/old-tc-gcc.log create mode 100644 upgrade-lane-logs/old-tc.log diff --git a/.agents/docs/2026-10-17-upgrade-continuity-design.md b/.agents/docs/2026-10-17-upgrade-continuity-design.md new file mode 100644 index 000000000..ff5fc91a3 --- /dev/null +++ b/.agents/docs/2026-10-17-upgrade-continuity-design.md @@ -0,0 +1,266 @@ +--- +subject: design +status: active +--- + +# 升级的连续性:打包修订不在使用路径上替换载荷、替换失败不拆掉可用状态、home 中的悬空记录自愈、真实的升级 lane + +- 日期:2026-10-17。状态:修订 2,O1、O2 按推荐项确定,实施中(§11)。 +- 起因:用户报告。`xlings self update && xlings update mcpp -y` 之后,`mcpp new hhss && mcpp run` 每次失败: + `toolchain 'llvm@23.1.3': install of 'xim:llvm@23.1.3' failed … sysroot update refused before metadata changed: + c++@16.1.0: installed asset registration missing`;`mcpp self doctor` 不报错。 +- 基线:`main` `f4816141`(mcpp 2026.10.16.3,内置 xlings 2026.10.16.2);openxlings/xlings `03a2002c` + (2026.10.16.2);openxlings/xim-pkgindex `5a70d12`。 +- 证据:本地复现(Linux x86_64);probe PR #814 两轮运行 + [38989869868](https://github.com/mcpp-community/mcpp/actions/runs/38989869868)(11 个 job)、 + [38990143701](https://github.com/mcpp-community/mcpp/actions/runs/38990143701)(3 个 job),见 §2。 +- 范围:每个仓库一个 PR:libxpkg(L1,`floor` 的解析)、xim-pkgindex(I1)、xlings(X1–X4,一次发布)、mcpp。 + 发布顺序 libxpkg → xlings → mcpp;xim-pkgindex 的规范与检查与之并行,最后合入。不分阶段,不留临时方案。 + +## 0. 结论 + +升级之所以不是无感的,是 2026.10.16.1 把「打包修订是否最新」并进了「载荷是否完整」,而 mcpp 在使用路径 +(build/run/test/doctor)上把「不完整」当作「未安装」同步重装。于是 index 中任何一次修订号上调,都会在用户 +下一次 `mcpp run` 时变成一次替换:在一致的 home 中是一次多余的下载(gcc 101.7 MB、llvm、glibc 36.7 MB), +在带一条悬空记录的 home 中是每次都失败,而 xlings 保留下来的可用载荷被 mcpp 丢弃。CI 全部从空 home 起步, +装出来的修订号永远等于索引的修订号,这条路径从未被执行。 + +修复是四个不变量,各落到唯一的位置: + +1. **使用不变更**(P1):使用命令不替换一个可用的载荷。修订号不同是「有更新的打包」,不是「不完整」; + 只有低于索引声明的可用下限(`floor`)时才必须替换。 +2. **替换失败不降级**(P3):替换是事务;失败时旧载荷仍可用就继续用,并如实报告。 +3. **一个事务只被自己的问题阻止**(P4):工作区中引用不存在注册的记录是垃圾,xlings 在每个元数据事务开始时 + 以 doctor 的同一个函数规范化并报告。 +4. **诊断只读且如实**(P5),**升级由真实的上一发布版验证**(P6)。 + +## 1. 现象与链条 + +用户机器上的完整链条(代码位置以基线为准): + +1. mcpp 2026.10.16.3 第一次运行,把内置 xlings 从 2026.10.10.2 换成钉住的 2026.10.16.2。 +2. `Fetcher::resolve_xpkg_path`(`src/pm/package_fetcher.cppm`)的 `complete_here()` 问提供者 + `payload_status`。xlings 2026.10.16.1 起,`payload_state_()`(xlings `src/core/xim/commands.cpp`)在 + `payload_revision_verdict(...).stale` 时返回 `complete=false`(xlings #676,即 10-16 设计的 X4)。 + llvm 23.1.3 在索引中为 revision 1(xim-pkgindex #938,2026-10-08),载荷记录为 0。 +3. `complete_here()` 失败,mcpp 在 `mcpp run` 内调用安装。xlings 计划为 `reinstall: recipe revision 1, + installed revision 0`,下载、解包、把旧载荷移到一边。 +4. 元数据事务的投影预检 `prepare_projection_change`(xlings `src/core/xvm/materialize.cpp`)对被重新配置的 + target `c++` 收集期望的投影:`c++` 的 active 是 `16.1.0`(gcc 的),版本库里没有这条注册 → + `collect_claims(..., ClaimSource::Present)` 返回 `installed asset registration missing`。事务拒绝, + 旧载荷放回(`kept the previous payload`)。 +5. mcpp 再次 `complete_here()`:仍是 `complete=false`(修订号仍旧)→ 报错。失败后的索引刷新打印 + `Refreshing package index — xim:llvm@23.1.3 not found locally (one-time)`:载荷与描述文件都在本地, + 这句话的理由是错的,而且每过一次 debounce 窗口就出现一次。 +6. `mcpp self doctor` 的 runtime capabilities 检查调用 `prepare_build`,又发起一次同样的重装;失败被报告为 + `ok (run inside a package to check its runtime capabilities)`,而用户就在包里。它的 toolchain 一节检查的是 + PATH 上的 `~/.xlings/subos/default/bin/g++`,不是 mcpp 为这个项目解析的 llvm。 + +升级前为什么没事:v2026.10.11.1 的 `resolve_xpkg_path` 只看 `.mcpp_ok` 标记,不看修订号;xlings 2026.10.10.2 的 +`payload_status` 也不看。10-16 设计以「mcpp 不写载荷」取消标记、改由提供者回答完整性(§7.1、X4), +`docs/specs/package-identity.md` §10.3 第 3 条把「修订号与索引条目一致」写进了完整性。该设计的兼容一节写的是 +「已安装的 mcpp 行为不变」,实现与之相反。 + +用户 home 的状态(诊断脚本实测):版本库在 `.xlings.json`(无 `data/versions.json`);`xim-gnu-gcc` 只剩 +`15.1.0`,`c++` 只剩 `15.1.0 / 22.1.8 / 23.1.3`,工作区却以 `16.1.0` 为 active;工作区有而版本库没有的记录 +529 条;指向 `/tmp/claude-1000/fakehome/...` 的条目 1827 个(来源见 §3 F6)。 + +## 2. 实测(probe #814) + +上一发布版 2026.10.11.1(自带 xlings 2026.10.10.2)建立 home,装 llvm@23.1.3(Linux 另装 gcc@16.1.0), +`mcpp new && mcpp run`;换成 2026.10.16.3,在同一 home 中 `mcpp new && mcpp run` 若干次。 + +| 变体 | 平台 | 升级后第一次 run | 结论 | +|---|---|---|---| +| plain(今天的索引) | x86_64、aarch64、macOS、Windows | rc 0,1 s,无下载 | 修订号一致时升级无感 | +| snapshot(xim-pkgindex `ebf1fbb`,2026-10-07,真实的旧修订载荷) | x86_64 | 旧索引仍在时:rc 0,1–2 s,无替换,**用 revision 0 的 llvm 构建并运行成功**;`index update` 后:llvm 项目下载 glibc 36.7 MB 并替换 llvm(19–24 s),gcc 项目下载 gcc 16.1.0-r1 101.7 MB 与 binutils 2.42.1 | 修订号上调在使用路径上变成替换与下载 | +| stamp(载荷记录的修订号与索引不同) | x86_64、aarch64、macOS、Windows | rc 0,12–30 s,重新下载 llvm(Linux 另有 glibc、gcc) | 机制跨平台 | +| orphan(stamp + gcc 的 `c++@16.1.0` 为 active 且版本库无此注册,即用户 home 的形状) | x86_64、aarch64 | **rc 101,每次**(22–45 s 后失败),与用户报错逐字一致;`mcpp self doctor`(项目内)发起安装且返回 0 | 一条悬空记录使替换永久失败;doctor 改变状态且不报告 | + +附带的实测事实: + +- llvm revision 0 与 1 的 cfg 唯一差别是 glibc 库目录(`glibc/2.44.3/lib` → `lib64`);glibc revision 4 的载荷 + 同时有 `lib` 与 `lib64 -> lib`,所以 revision 0 的 llvm 在 revision 4 的 glibc 上仍然自洽。头文件搜索列表相同。 +- orphan 变体中,gcc 项目的运行替换了 gcc(修订 0 → 1),gcc 的 `config()` 重新注册了 `c++@16.1.0`,悬空记录 + 随之消失,此后 llvm 的替换成功。即:**重新配置记录的拥有者能修复它,但 xlings 不会主动这样做**。 +- `xlings self doctor` 对 orphan home 报 4 条 binding state 发现(`xvm-active-version-missing` 等), + 其中给出的 `run xlings install c++` 把 target 当成了包名。 +- 修订号的波及面(xim-pkgindex `5a70d12`,Linux):llvm 20.1.7/22.1.8/23.1.3 revision 1(10-08,归档不变, + cfg 随 glibc 布局);gcc 16.1.0 revision 1(10-12,`16.1.0-r1` 新归档,去掉硬链接,为 Android/FAT); + glibc 2.44.3 revision 4(去掉硬链接,为 Android);binutils revision 2。每个在这些日期之前装过的 Linux home + 都会在升级后的第一次 run 中替换它们。 +- macOS 上 `mcpp self doctor` 的 toolchain 一节报告 `/usr/bin/g++`(Apple clang 17),并因一条 Xcode SDK 的 + 警告返回 1。 + +## 3. 根因 + +### F1 使用路径替换载荷 + +完整性(载荷在、有内容、没有失败标记)与时效(安装的修订号是否等于索引当前的修订号)是两个独立的事实, +2026.10.16.1 把后者并进了前者。使用路径只该关心前者:一个完整的载荷,连同它安装时解析出的闭包 +(载荷中的 `.xlings-resolution.json`),是自洽的,可以使用。修订号上调的原因多种多样:为 Android 去掉硬链接、 +随依赖的布局重新生成 cfg、修复一个所有人都会遇到的缺陷。只有最后一种要求已装的载荷必须替换,而这一点只有 +写描述文件的人知道。今天的数据模型无法表达它,于是 xlings 和 mcpp 只能对所有修订号一律替换。 + +### F2 替换失败丢弃可用载荷 + +xlings 的替换是事务(`PayloadReplacement`,失败放回旧载荷),但 mcpp 在失败后仍以 `complete` 判断,旧载荷 +因修订号旧而被判为不完整,于是报错。提示固定为「retry」,而这次失败是确定性的。失败后的索引刷新沿用 +`DescriptorMiss` 的理由(`not found locally (one-time)`),对一个本地存在的描述文件既不真也不是一次性的。 + +### F3 一个事务被别的包的悬空记录阻止 + +投影预检对「被重新配置的 target 的期望投影」使用 `ClaimSource::Present`,遇到没有注册的 active 版本就拒绝。 +这条记录属于 gcc,与 llvm 的事务无关;它本来就是坏的(`c++` 的 shim 无处可派发),llvm 的替换只是第一个读到它。 +xlings 的 doctor 已经认得它(`xvm-active-version-missing`),`--fix` 也有处理悬空引用的代码(`repair_state_`), +但安装路径没有用同一个函数。结果是一个与本次操作无关的旧问题把这次操作永久卡住。 + +### F4 诊断命令改变状态且掩盖失败 + +`mcpp self doctor` 的 runtime capabilities 检查调用完整的 `prepare_build`,因此会安装与替换;它把准备失败 +一律说成「不在包里」。toolchain 一节用 `mcpp::toolchain::detect()` 检查 PATH 上的编译器,不是 mcpp 为当前 +上下文解析的工具链,所以在工具链坏掉时报 `ok`。 + +### F5 CI 看不见升级 + +mcpp 的 fresh-install 与 e2e 每次新建 `MCPP_HOME`,从当前索引安装。`916_a_home_from_before_keeps_working` +手写一个只有假 mcpplibs 包的 home,不含工具链、不用上一版的 xlings、不跑 `mcpp new && mcpp run`;10-16 设计 +§15 写的「2026.10.11.1 建立的 home」在落地时变成了伪造的 home。xlings 的 `install_revision_test.sh` 是干净 home +中单个 hook-only 包,没有多个包共享一个 target,也没有不一致的记录。 + +### F6 用户 home 的污染 + +1827 个条目与 doctor 中数百条悬空链接指向 `/tmp/claude-1000/fakehome/.mcpp/registry/...`,即曾有以假 HOME +运行的测试进程写入了真实的 `~/.mcpp/registry`。来源进程未定位。产品侧的要求由 F3 的自愈覆盖(坏记录不再阻止 +操作);这台机器的 home 另行修复,不属于本设计的代码改动。 + +## 4. 原则 + +- **P1 使用不变更。** build、run、test、pack、doctor 等使用命令不替换一个可用的载荷。可用 = 完整,且安装的修订号 + 不低于索引条目声明的 `floor`。 +- **P2 身份与可用性分离。** 载荷的身份仍是 `(namespace, name, version, revision)`;构建记录的是**实际使用的** + 身份。索引的当前修订号决定新安装得到什么,不决定已安装的是否可用。 +- **P3 替换是事务,失败不降级。** 替换只发生在显式的供给命令中,或可用性要求它时;失败后旧载荷仍可用就继续用。 +- **P4 版本库是 home 的权威。** 工作区中引用不存在注册的记录是垃圾;任何元数据事务开始时以 doctor 的同一个函数 + 规范化,并说出做了什么。一个事务只能被它自己的问题阻止。 +- **P5 诊断只读且如实。** doctor 不安装、不替换;它报告 mcpp 实际会用的东西,失败就说失败。 +- **P6 升级由真实的上一发布版验证。** 不伪造旧 home。 + +## 5. 决策 + +| # | 决策 | 理由 | +|---|---|---| +| D1 | 提供者契约分开报告完整性与时效:`payload_status` 与 `install_targets` 的每个载荷给出 `complete`(只表示完整性)、`revision`(索引当前)、`installed_revision`、`floor`、`usable`(`complete` 且 `installed_revision ≥ floor`;未记录修订号的载荷按可用)、`current`(`installed_revision == revision`)。xlings 以特性 `payload_currency` 宣告;mcpp 的 `unmet()` 要求它 | F1:一个字段承载两件事,调用方就无法区分「旧」与「坏」。判定由提供者给出,mcpp 不复刻版本文法与修订号规则(10-16 设计的端口边界) | +| D2 | 索引的版本条目可声明 `floor`(最低可用修订号,`0 ≤ floor ≤ revision`,缺省 0)。修订号上调时由描述文件作者决定已装的旧修订是否仍可用;现有条目不声明,即全部可用 | 只有作者知道一次修订是「为 Android 去掉硬链接」还是「所有人都会踩的缺陷」。§2 实测本次的 llvm、gcc、glibc 修订对已装载荷都不要求替换 | +| D3 | mcpp 的载荷解析分两种意图:**使用**(build/run/test/pack/doctor、运行库 glibc 的 `ensure_declared_runtime`、宿主工具引导)与**供给**(`mcpp toolchain install`)。使用:`usable` 即用;不可用(缺失、不完整、低于 floor)才安装或替换,并以状态行说明原因(`Updating llvm@23.1.3 packaging revision 0 → 1 (below the index's floor 1)`)。供给:`usable && !current` 时替换为当前修订(`Updating … packaging revision 0 → 1`),`current` 时说已安装 | P1、P3。用户显式要求的才是升级;`mcpp toolchain install ` 本来就是「让这个工具链就绪」的命令,不新增动词 | +| D4 | 库依赖(mcpplibs 等,#524 A)保持「描述文件与载荷同修订」:修订号不同按未安装处理,在构建中替换。它们获得 D5 的失败语义与 X2 的自愈 | 库依赖的描述文件在构建时被读(链接面、flags,#524 的实例),载荷必须与之一致;工具链与运行库的构建输入是载荷本身及其安装时的闭包,不读描述文件。两者语义不同,各自正确 | +| D5 | 替换失败:提供者的替换是事务,原载荷保留。只有供给意图会替换一个仍 usable 的载荷,它失败时报错,并说明仍在使用的修订号;使用意图只替换不 usable 的载荷,失败时没有可退回的载荷,报错。(修订 1 写的「使用意图继续用旧载荷」在使用意图下不会出现:使用意图对 usable 的载荷不发起替换。)提示由错误码决定:确定性的拒绝(`E_INTERNAL`、`E_PERMISSION`、`E_DISK_FULL`、`E_CANCELLED`)不说 retry、不经命令行重试、不刷新索引;只有 `E_NOT_FOUND`(以及没有错误码的旧路径)触发失败后的索引刷新,理由文本为「could not be installed; the index may predate it」 | F2。诊断分级:使用意图下为命令失败并给出原因,供给意图下为命令失败并说明仍在使用的修订号 | +| D6 | 时效的呈现不在构建中:`mcpp index update` 结束时列出非当前的已装载荷,`mcpp toolchain list` 标出 `packaging revision 1 available`(JSON 为 `installedRevision`/`availableRevision`),doctor 同样报告;构建中的自动刷新只在 verbose 日志中记录 | 无感:构建输出不随索引变化而变化;用户在显式改变索引的那一刻、在查看工具链时看到它 | +| D7 | mcpp doctor 只读且如实:runtime capabilities 以不供给的意图准备(解析失败即报告错误与原因);toolchain 一节报告 mcpp 为当前上下文解析的工具链(项目 → 配置默认),附载荷状态(`usable/current`、两个修订号)与 xlings home 的一致性摘要(悬空记录数,来自 X3);PATH 上的编译器不再作为「toolchain」报告 | F4、P5 | +| D8 | 构建身份记录实际使用的修订号:运行时契约(`package-identity` §10.2 的 `revision=`)本来就读载荷的安装记录;工具链的驱动身份在修订号大于 0 时追加 `payload-revision: `(同样读安装记录),因而进入指纹与缓存键 | P2:同一项目在两个修订的载荷上构建,产物与缓存不得混用。代价:修订号大于 0 的工具链在升级后重建一次缓存,CHANGELOG 写明 | +| D9 | CI 增加真实升级 lane,§7 | P6、F5 | + +开放问题(维护者选择,不是推迟的范围): + +- **O1** 悬空 active 的处理(X2):推荐「解除该 target 的 active,并报告 `c++: active version 16.1.0 had no + registration; deactivated (xlings use c++ )`」;另一选项是改选该 target 另一个已注册的版本。推荐前者, + 因为改选会悄悄改变用户 shell 中 `c++` 指向的编译器。 +- **O2** `floor` 放在索引(推荐,D2)还是 mcpp 内置表。放在 mcpp 只能表达「mcpp 需要」,不能表达「这个包的这次 + 修订修了所有人的缺陷」;前者在需要时也可以由 mcpp 对索引提出。 + +## 6. 生态的需求 + +libxpkg(recipe 的解析者,`PlatformResource::revision` 在此): + +- **L1** 版本条目解析 `floor`(非负整数,高于 `revision` 时按 `revision` 读);`ResolvedResource` 随 `ref` 报告解析到的条目的 + `floor`;版本 0.0.65。 + +xim-pkgindex: + +- **I1** V2 规范为版本条目增加 `floor`;`.github/scripts/check-revision.lua` 校验 `0 ≤ floor ≤ revision`、位置 + (版本项上,不在 alias 与按架构的表内),`floor` 不属于资源。现有条目不需要改动。(修订 1 曾要求「不改 + `revision` 不得提高 `floor`」;实施时删去:作者事后才确认某个已发布修订修复了所有已装载荷的缺陷时,把 `floor` + 提到这个修订是正当的。) + +xlings(一次发布,mcpp 的 PR 把内置 xlings 钉到它): + +- **X1** `payload_status`、`install_targets` 按 D1 报告;特性 `payload_currency`;协议版本上调。xlings 的安装计划 + 不变:被请求的目标与计划中的依赖都按 `current` 替换。一次安装是供给动作,新载荷是对着索引当前的闭包写成的, + 它的依赖必须同为当前修订(llvm revision 1 的 cfg 指向 glibc revision 4 的 `lib64`,配上 revision 3 的 glibc + 就不自洽)。使用路径不受影响:按 P1,使用意图对一个 usable 的载荷根本不发起安装。(修订 1 曾写「依赖按 + usable 判断」,实施前的审读发现上述不自洽,改为此条。) +- **X2** 元数据事务(`process_xvm_operations_`、`publish_removal_state_`)在计算投影之前,以 doctor 的同一个函数 + 规范化候选工作区:引用不存在注册的 `installed[]` 条目删除,悬空 active 按 O1 处理;规范化随事务提交,并以 + 事件报告(mcpp 原样显示)。投影预检只对本事务注册与移除的成员严格。 +- **X3** doctor:悬空记录的检测与 X2 的规范化是同一个函数;remedy 文本不再把 target 当包名(`xlings install c++`), + 拥有者可知时给出 `xlings install :@`,否则给出 `xlings use`;以 `interface` 提供 + 一致性摘要,供 mcpp doctor 读取。 +- **X4** 测试:两个包注册同一 target、删除一方的注册后,另一方的安装与替换成功并报告规范化;`payload_status` + 的六个字段与 `floor` 的组合;依赖中的旧修订不被替换。新测试在 2026.10.16.2 上必须失败。 + +## 7. 测试 + +mcpp: + +- **单元(provider 端口的假实现)**:`(complete, usable, current, 意图)` → 动作的完整矩阵(用、替换、安装、报错、 + 降级继续);替换失败且旧载荷 usable 时使用意图继续、供给意图报错;错误码 → 提示与是否刷新的对应。 +- **e2e(本地夹具,离线)**:以本地索引夹具与载荷记录,断言旧修订的工具链被使用、构建无下载、`mcpp toolchain + list` 标出可用的新修订、`mcpp toolchain install` 替换它;doctor 在项目中不发起任何安装(以提供者调用日志断言)。 +- **CI lane `ci-upgrade.yml`**(Linux x86_64、aarch64、macOS、Windows):上一发布版(从 GitHub release 取,版本由 + `mcpp-release.json` 与本仓库版本推出,每次发布自动前移)建立 home、装工具链、`mcpp new && mcpp run`;换成本 PR + 构建的 mcpp,断言 `mcpp new && mcpp run` 成功、零下载、无替换。Linux x86_64 另跑 snapshot(旧修订载荷)与 + orphan(悬空记录)两个变体:前者断言零下载且 `index update` 报告非当前载荷,后者断言成功并出现规范化报告。 +- 新增测试在 2026.10.16.3 上必须失败(snapshot 与 orphan 变体已由 probe 证明会失败)。 +- `916` 保留为 index 迁移的 e2e,其注释不再声称覆盖「旧 home 继续可用」;升级由上面的 lane 覆盖。 + +## 8. 规范与文档 + +- `docs/specs/package-identity.md`:§10.2 改写 mcpp 侧对修订号的三处使用(工具链与运行库按 usable,库依赖按 + 一致,契约记录实际使用的修订号);§10.3 第 3 条把「修订号与索引条目一致」移出完整性,加入 usable 与 current + 的定义;变更记录与版本。 +- `docs/specs/toolchain-management.md`:`mcpp toolchain install` 对已装旧修订的语义;`toolchain list` 的时效列。 +- `docs/` 与 `docs/zh/` 中工具链管理与 doctor 的章节:升级、修订号与 `floor` 的用户可见行为。 + +## 9. 提交顺序(一个 PR 内) + +1. 内置 xlings 钉到 X1–X4 的发布;`unmet()` 要求 `payload_currency`;provider 端口的 `PayloadStatus` 增加 D1 字段。 +2. 解析意图(D3)与失败语义(D5):`resolve_xpkg_path` 的意图参数,使用与供给的调用点逐一标明; + `ensure_declared_runtime` 与宿主工具引导走使用意图;单元测试。 +3. D8:工具链指纹与运行时契约取 `installed_revision`。 +4. D6:`index update` 的非当前载荷报告、`toolchain list` 的时效列。 +5. D7:doctor 只读与如实;读取 X3 的一致性摘要。 +6. 失败后刷新的理由与条件(D5)。 +7. e2e 与 `ci-upgrade.yml`;规范、双语文档、CHANGELOG。 + +## 10. 这台机器 + +用户的 home 需要单独修复,不依赖本设计落地:它的版本库与工作区有 529 条不一致,且有 1827 个条目指向不存在的 +假 HOME。probe 实测「重新配置悬空记录的拥有者」能修复 `c++@16.1.0` 这一条;整体上更稳妥的是在备份后重建 +`~/.mcpp/registry`。具体操作由维护者决定。 + +## 11. 实施计划与依赖 + +``` +L1 libxpkg floor ──► X1 payload_status/install_targets 六字段 ──┐ +I1 规范与 check-revision(并行) ├─► xlings 发布 2026.10.17.1 ──► M1 钉住与 unmet() +X2 事务规范化(plan_scope_reference_repair)──┬─► X4 测试 ──────┘ │ +X3 doctor 共用函数、remedy、system_status 的 binding_state ─┘ ▼ + M2 意图与失败语义(D3/D5)──► M3 D8 ──► M4 D6 ──► M5 D7 ──► M6 测试与 ci-upgrade + │ + 规范、双语文档、CHANGELOG ◄┘ +``` + +| 任务 | 仓库 | 内容 | 依赖 | 验证 | +|---|---|---|---|---| +| L1 | libxpkg | `floor` 的解析与随 `ref` 解析;夹具与单元测试;0.0.65 | — | `mcpp test` | +| I1 | xim-pkgindex | V2 规范的 `floor`;`check-revision.lua` 的范围与「不改 revision 不得抬 floor」;双语文档 | — | 本地对 `main` 跑检查脚本 | +| X1 | xlings | `payload_state_` 的 `complete` 只表示完整性;六字段;`payload_currency`;协议 1.11 | L1 | 单元与 e2e | +| X2 | xlings | 纯函数 `plan_scope_reference_repair`;`process_xvm_operations_`、`publish_removal_state_` 先规范化候选工作区并报告(`scope_repaired` 数据事件与日志);其他 subos 的投影用规范化后的副本 | — | 单元、e2e(两包共享 target) | +| X3 | xlings | doctor 的当前 subos 修复调用同一纯函数(含 `installed[]`);`xvm-active-version-missing` 的 remedy;`system_status` 输出 `binding_state` 摘要,特性 `binding_state` | X2 | 单元 | +| X4 | xlings | e2e:orphan 形状下替换成功并报告规范化;`payload_status` 字段与 `floor` 组合 | X1–X3 | 在 2026.10.16.2 上失败 | +| M1 | mcpp | 内置 xlings 2026.10.17.1;`unmet()` 要求 `payload_currency`;`PayloadStatus` 增字段 | xlings 发布 | 单元 | +| M2 | mcpp | `PayloadIntent{Locate, Use, Provision}`;调用点逐一标明;失败语义按错误码;失败后刷新只对 `E_NOT_FOUND` | M1 | 单元(假提供者) | +| M3 | mcpp | 工具链 `driverIdent` 记录载荷的 `installed_revision`(>0 时) | M2 | 单元 | +| M4 | mcpp | `index update` 结束时报告非当前载荷;`toolchain list` 的时效标注 | M1 | e2e | +| M5 | mcpp | doctor 只读:runtime capabilities 以 Locate 准备;toolchain 一节报告解析到的工具链与载荷状态、home 一致性摘要 | M2 | e2e(调用日志无安装) | +| M6 | mcpp | e2e(离线夹具);`ci-upgrade.yml`(4 平台 + snapshot/orphan);916 注释 | M1–M5 | CI;在 2026.10.16.3 上失败 | diff --git a/.agents/docs/README.md b/.agents/docs/README.md index de9396ca4..d039868d6 100644 --- a/.agents/docs/README.md +++ b/.agents/docs/README.md @@ -18,7 +18,7 @@ superseded_by: 2026-09-07-....md # when status is superseded --- ``` -332 records. +333 records. ## By subject @@ -31,6 +31,7 @@ Records that declare one. Everything else is listed by date below. ### design +- [升级的连续性:打包修订不在使用路径上替换载荷、替换失败不拆掉可用状态、home 中的悬空记录自愈、真实的升级 lane](2026-10-17-upgrade-continuity-design.md) — active - [一处来源,一种含义:工作空间的传递语义、生成输出、模块角色与首次运行(#786、#785、#778、#790)](2026-10-10-one-source-one-meaning-design.md) — landed - [2026.10.5.3 发布方案:32 位 x86 的架构词汇、资源编译器的识别与增量(#776 后续)](2026-10-06-windows-x86-arch-vocabulary-and-rc-follow-ups-design.md) — active - [下一个版本的发布方案:标准库模块、原生 MSVC LTO 与导出发现、共享库的链接配置、Windows 参数引号(#768 后续、#770、#771)](2026-10-05-std-module-pair-msvc-lto-and-export-discovery-design.md) — active @@ -129,6 +130,7 @@ Records that declare one. Everything else is listed by date below. ### 2026-10 +- [升级的连续性:打包修订不在使用路径上替换载荷、替换失败不拆掉可用状态、home 中的悬空记录自愈、真实的升级 lane](2026-10-17-upgrade-continuity-design.md) — active - [mcpp 对 xlings 的依赖:Provider 端口、归属与位置、上游复用、系统级安装、payload 不可变、文件系统能力与长路径(#809)](2026-10-16-xlings-provider-port-and-shared-store-design.md) — active - [构建依赖的一条管线:配置与产物两轴、包的实例、库接口、兼容系统与工作空间收尾(#797)](2026-10-11-build-dependencies-one-pipeline-design.md) — active - [一处来源,一种含义:工作空间的传递语义、生成输出、模块角色与首次运行(#786、#785、#778、#790)](2026-10-10-one-source-one-meaning-design.md) — landed diff --git a/.github/actions/bootstrap-mcpp/action.yml b/.github/actions/bootstrap-mcpp/action.yml index 6d5efab51..b6366ab53 100644 --- a/.github/actions/bootstrap-mcpp/action.yml +++ b/.github/actions/bootstrap-mcpp/action.yml @@ -33,7 +33,7 @@ inputs: # `package.name`, so one of the two was simply unreachable — and which one # depended on the machine, which is why CI failed on `compat:lua` on # Windows and `mcpplibs.capi:lua` on Linux. Never pin below that. - default: '2026.10.16.2' + default: '2026.10.17.1' outputs: sandbox-key: diff --git a/.github/actions/setup-macos-llvm/action.yml b/.github/actions/setup-macos-llvm/action.yml index 807df7ce2..e31d38365 100644 --- a/.github/actions/setup-macos-llvm/action.yml +++ b/.github/actions/setup-macos-llvm/action.yml @@ -15,7 +15,7 @@ inputs: # Floor imposed by the index, not a routine bump — see # .github/actions/bootstrap-mcpp/action.yml for why 0.4.69 is required # (two packages named `lua` in one repo need openxlings/xlings#381). - default: '2026.10.16.2' + default: '2026.10.17.1' image: description: > The runner label the job runs on (macos-15, xcode-27). It is part of the diff --git a/.github/tools/upgrade_lane.sh b/.github/tools/upgrade_lane.sh new file mode 100644 index 000000000..74f133154 --- /dev/null +++ b/.github/tools/upgrade_lane.sh @@ -0,0 +1,197 @@ +#!/usr/bin/env bash +# upgrade_lane.sh -- a home the previous +# release made keeps working under this commit's mcpp (design 2026-10-17 D9). +# +# Every other lane starts from an empty home, so the installed packaging +# revision always equals the index's and an upgrade is never executed. A user +# upgraded to 2026.10.16.3 and every `mcpp run` failed from then on; nothing +# in CI could see it. This lane builds the home with the PREVIOUS RELEASE -- +# the newest published release that is not this commit's version, read from +# GitHub, so it moves forward with every release -- and then runs this +# commit's mcpp in it, laid out as a release (bin/mcpp, registry/bin/xlings). +# +# variant: +# plain the previous release installs from today's index. +# snapshot the previous release installs from the xim index of 2026-10-07 +# (xim-pkgindex ebf1fbb): payloads genuinely of older packaging +# revisions (llvm revision 0, gcc 16.1.0 before -r1). +# orphan every payload recorded at another revision, and `c++` active at +# gcc's version whose registration is gone (a reported home). +# +# Asserted, every variant: the first `mcpp new && mcpp run` of the new mcpp +# succeeds and downloads nothing; a second run likewise; `mcpp self doctor` +# in the project installs nothing and names the toolchain the build uses. +# snapshot: `mcpp index update` names the newer packagings, the run after it +# still downloads nothing, and `mcpp toolchain list` offers the new one. +# orphan: `mcpp toolchain install` replaces the payload, and the dangling +# record is reported as repaired instead of refusing the replacement. +set -uo pipefail +VARIANT="${1:?variant}" +NEW_MCPP="${2:?new mcpp binary}" +NEW_XLINGS="${3:?new xlings binary}" +SNAPSHOT=ebf1fbb3417ec513b923279044433887b3af8850 +TC=llvm@23.1.3 + +unset MCPP_VENDORED_XLINGS MCPP MCPP_FRESH MCPP_BOOT XLINGS_HOME XLINGS_PROJECT_DIR XLINGS_ACTIVE_SUBOS +W="${RUNNER_TEMP:-/tmp}/upgrade-lane" +rm -rf "$W"; mkdir -p "$W" +export MCPP_HOME="$W/home" +REG="$MCPP_HOME/registry" +PY=$(command -v python3 || command -v python) +FAILED=0 +fail() { echo "::error::upgrade lane ($VARIANT): $*"; FAILED=1; } + +case "$(uname -s)-$(uname -m)" in + Linux-x86_64) PLAT=linux-x86_64; EXT=tar.gz; EXE= ;; + Linux-aarch64) PLAT=linux-aarch64; EXT=tar.gz; EXE= ;; + Darwin-arm64) PLAT=macosx-arm64; EXT=tar.gz; EXE= ;; + MINGW*|MSYS*) PLAT=windows-x86_64; EXT=zip; EXE=.exe ;; + *) echo "::error::unknown platform $(uname -a)"; exit 1 ;; +esac +IS_LINUX=0; [[ $PLAT == linux-* ]] && IS_LINUX=1 + +# ── the previous release ─────────────────────────────────────────────── +CURRENT=$(sed -n 's/^version *= *"\([^"]*\)".*/\1/p' mcpp.toml | head -1) +OLD_VER=$(curl -fsSL ${GH_TOKEN:+-H "Authorization: Bearer $GH_TOKEN"} \ + "https://api.github.com/repos/mcpp-community/mcpp/releases?per_page=20" \ + | "$PY" -c ' +import json, sys +current = sys.argv[1] +for r in json.load(sys.stdin): + v = r["tag_name"].lstrip("v") + if not r["draft"] and not r["prerelease"] and v != current: + print(v); break +' "$CURRENT") +case "$OLD_VER" in ''|*[!0-9.]*) echo "::error::no previous release resolved (got '$OLD_VER')"; exit 1 ;; esac +echo "previous release: $OLD_VER; this commit: $CURRENT ($PLAT, variant $VARIANT)" + +name="mcpp-$OLD_VER-$PLAT.$EXT" +bash .github/tools/fetch_release.sh \ + "https://github.com/mcpp-community/mcpp/releases/download/v$OLD_VER/$name" "$W/$name" || exit 1 +mkdir -p "$W/x" +if [[ $EXT == zip ]]; then + "$PY" -c "import zipfile,sys; zipfile.ZipFile(sys.argv[1]).extractall(sys.argv[2])" "$W/$name" "$W/x" +else + tar xzf "$W/$name" -C "$W/x" +fi +mv "$W/x/mcpp-$OLD_VER-$PLAT" "$W/old" +OLD="$W/old/bin/mcpp$EXE" +mkdir -p "$W/new/bin" "$W/new/registry/bin" +cp "$NEW_MCPP" "$W/new/bin/mcpp$EXE" +cp "$NEW_XLINGS" "$W/new/registry/bin/xlings$EXE" +chmod +x "$OLD" "$W/old/registry/bin/xlings$EXE" "$W/new/bin/mcpp$EXE" "$W/new/registry/bin/xlings$EXE" 2>/dev/null +NEW="$W/new/bin/mcpp$EXE" +"$OLD" --version; "$NEW" --version + +# run