fix(installer): close detector false-pass gaps
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
@@ -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="$(
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" \
|
||||
|
||||
Reference in New Issue
Block a user