Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
- Display the analyzed song tempo (BPM) as a badge in the rehearsal workspace.
- 각 합주 역할(Role)별 개인 연습 진행도를 0~100% 범위로 기록 및 시각화할 수 있는 연습 진척도(`practiceProgress`) 트래커 기능 추가. UI 컨트롤(슬라이더 및 +/- 버튼)과 한/영 다국어 지원 포함.

### Fixed

- Redacted dependency-controlled exception messages and tracebacks from routine articulation-analysis failure logs while retaining the operation and exception class for bounded diagnostics.

## [0.1.3] - 2026-04-29

### Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
- All computations are bounded by the input array sizes.
- Fails safe: invalid, empty, or silent audio yields a neutral "mixed"
result with zeroed metrics, and no exceptions escape the public API.
- Unexpected dependency failures are logged with only the BandScope-owned
operation and exception class; dependency messages and tracebacks are not
retained in routine logs.
"""

from __future__ import annotations
Expand Down Expand Up @@ -138,8 +141,11 @@ def analyze_articulation(
"onset_density_per_s": round(onset_density, 3),
"duty_cycle": round(duty_cycle, 3),
}
except Exception:
logger.warning("Articulation analysis failed; returning safe default", exc_info=True)
except Exception as error:
logger.warning(
"Articulation analysis failed; returning safe default (%s)",
type(error).__name__,
)
return dict(_SAFE_DEFAULT)


Expand Down
28 changes: 25 additions & 3 deletions services/analysis-engine/tests/test_articulation.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Tests for sustained-versus-choppy articulation detection."""

import logging
from typing import Any

import numpy as np
Expand Down Expand Up @@ -99,14 +100,35 @@ def _zero_rms(**_kwargs: Any) -> NDArray[np.float32]:
assert analyze_articulation(_sine(1.0), SR) == SAFE_DEFAULT


def test_internal_failure_returns_safe_default(monkeypatch: pytest.MonkeyPatch) -> None:
"""No exception escapes: analysis failures return the safe default."""
def test_internal_failure_returns_payload_safe_default(
monkeypatch: pytest.MonkeyPatch,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Unexpected dependency failures stay out of routine articulation logs."""
sensitive_detail = "/Users/Alice/private-articulation.wav token=super-secret"

def _boom(**_kwargs: Any) -> NDArray[np.float32]:
raise RuntimeError("synthetic failure")
raise RuntimeError(sensitive_detail)

monkeypatch.setattr(articulation.librosa.onset, "onset_strength", _boom)
caplog.set_level(logging.WARNING, logger=articulation.__name__)

assert analyze_articulation(_sine(1.0), SR) == SAFE_DEFAULT
assert "Articulation analysis failed; returning safe default" in caplog.text
assert "/Users/Alice" not in caplog.text
assert "private-articulation.wav" not in caplog.text
assert "super-secret" not in caplog.text
matching_records = [
record
for record in caplog.records
if record.name == articulation.__name__
and record.getMessage().startswith("Articulation analysis failed; returning safe default")
]
assert len(matching_records) == 1
assert matching_records[0].getMessage() == (
"Articulation analysis failed; returning safe default (RuntimeError)"
)
assert matching_records[0].exc_info is None


def test_empty_stems_dict_returns_empty() -> None:
Expand Down
Loading