Files
Jarvis/scripts/probe-speech-assets.sh
james.maes b332214a7a feat(06-03): STTProvider + SpeechAnalyzerSTT + WhisperKitSTT + STTBackendSelector (VOICE-03/04)
- STTProvider protocol: transcribe(stream:) -> AsyncStream<PartialTranscript> + finalize()
- AudioChunk + PartialTranscript value types
- SpeechAnalyzerSTT: SpeechAnalyzerBridge seam; LiveSpeechAnalyzerBridge @available(macOS 26)
  + UnavailableSpeechAnalyzerBridge fallback; maps SFSpeechErrorCode.assetUnavailable → STTError.assetMissing
- WhisperKitSTT: LazyWhisperKitBridge defers async init; model='large-v3-v20240930_626MB' (anti-pattern guard)
  + WhisperKitBridge protocol seam for tests
- STTBackendSelector: make(backend:warningLogger:) — synchronous factory; 'speech_analyzer' default;
  unknown value falls back + emits warning via STTWarningLogger protocol
- STTError: backendUnavailable / assetMissing / finalizationFailed
- scripts/probe-speech-assets.sh: scaffold-time entitlement probe (AUDIT-R2-S5)
  uses ${VAR-} (no colon) for empty-string semantics per guardrail
- All tests pass: S1/S2 (SpeechAnalyzerSTT) + W1/W2 (WhisperKitSTT) + B1..B4 (selector)
- AVSpeechSmokeTests.swift stub created for Plan 06-04
2026-04-27 11:54:17 -05:00

113 lines
4.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# Scaffold-time probe: verifies that the speech-recognition-assets entitlement
# is load-bearing by cold-launching a Release archive with the entitlement stripped.
#
# Expected outcome: SFSpeechErrorCode.assetUnavailable fires in stderr within 3s.
# This confirms AUDIT-R2-S5: without the entitlement, on-device speech recognition
# assets cannot be downloaded and SpeechAnalyzer is non-functional.
#
# If STT works WITHOUT the entitlement, that refutes AUDIT-R2-S5 (the claim that the
# entitlement is strictly required). Either outcome is valid probe output — the script
# reports it clearly and exits 0 for pass, 2 for inconclusive. Only unexpected
# infrastructure errors exit 1.
#
# Usage:
# scripts/probe-speech-assets.sh path/to/Jarvis.app
#
# The --probe-stt flag is wired by Plan 06-05 (VoiceController). The flag instructs
# the app to immediately instantiate SpeechAnalyzerSTT and write any errors to stderr,
# then exit. Without the flag the probe may time-out waiting for normal app startup.
#
# IMPORTANT: Run this against a RELEASE archive (not Debug). The entitlement is
# enforced by the codesignature — in Debug the sandbox is relaxed and the probe
# may produce a false-negative (STT works without entitlement in Debug).
#
# Variable discipline: this script uses ${VAR-default} (no colon) where an empty
# value is semantically meaningful (e.g. ENTITLEMENT_STRIPPED=""), not ${VAR:-default}.
set -euo pipefail
APP="${1:?Usage: $0 path/to/Jarvis.app}"
if [ ! -d "$APP" ]; then
echo "ERROR: '$APP' is not a directory or does not exist" >&2
exit 1
fi
TMP=$(mktemp -d)
trap 'rm -rf "$TMP"' EXIT
echo "Probe: copying app to temp directory..."
cp -R "$APP" "$TMP/"
TARGET="$TMP/$(basename "$APP")"
# ---- Step 1: Extract current entitlements ----
echo "Probe: extracting entitlements..."
ENTITLEMENTS_FILE="$TMP/entitlements.plist"
codesign -d --entitlements :- "$TARGET" 2>/dev/null > "$ENTITLEMENTS_FILE" || {
echo "ERROR: Could not extract entitlements from $TARGET" >&2
exit 1
}
# Check if the speech-recognition-assets entitlement is present
ENTITLEMENT_KEY="com.apple.developer.speech-recognition-assets"
ENTITLEMENT_STRIPPED=""
if plutil -extract "$ENTITLEMENT_KEY" xml1 -o /dev/null "$ENTITLEMENTS_FILE" 2>/dev/null; then
# Entitlement is present — strip it for the probe
plutil -remove "$ENTITLEMENT_KEY" "$ENTITLEMENTS_FILE" 2>/dev/null || true
ENTITLEMENT_STRIPPED="yes"
echo "Probe: stripped '$ENTITLEMENT_KEY' from entitlements"
else
echo "WARNING: '$ENTITLEMENT_KEY' not found in app entitlements — probe may be inconclusive"
# Note: uses ${ENTITLEMENT_STRIPPED-} (no colon) — empty string is meaningful here
fi
# ---- Step 2: Re-sign with stripped entitlements ----
echo "Probe: re-signing with stripped entitlements (ad-hoc)..."
codesign --force --sign - --entitlements "$ENTITLEMENTS_FILE" "$TARGET" 2>/dev/null || {
echo "ERROR: Re-signing failed" >&2
exit 1
}
# ---- Step 3: Cold-launch and observe stderr ----
RESULT_LOG="$TMP/launch.log"
BINARY="$TARGET/Contents/MacOS/Jarvis"
if [ ! -f "$BINARY" ]; then
echo "ERROR: Binary not found at $BINARY" >&2
exit 1
fi
echo "Probe: cold-launching with --probe-stt flag (3s timeout)..."
# Note: --probe-stt flag is wired by Plan 06-05. Without it the binary may not
# write the STT error to stderr quickly enough.
"$BINARY" --probe-stt 2>&1 | tee "$RESULT_LOG" &
LAUNCH_PID=$!
sleep 3
kill -TERM "$LAUNCH_PID" 2>/dev/null || true
wait "$LAUNCH_PID" 2>/dev/null || true
# ---- Step 4: Evaluate result ----
if grep -qi "SFSpeechError\|assetUnavailable\|speech recognition assets" "$RESULT_LOG"; then
echo ""
echo "PROBE PASS: The speech-recognition-assets entitlement IS load-bearing."
echo " SFSpeechErrorCode.assetUnavailable (or similar) was observed when the entitlement was absent."
echo " AUDIT-R2-S5 confirmed."
exit 0
elif [ -z "${ENTITLEMENT_STRIPPED-}" ]; then
echo ""
echo "PROBE INCONCLUSIVE: The entitlement was not present in the original app binary."
echo " Re-run against a properly entitled Release archive."
exit 2
else
echo ""
echo "PROBE INCONCLUSIVE: The speech-recognition-assets entitlement may NOT be load-bearing."
echo " STT worked (or did not produce assetUnavailable) after entitlement was stripped."
echo " This may mean: (a) STT was not exercised due to missing --probe-stt flag in 06-05,"
echo " (b) the macOS version does not require the entitlement, or"
echo " (c) AUDIT-R2-S5 is refuted — investigate before shipping."
echo ""
echo " Log contents:"
cat "$RESULT_LOG"
exit 2 # Not a hard failure — document the result; not necessarily a bug.
fi