src/calendaring_sync/conflict.py:
classify(key, local_content, remote_etag, remote_content) -> ChangeClass
resolve(key, change_class, local_content, remote_content, resolver_fn)
ChangeClass enum: REMOTE_ONLY, LOCAL_ONLY, CONFLICT, DELETED_REMOTE, DELETED_LOCAL, UNCHANGED
Four cases:
- Remote changed, local unchanged: server ETag differs from cached, local content matches cache. Safe to apply remote.
- Local changed, remote unchanged: local content differs from cache, server ETag matches cached. Safe to push local.
- Both changed: server ETag differs and local content differs from cache. Conflict. Surface to caller.
- Deleted on one side: one side has the object, the other doesn't. Surface to caller.
resolver_fn receives both versions and returns the content to keep, or raises ConflictError to abort. The library never merges or makes opinionated decisions. That's the caller's job.
Tests: one unit test per ChangeClass case. resolver_fn is called for CONFLICT, not called for REMOTE_ONLY or LOCAL_ONLY. ConflictError propagates cleanly.
src/calendaring_sync/conflict.py:ChangeClassenum:REMOTE_ONLY,LOCAL_ONLY,CONFLICT,DELETED_REMOTE,DELETED_LOCAL,UNCHANGEDFour cases:
resolver_fnreceives both versions and returns the content to keep, or raisesConflictErrorto abort. The library never merges or makes opinionated decisions. That's the caller's job.Tests: one unit test per ChangeClass case.
resolver_fnis called for CONFLICT, not called for REMOTE_ONLY or LOCAL_ONLY. ConflictError propagates cleanly.