commit remaining modified project files

This commit is contained in:
windyboy
2026-04-07 08:07:18 +08:00
parent d010cf936c
commit fb128c70d6
28 changed files with 4140 additions and 19643 deletions
+152
View File
@@ -0,0 +1,152 @@
# Technical review: video-library-manager
## Metadata
| Field | Value |
|--------|--------|
| Repository path | `dl-organizer` (package `vlm`) |
| Verification date (UTC) | 2026-04-06 |
| Git revision verified | `ea21e15` |
| Test run | `uv run pytest -q`**496 passed** |
## Scope
- Source: `src/vlm/**/*.py`, `tests/`
- Config: `pyproject.toml`
- Assumption: single-user local library; artifacts semi-trusted; no formal threat model in repo.
## Executive summary
Architecture and tests are solid. Verified gaps: **no `library_root` check on move/rename source** in executor; **`by_quality` falls back to index 0 without surfacing reason**; **quarantine raises `ValueError` for unsupported category** while `execute_plan` has no per-operation try/except, so one bad op can abort the run; **duplicate join uses exact string path keys**; **`find` non-zero exit still consumes stdout**.
Weighted score (010): **8.0**.
## Score rubric
| Criterion | Score | Notes |
|-----------|-------|--------|
| Module boundaries / pipeline | 8.5 | Commands → domain modules; scan→parse→analyze→plan→execute is explicit. |
| Execution safety (filesystem) | 7.0 | Destination under root checked; **source not checked** for move/rename. |
| Planning / duplicate logic | 7.5 | Multiple strategies; string path identity; silent `by_quality` fallback; unknown `strategy` → keep index 0 (`duplicate_resolve.py:48`). |
| Data I/O & validation | 8.0 | `io.py` validates identities/analysis/plan shapes; validated objects remain dict-typed at boundaries (`type: ignore` in places). |
| Error handling consistency | 7.5 | Quarantine: `raise` vs `OperationResult` mismatch on category rejection. |
| Test signal | 8.5 | Broad `tests/`; path safety and resolver covered (`test_path_safety.py`, `test_duplicate_resolve.py`, etc.). |
| Dependencies | 9.0 | Runtime: `click`, `pyyaml` only (`pyproject.toml:79`). |
## Findings
### F1 [High] Move/rename: source not constrained to `library_root`
**Evidence:** `ExecutionEngine._perform_operation` checks destination with `is_within_root(..., self.config.library_root)` then calls `operation.source_path.rename(...)` without checking the source (`executor.py:354377`).
**Impact:** Malformed or hand-edited `plan.json` can rename **from any path the process can access** into the library tree. `QuarantineManager.quarantine_file` instead requires `file_path.relative_to(self.config.library_root)` (`quarantine.py:133137`).
**Recommendation:** For `move` / `rename`, require `is_within_root(operation.source_path, self.config.library_root)` (after `Path` resolution policy is defined). Fail with `OperationResult(success=False)`. Optional config gate for deliberate “import from outside,” default off. Add test alongside `tests/test_path_safety.py:76103`.
---
### F2 [Medium] `by_quality`: silent fallback to first item
**Evidence:** `choose_keep_index``if quality_comparison is None or len(quality_comparison) != len(items): return 0` (`duplicate_resolve.py:4042`).
**Impact:** Behavior equals **input order**, not quality, with no structured flag in plan metadata from this function alone.
**Recommendation:** Return `None` and skip auto-quarantine for that group, or require CLI/plan failure when `duplicate_keep == by_quality` and rows misaligned, or write explicit `metadata` / summary line when fallback occurs.
---
### F3 [Medium] Duplicate group ↔ identities join: exact path strings
**Evidence:** `path_to_index = {str(vf.path): i for ...}` and `indices = [path_to_index[p] for p in paths if p in path_to_index]` (`planner.py:108111`). Analysis `files` must match `str(VideoFile.path)` byte-for-byte.
**Impact:** Symlinks, differing normalization between analysis writer and identities loader, or OS case rules can **drop** files from duplicate resolution.
**Recommendation:** Normalize keys (e.g. `utils.canonical_path_str`) at both analysis emission and plan consumption; document contract; regression test if symlinks are in scope.
---
### F4 [LowMedium] Quarantine: `ValueError` vs `OperationResult`
**Evidence:** Unsupported category path does `raise ValueError(error_msg)` after logging (`quarantine.py:116129`). Other failure modes return `OperationResult`.
**Evidence:** `execute_plan` loop calls `execute_operation` with no try/except (`executor.py:110112`).
**Impact:** One invalid quarantine operation (e.g. manual plan) can **abort** the whole execute pass instead of recording a single failed result.
**Recommendation:** Return failed `OperationResult` for unsupported category; reserve exceptions for invariant violations only.
---
### F5 [Low] `find` non-zero exit: stdout still parsed
**Evidence:** On `process.returncode != 0`, code logs stderr if present, then always parses `stdout` into paths (`scanner.py:178192`).
**Impact:** Partial or stale listing possible without hard failure.
**Recommendation:** Document behavior; optionally fail if `returncode != 0` and empty result, or add `--strict-scan`.
---
### F6 [Low] Optional dependencies: `textual` in both `dev` and `tui`
**Evidence:** `pyproject.toml:1316` (`dev`), `1819` (`tui`).
**Impact:** Install surface ambiguity only.
**Recommendation:** Document that `dev` includes Textual for TUI-related tests, or slim `dev` if CI wants fewer deps.
---
### F7 [Informational] Validated JSON remains dict-typed at I/O edge
**Evidence:** `validate_plan_json` returns `dict` (`io.py:224251`); constructors may still bridge dict ↔ dataclass elsewhere.
**Impact:** Field drift between validator and `models.py` possible over time.
**Recommendation:** Single factory path: validated dict → domain objects for hot paths.
---
### F8 [Informational] Unknown `duplicate_keep` strategy string
**Evidence:** After strategy checks, `choose_keep_index` falls through to `return 0` (`duplicate_resolve.py:48`).
**Impact:** Config typo **keeps first duplicate** like `first_seen` without failing load.
**Recommendation:** Validate `duplicate_keep` in `config.py` / `validate_config` against an allowlist; reject unknown values.
## Verified strengths
| Claim | Evidence |
|--------|----------|
| `find` invoked without shell string | argv list: `subprocess.Popen(command, ...)` (`scanner.py:171172`, `164169`). |
| Path component sanitization | `sanitize_path_component` strips controls and separators (`utils.py:6575`); planner uses it before templates (`planner.py:301`, `419`). |
| Destination under library in planner | `is_within_root(destination, config.library_root)` (`planner.py:321`, `440`). |
| Quarantine manifest two-phase | Pending manifest write before `rename` (`quarantine.py:221268` region). |
| JSON artifact validation | `_validate_parsed_identities_json`, `_validate_analysis_json`, `validate_plan_json` (`io.py:157251`). |
| Parallel ffprobe | `ThreadPoolExecutor` when `include_video_metadata` and more than one path (`scanner.py:85101`). |
## Priority order
1. F1
2. F2
3. F4
4. F3
5. F8, F5, F6, F7
## Verification log (document vs codebase)
| Statement in this doc | Checked against |
|------------------------|-----------------|
| F1 source not under root guard | `executor.py:321377` |
| F2 by_quality fallback | `duplicate_resolve.py:4043` |
| F3 path_to_index | `planner.py:108111` |
| F4 raise + no try in loop | `quarantine.py:116129`, `executor.py:110112` |
| F5 find returncode | `scanner.py:178192` |
| F6 pyproject optional | `pyproject.toml:1220` |
| F7 validate_plan_json returns dict | `io.py:224251` |
| F8 unknown strategy | `duplicate_resolve.py:3648` |
| Strengths table | `scanner.py`, `utils.py`, `planner.py`, `quarantine.py`, `io.py` as cited |
| 496 tests passed | `uv run pytest -q` on 2026-04-06 |
This document is evidence-based against the paths above; behavior not re-listed here is **not** claimed verified.