block/buzz · warning
replace roster fence with inert body
Error message
replace roster fence with inert body
What it means
The test intentionally replaced the `guard_channel_roster_snapshot` trigger function with an inert body and expects `verify_channel_roster_fence()` to return an error (fail closed). This panic fires when verification unexpectedly SUCCEEDED, i.e. the fence verification is not detecting the neutered trigger — a verification-logic regression, not an environment issue.
Source
Thrown at crates/buzz-db/src/store/channel_members.rs:3033
drop_scratch_db(&admin, pool, &scratch_name).await;
}
#[tokio::test]
#[ignore = "requires Postgres"]
async fn channel_roster_fence_behavior_verification_detects_inert_function() {
let admin = PgPool::connect(&admin_url().await)
.await
.expect("connect admin");
let (pool, scratch_name) = create_scratch_db(&admin, "roster_fence_inert").await;
let db = Db::from_pool(pool.clone());
sqlx::raw_sql(
"CREATE OR REPLACE FUNCTION guard_channel_roster_snapshot() \
RETURNS TRIGGER AS $$ BEGIN RETURN NEW; END; $$ LANGUAGE plpgsql;",
)
.execute(&pool)
.await
.expect("replace roster fence with inert body");
let error = db
.verify_channel_roster_fence()
.await
.expect_err("inert roster fence must fail closed");
assert!(
error
.to_string()
.contains("stale probe roster was accepted"),
"behavior probe must identify inert semantics: {error}"
);
drop_scratch_db(&admin, pool, &scratch_name).await;
}
#[tokio::test]
#[ignore = "requires Postgres"]
async fn channel_roster_fence_catalog_verification_fails_closed() {
let admin = PgPool::connect(&admin_url().await)View on GitHub (pinned to dad5a33865)
Solutions
- Update verify_channel_roster_fence to inspect pg_proc/pg_trigger for the actual function body/trigger it guards
- Ensure the CREATE OR REPLACE targets the same schema-qualified function the verifier checks
- Run the test after any migration touching the roster fence to keep the verifier in sync
Example fix
// before
let error = db.verify_channel_roster_fence().await.expect_err("inert roster fence must fail closed");
// after
match db.verify_channel_roster_fence().await {
Err(e) => assert!(/* e mentions inert body */),
Ok(_) => panic!("verifier accepted inert roster fence — update verification"),
} Defensive patterns
Strategy: type-guard
Validate before calling
// sanity: function body really was replaced
let body: String = sqlx::query_scalar(
"SELECT prosrc FROM pg_proc WHERE oid = 'guard_channel_roster_snapshot()'::regproc"
).fetch_one(&pool).await?;
assert_eq!(body.trim(), "BEGIN RETURN NEW; END;"); Type guard
fn fence_is_inert(prosrc: &str) -> bool {
prosrc.contains("RETURN NEW") && !prosrc.to_lowercase().contains("delete") && !prosrc.to_lowercase().contains("raise")
} Try / catch
match db.verify_channel_roster_fence().await {
Err(e) => assert!(format!("{e}").contains("roster"), "unexpected error: {e}"),
Ok(_) => panic!("verifier must reject inert roster fence"),
} Prevention
- Keep verify_channel_roster_fence in lockstep with any migration touching the guard trigger/function
- Schema-qualify the function in CREATE OR REPLACE to avoid search_path traps
- Run this fail-closed test in CI on every schema change
When it happens
Trigger: `CREATE OR REPLACE FUNCTION ... RETURN NEW` executed, then `verify_channel_roster_fence().expect_err(...)` got Ok instead of Err — e.g. verification checks the wrong catalog object, wrong function signature, or the trigger lives on a partition the verifier ignores.
Common situations: Schema changes renaming the trigger/function without updating verify_channel_roster_fence; verifier reading a cached or different search_path (e.g. function replaced in another schema).
Related errors
- migrated roster fence must verify
- count, entry too old
- connect to test DB
- ban failed: {e}
- timeout failed: {e}
AI-assisted analysis of block/buzz@dad5a33865 (2026-08-30).
Data as JSON: /api/errors/983f99f2477aba01.
Report an issue: GitHub.