Added
sync_report.json, logs, notifications, and Prometheus metrics now includefull_enumeration_reasonwhen kei has to run a full enumeration, so operators can tell whether a full scan came from missing or invalid sync tokens, retry or pending rows, metadata backfill, config drift, explicit retry-failed mode, or a requested full sync. (#511)--recent-scopeand[filters].recent_scopelet count-form recent syncs choose either one library-wide recent frontier (global, the default) or a separate recent window for each selected album, smart folder, or unfiled pass (per-filter). (#519)- Album-filtered incremental syncs can now use trusted album membership snapshots and targeted backfill instead of falling back to full enumeration for every selected-album run. (#520, #523, #525, #526, #531)
- Deferred unfiled full-sync passes now emit periodic progress logs with the library, pass type, enumerated asset count, expected count, and elapsed time. (#540)
Changed
- Count-form
recentlimits now default to a single library-wide recent window before album, smart-folder, unfiled, media, filename, and date filters narrow that set. Setrecent_scope = "per-filter"to keep the old per-filter window shape. (#519) - Incremental sync can now split selected smart-folder refreshes from album and unfiled work, letting unfiled changes use
/changes/zonewhen the smart-folder pass needs its own refresh. (#528) - Path-affecting download config drift now forces a full reconciliation and clears stored zone tokens so known assets are planned into the new directory or filename template. (#533)
Fixed
- Watch mode now stops before the next sync cycle if it cannot reacquire the iCloud session lock after idle sleep, instead of continuing toward another sync without the lock. (#544)
- Resumed downloads now validate
Content-Rangebefore appending to an existing.partfile, and XMP Toolkit metadata rewrites now use the same durable write path as downloaded media. (#543) - Full-enumeration sync tokens are now blocked when iCloud count results exceed the records kei actually enumerated, when full-query asset/master records cannot be paired safely, or when an empty-page termination is ambiguous. (#541)
- Incremental zone tokens are now blocked when source delete or hidden-state writes fail, including zero-row updates that would otherwise look successful. (#542)
- Deferred unfiled streams are reopened after album passes finish, keeping signed iCloud URLs fresher and avoiding zero-download expired-URL outcomes. The same fix stops per-pass album templates from causing repeated "verifying all files" runs. (#539)
- Watch-mode
/changes/databaseprechecks no longer advancedb_sync_tokenwhen Apple returns an empty complete page. kei still skips that wakeup, but the next wakeup rechecks from the previous token instead of treating the empty response as a confirmed checkpoint. (#503) - JSON CDN error responses identified by content type are rejected before
.partwrites, matching the existing HTML error-page rejection. Valid or unknown media bytes still save when size checks pass. (#512) - Password redaction now catches secrets split across adjacent log writes, so passwords passed by CLI or env are not exposed when a writer chunks the log line. (#516)
- HEIF XMP extraction now converts unsupported
infev1 metadata entries into normal metadata rewrite failures instead of panicking on malformed or unusual HEIC files. (#516) - Password-file permission warnings now fire after a file first becomes readable or permissive; an earlier missing or secure file state no longer suppresses a later warning. (#516)
- Recent and lower-date-bounded full syncs now keep their enumeration bounded without advancing normal full-enumeration zone tokens.
skip_created_afterstill drains past the newer prefix so older matching assets are not missed. (#519) - Full-enumeration and count-form recent token diagnostics now report more precise blocked-token and fallback reasons, including incomplete album relation hydration. (#518, #510)
- Password commands that emit invalid UTF-8 are rejected cleanly, CloudKit
/changes/databaseresponses with missingzonesdefault to an empty zone list, and downloaded rows now persist the verified download checksum. (#535) - The live import rehearsal now seeds its test album with
--recent-scope per-filter, so the selected album contributes files even when the account-wide newest assets are elsewhere. (#534)
Full changelog: CHANGELOG.md