diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e9c21e..575e567 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -184,6 +184,47 @@ callers). globally — noisy on parser mappers and orchestrator use-cases where early-return validation is essential complexity. Ignore `PLW0603` for the documented memory singleton (`infrastructure/persistence/context.py`). +- **Release-knowledge DDD purification** (`refactor/domain-release-knowledge`): + the last domain → infrastructure leak (`domain/release/value_objects.py` + loading YAML at import-time) is gone. Achieved via: + - **`ReleaseKnowledge` Protocol port** at + `alfred/domain/release/ports/knowledge.py` declares the read-only query + surface release parsing needs (token sets for resolutions, sources, codecs, + languages, hdr extras; structured dicts for audio, video_meta, editions, + media_type_tokens; separators list; file-extension sets used by + application/infra callers; `sanitize_for_fs(text)` method). + - **`YamlReleaseKnowledge` adapter** at + `alfred/infrastructure/knowledge/release_kb.py` loads every YAML constant + once at construction. Builds an immutable `str.maketrans` translation + table for filesystem sanitization. + - **`parse_release(name, kb)`** takes the knowledge as an explicit + parameter — no more module-level YAML loading inside the domain. Every + internal helper (`_tokenize`, `_extract_tech`, `_extract_languages`, + `_extract_audio`, `_extract_video_meta`, `_extract_edition`, + `_extract_title`, `_infer_media_type`, `_is_well_formed`) takes `kb`. + - **`ParsedRelease` Option B**: sanitization happens once at parse time + and is stored on a new `title_sanitized: str` field. Builder methods + (`show_folder_name`, `season_folder_name`, `episode_filename`, + `movie_folder_name`, `movie_filename`) are now pure — they accept + already-sanitized `tmdb_title_safe` / `tmdb_episode_title_safe` + arguments. Callers at the use-case boundary sanitize TMDB strings + via `kb.sanitize_for_fs(...)` before passing them in. + - **All domain-knowledge constants removed from `value_objects.py`**: + `_RESOLUTIONS`, `_SOURCES`, `_CODECS`, `_AUDIO`, `_VIDEO_META`, + `_EDITIONS`, `_HDR_EXTRA`, `_MEDIA_TYPE_TOKENS`, `_LANGUAGE_TOKENS`, + `_FORBIDDEN_CHARS`, `_VIDEO_EXTENSIONS`, `_NON_VIDEO_EXTENSIONS`, + `_SUBTITLE_EXTENSIONS`, `_METADATA_EXTENSIONS`, `_WIN_FORBIDDEN_TABLE`, + and the `_sanitize_for_fs` helper. The domain module is now pure. + - **Application-layer KB singleton**: `resolve_destination.py` instantiates + a module-level `_KB: ReleaseKnowledge = YamlReleaseKnowledge()` and + threads it through every `parse_release(...)` call. The local + `_sanitize` helper and `_WIN_FORBIDDEN` regex were dropped in favor of + `_KB.sanitize_for_fs(...)`. + - **`detect_media_type(parsed, source_path, kb)` and + `find_video_file(path, kb)`** now take the knowledge explicitly + instead of importing `_*_EXTENSIONS` constants from the domain. + `agent/tools/filesystem.py::analyze_release` imports the application + KB singleton and passes it through. ---