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
+64
View File
@@ -0,0 +1,64 @@
# Phase 3 Re-Audit Candidates — audit-2026-05-25T21-35Z (post-fix state)
Auditor: fresh Explore, blind context
Scope: 6 files modified during Phase 2 fixes
Looking for: bugs INTRODUCED by Phase 2 fixes (not pre-existing — those are in bugs-*.md)
## Findings: One introduced bug detected
### C-1: M-3 spawn_event race allows cancel to see _scan_proc = None
- **File**: D:\DEV\Extensions\Production\rclone-jav\host\rcjav-host.py:21832190, 23762387, 24082410
- **Symptom**: If user calls cancel-scan within ~15 ms after a scan starts, _scan_proc may still be None when handle_scan_cancel reads it under the lock, causing the cancel to return "no scan running" and skip the cancel-flag file write. The scan continues uninterrupted.
- **Trace**:
1. _scan_worker spawns Popen at line 2176, enters try block
2. Sets `spawn_result["spawn_ok"] = True` (line 2186)
3. Sets `spawn_event.set()` (line 2188) — this wakes handle_scan which is waiting
4. handle_scan timeout fires (line 2376), reads `spawn_result.get("spawn_ok")` → True
5. handle_scan returns `{"ok": True, "scanning": True, "started": True}` (line 2378)
6. Meanwhile, worker thread hasn't yet executed line 2190: `_scan_proc = proc`
7. Extension receives ok:true and immediately sends cancel-scan RPC
8. handle_scan_cancel reads `_scan_proc` under lock (line 2410) and gets None
9. Line 2411 condition is true: `if not running: return ...` and never writes cancel flag
10. Scan continues because rc-jav.py never sees the cancel flag
- **Root cause**: spawn_event is signaled (line 2188) and handle_scan returns before _scan_proc is assigned (line 2190). The critical assignment is inside `with _scan_lock:` which prevents a true race on the read, but the signal happens outside the lock. A cancel arriving in that window sees stale None.
- **Repro**: Stress-test with rapid scan-start / cancel-scan pairs; observe: handle_scan returns ok:true, cancel-scan returns "no scan running" instead of cancelling, scan directory walk completes uninterrupted.
---
## Clear findings on other fixes (no issues):
### M-2 ensureContextMenu lock
✓ Correct. Lock is `_contextMenuLock = Promise.resolve()` at module scope (line 798). Each call chains via `.then()` (line 800). No nesting with other locks; isolated invariant (removeAll + create is atomic in chain). No stale closures — `async () => { ... }` captures its own scope. Top-level call (line 1235) + onInstalled/onStartup listeners are idempotent (removeAll first). **No bugs.**
### M-6 recordRpc lock
✓ Correct. Lock is `_rpcLogLock = Promise.resolve()` at line 169. Wraps get-then-set of NATIVE_LOG_KEY (lines 171179). No rejection escape (catch block swallows, never re-throws). maybeNotifyHostError is called OUTSIDE the lock (line 184) as documented. No deadlock (independent from _hostAlertLock). **No bugs.**
### L-1 maybeNotifyHostError lock
✓ Correct. Lock is `_hostAlertLock = Promise.resolve()` at line 201. Wraps rate-limit read/check/write + notification + Discord post (lines 209240). Separate from _rpcLogLock (different storage key). Called outside _rpcLogLock by recordRpc, so no nesting. On burst, only first caller's check passes; rest read fresh ts and bail (lines 214). No rejection escape. **No bugs.**
### S-1 export handler
✓ Correct. Lines 403440: if get-keep-ranking RPC fails, blocks export and shows error message. Checks both `!r.ok` and missing `keep_ranking` payload. Success path writes to payload._meta.host_config.keep_ranking (line 426). File uses `app: "rclone-jav"` not `rclonex` (line 423). **No bugs.**
### M-1 sanitizeImportedSettings validators
✓ Correct. Profiles validator (lines 592596): accepts `{ name: string, source?: string[], target?: string[] }`. Uses `e.source || []` and `e.target || []` to handle missing fields. Consumer profileOverrides (background.js:407408) safely does `prof.source || []` again. Validator passes profiles with missing source/target; profileOverrides then treats them as empty arrays. This is safe — the consumer never assumes source/target exist as properties. **No bugs.**
### M-5 popup _currentSearchId counter
✓ Correct. Module-level counter (line 294). runCheck bumps at entry (line 300), captures myId, compares in callback (line 307). runManualSearch bumps at entry (line 461), captures myId, compares in callback (line 475). Popup is recreated on each open; each session is isolated. Bumping before paused early-exit ensures older callbacks bail. **No bugs.**
### M-3 spawn_event signal timing
**BUG FOUND** (see C-1 above).
### M-4 Discord post-alert threaded fire-and-forget
✓ Correct. post_discord_alert (lines 242268): checks rate limit via _alert_rate_limited() before spawning thread (line 257). Rate-limit file write (line 149) happens before thread spawn. On burst, only first post passes rate limit; rest return early without thread spawn. _discord_post_worker receives alert_source label (lines 262263). All 4 main-loop callsites pass alert_source (lines 2682, 2701, 2739, 2810). **No bugs.**
### M-7 save_config retry
✓ Correct. Lines 186196: Popen creates tmp file, writes JSON, tries replace. PermissionError triggers sleep(0.5) + one retry. On second PermissionError, re-raises (no infinite loop). Mirrors save_cache design. **No bugs.**
### Manifest version field
✓ Valid. Version is `"0.1.43"` (line 4). Valid semver. No trailing commas. Valid JSON confirmed. **No bugs.**
---
## Summary
**One introduced bug detected in M-3** spawn_event race condition. The remaining five fixes (M-2, M-6, L-1, S-1, M-1, M-5, M-4, M-7) and manifest version are correct and safe.