|
| 1 | +#!/usr/bin/env bash |
| 2 | +# Ecosystem verification for the 2026.9.21.2 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-21-macros-and-withdrawal-verify.sh) |
| 6 | +# xlings subos use v920 --sandbox --cmd \ |
| 7 | +# "echo $B64 | base64 -d > /tmp/v.sh && MCPP_VERIFY_VERSION=2026.9.21.2 bash /tmp/v.sh" |
| 8 | +# |
| 9 | +# RUN IT AGAINST THE PREVIOUS RELEASE FIRST (MCPP_VERIFY_VERSION=2026.9.21.1): |
| 10 | +# every CHANGE section must FAIL there and pass here, and every GUARD section |
| 11 | +# must pass on both. A CHANGE section green on both measured nothing. |
| 12 | +# |
| 13 | +# The sandbox's $HOME persists between runs of one SubOS, so each section |
| 14 | +# clears its own directory. A section that cannot run says so and is listed |
| 15 | +# again at the end: a run reporting only failures cannot be told from one that |
| 16 | +# examined nothing. |
| 17 | +# |
| 18 | +# TWO RUNS AND THE READING FROM EACH (host dry run, 2026-09-21, the older one |
| 19 | +# against the genuine published archive rather than a local build): |
| 20 | +# |
| 21 | +# mcpp 2026.9.21.1 (published) fails=2 |
| 22 | +# B the engine does not define the upper-case target macro |
| 23 | +# C an unanswered requirement produced no note |
| 24 | +# mcpp 2026.9.21.2 fails=0 |
| 25 | +# |
| 26 | +# C's SECOND LEG PASSES ON BOTH, and that is the point of having it: it asserts |
| 27 | +# the note is ABSENT when the provider does state its list, so without it the |
| 28 | +# first leg would pass against an engine that printed the line unconditionally. |
| 29 | +# A negative control is not a hole in a CHANGE section. |
| 30 | +# |
| 31 | +# D and F needed `openkal-llvm-runtime@0.14.0`, registered after that dry run, |
| 32 | +# and both reported NOT RUN rather than passing. |
| 33 | +set -u |
| 34 | + |
| 35 | +VER="${MCPP_VERIFY_VERSION:?set MCPP_VERIFY_VERSION}" |
| 36 | +STORE="${MCPP_VERIFY_BIN:-$HOME/.xlings/data/xpkgs/xim-x-mcpp/$VER/bin/mcpp}" |
| 37 | + |
| 38 | +fails=0 |
| 39 | +skipped="" |
| 40 | +fail() { printf 'ASSERT-FAIL: %s\n' "$1"; fails=$((fails + 1)); } |
| 41 | +ok() { printf 'ok: %s\n' "$1"; } |
| 42 | +section() { printf '\n== %s ==\n' "$1"; } |
| 43 | +skip() { printf 'NOT RUN: %s\n' "$1"; skipped="$skipped |
| 44 | + - $1"; } |
| 45 | +unset XLINGS_ACTIVE_SUBOS |
| 46 | + |
| 47 | +root="$HOME/verify-9212" |
| 48 | +rm -rf "$root"; mkdir -p "$root" |
| 49 | + |
| 50 | +section "A. identity and mirror" |
| 51 | +if [ ! -x "$STORE" ]; then |
| 52 | + skip "mcpp $VER is not in the store at $STORE" |
| 53 | + printf '\n-- summary --\nfails=%d\nnot run:%s\n' "$fails" "${skipped:- (none)}" |
| 54 | + exit 1 |
| 55 | +fi |
| 56 | +got="$("$STORE" --version 2>&1 | head -1)" |
| 57 | +case "$got" in |
| 58 | + *"$VER"*) ok "mcpp $VER from $STORE" ;; |
| 59 | + *) fail "the binary at $STORE reports '$got'" ;; |
| 60 | +esac |
| 61 | +"$STORE" self config --mirror CN >/dev/null 2>&1 \ |
| 62 | + && ok "mcpp mirror set to CN" || fail "mcpp self config --mirror CN" |
| 63 | + |
| 64 | +# ── CHANGE 1. The owned macros are spelt in upper case ────────────────────── |
| 65 | +# |
| 66 | +# BOTH DIRECTIONS IN ONE TRANSLATION UNIT, because either alone passes for the |
| 67 | +# wrong reason: an engine defining NEITHER spelling satisfies "the lower-case |
| 68 | +# one is gone", and one defining BOTH satisfies "the upper-case one is here". |
| 69 | +section "B. __MCPP_TARGET_<OS>__ replaces __mcpp_target_<os>__ (CHANGE)" |
| 70 | +b="$root/b"; rm -rf "$b"; mkdir -p "$b/src" |
| 71 | +cat > "$b/src/main.c" <<'EOF' |
| 72 | +#if !defined(__MCPP_TARGET_LINUX__) |
| 73 | +#error "__MCPP_TARGET_LINUX__ is not defined" |
| 74 | +#endif |
| 75 | +#if defined(__mcpp_target_linux__) |
| 76 | +#error "the lower-case spelling is still defined" |
| 77 | +#endif |
| 78 | +int main(void) { return 0; } |
| 79 | +EOF |
| 80 | +cat > "$b/mcpp.toml" <<'EOF' |
| 81 | +[package] |
| 82 | +name = "macro-probe" |
| 83 | +version = "0.1.0" |
| 84 | +
|
| 85 | +[targets.macro-probe] |
| 86 | +kind = "bin" |
| 87 | +main = "src/main.c" |
| 88 | +EOF |
| 89 | +if (cd "$b" && "$STORE" build >/dev/null 2>&1); then |
| 90 | + ok "the upper-case target macro is defined and the lower-case one is not" |
| 91 | +else |
| 92 | + out=$(cd "$b" && "$STORE" build 2>&1) |
| 93 | + case "$out" in |
| 94 | + *"__MCPP_TARGET_LINUX__ is not defined"*) |
| 95 | + fail "the engine does not define the upper-case target macro" ;; |
| 96 | + *"lower-case spelling is still defined"*) |
| 97 | + fail "the engine still defines the lower-case target macro" ;; |
| 98 | + *) fail "the probe did not build, and for neither of the two reasons" ;; |
| 99 | + esac |
| 100 | +fi |
| 101 | + |
| 102 | +# THE SPELLING IS DERIVED, NOT ENUMERATED. A second target with a different |
| 103 | +# `os` says the engine reads the triple rather than carrying a table of names. |
| 104 | +b2="$root/b2"; rm -rf "$b2"; mkdir -p "$b2/src" |
| 105 | +cat > "$b2/src/main.c" <<'EOF' |
| 106 | +#if !defined(__MCPP_TARGET_NONE__) |
| 107 | +#error "__MCPP_TARGET_NONE__ is not defined for a freestanding target" |
| 108 | +#endif |
| 109 | +void _start(void) {} |
| 110 | +EOF |
| 111 | +cat > "$b2/mcpp.toml" <<'EOF' |
| 112 | +[package] |
| 113 | +name = "macro-probe-bare" |
| 114 | +version = "0.1.0" |
| 115 | +
|
| 116 | +[targets.macro-probe-bare] |
| 117 | +kind = "bin" |
| 118 | +main = "src/main.c" |
| 119 | +
|
| 120 | +[build] |
| 121 | +ldflags = ["-nostdlib", "-nostartfiles", "-static"] |
| 122 | +EOF |
| 123 | +if (cd "$b2" && "$STORE" build --target riscv64-none-elf >/dev/null 2>&1); then |
| 124 | + ok "a freestanding target spells its own macro from the triple" |
| 125 | +else |
| 126 | + skip "the freestanding toolchain did not resolve in this sandbox" |
| 127 | +fi |
| 128 | + |
| 129 | +# ── CHANGE 2. A requirement nobody answered is named ──────────────────────── |
| 130 | +# |
| 131 | +# Three situations exist and two build. Without this line the first and the |
| 132 | +# third produce identical output, so a consumer cannot tell "checked and |
| 133 | +# agreed" from "never asked". Both legs, because a note printed |
| 134 | +# unconditionally would satisfy the first one alone. |
| 135 | +section "C. an unanswered requirement is named (CHANGE)" |
| 136 | +c="$root/c"; rm -rf "$c"; mkdir -p "$c/impl/src" "$c/src" |
| 137 | +printf 'int fake_kernel_marker(void){return 0;}\n' > "$c/impl/src/lib.c" |
| 138 | +printf 'int main(void){return 0;}\n' > "$c/src/main.c" |
| 139 | +mk_impl() { # $1 = provides-interfaces body, or empty |
| 140 | + if [ -z "$1" ]; then |
| 141 | + cat > "$c/impl/mcpp.toml" <<'EOF' |
| 142 | +[package] |
| 143 | +name = "fakekernel" |
| 144 | +version = "0.1.0" |
| 145 | +provides = ["mcpp:kernel-abi=openkal"] |
| 146 | +
|
| 147 | +[targets.fakekernel] |
| 148 | +kind = "lib" |
| 149 | +sources = ["src/*.c"] |
| 150 | +EOF |
| 151 | + else |
| 152 | + cat > "$c/impl/mcpp.toml" <<EOF |
| 153 | +[package] |
| 154 | +name = "fakekernel" |
| 155 | +version = "0.1.0" |
| 156 | +provides = ["mcpp:kernel-abi=openkal"] |
| 157 | +
|
| 158 | +[targets.fakekernel] |
| 159 | +kind = "lib" |
| 160 | +sources = ["src/*.c"] |
| 161 | +
|
| 162 | +[kernel-abi] |
| 163 | +provides-interfaces = [$1] |
| 164 | +EOF |
| 165 | + fi |
| 166 | +} |
| 167 | +cat > "$c/mcpp.toml" <<'EOF' |
| 168 | +[package] |
| 169 | +name = "iface-probe" |
| 170 | +version = "0.1.0" |
| 171 | +
|
| 172 | +[dependencies] |
| 173 | +fakekernel = { path = "impl" } |
| 174 | +
|
| 175 | +[build] |
| 176 | +allow_host_libs = true |
| 177 | +
|
| 178 | +[kernel-abi] |
| 179 | +requires-interfaces = ["openkal.fs", "openkal.net"] |
| 180 | +EOF |
| 181 | +mk_impl '' |
| 182 | +out=$(cd "$c" && "$STORE" build 2>&1) |
| 183 | +if [ $? -ne 0 ]; then |
| 184 | + fail "a provider that states nothing must not be refused" |
| 185 | +else |
| 186 | + case "$out" in |
| 187 | + *"kernel-abi interfaces"*"states none"*"2 requirements unchecked"*) |
| 188 | + ok "the note names the implementation and how many went unchecked" ;; |
| 189 | + *"kernel-abi interfaces"*) |
| 190 | + fail "the note is printed but does not carry the count" ;; |
| 191 | + *) fail "an unanswered requirement produced no note" ;; |
| 192 | + esac |
| 193 | +fi |
| 194 | +rm -rf "$c/target" |
| 195 | +mk_impl '"openkal.fs", "openkal.net", "openkal.abort"' |
| 196 | +out=$(cd "$c" && "$STORE" build 2>&1) |
| 197 | +if [ $? -ne 0 ]; then |
| 198 | + fail "a graph whose provider states every requirement must build" |
| 199 | +else |
| 200 | + case "$out" in |
| 201 | + *"kernel-abi interfaces"*) |
| 202 | + fail "the note appeared for a graph in which everything WAS checked" ;; |
| 203 | + *) ok "a provider that states its list draws no note" ;; |
| 204 | + esac |
| 205 | +fi |
| 206 | + |
| 207 | +# ── CHANGE 3. The borrowed name is withdrawn ──────────────────────────────── |
| 208 | +# |
| 209 | +# `__CYGWIN__` was left defined so that code needing "PE object format with a |
| 210 | +# POSIX-presenting C environment" had a name. A 30-member measurement found |
| 211 | +# four members reading it as "Win32 is available" and reaching windows.h. |
| 212 | +# This needs a real openkal graph, so it skips rather than failing when the |
| 213 | +# sandbox cannot reach one. |
| 214 | +section "D. __CYGWIN__ is withdrawn on a Windows target presenting POSIX (CHANGE)" |
| 215 | +d="$root/d"; rm -rf "$d"; mkdir -p "$d/src" |
| 216 | +cat > "$d/src/main.cpp" <<'EOF' |
| 217 | +#if defined(__CYGWIN__) || defined(__CYGWIN32__) |
| 218 | +#error "the borrowed name is still defined" |
| 219 | +#endif |
| 220 | +#if !defined(__MCPP_TARGET_WINDOWS__) |
| 221 | +#error "mcpp's own name for the target is missing" |
| 222 | +#endif |
| 223 | +#if defined(_WIN32) |
| 224 | +#error "presents = posix must suppress _WIN32" |
| 225 | +#endif |
| 226 | +#if !defined(__OPENKAL__) |
| 227 | +#error "__OPENKAL__ is not defined over a resolved openkal layer" |
| 228 | +#endif |
| 229 | +#if defined(__openkal__) |
| 230 | +#error "the lower-case spelling is still defined" |
| 231 | +#endif |
| 232 | +int main() { return 0; } |
| 233 | +EOF |
| 234 | +cat > "$d/mcpp.toml" <<'EOF' |
| 235 | +[package] |
| 236 | +name = "withdrawal-probe" |
| 237 | +version = "0.1.0" |
| 238 | +
|
| 239 | +[dependencies] |
| 240 | +openkal-llvm-runtime = "0.14.0" |
| 241 | +EOF |
| 242 | +out=$(cd "$d" && "$STORE" build --target x86_64-windows-gnu 2>&1) |
| 243 | +rc=$? |
| 244 | +case "$out" in |
| 245 | + *"openkal-llvm-runtime"*"not found"*|*"did not resolve"*) |
| 246 | + skip "openkal-llvm-runtime 0.14.0 did not resolve from the index" ;; |
| 247 | + *) |
| 248 | + if [ $rc -eq 0 ]; then |
| 249 | + ok "the borrowed name is gone, mcpp's own name is there, over openkal" |
| 250 | + else |
| 251 | + case "$out" in |
| 252 | + *"borrowed name is still defined"*) fail "__CYGWIN__ is still defined" ;; |
| 253 | + *"own name for the target is missing"*) fail "__MCPP_TARGET_WINDOWS__ is missing" ;; |
| 254 | + *"__OPENKAL__ is not defined"*) fail "__OPENKAL__ is missing over openkal" ;; |
| 255 | + *"lower-case spelling is still defined"*) fail "__openkal__ is still defined" ;; |
| 256 | + *"presents = posix"*) fail "_WIN32 survived the substitution" ;; |
| 257 | + *) skip "the openkal Windows graph did not build in this sandbox" ;; |
| 258 | + esac |
| 259 | + fi ;; |
| 260 | +esac |
| 261 | + |
| 262 | +# ── GUARD. A package declaring nothing is untouched ───────────────────────── |
| 263 | +section "E. a package declaring neither key builds and runs (GUARD)" |
| 264 | +e="$root/e"; rm -rf "$e"; mkdir -p "$e/src" |
| 265 | +printf '#include <cstdio>\nint main(){std::puts("plain ok");return 0;}\n' > "$e/src/main.cpp" |
| 266 | +cat > "$e/mcpp.toml" <<'EOF' |
| 267 | +[package] |
| 268 | +name = "plain" |
| 269 | +version = "0.1.0" |
| 270 | +EOF |
| 271 | +if (cd "$e" && "$STORE" build >/dev/null 2>&1) \ |
| 272 | + && "$e"/target/*/*/bin/plain 2>/dev/null | grep -q "plain ok"; then |
| 273 | + ok "a package that declares nothing builds and runs" |
| 274 | +else |
| 275 | + fail "a package that declares nothing must be untouched by this release" |
| 276 | +fi |
| 277 | + |
| 278 | +# ── GUARD. An openkal program from the published index ────────────────────── |
| 279 | +section "F. an openkal program from the published index (GUARD)" |
| 280 | +f="$root/f"; rm -rf "$f"; mkdir -p "$f/src" |
| 281 | +printf '#include <cstdio>\nint main(){std::puts("openkal ok");return 0;}\n' > "$f/src/main.cpp" |
| 282 | +cat > "$f/mcpp.toml" <<'EOF' |
| 283 | +[package] |
| 284 | +name = "openkal-hello" |
| 285 | +version = "0.1.0" |
| 286 | +
|
| 287 | +[dependencies] |
| 288 | +openkal-llvm-runtime = "0.14.0" |
| 289 | +EOF |
| 290 | +if (cd "$f" && "$STORE" build >/dev/null 2>&1); then |
| 291 | + if "$f"/target/*/*/bin/openkal-hello 2>/dev/null | grep -q "openkal ok"; then |
| 292 | + ok "an openkal program builds and runs from the published index" |
| 293 | + else |
| 294 | + fail "the openkal program built and did not run" |
| 295 | + fi |
| 296 | +else |
| 297 | + skip "openkal-llvm-runtime 0.14.0 did not resolve from the index" |
| 298 | +fi |
| 299 | + |
| 300 | +printf '\n-- summary --\nfails=%d\nnot run:%s\n' "$fails" "${skipped:- |
| 301 | + (none)}" |
| 302 | +[ "$fails" -eq 0 ] |
0 commit comments