Final review of #313:
- C1: the duplicate find tells files apart by (st_dev, st_ino), so a
source listed twice, a symlinked source inside another and a hardlink
are one file. The preview and the job refuse a removal that
os.path.samestat matches with its kept copy.
- I1: each file's records move to its verified kept copy in one short
transaction before that file is unlinked; a failed move deletes
nothing and a failed unlink leaves the records on the identical kept
copy. The status line counts files removed and not removed.
- I2: links are looked up under every spelling of the source (the real
path, the configured and monitored folders, ComfyUI's output).
- I5: image tags join the kept copy as their union and the removed
copy's pending, undecided Needs review items are settled; the plan
says so.
- I3: the VACUUM disk check measures SQLite's real temporary folder.
- M1: the token is looked at again with no await before the job starts.
- M2: the backup decision is made once and handed to the job.
- M3: backups are linked into place; startup sweeps interrupted temps.
- M4: Optimize runs alone (new "exclusive" admission) and its plan says
the database is locked while VACUUM runs.
- M5: the reviewed duplicate merge is strict: collisions and Library
titles are protected, and errors roll back. The legacy cleanup is
unchanged.
- M7: 2,000 keep entries within a 1 MiB body. M8: event waits in tests.
- M9: the preview binds files by size, mtime and inode without hashing;
the job hashes right before each unlink.