feat(.alfred v2 — Phase 2): Pydantic sidecars, atomic repos, auto-heal index
Spec: specs/dot_alfred_v2.md (Phase 2).
New package alfred/infrastructure/persistence/dot_alfred/v2/:
* sidecar_release.py / sidecar_root.py — Pydantic DTOs
(extra="forbid", frozen=True) for per-item sidecars and the
library-root index. schema_version enforced via model_validator.
* serializer.py — read_yaml / atomic_write_yaml (.tmp + os.replace).
SidecarSchemaError wraps YAML + Pydantic errors uniformly.
* bridge.py — lossless domain <-> sidecar for SeriesRelease /
MovieRelease; projection-only show_index_entry_from /
movie_index_entry_from with multi-episode-file flattening.
* repository.py — DotAlfredSeriesReleaseRepository /
DotAlfredMovieReleaseRepository (log+skip on corruption),
DotAlfredTVShowLibraryIndex / DotAlfredMovieLibraryIndex with
silent auto-heal on missing/corrupt index reads. Writes never
auto-heal (read paths handle that).
TMDB client extensions:
* TmdbSeasonInfo / TmdbShowInfo DTOs + pure parse_tv_show_info.
* TMDBClient.get_tv_show_info aggregates /tv/{id} +
/tv/{id}/external_ids.
Domain change:
* SubtitleTrack gains is_sdh: bool = False, populated from
ffprobe's hearing_impaired disposition. Required for v2 sidecar
parity (spec replaces v1's type: "sdh" with explicit flag).
Default keeps every existing caller unchanged.
Tests: 37 new v2 integration tests on tmp_path (round-trips, atomic
writes, schema mismatch handling, anchor warnings, auto-heal paths)
plus 16 TMDB DTO tests. Full suite: 1240 -> 1277 passed.
Implementation notes filed in .claude/specs/dot_alfred_v2_notes.md
(strict=True trade-off, upsert signature deviation from spec, etc.).
Phases 3-5 (TVShow/Movie refactor to TMDB-only, rescan_show rewrite,
v1 deletion + wiring) are next.
This commit is contained in:
@@ -17,6 +17,59 @@ callers).
|
||||
|
||||
### Added
|
||||
|
||||
- **`.alfred` v2 — Phase 2: new persistence package + TMDB client
|
||||
extensions.** Second phase of `specs/dot_alfred_v2.md` on branch
|
||||
`refactor/dot-alfred-v2`. The new
|
||||
`alfred/infrastructure/persistence/dot_alfred/v2/` package ships
|
||||
the full v2 sidecar stack while leaving v1 (and the existing
|
||||
`TVShow` aggregate) untouched — Phase 3 is the cutover.
|
||||
- **Pydantic DTOs** — `SeriesReleaseSidecar` /
|
||||
`MovieReleaseSidecar` (per-item), `TVShowLibraryIndexSidecar` /
|
||||
`MovieLibraryIndexSidecar` (library-root index). All built on a
|
||||
common `_Strict` base (`extra="forbid"`, `frozen=True`) with a
|
||||
`@model_validator` enforcing `schema_version == 1`.
|
||||
- **Track entries** — `AudioTrackEntry` / `SubtitleEntry` (sidecar
|
||||
cache shape, slimmed from the domain track types). `SubtitleEntry`
|
||||
carries `is_forced` + `is_sdh` as explicit booleans (v1's
|
||||
`type: "sdh"` overload is gone).
|
||||
- **Serializer** — `read_yaml` / `atomic_write_yaml` helpers
|
||||
centralize YAML I/O and atomic writes (`.tmp + os.replace`).
|
||||
`SidecarSchemaError` wraps both YAML parse errors and Pydantic
|
||||
validation errors for uniform catch-and-skip semantics.
|
||||
- **Bridge** — lossless `domain ↔ sidecar` conversion for
|
||||
`SeriesRelease` / `MovieRelease` (round-trippable, including
|
||||
multi-episode ranges and `is_sdh` subtitles); one-way projection
|
||||
for library-index entries (`show_index_entry_from`,
|
||||
`movie_index_entry_from`) that flattens multi-episode files into
|
||||
per-TMDB-slot maps in `seasons[*].episodes`.
|
||||
- **Repositories** —
|
||||
`DotAlfredSeriesReleaseRepository` /
|
||||
`DotAlfredMovieReleaseRepository` walk `library_root/*/` with
|
||||
log+skip on corruption; **`DotAlfredTVShowLibraryIndex`** /
|
||||
**`DotAlfredMovieLibraryIndex`** auto-heal silently on missing or
|
||||
corrupt index files by rebuilding from the per-item sidecars
|
||||
(healed entries keep TMDB-cached fields as placeholders until the
|
||||
next sync repopulates them). Writes are atomic and never auto-heal
|
||||
(read paths handle that).
|
||||
- **TMDB client extensions** — `TmdbSeasonInfo` / `TmdbShowInfo`
|
||||
DTOs + `TMDBClient.get_tv_show_info(tmdb_id)` aggregating
|
||||
`/tv/{id}` + `/tv/{id}/external_ids`. The parsing logic is a pure
|
||||
function (`parse_tv_show_info`) testable without HTTP, with an
|
||||
injectable reference date for deterministic `aired` flag tests.
|
||||
- **`is_sdh` flag on `SubtitleTrack`.** Added to
|
||||
`alfred/domain/shared/media.py::SubtitleTrack` to mirror ffprobe's
|
||||
`hearing_impaired` disposition. Wired through the ffprobe layer
|
||||
(`ffprobe_prober.py`) and the v2 sidecar bridge so SDH information
|
||||
round-trips end-to-end. Defaults to `False` — backwards-compatible
|
||||
for every existing caller.
|
||||
- **37 v2 integration tests** on `tmp_path` covering round-trips
|
||||
(domain ↔ sidecar ↔ YAML ↔ domain), atomic writes (no `.tmp`
|
||||
leftovers), per-item log+skip on corruption / schema mismatch,
|
||||
movie anchor-mismatch warning, full upsert / find / delete on both
|
||||
library indexes, and the auto-heal path on missing / corrupt /
|
||||
schema-mismatched index files. **16 TMDB DTO tests** for the new
|
||||
`parse_tv_show_info` pure function.
|
||||
|
||||
- **`.alfred` v2 — Phase 1: new `releases/` domain.** First step of
|
||||
`specs/dot_alfred_v2.md` on branch `refactor/dot-alfred-v2`. The
|
||||
new `alfred/domain/releases/` package introduces a filesystem-only
|
||||
|
||||
Reference in New Issue
Block a user