Sync working tree before initial Gitea push

Includes:
- cli.py path fix (parents[1]) for config/catalog resolution
- Library cleanup feature design docs (TODO.md, mockup)
- Audit + bug-queue markdowns from May 2026 reliability pass
- .gitignore expanded for transient artifacts
This commit is contained in:
admin
2026-05-26 22:35:42 +02:00
parent 8d6bdb81af
commit f7fc15b17c
24 changed files with 2938 additions and 41 deletions
+67
View File
@@ -0,0 +1,67 @@
# Bug Report — Python CLI — audit-snapshot-2026-05-24T15-55Z.md
Snapshot: audit-snapshot-2026-05-24T15-55Z.md
Required-reading docs read: AGENTS.md / TODO.md / CACHE_CONTRACT.md (at D:\DEV\Extensions\Production\rclone-jav\docs\CACHE_CONTRACT.md) / bug-audit-plan.md / project memory
Auditor agent: fresh Explore agent (chunk 1 auditor)
Verifier agents: fresh Explore agents per candidate, blind context, stricter contract-check prompt
This file contains CONFIRMED + PARTIAL findings only. Candidate scratch lives in `bugs-candidates-python.md`. REFUTED / NEEDS-INFO candidates stay in scratch with verifier response appended.
**Chunk 1 calibration note:** Moderate verification yielded 1 confirmed bug with 75% rejection rate (3/4 REFUTED). Auditor's recurring weakness: flagging `f["key"]` direct access as KeyError risk without checking the contract that guarantees the dict shape upstream (rclone lsjson schema, cache.json schema enforced by load_cache validation + CACHE_CONTRACT.md). Stricter verifier prompt with required contract-check caught all 3 false positives. **Light candidates were NOT verified per audit-plan stop condition** (>30% rejection → halt L verification). The Python auditor likely shares a similar pattern-matching weakness on L candidates — revisit only if needed. See `bugs-candidates-python.md` for unverified L list (C-5, C-6, C-7, C-8, C-9).
---
## Severe (S)
(none flagged by auditor in this chunk)
---
## Moderate (M)
### M-1 — save_config lacks Windows file-locking retry that save_cache has
- **File:** `D:\DEV\Project\rclone-jav\rcjav\cli.py:186-189` (save_config), with comparison at `rcjav/cache.py:142-147` (save_cache)
- **Symptom (one sentence):** When a user runs `--save` while config.json is briefly locked by antivirus, Windows Search indexer, or any reader, `os.replace(tmp, CONFIG_PATH)` raises uncaught PermissionError and the user sees a Python traceback — config write fails. `save_cache` for the same os.replace pattern has explicit PermissionError + 0.5s retry; `save_config` does not.
- **Why it's a bug:** Asymmetric protection. `save_cache` (cache.py:142-147):
```python
try: os.replace(tmp, CACHE_PATH)
except PermissionError: time.sleep(0.5); os.replace(tmp, CACHE_PATH)
```
`save_config` (cli.py:186-189):
```python
tmp.write_text(json.dumps(cfg, indent=2), encoding="utf-8")
os.replace(tmp, CONFIG_PATH)
```
Single call site at cli.py:465 inside `--save` flag handler, NOT wrapped in try/except. Outer exception handler at cli.py:1000-1004 catches only KeyboardInterrupt. PermissionError propagates uncaught → Python traceback to user. On Windows with active AV (Defender, Avast, etc.), file-lock-during-replace is common.
- **Reproduction:**
1. Input: user runs `python rc-jav.py --save --target cq:JAV` while config.json is being read by another process (AV scan, Windows Search indexer reindexing, manual file open in editor)
2. Expected: write retries briefly + succeeds, OR clear "config write failed, retry" message
3. Actual: PermissionError raised from os.replace, uncaught, Python prints traceback `PermissionError: [WinError 32] The process cannot access the file because it is being used by another process`. tmp file may remain on disk. Config not persisted. User confused.
- **Suggested fix sketch:** copy save_cache's pattern verbatim — wrap os.replace in try/except PermissionError with 0.5s sleep + single retry
- **Verifier agent:** fresh Explore, blind context, stricter prompt
- **Verifier verdict:** CONFIRMED
- **Verifier confidence:** high (95%)
- **Contract refs verifier read:** save_cache implementation as comparison; outer exception handler scope
- **Mirror check needed in:** any other `os.replace` callsite in `rcjav/` package that writes user-visible config/state (search for `os.replace` in rcjav/ — only save_cache and save_config currently)
- **Status:** fixed
- **Fix:** `D:\DEV\Project\rclone-jav\rcjav\cli.py:186-194` — wrapped `os.replace(tmp, CONFIG_PATH)` in same try/except PermissionError + time.sleep(0.5) + retry pattern that save_cache uses (rcjav/cache.py:142-147). Now symmetric: both writers handle transient Windows file locks identically. Single retry (not infinite) — persistent locks still bubble PermissionError to caller, matching save_cache behavior. `time` already imported in cli.py:14 — no new import needed. **No manifest bump** — CLI repo only, no extension files touched. Python syntax verified via `py_compile`. Smoke-tested in isolation: (1) normal write produces correct file; (2) first os.replace raises PermissionError then succeeds on retry — final state correct, 0.5s sleep observed (2 calls, elapsed 0.50s); (3) persistent PermissionError on both attempts → bubbles up to caller (2 attempts, matches save_cache). Mirror check resolved: only save_cache and save_config use os.replace in rcjav/; both now have retry.
---
## Light (L)
(none promoted — chunk 1 L verification skipped per stop condition)
---
## Needs Input (N)
(none promoted)
---
## False Positives (discarded)
- `rcjav/rclone_io.py:66` — flagged as Moderate "rclone KeyError on Path". REFUTED. rclone lsjson output contract guarantees `Path` field on every item per official docs. Direct `item["Path"]` access is appropriate fail-fast for contract violation. Lines 77-78's `.get()` pattern for Size/ModTime is defensive over-engineering for those fields, NOT evidence Path needs the same.
- `rcjav/library.py:257` — flagged as Moderate "library cache KeyError on path". REFUTED via 3 converging facts: (1) CACHE_CONTRACT.md mandates `path` key on every file entry, (2) `load_cache()` (cache.py:67-106) validates schema before find_library_issues runs — non-conformant caches get wiped via `_fresh_cache()`, (3) FileEntry dataclass + every cache write site explicitly emits `path`. The `.get()` pattern at cli.py:526 (`--reextract`) is defensive because that path reads cache.json directly without re-validation; library.py operates on already-validated data.
- `rcjav/library.py:328-330` — flagged as Moderate "rename_file KeyError on path/jav_id". REFUTED. `f` comes only from cache entries (`remote_data.get("files", [])`), which are contract-guaranteed to have `path`. Caller scalar args (`old_rel_path`, `new_rel_path`) are strings, not dicts. Line 330's `or f["jav_id"]` fallback is for `extract_id` returning None, NOT for missing key — correct design. Auditor conflated scalar caller args with iterated dict entries.