Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions .agents/docs/2026-09-05-heterogeneous-build-ecosystem-design-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ Dependencies run downward; each task states the criterion that decides it.

| # | task | criterion | depends on |
|---|---|---|---|
| C1 | `mcpp.build.spirv` + `examples/10-vulkan-compute` | three devices, one artifact — **done** | — |
| C1 | `mcpp.build.spirv` + `examples/10-vulkan-compute` (the module is `mcpp.rules.spirv` since round 3; see 6.2) | three devices, one artifact — **done** | — |
| C2 | `mcpp.build.sycl` driving the `dpcpp` payload | a SYCL kernel runs on the CUDA backend | — |
| C3 | `mcpp.build.hip` | `HIP_PLATFORM=nvidia` kernel runs | `hip-runtime` payload |
| C4 | llama.cpp Vulkan lane | tokens on lavapipe with no GPU | C1, and a `glslc` payload or a flag translator |
Expand Down Expand Up @@ -332,6 +332,9 @@ Status is one of `done`, `open`, `deferred (reason)`.
| I3 | `compat.opencl-headers`, `compat.opencl` verified with a probe (`tests/examples/opencl`, a workspace member); `compat.opencl-runtime` 2026.09.05 farms the libraries the host manifests name, their closure and the vendor family, prefers payloads, records the surface; payload entries of `OCL_ICD_FILENAMES` are left to the payload | the probe enumerates the NVIDIA platform on this machine and zero platforms on a runner; the pocl platform once X2 is installed | X2 for the pocl half | done for the host half: `tests/examples/opencl` lists `NVIDIA CUDA / RTX 4080` here and zero platforms on the Linux runner; with `OCL_ICD_FILENAMES=<pocl>/lib/libpocl.so` the same loader lists `Portable Computing Language` and `NVIDIA CUDA` in one process |
| I4 | CI green; merge; index artifact published | `Publish Index Artifact` green on the merge commit | I1–I3 | done: PR #349 green (10 pass; the `full sweep` job skips by design), merged as 754d775, `Publish Index Artifact` green on it (run 33968256961) |

| I5 | `compat.vulkan-runtime` 2026.09.06: the payload set is declared as `xpm.linux.deps.runtime` rather than discovered in the store; `PAYLOAD_PACKAGES` maps each soname to the package declared for it and reports a declaration that did not take effect; both adapters refuse a payload built for another machine (ELF `e_machine`); the Vulkan adapter gains the aarch64 multiarch directories | a fresh environment substitutes the same set a developer machine does; the report contains no line saying a declaration did not take effect | I4, X5 | done: merged as 0bbf3ad, index artifact published; measured in a fresh subos, 24 payload substitutions against 1, and four host sonames without a payload against thirty |
| I6 | Both adapters 2026.09.07: a soname carried by more than one installed payload is decided by symbol coverage rather than by which store path sorts last; `compat.vulkan` and `compat.opencl` move their pins | in a fresh subos, `libX11.so.6` comes from `xim:libX11` rather than staying on the host with `xim:mesa-lavapipe`'s bundled copy named as the reason | I5 | PR #351 |

#### xim-pkgindex (PR #762)

| # | task | criterion | depends on | status |
Expand All @@ -343,9 +346,37 @@ Status is one of `done`, `open`, `deferred (reason)`.
| X4 | `xim:mesa` gains the Intel Vulkan driver | on an Intel machine `HOST-SURFACE.txt` has no driver entry | libclc and SPIRV-LLVM-Translator payloads | deferred: anv requires `intel_clc`, which needs a libclc and clang chain this index does not publish yet; the chain is a separate packaging round and is recorded here rather than approximated |
| X5 | CI green; merge; index artifact published | `Publish Index Artifact` green on the merge commit | X1–X3 | done: PR #762 green on its pull_request-event runs (the push-event `linux-install-test` sees only the last push and fails on libbsd by construction, as the workflow header states), merged as 2d2a2ee; `Publish Index Artifact` green on it (run 33967729241) |

#### mcpp, second round (PR #570, version 2026.9.5.4)

| # | task | criterion | depends on | status |
|---|---|---|---|---|
| M7 | The fast path compares every declared build-program input, not only glob path sets | e2e 612: editing a file declared with `rerun_if_changed` changes what the binary prints, and an unchanged input still takes the fast path | — | done: verified failing on 2026.9.5.3 and passing on this branch |
| M8 | A version conflict on a package with no named C++ module names both versions and both requesters | the message example 10 produced now says which two versions are in the graph | — | done |
| M9 | The markers removed from `bench/`, `tools/`, `scripts/`, `mcpp.toml` and `README.zh-CN.md`; the Chinese target table gains the row and the words its English counterpart has | a sweep over the emoji ranges returns nothing outside program output | — | done |
| M10 | Release 2026.9.5.4, mirror, index bump, bootstrap pin | both mirrors return 200 and identical bytes; xim-pkgindex names it as `latest` | M7-M9 | open |

#### mcpp-plugins, second round (PR #2, version 0.1.1)

| # | task | criterion | depends on | status |
|---|---|---|---|---|
| P4 | `mcpp.tools.embed`, the first member of the tools half, selected by `tools-embed` | the fixture activating only that feature embeds a data file, and its second run proves an edit reaches the binary | M7 | done, CI pending on the 2026.9.5.4 release |
| P5 | The markers removed from the rule sources; version 0.1.1; release and mirror; index descriptor; the two examples move their pin | the same two URLs return 200 with one sha256 | P4, M10 | open |

#### Verification

| # | task | criterion | depends on | status |
|---|---|---|---|---|
| V1 | Fresh sandbox (`xlings subos <name> --sandbox --cmd`), CN mirror configured for both mcpp and xlings: install mcpp 2026.9.5.3, build examples 09 and 10 against `mcpp:plugins`, run 10 on the lavapipe payload, run the OpenCL probe on pocl | `12 24 36 48` from example 10 with no GPU; the pocl platform enumerated; `HOST-SURFACE.txt` empty in the sandbox | M6, P3, I4, X5 | open |
| V2 | The same on this host with the GPU: example 10 on the host ICD, example 09 on both routes | `12 24 36 48` in every case; `HOST-SURFACE.txt` lists proprietary userspace only | V1 | open |
| V1 | Fresh sandbox (`xlings subos <name> --sandbox --cmd`), CN mirror configured for both mcpp and xlings: install mcpp 2026.9.5.3, build examples 09 and 10 against `mcpp:plugins`, run 10 on the lavapipe payload, run the OpenCL probe on pocl | `12 24 36 48` from example 10 with no GPU; the pocl platform enumerated; every declared payload substitution took effect (see 6.4 for why an empty host surface is not the criterion) | M6, P3, I4, X5, I5 | open |
| V2 | The same on this host with the GPU: example 10 on the host ICD, example 09 on both routes | `12 24 36 48` in every case; the host entries of `HOST-SURFACE.txt` are proprietary userspace, host Mesa drivers, and the recorded symbol-set and soname gaps only | V1 | open |

### 6.4 What the first verification run measured, and the two criteria it retired

The first sandbox run failed six assertions. One was a defect in the ecosystem, two were criteria that could not hold by construction, and three were the machine's disk filling up mid-run. They are separated here because only the first is a change to a package.

**The defect: a substitution that the environment decided.** `compat.vulkan-runtime` 2026.09.05 re-points a farmed soname at an installed payload when the payload's symbol set covers the host copy's, and *installed* was left to chance -- the pass looked in the store and took what an earlier, unrelated install had put there. The same package therefore produced twenty payload substitutions on the developer machine and one in a fresh subos, and the report read `no installed payload provides this soname` for sonames this index publishes. A published package whose behaviour is that much better on the machine that wrote it is the shape recorded in `.agents/docs` as a development overlay verifying a world that does not exist. 2026.09.06 declares the set (row I5). Measured against a project-local index on this host: 24 substitutions against 20, seven host entries against eleven, and a Vulkan probe still enumerating two devices.

**The first retired criterion: an empty host surface in a sandbox.** A subos shares the host's `/usr`, which the run confirmed: 5354 entries in `/usr/lib/x86_64-linux-gnu` inside the sandbox, `libnvidia-*` among them. The farm therefore sees the proprietary driver there exactly as it does on the host, and `HOST-SURFACE.txt empty in the sandbox` was unreachable by construction rather than a statement about the ecosystem. What a sandbox does measure is the substitution invariant: every soname the adapter declares a payload for is taken from that payload, and a declaration that did not take effect says so in the report.

**The second retired criterion: the exit code of a mirror write.** Both tools print their configuration on stderr, so reading it through `2>/dev/null` returned an empty string and reported a mirror that was in fact set to CN.

**Two properties of the sandbox that the run established.** A subos root has no `/run`, so the inherited `XDG_RUNTIME_DIR` names a directory that does not exist; Mesa falls back to it when `memfd` is unavailable and lavapipe fails with `Failed to create anonymous file for memory allocations` before it reports a device. And the registry's index snapshot is shared with whatever wrote it last: the run resolved against a snapshot four commits behind `main`, where `compat.opencl-headers` reported `download artifact missing` because the recipe was not in it. The verification script now redirects the first and refreshes the second before it measures anything.
59 changes: 55 additions & 4 deletions .agents/docs/2026-09-05-heterogeneous-verify.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,35 @@ got=$("$STORE" --version 2>&1 | head -1)
# value was written, so both tools are asked what they hold afterwards.
"$STORE" self config --mirror "${MCPP_VERIFY_MIRROR:-CN}" >/dev/null 2>&1 || true
"$XL" config --mirror "${MCPP_VERIFY_MIRROR:-CN}" >/dev/null 2>&1 || true
xm=$("$XL" config 2>/dev/null | grep -i 'mirror' | head -1 | awk '{print $NF}')
# THE SETTING, NOT THE TOOL'S OUTPUT. `xlings config` renders a banner through
# its ui layer, which prints nothing when its stdout is a pipe -- so a command
# substitution reads an empty string and reports a mirror that is in fact set.
# The file the tool writes answers the same question and cannot be suppressed.
xm=$(python3 -c "import json,os;print(json.load(open(os.path.expanduser('~/.xlings/.xlings.json'))).get('mirror',''))" 2>/dev/null)
[ "$xm" = "${MCPP_VERIFY_MIRROR:-CN}" ] && ok "xlings mirror is $xm" || fail "xlings mirror is '$xm', not ${MCPP_VERIFY_MIRROR:-CN}"
mm=$("$STORE" self config 2>/dev/null | grep -i 'mirror' | head -1 | awk '{print $NF}')
mm=$("$STORE" self config 2>&1 | sed 's/\x1b\[[0-9;]*m//g' | grep -i 'mirror' | head -1 | awk '{print $NF}')
[ -n "$mm" ] && ok "mcpp mirror is $mm" || printf 'note: mcpp self config does not print its mirror (%s)\n' "$("$STORE" self config 2>&1 | head -1)"

# THE REGISTRY IS SHARED WITH WHATEVER WROTE IT LAST, AND ITS INDEX SNAPSHOT
# IS PART OF THAT. A fresh subos inherits the snapshot the registry holds, and
# the refresh is resolution-driven with a freshness window, so a package merged
# minutes ago resolves as `download artifact missing` until the snapshot moves.
# Measured 2026-09-05: section E failed against a snapshot four commits behind.
"$STORE" index update >/dev/null 2>&1 || true
snap=$(cat "$HOME/.mcpp/registry/data/mcpplibs/.xlings-index-version" 2>/dev/null || echo "unknown")
ok "index snapshot $snap"

# Mesa creates its allocations through an anonymous file and falls back to
# XDG_RUNTIME_DIR when memfd is unavailable. A subos root has no /run, so the
# inherited value names a directory that does not exist and lavapipe fails with
# `Failed to create anonymous file for memory allocations` before it reports a
# device. A writable directory inside the sandbox is the whole fix.
if [ ! -d "${XDG_RUNTIME_DIR:-/nonexistent}" ]; then
XDG_RUNTIME_DIR="$HOME/.cache/xdg-runtime"; mkdir -p "$XDG_RUNTIME_DIR"
chmod 700 "$XDG_RUNTIME_DIR"; export XDG_RUNTIME_DIR
ok "XDG_RUNTIME_DIR redirected to $XDG_RUNTIME_DIR"
fi

# -- B. the plugin collection resolves from the index -------------------------
#
# A consumer that names mcpp:plugins with one feature and imports the member
Expand Down Expand Up @@ -103,6 +127,13 @@ if [ -n "$SRC" ] && [ -d "$SRC/examples/10-vulkan-compute/app" ]; then
icd=$(ls "$HOME"/.mcpp/registry/data/xpkgs/xim-x-mesa-lavapipe/*/share/vulkan/icd.d/*.json \
"$ex10"/.mcpp/.xlings/data/xpkgs/xim-x-mesa-lavapipe/*/share/vulkan/icd.d/*.json \
"$HOME"/.xlings/data/xpkgs/xim-x-mesa-lavapipe/*/share/vulkan/icd.d/*.json 2>/dev/null | head -1)
if [ -z "$icd" ]; then
# The example declares the payload (mcpp#569); an older checkout does
# not, in which case the payload is installed here so the criterion is
# still the driver and not the manifest's history.
"$XL" install mesa-lavapipe -y >/dev/null 2>&1 || true
icd=$(ls "$HOME"/.xlings/data/xpkgs/xim-x-mesa-lavapipe/*/share/vulkan/icd.d/*.json 2>/dev/null | head -1)
fi
[ -n "$icd" ] && ok "lavapipe manifest at $icd" || fail "no lavapipe ICD manifest in any store"
out=$(cd "$ex10" && VK_DRIVER_FILES="$icd" "$STORE" run 2>&1)
printf '%s\n' "$out" | grep -q '12 24 36 48' && ok "example 10 answered 12 24 36 48 on the payload driver" \
Expand All @@ -113,11 +144,31 @@ if [ -n "$SRC" ] && [ -d "$SRC/examples/10-vulkan-compute/app" ]; then
|| fail "example 10 --no-accel: $(printf '%s' "$out" | tail -3 | tr '\n' ' ')"
hs=$(ls "$HOME"/.mcpp/registry/data/xpkgs/compat-x-vulkan-runtime/*/mcpp_generated/vulkan_runtime/HOST-SURFACE.txt \
"$ex10"/.mcpp/.xlings/data/xpkgs/compat-x-vulkan-runtime/*/mcpp_generated/vulkan_runtime/HOST-SURFACE.txt 2>/dev/null | head -1)
# WHICH ADAPTER WROTE IT. The substitution criteria below are properties of
# 2026.09.06, the version that declares its payload set; an older report
# carries neither the declarations nor the class they produce, so both
# would pass on an empty finding. The version is a path segment.
adapter=$(printf '%s' "$hs" | sed -n 's#.*/compat-x-vulkan-runtime/\([^/]*\)/.*#\1#p')
[ "$adapter" = "${MCPP_VERIFY_ADAPTER:-2026.09.06}" ] && ok "the farm was written by compat.vulkan-runtime $adapter" \
|| fail "the farm was written by compat.vulkan-runtime '$adapter', not ${MCPP_VERIFY_ADAPTER:-2026.09.06}"
if [ -n "$hs" ]; then
printf -- '--- %s\n' "$hs"; sed -n '/^## farmed/,$p' "$hs" | head -60
# WHAT A SANDBOX CAN AND CANNOT ASSERT. A subos shares the host's
# /usr, so the farm sees the host's proprietary driver there and a
# count of zero host entries is not reachable by construction -- the
# earlier form of this check asserted it and failed on a correct farm.
# What the sandbox does measure is the substitution invariant: every
# soname compat.vulkan-runtime declares a payload for is taken from
# that payload, and a declaration that did not take effect says so in
# the report.
hostlines=$(grep -c -- '-- host;' "$hs" || true)
if [ -n "${MCPP_VERIFY_HOST:-}" ]; then ok "host surface recorded ($hostlines host entries, see above)"; else
[ "$hostlines" -eq 0 ] && ok "no host library is on the runtime path in the sandbox" || fail "$hostlines host entries in a sandbox"; fi
payloads=$(grep -c -- '-- payload;' "$hs" || true)
undeclared=$(grep -c 'did not take effect' "$hs" || true)
printf 'note: %s payload substitutions, %s host entries\n' "$payloads" "$hostlines"
[ "$undeclared" -eq 0 ] && ok "every declared payload took effect" \
|| fail "$undeclared declared payload(s) did not take effect"
[ "$payloads" -ge 20 ] && ok "$payloads farmed libraries come from payloads" \
|| fail "only $payloads payload substitutions; the declared set did not install"
else fail "no HOST-SURFACE.txt written by compat.vulkan-runtime"; fi
else
printf 'skip: MCPP_VERIFY_SRC not set or has no examples/10-vulkan-compute\n'
Expand Down
26 changes: 24 additions & 2 deletions .agents/docs/2026-09-05-multi-device-ecosystem-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -1238,9 +1238,9 @@ GPU 索引包不自己探测宿主(委托 xim sentinel);链宿主 `libcudart`
| 项 | 理由 |
|---|---|
| T1.2 `llvm-offload` | `dpcpp@7.1.0` 载荷自带全套 offload 工具,需要 RDC 的工程可用它;独立包仍待做 |
| T1.4 / T1.5(pocl / lavapipe)与 T4.2 | 需要**重打 mesa 载荷**(见 12.1 末行),或新建 pocl 源码构建配方。两者都是多小时的载荷工程 |
| ~~T1.4 / T1.5(pocl / lavapipe)~~ | **已做**(2026-09-06,见 12.6)。理由被一条更短的路推翻:两者在 conda-forge 上都有现成的 Linux 二进制,重打载荷因此不是「重新构建 mesa」而是「重打包并核验闭包」 |
| T1.8/T1.9/T1.10(chipstar / adaptivecpp / hip) | 依赖 T1.2/T1.4 |
| T4.3 规则包进索引 | 依赖 ③ —— 描述符指向 mcpp 的**源码 tarball**(`grpcgen` 同形),tag 不存在则算不出 sha256。规则包已改名到 `mcpplibs` 命名空间,就是为了让它可被引用而不是被复制 |
| ~~T4.3 规则包进索引~~ | **已做,但形态换了**(2026-09-05/06,见 12.6)。不再指向 mcpp 源码 tarball 的子路径:规则集中到 `mcpp-community/mcpp-plugins`,以一个包 `mcpp:plugins` 发布,成员由 feature 选择。命名空间也随之从 `mcpplibs` 改为 `mcpp` |
| ⑤ 九个框架 | 依赖 ④ 的规则包条目。`ggml-org.llamacpp` 与 `opencv.opencv` 已在索引里,多后端是改**它们各自的 `-m` 仓库**而不是索引条目。T5.1 已做到「链路全通、卡在载荷矩阵」—— 见 12.5 |
| T2.6 的端到端判据 | `accel` 已是 `pack::AbiTag` 第四维并进指纹;「`.a` 随包传播」还缺一条跨包的判据 |

Expand Down Expand Up @@ -1274,3 +1274,25 @@ action **只**挂到可执行/共享库/测试上,而 llama.cpp 的 CUDA 后端
`cuda_runtime.h` 同一形状,第三次);
**clang 路线要带 `-D_ALLOW_UNSUPPORTED_LIBCPP`**,因为 NVIDIA 那条 `libc++ is not
supported` 的守卫看的是 `__CUDACC__`,而 clang 编 CUDA 时自己就定义它。

## 12.6 2026-09-06 补记:12.4 里两行被后一轮关掉

本文 12.4 记的是 2026-09-05 当天的读数。次日那一轮(设计见
`2026-09-05-heterogeneous-build-ecosystem-design-v2.md` §6)关掉了其中两行,都不是
靠更多工时,而是靠换一条更短的路。

**T1.4 / T1.5 —— pocl 与 lavapipe 都成了载荷。** 当时写的理由是「需要重打 mesa 载荷
或新建 pocl 源码构建配方,都是多小时的载荷工程」。这个理由把「载荷」等同于「从源码
构建」;而 conda-forge 对这两者都发布了 Linux 二进制,于是工作变成重打包并核验闭包。
`xim:mesa-lavapipe@26.2.1`(x86_64 与 aarch64)与 `xim:pocl@7.1` 已发布,
`xlings install pocl` 之后 Khronos loader 在一个进程里同时列出
`Portable Computing Language` 与 `NVIDIA CUDA`。

**T4.3 —— 规则包进了索引,但不是当时设想的形态。** 当时的方案是让描述符指向 mcpp
源码 tarball 的子路径(`grpcgen` 同形),受阻于「tag 不存在则算不出 sha256」。实际
落地的是另一种:规则不再住在 mcpp 的 `examples/` 下,而集中到
`mcpp-community/mcpp-plugins`,以一个包 `mcpp:plugins` 发布,成员由 feature 选择,
命名空间从 `mcpplibs` 改为 `mcpp`。索引里的 `pkgs/m/mcpp.plugins.lua` 指向那个仓库
自己的 release 资产,sha256 因此是一个已经存在的文件的哈希。

其余各行的理由未变。
Loading
Loading