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
84 changes: 84 additions & 0 deletions .agents/tools/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# `.agents/tools/` — the exit-code contract

Every script here reports through its exit code. There are four states, and a
caller has to handle all four:

```
0 = proven the assertion was evaluated and holds
1 = broken the assertion was evaluated and fails
2 = inconclusive the check ran but cannot distinguish its cases
3 = could-not-run this machine cannot exercise the check at all
```

**A caller MUST map 3 to "not run". It MUST NOT fold 3 into a pass**, and it
should not fold it into a failure either — a machine without an AMD GPU has not
found a bug in radeonsi.

## Why 3 exists

`skip()` in `graphics/selfcontained-check.sh` used to `exit 0`, and 0 is what
the caller reads as "S1-S4 pass". A machine without bwrap, or without a compiler
inside the subos, therefore printed

```
✓ empty-host self-containment S1-S4 pass
```

for a check that ran nothing at all. Not a weaker pass, not a partial one: the
same green tick, the same words, from a script that exited before it built the
probe.

That is the exact bug class this tooling exists to catch, sitting inside the
tooling. Not-run and succeeded produced identical output — which is the whole
reason `verify-stack.sh` counts a third outcome, and the reason
`selfcontained-check.sh` runs a control container before it is willing to blame
the closure.

The reason it survived is worth keeping too: `verify-stack.sh` happens to probe
`bwrap` itself before calling the script, so the one skip anybody exercised was
shadowed by a caller-side guard. Every other skip path was live, and each of
them printed the tick.

## What each state means in practice

**2 and 3 are different questions.** 2 says the check ran and its result does
not separate the cases it was written to separate — `selfcontained-check.sh`
returns it when the *control* container fails, because then a sealed-container
failure proves nothing about self-containment and "S1: closure incomplete" would
name the wrong cause. 3 says nothing was measured, and says why: no bwrap, no
GPU, no compiler, no `/dev/dxg`, no payload to look at.

A check that cannot tell which it is should return 2. A wrong cause is worse
than no cause, because someone acts on it.

**1 is a claim about the subject, not about the environment.** A missing
`patchelf` is not a broken package. A missing subos is not an unsealed closure.
Build scripts follow the same split: 3 when the build never started (no subos,
no cmake, wrong architecture), 1 only when it started and broke — that is the
only status worth opening a log for.

An argument error (unknown flag, missing required option) sits outside the four:
nothing was checked and no verdict exists. Those exit 2, which is the safe
direction — a caller that follows the contract will not read it as a pass.

## Obligations on a caller

- Match on the code. `if script; then ok; else bad; fi` is the caller half of
the same bug: it renders 2 and 3 as red. `verify-stack.sh` did exactly this to
`verify-host-link.sh`, so a machine with no host compiler got a failing NVIDIA
cell for a probe that was never built.
- Pass the child's code through rather than rewriting it. `|| return 1` in a
driver loop erases the distinction the child went to the trouble of making.
- Carry the reason, not just the state. `verify-stack.sh`'s `na()` requires one,
because "not applicable here" with no reason is indistinguishable from "nobody
implemented it".
- Never let an absent tool answer the question. `patchelf --print-rpath` on a
machine with no patchelf prints nothing, and "no host directories on the
RPATH" is what nothing looks like. Probe for the tool; route to not-run.

## Reporting coverage

`verify-stack.sh` is the union point: it runs the whole matrix on one machine
and marks every cell it could not exercise. No single machine covers the matrix,
so the not-run cells are a recruitment list rather than an embarrassment. See
`graphics/collect-matrix.md` for what to run and where to send it.
12 changes: 11 additions & 1 deletion .agents/tools/build-llvm-subpkg.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ set -euo pipefail

die() { echo "error: $*" >&2; exit 1; }
log() { echo ">> $*" >&2; }
# 3 = this machine cannot do the work; 1 = it tried and the work is wrong.
# Contract: .agents/tools/README.md.
skip() { echo "SKIP: $*" >&2; exit 3; }

IN="" PKG="" VERSION="" PLATFORM="" ARCH="" OUT="$PWD" FORMATS=""
while [ $# -gt 0 ]; do
Expand Down Expand Up @@ -208,8 +211,10 @@ do_libcxx() {
cxxdir=$(dirname "$(find "$DEST/lib" -maxdepth 2 -name 'libc++.so.1' 2>/dev/null | head -1)")
[ -n "$cxxdir" ] && [ -d "$cxxdir" ] \
|| die "libatomic: cannot locate libc++ triple lib dir under $DEST/lib"
# 3, not 1: the carve is fine, this host just has no GCC to source
# libatomic from. Producing the package here is impossible, not wrong.
command -v gcc >/dev/null 2>&1 \
|| die "libatomic: gcc not on build host (needed to source libatomic for self-containment)"
|| skip "libatomic: gcc not on build host (needed to source libatomic for self-containment)"
atomic_real=$(readlink -f "$(gcc -print-file-name=libatomic.so.1)" 2>/dev/null)
[ -n "$atomic_real" ] && [ -e "$atomic_real" ] \
|| die "libatomic: 'gcc -print-file-name=libatomic.so.1' did not resolve to a real file"
Expand Down Expand Up @@ -239,6 +244,11 @@ esac

# --- macOS self-containment check (Mach-O LC_LOAD_DYLIB) -------------------
if [ "$PLATFORM" = "macosx" ] && [ -d "$DEST/bin" ]; then
# Without this probe a missing python3 exits 127 through the `|| die` below
# and the carve reports "self-containment check failed" -- a specific and
# entirely wrong claim about the bundle. The Mach-O reader never ran.
command -v python3 >/dev/null 2>&1 \
|| skip "no python3 — the macOS self-containment check was NOT performed on this bundle"
log "verifying macOS binaries are self-contained (system-only dylibs) ..."
# only real Mach-O files (skip symlinks)
mapfile -t MACHO < <(find "$DEST/bin" -type f)
Expand Down
9 changes: 9 additions & 0 deletions .agents/tools/build-llvm-tools.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ set -euo pipefail

die() { echo "error: $*" >&2; exit 1; }
log() { echo ">> $*" >&2; }
# 3 = this machine cannot do the work; 1 = it tried and the work is wrong.
# Contract: .agents/tools/README.md.
skip() { echo "SKIP: $*" >&2; exit 3; }

IN="" VERSION="" PLATFORM="" ARCH="" OUT="$PWD" FMT="tar.xz"
while [ $# -gt 0 ]; do
Expand Down Expand Up @@ -108,6 +111,12 @@ log " + lib/clang/${RESVER}/include ($(du -sh "$WORK/$BUNDLE/lib/clang/${RESVE

# --- macOS self-containment check (Mach-O LC_LOAD_DYLIB) -------------------
if [ "$PLATFORM" = "macosx" ]; then
# Without this probe a missing python3 exits 127 through the `|| die`, and
# the carve reports "self-containment check failed" -- a specific,
# actionable and entirely wrong claim about the bundle. The Mach-O reader
# never ran.
command -v python3 >/dev/null 2>&1 \
|| skip "no python3 — the macOS self-containment check was NOT performed on this bundle"
log "verifying macOS binaries are self-contained (system-only dylibs) ..."
python3 - "$WORK/$BUNDLE/bin"/* <<'PY' || die "self-containment check failed"
import struct, sys
Expand Down
6 changes: 5 additions & 1 deletion .agents/tools/build-musl.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,12 @@ DIST="$WORK/dist"

log() { echo "[build:$NAME] $*"; }
fail() { echo "[build:$NAME] FAIL: $*" >&2; exit 1; }
# 3, not 1. Exit-code contract: .agents/tools/README.md. "this machine is the
# wrong architecture" is not a failed build, and a matrix driver that reads 1 as
# a build failure would open a bug against musl for a working script.
skip() { echo "[build:$NAME] SKIP: $*" >&2; exit 3; }

[[ "$ARCH" == "x86_64" ]] || fail "this script only builds the x86_64 payload (host is $ARCH)"
[[ "$ARCH" == "x86_64" ]] || skip "this script only builds the x86_64 payload (host is $ARCH)"

rm -rf "$STAGE"; mkdir -p "$SRC" "$STAGE" "$DIST"

Expand Down
5 changes: 4 additions & 1 deletion .agents/tools/graphics/build-glibc.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,11 @@ DIST="$WORK/dist"

log() { echo "[gfx-build:$NAME] $*"; }
fail() { echo "[gfx-build:$NAME] FAIL: $*" >&2; exit 1; }
# 3, not 1: nothing was built because there was nowhere to build it. See the
# exit-code contract in .agents/tools/README.md.
skip() { echo "[gfx-build:$NAME] SKIP: $*" >&2; exit 3; }

[[ -d "$SUBOS" ]] || fail "subos '$SUBOS_NAME' not found"
[[ -d "$SUBOS" ]] || skip "subos '$SUBOS_NAME' not found — xlings subos new $SUBOS_NAME"
rm -rf "$STAGE"; mkdir -p "$SRC" "$STAGE" "$DIST"

# Same shape as the published 2.39, so a home holding both resolves them the
Expand Down
Loading
Loading