|
| 1 | +#!/usr/bin/env bash |
| 2 | +# Ecosystem verification for the 2026.9.20.1 wave against the PUBLISHED mcpp |
| 3 | +# and index, run inside a SubOS sandbox with CN mirrors for xlings and mcpp. |
| 4 | +# |
| 5 | +# B64=$(base64 -w0 .agents/docs/2026-09-20-cenv-interfaces-verify.sh) |
| 6 | +# xlings subos new v920 |
| 7 | +# xlings subos use v920 --sandbox --cmd \ |
| 8 | +# "echo $B64 | base64 -d > /tmp/v.sh && MCPP_VERIFY_VERSION=2026.9.20.1 bash /tmp/v.sh" |
| 9 | +# |
| 10 | +# Run it once against the PREVIOUS release first |
| 11 | +# (MCPP_VERIFY_VERSION=2026.9.18.3): every section marked CHANGE must fail there |
| 12 | +# and pass here; every section marked GUARD must pass on both. A section that |
| 13 | +# passes on both releases in a CHANGE section measured nothing. |
| 14 | +# |
| 15 | +# The sandbox's $HOME persists between runs of one SubOS, so each section clears |
| 16 | +# its own directory. A section that cannot run says so and is listed again at |
| 17 | +# the end, because a run that reports only failures cannot be told from one that |
| 18 | +# examined nothing. |
| 19 | +# TWO RUNS, AND THE READING FROM EACH (host dry run, 2026-09-20): |
| 20 | +# |
| 21 | +# mcpp 2026.9.17.1 (published) fails=3 |
| 22 | +# B a requirement the implementation does not provide must be refused |
| 23 | +# C a declared absence with a known shape must be accepted |
| 24 | +# C the refusal does not name the shapes -- "[c-abi] has no member 'absent'" |
| 25 | +# mcpp 2026.9.20.1 fails=0 |
| 26 | +# |
| 27 | +# B's FIRST leg passes on both, and that is the documented behaviour rather |
| 28 | +# than a hole: `[kernel-abi]` is an unknown top-level table to an older engine |
| 29 | +# and is ignored, so a graph that satisfies its requirements builds either way. |
| 30 | +# The leg that distinguishes the releases is the refusal. C fails outright on |
| 31 | +# the older engine because `[c-abi.absent]` is a new key inside a table it |
| 32 | +# knows, where an unrecognised key is a parse error -- the asymmetry docs/22 |
| 33 | +# records. |
| 34 | +set -u |
| 35 | + |
| 36 | +VER="${MCPP_VERIFY_VERSION:?set MCPP_VERIFY_VERSION}" |
| 37 | +STORE="${MCPP_VERIFY_BIN:-$HOME/.xlings/data/xpkgs/xim-x-mcpp/$VER/bin/mcpp}" |
| 38 | + |
| 39 | +fails=0 |
| 40 | +skipped="" |
| 41 | +fail() { printf 'ASSERT-FAIL: %s\n' "$1"; fails=$((fails + 1)); } |
| 42 | +ok() { printf 'ok: %s\n' "$1"; } |
| 43 | +section() { printf '\n== %s ==\n' "$1"; } |
| 44 | +skip() { printf 'NOT RUN: %s\n' "$1"; skipped="$skipped |
| 45 | + - $1"; } |
| 46 | +unset XLINGS_ACTIVE_SUBOS |
| 47 | + |
| 48 | +root="$HOME/verify-920" |
| 49 | +rm -rf "$root"; mkdir -p "$root" |
| 50 | + |
| 51 | +section "A. identity and mirror" |
| 52 | +if [ ! -x "$STORE" ]; then |
| 53 | + skip "mcpp $VER is not in the store at $STORE" |
| 54 | + printf '\n-- summary --\nfails=%d\nnot run:%s\n' "$fails" "${skipped:- (none)}" |
| 55 | + exit 1 |
| 56 | +fi |
| 57 | +got="$("$STORE" --version 2>&1 | head -1)" |
| 58 | +case "$got" in |
| 59 | + *"$VER"*) ok "mcpp $VER from $STORE" ;; |
| 60 | + *) fail "the binary at $STORE reports '$got'" ;; |
| 61 | +esac |
| 62 | +"$STORE" self config --mirror CN >/dev/null 2>&1 \ |
| 63 | + && ok "mcpp mirror set to CN" || fail "mcpp self config --mirror CN" |
| 64 | + |
| 65 | +# ── CHANGE 1. A package states which interfaces of the layer it requires ───── |
| 66 | +# |
| 67 | +# The engine knows no interface name: it compares two sets and refuses before |
| 68 | +# anything is compiled. Both legs are here because only the second says the |
| 69 | +# comparison happened -- a build that succeeds proves nothing about a check |
| 70 | +# that never ran. |
| 71 | +section "B. requires-interfaces is answered at resolution (CHANGE)" |
| 72 | +b="$root/b"; rm -rf "$b"; mkdir -p "$b/impl/src" "$b/src" |
| 73 | +printf 'int fake_kernel_marker(void){return 0;}\n' > "$b/impl/src/lib.c" |
| 74 | +printf 'int main(void){return 0;}\n' > "$b/src/main.c" |
| 75 | +cat > "$b/impl/mcpp.toml" <<'EOF' |
| 76 | +[package] |
| 77 | +name = "fakekernel" |
| 78 | +version = "0.1.0" |
| 79 | +provides = ["mcpp:kernel-abi=openkal"] |
| 80 | +
|
| 81 | +[targets.fakekernel] |
| 82 | +kind = "lib" |
| 83 | +sources = ["src/*.c"] |
| 84 | +
|
| 85 | +[kernel-abi] |
| 86 | +provides-interfaces = ["openkal.abort", "openkal.stream", "openkal.memory"] |
| 87 | +EOF |
| 88 | +mk_root() { # $1 = requires-interfaces body |
| 89 | + cat > "$b/mcpp.toml" <<EOF |
| 90 | +[package] |
| 91 | +name = "iface-probe" |
| 92 | +version = "0.1.0" |
| 93 | +
|
| 94 | +[dependencies] |
| 95 | +fakekernel = { path = "impl" } |
| 96 | +
|
| 97 | +[build] |
| 98 | +allow_host_libs = true |
| 99 | +
|
| 100 | +[kernel-abi] |
| 101 | +requires-interfaces = [$1] |
| 102 | +EOF |
| 103 | +} |
| 104 | +mk_root '"openkal.stream"' |
| 105 | +rm -rf "$b/target" |
| 106 | +if (cd "$b" && "$STORE" build >/dev/null 2>&1); then |
| 107 | + ok "a requirement the implementation provides builds" |
| 108 | +else |
| 109 | + fail "a requirement the implementation provides must build" |
| 110 | +fi |
| 111 | +mk_root '"openkal.stream", "openkal.net"' |
| 112 | +rm -rf "$b/target" |
| 113 | +out="$(cd "$b" && "$STORE" build 2>&1)" |
| 114 | +if [ $? -eq 0 ]; then |
| 115 | + fail "a requirement the implementation does not provide must be refused" |
| 116 | +else |
| 117 | + case "$out" in |
| 118 | + *openkal.net*iface-probe*|*iface-probe*openkal.net*) |
| 119 | + ok "the refusal names the interface and the package" ;; |
| 120 | + *) fail "the refusal does not name both: $(printf '%s' "$out" | head -3 | tr '\n' ' ')" ;; |
| 121 | + esac |
| 122 | + if [ -d "$b/target" ] && find "$b/target" -name '*.o' -print -quit 2>/dev/null | grep -q .; then |
| 123 | + fail "the refusal arrived after something was compiled" |
| 124 | + else |
| 125 | + ok "nothing was compiled before the refusal" |
| 126 | + fi |
| 127 | +fi |
| 128 | + |
| 129 | +# ── CHANGE 2. A C library states what it does not supply ──────────────────── |
| 130 | +section "C. [c-abi.absent] is read, and a bad shape is refused (CHANGE)" |
| 131 | +c="$root/c"; rm -rf "$c"; mkdir -p "$c/libc/src" "$c/src" |
| 132 | +printf 'int fake_libc_marker(void){return 0;}\n' > "$c/libc/src/lib.c" |
| 133 | +printf 'int main(void){return 0;}\n' > "$c/src/main.c" |
| 134 | +cat > "$c/mcpp.toml" <<'EOF' |
| 135 | +[package] |
| 136 | +name = "absent-probe" |
| 137 | +version = "0.1.0" |
| 138 | +
|
| 139 | +[dependencies] |
| 140 | +fakelibc = { path = "libc" } |
| 141 | +
|
| 142 | +[build] |
| 143 | +allow_host_libs = true |
| 144 | +EOF |
| 145 | +mk_libc() { # $1 = the form value |
| 146 | + cat > "$c/libc/mcpp.toml" <<EOF |
| 147 | +[package] |
| 148 | +name = "fakelibc" |
| 149 | +version = "0.1.0" |
| 150 | +provides = ["mcpp:c-abi=musl"] |
| 151 | +
|
| 152 | +[targets.fakelibc] |
| 153 | +kind = "lib" |
| 154 | +sources = ["src/*.c"] |
| 155 | +
|
| 156 | +[c-abi] |
| 157 | +presents = "posix" |
| 158 | +data-model = "arch-default" |
| 159 | +wchar = 32 |
| 160 | +
|
| 161 | +[c-abi.absent] |
| 162 | +fork = { form = "$1", note = "no process image duplication" } |
| 163 | +EOF |
| 164 | +} |
| 165 | +mk_libc link |
| 166 | +rm -rf "$c/target" |
| 167 | +if (cd "$c" && "$STORE" build >/dev/null 2>&1); then |
| 168 | + ok "a declared absence with a known shape is accepted" |
| 169 | +else |
| 170 | + fail "a declared absence with a known shape must be accepted" |
| 171 | +fi |
| 172 | +mk_libc sometimes |
| 173 | +rm -rf "$c/target" |
| 174 | +out="$(cd "$c" && "$STORE" build 2>&1)" |
| 175 | +if [ $? -eq 0 ]; then |
| 176 | + fail "an absence with an unknown shape must be refused" |
| 177 | +else |
| 178 | + case "$out" in |
| 179 | + *accepted-no-effect*) ok "the refusal names the shapes that exist" ;; |
| 180 | + *) fail "the refusal does not name the shapes: $(printf '%s' "$out" | head -2 | tr '\n' ' ')" ;; |
| 181 | + esac |
| 182 | +fi |
| 183 | + |
| 184 | +# ── GUARD. A package that declares neither key is untouched ───────────────── |
| 185 | +section "D. a package declaring neither key is unchanged (GUARD)" |
| 186 | +d="$root/d"; rm -rf "$d"; mkdir -p "$d/src" |
| 187 | +printf '#include <cstdio>\nint main(){std::puts("plain");return 0;}\n' > "$d/src/main.cpp" |
| 188 | +cat > "$d/mcpp.toml" <<'EOF' |
| 189 | +[package] |
| 190 | +name = "plain" |
| 191 | +version = "0.1.0" |
| 192 | +EOF |
| 193 | +if (cd "$d" && "$STORE" build >/dev/null 2>&1) \ |
| 194 | + && "$d"/target/*/*/bin/plain 2>/dev/null | grep -q plain; then |
| 195 | + ok "a package declaring nothing builds and runs" |
| 196 | +else |
| 197 | + fail "a package declaring nothing must build and run unchanged" |
| 198 | +fi |
| 199 | + |
| 200 | +# ── GUARD. openkal from the published index ───────────────────────────────── |
| 201 | +section "E. an openkal program from the published index (GUARD)" |
| 202 | +e="$root/e"; rm -rf "$e"; mkdir -p "$e/src" |
| 203 | +printf '#include <cstdio>\nint main(){std::puts("openkal ok");return 0;}\n' > "$e/src/main.cpp" |
| 204 | +cat > "$e/mcpp.toml" <<'EOF' |
| 205 | +[package] |
| 206 | +name = "openkal-hello" |
| 207 | +version = "0.1.0" |
| 208 | +
|
| 209 | +[dependencies] |
| 210 | +openkal-llvm-runtime = "0.12.0" |
| 211 | +EOF |
| 212 | +if (cd "$e" && "$STORE" build >/dev/null 2>&1); then |
| 213 | + if "$e"/target/*/*/bin/openkal-hello 2>/dev/null | grep -q "openkal ok"; then |
| 214 | + ok "an openkal program builds and runs from the published index" |
| 215 | + else |
| 216 | + fail "the openkal program built and did not run" |
| 217 | + fi |
| 218 | +else |
| 219 | + skip "openkal-llvm-runtime 0.12.0 did not resolve from the index" |
| 220 | +fi |
| 221 | + |
| 222 | +printf '\n-- summary --\nfails=%d\nnot run:%s\n' "$fails" "${skipped:- |
| 223 | + (none)}" |
| 224 | +[ "$fails" -eq 0 ] |
0 commit comments