Compare commits

..
Author SHA1 Message Date
fred 6497f79966 quality: scan the framework, not just tools/, in the enumeration guard (#1017)
ci/woodpecker/pr/ci Pipeline failed
The guard's population is 'basename matches *test*.sh'. That is not a tools/
property, but Direction A scanned only framework/tools/, so a suite in a SIBLING
of tools/ was structurally invisible to the guard whose stated purpose is making
that impossible.

The live specimen, found by @scooby on origin/next:
framework/systemd/user/test-fleet-units.sh — a population member by the guard's
own definition, named on neither package.json nor ci.yml, and in no exclusion.
It is the only such file today. Same shape as #1017's original 17-of-39,
reintroduced for one subtree.

Three symmetric changes. The scan root moves to the framework. The S2 ci.yml
regex widens with it — not cosmetic: S1 is already general, S2 was the only
tools-scoped surface, so without it 'enumerate the file' would not be a
reachable disposition and an exclusion would be the only way to green. And the
directory-exists precondition follows the root it guards.

Disposition for the specimen: enumerated, not excluded. Its tmux block
self-skips on `command -v tmux && cc`; the rest — the unit-file assertions and
`systemd-analyze verify --user` — is real structural coverage that runs in CI
today. Measured in a CI shape (/usr/bin minus tmux, cc, systemd-analyze): rc=0.
That is strictly better than the two send-message siblings at exclusion lines
31-32, which have no CI-valuable tmux-free half.

Needles n9/c5 added, since a widening with no needle is the same silence one
layer up. n9 fails against the original guard. c5's scope is narrower than it
looks and the comment records the measurement: it passes vacuously on the
original and discriminates against the half-patch (scan wide, S2 narrow), which
is the realistic future regression.

    guard, real tree:  OK — population 53 (was 52), enumerated 38, excluded 15
    needles:           16 passed, 0 failed
2026-08-16 15:07:41 -05:00
5 changed files with 39 additions and 102 deletions
@@ -268,16 +268,6 @@ esac
_build_runtime_bin_prefix() { _build_runtime_bin_prefix() {
local candidates=() local candidates=()
if [ -n "$MOSAIC_RUNTIME_BIN" ]; then candidates+=("$MOSAIC_RUNTIME_BIN"); fi if [ -n "$MOSAIC_RUNTIME_BIN" ]; then candidates+=("$MOSAIC_RUNTIME_BIN"); fi
# A host with no system Node gets one bootstrapped here by tools/install.sh, which
# records it in ~/.profile. The fleet unit runs `env -i ... bash --noprofile --norc`
# by design, so ~/.profile is never read and the directory has to be named here.
# The npm probe below cannot cover this: it reports a package prefix
# (~/.npm-global), never a Node runtime directory. It sits ahead of the npm probe so
# the bootstrapped runtime wins on a host that has both — that is the one the installer
# verified — while an explicit MOSAIC_RUNTIME_BIN still outranks it.
# Runtime binaries are `#!/usr/bin/env node`, so without this the pane resolves the
# binary and then dies on `env: 'node': No such file or directory`.
candidates+=("$PANE_HOME/.mosaic/node/current/bin")
if command -v npm >/dev/null 2>&1; then if command -v npm >/dev/null 2>&1; then
local npm_prefix local npm_prefix
npm_prefix=$(npm config get prefix 2>/dev/null) || true npm_prefix=$(npm config get prefix 2>/dev/null) || true
@@ -348,93 +348,6 @@ for blocked in LD_PRELOAD= BASH_ENV= MOSAIC_UNTRUSTED_SENTINEL=; do
echo "$pane_environment" | grep -qF "$blocked" && fail "runtime pane received $blocked" echo "$pane_environment" | grep -qF "$blocked" && fail "runtime pane received $blocked"
done done
# #1256. On a host with no system Node, tools/install.sh bootstraps one into
# ~/.mosaic/node/ and writes that directory to ~/.profile. The fleet unit runs
# `env -i ... bash --noprofile --norc`, so ~/.profile is never read — correctly, by
# design — and _build_runtime_bin_prefix does not list the bootstrap directory. Its
# `npm config get prefix` branch cannot cover the gap either: the installer points
# npm's prefix at ~/.npm-global, so that branch contributes the npm-global directory
# and never the Node one, however it resolves.
#
# The property under test is not "the string is in PATH". It is that the pane can
# EXECUTE a Node-shebang runtime binary — which is what `mosaic` is
# (`#!/usr/bin/env node`) and what actually failed: measured on a greenfield VM as
# `env: 'node': No such file or directory` after a clean install that reported success.
#
# So this case runs the pane for real and requires it to have run. A PATH-substring
# assertion would pass on a fix that put the directory in the wrong position, and it
# would keep passing if the pane later stopped running for some unrelated reason.
: > "$TMUX_CALLS"
HOME_NODE="$ROOT/bootstrap-node/.config/mosaic"
write_generated "$HOME_NODE" "coder-node"
NODE_PANE_HOME="${HOME_NODE%/.config/mosaic}"
NODE_BOOTSTRAP_BIN="$NODE_PANE_HOME/.mosaic/node/current/bin"
mkdir -p "$NODE_BOOTSTRAP_BIN"
# The bootstrapped runtime. It records that it ran, which is the evidence this case
# turns on: no node reachable from the pane means no marker.
cat > "$NODE_BOOTSTRAP_BIN/node" <<'SHIM'
#!/usr/bin/env bash
set -euo pipefail
env -0 > "${MOSAIC_HOME:?}/fleet/pane-environment"
SHIM
chmod +x "$NODE_BOOTSTRAP_BIN/node"
# write_generated plants its symlinks under the MOSAIC_HOME it is given; here the
# pane's HOME is the trusted parent, so the pane's view of "installed" is this
# directory instead. `pi` is what #1241 resolves against PANE_PATH; `mosaic` is what
# the pane then executes, and it is a Node script — not a bash script that would run
# anywhere and quietly hide the defect.
mkdir -p "$NODE_PANE_HOME/.npm-global/bin"
ln -sf "$FAKE_BIN/pi" "$NODE_PANE_HOME/.npm-global/bin/pi"
printf '#!/usr/bin/env node\n' > "$NODE_PANE_HOME/.npm-global/bin/mosaic"
chmod +x "$NODE_PANE_HOME/.npm-global/bin/mosaic"
# The npm branch is modelled ALIVE and still cannot close the gap, which is the
# stronger statement. An earlier draft of this case tried to model npm as absent —
# true on a real bootstrap host, where npm lives only in the Node directory — and it
# refused to run anywhere npm is in the system path, i.e. most machines. It was also
# the weaker claim: it would have proven only that a dead branch supplies nothing.
#
# On a bootstrap host the installer sets npm's prefix to ~/.npm-global. So even with
# `command -v npm` true and the branch executing, `npm config get prefix` yields the
# npm-global directory and never the Node one. The gap does not depend on whether
# that branch runs.
NODE_LAUNCHER_BIN="$ROOT/bootstrap-node-launcher-bin"
mkdir -p "$NODE_LAUNCHER_BIN"
ln -sf "$FAKE_BIN/tmux" "$NODE_LAUNCHER_BIN/tmux"
ln -sf "$FAKE_BIN/npm" "$NODE_LAUNCHER_BIN/npm"
/usr/bin/env -i \
"HOME=$NODE_PANE_HOME" \
"PATH=$NODE_LAUNCHER_BIN:/usr/bin:/bin" \
"MOSAIC_HOME=$HOME_NODE" \
"MOSAIC_TEST_TMUX_CALLS=$TMUX_CALLS" \
"MOSAIC_TEST_HOME=$NODE_PANE_HOME" \
"MOSAIC_TEST_NPM_PREFIX=$NODE_PANE_HOME/.npm-global" \
MOSAIC_TEST_FLEET_OWNER=123e4567-e89b-12d3-a456-426614174000 \
MOSAIC_TEST_EXECUTE_PANE=1 \
"MOSAIC_TEST_PANE_PID=$$" \
"$START" coder-node
[ -f "$HOME_NODE/fleet/pane-environment" ] || \
fail "pane could not execute a Node-shebang runtime: $NODE_BOOTSTRAP_BIN is absent from PANE_PATH (#1256)"
node_pane_environment=$(tr '\0' '\n' < "$HOME_NODE/fleet/pane-environment")
# Colon-pad and match a whole element. A regex with `(^|:)` after `.*` looks like it
# does this and does not: an anchor cannot match mid-pattern, so it silently requires
# a leading colon and rejects the directory in FIRST position — which is where THIS
# FIXTURE puts it: it runs under `env -i` with no MOSAIC_RUNTIME_BIN, so the bootstrap
# directory leads. That is a property of the fixture, not of the fix — in general the
# directory sits second, after MOSAIC_RUNTIME_BIN. The colon padding makes the
# assertion position-independent either way, which is why it is written this way and
# not with an anchor. That produced a failure reading "pane ran but PANE_PATH does not
# carry <dir>" against a PATH whose first element was that dir.
node_pane_path=":$(printf '%s\n' "$node_pane_environment" | sed -n 's/^PATH=//p' | head -1):"
case "$node_pane_path" in
*":$NODE_BOOTSTRAP_BIN:"*) ;;
*) fail "pane ran but PANE_PATH does not carry $NODE_BOOTSTRAP_BIN (PATH=$node_pane_path)" ;;
esac
write_interaction_generated() { write_interaction_generated() {
local home="$1" local home="$1"
local agent="$2" local agent="$2"
@@ -57,12 +57,18 @@ done
PKG_JSON="$ROOT/packages/mosaic/package.json" PKG_JSON="$ROOT/packages/mosaic/package.json"
CI_YML="$ROOT/.woodpecker/ci.yml" CI_YML="$ROOT/.woodpecker/ci.yml"
TOOLS_DIR="$ROOT/packages/mosaic/framework/tools" TOOLS_DIR="$ROOT/packages/mosaic/framework/tools"
# The population is "basename matches *test*.sh", which is not a tools/ property.
# Direction A used to scan TOOLS_DIR, so a suite in a SIBLING of tools/ was invisible
# to the guard whose whole purpose is making that impossible — measured on origin/next
# as systemd/user/test-fleet-units.sh, on no CI surface and in no exclusion (@scooby).
# Scan the framework, so the scanned surface matches the claimed property.
FRAMEWORK_DIR="$ROOT/packages/mosaic/framework"
EXCLUSIONS="$TOOLS_DIR/quality/test-enumeration-exclusions.txt" EXCLUSIONS="$TOOLS_DIR/quality/test-enumeration-exclusions.txt"
for f in "$PKG_JSON" "$CI_YML"; do for f in "$PKG_JSON" "$CI_YML"; do
[[ -f "$f" ]] || { echo "FAIL: required surface file missing: $f" >&2; exit 2; } [[ -f "$f" ]] || { echo "FAIL: required surface file missing: $f" >&2; exit 2; }
done done
[[ -d "$TOOLS_DIR" ]] || { echo "FAIL: tools dir missing: $TOOLS_DIR" >&2; exit 2; } [[ -d "$FRAMEWORK_DIR" ]] || { echo "FAIL: framework dir missing: $FRAMEWORK_DIR" >&2; exit 2; }
fail_count=0 fail_count=0
fail() { printf 'FAIL %s\n' "$1"; fail_count=$(( fail_count + 1 )); } fail() { printf 'FAIL %s\n' "$1"; fail_count=$(( fail_count + 1 )); }
@@ -90,7 +96,7 @@ print("\n".join(seen))
PY PY
) )
# --- Surface 2: ci.yml, every framework/tools token wherever it appears ------ # --- Surface 2: ci.yml, every framework token wherever it appears ------------
# Comment lines (first non-whitespace char is #) are skipped BEFORE matching: # Comment lines (first non-whitespace char is #) are skipped BEFORE matching:
# commenting an invocation out is the most common way a suite actually gets # commenting an invocation out is the most common way a suite actually gets
# disabled, and a raw-text regex would keep calling it enumerated (F1, 20155 on # disabled, and a raw-text regex would keep calling it enumerated (F1, 20155 on
@@ -98,7 +104,7 @@ PY
# in a TRAILING comment on a live line still matches; no such line exists today # in a TRAILING comment on a live line still matches; no such line exists today
# and full fidelity would need a YAML parser the CI image does not ship. # and full fidelity would need a YAML parser the CI image does not ship.
mapfile -t S2 < <(grep -vE '^[[:space:]]*#' "$CI_YML" \ mapfile -t S2 < <(grep -vE '^[[:space:]]*#' "$CI_YML" \
| grep -oE 'packages/mosaic/framework/tools/[A-Za-z0-9_./-]+\.(sh|py)' | sort -u) | grep -oE 'packages/mosaic/framework/[A-Za-z0-9_./-]+\.(sh|py)' | sort -u)
# --- Union, and its population-restricted view ------------------------------- # --- Union, and its population-restricted view -------------------------------
declare -A ENUM=() ENUM_POP=() declare -A ENUM=() ENUM_POP=()
@@ -154,7 +160,7 @@ while IFS= read -r f; do
fail "UNENUMERATED: '$rel' exists on disk but is neither enumerated on any CI surface nor signed in the exclusions file" fail "UNENUMERATED: '$rel' exists on disk but is neither enumerated on any CI surface nor signed in the exclusions file"
unlisted=$(( unlisted + 1 )) unlisted=$(( unlisted + 1 ))
fi fi
done < <(find "$TOOLS_DIR" -type f -name '*.sh' | sort) done < <(find "$FRAMEWORK_DIR" -type f -name '*.sh' | sort)
if (( fail_count > 0 )); then if (( fail_count > 0 )); then
printf 'enumeration guard: %d failure(s) — population %d, enumerated (in-population) %d, excluded %d\n' \ printf 'enumeration guard: %d failure(s) — population %d, enumerated (in-population) %d, excluded %d\n' \
@@ -161,6 +161,34 @@ R="$(fixture n7)"
excl "$R" "packages/mosaic/framework/tools/quality/scripts/verify-thing.sh | not a suite but signing it anyway" excl "$R" "packages/mosaic/framework/tools/quality/scripts/verify-thing.sh | not a suite but signing it anyway"
expect NEEDLE 1 "out-of-population exclusion rejected" --out "EXCLUSION OUTSIDE POPULATION" -- "$R" expect NEEDLE 1 "out-of-population exclusion rejected" --out "EXCLUSION OUTSIDE POPULATION" -- "$R"
echo "=== n9/c5: a suite in a SIBLING of tools/ is in the population (@scooby, 2026-08-16) ==="
# Every other fixture here lives under framework/tools/, which is how the guard came to
# scan TOOLS_DIR while claiming a population defined by basename alone. The real specimen
# was framework/systemd/user/test-fleet-units.sh: a member by the guard's own definition,
# on no CI surface, in no exclusion, and structurally unreachable by the scan. n9 is that
# blind spot; without it a future narrowing back to TOOLS_DIR passes all fourteen needles.
R="$(fixture n9)"
mkdir -p "$R/packages/mosaic/framework/systemd/user"
printf '#!/usr/bin/env bash\nexit 0\n' > "$R/packages/mosaic/framework/systemd/user/test-sibling.sh"
expect NEEDLE 1 "suite outside tools/ but inside framework/ is enumerable, not invisible" \
--out "UNENUMERATED: 'packages/mosaic/framework/systemd/user/test-sibling.sh'" -- "$R"
# c5 is why the S2 regex had to widen WITH the scan: detecting the file is useless if the
# fix for it cannot be recognised. Enumerating a sibling-directory suite on ci.yml must
# clear the finding — under a tools/-scoped S2 it stays UNENUMERATED forever and the only
# reachable disposition is an exclusion.
#
# Measured scope of what c5 catches, because it is narrower than it looks: against the
# ORIGINAL guard (both hunks absent) c5 passes vacuously — the scan never sees the file
# and S2 never matches it, so nothing is asserted. It discriminates against the HALF-patch
# — scan widened, S2 narrowed back — which is the realistic future regression, and it was
# confirmed red in exactly that state. n9 is the one that fails on the original.
R="$(fixture c5)"
mkdir -p "$R/packages/mosaic/framework/systemd/user"
printf '#!/usr/bin/env bash\nexit 0\n' > "$R/packages/mosaic/framework/systemd/user/test-sibling.sh"
printf ' - bash packages/mosaic/framework/systemd/user/test-sibling.sh\n' >> "$R/.woodpecker/ci.yml"
expect CONTROL 0 "enumerating a sibling-directory suite on ci.yml actually clears it" \
--out "enumeration guard: OK" -- "$R"
echo echo
printf 'enumeration-guard needles: %d passed, %d failed\n' "$PASS" "$FAIL" printf 'enumeration-guard needles: %d passed, %d failed\n' "$PASS" "$FAIL"
(( FAIL == 0 )) (( FAIL == 0 ))
+1 -1
View File
@@ -25,7 +25,7 @@
"lint": "eslint src", "lint": "eslint src",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"test": "vitest run --passWithNoTests && pnpm run test:framework-shell", "test": "vitest run --passWithNoTests && pnpm run test:framework-shell",
"test:framework-shell": "bash framework/tools/quality/scripts/check-test-enumeration.sh && bash framework/tools/quality/scripts/test-check-test-enumeration.sh && python3 src/lease-broker/daemon_deadline_unittest.py && python3 src/lease-broker/normative_fragments_unittest.py && python3 src/lease-broker/promotion_binding_unittest.py && python3 src/lease-broker/promotion_trigger_unittest.py && python3 src/lease-broker/receipt_challenge_unittest.py && python3 src/lease-broker/context_recovery_unittest.py && python3 src/lease-broker/recovery_runtime_unittest.py && python3 src/lease-broker/recovery_b1_adversarial_unittest.py && python3 src/lease-broker/receipt_observer_client_unittest.py && python3 src/lease-broker/invariant_r_unittest.py && python3 src/lease-broker/framework_skill_portability_unittest.py && python3 src/mutator-gate/runtime_tools_unittest.py && python3 src/mutator-gate/runtime_launch_guard_unittest.py && python3 src/mutator-gate/version_coupling_unittest.py && python3 framework/tools/lease-broker/check-runtime-launches.py --root ../.. && bash framework/tools/codex/test-pr-diff-context.sh && bash framework/tools/qa/test-deps-preflight.sh && bash framework/tools/git/test-pr-review-gitea-comment.sh && bash framework/tools/git/test-pr-review-repo-host-override.sh && bash framework/tools/git/test-ci-queue-wait-branch-absent.sh && bash framework/tools/git/test-ci-queue-wait-tristate.sh && bash framework/tools/git/test-ci-queue-wait-github-checks.sh && bash framework/tools/git/test-pr-merge-queue-branch.sh && bash framework/tools/git/test-pr-merge-head-pin.sh && bash framework/tools/git/test-pr-merge-message-field.sh && bash framework/tools/git/test-git-credential-mosaic.sh && bash framework/tools/git/test-gitea-token-identity.sh && bash framework/tools/woodpecker/test-terminal-green-contract.sh && bash framework/tools/_scripts/test-install-ordering-guard.sh && bash framework/tools/_scripts/test-mosaic-init-rce.sh && bash framework/tools/tmux/agent-send.test.sh && bash framework/tools/wake/test-wake-store-ack.sh && bash framework/tools/wake/test-wake-store-enqueue-race.sh && bash framework/tools/wake/test-wake-digest-hmac.sh && bash framework/tools/wake/test-wake-digest-quarantine.sh && bash framework/tools/wake/test-wake-detector.sh && bash framework/tools/wake/test-wake-fn-oracle.sh && bash framework/tools/wake/test-wake-reconcile.sh && bash framework/tools/wake/test-wake-beacon.sh && bash framework/tools/wake/test-wake-preimage.sh && bash framework/tools/wake/test-wake-install.sh && bash framework/tools/fleet/test-start-agent-session.sh && bash framework/tools/glpi/test-list-http-status.sh && bash framework/tools/orchestrator/test-board-roll.sh && bash framework/tools/woodpecker/test-ci-wait-exit-matrix.sh && bash framework/tools/_scripts/test-fleet-transport-check.sh" "test:framework-shell": "bash framework/tools/quality/scripts/check-test-enumeration.sh && bash framework/tools/quality/scripts/test-check-test-enumeration.sh && python3 src/lease-broker/daemon_deadline_unittest.py && python3 src/lease-broker/normative_fragments_unittest.py && python3 src/lease-broker/promotion_binding_unittest.py && python3 src/lease-broker/promotion_trigger_unittest.py && python3 src/lease-broker/receipt_challenge_unittest.py && python3 src/lease-broker/context_recovery_unittest.py && python3 src/lease-broker/recovery_runtime_unittest.py && python3 src/lease-broker/recovery_b1_adversarial_unittest.py && python3 src/lease-broker/receipt_observer_client_unittest.py && python3 src/lease-broker/invariant_r_unittest.py && python3 src/lease-broker/framework_skill_portability_unittest.py && python3 src/mutator-gate/runtime_tools_unittest.py && python3 src/mutator-gate/runtime_launch_guard_unittest.py && python3 src/mutator-gate/version_coupling_unittest.py && python3 framework/tools/lease-broker/check-runtime-launches.py --root ../.. && bash framework/tools/codex/test-pr-diff-context.sh && bash framework/tools/qa/test-deps-preflight.sh && bash framework/tools/git/test-pr-review-gitea-comment.sh && bash framework/tools/git/test-pr-review-repo-host-override.sh && bash framework/tools/git/test-ci-queue-wait-branch-absent.sh && bash framework/tools/git/test-ci-queue-wait-tristate.sh && bash framework/tools/git/test-ci-queue-wait-github-checks.sh && bash framework/tools/git/test-pr-merge-queue-branch.sh && bash framework/tools/git/test-pr-merge-head-pin.sh && bash framework/tools/git/test-pr-merge-message-field.sh && bash framework/tools/git/test-git-credential-mosaic.sh && bash framework/tools/git/test-gitea-token-identity.sh && bash framework/tools/woodpecker/test-terminal-green-contract.sh && bash framework/tools/_scripts/test-install-ordering-guard.sh && bash framework/tools/_scripts/test-mosaic-init-rce.sh && bash framework/tools/tmux/agent-send.test.sh && bash framework/tools/wake/test-wake-store-ack.sh && bash framework/tools/wake/test-wake-store-enqueue-race.sh && bash framework/tools/wake/test-wake-digest-hmac.sh && bash framework/tools/wake/test-wake-digest-quarantine.sh && bash framework/tools/wake/test-wake-detector.sh && bash framework/tools/wake/test-wake-fn-oracle.sh && bash framework/tools/wake/test-wake-reconcile.sh && bash framework/tools/wake/test-wake-beacon.sh && bash framework/tools/wake/test-wake-preimage.sh && bash framework/tools/wake/test-wake-install.sh && bash framework/tools/fleet/test-start-agent-session.sh && bash framework/tools/glpi/test-list-http-status.sh && bash framework/tools/orchestrator/test-board-roll.sh && bash framework/tools/woodpecker/test-ci-wait-exit-matrix.sh && bash framework/tools/_scripts/test-fleet-transport-check.sh && bash framework/systemd/user/test-fleet-units.sh"
}, },
"dependencies": { "dependencies": {
"@mosaicstack/brain": "workspace:*", "@mosaicstack/brain": "workspace:*",