Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions crates/buzz-core/src/kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,9 @@ pub const KIND_THREAD_SUMMARY: u32 = 39005;
/// content = `{has_more, next_cursor}`. The only authority on exhaustion —
/// clients must not infer `has_more` from row counts.
pub const KIND_WINDOW_BOUNDS: u32 = 39006;
/// Deployment banner overlay: relay-signed active operator banner for this viewer.
/// Content is `{id, severity, text, maxDisplays}` and `d` tag is the banner public id.
pub const KIND_RELAY_BANNER: u32 = 13536;

/// Workflow definition (parameterized replaceable, d=workflow_uuid).
pub const KIND_WORKFLOW_DEF: u32 = 30620;
Expand Down Expand Up @@ -694,6 +697,7 @@ pub const ALL_KINDS: &[u32] = &[
KIND_NIP29_GROUP_ROLES,
KIND_THREAD_SUMMARY,
KIND_WINDOW_BOUNDS,
KIND_RELAY_BANNER,
KIND_PRESENCE_UPDATE,
KIND_TYPING_INDICATOR,
KIND_HUDDLE_REACTION,
Expand Down Expand Up @@ -839,6 +843,7 @@ pub const fn is_relay_only_kind(kind: u32) -> bool {
| KIND_DM_VISIBILITY
| KIND_THREAD_SUMMARY
| KIND_WINDOW_BOUNDS
| KIND_RELAY_BANNER
)
}

Expand Down Expand Up @@ -867,6 +872,7 @@ const _: () = assert!(is_parameterized_replaceable(KIND_DM_VISIBILITY)); // 3062
const _: () = assert!(is_parameterized_replaceable(KIND_PROJECT)); // 30621 ∈ 30000–39999
const _: () = assert!(is_parameterized_replaceable(KIND_THREAD_SUMMARY)); // 39005 ∈ 30000–39999
const _: () = assert!(is_parameterized_replaceable(KIND_WINDOW_BOUNDS)); // 39006 ∈ 30000–39999
const _: () = assert!(is_replaceable(KIND_RELAY_BANNER)); // 13536 ∈ 10000–19999

// Compile-time: NIP-34 parameterized replaceable kinds are in the correct range.
const _: () = assert!(
Expand Down Expand Up @@ -936,6 +942,14 @@ mod tests {
}
}

#[test]
fn relay_banner_is_replaceable_relay_only_not_parameterized() {
assert_eq!(KIND_RELAY_BANNER, 13536);
assert!(is_replaceable(KIND_RELAY_BANNER));
assert!(!is_parameterized_replaceable(KIND_RELAY_BANNER));
assert!(is_relay_only_kind(KIND_RELAY_BANNER));
}

// ── event_is_shared / is_unshared_gated_event ────────────────────────

fn make_event_of_kind(kind: u32, tags: &[&[&str]]) -> nostr::Event {
Expand Down
9 changes: 7 additions & 2 deletions crates/buzz-db/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ pub(crate) use runtime::{
pub use store::{
admin_moderation, allowlist, api_token, archived_identities, channel, channel_members,
community, deletion, dm, event, feed, git_repo, moderation, partition, product_feedback, push,
reaction, relay_admin_actions, relay_invite, relay_members, relay_operators, reminder,
replaceable, storage_accounting, thread, usage, user, workflow,
reaction, relay_admin_actions, relay_banners, relay_invite, relay_members, relay_operators,
reminder, replaceable, storage_accounting, thread, usage, user, workflow,
};

pub use allowlist::AllowlistEntry;
Expand All @@ -78,6 +78,11 @@ pub use community::{
pub use error::{DbError, Result};
pub use event::{EventQuery, DEFAULT_MAX_PAGE_LIMIT};
pub use reaction::ReactionEventInsertOutcome;
pub use relay_banners::{
RelayBannerDismissOutcome, RelayBannerRecord, RelayBannerScope, RelayBannerSeverity,
RelayBannerTargetScope, RelayBannerUpsert, RelayBannerUpsertOutcome, RelayBannerViewOutcome,
MAX_BANNER_MESSAGE_CHARS,
};
pub use reminder::DueReminder;
pub use usage::UsageMetricsLeader;

Expand Down
28 changes: 27 additions & 1 deletion crates/buzz-db/src/runtime/migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,10 @@ mod postgres_tests {
"relay_admin_outbox",
"relay_operator_audit",
"storage_accounting_snapshots",
"relay_banners",
"relay_banner_communities",
"relay_banner_user_state",
"relay_banner_view_acks",
] {
if normalized[insert_pos..].contains(&format!("'{value}'")) {
globals.insert(value.to_owned());
Expand Down Expand Up @@ -703,7 +707,7 @@ mod postgres_tests {
let mut migrations: Vec<_> = MIGRATOR.iter().collect();
migrations.sort_by_key(|migration| migration.version);

assert_eq!(migrations.len(), 46);
assert_eq!(migrations.len(), 47);
assert_eq!(migrations[0].version, 1);
assert_eq!(&*migrations[0].description, "initial schema");
assert!(migrations[0]
Expand Down Expand Up @@ -1288,6 +1292,21 @@ mod postgres_tests {
.sql
.as_str()
.contains("CREATE TABLE storage_accounting_snapshots"));

// Relay banners are deployment-global operator configuration and
// per-user display state. Keep them additive after storage accounting
// so the brownfield 0046 checksum from main remains immutable.
assert_eq!(migrations[46].version, 47);
let relay_banners = migrations[46].sql.as_str();
assert!(relay_banners.contains("CREATE TABLE relay_banners"));
assert!(relay_banners.contains("CREATE TABLE relay_banner_communities"));
assert!(relay_banners.contains("CREATE TABLE relay_banner_user_state"));
assert!(relay_banners.contains("CREATE TABLE relay_banner_view_acks"));
assert!(relay_banners.contains("_operator_global_tables"));
assert!(relay_banners.contains("attach_community_write_fence('relay_banner_communities')"));
assert!(relay_banners.contains("attach_community_write_fence('relay_banner_user_state')"));
assert!(relay_banners.contains("attach_community_write_fence('relay_banner_view_acks')"));
assert!(!migrations[0].sql.as_str().contains("relay_banners"));
// schema.sql exclusion list must match the restored (pre-0041) body.
assert!(
desired_schema.contains("'rate_limit_violations'\n ]::TEXT[])"),
Expand Down Expand Up @@ -1828,6 +1847,9 @@ mod postgres_tests {
let mut expected_fences = migration.fence_attachments.clone();
expected_fences.remove("product_feedback");
expected_fences.remove("rate_limit_violations");
expected_fences.insert("relay_banner_communities".to_owned());
expected_fences.insert("relay_banner_user_state".to_owned());
expected_fences.insert("relay_banner_view_acks".to_owned());
assert_eq!(
expected_fences, schema.fence_attachments,
"write-fence attachment targets differ after recovery policy"
Expand Down Expand Up @@ -2267,6 +2289,10 @@ mod postgres_tests {
"relay_admin_actions",
"relay_admin_outbox",
"relay_operator_audit",
"relay_banners",
"relay_banner_communities",
"relay_banner_user_state",
"relay_banner_view_acks",
] {
assert_eq!(
columns(&desired, table).await,
Expand Down
2 changes: 2 additions & 0 deletions crates/buzz-db/src/store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ pub mod push;
pub mod reaction;
/// HTTP report-resolution enforcement state machine persistence.
pub mod relay_admin_actions;
/// Deployment-global relay banner persistence.
pub mod relay_banners;
/// Use-limited relay invite persistence (v2 opaque tokens).
pub mod relay_invite;
/// Relay-level membership persistence (NIP-43).
Expand Down
Loading
Loading