mirror of
https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp.git
synced 2026-10-05 23:18:24 +00:00
feat(recovery): run trigger-a recovery on maintainer accept route
This commit is contained in:
@@ -398,6 +398,12 @@ impl Nip34WritePolicy {
|
||||
// Parse announcement to get details for logging
|
||||
match RepositoryAnnouncement::from_event(event.clone()) {
|
||||
Ok(announcement) => {
|
||||
// Phase 5 recovery trigger: any accepted announcement route
|
||||
// (including maintainer acceptance) can restore eligible
|
||||
// holding/archive state for this owner+identifier.
|
||||
self.maybe_recover_deleted_repository(event, &announcement.identifier)
|
||||
.await;
|
||||
|
||||
tracing::info!(
|
||||
"Accepted maintainer announcement {} (author {} is listed as maintainer for {})",
|
||||
event_id_str,
|
||||
|
||||
@@ -137,6 +137,82 @@ async fn publish_served_repo_for_identifier(
|
||||
commit_hash
|
||||
}
|
||||
|
||||
async fn publish_served_repo_for_identifier_with_maintainers(
|
||||
client: &AuditClient,
|
||||
relay: &TestRelay,
|
||||
repo_id: &str,
|
||||
maintainer_pubkeys: &[String],
|
||||
) -> String {
|
||||
let temp_dir = tempfile::tempdir().expect("create temp repo for served repo publish");
|
||||
let commit_hash = create_test_repo_with_commit(temp_dir.path(), CommitVariant::StateTest)
|
||||
.expect("create deterministic commit for served repo");
|
||||
|
||||
let relay_url = relay.url().to_string();
|
||||
let relay_domain = relay_url
|
||||
.trim_start_matches("ws://")
|
||||
.trim_start_matches("wss://")
|
||||
.to_string();
|
||||
let npub = client
|
||||
.public_key()
|
||||
.to_bech32()
|
||||
.expect("npub for served repo publish");
|
||||
let clone_url = format!("http://{}/{}/{}.git", relay_domain, npub, repo_id);
|
||||
|
||||
let mut announcement_builder = client
|
||||
.event_builder(Kind::GitRepoAnnouncement, "")
|
||||
.tag(Tag::identifier(repo_id))
|
||||
.tag(Tag::custom("name", vec![repo_id.to_string()]))
|
||||
.tag(Tag::custom("clone", vec![clone_url.clone()]))
|
||||
.tag(Tag::custom("relays", vec![relay_url.clone()]));
|
||||
if !maintainer_pubkeys.is_empty() {
|
||||
announcement_builder =
|
||||
announcement_builder.tag(Tag::custom("maintainers", maintainer_pubkeys.to_vec()));
|
||||
}
|
||||
|
||||
let announcement = announcement_builder
|
||||
.build(client.keys())
|
||||
.expect("build served announcement with maintainers");
|
||||
client
|
||||
.send_event(announcement.clone())
|
||||
.await
|
||||
.expect("send served announcement with maintainers");
|
||||
|
||||
let state_event = create_state_event(
|
||||
client.keys(),
|
||||
repo_id,
|
||||
&[("main", commit_hash.as_str())],
|
||||
&[],
|
||||
&[clone_url.as_str()],
|
||||
&[relay_url.as_str()],
|
||||
)
|
||||
.expect("build served state event with maintainers");
|
||||
client
|
||||
.send_event_and_note_purgatory(state_event.clone())
|
||||
.await
|
||||
.expect("send served state event with maintainers");
|
||||
|
||||
push_to_relay(temp_dir.path(), &relay_domain, &npub, repo_id)
|
||||
.expect("push served repo data with maintainers");
|
||||
tokio::time::sleep(Duration::from_millis(900)).await;
|
||||
|
||||
assert!(
|
||||
client
|
||||
.is_event_on_relay(announcement.id)
|
||||
.await
|
||||
.expect("query served announcement with maintainers"),
|
||||
"served owner announcement should be promoted"
|
||||
);
|
||||
assert!(
|
||||
client
|
||||
.is_event_on_relay(state_event.id)
|
||||
.await
|
||||
.expect("query served state with maintainers"),
|
||||
"served owner state event should be promoted"
|
||||
);
|
||||
|
||||
commit_hash
|
||||
}
|
||||
|
||||
async fn wait_for_event_presence(
|
||||
client: &AuditClient,
|
||||
event_id: EventId,
|
||||
@@ -572,6 +648,59 @@ async fn promotion_with_missing_or_invalid_recovery_artifacts_is_deterministic()
|
||||
fixture.relay.stop().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn maintainer_announcement_accept_route_runs_recovery_trigger_a() {
|
||||
let fixture = setup_parked_announcement_with_failed_ingest_recovery(
|
||||
"promotion-recovery-maintainer-accept-route",
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
std::fs::write(&fixture.archive_path, &fixture.archive_backup)
|
||||
.expect("restore archive bytes before maintainer-accept recovery");
|
||||
|
||||
let owner = AuditClient::new(fixture.relay.url(), AuditConfig::isolated())
|
||||
.await
|
||||
.expect("create owner client for maintainer route");
|
||||
let maintainer_hex = fixture.client.public_key().to_hex();
|
||||
|
||||
let _owner_commit = publish_served_repo_for_identifier_with_maintainers(
|
||||
&owner,
|
||||
&fixture.relay,
|
||||
&fixture.repo_id,
|
||||
&[maintainer_hex],
|
||||
)
|
||||
.await;
|
||||
|
||||
let maintainer_reannouncement =
|
||||
build_reannouncement(&fixture.client, &fixture.relay, &fixture.repo_id);
|
||||
fixture
|
||||
.client
|
||||
.send_event(maintainer_reannouncement.clone())
|
||||
.await
|
||||
.expect("send maintainer re-announcement recovery trigger");
|
||||
|
||||
assert!(
|
||||
wait_for_event_presence(
|
||||
&fixture.client,
|
||||
maintainer_reannouncement.id,
|
||||
Duration::from_secs(8)
|
||||
)
|
||||
.await,
|
||||
"maintainer-accept route should accept re-announcement"
|
||||
);
|
||||
assert!(
|
||||
wait_for_event_presence(&fixture.client, fixture.issue.id, Duration::from_secs(8)).await,
|
||||
"maintainer-accept route should run recovery and restore issue"
|
||||
);
|
||||
assert!(
|
||||
wait_for_event_presence(&fixture.client, fixture.pr.id, Duration::from_secs(8)).await,
|
||||
"maintainer-accept route should run recovery and restore PR"
|
||||
);
|
||||
|
||||
fixture.relay.stop().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn state_event_received_route_runs_promotion_recovery_with_tombstones() {
|
||||
let fixture = setup_parked_announcement_with_failed_ingest_recovery(
|
||||
|
||||
Reference in New Issue
Block a user