- 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
113 lines
4.6 KiB
Bash
Executable File
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
|