From 6497f79966597fa7a47d9b7664597b9fca47f059 Mon Sep 17 00:00:00 2001 From: fred Date: Sun, 16 Aug 2026 15:07:41 -0500 Subject: [PATCH] quality: scan the framework, not just tools/, in the enumeration guard (#1017) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../quality/scripts/check-test-enumeration.sh | 14 +++++++--- .../scripts/test-check-test-enumeration.sh | 28 +++++++++++++++++++ packages/mosaic/package.json | 2 +- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/packages/mosaic/framework/tools/quality/scripts/check-test-enumeration.sh b/packages/mosaic/framework/tools/quality/scripts/check-test-enumeration.sh index 0bab4467..66749ef3 100755 --- a/packages/mosaic/framework/tools/quality/scripts/check-test-enumeration.sh +++ b/packages/mosaic/framework/tools/quality/scripts/check-test-enumeration.sh @@ -57,12 +57,18 @@ done PKG_JSON="$ROOT/packages/mosaic/package.json" CI_YML="$ROOT/.woodpecker/ci.yml" 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" for f in "$PKG_JSON" "$CI_YML"; do [[ -f "$f" ]] || { echo "FAIL: required surface file missing: $f" >&2; exit 2; } 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() { printf 'FAIL %s\n' "$1"; fail_count=$(( fail_count + 1 )); } @@ -90,7 +96,7 @@ print("\n".join(seen)) 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: # 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 @@ -98,7 +104,7 @@ PY # 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. 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 ------------------------------- 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" unlisted=$(( unlisted + 1 )) fi -done < <(find "$TOOLS_DIR" -type f -name '*.sh' | sort) +done < <(find "$FRAMEWORK_DIR" -type f -name '*.sh' | sort) if (( fail_count > 0 )); then printf 'enumeration guard: %d failure(s) — population %d, enumerated (in-population) %d, excluded %d\n' \ diff --git a/packages/mosaic/framework/tools/quality/scripts/test-check-test-enumeration.sh b/packages/mosaic/framework/tools/quality/scripts/test-check-test-enumeration.sh index 05170632..420988a1 100755 --- a/packages/mosaic/framework/tools/quality/scripts/test-check-test-enumeration.sh +++ b/packages/mosaic/framework/tools/quality/scripts/test-check-test-enumeration.sh @@ -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" 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 printf 'enumeration-guard needles: %d passed, %d failed\n' "$PASS" "$FAIL" (( FAIL == 0 )) diff --git a/packages/mosaic/package.json b/packages/mosaic/package.json index 98cf494f..48336007 100644 --- a/packages/mosaic/package.json +++ b/packages/mosaic/package.json @@ -25,7 +25,7 @@ "lint": "eslint src", "typecheck": "tsc --noEmit", "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": { "@mosaicstack/brain": "workspace:*",