Guard against self-watermarks and apply backfill

Skip self-watermarks when peer is the local device; log a warning. Apply
current_state in dependency order by computing registry order. Fall back
to unordered if the computation fails. On automatic backfill failure,
reset device state to Uninitialized. This triggers a retry and clears
backfill_attempted.
This commit is contained in:
Jamie Pine 2025-11-28 08:20:28 -08:00
parent 0e5e9827d1
commit e4c60c58ca
3 changed files with 33 additions and 1 deletions

View File

@ -115,6 +115,17 @@ impl ResourceWatermarkStore {
resource_type: &str, resource_type: &str,
watermark: DateTime<Utc>, watermark: DateTime<Utc>,
) -> Result<(), WatermarkError> { ) -> Result<(), WatermarkError> {
// Prevent self-watermarks - device should never track watermarks for its own data
if peer_device_uuid == self.device_uuid {
tracing::warn!(
device_uuid = %self.device_uuid,
peer_device_uuid = %peer_device_uuid,
resource_type = %resource_type,
"Attempted to create self-watermark (device tracking itself) - skipping"
);
return Ok(());
}
// Check if newer before updating // Check if newer before updating
let existing = self.get(conn, peer_device_uuid, resource_type).await?; let existing = self.get(conn, peer_device_uuid, resource_type).await?;

View File

@ -781,7 +781,25 @@ impl BackfillManager {
// Apply current_state snapshot (contains pre-sync data not in peer_log) // Apply current_state snapshot (contains pre-sync data not in peer_log)
if let Some(state) = current_state { if let Some(state) = current_state {
if let Some(state_map) = state.as_object() { if let Some(state_map) = state.as_object() {
for (model_type, records_value) in state_map { // Get dependency-ordered list of models to prevent FK violations
// CRITICAL: Must apply parent models before children (e.g., user_metadata before user_metadata_tag)
let sync_order = match crate::infra::sync::registry::compute_registry_sync_order().await {
Ok(order) => order,
Err(e) => {
warn!("Failed to compute sync order, using unordered: {}", e);
// Fallback to unordered if dependency graph fails
state_map.keys().map(|k| k.clone()).collect::<Vec<_>>()
}
};
// Apply snapshot records in dependency order
for model_type in sync_order {
// Skip if model not in snapshot
let records_value = match state_map.get(&model_type) {
Some(val) => val,
None => continue,
};
if let Some(records_array) = records_value.as_array() { if let Some(records_array) = records_value.as_array() {
info!( info!(
model_type = %model_type, model_type = %model_type,

View File

@ -330,6 +330,9 @@ impl SyncService {
} }
Err(e) => { Err(e) => {
warn!("Automatic backfill failed: {}", e); warn!("Automatic backfill failed: {}", e);
// Reset state to Uninitialized so retry logic runs
let mut state = peer_sync.state.write().await;
*state = DeviceSyncState::Uninitialized;
// Reset flag to retry on next loop // Reset flag to retry on next loop
backfill_attempted = false; backfill_attempted = false;
} }