fix(installer): close detector false-pass gaps
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/pr/greenfield-install Pipeline failed

This commit is contained in:
2026-08-05 19:00:22 -05:00
parent e5d5c8495a
commit 378bc1afe3
10 changed files with 227 additions and 39 deletions
+10 -5
View File
@@ -182,14 +182,19 @@ fi
printf '[fixture] installer_exit=%d done_claims=%s\n' \
"$install_status" "$(grep -cF 'Done.' "$install_log" || true)"
# P0 Resolve context
shell="$(getent passwd "$(id -u)" | cut -d: -f7)"
if [[ "$(id -u)" -ne 0 && "$home" == "/home/mosaic" && "$shell" == "/bin/bash" ]] \
# P0 Resolve context. Keep this final fixture row as discriminating as the
# installer's own P0 row: the expected-RED comparator binds this exact reason.
passwd_row="$(getent passwd "$(id -u)")"
target_user="$(printf '%s' "$passwd_row" | cut -d: -f1)"
passwd_home="$(printf '%s' "$passwd_row" | cut -d: -f6)"
shell="$(printf '%s' "$passwd_row" | cut -d: -f7)"
if [[ "$(id -u)" -eq 1001 && "$target_user" == "mosaic" \
&& "$home" == "/home/mosaic" && "$home" == "$passwd_home" && "$shell" == "/bin/bash" ]] \
&& ldd --version 2>&1 | grep -i 'glibc\|gnu libc' >/dev/null \
&& [[ "$(node -p 'Number(process.versions.node.split(".")[0])')" -ge 20 ]]; then
phase_pass P0 "target=mosaic uid=$(id -u) HOME=$home shell=$shell libc=glibc node=$(node --version)"
phase_pass P0 "target=$target_user uid=$(id -u) HOME=$home passwd_HOME=$passwd_home shell=$shell privilege=user arch=$(uname -m) libc=glibc node=$(node --version) npm=$(npm --version)"
else
phase_fail P0 "context unresolved or unsupported (uid=$(id -u) HOME=$home shell=${shell:-unknown})"
phase_fail P0 "context unresolved or unsupported (target=${target_user:-unknown} uid=$(id -u) HOME=$home passwd_HOME=${passwd_home:-unknown} shell=${shell:-unknown} privilege=user)"
fi
# P1 Preflight
+4 -1
View File
@@ -13,10 +13,11 @@ next-git-present phase P8=FAIL
next-git-present phase P9=FAIL
next-git-present require ^\[fixture\] resolved lane=next .*version=[0-9]+\.[0-9]+\.[0-9]+-next\.
next-git-present require ^\[fixture\] installer_exit=1 done_claims=0$
next-git-present phase-reason P0=target=mosaic uid=1001 HOME=/home/mosaic passwd_HOME=/home/mosaic shell=/bin/bash privilege=user arch=x86_64 libc=glibc node=v[0-9]+\.[0-9]+\.[0-9]+ npm=[0-9]+\.[0-9]+\.[0-9]+
next-git-present require ^\[SECRET-CONTROL\] PASS:
next-git-present require ^\[P3\] PASS: absolute_path=.* version=.* equals resolved lane version$
next-git-present require ^\[P4\] FAIL: NOT-MEASURED / UNDECLARED:
next-git-present require ^\[P6\] FAIL: broker absent but dead enforcement hooks are active
next-git-present require ^\[P6\] FAIL: runtime linking/activation action reported a required failure$
next-git-present forbid Done\.|MOSAIC_C1_CANARY_|CLI not found on PATH
main-git-present exit 1
main-git-present phase P0=PASS
@@ -31,6 +32,7 @@ main-git-present phase P8=FAIL
main-git-present phase P9=FAIL
main-git-present require ^\[fixture\] resolved lane=main .*version=[0-9]+\.[0-9]+\.[0-9]+$
main-git-present require ^\[fixture\] installer_exit=1 done_claims=0$
main-git-present phase-reason P0=target=mosaic uid=1001 HOME=/home/mosaic passwd_HOME=/home/mosaic shell=/bin/bash privilege=user arch=x86_64 libc=glibc node=v[0-9]+\.[0-9]+\.[0-9]+ npm=[0-9]+\.[0-9]+\.[0-9]+
main-git-present require ^\[SECRET-CONTROL\] PASS:
main-git-present require ^\[P3\] PASS: absolute_path=.* version=.* equals resolved lane version$
main-git-present require ^\[P4\] FAIL: NOT-MEASURED / UNDECLARED:
@@ -48,6 +50,7 @@ next-git-absent phase P7=PASS
next-git-absent phase P8=FAIL
next-git-absent phase P9=FAIL
next-git-absent require ^\[fixture\] installer_exit=1 done_claims=0$
next-git-absent phase-reason P0=target=mosaic uid=1001 HOME=/home/mosaic passwd_HOME=/home/mosaic shell=/bin/bash privilege=user arch=x86_64 libc=glibc node=v[0-9]+\.[0-9]+\.[0-9]+ npm=[0-9]+\.[0-9]+\.[0-9]+
next-git-absent require ^\[SECRET-CONTROL\] PASS:
next-git-absent require ^\[P1\] FAIL: undeclared/missing prerequisite\(s\)=git;
next-git-absent require ^\[P3\] FAIL: .*executable=no
1 # Pinned C1 expected-RED contract. Updating a verdict/reason requires review by the owning remediation lane.
13 next-git-present
14 next-git-present
15 next-git-present
16 next-git-present
17 next-git-present
18 next-git-present
19 next-git-present
20 next-git-present
21 next-git-present
22 main-git-present
23 main-git-present
32 main-git-present
33 main-git-present
34 main-git-present
35 main-git-present
36 main-git-present
37 main-git-present
38 main-git-present
50 next-git-absent
51 next-git-absent
52 next-git-absent
53 next-git-absent
54 next-git-absent
55 next-git-absent
56 next-git-absent
+26
View File
@@ -201,6 +201,9 @@ if [[ -z "$dest" ]]; then
exit 1
fi
mkdir -p "$dest/stack/packages/mosaic/framework" "$dest/stack/apps/gateway"
if [[ "${MOSAIC_TEST_EXTRA_ARCHIVE_ROOT:-0}" == "1" ]]; then
mkdir -p "$dest/unexpected-second-root"
fi
cat > "$dest/stack/packages/mosaic/framework/install.sh" <<'FRAMEWORK'
#!/usr/bin/env bash
set -euo pipefail
@@ -384,6 +387,29 @@ set -e
grep -qF 'archive safety/integrity check failed' <<<"$OUTPUT"
[[ "$(node -p "require('$ACTIVE').status")" == "rolled-back" ]]
reset_state
echo "[test] source archive with multiple extracted roots fails instead of selecting by find order"
before_prefix="$(prefix_fingerprint)"
set +e
OUTPUT="$(
HOME="$HOME_DIR" \
MOSAIC_HOME="$MOSAIC_HOME" \
MOSAIC_PREFIX="$PREFIX" \
MOSAIC_NO_COLOR=1 \
MOSAIC_TEST_NPM_LOG="$LOG" \
MOSAIC_TEST_STATE="$STATE" \
MOSAIC_TEST_FAIL_NEXT_GATEWAY_INSTALL=1 \
MOSAIC_TEST_EXTRA_ARCHIVE_ROOT=1 \
PATH="$FAKE_BIN:$PATH" \
bash "$ROOT/tools/install.sh" --cli --next --yes --no-auto-launch 2>&1
)"
FAIL_STATUS=$?
set -e
[[ "$FAIL_STATUS" -ne 0 ]]
[[ "$(prefix_fingerprint)" == "$before_prefix" ]]
grep -qF 'expected exactly one extracted source root' <<<"$OUTPUT"
[[ "$(node -p "require('$ACTIVE').status")" == "rolled-back" ]]
reset_state
echo "[test] --dev source install does not require registry version resolution"
OUTPUT="$(
+28
View File
@@ -304,6 +304,34 @@ grep -q '^\[P4\] FAIL:.*owner/mode policy' "$TMP/p4-tree-mode.log" \
|| fail_case 'P4 did not attribute unsafe created-path mode'
chmod 0644 "$good_mosaic/AGENTS.md"
printf '[test] case: P4 fails closed when created-path enumeration is incomplete\n'
real_find="$(command -v find)"
cat > "$good_bin/find" <<FIND
#!/bin/bash
if [[ "\${1:-}" == '$good_mosaic' && "\${2:-}" == '-xdev' && "\${3:-}" == '-print0' ]]; then
printf '%s\\0' '$good_mosaic'
exit 73
fi
exec '$real_find' "\$@"
FIND
chmod 0755 "$good_bin/find"
printf '# hidden unsafe child\n' > "$good_mosaic/AGENTS.md"
chmod 0666 "$good_mosaic/AGENTS.md"
set +e
HOME="$good_home" MOSAIC_HOME="$good_mosaic" MOSAIC_PREFIX="$good_prefix" MOSAIC_NO_COLOR=1 \
PATH="$good_bin:/usr/local/bin:/usr/bin:/bin" bash "$ROOT/tools/install.sh" --check --next \
>"$TMP/p4-enumeration-failure.log" 2>&1
p4_enumeration_status=$?
set -e
rm -f "$good_bin/find"
[[ "$p4_enumeration_status" -ne 0 ]] \
&& pass_case 'P4 rejects an incomplete created-path inventory' \
|| fail_case 'P4 accepted a partial created-path inventory after find failed'
grep -q '^\[P4\] FAIL:.*enumeration failed' "$TMP/p4-enumeration-failure.log" \
&& pass_case 'P4 attributes the failed created-path enumeration' \
|| fail_case 'P4 did not report failed created-path enumeration'
chmod 0644 "$good_mosaic/AGENTS.md"
printf '[test] case: persisted required-action failures remain blocking\n'
for blocked_phase in P4 P6; do
node -e '
+32 -8
View File
@@ -657,15 +657,27 @@ state_path_owner_mode_ok() {
}
state_tree_owner_mode_ok() {
local root="$1" path policy
local root="$1" path policy scan valid=true
[[ -e "$root" ]] || return 0
scan="$(mktemp)" \
|| { STATE_POLICY_REASON="$root enumeration staging failed"; return 1; }
if ! find "$root" -xdev -print0 > "$scan"; then
STATE_POLICY_REASON="$root enumeration failed; created-path inventory is incomplete"
rm -f "$scan"
return 1
fi
while IFS= read -r -d '' path; do
policy=shared-read
case "$path" in
"$MOSAIC_HOME/credentials"|"$MOSAIC_HOME/credentials"/*|"$MOSAIC_HOME/SOUL.md"|"$MOSAIC_HOME/USER.md") policy=private ;;
esac
state_path_owner_mode_ok "$path" "$policy" || return
done < <(find "$root" -xdev -print0)
if ! state_path_owner_mode_ok "$path" "$policy"; then
valid=false
break
fi
done < "$scan"
rm -f "$scan"
[[ "$valid" == true ]]
}
state_resolved_version() {
@@ -1255,13 +1267,25 @@ PY
tar xzf "$archive" -C "$WORK_DIR" || return
state_json_line artifact P2 committed "lane=$GIT_REF source_commit=$RESOLVED_SOURCE_COMMIT sha256=$RESOLVED_SOURCE_DIGEST" || return
# Gitea archives extract to <repo-name>/ inside the work dir
EXTRACTED_DIR="$(find "$WORK_DIR" -maxdepth 1 -mindepth 1 -type d | head -1)" || return
if [[ -z "$EXTRACTED_DIR" ]] || [[ ! -d "$EXTRACTED_DIR" ]]; then
fail "Could not locate extracted source in archive."
ls -la "$WORK_DIR" >&2 || true # Diagnostic only; the named P2 failure is authoritative.
# Gitea archives must extract to exactly one <repo-name>/ directory. Capture
# and check the complete walk before selecting it: `find | head -1` both hides
# a failed enumeration and makes multiple roots depend on filesystem order.
local extracted_roots_file
local -a extracted_roots=()
extracted_roots_file="$(mktemp)" \
|| { fail "P2 Acquire artifacts failed: could not stage extracted-root inventory."; return 1; }
if ! find "$WORK_DIR" -maxdepth 1 -mindepth 1 -type d -print0 > "$extracted_roots_file"; then
rm -f "$extracted_roots_file"
fail "P2 Acquire artifacts failed: could not enumerate extracted source roots."
return 1
fi
mapfile -d '' -t extracted_roots < "$extracted_roots_file"
rm -f "$extracted_roots_file"
if [[ "${#extracted_roots[@]}" -ne 1 ]] || [[ ! -d "${extracted_roots[0]:-}" ]]; then
fail "P2 Acquire artifacts failed: expected exactly one extracted source root; found ${#extracted_roots[@]}."
return 1
fi
EXTRACTED_DIR="${extracted_roots[0]}"
}
# Build @mosaicstack/mosaic + @mosaicstack/gateway from source and install both
+19 -2
View File
@@ -23,7 +23,7 @@ declare -A allowed_case=(
declare -A expected_requires=(
[next-git-present]=6 [main-git-present]=6 [next-git-absent]=4
)
declare -A row_count=() exit_count=() require_count=() forbid_count=() phase_count=() unique_rows=()
declare -A row_count=() exit_count=() require_count=() forbid_count=() phase_count=() phase_reason_count=() unique_rows=()
while IFS= read -r raw; do
[[ -n "$raw" && "${raw:0:1}" != "#" ]] || continue
field_count="$(awk -F '\t' '{print NF}' <<<"$raw")"
@@ -46,6 +46,11 @@ while IFS= read -r raw; do
phase_key="$case_name|$phase"
phase_count[$phase_key]=$((${phase_count[$phase_key]:-0} + 1))
;;
phase-reason)
[[ "$expectation" =~ ^P0=(.+)$ ]] \
|| { echo "invalid expected-RED phase reason: case=$case_name value=$expectation" >&2; exit 2; }
phase_reason_count[$case_name|P0]=$((${phase_reason_count[$case_name|P0]:-0} + 1))
;;
require)
[[ -n "$expectation" ]] || { echo "empty expected-RED require row: case=$case_name" >&2; exit 2; }
require_count[$case_name]=$((${require_count[$case_name]:-0} + 1))
@@ -65,11 +70,13 @@ for case_name in "${expected_cases[@]}"; do
[[ "${phase_count[$case_name|$phase]:-0}" -eq 1 ]] \
|| { echo "expected-RED manifest requires exactly one $phase disposition for case=$case_name" >&2; exit 2; }
done
[[ "${phase_reason_count[$case_name|P0]:-0}" -eq 1 ]] \
|| { echo "expected-RED manifest requires exactly one P0 reason binding for case=$case_name" >&2; exit 2; }
[[ "${require_count[$case_name]:-0}" -eq "${expected_requires[$case_name]}" ]] \
|| { echo "expected-RED manifest require-row population changed for case=$case_name" >&2; exit 2; }
[[ "${forbid_count[$case_name]:-0}" -eq 1 ]] \
|| { echo "expected-RED manifest requires exactly one forbid row for case=$case_name" >&2; exit 2; }
expected_total=$((1 + 10 + expected_requires[$case_name] + 1))
expected_total=$((1 + 10 + 1 + expected_requires[$case_name] + 1))
[[ "${row_count[$case_name]:-0}" -eq "$expected_total" ]] \
|| { echo "expected-RED manifest row population changed for case=$case_name" >&2; exit 2; }
done
@@ -98,6 +105,16 @@ while IFS=$'\t' read -r case_name kind expectation; do
failures=$((failures + 1))
fi
;;
phase-reason)
phase="${expectation%%=*}"
expected_reason="${expectation#*=}"
last_row="$(grep -E "^\[$phase\] (PASS|FAIL):" "$LOG" | tail -n 1 || true)"
actual_reason="${last_row#*: }"
if [[ -z "$last_row" ]] || ! grep -Eq -- "^${expected_reason}$" <<<"$actual_reason"; then
echo "expected-RED phase reason mismatch: case=$CASE phase=$phase reason=${actual_reason:-missing}" >&2
failures=$((failures + 1))
fi
;;
require)
if ! grep -Eq -- "$expectation" "$LOG"; then
echo "expected-RED missing required evidence: case=$CASE regex=$expectation" >&2
+16 -2
View File
@@ -8,13 +8,13 @@ cat > "$TMP/match.log" <<'LOG'
[fixture] resolved lane=next package=@mosaicstack/mosaic@next version=0.0.50-next.999
[fixture] installer_exit=1 done_claims=0
[SECRET-CONTROL] PASS: seeded canary absent from complete scan population
[P0] PASS: supported context
[P0] PASS: target=mosaic uid=1001 HOME=/home/mosaic passwd_HOME=/home/mosaic shell=/bin/bash privilege=user arch=x86_64 libc=glibc node=v24.0.0 npm=10.6.2
[P1] PASS: preflight complete
[P2] PASS: pinned artifact
[P3] PASS: absolute_path=/home/test/.npm-global/bin/mosaic version=0.0.50-next.999 equals resolved lane version
[P4] FAIL: NOT-MEASURED / UNDECLARED: declaration absent
[P5] FAIL: identity absent
[P6] FAIL: broker absent but dead enforcement hooks are active
[P6] FAIL: runtime linking/activation action reported a required failure
[P7] PASS: no services requested
[P8] FAIL: shell path absent
[P9] FAIL: aggregate refusal
@@ -23,6 +23,13 @@ LOG
bash "$ROOT/tools/verify-greenfield-expected-red.sh" next-git-present "$TMP/match.log" 1 >/dev/null
printf '[test] PASS: matching detector findings make the CI verifier green\n'
sed 's/^\[P0\] PASS:.*/[P0] PASS: arbitrary unconditional success/' "$TMP/match.log" > "$TMP/vacuous-p0.log"
if bash "$ROOT/tools/verify-greenfield-expected-red.sh" next-git-present "$TMP/vacuous-p0.log" 1 >/dev/null 2>&1; then
echo '[test] FAIL: vacuous P0 PASS satisfied the expected-RED contract without identity/context evidence' >&2
exit 1
fi
printf '[test] PASS: P0 PASS must bind target identity, HOME, shell, privilege, architecture, and runtime reason\n'
sed 's/^\[P4\] FAIL:/[P4] PASS:/' "$TMP/match.log" > "$TMP/drift.log"
if bash "$ROOT/tools/verify-greenfield-expected-red.sh" next-git-present "$TMP/drift.log" 1 >/dev/null 2>&1; then
echo '[test] FAIL: changed P4 verdict did not invalidate the pinned manifest' >&2
@@ -52,6 +59,13 @@ if MOSAIC_EXPECTED_RED_MANIFEST="$TMP/missing-phase.tsv" \
fi
printf '[test] PASS: every case requires one P0-P9 disposition\n'
grep -v $'^next-git-present\tphase-reason\tP0=' "$manifest" > "$TMP/missing-p0-reason.tsv"
if MOSAIC_EXPECTED_RED_MANIFEST="$TMP/missing-p0-reason.tsv" \
bash "$ROOT/tools/verify-greenfield-expected-red.sh" next-git-present "$TMP/match.log" 1 >/dev/null 2>&1; then
echo '[test] FAIL: missing P0 reason binding was accepted' >&2; exit 1
fi
printf '[test] PASS: every case requires one discriminating P0 reason binding\n'
cp "$manifest" "$TMP/duplicate.tsv"
printf 'next-git-present\tphase\tP3=PASS\n' >> "$TMP/duplicate.tsv"
if MOSAIC_EXPECTED_RED_MANIFEST="$TMP/duplicate.tsv" \