Loading debian/changelog +11 −0 Original line number Diff line number Diff line mediadb (20260419+38) unstable; urgency=critical * Fix import + tombstone conflict: importing stores that were previously deleted caused tombstones to immediately re-delete them on the next sync. Import now clears tombstones for all imported store IDs and replicates the cleared tombstone list to the cluster. Also fixed replicate_tombstones() to push even when the set is empty, so cleared tombstones actually propagate to other nodes. -- Jan Koester <jan.koester@tuxist.de> Sat, 19 Apr 2026 00:00:00 +0200 mediadb (20260419+37) unstable; urgency=critical * Prevent stale index overwrite: replicate_index() now compares local Loading src/backend.cpp +18 −2 Original line number Diff line number Diff line Loading @@ -2903,6 +2903,22 @@ bool ClusterMediaBackend::import_db_from_buffer(const std::uint8_t* data, std::s << " replicated=" << repl_count << " failed=" << repl_fail << "\n"; if (!parse_ok || repl_fail > 0) { importing_.store(false); return false; } // Clear tombstones for any stores that were just imported. // Without this, a previous delete_store tombstone would cause // sync_from_cluster to immediately remove the reimported store. { auto sids = local_.store_ids(); bool tombstone_changed = false; for (const auto& sid : sids) { if (tombstones_.erase(sid) > 0) tombstone_changed = true; } if (tombstone_changed) { replicate_tombstones(); std::cerr << "[CLUSTER-IMPORT] cleared tombstones for imported stores\n"; } } // Verify replication: ensure all keys have enough shards on all nodes. std::cerr << "[CLUSTER-IMPORT] verifying replication\n"; { Loading Loading @@ -3301,8 +3317,8 @@ void ClusterMediaBackend::replicate_store(const std::string& store_id) { void ClusterMediaBackend::replicate_tombstones() { if (!cluster_.isRunning()) return; if (tombstones_.empty()) return; // Serialise: u32 count, then each id as length-prefixed string // Serialise: u32 count, then each id as length-prefixed string. // Always replicate — even when empty — so other nodes see the cleared list. std::vector<std::uint8_t> buf; auto push_u32 = [&](std::uint32_t v) { buf.push_back(static_cast<uint8_t>(v & 0xFF)); Loading Loading
debian/changelog +11 −0 Original line number Diff line number Diff line mediadb (20260419+38) unstable; urgency=critical * Fix import + tombstone conflict: importing stores that were previously deleted caused tombstones to immediately re-delete them on the next sync. Import now clears tombstones for all imported store IDs and replicates the cleared tombstone list to the cluster. Also fixed replicate_tombstones() to push even when the set is empty, so cleared tombstones actually propagate to other nodes. -- Jan Koester <jan.koester@tuxist.de> Sat, 19 Apr 2026 00:00:00 +0200 mediadb (20260419+37) unstable; urgency=critical * Prevent stale index overwrite: replicate_index() now compares local Loading
src/backend.cpp +18 −2 Original line number Diff line number Diff line Loading @@ -2903,6 +2903,22 @@ bool ClusterMediaBackend::import_db_from_buffer(const std::uint8_t* data, std::s << " replicated=" << repl_count << " failed=" << repl_fail << "\n"; if (!parse_ok || repl_fail > 0) { importing_.store(false); return false; } // Clear tombstones for any stores that were just imported. // Without this, a previous delete_store tombstone would cause // sync_from_cluster to immediately remove the reimported store. { auto sids = local_.store_ids(); bool tombstone_changed = false; for (const auto& sid : sids) { if (tombstones_.erase(sid) > 0) tombstone_changed = true; } if (tombstone_changed) { replicate_tombstones(); std::cerr << "[CLUSTER-IMPORT] cleared tombstones for imported stores\n"; } } // Verify replication: ensure all keys have enough shards on all nodes. std::cerr << "[CLUSTER-IMPORT] verifying replication\n"; { Loading Loading @@ -3301,8 +3317,8 @@ void ClusterMediaBackend::replicate_store(const std::string& store_id) { void ClusterMediaBackend::replicate_tombstones() { if (!cluster_.isRunning()) return; if (tombstones_.empty()) return; // Serialise: u32 count, then each id as length-prefixed string // Serialise: u32 count, then each id as length-prefixed string. // Always replicate — even when empty — so other nodes see the cleared list. std::vector<std::uint8_t> buf; auto push_u32 = [&](std::uint32_t v) { buf.push_back(static_cast<uint8_t>(v & 0xFF)); Loading