is_current() stat'd every source file bare, so a file removed between
iterdir() and stat() aborted the whole extract run; a vanished source
now just doesn't affect currency.
The lazy Zotero snapshot stamped the snapshot with the source's mtime
even when shutil.copy2 had failed, which would have passed a stale
snapshot off as current on every later run. Copying moved into
_copy_snapshot() so both callers know whether it worked.
Docs: upsert_status spells out the merge rules (extra_json exempt from
empty-keeps-stored, tags/collections union-merged) and
`backfill-comments --force` says it bypasses the docket seal only.