Integrate graph-based deletion algorithm into deletion flow

This commit is contained in:
DanConwayDev
2026-01-14 14:18:01 +00:00
parent 8f2f74da04
commit 8c7e840c97
3 changed files with 177 additions and 11 deletions
+8 -8
View File
@@ -551,30 +551,30 @@ impl Nip34WritePolicy {
None
};
// Query for all dependent events
match crate::nostr::policy::query_dependent_events(
// Determine which events should be deleted using graph-based algorithm
match crate::nostr::policy::determine_events_to_delete(
&self.ctx.database,
&event_ids,
&addresses,
)
.await
{
Ok(all_events) => {
Ok(events_to_delete) => {
tracing::info!(
event_id = %event_id_str,
author = %event.pubkey,
event_count = event_ids.len(),
address_count = addresses.len(),
total_events = all_events.len(),
"Processing deletion request - moving {} events to holding database",
all_events.len()
total_events = events_to_delete.len(),
"Processing deletion request - moving {} events to holding database (graph-based)",
events_to_delete.len()
);
// Move events to holding database
match crate::nostr::policy::move_to_holding_database(
&self.ctx.database,
holding_db,
&all_events,
&events_to_delete,
&event.id,
self.ctx.config.archive_retention_secs,
archive_path,
@@ -585,7 +585,7 @@ impl Nip34WritePolicy {
tracing::info!(
event_id = %event_id_str,
moved_count = moved_count,
"Successfully moved {} events to holding database",
"Successfully moved {} events to holding database (graph-based)",
moved_count
);
WritePolicyResult::Accept
+167 -2
View File
@@ -11,6 +11,130 @@ use super::SharedDatabase;
use crate::database::{DeletionMetadata, HoldingDatabase};
use crate::git::archive::{archive_repository, create_archive_metadata};
/// Determine which events should be deleted using graph-based algorithm
///
/// This is the main integration point for the multi-maintainer deletion algorithm.
/// It replaces the simple cascade deletion with a sophisticated graph-based approach:
///
/// 1. Query all potentially affected events (same as before)
/// 2. Build EventGraph to understand dependencies
/// 3. Re-evaluate events to determine which have alternative retention reasons
/// 4. Traverse graph to mark events for deletion
/// 5. Return only events that should actually be deleted
///
/// # Arguments
/// * `database` - Main database to query
/// * `event_ids` - Event IDs being deleted
/// * `addresses` - Addresses being deleted (format: `<kind>:<pubkey>:<d-tag>`)
///
/// # Returns
/// Set of event IDs that should be DELETED (not all dependents, only those without retention reasons)
pub async fn determine_events_to_delete(
database: &SharedDatabase,
event_ids: &[EventId],
addresses: &[String],
) -> Result<HashSet<EventId>, String> {
// Step 1: Query all potentially affected events (same as simple cascade)
let potentially_affected = query_dependent_events(database, event_ids, addresses).await?;
tracing::debug!(
event_ids_count = event_ids.len(),
addresses_count = addresses.len(),
potentially_affected_count = potentially_affected.len(),
"Queried potentially affected events for graph-based deletion"
);
// Step 2: Query all events to build the graph
// We need ALL events in the database to properly build the dependency graph
let all_events = query_all_events(database).await?;
tracing::debug!(
all_events_count = all_events.len(),
"Queried all events for graph building"
);
// Step 3: Build event dependency graph
let graph = super::build_event_graph(&all_events);
tracing::info!(
nodes = graph.node_count(),
edges = graph.edge_count(),
max_depth = graph.max_depth(),
"Built event dependency graph"
);
// Step 4: Re-evaluate events to determine retention reasons
// For each deleted address, run re-evaluation
let mut all_kept_events = std::collections::HashMap::new();
let mut all_deleted_events = HashSet::new();
for address in addresses {
let reeval_result =
super::reevaluate_events_without_announcement(database, address, &potentially_affected)
.await?;
tracing::info!(
address = %address,
kept = reeval_result.keep.len(),
deleted = reeval_result.delete.len(),
"Re-evaluation complete for address"
);
// Merge results
all_kept_events.extend(reeval_result.keep);
all_deleted_events.extend(reeval_result.delete);
}
// Step 5: Traverse graph to mark events for deletion
// Use the first deleted event ID for logging (or generate a dummy one)
let deleted_event_id = event_ids.first().copied().unwrap_or_else(|| {
EventId::from_hex("0000000000000000000000000000000000000000000000000000000000000000")
.unwrap()
});
let traversal_result =
super::traverse_and_mark_deletions(&graph, &all_kept_events, deleted_event_id);
// Log retention reasons
for (event_id, reason) in &all_kept_events {
tracing::debug!(
event_id = %event_id,
event_type = %reason.event_type,
valid_announcements = ?reason.valid_announcements,
"Event KEPT: has alternative retention reason"
);
}
// Log circular dependencies
for circular_dep in &traversal_result.circular_dependencies {
if circular_dep.anchored {
tracing::info!(
events = ?circular_dep.events,
"Detected anchored circular dependency (events will be kept)"
);
} else {
tracing::warn!(
events = ?circular_dep.events,
"Detected unanchored circular dependency (events will be deleted)"
);
}
}
// Log summary
tracing::info!(
total_evaluated = potentially_affected.len(),
kept = traversal_result.keep.len(),
deleted = traversal_result.delete.len(),
circular_deps = traversal_result.circular_dependencies.len(),
"Graph-based deletion complete: {} events kept, {} events deleted",
traversal_result.keep.len(),
traversal_result.delete.len()
);
// Return only events that should be deleted
Ok(traversal_result.delete)
}
/// Query all events that depend on the given event IDs
///
/// This performs a recursive traversal to find:
@@ -18,8 +142,7 @@ use crate::git::archive::{archive_repository, create_archive_metadata};
/// 2. Events with `e` tags referencing the events found in step 1
/// 3. Recursively find events referencing those events
///
/// For Phase 1, this is a simple cascade (no graph complexity).
/// Phase 3 will add multi-maintainer graph algorithm.
/// This is used by the graph-based deletion algorithm to find potentially affected events.
///
/// # Arguments
/// * `database` - Main database to query
@@ -69,6 +192,48 @@ pub async fn query_dependent_events(
Ok(all_dependents)
}
/// Query all events from the database
///
/// This is used by the graph-based deletion algorithm to build the complete
/// dependency graph.
///
/// # Arguments
/// * `database` - Database to query
///
/// # Returns
/// Vector of all events in the database
async fn query_all_events(database: &SharedDatabase) -> Result<Vec<Event>, String> {
// Query all NIP-34 event types
let kinds = vec![
Kind::from(30617), // Repository announcements
Kind::from(1617), // Patches
Kind::from(1621), // Issues
Kind::from(1619), // PR Updates
Kind::from(1630), // Issue status
Kind::from(1631), // PR status
Kind::from(1632), // Patch status
Kind::from(1633), // Repository status
Kind::from(30618), // Repository state
Kind::from(1630), // Pull requests
];
let mut all_events = Vec::new();
for kind in kinds {
let filter = Filter::new().kind(kind);
let events: Vec<Event> = database
.query(filter)
.await
.map_err(|e| format!("Database query failed: {}", e))?
.into_iter()
.collect();
all_events.extend(events);
}
Ok(all_events)
}
/// Query events that have an `a` tag referencing the given address
///
/// # Arguments
+2 -1
View File
@@ -23,7 +23,8 @@ mod traversal;
pub use announcement::{AnnouncementPolicy, AnnouncementResult};
pub use deletion::{DeletionPolicy, DeletionResult};
pub use deletion_ops::{
archive_repositories_for_addresses, move_to_holding_database, query_dependent_events,
archive_repositories_for_addresses, determine_events_to_delete, move_to_holding_database,
query_dependent_events,
};
pub use graph::{build_event_graph, EventGraph, EventNode};
pub use pr_event::PrEventPolicy;