diff --git a/docs/reports/quality/1099-pipefail-sweep.md b/docs/reports/quality/1099-pipefail-sweep.md index 798356ba..ff961908 100644 --- a/docs/reports/quality/1099-pipefail-sweep.md +++ b/docs/reports/quality/1099-pipefail-sweep.md @@ -6,78 +6,78 @@ This is a site inventory, not a risk count. `FIXED` means the early-exiting cons ## Tranche 1 — runtime and general scripts -| Baseline site | Verdict | Construction / reason | -| --- | --- | --- | -| `tools/matrix-presence-harness/run.sh:38` | FIXED | nullglob array selects the first path; no pipeline | -| `tools/e2e-install-test.sh:139` | FIXED | capture help completely, then grep via redirection | -| `tools/install.sh:312` | FIXED | NUL `mapfile` reads all roots; count != 1 reaches the named malformed-archive diagnostic | -| `scripts/analysis/reflect-board-history.sh:76` | FIXED | capture Git history completely, then grep via redirection | -| `scripts/analysis/reflect-git-history.sh:67` | FIXED | grep reads from a here-string | -| `scripts/analysis/reflect-git-history.sh:69` | FIXED | grep reads from a here-string | -| `packages/mosaic/framework/tools/authentik/user-create.sh:72` | FIXED | jq `first(...)` reads the response directly | -| `packages/mosaic/framework/tools/git/mutate-push-guard.sh:87` | FIXED | grep `-m1` reads the file directly; downstream `cut` consumes its complete scalar output | -| `packages/mosaic/framework/tools/orchestrator/session-resume.sh:94` | FIXED | `mapfile` plus bounded indexed loop replaces `head` pipeline | -| `packages/mosaic/framework/tools/prdy/prdy-status.sh:69` | FIXED | grep reads from a here-string | -| `packages/mosaic/framework/tools/qa/reflect-stop-hook.sh:172` | FIXED | grep reads from a here-string | -| `packages/mosaic/framework/tools/qa/reflect-stop-hook.sh:173` | FIXED | grep reads from a here-string | -| `packages/mosaic/framework/tools/qa/reflect-stop-hook.sh:174` | FIXED | grep reads from a here-string | -| `packages/mosaic/framework/tools/qa/reflect-stop-hook.sh:175` | FIXED | grep reads from a here-string | -| `packages/mosaic/framework/tools/qa/reflect-stop-hook.sh:176` | FIXED | grep reads from a here-string | -| `packages/mosaic/framework/tools/qa/reflect-stop-hook.sh:177` | FIXED | grep reads from a here-string | -| `packages/mosaic/framework/tools/qa/reflect-stop-hook.sh:178` | FIXED | grep reads from a here-string | -| `packages/mosaic/framework/tools/qa/typecheck-hook.sh:16` | FIXED | Bash regex extracts the first field without a pipeline | -| `packages/mosaic/framework/tools/qa/typecheck-hook.sh:56` | FIXED | grep and bounded sed each read from a here-string | -| `packages/mosaic/framework/tools/tmux/send-message.sh:113` | FIXED | grep reads from a here-string | -| `packages/mosaic/framework/tools/tmux/send-message.sh:124` | FIXED | grep reads from a here-string | -| `packages/mosaic/framework/tools/wake/detector.sh:126` | FIXED | one awk reads the manifest directly and exits after the first exact key | -| `packages/mosaic/framework/tools/wake/detector.sh:270` | FIXED | grep reads from a here-string | -| `packages/mosaic/framework/tools/wake/detector.sh:278` | FIXED | grep reads from a here-string | -| `packages/mosaic/framework/tools/wake/digest.sh:647` | FIXED | capture complete locator output, then select first line by parameter expansion | -| `packages/mosaic/framework/tools/wake/reconcile.sh:149` | FIXED | one awk reads the manifest directly and exits after the first exact key | +| Baseline site | Verdict | Construction / reason | +| ------------------------------------------------------------------- | ------- | ---------------------------------------------------------------------------------------- | +| `tools/matrix-presence-harness/run.sh:38` | FIXED | nullglob array selects the first path; no pipeline | +| `tools/e2e-install-test.sh:139` | FIXED | capture help completely, then grep via redirection | +| `tools/install.sh:312` | FIXED | NUL `mapfile` reads all roots; count != 1 reaches the named malformed-archive diagnostic | +| `scripts/analysis/reflect-board-history.sh:76` | FIXED | capture Git history completely, then grep via redirection | +| `scripts/analysis/reflect-git-history.sh:67` | FIXED | grep reads from a here-string | +| `scripts/analysis/reflect-git-history.sh:69` | FIXED | grep reads from a here-string | +| `packages/mosaic/framework/tools/authentik/user-create.sh:72` | FIXED | jq `first(...)` reads the response directly | +| `packages/mosaic/framework/tools/git/mutate-push-guard.sh:87` | FIXED | grep `-m1` reads the file directly; downstream `cut` consumes its complete scalar output | +| `packages/mosaic/framework/tools/orchestrator/session-resume.sh:94` | FIXED | `mapfile` plus bounded indexed loop replaces `head` pipeline | +| `packages/mosaic/framework/tools/prdy/prdy-status.sh:69` | FIXED | grep reads from a here-string | +| `packages/mosaic/framework/tools/qa/reflect-stop-hook.sh:172` | FIXED | grep reads from a here-string | +| `packages/mosaic/framework/tools/qa/reflect-stop-hook.sh:173` | FIXED | grep reads from a here-string | +| `packages/mosaic/framework/tools/qa/reflect-stop-hook.sh:174` | FIXED | grep reads from a here-string | +| `packages/mosaic/framework/tools/qa/reflect-stop-hook.sh:175` | FIXED | grep reads from a here-string | +| `packages/mosaic/framework/tools/qa/reflect-stop-hook.sh:176` | FIXED | grep reads from a here-string | +| `packages/mosaic/framework/tools/qa/reflect-stop-hook.sh:177` | FIXED | grep reads from a here-string | +| `packages/mosaic/framework/tools/qa/reflect-stop-hook.sh:178` | FIXED | grep reads from a here-string | +| `packages/mosaic/framework/tools/qa/typecheck-hook.sh:16` | FIXED | Bash regex extracts the first field without a pipeline | +| `packages/mosaic/framework/tools/qa/typecheck-hook.sh:56` | FIXED | grep and bounded sed each read from a here-string | +| `packages/mosaic/framework/tools/tmux/send-message.sh:113` | FIXED | grep reads from a here-string | +| `packages/mosaic/framework/tools/tmux/send-message.sh:124` | FIXED | grep reads from a here-string | +| `packages/mosaic/framework/tools/wake/detector.sh:126` | FIXED | one awk reads the manifest directly and exits after the first exact key | +| `packages/mosaic/framework/tools/wake/detector.sh:270` | FIXED | grep reads from a here-string | +| `packages/mosaic/framework/tools/wake/detector.sh:278` | FIXED | grep reads from a here-string | +| `packages/mosaic/framework/tools/wake/digest.sh:647` | FIXED | capture complete locator output, then select first line by parameter expansion | +| `packages/mosaic/framework/tools/wake/reconcile.sh:149` | FIXED | one awk reads the manifest directly and exits after the first exact key | ## Explicit withdrawn / non-load-bearing sites -| Baseline site | Verdict | Reason | -| --- | --- | --- | -| `tools/install.sh:182` | NOT-LOAD-BEARING | `|| true` explicitly discards lookup status | -| `tools/install.sh:356` | UNREACHABLE-AND-WHY | `pnpm pack` writes one matching CLI tarball into a fresh directory immediately before lookup; citation withdrawn in #1099 | -| `tools/install.sh:357` | UNREACHABLE-AND-WHY | same fresh-directory invariant for gateway tarball; citation withdrawn in #1099 | -| `tools/install.sh:627` | NOT-LOAD-BEARING | `|| true` explicitly discards lookup status | -| `scripts/agent/session-start.sh:70` | NOT-LOAD-BEARING | optional scratchpad lookup has `|| true` | -| `packages/mosaic/framework/templates/repo/scripts/agent/session-start.sh:58` | NOT-LOAD-BEARING | optional scratchpad lookup has `|| true` | -| `packages/mosaic/framework/tools/qa/qa-hook-stdin.sh:25` | UNREACHABLE-AND-WHY | withdrawn in #1099 after designed-input reachability measurement; preserved without re-litigation | -| `packages/mosaic/framework/tools/qa/qa-hook-stdin.sh:27` | UNREACHABLE-AND-WHY | same withdrawn designed-input finding | -| `packages/mosaic/framework/tools/qa/qa-hook-stdin.sh:30` | UNREACHABLE-AND-WHY | same withdrawn designed-input finding | -| `packages/mosaic/framework/tools/qa/qa-hook-stdin.sh:32` | UNREACHABLE-AND-WHY | same withdrawn designed-input finding | -| `packages/mosaic/framework/tools/qa/qa-hook-stdin.sh:34` | UNREACHABLE-AND-WHY | same withdrawn designed-input finding | +| Baseline site | Verdict | Reason | +| ---------------------------------------------------------------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------- | --- | --------------------------------------- | +| `tools/install.sh:182` | NOT-LOAD-BEARING | ` | | true` explicitly discards lookup status | +| `tools/install.sh:356` | UNREACHABLE-AND-WHY | `pnpm pack` writes one matching CLI tarball into a fresh directory immediately before lookup; citation withdrawn in #1099 | +| `tools/install.sh:357` | UNREACHABLE-AND-WHY | same fresh-directory invariant for gateway tarball; citation withdrawn in #1099 | +| `tools/install.sh:627` | NOT-LOAD-BEARING | ` | | true` explicitly discards lookup status | +| `scripts/agent/session-start.sh:70` | NOT-LOAD-BEARING | optional scratchpad lookup has ` | | true` | +| `packages/mosaic/framework/templates/repo/scripts/agent/session-start.sh:58` | NOT-LOAD-BEARING | optional scratchpad lookup has ` | | true` | +| `packages/mosaic/framework/tools/qa/qa-hook-stdin.sh:25` | UNREACHABLE-AND-WHY | withdrawn in #1099 after designed-input reachability measurement; preserved without re-litigation | +| `packages/mosaic/framework/tools/qa/qa-hook-stdin.sh:27` | UNREACHABLE-AND-WHY | same withdrawn designed-input finding | +| `packages/mosaic/framework/tools/qa/qa-hook-stdin.sh:30` | UNREACHABLE-AND-WHY | same withdrawn designed-input finding | +| `packages/mosaic/framework/tools/qa/qa-hook-stdin.sh:32` | UNREACHABLE-AND-WHY | same withdrawn designed-input finding | +| `packages/mosaic/framework/tools/qa/qa-hook-stdin.sh:34` | UNREACHABLE-AND-WHY | same withdrawn designed-input finding | ## Tranche 2 — non-wake test harnesses All 22 baseline sites below are `FIXED`; the checked-in tranche fixture is passed through the same scanner and asserts all 22 occurrences and 21 normalized identities (the same response-split line occurs twice). -| Baseline site(s) | Verdict | Construction | -| --- | --- | --- | -| `systemd/user/test-fleet-units.sh:148` | FIXED | capture tmux output, then grep via redirection | -| `git/test-issue-comment-readback.sh:283,302` | FIXED | parameter expansion splits status/body without `head` | -| `git/test-pr-review-gitea-comment.sh:228` | FIXED | parameter expansion splits status/body | -| `git/test-lane-brief-pr-linkage.sh:72` | FIXED | grep reads from a here-string | -| `git/test-pr-review-repo-host-override.sh:225-226` | FIXED | grep reads from a here-string | -| `orchestrator/smoke-test.sh:67,72` | FIXED | parameter expansion selects first line | -| `orchestrator/test-board-roll.sh:99-100` | FIXED | grep reads from a here-string | -| `quality/scripts/test-upgrade-durable-snapshot.sh:180` | FIXED | complete sorted output is read with `mapfile`, then indexed | -| `quality/scripts/test-upgrade-rollback.sh:339,356` | FIXED | direct `grep -m1` file reads; cleanup captures before testing | -| `tmux/test-send-message-socket.sh:37,38,44-46,68,72` | FIXED | capture commands complete before redirected grep assertions | -| `tmux/test-send-message-verdict.sh:34` | FIXED | grep reads from a here-string | +| Baseline site(s) | Verdict | Construction | +| ------------------------------------------------------ | ------- | ------------------------------------------------------------- | +| `systemd/user/test-fleet-units.sh:148` | FIXED | capture tmux output, then grep via redirection | +| `git/test-issue-comment-readback.sh:283,302` | FIXED | parameter expansion splits status/body without `head` | +| `git/test-pr-review-gitea-comment.sh:228` | FIXED | parameter expansion splits status/body | +| `git/test-lane-brief-pr-linkage.sh:72` | FIXED | grep reads from a here-string | +| `git/test-pr-review-repo-host-override.sh:225-226` | FIXED | grep reads from a here-string | +| `orchestrator/smoke-test.sh:67,72` | FIXED | parameter expansion selects first line | +| `orchestrator/test-board-roll.sh:99-100` | FIXED | grep reads from a here-string | +| `quality/scripts/test-upgrade-durable-snapshot.sh:180` | FIXED | complete sorted output is read with `mapfile`, then indexed | +| `quality/scripts/test-upgrade-rollback.sh:339,356` | FIXED | direct `grep -m1` file reads; cleanup captures before testing | +| `tmux/test-send-message-socket.sh:37,38,44-46,68,72` | FIXED | capture commands complete before redirected grep assertions | +| `tmux/test-send-message-verdict.sh:34` | FIXED | grep reads from a here-string | ## Tranche 3 — wake validation harnesses All 26 baseline occurrences (25 normalized identities; one preimage selector occurs twice) are `FIXED` and mechanically bound through the wake fixture and shared scanner. -| Baseline site(s) | Verdict | Construction | -| --- | --- | --- | -| `wake/test-wake-digest-quarantine.sh:567` | FIXED | complete match populations are captured, then first line selected by parameter expansion | -| `wake/test-wake-preimage.sh:182-183,346-347` | FIXED | jq `first(...)` reads each JSONL file directly | -| `wake/validate-973/microtest-wake-assert.sh:153,170-171,176,204-209,233-234,251-252,286-287` | FIXED | scalar assertions use here-strings; diagnostics use non-early sed ranges; source line captured before matching | -| `wake/validate-973/validate-973.sh:110,119,180,182,187` | FIXED | scalar assertions use here-strings; diagnostic truncation uses consuming sed ranges | +| Baseline site(s) | Verdict | Construction | +| -------------------------------------------------------------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------- | +| `wake/test-wake-digest-quarantine.sh:567` | FIXED | complete match populations are captured, then first line selected by parameter expansion | +| `wake/test-wake-preimage.sh:182-183,346-347` | FIXED | jq `first(...)` reads each JSONL file directly | +| `wake/validate-973/microtest-wake-assert.sh:153,170-171,176,204-209,233-234,251-252,286-287` | FIXED | scalar assertions use here-strings; diagnostics use non-early sed ranges; source line captured before matching | +| `wake/validate-973/validate-973.sh:110,119,180,182,187` | FIXED | scalar assertions use here-strings; diagnostic truncation uses consuming sed ranges | The scoped inventory is complete: 26 runtime/general + 22 non-wake tests + 26 wake tests fixed; 11 explicitly withdrawn or non-load-bearing sites retain their documented verdicts. diff --git a/scripts/verify-release.mjs b/scripts/verify-release.mjs index ca7ab100..0e23adec 100644 --- a/scripts/verify-release.mjs +++ b/scripts/verify-release.mjs @@ -58,6 +58,11 @@ export const STAGES = [ 'bash packages/mosaic/framework/tools/quality/scripts/check-resident-budget.sh --self-test', 'bash packages/mosaic/framework/tools/quality/scripts/check-resident-budget.sh', 'bash packages/mosaic/framework/tools/quality/scripts/check-test-enumeration.sh', + 'bash packages/mosaic/framework/tools/quality/scripts/check-tools-index.sh --self-test', + 'bash packages/mosaic/framework/tools/quality/scripts/check-tools-index.sh', + 'bash packages/mosaic/framework/tools/git/test-issue-close-fail-closed.sh', + 'bash packages/mosaic/framework/tools/git/test-wrapper-guard.sh', + 'bash packages/mosaic/framework/tools/git/test-mosaic-worktree-large-repo.sh', ], }, { diff --git a/tools/install.sh b/tools/install.sh index 44f99a4e..ca72d8ca 100755 --- a/tools/install.sh +++ b/tools/install.sh @@ -161,7 +161,11 @@ newest_matching_file() { shopt -u nullglob [[ "${#matches[@]}" -gt 0 ]] || return 0 # shellcheck disable=SC2012 # Need portable mtime sorting across Linux/macOS. - ls -1t "${matches[@]}" 2>/dev/null | head -1 + # pipefail-safe: take the first line via process substitution; piping into + # an early-exiting consumer would inherit ls's exit status under pipefail (#1099). + local newest="" + while IFS= read -r newest; do break; done < <(ls -1t "${matches[@]}" 2>/dev/null) + if [[ -n "$newest" ]]; then printf '%s\n' "$newest"; fi } # ─── uninstall path ─────────────────────────────────────────────────────────── @@ -413,9 +417,18 @@ check_fleet_transport() { local declared="" if [[ -f "$roster" ]]; then - declared="$(sed -n 's/^[[:space:]]*transport:[[:space:]]*//p' "$roster" | head -1 | - tr -d '"'\''' | tr -d '\r' | awk '{print $1}')" - [[ -n "$declared" ]] && transport="$declared" + # pipefail-safe: consume the first match via process substitution, then + # strip quotes/CR and keep the first field without piping into a consumer (#1099). + local first_transport="" + while IFS= read -r first_transport; do break; done < \ + <(sed -n 's/^[[:space:]]*transport:[[:space:]]*//p' "$roster") + if [[ -n "$first_transport" ]]; then + declared="${first_transport//\"/}" + declared="${declared//\'/}" + declared="${declared//$'\r'/}" + declared="${declared%%[[:space:]]*}" + [[ -n "$declared" ]] && transport="$declared" + fi fi command -v "$transport" &>/dev/null && return 0