From 148cbaba8b1fca297192d2ecc94cab15a21d006e Mon Sep 17 00:00:00 2001 From: Daniel Rouleau Date: Mon, 2 Nov 2026 15:51:37 -0800 Subject: [PATCH 1/2] Enable incremental artifact reads by default Signed-off-by: Daniel Rouleau --- crates/buzz-relay/src/config.rs | 2 +- docs/artifact-sync-deployment.md | 10 ++++++---- docs/nips/NIP-AR.md | 6 +++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/crates/buzz-relay/src/config.rs b/crates/buzz-relay/src/config.rs index cbed06b641b..76ceefb0aab 100644 --- a/crates/buzz-relay/src/config.rs +++ b/crates/buzz-relay/src/config.rs @@ -801,7 +801,7 @@ impl Config { .unwrap_or(false); let artifact_incremental_enabled = std::env::var("BUZZ_ARTIFACT_INCREMENTAL_ENABLED") .map(|v| v == "true" || v == "1") - .unwrap_or(false); + .unwrap_or(true); // Defaults true → single-pod (N=1) keeps today's huddle behavior. A // horizontally-scaled deployment sets this false; see the field doc. diff --git a/docs/artifact-sync-deployment.md b/docs/artifact-sync-deployment.md index 6af589b2bd5..141391352ff 100644 --- a/docs/artifact-sync-deployment.md +++ b/docs/artifact-sync-deployment.md @@ -1,8 +1,8 @@ # Incremental artifact reads deployment Incremental artifact reads use a 120-second overlap and database modification -timestamps. `BUZZ_ARTIFACT_INCREMENTAL_ENABLED` is off by default. Enable it only -after the schema, indexes, and every serving writer are upgraded. +timestamps. `BUZZ_ARTIFACT_INCREMENTAL_ENABLED` is on by default. Set it to +`false` until the schema, indexes, and every serving writer are upgraded. ## Schema and indexes @@ -51,10 +51,12 @@ migration ledger. ## Enable and rollback -Deploy the timestamp writers to every relay instance with the gate off. +Set `BUZZ_ARTIFACT_INCREMENTAL_ENABLED=false` while the serving fleet contains +old writers. Deploy the timestamp writers to every relay instance with the gate off. Drain the old writers. All instances must use the same configured relay signing key, because that key authenticates the progress tokens. Then enable -`BUZZ_ARTIFACT_INCREMENTAL_ENABLED=true` on the serving fleet. NIP-11 advertises +`BUZZ_ARTIFACT_INCREMENTAL_ENABLED=true` on the serving fleet, or remove the +override to use the enabled default. NIP-11 advertises `artifacts.incremental` only when the gate is enabled. The endpoint refuses incremental requests while the gate is off. diff --git a/docs/nips/NIP-AR.md b/docs/nips/NIP-AR.md index ce829897a3b..6221ca8ca8f 100644 --- a/docs/nips/NIP-AR.md +++ b/docs/nips/NIP-AR.md @@ -371,6 +371,6 @@ HTTP 400 `incremental_invalid` rejects invalid requests or tokens. HTTP 503 `incremental_unavailable` reports a service or work-budget failure. These failures do not advance progress. -The rollout gate is `BUZZ_ARTIFACT_INCREMENTAL_ENABLED`, off by default. Follow -[artifact-sync-deployment.md](../artifact-sync-deployment.md) before enabling -it. Incremental completion does not establish a live subscription to every channel. +The rollout gate is `BUZZ_ARTIFACT_INCREMENTAL_ENABLED`, on by default. Set it +to `false` during upgrades until the prerequisites in +[artifact-sync-deployment.md](../artifact-sync-deployment.md) hold. Incremental completion does not establish a live subscription to every channel. From be9dfb77185268d93c6727b6731aa826b3367c57 Mon Sep 17 00:00:00 2001 From: Daniel Rouleau Date: Mon, 2 Nov 2026 16:29:07 -0800 Subject: [PATCH 2/2] Remove the incremental artifact feature flag Signed-off-by: Daniel Rouleau --- .../sync_access_postgres_tests.rs | 82 ++++++++----------- .../sync_postgres_tests.rs | 6 +- .../sync_scale_postgres_tests.rs | 1 - crates/buzz-relay/src/api/artifact_sync.rs | 3 - crates/buzz-relay/src/config.rs | 6 -- crates/buzz-relay/src/nip11.rs | 4 +- docs/artifact-sync-deployment.md | 27 ++---- docs/nips/NIP-AR.md | 4 +- 8 files changed, 44 insertions(+), 89 deletions(-) diff --git a/crates/buzz-relay/src/api/artifact_postgres_tests/sync_access_postgres_tests.rs b/crates/buzz-relay/src/api/artifact_postgres_tests/sync_access_postgres_tests.rs index 92461132597..9834c6d9ec7 100644 --- a/crates/buzz-relay/src/api/artifact_postgres_tests/sync_access_postgres_tests.rs +++ b/crates/buzz-relay/src/api/artifact_postgres_tests/sync_access_postgres_tests.rs @@ -262,56 +262,42 @@ async fn sync_shrinks_large_pages_and_current_batches_fail_explicitly() { #[tokio::test] #[ignore = "requires Postgres"] -async fn sync_gate_controls_http_reads_and_capability() { +async fn sync_http_reads_and_capability_are_available() { use tower::ServiceExt; let _guard = SERIAL.lock().await; - let mut f = fixture().await; - let mut websocket_sync = Value::Null; - for enabled in [false, true] { - Arc::make_mut(&mut Arc::make_mut(&mut f.state).config).artifact_incremental_enabled = - enabled; - let response = crate::router::build_router(f.state.clone()) - .oneshot( - axum::http::Request::builder() - .uri("/info") - .header("host", &f.host) - .body(axum::body::Body::empty()) - .unwrap(), - ) - .await - .unwrap(); - assert!(response.status().is_success()); - let bytes = axum::body::to_bytes(response.into_body(), 1024 * 1024) - .await - .unwrap(); - let info: Value = serde_json::from_slice(&bytes).unwrap(); - if enabled { - assert_eq!(info["artifacts"]["sync"], websocket_sync); - assert_eq!( - info["artifacts"]["incremental"], - json!({"version":1,"overlapSeconds":120, - "pageRows":100,"currentRows":100,"responseBytes":8388608, - "permanentChannelsOnly":true,"checkpointLifetimeSeconds":2592000}) - ); - } else { - websocket_sync = info["artifacts"]["sync"].clone(); - assert!(info["artifacts"].get("incremental").is_none()); - } - let (status, _) = f - .request( - "/query", - json!([{"artifact":"incremental","query":query()}]), - ) - .await; - assert_eq!( - status, - if enabled { - StatusCode::OK - } else { - StatusCode::SERVICE_UNAVAILABLE - } - ); - } + let f = fixture().await; + let response = crate::router::build_router(f.state.clone()) + .oneshot( + axum::http::Request::builder() + .uri("/info") + .header("host", &f.host) + .body(axum::body::Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + assert!(response.status().is_success()); + let bytes = axum::body::to_bytes(response.into_body(), 1024 * 1024) + .await + .unwrap(); + let info: Value = serde_json::from_slice(&bytes).unwrap(); + assert_eq!( + info["artifacts"]["sync"], + json!({"version":1,"max_events":4096}) + ); + assert_eq!( + info["artifacts"]["incremental"], + json!({"version":1,"overlapSeconds":120, + "pageRows":100,"currentRows":100,"responseBytes":8388608, + "permanentChannelsOnly":true,"checkpointLifetimeSeconds":2592000}) + ); + let (status, _) = f + .request( + "/query", + json!([{"artifact":"incremental","query":query()}]), + ) + .await; + assert_eq!(status, StatusCode::OK); } #[tokio::test] diff --git a/crates/buzz-relay/src/api/artifact_postgres_tests/sync_postgres_tests.rs b/crates/buzz-relay/src/api/artifact_postgres_tests/sync_postgres_tests.rs index a5025b8dca3..db76a358c54 100644 --- a/crates/buzz-relay/src/api/artifact_postgres_tests/sync_postgres_tests.rs +++ b/crates/buzz-relay/src/api/artifact_postgres_tests/sync_postgres_tests.rs @@ -10,11 +10,7 @@ mod policy_postgres_tests; mod scale_postgres_tests; async fn fixture() -> Fixture { - let mut f = Fixture::new().await; - let mut state = (*f.state).clone(); - Arc::make_mut(&mut state.config).artifact_incremental_enabled = true; - f.state = Arc::new(state); - f + Fixture::new().await } static SERIAL: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(()); diff --git a/crates/buzz-relay/src/api/artifact_postgres_tests/sync_scale_postgres_tests.rs b/crates/buzz-relay/src/api/artifact_postgres_tests/sync_scale_postgres_tests.rs index cf23203eaab..aa7ace8828a 100644 --- a/crates/buzz-relay/src/api/artifact_postgres_tests/sync_scale_postgres_tests.rs +++ b/crates/buzz-relay/src/api/artifact_postgres_tests/sync_scale_postgres_tests.rs @@ -46,7 +46,6 @@ async fn sync_downloads_ten_thousand_tasks_with_sixty_thousand_memberships() { let mut other = (*bridge_handler_test_state().await.unwrap()).clone(); let config = Arc::make_mut(&mut other.config); config.require_auth_token = true; - config.artifact_incremental_enabled = true; other.relay_keypair = f.state.relay_keypair.clone(); let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); let url = format!("http://{}/query", listener.local_addr().unwrap()); diff --git a/crates/buzz-relay/src/api/artifact_sync.rs b/crates/buzz-relay/src/api/artifact_sync.rs index 6ec686d3ff2..8b50f748939 100644 --- a/crates/buzz-relay/src/api/artifact_sync.rs +++ b/crates/buzz-relay/src/api/artifact_sync.rs @@ -125,9 +125,6 @@ pub(super) async fn query( reader: &nostr::PublicKey, filter: &Value, ) -> Response { - if !state.config.artifact_incremental_enabled { - return Err(unavailable()); - } let _permit = WORK.try_acquire().map_err(|_| unavailable())?; tokio::time::timeout( std::time::Duration::from_secs(8), diff --git a/crates/buzz-relay/src/config.rs b/crates/buzz-relay/src/config.rs index 76ceefb0aab..79c439d8986 100644 --- a/crates/buzz-relay/src/config.rs +++ b/crates/buzz-relay/src/config.rs @@ -116,8 +116,6 @@ pub const MAX_DRAIN_JITTER_MS: u64 = 20_000; /// Relay runtime configuration, loaded from environment variables. #[derive(Debug, Clone)] pub struct Config { - /// Enable incremental artifact reads after every serving writer is upgraded. - pub artifact_incremental_enabled: bool, /// Address the relay HTTP/WebSocket server binds to. pub bind_addr: SocketAddr, /// Postgres database connection URL. @@ -799,9 +797,6 @@ impl Config { let require_relay_membership = std::env::var("BUZZ_REQUIRE_RELAY_MEMBERSHIP") .map(|v| v == "true" || v == "1") .unwrap_or(false); - let artifact_incremental_enabled = std::env::var("BUZZ_ARTIFACT_INCREMENTAL_ENABLED") - .map(|v| v == "true" || v == "1") - .unwrap_or(true); // Defaults true → single-pod (N=1) keeps today's huddle behavior. A // horizontally-scaled deployment sets this false; see the field doc. @@ -1457,7 +1452,6 @@ impl Config { partition_manager_advance_enabled, pubkey_allowlist_enabled, require_relay_membership, - artifact_incremental_enabled, huddle_audio_available, mesh, mesh_demo_echo, diff --git a/crates/buzz-relay/src/nip11.rs b/crates/buzz-relay/src/nip11.rs index 0f880fb5e56..fb66c997b5b 100644 --- a/crates/buzz-relay/src/nip11.rs +++ b/crates/buzz-relay/src/nip11.rs @@ -395,9 +395,7 @@ pub(crate) async fn nip11_document(state: &crate::state::AppState, raw_host: &st admin_api.as_deref(), state.config.klipy.as_ref().map(|_| "klipy"), ); - if state.config.artifact_incremental_enabled { - info.artifacts["incremental"] = crate::api::artifact_sync::capability(); - } + info.artifacts["incremental"] = crate::api::artifact_sync::capability(); if let Ok(tenant) = crate::tenant::bind_community(&state.db, raw_host).await { if let Some(profile) = crate::verified_profiles::discovery(state, &tenant).await { if let Some(fi) = info.federated_identity.as_mut() { diff --git a/docs/artifact-sync-deployment.md b/docs/artifact-sync-deployment.md index 141391352ff..e4bf6abb82e 100644 --- a/docs/artifact-sync-deployment.md +++ b/docs/artifact-sync-deployment.md @@ -1,8 +1,8 @@ # Incremental artifact reads deployment Incremental artifact reads use a 120-second overlap and database modification -timestamps. `BUZZ_ARTIFACT_INCREMENTAL_ENABLED` is on by default. Set it to -`false` until the schema, indexes, and every serving writer are upgraded. +timestamps. The schema, indexes, and every serving writer must support +incremental reads. ## Schema and indexes @@ -49,24 +49,11 @@ After all index builds succeed, run `buzz-admin migrate` with the target then verifies and records `0078`. Direct SQL application does not record the migration ledger. -## Enable and rollback +## Serving fleet -Set `BUZZ_ARTIFACT_INCREMENTAL_ENABLED=false` while the serving fleet contains -old writers. Deploy the timestamp writers to every relay instance with the gate off. -Drain the old writers. All instances must use the same configured relay -signing key, because that key authenticates the progress tokens. Then enable -`BUZZ_ARTIFACT_INCREMENTAL_ENABLED=true` on the serving fleet, or remove the -override to use the enabled default. NIP-11 advertises -`artifacts.incremental` only when the gate is enabled. The endpoint refuses incremental -requests while the gate is off. - -Before a rollback to an old writer, disable the gate on every serving -instance. Before enabling it again, upgrade every writer and advance -`communities.artifact_policy_modified_at` for each affected active community -in a reviewed maintenance transaction under community deletion admission. -This forces clients to discover current records and revalidate saved IDs. -No local absence scan is permitted. +All instances must use the same configured relay signing key, because that key +authenticates the progress tokens. NIP-11 advertises `artifacts.incremental`. +Every serving writer must maintain the modification timestamps. The 120-second stamp-to-visibility bound remains an operating assumption. -Runtime measurements are tracked separately. This PR does not authorize -a deployment or a production maintenance write. +Runtime measurements are tracked separately. diff --git a/docs/nips/NIP-AR.md b/docs/nips/NIP-AR.md index 6221ca8ca8f..da0c36a17db 100644 --- a/docs/nips/NIP-AR.md +++ b/docs/nips/NIP-AR.md @@ -371,6 +371,4 @@ HTTP 400 `incremental_invalid` rejects invalid requests or tokens. HTTP 503 `incremental_unavailable` reports a service or work-budget failure. These failures do not advance progress. -The rollout gate is `BUZZ_ARTIFACT_INCREMENTAL_ENABLED`, on by default. Set it -to `false` during upgrades until the prerequisites in -[artifact-sync-deployment.md](../artifact-sync-deployment.md) hold. Incremental completion does not establish a live subscription to every channel. +Incremental completion does not establish a live subscription to every channel.