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.
First step of specs/dot_alfred_v2.md. Introduces a separate bounded
context (alfred/domain/releases/) for the filesystem-side aggregates,
disjoint from TMDB identity which stays in tv_shows/ and movies/.
The link between the two worlds is TmdbId, used as the natural key
in the persistence layer (no domain-level reference).
New package alfred/domain/releases/:
- value_objects: EpisodeRange (covers SxxE01E02E03 multi-episode
files via start/end inclusive range, with count/numbers/is_single
helpers), ReleaseMode enum (PACK = N video files direct in the
season folder, EPISODIC = N sub-folders).
- entities: TrackProfile, EpisodeRelease, SeasonRelease (with
episode_count() summing each EpisodeRange.count()), SeriesRelease
(tmdb_id primary anchor, optional imdb_id secondary), MovieRelease.
All frozen dataclasses.
- builders: SeasonReleaseBuilder + SeriesReleaseBuilder mirroring
the v1 TVShowBuilder pattern. Builders sort episodes by range
start on emit and reject overlapping ranges (two files claiming
the same TMDB slot). from_existing() seeds a builder from an
existing frozen aggregate for round-trip edits.
- repositories: abstract ports (SeriesReleaseRepository,
MovieReleaseRepository); concrete .alfred sidecar impls arrive
in Phase 2.
New shared VO alfred/domain/shared/value_objects.py::TmdbId — positive
int, rejects bool/str/float, symmetric with the existing ImdbId VO.
73 unit tests cover VO validation, entity invariants, builder sort
+ overlap detection, and from_existing() round-trips.
v1 code paths are untouched at this stage; the new domain coexists
with the old TVShow aggregate until Phase 3 refactors it.