← back to Ios Fleet Recording Qa
recording-qa.sh
160 lines
#!/usr/bin/env bash
# ios-fleet-recording-qa — pre-submission QA gate for App Store Guideline 2.1 device recordings.
# Rebuilds the readiness-probe the TK-11155 approved memos assumed existed (verify-recording + privacy-scan).
# Works for ALL 5 rejected apps (GovArbitrage, Homes on Spec, Beverly Hills Videos, Celebrity Signatures, 90210).
#
# Usage:
# recording-qa.sh <video.mov> [app-name] # run the gate, print human verdict
# recording-qa.sh --json <video.mov> # machine-readable verdict line
# recording-qa.sh --approve <video.mov> # AFTER a human PII eyeball, bind the review to the exact bytes
#
# Verdict codes (match the memo semantics):
# TECH_FAIL too short / mostly black / unreadable
# REJECTED_NOT_DEVICE no iPhone/iPad capture metadata (simulator or re-encoded — Apple rejects for 2.1)
# PRIVACY_FLAGGED OCR found email/phone/address text in a frame -> MUST review/redo
# TECH_PASS_PRIVACY_PENDING tech ok, OCR clean, but no human sign-off sidecar yet
# READY_TO_SEND tech ok + OCR clean + human sidecar present & matches current bytes
set -uo pipefail
MIN_SEC=20 # Apple/2.1 floor
WANT_SEC=30 # memo wants ~30-60s
FRAME_EVERY=2 # sample one frame every N seconds for OCR
BLACK_FRAC_FAIL=0.5
die(){ echo "recording-qa: $*" >&2; exit 2; }
command -v ffprobe >/dev/null || die "ffprobe missing (brew install ffmpeg)"
command -v ffmpeg >/dev/null || die "ffmpeg missing"
MODE="run"; JSON=0
case "${1:-}" in
--approve) MODE="approve"; shift;;
--json) JSON=1; shift;;
"") die "usage: recording-qa.sh <video.mov> [app-name] | --json <v> | --approve <v>";;
esac
VID="${1:-}"; APP="${2:-app}"
[ -f "$VID" ] || die "no such file: $VID"
SIDE="${VID}.privacy-reviewed.sha256"
# ---- human sign-off path -------------------------------------------------
if [ "$MODE" = "approve" ]; then
# refuse to bind a human review to a clip that doesn't already pass tech+OCR
chk=$("$0" --json "$VID" 2>/dev/null)
case "$chk" in
*'"verdict":"PRIVACY_FLAGGED"'*|*'"verdict":"TECH_FAIL"'*|*'"verdict":"REJECTED_NOT_DEVICE"'*)
echo "❌ refusing to approve — clip does not pass the gate:"; echo " $chk"; exit 1;;
esac
shasum -a 256 "$VID" | awk '{print $1}' > "$SIDE"
echo "✅ privacy review bound to bytes -> $SIDE"
echo " (re-run the gate; it should now report READY_TO_SEND)"
exit 0
fi
# ---- technical checks -----------------------------------------------------
DUR=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$VID" 2>/dev/null | cut -d. -f1); DUR=${DUR:-0}
W=$(ffprobe -v error -select_streams v:0 -show_entries stream=width -of csv=p=0 "$VID" 2>/dev/null); W=${W:-0}
H=$(ffprobe -v error -select_streams v:0 -show_entries stream=height -of csv=p=0 "$VID" 2>/dev/null); H=${H:-0}
MODEL=$(ffprobe -v error -show_entries format_tags=com.apple.quicktime.model -of csv=p=0 "$VID" 2>/dev/null)
MAKE=$(ffprobe -v error -show_entries format_tags=com.apple.quicktime.make -of csv=p=0 "$VID" 2>/dev/null)
TECH_NOTES=(); VERDICT=""; DETAIL=""
[ "$DUR" -ge "$MIN_SEC" ] 2>/dev/null || { VERDICT="TECH_FAIL"; DETAIL="duration ${DUR}s < ${MIN_SEC}s floor"; }
[ "$H" -gt "$W" ] 2>/dev/null || TECH_NOTES+=("not portrait (${W}x${H}) — iPhone captures are portrait; confirm this is intended")
[ "$DUR" -lt "$WANT_SEC" ] 2>/dev/null && [ -z "$VERDICT" ] && TECH_NOTES+=("short: ${DUR}s (memo wants ~${WANT_SEC}-60s)")
# real-device signal: iOS screen recordings carry com.apple.quicktime.model=iPhone/iPad.
# NOTE: this tag is legitimately STRIPPED by AirDrop / any re-encode, so treat its ABSENCE
# as a warning (not a hard reject). Hard-reject only on a positive simulator tell.
DEVICE_OK=0; SIM_TELL=0
case "$MODEL$MAKE" in *iPhone*|*iPad*|*Apple*) DEVICE_OK=1;; esac
case "$VID" in *[Ss]imulator*|*-sim.*|*_sim.*|*.sim.*) SIM_TELL=1;; esac
# black-frame detection over the whole clip
BLACK=$(ffmpeg -nostats -i "$VID" -vf "blackdetect=d=0.3:pic_th=0.98" -an -f null - 2>&1 \
| awk -F'black_duration:' '/black_duration/{s+=$2} END{printf "%d", s+0}')
BLACKFRAC=$(awk -v b="${BLACK:-0}" -v d="${DUR:-1}" 'BEGIN{ if(d<=0)d=1; printf "%.2f", b/d }')
awk -v f="$BLACKFRAC" -v t="$BLACK_FRAC_FAIL" 'BEGIN{exit !(f>t)}' && [ -z "$VERDICT" ] && {
VERDICT="TECH_FAIL"; DETAIL="mostly black (${BLACKFRAC} of clip) — bad capture"; }
if [ -z "$VERDICT" ] && [ "$SIM_TELL" -eq 1 ]; then
VERDICT="REJECTED_NOT_DEVICE"; DETAIL="filename indicates a simulator clip — Apple rejects simulator recordings for 2.1"
elif [ "$DEVICE_OK" -eq 0 ]; then
TECH_NOTES+=("physical-device capture UNCONFIRMED (no iPhone/iPad metadata — fine if AirDropped/edited; NOT fine if it's a simulator clip — eyeball it)")
fi
# ---- privacy / PII OCR scan ----------------------------------------------
PII_HITS=(); CRED_HITS=(); FRAMES=0
if [ -z "$VERDICT" ] && command -v tesseract >/dev/null 2>&1; then
TMP=$(mktemp -d); trap 'rm -rf "$TMP"' EXIT
ffmpeg -nostats -loglevel error -i "$VID" -vf "fps=1/${FRAME_EVERY}" "$TMP/f_%04d.png" 2>/dev/null || true
EMAIL='[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}'
PHONE='(\+?1[-. ]?)?\(?[0-9]{3}\)?[-. ][0-9]{3}[-. ][0-9]{4}'
ADDR='[0-9]{1,6} +([A-Z][a-z]+ ){1,3}(St|Street|Ave|Avenue|Rd|Road|Blvd|Dr|Drive|Ln|Lane|Ct|Court|Way|Pl|Place|Blvd)\b'
# credential lens (TK-11155): a clip can be PII-clean yet expose a plaintext
# username / API key / backend host. Apple's 2.1 item #4 ASKS for demo
# credentials, so this WARNS (conscious decision) — it never hard-fails.
CRED_USER='(User ?name|Username|Login|User ID)[: ]+[A-Za-z0-9._-]{3,}'
CRED_SECRET='(api[ _-]?key|secret|bearer|token|authorization)[: ]+[A-Za-z0-9._-]{8,}'
CRED_BASIC='Basic [A-Za-z0-9+/=]{12,}'
CRED_HOST='https?://[A-Za-z0-9.-]*(agentabrams\.com|localhost|127\.0\.0\.1)[^ ]*'
for f in "$TMP"/f_*.png; do
[ -e "$f" ] || continue
FRAMES=$((FRAMES+1))
TXT=$(tesseract "$f" stdout 2>/dev/null | tr -s ' ')
for pat in "$EMAIL:email" "$PHONE:phone" "$ADDR:street-address"; do
rx="${pat%:*}"; label="${pat##*:}"
m=$(printf '%s' "$TXT" | grep -oE "$rx" | head -1)
[ -n "$m" ] && PII_HITS+=("$(basename "$f") $label: $m")
done
# credential labels sit on their OWN OCR line above the value
# ("Username\n\nadmin"), so match against a newline-flattened copy.
TXTF=$(printf '%s' "$TXT" | tr '\n' ' ' | tr -s ' ')
for pat in "$CRED_USER:username" "$CRED_SECRET:api-secret" "$CRED_BASIC:basic-auth" "$CRED_HOST:backend-host"; do
rx="${pat%:*}"; label="${pat##*:}"
m=$(printf '%s' "$TXTF" | grep -oiE "$rx" | head -1)
# drop label-followed-by-ordinary-word noise ("Login with", "Password required")
case "$(printf '%s' "$m" | awk '{print tolower($NF)}')" in
with|to|or|and|using|required|screen|button|here|below|above|optional|failed|error|settings) m="";; esac
# a masked value (dots/asterisks) is fine — only flag a VISIBLE one
case "$m" in *"..."*|*"***"*|*"•"*) m="";; esac
[ -n "$m" ] && CRED_HITS+=("$(basename "$f") $label: $m")
done
done
if [ "${#PII_HITS[@]}" -gt 0 ]; then VERDICT="PRIVACY_FLAGGED"; DETAIL="OCR found PII in ${#PII_HITS[@]} frame(s) — redo or crop"; fi
CRED_NOTE=""
[ "${#CRED_HITS[@]}" -gt 0 ] && CRED_NOTE=" · ${#CRED_HITS[@]} credential/backend string(s) VISIBLE — confirm intentional (Apple 2.1 #4 asks for demo credentials) or crop"
fi
# ---- final verdict --------------------------------------------------------
if [ -z "$VERDICT" ]; then
if [ -f "$SIDE" ] && [ "$(shasum -a 256 "$VID" | awk '{print $1}')" = "$(cat "$SIDE" 2>/dev/null)" ]; then
VERDICT="READY_TO_SEND"; DETAIL="tech PASS + OCR clean (${FRAMES} frames) + human sidecar matches"
else
VERDICT="TECH_PASS_PRIVACY_PENDING"; DETAIL="tech PASS + OCR clean (${FRAMES} frames); needs human eyeball then: recording-qa.sh --approve '$VID'"
fi
fi
if [ "$JSON" -eq 1 ]; then
printf '{"app":"%s","file":"%s","verdict":"%s","duration_s":%s,"dims":"%dx%d","device_meta":"%s","black_frac":%s,"ocr_frames":%d,"pii_hits":%d,"cred_hits":%d,"detail":"%s"}\n' \
"$APP" "$VID" "$VERDICT" "${DUR:-0}" "${W:-0}" "${H:-0}" "${MODEL:-none}" "$BLACKFRAC" "$FRAMES" "${#PII_HITS[@]}" "${#CRED_HITS[@]}" "$DETAIL${CRED_NOTE:-}"
case "$VERDICT" in READY_TO_SEND|TECH_PASS_PRIVACY_PENDING) exit 0;; *) exit 1;; esac
fi
echo "════════ recording-qa · $APP ════════"
echo "file : $VID"
echo "duration : ${DUR}s dims: ${W}x${H} device: ${MODEL:-<none>} ${MAKE:-}"
echo "black-frac : $BLACKFRAC ocr-frames: $FRAMES"
for n in "${TECH_NOTES[@]:-}"; do [ -n "$n" ] && echo "note : $n"; done
for h in "${PII_HITS[@]:-}"; do [ -n "$h" ] && echo "⚠️ PII : $h"; done
for h in "${CRED_HITS[@]:-}"; do [ -n "$h" ] && echo "🔑 CRED : $h"; done
echo "────────────────────────────────────"
echo "VERDICT : $VERDICT"
echo " $DETAIL${CRED_NOTE:-}"
case "$VERDICT" in
READY_TO_SEND) echo "➡️ attach this clip in the ASC Resolution Center reply."; exit 0;;
TECH_PASS_PRIVACY_PENDING) echo "➡️ eyeball it frame-by-frame; if clean run: recording-qa.sh --approve '$VID'"; exit 0;;
PRIVACY_FLAGGED) echo "➡️ do NOT upload — re-record or crop out the PII."; exit 1;;
REJECTED_NOT_DEVICE) echo "➡️ capture on a PHYSICAL iPhone (Control Center screen record), not the simulator."; exit 1;;
*) echo "➡️ re-capture."; exit 1;;
esac