Skip to content

fix: handoff audit — enforce config validation, repair broken provider paths, add guardrails#53

Draft
betmoar wants to merge 1 commit into
mainfrom
claude/codebase-handoff-audit-luqkzq
Draft

fix: handoff audit — enforce config validation, repair broken provider paths, add guardrails#53
betmoar wants to merge 1 commit into
mainfrom
claude/codebase-handoff-audit-luqkzq

Conversation

@betmoar

@betmoar betmoar commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Handoff audit: fixes + self-enforcing guardrails

Full principal-architect audit of the codebase (recon → deep audit → remediation → infrastructure). Every fix is locked by an invariant test in tests/test_handoff_invariants.py (I1–I8), and the judgment behind them is encoded in three new docs so it survives the handoff.

Correctness fixes (production path)

Bug Impact Fix
Config validation never ranBaseConfig._validate() had an empty body; the ~13 type/range/path rules registered in _setup_validation were dead code Any garbage env value accepted silently _validate() now feeds dataclass fields to ConfigValidator; added cross-field rule overlap_duration < segment_length (I1)
Infinite loop in split_audio when overlap_duration >= segment_length (step ≤ 0 → while-loop never advances) Hang + unbounded memory Runtime guard raises with actionable message; config is mutable post-load so the config-time check alone wasn't enough (I2)
ACRCloud provider unusable since inception — factory called ACRCloudProvider() with no credentials (instant TypeError), and identify_track took raw bytes while the pipeline passes AudioSegment -p acrcloud crashed unconditionally Factory reads TRACKLISTIFY_ACR_ACCESS_KEY/_SECRET/_HOST from env (secrets deliberately kept off the config dataclass), raises ConfigError naming the exact vars when missing; identify_track accepts AudioSegment or bytes (I3, I4)
Provider fallback was a no-opfallback_enabled, fallback_providers, and --no-fallback were read by nothing Documented feature didn't exist IdentificationManager walks a primary→fallback provider chain per segment, with per-provider rate limiting (I5)
Circuit breaker never learned_update_circuit_breaker had zero production callers Breaker could never open New public RateLimiter.record_result(), called on every provider outcome (I6)
Cache TTL disabledset() stored ttl: None, which shadowed the strategy default in metadata.get("ttl", default_ttl), so is_valid always returned True Entries never expired set() stamps the cache-level TTL (I7)
Cache index only persisted from cleanup()/clear() Entries written by one process invisible to the next, then deleted as "orphans" by the next cleanup — silent data loss Index persisted on every set/delete; index save now fsyncs before rename (I8)

Smaller: CLI fail-fast when ffmpeg missing (was a cryptic per-segment error); exporter mkdir(parents=True); Spotify 429 carries structured retry_after; cz bump repaired (version_provider was still "poetry" post-uv-migration); scripts/generate_config_docs.py called a method that never existed; removed dead module-level identify_tracks() that iterated a string as segments.

Infrastructure (the actual deliverable)

  • CI (.github/workflows/ci.yml) — the repo had no CI at all. Now: ruff lint + format check, .env.example drift check (generate_env_example.py --check), pytest on 3.11/3.12/3.13 with ffmpeg installed.
  • tests/test_handoff_invariants.py — 19 tests locking the 8 named invariants above; header explains each.
  • docs/ARCHITECTURE.md — load-bearing inventory (ranked by blast radius), implicit contracts, landmines (e.g. min_confidence is 0–1 while Track.confidence is 0–100 and the knob is deliberately unwired; the cache subsystem is still not wired into the pipeline), and touch-X-update-Y couplings.
  • docs/PLAYBOOKS.md — literal step-by-step procedures (add a provider / config option / output format; touch split_audio / rate limiter / config loading; 3am "no tracks identified" triage), each ending with exact verification commands and the real trap that motivated it.
  • docs/BACKLOG.md — residual risk register: P1 wire the cache into identification (now safe; plan included), P2 min_confidence decision, P2 Spotify enrichment wiring vs. the broken client-credentials /me/* playlist path, P3 dead downloaders/spotify.py, dev_cli cleanup, security polish. Plus a "fixed, do not re-fix" table.
  • CLAUDE.md — corrected a gotcha that lied (per-module logger overrides don't exist), added the new invariants and doc pointers.

Verification

  • uv run python -m pytest -q388 passed (369 pre-existing + 19 new), 2 environment skips
  • uv run ruff check / ruff format --check on src/ tests/ scripts/ → clean
  • uv run python scripts/generate_env_example.py --check → in sync
  • End-to-end with real ffmpeg: generated a 180 s audio file, ran AsyncApp.split_audio → 4 segments at the correct 50 s stride, sorted, non-empty; step guard fires with the actionable message
  • Empirically confirmed each bug before fixing (e.g. ACRCloudProvider TypeError via factory, out-of-range TRACKLISTIFY_SEGMENT_LENGTH=5000 accepted pre-fix, rejected post-fix)

🤖 Generated with Claude Code

https://claude.ai/code/session_01LRC2BkXVyNBfa2a6w844uU


Generated by Claude Code

…r paths, add guardrails

Phase-3/4 output of the 2026-07 principal-architect handoff audit. Every
fix is locked by tests/test_handoff_invariants.py (19 tests, invariants
I1-I8) and documented in docs/ARCHITECTURE.md / docs/BACKLOG.md.

Correctness (production path):
- Config validation rules were declared but NEVER executed:
  BaseConfig._validate() had an empty body. It now runs every registered
  rule against the dataclass fields, plus a new cross-field rule
  (overlap_duration < segment_length).
- split_audio: guard against non-positive step (segment_length <=
  overlap_duration previously hung in an infinite while-loop; config is
  mutable post-load so the config-time check alone is insufficient).
- ACRCloud provider was unusable end-to-end: the factory constructed it
  with no credentials (TypeError) and identify_track took raw bytes while
  the pipeline passes AudioSegment. Factory now reads
  TRACKLISTIFY_ACR_ACCESS_KEY/_SECRET/_HOST (ConfigError with actionable
  message when missing); identify_track accepts AudioSegment or bytes.
- Provider fallback was a complete no-op: fallback_enabled,
  fallback_providers and --no-fallback were read by nothing.
  IdentificationManager now walks a primary+fallback provider chain per
  segment.
- Circuit breaker never received request outcomes; new public
  RateLimiter.record_result() is called on every provider success/failure.
- Cache TTL was disabled (set() stored ttl=None which shadowed the
  strategy default -> is_valid always True) and the cache index was only
  persisted from cleanup()/clear(), so entries written by one process
  were invisible to the next and then deleted as "orphans". set()/delete()
  now persist the index; index save fsyncs before rename.

Smaller fixes:
- cli: fail fast with an actionable error when ffmpeg is missing.
- exporters: output_dir mkdir(parents=True).
- Spotify 429 now carries structured retry_after on RateLimitError.
- commitizen version_provider poetry -> pep621 (cz bump was broken since
  the uv migration).
- scripts/generate_config_docs.py called a method that never existed;
  now uses ConfigDocGenerator.
- Removed dead module-level identify_tracks() that iterated a string as
  segments.

Infrastructure:
- .github/workflows/ci.yml: ruff lint + format check, .env.example drift
  check, pytest on 3.11/3.12/3.13 (repo previously had NO CI).
- docs/ARCHITECTURE.md: load-bearing map, implicit contracts, landmines,
  touch-X-update-Y couplings.
- docs/PLAYBOOKS.md: step-by-step procedures for recurring changes.
- docs/BACKLOG.md: residual risk register + prioritized backlog.
- CLAUDE.md: corrected stale gotchas, new invariant pointers.
- .env.example regenerated (ACR credential vars incl. new _HOST).

388 tests pass (369 existing + 19 new invariants).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants