fix(installer): fail closed on test enumeration errors
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/pr/greenfield-install Pipeline failed

This commit is contained in:
2026-08-05 21:11:05 -05:00
parent 378bc1afe3
commit ff3f0d29f1
13 changed files with 227 additions and 14 deletions
@@ -2,6 +2,8 @@
set -euo pipefail
ROOT="/work"
# shellcheck source=tools/test-enumeration-assertions.sh
source "$ROOT/tools/test-enumeration-assertions.sh"
TMP="$(mktemp -d "${TMPDIR:-/tmp}/mosaic-next-install-test-XXXXXX")"
trap 'rm -rf "$TMP"' EXIT
export TMPDIR="$TMP/runtime-tmp"
@@ -494,9 +496,9 @@ fi
if [[ "$(cat "$TMP/canary-observed" 2>/dev/null || true)" != "$canary" ]]; then
echo 'credential canary positive control was not exercised' >&2; exit 1
fi
if find "$TMPDIR" -maxdepth 1 -type f \( -name 'mosaic-phase-redacted.*' -o -name 'mosaic-post-redacted.*' \) -print -quit | grep -q .; then
echo 'redacted diagnostic staging file survived normal completion' >&2; exit 1
fi
test_assert_find_empty 'redacted diagnostic staging files' \
"$TMPDIR" -maxdepth 1 -type f \
\( -name 'mosaic-phase-redacted.*' -o -name 'mosaic-post-redacted.*' \) || exit 1
printf '[test] framework nested capture redacts the same canary and URL variants\n'
framework_test_home="$TMP/framework-redact-home"
@@ -553,9 +555,8 @@ for phase in P2 P3 P4 P5 P6 P7 P8; do
[[ -s "$LOG" ]] || { echo "$phase fault never entered the real action path" >&2; exit 1; }
[[ "$(tree_fingerprint "$HOME_DIR")" == "$before" ]] || { echo "$phase real rollback mismatch" >&2; exit 1; }
grep -q "phase=$phase" "$TMP/fault-$phase.log"
if find "$TMP/fault-$phase" -type f -exec grep -l '"status"[[:space:]]*:[[:space:]]*"in-progress"' {} + 2>/dev/null | grep -q .; then
echo "$phase left an in-progress transaction" >&2; exit 1
fi
test_assert_no_file_content_match "$phase fault-state" \
'"status"[[:space:]]*:[[:space:]]*"in-progress"' "$TMP/fault-$phase" || exit 1
done
printf '[test] stale projection is preserved while the real fault path acquires a free OS lock\n'
@@ -0,0 +1,13 @@
[test] --next fast path pins resolved package versions
[test] fast path failure falls back to source build
[test] source-build failure is fatal and restores the pre-install prefix
[test] corrupt source archive is fatal and restores the pre-install prefix
[test] source archive with multiple extracted roots fails instead of selecting by find order
[test] --dev source install does not require registry version resolution
[test] explicit --ref keeps source lane and avoids @next lookup
[test] --check --next rejects mismatched prerelease pipeline suffixes
[test] full framework path receives P3 absolute CLI without relying on PATH
[test] captured diagnostics redact seeded credential canary everywhere
[test] framework nested capture redacts the same canary and URL variants
[test] real P2-P8 actions run under fault injection and restore actual surfaces
P2 left an in-progress transaction
@@ -0,0 +1,14 @@
[test] --next fast path pins resolved package versions
[test] fast path failure falls back to source build
[test] source-build failure is fatal and restores the pre-install prefix
[test] corrupt source archive is fatal and restores the pre-install prefix
[test] source archive with multiple extracted roots fails instead of selecting by find order
[test] --dev source install does not require registry version resolution
[test] explicit --ref keeps source lane and avoids @next lookup
[test] --check --next rejects mismatched prerelease pipeline suffixes
[test] full framework path receives P3 absolute CLI without relying on PATH
[test] captured diagnostics redact seeded credential canary everywhere
[test] framework nested capture redacts the same canary and URL variants
[test] real P2-P8 actions run under fault injection and restore actual surfaces
[test] stale projection is preserved while the real fault path acquires a free OS lock
[test] installer next lane tests passed
@@ -0,0 +1,2 @@
[test] enumeration failure cannot mask a planted in-progress transaction
[test] FAIL: planted in-progress transaction plus failed enumeration passed the full suite
@@ -0,0 +1,14 @@
[test] --next fast path pins resolved package versions
[test] fast path failure falls back to source build
[test] source-build failure is fatal and restores the pre-install prefix
[test] corrupt source archive is fatal and restores the pre-install prefix
[test] source archive with multiple extracted roots fails instead of selecting by find order
[test] --dev source install does not require registry version resolution
[test] explicit --ref keeps source lane and avoids @next lookup
[test] --check --next rejects mismatched prerelease pipeline suffixes
[test] full framework path receives P3 absolute CLI without relying on PATH
[test] captured diagnostics redact seeded credential canary everywhere
[test] framework nested capture redacts the same canary and URL variants
[test] real P2-P8 actions run under fault injection and restore actual surfaces
find: /tmp/mosaic-next-install-test-hqL7U0/fault-P2/blocked: Permission denied
[test] ERROR: P2 fault-state enumeration failed
@@ -0,0 +1,33 @@
# #1050 C1 fix-round verification
Frozen reviewed head before remediation: `378bc1afe3bc485adb8614897d66c5edccd4a527`.
Status: **believed-fixed, pending jarvis validation**. PR #1054 is not self-merged and issue #1050 remains open.
## Blocker B — fail-closed test enumeration
The RED-first control used a real filesystem permission failure, not binary shadowing or PATH interception.
1. A planted `{"status":"in-progress"}` file in a readable P2 fault tree made the complete real walk fail the frozen suite at `P2 left an in-progress transaction` (`01-pre-fix-positive-control.log`, exit 1).
2. The same planted defect beneath a target-owned mode-0100 directory made real `find` report a permission failure. The frozen suite erased the producer failure and exited 0 with `installer next lane tests passed` (`02-pre-fix-permission-failure-attack.log`).
3. The committed regression control initially failed because the child full-suite attack still exited 0 (`03-regression-test-red.log`).
4. After remediation, the same child full-suite input exits 1 and names `[test] ERROR: P2 fault-state enumeration failed` (`04-post-fix-permission-failure-attack.log`). The ordinary full suite remains green.
`tools/test-enumeration-assertions.sh` now captures each complete NUL-delimited population and checks the producer status before asserting absence. Content checks inspect the captured population and distinguish “no match” from a read error. The shared fail-closed implementation covers:
- `tools/install-next-lane.test.sh`: redacted staging-file cleanup and fault-state transaction scan;
- `tools/verified-installer-fetch.test.sh`: temporary-download cleanup;
- `tools/install-state-machine.test.sh`: symlink-target non-mutation;
- `docs/reports/verification/1050-b8-redaction-control/positive-control.test.sh`: both copied counterparts.
No assertion was loosened. A1, A2, upgrade-guard, source-root, the species-2 sweep, #869, and expected-RED verdict rows remain outside this remediation.
## Blocker A — installer digest
The stale sidecar value was replaced with the exact `sha256sum` record for `tools/install.sh`:
```text
e59cb441a2f37ae9150f8eae470238e9d858a1816df93343d9784a6796676096 install.sh
```
Local `sha256sum -c tools/install.sh.sha256` and the workflow's exact expected/actual equality both pass. The immutable provider-fetch arm at the new `${CI_COMMIT_SHA}` is recorded in the freeze artifact after push; local equality alone is not treated as sufficient evidence.
@@ -0,0 +1,22 @@
# Documentation completion checklist — #1050 C1 fix round
## Required artifacts
- [x] `docs/PRD.md` exists; #1050 C1 requirements remain current and unchanged.
- [x] User guide: not applicable; no user-facing installer behavior changed.
- [x] Admin guide: not applicable; no operator procedure or deployment behavior changed.
- [x] Developer guide: existing `docs/guides/installer-state-machine.md` already defines the fail-closed and immutable-remote contracts; this round adds verification evidence without changing the contract.
- [x] OpenAPI and endpoint index: not applicable; no API changed.
- [x] Sitemap: not applicable; no navigation changed.
## API and structural coverage
- [x] API schema/auth/error coverage: not applicable; no endpoint changed.
- [x] Guide book indexes: not applicable; no guide page was added or moved.
- [x] Root hygiene preserved; all new artifacts are under `docs/reports/verification/1050-c1-fix-round/` and the active scratchpad remains under `docs/scratchpads/`.
## Review and publishing
- [x] Verification documentation is in the same logical change set as the shell-test remediation.
- [x] Independent code and security reviews found no documentation blocker.
- [x] Canonical evidence remains in-repo. No external publishing action was requested or performed.