From d7d29f40be6483a5412b3d2ff382b6018e1e95a7 Mon Sep 17 00:00:00 2001 From: Ankush <43288948+AnkushMalaker@users.noreply.github.com> Date: Thu, 23 Jul 2026 16:55:19 +0530 Subject: [PATCH 1/3] fix(speaker-recognition): restore green integration CI Three independent problems kept the speaker-recognition workflow red on every run since 2026-07-15. 1. BuildKit disabled but required (the actual CI failure) run-test.sh exported DOCKER_BUILDKIT=0, forcing the legacy builder, while the Dockerfile uses `RUN --mount=type=cache` for the uv cache: Step 10/21 : RUN --mount=type=cache,target=/root/.cache/uv uv sync ... the --mount option requires BuildKit. At the last green commit (087a1060) the Dockerfile had a plain `RUN uv sync ...` with no --mount, and DOCKER_BUILDKIT=0 was already present. The cache mount was re-added later without dropping the export, so `docker compose up` died ~90s in on every run. Swap DOCKER_BUILDKIT=0 for BUILDKIT_PROGRESS=plain: the cache mount works and we keep the flat, greppable build log that disabling BuildKit was presumably meant to preserve. The workflow already runs docker/setup-buildx-action, so BuildKit was set up and then discarded. 2. `uv run` discarded the CPU torch selection run-test.sh synced `--extra cpu`, then bare `uv run` resynced to the default extra set, pulling PyPI CUDA torch plus 15 nvidia wheels on top of the CPU build - ~40s and ~12GB of pure waste per run. Pin the invocation to `uv run --extra cpu --group test`. 3. Phase 7 asserted a contract that never existed Phase 7 required a `words` field on every /diarize-and-identify segment, but that endpoint only diarizes and identifies - it has never transcribed, on main or dev. Phases 7-8 were added after the last green run (which had only Phases 1-6), so Phase 7 has never passed once. Point it at the structure the endpoint actually guarantees rather than bolting transcription onto a diarization endpoint. Word-level coverage is not lost: Phase 8 still validates it against /v1/diarize-identify-match, which is handed a transcript to match. Verified locally against the real service in a container with real HF/Deepgram credentials: 1 passed in 110.92s, with Phase 8 executing for the first time. The BuildKit change is read directly from the CI error and cannot be exercised locally (docker here is a podman shim that accepts --mount regardless), so it needs a CI run to confirm. --- extras/speaker-recognition/run-test.sh | 12 ++-- .../tests/test_speaker_service_integration.py | 57 +++++++------------ 2 files changed, 30 insertions(+), 39 deletions(-) diff --git a/extras/speaker-recognition/run-test.sh b/extras/speaker-recognition/run-test.sh index 9b89ad989..c70ce1b04 100755 --- a/extras/speaker-recognition/run-test.sh +++ b/extras/speaker-recognition/run-test.sh @@ -134,10 +134,13 @@ docker compose -f docker-compose-test.yml down -v || true # Run speaker recognition integration tests print_info "Running speaker recognition integration tests..." -print_info "Disabling BuildKit for integration tests (DOCKER_BUILDKIT=0)" +print_info "Building with BuildKit, plain progress output (BUILDKIT_PROGRESS=plain)" -# Set environment variables for the test -export DOCKER_BUILDKIT=0 +# BuildKit is required: the Dockerfile uses `RUN --mount=type=cache` for the uv +# cache, which the legacy builder rejects outright ("the --mount option requires +# BuildKit"). Plain progress keeps the unrolled, greppable build log that the +# legacy builder used to give us. +export BUILDKIT_PROGRESS=plain # Run the integration test with timeout (speaker recognition models need time) print_info "Starting speaker recognition test (timeout: 30 minutes)..." @@ -145,7 +148,8 @@ print_info "Starting speaker recognition test (timeout: 30 minutes)..." # Run test with proper signal forwarding and output handling { timeout --foreground --kill-after=60 1800 \ - uv run pytest tests/test_speaker_service_integration.py -v -s --tb=short --log-cli-level=INFO + uv run --extra cpu --group test \ + pytest tests/test_speaker_service_integration.py -v -s --tb=short --log-cli-level=INFO } || { exit_code=$? if [ $exit_code -eq 124 ]; then diff --git a/extras/speaker-recognition/tests/test_speaker_service_integration.py b/extras/speaker-recognition/tests/test_speaker_service_integration.py index ecc94e1c4..c4b60a41c 100644 --- a/extras/speaker-recognition/tests/test_speaker_service_integration.py +++ b/extras/speaker-recognition/tests/test_speaker_service_integration.py @@ -391,39 +391,28 @@ def test_speaker_recognition_pipeline(speaker_service): assert total_segments > 0, "No segments produced" print("✅ Conversation processing API works correctly") - # Phase 7: Word-Level Data Validation - print("📝 Phase 7: Validating word-level timestamp data in segments...") - segments_with_words = 0 - total_words_found = 0 - + # Phase 7: Segment Structure Validation + print("📝 Phase 7: Validating segment structure...") + # /diarize-and-identify only diarizes and identifies speakers - it does not + # transcribe, so its segments carry no word-level data. Word-level timestamps + # are validated in Phase 8 against /v1/diarize-identify-match, which is handed + # a transcript to match against. for seg in result["segments"]: - # Each segment should have a words array (empty segments might have empty array) - assert "words" in seg, f"Segment missing 'words' field: {seg}" - words = seg.get("words", []) - - if len(words) > 0: - segments_with_words += 1 - total_words_found += len(words) - - # Validate word structure - for word in words[:3]: # Check first 3 words of each segment - assert "word" in word, f"Word missing 'word' field: {word}" - assert "start" in word, f"Word missing 'start' field: {word}" - assert "end" in word, f"Word missing 'end' field: {word}" - # confidence is optional - assert isinstance( - word["start"], (int, float) - ), f"Word 'start' should be numeric: {word}" - assert isinstance( - word["end"], (int, float) - ), f"Word 'end' should be numeric: {word}" - - print( - f" ✅ Word-level data: {segments_with_words}/{total_segments} segments have words ({total_words_found} total words)" - ) - assert segments_with_words > 0, "No segments contain word-level timestamp data" - assert total_words_found > 0, "No words found across all segments" - print("✅ Word-level timestamp data validated successfully") + assert isinstance( + seg["start"], (int, float) + ), f"Segment 'start' should be numeric: {seg}" + assert isinstance( + seg["end"], (int, float) + ), f"Segment 'end' should be numeric: {seg}" + assert seg["end"] >= seg["start"], f"Segment ends before it starts: {seg}" + assert seg.get("status") in { + "identified", + "unknown", + "error", + }, f"Unexpected segment status: {seg}" + + print(f" ✅ All {total_segments} segments have a valid structure") + print("✅ Segment structure validated successfully") # Phase 8: Diarize-Identify-Match Endpoint (Backend Integration Mode) print( @@ -512,9 +501,7 @@ def test_speaker_recognition_pipeline(speaker_service): print( f"✅ Conversation processing: PASS ({total_segments} segments, {identified_segments} identified)" ) - print( - f"✅ Word-level timestamps: PASS ({total_words_found} words in {segments_with_words} segments)" - ) + print(f"✅ Segment structure: PASS ({total_segments} segments)") print( f"✅ Diarize-identify-match: PASS ({match_total_words} matched words in {match_segments_with_words} segments)" ) From e50da148d9925278afd7e00f51350b517a0e5178 Mon Sep 17 00:00:00 2001 From: Ankush <43288948+AnkushMalaker@users.noreply.github.com> Date: Thu, 23 Jul 2026 21:58:54 +0530 Subject: [PATCH 2/3] ci(speaker-recognition): trigger on dev, not the removed develop branch The workflow gated both its push and pull_request triggers on [main, develop], but develop was retired in favour of dev years ago and no longer exists on the remote (origin/HEAD -> origin/dev). The practical effect was silent: PRs targeting dev never ran the speaker recognition suite at all, so the only runs came from PRs into main. Point both triggers at dev so the tests actually gate the branch we merge into. ios-ipa-build.yml and android-apk-build.yml carry the same stale reference and are left alone here to keep this change scoped. --- .github/workflows/speaker-recognition-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/speaker-recognition-tests.yml b/.github/workflows/speaker-recognition-tests.yml index 1cb1e0276..0225fb47a 100644 --- a/.github/workflows/speaker-recognition-tests.yml +++ b/.github/workflows/speaker-recognition-tests.yml @@ -2,7 +2,7 @@ name: Speaker Recognition Tests on: push: - branches: [ main, develop ] + branches: [ main, dev ] paths: - 'extras/speaker-recognition/src/**' - 'extras/speaker-recognition/tests/**' @@ -13,7 +13,7 @@ on: - 'extras/speaker-recognition/run-test.sh' - '.github/workflows/speaker-recognition-tests.yml' pull_request: - branches: [ main, develop ] + branches: [ main, dev ] paths: - 'extras/speaker-recognition/src/**' - 'extras/speaker-recognition/tests/**' From 7abaf8d68a9d92c7166c0a8be8bb8225e1326bb2 Mon Sep 17 00:00:00 2001 From: Ankush <43288948+AnkushMalaker@users.noreply.github.com> Date: Thu, 23 Jul 2026 22:17:19 +0530 Subject: [PATCH 3/3] ci(app): build on dev for both pushes and pull requests The iOS and Android workflows had two overlapping gaps: push: branches: [main, develop] # develop no longer exists pull_request: branches: [main] # dev never included at all develop was retired in favour of dev, so the push trigger's second entry was dead. Worse, the pull_request trigger only ever covered main, so app changes merged into dev were never built - not by the stale entry, and not by the PR gate either. Four merged PRs touched app/** with base=dev and got no build: #319, #315, #311, #233. Point both triggers at [main, dev]. --- .github/workflows/android-apk-build.yml | 4 ++-- .github/workflows/ios-ipa-build.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/android-apk-build.yml b/.github/workflows/android-apk-build.yml index c08db2871..756da7077 100644 --- a/.github/workflows/android-apk-build.yml +++ b/.github/workflows/android-apk-build.yml @@ -5,10 +5,10 @@ permissions: on: push: - branches: [main, develop] + branches: [main, dev] paths: ['app/**'] pull_request: - branches: [main] + branches: [main, dev] paths: ['app/**'] workflow_dispatch: diff --git a/.github/workflows/ios-ipa-build.yml b/.github/workflows/ios-ipa-build.yml index e3a02be37..28d987f76 100644 --- a/.github/workflows/ios-ipa-build.yml +++ b/.github/workflows/ios-ipa-build.yml @@ -5,10 +5,10 @@ permissions: on: push: - branches: [main, develop] + branches: [main, dev] paths: ['app/**'] pull_request: - branches: [main] + branches: [main, dev] paths: ['app/**'] workflow_dispatch: