diff --git a/crates/buzz-relay/src/handlers/create_with_members_postgres_tests.rs b/crates/buzz-relay/src/handlers/create_with_members_postgres_tests.rs index 973225f245..de8327ddb4 100644 --- a/crates/buzz-relay/src/handlers/create_with_members_postgres_tests.rs +++ b/crates/buzz-relay/src/handlers/create_with_members_postgres_tests.rs @@ -170,7 +170,8 @@ async fn create_respects_the_members_add_policy() { /// Resending a create whose OK was lost is accepted; a different create for /// the same UUID, even from its creator, is refused before its side effects, /// so it can neither add nor announce anyone. -/// Mutations: refuse every existing UUID → resend not accepted → RED; let the +/// Mutations: refuse every existing UUID → resend not accepted → RED; check the +/// members' policies before recognizing a stored resend → RED; let the /// creator's changed create through → stranger announced → RED. #[tokio::test] #[ignore = "requires Postgres"] @@ -191,7 +192,7 @@ async fn resending_a_create_confirms_it() { .expect("create"); assert!(first.accepted, "{}", first.message); let before = roles(&state, &tenant, channel_id).await; - let resent = ingest(&state, &tenant, create, &creator) + let resent = ingest(&state, &tenant, create.clone(), &creator) .await .expect("resend"); assert!(resent.accepted, "{}", resent.message); @@ -209,6 +210,27 @@ async fn resending_a_create_confirms_it() { assert_eq!(changed.message, "duplicate: channel already exists"); } + // The agent closing its adds after the create doesn't refuse the resend. + let agent_bytes = agent.public_key().to_bytes().to_vec(); + state + .db + .ensure_user(tenant.community(), &agent_bytes) + .await + .expect("agent user"); + state + .db + .set_channel_add_policy(tenant.community(), &agent_bytes, "nobody") + .await + .expect("policy"); + let resent_after_policy = ingest(&state, &tenant, create, &creator) + .await + .expect("resend after policy change"); + assert!( + resent_after_policy.accepted, + "{}", + resent_after_policy.message + ); + assert_eq!(roles(&state, &tenant, channel_id).await, before); let notices = state .db diff --git a/crates/buzz-relay/src/handlers/side_effects.rs b/crates/buzz-relay/src/handlers/side_effects.rs index e985a2dbac..880369cb88 100644 --- a/crates/buzz-relay/src/handlers/side_effects.rs +++ b/crates/buzz-relay/src/handlers/side_effects.rs @@ -543,8 +543,20 @@ pub async fn validate_admin_event( // CREATE_GROUP doesn't need an existing channel — skip h-tag extraction. // Its initial members get the third-party add check a kind:9000 would. if kind == 9007 { + let members = initial_members(event)?; + // Ingest confirms a resend of a stored create without writing anything, + // so a policy changed since then must not refuse it. + if members.is_empty() + || state + .db + .get_event_by_id_for_event_write(tenant.community(), event.id.as_bytes()) + .await? + .is_some() + { + return Ok(()); + } let actor_bytes = event.pubkey.to_bytes().to_vec(); - for member in initial_members(event)? { + for member in members { if let Some((policy, owner)) = state .db .get_agent_channel_policy(tenant.community(), &member)