refactor(nostr): move deletion orchestration into deletion subsystem

This commit is contained in:
DanConwayDev
2026-06-19 13:58:23 +00:00
parent 829d92f873
commit 3d346be5f5
16 changed files with 74 additions and 43 deletions
+1 -1
View File
@@ -21,8 +21,8 @@ use std::time::Duration;
use nostr_sdk::prelude::*;
use tracing::{debug, error, info, warn};
use crate::nostr::builder::SharedDatabase;
use crate::nostr::events::RepositoryAnnouncement;
use crate::nostr::SharedDatabase;
/// How old an audit event must be before it is eligible for deletion (2 hours).
const AUDIT_CLEANUP_AGE_SECS: u64 = 2 * 3600;
+1 -1
View File
@@ -35,8 +35,8 @@ use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use tracing::{debug, info, warn};
use crate::nostr::builder::SharedDatabase;
use crate::nostr::events::{RepositoryAnnouncement, RepositoryState};
use crate::nostr::SharedDatabase;
use crate::purgatory::Purgatory;
use nostr_sdk::prelude::{Kind, PublicKey};
+3 -1
View File
@@ -17,8 +17,9 @@ use super::subprocess::GitSubprocess;
use crate::git::authorization::{authorize_push, parse_pushed_refs};
use crate::git::sync::process_newly_available_git_data;
use crate::nostr::builder::{Nip34WritePolicy, SharedDatabase};
use crate::nostr::builder::Nip34WritePolicy;
use crate::nostr::holding::LifecycleReadGuard;
use crate::nostr::SharedDatabase;
use crate::purgatory::Purgatory;
use crate::sync::rejected_index::RejectedEventsIndex;
@@ -408,6 +409,7 @@ pub async fn handle_receive_pack(
&purgatory,
git_data_path_buf,
Some(&write_policy),
Some(write_policy.deletion()),
Some(&rejected_events_index),
)
.await
+16 -12
View File
@@ -42,8 +42,10 @@ use crate::git::authorization::{
fetch_repository_data_with_purgatory, RepositoryData,
};
use crate::git::{self, oid_exists};
use crate::nostr::builder::{Nip34WritePolicy, SharedDatabase};
use crate::nostr::builder::Nip34WritePolicy;
use crate::nostr::deletion::DeletionService;
use crate::nostr::events::RepositoryState;
use crate::nostr::SharedDatabase;
use crate::purgatory::{can_apply_state, Purgatory};
use crate::sync::rejected_index::RejectedEventsIndex;
@@ -835,6 +837,7 @@ pub async fn process_newly_available_git_data(
purgatory: &Purgatory,
git_data_path: &Path,
write_policy: Option<&Nip34WritePolicy>,
deletion_service: Option<&DeletionService>,
rejected_events_index: Option<&Arc<RejectedEventsIndex>>,
) -> anyhow::Result<ProcessResult> {
let mut result = ProcessResult::default();
@@ -866,6 +869,7 @@ pub async fn process_newly_available_git_data(
purgatory,
git_data_path,
write_policy,
deletion_service,
rejected_events_index,
)
.await;
@@ -879,7 +883,7 @@ pub async fn process_newly_available_git_data(
local_relay,
purgatory,
git_data_path,
write_policy,
deletion_service,
)
.await;
result.merge(state_result);
@@ -946,7 +950,7 @@ async fn process_purgatory_state_events(
local_relay: Option<&nostr_relay_builder::LocalRelay>,
purgatory: &Purgatory,
git_data_path: &Path,
write_policy: Option<&Nip34WritePolicy>,
deletion_service: Option<&DeletionService>,
) -> ProcessResult {
let mut result = ProcessResult::default();
@@ -1136,11 +1140,11 @@ async fn process_purgatory_state_events(
continue;
}
let owner = &announcement.event.pubkey;
if let (Some(wp), Some(entry)) =
(write_policy, purgatory.find_announcement(owner, identifier))
{
wp.deletion()
.maybe_recover_deleted_repository(&entry.event, identifier)
if let (Some(ds), Some(entry)) = (
deletion_service,
purgatory.find_announcement(owner, identifier),
) {
ds.maybe_recover_deleted_repository(&entry.event, identifier)
.await;
}
@@ -1414,6 +1418,7 @@ async fn process_purgatory_announcements(
purgatory: &Purgatory,
git_data_path: &Path,
write_policy: Option<&Nip34WritePolicy>,
deletion_service: Option<&DeletionService>,
rejected_events_index: Option<&Arc<RejectedEventsIndex>>,
) -> ProcessResult {
let mut result = ProcessResult::default();
@@ -1447,12 +1452,11 @@ async fn process_purgatory_announcements(
}
};
if let (Some(wp), Some(entry)) = (
write_policy,
if let (Some(ds), Some(entry)) = (
deletion_service,
purgatory.find_announcement(&owner, identifier),
) {
wp.deletion()
.maybe_recover_deleted_repository(&entry.event, identifier)
ds.maybe_recover_deleted_repository(&entry.event, identifier)
.await;
}
+3 -1
View File
@@ -71,7 +71,8 @@ use crate::git::sync::process_newly_available_git_data;
use crate::git::{delete_ref, list_refs};
use crate::grasp06::endpoint::PrsUrl;
use crate::grasp06::paths::prs_repo_path;
use crate::nostr::builder::{Nip34WritePolicy, SharedDatabase};
use crate::nostr::builder::Nip34WritePolicy;
use crate::nostr::SharedDatabase;
use crate::purgatory::Purgatory;
use crate::sync::rejected_index::RejectedEventsIndex;
@@ -356,6 +357,7 @@ pub async fn handle_prs_receive_pack(
&purgatory,
Path::new(git_data_path),
Some(&write_policy),
Some(write_policy.deletion()),
Some(&rejected_events_index),
)
.await
+2 -1
View File
@@ -27,7 +27,8 @@ use crate::config::Config;
use crate::git;
use crate::grasp06::receive::RepoInitLocks;
use crate::metrics::Metrics;
use crate::nostr::builder::{Nip34WritePolicy, SharedDatabase};
use crate::nostr::builder::Nip34WritePolicy;
use crate::nostr::SharedDatabase;
use crate::purgatory::Purgatory;
use crate::sync::rejected_index::RejectedEventsIndex;
+8 -10
View File
@@ -2,8 +2,8 @@
///
/// Wires nostr-relay-builder with NIP-34 admission/routing policy.
/// Deletion behaviour is owned by the `deletion` subsystem
/// (`crate::nostr::deletion`); this module holds only thin delegation
/// accessors and the top-level `admit_event` dispatch.
/// (`crate::nostr::deletion`); this module keeps top-level `admit_event`
/// dispatch and non-deletion admission/router policy logic.
use std::net::SocketAddr;
use std::num::NonZeroUsize;
use std::path::Path;
@@ -24,9 +24,7 @@ use crate::nostr::policy::{
AnnouncementPolicy, AnnouncementResult, PolicyContext, PrEventPolicy, ReferenceResult,
RelatedEventPolicy, StatePolicy, StateResult,
};
/// Type alias for the shared database used by the relay
pub type SharedDatabase = Arc<dyn NostrDatabase>;
use crate::nostr::SharedDatabase;
/// NIP-34 Write Policy — admission and routing for GRASP-01 events
///
@@ -530,7 +528,7 @@ impl Nip34WritePolicy {
// Process state alignment asynchronously
match self
.state_policy
.process_state_event(event, is_synced, Some(self))
.process_state_event(event, is_synced, Some(self.deletion()))
.await
{
Ok(policy_result) => {
@@ -747,7 +745,7 @@ impl Nip34WritePolicy {
// Re-evaluate authorization with the new announcement
match self
.state_policy
.process_state_event(&entry.event, false, Some(self))
.process_state_event(&entry.event, false, Some(self.deletion()))
.await
{
Ok(WritePolicyResult::Accept) => {
@@ -1044,7 +1042,7 @@ pub async fn create_relay(
//
// NIP-09 (deletion) and NIP-62 (request to vanish) auto-processing is
// explicitly disabled on the main database: ngit-grasp owns this handling
// (see `DeletionPolicy`, the kind-62 handler, and the `Tombstones` store).
// (see the deletion subsystem facade + `Tombstones` store).
// Leaving the backend defaults (`true`) would double-process: the backend
// would silently hard-delete events and block re-submission via its own
// internal tables that we cannot inspect, conflicting with our purgatory /
@@ -1058,8 +1056,8 @@ pub async fn create_relay(
DatabaseBackend::Memory => {
tracing::info!("Using in-memory database (no persistence)");
// Disable the backend's built-in NIP-09 / NIP-62 auto-processing to
// match the LMDB path: ngit-grasp owns this handling (DeletionPolicy,
// the kind-62 handler, and the Tombstones store). Leaving the memory
// match the LMDB path: ngit-grasp owns this handling (deletion
// subsystem + Tombstones store). Leaving the memory
// backend's defaults (`true`) would silently suppress targeted events
// at query time the moment a kind-5 is *stored* — which in particular
// defeats `deletion_request_disrespector` (archival) mode, where the
+1 -1
View File
@@ -1,8 +1,8 @@
use crate::nostr::builder::SharedDatabase;
use crate::nostr::history::ReplaceableHistoryStore;
use crate::nostr::holding::HoldingStore;
use crate::nostr::policy::PolicyContext;
use crate::nostr::tombstones::Tombstones;
use crate::nostr::SharedDatabase;
use crate::{config::Config, grasp06::receive::RepoInitLocks, purgatory::Purgatory};
use std::path::PathBuf;
use std::sync::Arc;
+1 -1
View File
@@ -830,7 +830,7 @@ impl DeletionPolicy {
/// [`Self::archive_and_delete_filter`]: every deleted orphan/coordinate
/// target is moved into holding before main-DB deletion, and announcement
/// deletions include git archive metadata linkage for cleanup lifecycle.
/// Recovery orchestration remains a future phase.
/// Recovery orchestration lives in `crate::nostr::deletion::recovery`.
async fn cascade_delete_announcement(
&self,
author: &PublicKey,
+2 -2
View File
@@ -6,5 +6,5 @@ pub mod holding;
pub mod policy;
pub mod tombstones;
/// Re-export SharedDatabase for use by policy modules
pub use builder::SharedDatabase;
/// Shared database type used across nostr modules.
pub type SharedDatabase = std::sync::Arc<dyn nostr_relay_builder::prelude::NostrDatabase>;
+4 -3
View File
@@ -12,7 +12,7 @@ use nostr_relay_builder::prelude::Event;
use super::{accepted_purgatory, duplicate, reject_invalid, reject_restricted, PolicyContext};
use crate::git;
use crate::git::authorization::fetch_repository_data_with_purgatory;
use crate::nostr::builder::Nip34WritePolicy;
use crate::nostr::deletion::DeletionService;
use crate::nostr::events::{validate_state, RepositoryAnnouncement, RepositoryState};
/// Result of state policy evaluation
@@ -54,7 +54,7 @@ impl StatePolicy {
&self,
event: &Event,
is_synced: bool,
recovery_policy: Option<&Nip34WritePolicy>,
deletion_service: Option<&DeletionService>,
) -> Result<WritePolicyResult> {
// Parse state to get HEAD and branch info
let state =
@@ -237,7 +237,8 @@ impl StatePolicy {
local_relay.as_ref(),
&self.ctx.purgatory,
&self.ctx.git_data_path,
recovery_policy,
None,
deletion_service,
None,
)
.await
+2 -1
View File
@@ -193,8 +193,8 @@ use std::sync::Arc;
use tracing::debug;
use crate::nostr::builder::Nip34WritePolicy;
use crate::nostr::builder::SharedDatabase;
use crate::nostr::events::RepositoryState;
use crate::nostr::SharedDatabase;
use crate::purgatory::Purgatory;
use crate::sync::naughty_list::NaughtyListTracker;
@@ -526,6 +526,7 @@ impl SyncContext for RealSyncContext {
&self.purgatory,
&self.git_data_path,
self.write_policy.as_ref(),
self.write_policy.as_ref().map(|wp| wp.deletion()),
None,
)
.await?;
+2 -1
View File
@@ -51,7 +51,8 @@ use nostr_sdk::prelude::*;
use tokio::sync::{broadcast, Mutex, RwLock};
use crate::config::Config;
use crate::nostr::builder::{Nip34WritePolicy, SharedDatabase};
use crate::nostr::builder::Nip34WritePolicy;
use crate::nostr::SharedDatabase;
use nostr_relay_builder::prelude::LocalRelay;
// =============================================================================
+1 -1
View File
@@ -19,7 +19,7 @@ use futures_util::StreamExt;
use nostr_sdk::prelude::*;
use tokio::sync::mpsc;
use crate::nostr::builder::SharedDatabase;
use crate::nostr::SharedDatabase;
/// Events from a relay connection
#[derive(Debug)]
+1 -1
View File
@@ -16,7 +16,7 @@ use nostr_sdk::prelude::Timestamp;
use nostr_sdk::prelude::*;
use tokio::sync::{broadcast, mpsc};
use crate::nostr::builder::SharedDatabase;
use crate::nostr::SharedDatabase;
use super::{AddFilters, RepoSyncIndex, RepoSyncNeeds, SyncLevel};
+26 -5
View File
@@ -8,12 +8,13 @@ use clap::Parser;
use ngit_grasp::config::Config;
use ngit_grasp::grasp06::receive::new_repo_init_locks;
use ngit_grasp::metrics::{self, REGISTRY};
use ngit_grasp::nostr::builder::{Nip34WritePolicy, SharedDatabase};
use ngit_grasp::nostr::builder::Nip34WritePolicy;
use ngit_grasp::nostr::history::ReplaceableHistoryStore;
use ngit_grasp::nostr::holding::{
DeletionSource, GitArchiveMetadata, HoldingMetadata, HoldingStore, HOLDING_ARCHIVE_PATH_TAG,
};
use ngit_grasp::nostr::tombstones::Tombstones;
use ngit_grasp::nostr::SharedDatabase;
use ngit_grasp::purgatory::Purgatory;
use nostr_sdk::prelude::*;
use prometheus::{Encoder, TextEncoder};
@@ -480,7 +481,12 @@ async fn startup_whitelist_restore_recovers_now_whitelisted_scope_and_is_idempot
"ngit_whitelist_startup_restore_total",
&[("result", "attempted"), ("reason", "none")],
);
assert_eq!(attempted_after, attempted_before + 1.0);
assert!(
attempted_after >= attempted_before + 1.0,
"expected attempted restore metric to increase by at least 1 (before={}, after={})",
attempted_before,
attempted_after
);
let success_before = metric_value(
&before,
@@ -492,7 +498,12 @@ async fn startup_whitelist_restore_recovers_now_whitelisted_scope_and_is_idempot
"ngit_whitelist_startup_restore_total",
&[("result", "succeeded"), ("reason", "none")],
);
assert_eq!(success_after, success_before + 1.0);
assert!(
success_after >= success_before + 1.0,
"expected successful restore metric to increase by at least 1 (before={}, after={})",
success_before,
success_after
);
let second_stats = restore_policy
.deletion()
@@ -702,7 +713,12 @@ async fn startup_blacklist_restore_recovers_unblacklisted_scope_and_is_idempoten
"ngit_blacklist_startup_restore_total",
&[("result", "attempted"), ("reason", "none")],
);
assert_eq!(attempted_after, attempted_before + 1.0);
assert!(
attempted_after >= attempted_before + 1.0,
"expected attempted restore metric to increase by at least 1 (before={}, after={})",
attempted_before,
attempted_after
);
let success_before = metric_value(
&before,
@@ -714,7 +730,12 @@ async fn startup_blacklist_restore_recovers_unblacklisted_scope_and_is_idempoten
"ngit_blacklist_startup_restore_total",
&[("result", "succeeded"), ("reason", "none")],
);
assert_eq!(success_after, success_before + 1.0);
assert!(
success_after >= success_before + 1.0,
"expected successful restore metric to increase by at least 1 (before={}, after={})",
success_before,
success_after
);
let second_stats = restore_policy
.deletion()