Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

### Fixed

- Redacted dependency-controlled exception messages and tracebacks from routine articulation-analysis failure logs while retaining the BandScope-owned operation and exception class for bounded diagnostics.
- Upgraded the local score PDF parser to `pdfjs-dist` 6.2.108, pinned Undici 7.29.0 across the workspace, and constrained PDF loading to copied in-memory bytes with a same-origin bundled worker and npm-generated lock provenance.

## [0.1.3] - 2026-04-29
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