Anti-corruption boundary tightened on the TMDB adapter:
* TmdbMovieInfo / TmdbShowInfo now carry domain VOs (TmdbId, ImdbId,
MovieTitle, ReleaseYear, ShowStatus) instead of raw scalars —
validation happens at the boundary, not three layers later.
* ShowStatus enum added (domain/tv_shows/value_objects) with a
from_tmdb() mapper that falls back to UNKNOWN + logs a warning on
unrecognized values. TVShow.status is now ShowStatus, not str.
* MovieTitle cap raised from 100 to 150 chars.
* MediaResult / ExternalIds dropped. Replaced by per-media search
DTOs: TmdbMovieSearchResult and TmdbShowSearchResult. Neither
carries imdb_id — search no longer enriches with external_ids
(callers needing imdb_id follow up with get_movie_info /
get_tv_show_info on the chosen tmdb_id).
* TMDBClient: search_multi / search_media / _parse_result removed.
search_movies (/search/movie) and search_shows (/search/tv) added,
each parsing hits into VO-typed DTOs.
* SearchMovieUseCase returns a list of MovieHit (flattened to
primitives for the agent). New symmetric SearchShowUseCase +
ShowHit / SearchShowResponse DTOs.
* agent/tools/api.py: find_media_imdb_id → search_movies +
search_shows wrappers.
* FileEntry moved from domain/shared/ports/filesystem_scanner.py to
domain/shared/file_entry.py (it's a DTO, not a Protocol); size_kb
(float) → size (int bytes). Scanner and SubtitleIdentifier
updated.
Tests: 79/79 pass on tests/infrastructure/api/ +
tests/application/test_search_movie.py +
tests/application/test_search_show.py.
Series repo returns (release, folder) so the upcoming sync
orchestrator can feed the library index's upsert(..., path=...).
Movie repo returns the release alone (folder is on release.folder
by the one-folder-one-file convention) — kept as a semantic alias
of find_by_tmdb_id for symmetry with the series side.
Two small additions that close out Phase 4's loose ends.
Settings — tmdb_cache_ttl_days
class Settings(BaseSettings):
# --- DOT_ALFRED ---
tmdb_cache_ttl_days: int = 14
Default 14 days, matching the dot_alfred_v2 master spec. Will drive
the Phase 5 TTL policy on TVShowLibraryIndexSidecar /
MovieLibraryIndexSidecar (decide when a TMDB-cached entry is stale
and triggers a refresh sync).
Anchor-mismatch warning
DotAlfredTVShowLibraryIndex._load_or_heal and DotAlfredMovieLibraryIndex
._load_or_heal now cross-check each indexed entry's metadata.path
against the on-disk folder layout right after a successful parse.
Drift (sidecar says folder X, X no longer exists under library_root)
is surfaced as a WARNING log — one per missing folder, with the
tmdb_id for cross-reference. No auto-heal on drift; the caller
decides (the heal path remains opt-in via index.heal()).
The warning fires only on the parsed-index path. The heal path
always synthesizes entries from real folder names, so it can never
drift — silent by construction.
Tests
* TestTVShowLibraryIndexAnchorWarning — 3 scenarios:
warn-on-drift / no-warn-on-match / no-warn-on-heal.
* TestMovieLibraryIndexAnchorWarning — symmetric coverage.
Full suite: 1237 passed / 8 skipped / 4 xfailed.
Phase 3 prep: Movie aggregate is about to become TMDB-only (no
filesystem fields). added_at is a release-time observation, not a
TMDB-aggregate concern, so it moves to MovieRelease +
MovieReleaseSidecar.
- Add added_at: datetime (required) to MovieRelease with a
type-check in __post_init__.
- Add added_at: datetime (required) to MovieReleaseSidecar.
- Bump SCHEMA_VERSION 1 → 2 with a version-history note.
- Bridge round-trips added_at via Pydantic mode="json" (datetime
→ ISO 8601 string).
- Tests: update MovieRelease fixtures, add a validator test, add
an added_at round-trip test, switch hard-coded `1` assertions
to SCHEMA_VERSION for future-proofing.
No v1 sidecars in the wild yet — no migration code needed.