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

  1. Update verify_channel_roster_fence to inspect pg_proc/pg_trigger for the actual function body/trigger it guards
  2. Ensure the CREATE OR REPLACE targets the same schema-qualified function the verifier checks
  3. 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

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


AI-assisted analysis of block/buzz@dad5a33865 (2026-08-30). Data as JSON: /api/errors/983f99f2477aba01. Report an issue: GitHub.