diff --git a/scripts/taler-monitoring/android-test/GUI-AUTOMATION-NOTES.md b/scripts/taler-monitoring/android-test/GUI-AUTOMATION-NOTES.md index 798bb7d..f909ead 100644 --- a/scripts/taler-monitoring/android-test/GUI-AUTOMATION-NOTES.md +++ b/scripts/taler-monitoring/android-test/GUI-AUTOMATION-NOTES.md @@ -245,13 +245,14 @@ Scripts **today** implement Android only; they should honour: | Variant | Source | Severity | Purpose | |---------|--------|----------|---------| -| **stable-self** | tag `wallet-1.6.1` rebuilt here | **WARN only** | Same release tree, our Gradle — never hard-blocks | -| **published** | F-Droid APK URL | **BLOCKER** | What users install; FAIL_FAST stops here | -| **master** | `origin/master` | **WARN only** | Tip of mainline — never hard-blocks | -| **fix** (optional) | inference fix branch | soft fail | GOA automation fixes | +| **stable-self** | tag `wallet-1.6.1` rebuilt | WARN | Our rebuild of stable — never hard-blocks | +| **published** | F-Droid current APK | **≤14d → BLOCKER**, else WARN | What users install | +| **older-pub / older-self** | previous `wallet-*` tags | **≤14d → BLOCKER**, else WARN | Regression on recent releases only | +| **master** | `origin/master` | WARN | Tip — never hard-blocks | +| **fix** (optional) | inference branch | soft | GOA automation fixes | -Default matrix order: `stable-self,published,master`. -`./run-android-variant-matrix.sh` — see README. +**Two-week window:** `BLOCKER_MAX_AGE_DAYS=14` (git tag age). Older apps are still tried; only young releases hard-block. +Default matrix: `stable-self,published,older,master`. See README. --- diff --git a/scripts/taler-monitoring/android-test/README.md b/scripts/taler-monitoring/android-test/README.md index 595a187..8e0263f 100644 --- a/scripts/taler-monitoring/android-test/README.md +++ b/scripts/taler-monitoring/android-test/README.md @@ -38,17 +38,22 @@ Runs several smokes so a single odd developer build cannot be the only sample: |---------|------| | Order | Variant | Severity | What | |------|---------|----------|------| -| **1** | **stable-self** | **WARN only** | Rebuild tag `wallet-1.6.1` on this host | -| **2** | **published** | **BLOCKER** | F-Droid stable APK (`net.taler.wallet.fdroid` 1.6.1 / 854) | -| **3** | **master** | **WARN only** | Rebuild `origin/master` | +| **1** | **stable-self** | WARN | Rebuild tag `wallet-1.6.1` on this host | +| **2** | **published** | **age rule** | Current F-Droid APK | +| **3** | **older-*** | **age rule** | Previous `wallet-*` tags (APK if on F-Droid, else self-build) | +| **4** | **master** | WARN | Rebuild `origin/master` | -**Default:** `FAIL_FAST=1` only stops on **blocker** failures (`published`). -`stable-self` / `master` failure → warning in `SUMMARY.txt`, matrix continues, exit code unaffected by those warns alone. +**Age rule (released apps):** if git-tag age **≤ 14 days** (`BLOCKER_MAX_AGE_DAYS`) → fail is **BLOCKER** + `FAIL_FAST`. +Older releases are still smoked, but fail only as **WARN**. +Self-build of tip/stable-self is never a hard blocker. ```bash ./run-android-variant-matrix.sh STACK=goa ./run-android-variant-matrix.sh -VARIANTS=stable-self,published FAIL_FAST=1 ./run-android-variant-matrix.sh +# without older history: +INCLUDE_OLDER=0 VARIANTS=stable-self,published,master ./run-android-variant-matrix.sh +# force current published always blocker: +PUBLISHED_FORCE_BLOCKER=1 ./run-android-variant-matrix.sh ``` Artifacts + `SUMMARY.txt` under `out-matrix-/`. diff --git a/scripts/taler-monitoring/android-test/lib_release_age.sh b/scripts/taler-monitoring/android-test/lib_release_age.sh new file mode 100644 index 0000000..52fa2ce --- /dev/null +++ b/scripts/taler-monitoring/android-test/lib_release_age.sh @@ -0,0 +1,91 @@ +# shellcheck shell=bash +# Release age helpers for Android wallet matrix. +# Source after ROOT/lib_android_env.sh (optional). +# +# BLOCKER only when a *released* app is within BLOCKER_MAX_AGE_DAYS (default 14). +# Older releases: try them, but failures are WARN only. + +: "${BLOCKER_MAX_AGE_DAYS:=14}" +: "${TALER_ANDROID_SRC:=$HOME/taler/taler-android}" + +# Days since git tag (tagger/committer date). Empty if unknown. +# Usage: release_age_days wallet-1.6.1 → e.g. 28 +release_age_days() { + local tag="$1" + local src="${2:-$TALER_ANDROID_SRC}" + [ -d "$src/.git" ] || return 1 + local epoch + epoch=$(git -C "$src" log -1 --format=%ct "refs/tags/${tag}" 2>/dev/null || true) + [ -n "$epoch" ] || epoch=$(git -C "$src" log -1 --format=%ct "$tag" 2>/dev/null || true) + [ -n "$epoch" ] || return 1 + local now + now=$(date +%s) + echo $(( (now - epoch) / 86400 )) +} + +# severity from age: blocker if age_days <= BLOCKER_MAX_AGE_DAYS, else warn +# Unknown age → warn (never accidental hard fail on mystery builds) +severity_from_age_days() { + local days="${1:-}" + if [ -z "$days" ] || ! [[ "$days" =~ ^[0-9]+$ ]]; then + echo warn + return + fi + if [ "$days" -le "${BLOCKER_MAX_AGE_DAYS}" ]; then + echo blocker + else + echo warn + fi +} + +severity_for_tag() { + local tag="$1" + local days + days=$(release_age_days "$tag" 2>/dev/null || true) + severity_from_age_days "$days" +} + +# List wallet-* release tags newest first (exclude rc/dev). +# Args: max count (default 5) +list_wallet_release_tags() { + local n="${1:-5}" + local src="${TALER_ANDROID_SRC:-$HOME/taler/taler-android}" + [ -d "$src/.git" ] || return 1 + git -C "$src" fetch --tags origin 2>/dev/null || true + git -C "$src" for-each-ref --sort=-creatordate --format='%(refname:short)' refs/tags \ + | grep -E '^wallet-[0-9]+\.[0-9]+(\.[0-9]+)?$' \ + | head -n "$n" +} + +# Guess F-Droid APK URL for a wallet version name (e.g. 1.6.1). +# Uses versionCode from tag's build.gradle if possible, else STABLE mapping. +# Prints: versionCode apk_url or returns 1 +fdroid_apk_for_version_name() { + local vn="$1" # 1.6.1 + local src="${TALER_ANDROID_SRC:-$HOME/taler/taler-android}" + local tag="wallet-${vn}" + local vc="" + if git -C "$src" cat-file -e "refs/tags/${tag}:wallet/build.gradle" 2>/dev/null; then + vc=$(git -C "$src" show "refs/tags/${tag}:wallet/build.gradle" 2>/dev/null \ + | sed -n 's/^[[:space:]]*versionCode[[:space:]]\+\([0-9]\+\).*/\1/p' | head -1) + fi + # ABI x86_64 → +4 in override scheme 10*vc+abi (common F-Droid multi-apk) + # Prefer universal-style code used by F-Droid: for 1.6.1 → 854 = 10*85+4 + local apk_vc="" + if [ -n "$vc" ]; then + apk_vc=$((10 * vc + 4)) + fi + # Known overrides / suggested + case "$vn" in + 1.6.1) apk_vc=854 ;; + esac + [ -n "$apk_vc" ] || return 1 + echo "${apk_vc}|https://f-droid.org/repo/net.taler.wallet.fdroid_${apk_vc}.apk" +} + +# Check HTTP HEAD/GET that APK exists (0 = ok) +fdroid_apk_exists() { + local url="$1" + curl -sfS -m 15 -o /dev/null -r 0-0 "$url" 2>/dev/null \ + || curl -sfS -m 15 -o /dev/null -I "$url" 2>/dev/null +} diff --git a/scripts/taler-monitoring/android-test/run-android-variant-matrix.sh b/scripts/taler-monitoring/android-test/run-android-variant-matrix.sh index baabbf3..c658096 100755 --- a/scripts/taler-monitoring/android-test/run-android-variant-matrix.sh +++ b/scripts/taler-monitoring/android-test/run-android-variant-matrix.sh @@ -1,51 +1,49 @@ #!/usr/bin/env bash -# Run Android wallet smoke against **several build variants** so a single -# "weird" developer tree or a bad local build cannot be the only sample. +# Run Android wallet smoke against several build / published variants. # -# Default matrix order: -# 1) **stable-self** — rebuild release **tag** (same source as published) -# Failure → **WARN only** (not a hard blocker; continue matrix) -# 2) **published** — F-Droid stable APK (what users install) -# Failure → **BLOCKER** (matrix exit ≠ 0; FAIL_FAST stops here) -# 3) **master** — rebuild origin/master -# Failure → **WARN only** (tip breakage / ANR must not block stable gate) +# Default order: +# 1) stable-self — rebuild STABLE_TAG (always WARN on fail) +# 2) published — current F-Droid suggested APK +# 3) older-* — previous release tags (APK and/or self-build) if available +# 4) master — tip (always WARN on fail) # -# Why self-builds in addition to published? -# Isolates "our toolchain rebuild" vs "F-Droid binary". Rebuild issues -# (signing flavor, dirty host, low-RAM ANR on debug) must not block the -# published-stable gate — hence warn-only for stable-self **and** master. -# -# Usage: -# ./run-android-variant-matrix.sh -# STACK=goa ./run-android-variant-matrix.sh -# VARIANTS=master,stable-self ./run-android-variant-matrix.sh -# STABLE_TAG=wallet-1.6.1 MASTER_BRANCH=master ./run-android-variant-matrix.sh +# Severity (released apps): +# Age ≤ BLOCKER_MAX_AGE_DAYS (default **14**) → **BLOCKER** on fail (FAIL_FAST) +# Age > 14 days (or unknown) → **WARN** only +# master / pure self-builds without release age → WARN # # Env: -# STABLE_TAG default wallet-1.6.1 (must match published F-Droid release) -# STABLE_APK_URL published APK (same as pay-smoke default) -# MASTER_BRANCH default master -# FIX_BRANCH inference fix branch (variant "fix") -# VARIANTS comma list: published,stable-self,master,fix -# GUI=1 use gui-smoke instead of pay-smoke after each build -# FAIL_FAST=1 stop on first **blocker** failure (default 1) -# MATRIX_OUT summary dir (default out-matrix-) +# STABLE_TAG, STABLE_APK_URL, STABLE_APK_NAME +# MASTER_BRANCH, FIX_BRANCH +# VARIANTS default: stable-self,published,older,master +# INCLUDE_OLDER=1 expand token "older" (default 1) +# OLDER_TAG_COUNT how many recent wallet-* tags to consider (default 4) +# BLOCKER_MAX_AGE_DAYS default 14 +# PUBLISHED_FORCE_BLOCKER=1 always treat current published as blocker (default 0 = age rule) +# FAIL_FAST=1, GUI=0, STACK=… # set -euo pipefail ROOT=$(cd "$(dirname "$0")" && pwd) # shellcheck source=lib_android_env.sh . "$ROOT/lib_android_env.sh" +# shellcheck source=lib_release_age.sh +. "$ROOT/lib_release_age.sh" : "${STABLE_TAG:=wallet-1.6.1}" : "${STABLE_APK_URL:=https://f-droid.org/repo/net.taler.wallet.fdroid_854.apk}" : "${STABLE_APK_NAME:=net.taler.wallet.fdroid_854.apk}" : "${MASTER_BRANCH:=master}" : "${FIX_BRANCH:=dev/hernani-inference/fix-bank-withdraw-auto-exchange}" -# Order: stable-self (warn) → published (blocker) → master. -: "${VARIANTS:=stable-self,published,master}" +: "${VARIANTS:=stable-self,published,older,master}" +: "${INCLUDE_OLDER:=1}" +: "${OLDER_TAG_COUNT:=4}" +: "${BLOCKER_MAX_AGE_DAYS:=14}" +: "${PUBLISHED_FORCE_BLOCKER:=0}" : "${FAIL_FAST:=1}" : "${GUI:=0}" : "${STACK:=auto}" +: "${TALER_ANDROID_SRC:=$HOME/taler/taler-android}" +export TALER_ANDROID_SRC BLOCKER_MAX_AGE_DAYS TS=$(date +%Y%m%d-%H%M%S) MATRIX_OUT="${MATRIX_OUT:-$ROOT/out-matrix-$TS}" @@ -55,11 +53,9 @@ SUMMARY="$MATRIX_OUT/SUMMARY.txt" echo "=== Android variant matrix ===" | tee -a "$SUMMARY" echo "STACK=$STACK GUI=$GUI headless=${EMULATOR_HEADLESS} variants=$VARIANTS" | tee -a "$SUMMARY" -echo "STABLE_TAG=$STABLE_TAG MASTER_BRANCH=$MASTER_BRANCH" | tee -a "$SUMMARY" -echo "severity: stable-self=WARN published=BLOCKER master=WARN" | tee -a "$SUMMARY" +echo "STABLE_TAG=$STABLE_TAG BLOCKER_MAX_AGE_DAYS=$BLOCKER_MAX_AGE_DAYS OLDER_TAG_COUNT=$OLDER_TAG_COUNT" | tee -a "$SUMMARY" echo "out: $MATRIX_OUT" | tee -a "$SUMMARY" -# Ensure device once (headless AVD by default) android_ensure_device || { echo "no adb device — start ./start-android-emulator.sh --wait" >&2 exit 3 @@ -67,63 +63,114 @@ android_ensure_device || { export SERIAL ANDROID_SERIAL echo "device: $SERIAL" | tee -a "$SUMMARY" -IFS=',' read -r -a VLIST <<<"$VARIANTS" +# --- expand VARIANTS (token "older" → concrete older-pub / older-self entries) --- +expand_variants() { + local raw="$1" + local -a out=() + local v tag vn age sev stable_vn + stable_vn="${STABLE_TAG#wallet-}" + + IFS=',' read -r -a toks <<<"$raw" + for v in "${toks[@]}"; do + v=$(echo "$v" | tr -d '[:space:]') + [ -n "$v" ] || continue + if [ "$v" = "older" ] || [ "$v" = "older-releases" ]; then + [ "$INCLUDE_OLDER" = "1" ] || continue + local -a tags=() + mapfile -t tags < <(list_wallet_release_tags "$OLDER_TAG_COUNT" 2>/dev/null || true) + local t + for t in "${tags[@]}"; do + [ -n "$t" ] || continue + # skip current stable tag (already covered by published / stable-self) + [ "$t" = "$STABLE_TAG" ] && continue + vn="${t#wallet-}" + age=$(release_age_days "$t" 2>/dev/null || echo "") + sev=$(severity_from_age_days "$age") + echo " older candidate $t age_days=${age:-?} severity=$sev" | tee -a "$SUMMARY" + # Prefer published APK when downloadable; always also allow self-build token + local meta apk_vc apk_url + meta=$(fdroid_apk_for_version_name "$vn" 2>/dev/null || true) + if [ -n "$meta" ]; then + apk_vc="${meta%%|*}" + apk_url="${meta#*|}" + if fdroid_apk_exists "$apk_url"; then + out+=("older-pub:${vn}:${apk_vc}:${sev}") + else + echo " (no F-Droid APK for $vn at $apk_url — self-build only)" | tee -a "$SUMMARY" + out+=("older-self:${t}:${sev}") + fi + else + out+=("older-self:${t}:${sev}") + fi + done + else + out+=("$v") + fi + done + printf '%s\n' "${out[@]}" +} + +mapfile -t VLIST < <(expand_variants "$VARIANTS") +echo "expanded variants: ${VLIST[*]}" | tee -a "$SUMMARY" + declare -a RESULTS=() declare -a WARNINGS=() overall=0 -# Severity: warn | blocker | soft -# warn — log WARN, never FAIL_FAST, never overall≠0 -# blocker — FAIL_FAST eligible; overall≠0 -# soft — overall≠0 on fail, but FAIL_FAST only if FAIL_FAST=1 and not warn -variant_severity() { - case "$1" in - stable-self|stable|self-stable|release-self) echo warn ;; - master|main) echo warn ;; - published|fdroid|stable-published) echo blocker ;; - # optional fix branch etc.: soft fail (counts) unless overridden - *) echo soft ;; - esac +severity_for_published_current() { + if [ "${PUBLISHED_FORCE_BLOCKER}" = "1" ]; then + echo blocker + return + fi + local days + days=$(release_age_days "$STABLE_TAG" 2>/dev/null || true) + severity_from_age_days "$days" } -run_published() { - local out="$MATRIX_OUT/published" +run_published_apk() { + local label="$1" + local apk_url="$2" + local apk_name="$3" + local sev="$4" + local out="$MATRIX_OUT/$label" mkdir -p "$out" echo "" | tee -a "$SUMMARY" - echo "-------- variant: published (F-Droid stable binary) [BLOCKER] --------" | tee -a "$SUMMARY" + echo "-------- variant: $label (F-Droid APK) [$(echo "$sev" | tr 'a-z' 'A-Z')] --------" | tee -a "$SUMMARY" + echo " url=$apk_url" | tee -a "$SUMMARY" local smoke="$ROOT/run-android-pay-smoke.sh" [ "$GUI" = "1" ] && smoke="$ROOT/run-android-gui-smoke.sh" + local strict=1 + [ "$sev" = "warn" ] && strict="${SMOKE_STRICT_WARN:-0}" + [ "$sev" = "blocker" ] && strict="${SMOKE_STRICT:-1}" set +e env STACK="$STACK" \ - APK_URL="$STABLE_APK_URL" \ - APK_NAME="$STABLE_APK_NAME" \ + APK_URL="$apk_url" \ + APK_NAME="$apk_name" \ APK_PATH="" \ PKG=net.taler.wallet.fdroid \ OUT_DIR="$out" \ SERIAL="$SERIAL" \ AUTO_START_EMULATOR=0 \ - SMOKE_STRICT="${SMOKE_STRICT:-1}" \ + SMOKE_STRICT="$strict" \ "$smoke" local rc=$? set -e - echo "published exit=$rc" | tee -a "$SUMMARY" - echo "published $rc blocker" >>"$MATRIX_OUT/results.tsv" + echo "$label exit=$rc severity=$sev" | tee -a "$SUMMARY" + echo "$label $rc $sev $apk_url" >>"$MATRIX_OUT/results.tsv" return "$rc" } run_selfbuild() { local label="$1" local ref="$2" - local sev="${3:-soft}" + local sev="${3:-warn}" local out="$MATRIX_OUT/$label" mkdir -p "$out" echo "" | tee -a "$SUMMARY" echo "-------- variant: $label (self-build ref=$ref) [$(echo "$sev" | tr 'a-z' 'A-Z')] --------" | tee -a "$SUMMARY" - # Warn-only self-build: still collect evidence; SMOKE_STRICT optional (default off for warn) local strict="${SMOKE_STRICT:-1}" - if [ "$sev" = "warn" ]; then - strict="${SMOKE_STRICT_SELF:-0}" - fi + [ "$sev" = "warn" ] && strict="${SMOKE_STRICT_SELF:-0}" + [ "$sev" = "blocker" ] && strict="${SMOKE_STRICT:-1}" set +e env STACK="$STACK" \ BRANCH="$ref" \ @@ -135,6 +182,7 @@ run_selfbuild() { AUTO_START_EMULATOR=0 \ PKG=net.taler.wallet.fdroid.debug \ SMOKE_STRICT="$strict" \ + TALER_ANDROID_SRC="$TALER_ANDROID_SRC" \ "$ROOT/run-android-build-and-smoke.sh" local rc=$? set -e @@ -147,63 +195,108 @@ run_selfbuild() { return "$rc" } +record_result() { + local v="$1" rc="$2" sev="$3" + if [ "$rc" -eq 0 ]; then + RESULTS+=("$v:OK($sev)") + return + fi + case "$sev" in + warn) + echo "WARN: $v failed rc=$rc — not a blocker; continuing" | tee -a "$SUMMARY" + WARNINGS+=("$v:$rc") + RESULTS+=("$v:WARN($rc)") + ;; + blocker) + echo "BLOCKER: $v failed rc=$rc (release within ${BLOCKER_MAX_AGE_DAYS}d window)" | tee -a "$SUMMARY" + RESULTS+=("$v:BLOCKER($rc)") + overall=1 + if [ "$FAIL_FAST" = "1" ]; then + echo "FAIL_FAST: stopping after blocker $v" | tee -a "$SUMMARY" + return 2 + fi + ;; + soft) + echo "FAIL: $v rc=$rc" | tee -a "$SUMMARY" + RESULTS+=("$v:FAIL($rc)") + overall=1 + if [ "$FAIL_FAST" = "1" ]; then + echo "FAIL_FAST: stopping after $v" | tee -a "$SUMMARY" + return 2 + fi + ;; + esac + return 0 +} + for raw in "${VLIST[@]}"; do v=$(echo "$raw" | tr -d '[:space:]') [ -n "$v" ] || continue rc=0 - sev=$(variant_severity "$v") + sev=warn + stop=0 + case "$v" in published|fdroid|stable-published) - run_published || rc=$? + sev=$(severity_for_published_current) + age=$(release_age_days "$STABLE_TAG" 2>/dev/null || echo "?") + echo "published current: tag=$STABLE_TAG age_days=$age → severity=$sev" | tee -a "$SUMMARY" + run_published_apk "published" "$STABLE_APK_URL" "$STABLE_APK_NAME" "$sev" || rc=$? ;; stable-self|stable|self-stable|release-self) + # rebuild of stable: always warn (toolchain), age does not make self-build a blocker + sev=warn run_selfbuild "stable-self" "$STABLE_TAG" warn || rc=$? ;; master|main) + sev=warn run_selfbuild "master" "$MASTER_BRANCH" warn || rc=$? ;; fix|fix-branch|inference) + sev=soft run_selfbuild "fix" "$FIX_BRANCH" soft || rc=$? ;; + older-pub:*) + # older-pub:VERSION:VC:SEV + IFS=':' read -r _ vn apk_vc sev <<<"$v" + : "${sev:=warn}" + label="older-pub-${vn}" + url="https://f-droid.org/repo/net.taler.wallet.fdroid_${apk_vc}.apk" + name="net.taler.wallet.fdroid_${apk_vc}.apk" + age=$(release_age_days "wallet-${vn}" 2>/dev/null || echo "?") + echo "older published $vn age_days=$age severity=$sev" | tee -a "$SUMMARY" + run_published_apk "$label" "$url" "$name" "$sev" || rc=$? + v="$label" + ;; + older-self:*) + # older-self:TAG:SEV or older-self:TAG + IFS=':' read -r _ tag sev <<<"$v" + : "${sev:=$(severity_for_tag "$tag")}" + label="older-self-${tag}" + age=$(release_age_days "$tag" 2>/dev/null || echo "?") + echo "older self-build $tag age_days=$age severity=$sev" | tee -a "$SUMMARY" + run_selfbuild "$label" "$tag" "$sev" || rc=$? + v="$label" + ;; *) - run_selfbuild "ref-${v//\//-}" "$v" soft || rc=$? + # raw git ref — warn unless looks like wallet-X.Y and young + if [[ "$v" =~ ^wallet-[0-9] ]]; then + sev=$(severity_for_tag "$v") + run_selfbuild "ref-${v}" "$v" "$sev" || rc=$? + else + sev=warn + run_selfbuild "ref-${v//\//-}" "$v" warn || rc=$? + fi ;; esac - if [ "$rc" -ne 0 ]; then - case "$sev" in - warn) - echo "WARN: variant $v failed rc=$rc — self-build, NOT a blocker; continuing" | tee -a "$SUMMARY" - WARNINGS+=("$v:$rc") - RESULTS+=("$v:WARN($rc)") - # do not set overall; do not FAIL_FAST - ;; - blocker) - echo "BLOCKER: variant $v failed rc=$rc" | tee -a "$SUMMARY" - RESULTS+=("$v:BLOCKER($rc)") - overall=1 - if [ "$FAIL_FAST" = "1" ]; then - echo "FAIL_FAST: stopping after blocker $v" | tee -a "$SUMMARY" - break - fi - ;; - soft) - echo "FAIL: variant $v rc=$rc (counts toward overall)" | tee -a "$SUMMARY" - RESULTS+=("$v:FAIL($rc)") - overall=1 - if [ "$FAIL_FAST" = "1" ]; then - echo "FAIL_FAST: stopping after $v rc=$rc" | tee -a "$SUMMARY" - break - fi - ;; - esac - else - RESULTS+=("$v:OK") - fi + record_result "$v" "$rc" "$sev" || stop=$? + [ "$stop" = "2" ] && break done echo "" | tee -a "$SUMMARY" echo "=== matrix done (overall=$overall) ===" | tee -a "$SUMMARY" +echo "rule: released app age ≤ ${BLOCKER_MAX_AGE_DAYS}d → BLOCKER on fail; older → WARN" | tee -a "$SUMMARY" for r in "${RESULTS[@]}"; do echo " $r" | tee -a "$SUMMARY" done @@ -214,6 +307,5 @@ if [ "${#WARNINGS[@]}" -gt 0 ]; then done fi echo "" -echo "Compare OUT_DIR trees under $MATRIX_OUT" -echo "stable-self / master failure = WARN only; published failure = BLOCKER." +echo "Artifacts: $MATRIX_OUT" exit "$overall"