{"record":{"id":"983f99f2477aba01","repo":"block/buzz","slug":"replace-roster-fence-with-inert-body","errorCode":null,"errorMessage":"replace roster fence with inert body","messagePattern":"replace roster fence with inert body","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/buzz-db/src/store/channel_members.rs","lineNumber":3033,"sourceCode":"        drop_scratch_db(&admin, pool, &scratch_name).await;\n    }\n\n    #[tokio::test]\n    #[ignore = \"requires Postgres\"]\n    async fn channel_roster_fence_behavior_verification_detects_inert_function() {\n        let admin = PgPool::connect(&admin_url().await)\n            .await\n            .expect(\"connect admin\");\n        let (pool, scratch_name) = create_scratch_db(&admin, \"roster_fence_inert\").await;\n        let db = Db::from_pool(pool.clone());\n\n        sqlx::raw_sql(\n            \"CREATE OR REPLACE FUNCTION guard_channel_roster_snapshot() \\\n             RETURNS TRIGGER AS $$ BEGIN RETURN NEW; END; $$ LANGUAGE plpgsql;\",\n        )\n        .execute(&pool)\n        .await\n        .expect(\"replace roster fence with inert body\");\n        let error = db\n            .verify_channel_roster_fence()\n            .await\n            .expect_err(\"inert roster fence must fail closed\");\n        assert!(\n            error\n                .to_string()\n                .contains(\"stale probe roster was accepted\"),\n            \"behavior probe must identify inert semantics: {error}\"\n        );\n\n        drop_scratch_db(&admin, pool, &scratch_name).await;\n    }\n\n    #[tokio::test]\n    #[ignore = \"requires Postgres\"]\n    async fn channel_roster_fence_catalog_verification_fails_closed() {\n        let admin = PgPool::connect(&admin_url().await)","sourceCodeStart":3015,"sourceCodeEnd":3051,"githubUrl":"https://github.com/block/buzz/blob/dad5a33865fc81a2e55b3b60746632f615ec1e3a/crates/buzz-db/src/store/channel_members.rs#L3015-L3051","documentation":"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.","triggerScenarios":"`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.","commonSituations":"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).","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"],"exampleFix":"// before\nlet error = db.verify_channel_roster_fence().await.expect_err(\"inert roster fence must fail closed\");\n// after\nmatch db.verify_channel_roster_fence().await {\n    Err(e) => assert!(/* e mentions inert body */),\n    Ok(_) => panic!(\"verifier accepted inert roster fence — update verification\"),\n}","handlingStrategy":"type-guard","validationCode":"// sanity: function body really was replaced\nlet body: String = sqlx::query_scalar(\n    \"SELECT prosrc FROM pg_proc WHERE oid = 'guard_channel_roster_snapshot()'::regproc\"\n).fetch_one(&pool).await?;\nassert_eq!(body.trim(), \"BEGIN RETURN NEW; END;\");","typeGuard":"fn fence_is_inert(prosrc: &str) -> bool {\n    prosrc.contains(\"RETURN NEW\") && !prosrc.to_lowercase().contains(\"delete\") && !prosrc.to_lowercase().contains(\"raise\")\n}","tryCatchPattern":"match db.verify_channel_roster_fence().await {\n    Err(e) => assert!(format!(\"{e}\").contains(\"roster\"), \"unexpected error: {e}\"),\n    Ok(_) => panic!(\"verifier must reject inert roster fence\"),\n}","preventionTips":["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"],"tags":["postgres","trigger","verification","fail-closed","test"],"backgroundTag":"verification-fail-open","analyzedSha":"dad5a33865fc81a2e55b3b60746632f615ec1e3a","analyzedAt":"2026-08-30T13:49:18.474Z","contentChangedAt":"2026-08-30T13:49:18.474Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}