From 30f173c1f2ab4985aa16a8afe874d64e51d5b96b Mon Sep 17 00:00:00 2001 From: Joel Teply Date: Sat, 15 Aug 2026 12:49:20 -0500 Subject: [PATCH] feat(persona): subscribe excludes heartbeat-stamped events at the router (#445 adoption) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adopts airc 574ce235 (PRs #1362 + slice 2): subscriber-side daemon filters — delivery classes + header predicates incl. the new HeaderFilter::Not/Has exclusion variants — now ride the attach and are enforced BEFORE fan-out. subscribe_every_room (the ONE seam all three persona stream consumers share) now excludes events stamped airc.heartbeat.kind at the router. Measured 2026-08-15: liveness beacons were 149 of 177 inbound events on a persona subscription (84%), every one decoded and discarded as no_continuum_body_hint. They now never cross the socket. Exclusion, never an allowlist — unstamped events keep flowing, so nothing a publisher hasn't classified yet can be silently dropped. Pin bump e910008 → 574ce235 (all six airc crates move together per the IPC-ABI note in Cargo.toml). One API drift adapted: page_recent_in now takes a resolved &Room; nav's cursor-advance resolves through room_by_name_or_channel, whose refuse-unsubscribed property is correct for a read-cursor write too. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo --- Cargo.lock | 56 +++++++++---------- Cargo.toml | 12 ++-- core/continuum-core/src/modules/nav.rs | 16 +++++- .../src/persona/airc_citizen.rs | 15 ++++- 4 files changed, 62 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2cdfa56cc..888006a17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,7 +81,7 @@ dependencies = [ [[package]] name = "airc-bus" version = "0.1.0" -source = "git+https://github.com/CambrianTech/airc?rev=e910008#e910008fe57a076596b8fe23f7137b7a6f30f014" +source = "git+https://github.com/CambrianTech/airc?rev=574ce235#574ce2352181591584ea52e913a80b202bd0c011" dependencies = [ "airc-core", "async-stream", @@ -98,7 +98,7 @@ dependencies = [ [[package]] name = "airc-core" version = "0.1.0" -source = "git+https://github.com/CambrianTech/airc?rev=e910008#e910008fe57a076596b8fe23f7137b7a6f30f014" +source = "git+https://github.com/CambrianTech/airc?rev=574ce235#574ce2352181591584ea52e913a80b202bd0c011" dependencies = [ "serde", "serde_json", @@ -108,7 +108,7 @@ dependencies = [ [[package]] name = "airc-diagnostics" version = "0.1.0" -source = "git+https://github.com/CambrianTech/airc?rev=e910008#e910008fe57a076596b8fe23f7137b7a6f30f014" +source = "git+https://github.com/CambrianTech/airc?rev=574ce235#574ce2352181591584ea52e913a80b202bd0c011" dependencies = [ "serde", "serde_json", @@ -118,7 +118,7 @@ dependencies = [ [[package]] name = "airc-identity" version = "0.1.0" -source = "git+https://github.com/CambrianTech/airc?rev=e910008#e910008fe57a076596b8fe23f7137b7a6f30f014" +source = "git+https://github.com/CambrianTech/airc?rev=574ce235#574ce2352181591584ea52e913a80b202bd0c011" dependencies = [ "airc-core", "airc-protocol", @@ -130,7 +130,7 @@ dependencies = [ [[package]] name = "airc-ipc" version = "0.1.0" -source = "git+https://github.com/CambrianTech/airc?rev=e910008#e910008fe57a076596b8fe23f7137b7a6f30f014" +source = "git+https://github.com/CambrianTech/airc?rev=574ce235#574ce2352181591584ea52e913a80b202bd0c011" dependencies = [ "airc-core", "airc-protocol", @@ -144,7 +144,7 @@ dependencies = [ [[package]] name = "airc-lib" version = "0.1.0" -source = "git+https://github.com/CambrianTech/airc?rev=e910008#e910008fe57a076596b8fe23f7137b7a6f30f014" +source = "git+https://github.com/CambrianTech/airc?rev=574ce235#574ce2352181591584ea52e913a80b202bd0c011" dependencies = [ "airc-bus", "airc-core", @@ -180,7 +180,7 @@ dependencies = [ [[package]] name = "airc-protocol" version = "0.1.0" -source = "git+https://github.com/CambrianTech/airc?rev=e910008#e910008fe57a076596b8fe23f7137b7a6f30f014" +source = "git+https://github.com/CambrianTech/airc?rev=574ce235#574ce2352181591584ea52e913a80b202bd0c011" dependencies = [ "airc-core", "chacha20poly1305", @@ -198,7 +198,7 @@ dependencies = [ [[package]] name = "airc-relay" version = "0.1.0" -source = "git+https://github.com/CambrianTech/airc?rev=e910008#e910008fe57a076596b8fe23f7137b7a6f30f014" +source = "git+https://github.com/CambrianTech/airc?rev=574ce235#574ce2352181591584ea52e913a80b202bd0c011" dependencies = [ "airc-core", "airc-protocol", @@ -215,7 +215,7 @@ dependencies = [ [[package]] name = "airc-store" version = "0.1.0" -source = "git+https://github.com/CambrianTech/airc?rev=e910008#e910008fe57a076596b8fe23f7137b7a6f30f014" +source = "git+https://github.com/CambrianTech/airc?rev=574ce235#574ce2352181591584ea52e913a80b202bd0c011" dependencies = [ "airc-bus", "airc-core", @@ -251,7 +251,7 @@ dependencies = [ [[package]] name = "airc-transport" version = "0.1.0" -source = "git+https://github.com/CambrianTech/airc?rev=e910008#e910008fe57a076596b8fe23f7137b7a6f30f014" +source = "git+https://github.com/CambrianTech/airc?rev=574ce235#574ce2352181591584ea52e913a80b202bd0c011" dependencies = [ "airc-core", "airc-diagnostics", @@ -275,7 +275,7 @@ dependencies = [ [[package]] name = "airc-trust" version = "0.1.0" -source = "git+https://github.com/CambrianTech/airc?rev=e910008#e910008fe57a076596b8fe23f7137b7a6f30f014" +source = "git+https://github.com/CambrianTech/airc?rev=574ce235#574ce2352181591584ea52e913a80b202bd0c011" dependencies = [ "airc-core", "airc-protocol", @@ -286,7 +286,7 @@ dependencies = [ [[package]] name = "airc-wire" version = "0.1.0" -source = "git+https://github.com/CambrianTech/airc?rev=e910008#e910008fe57a076596b8fe23f7137b7a6f30f014" +source = "git+https://github.com/CambrianTech/airc?rev=574ce235#574ce2352181591584ea52e913a80b202bd0c011" dependencies = [ "airc-bus", "airc-core", @@ -300,7 +300,7 @@ dependencies = [ [[package]] name = "airc-work" version = "0.1.0" -source = "git+https://github.com/CambrianTech/airc?rev=e910008#e910008fe57a076596b8fe23f7137b7a6f30f014" +source = "git+https://github.com/CambrianTech/airc?rev=574ce235#574ce2352181591584ea52e913a80b202bd0c011" dependencies = [ "airc-core", "airc-protocol", @@ -313,7 +313,7 @@ dependencies = [ [[package]] name = "airc-work-store" version = "0.1.0" -source = "git+https://github.com/CambrianTech/airc?rev=e910008#e910008fe57a076596b8fe23f7137b7a6f30f014" +source = "git+https://github.com/CambrianTech/airc?rev=574ce235#574ce2352181591584ea52e913a80b202bd0c011" dependencies = [ "airc-core", "airc-store", @@ -453,7 +453,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -464,7 +464,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3710,7 +3710,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4013,7 +4013,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4863,7 +4863,7 @@ dependencies = [ "gobject-sys", "libc", "system-deps", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -5529,7 +5529,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.58.0", + "windows-core 0.57.0", ] [[package]] @@ -7093,7 +7093,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -8353,7 +8353,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ "bytes", - "heck 0.4.1", + "heck 0.5.0", "itertools 0.12.1", "log", "multimap", @@ -8373,7 +8373,7 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" dependencies = [ - "heck 0.4.1", + "heck 0.5.0", "itertools 0.14.0", "log", "multimap", @@ -9474,7 +9474,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -10251,7 +10251,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -10757,10 +10757,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand 2.3.0", - "getrandom 0.3.4", + "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -12625,7 +12625,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c44c88a4f..68c13965a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -185,18 +185,18 @@ members = [ # room is still its default under either verb. This is what continuum's # room/join needs (task #65, Joel: a persona is first-class and can subscribe to # many rooms). Also fixes `RoomJoinedBody.is_default`, previously hardcoded true. -airc-core = { git = "https://github.com/CambrianTech/airc", rev = "e910008" } -airc-protocol = { git = "https://github.com/CambrianTech/airc", rev = "e910008" } -airc-ipc = { git = "https://github.com/CambrianTech/airc", rev = "e910008" } -airc-lib = { git = "https://github.com/CambrianTech/airc", rev = "e910008" } -airc-wire = { git = "https://github.com/CambrianTech/airc", rev = "e910008" } +airc-core = { git = "https://github.com/CambrianTech/airc", rev = "574ce235" } +airc-protocol = { git = "https://github.com/CambrianTech/airc", rev = "574ce235" } +airc-ipc = { git = "https://github.com/CambrianTech/airc", rev = "574ce235" } +airc-lib = { git = "https://github.com/CambrianTech/airc", rev = "574ce235" } +airc-wire = { git = "https://github.com/CambrianTech/airc", rev = "574ce235" } # airc-work: the work-board domain (cards/lanes/PRs). `Airc::work_board` # returns a `WorkBoardProjection` whose types (`WorkCard`, `LaneRecord`, # `CardState`, …) the positron kanban projector maps → `KanbanViewState` # at the seam. Only continuum-core (the projector) depends on it; the # neutral `continuum-positron` contract crate MIRRORS these enums and # must NOT depend on airc-work. -airc-work = { git = "https://github.com/CambrianTech/airc", rev = "e910008" } +airc-work = { git = "https://github.com/CambrianTech/airc", rev = "574ce235" } # Candle ML framework — patched via [patch.crates-io] below. # Fixes: Metal buffer pool leak (#2271), RoPE NEOX convention (#3410) diff --git a/core/continuum-core/src/modules/nav.rs b/core/continuum-core/src/modules/nav.rs index 82c6ef8ec..3785220ff 100644 --- a/core/continuum-core/src/modules/nav.rs +++ b/core/continuum-core/src/modules/nav.rs @@ -236,7 +236,21 @@ async fn advance_caller_cursor(caller: Uuid, room: Uuid, lamport: u64) -> u64 { let airc = runtime.airc(); // Resolve the event AT this lamport so airc gets the real source event (room + // kind, and the `SubscriptionAdvanced` emit). No event → nothing to mark. - let events = match airc.page_recent_in(Some(airc_core::RoomId::from_uuid(room)), 256).await { + // airc 574ce235: page_recent_in takes a resolved &Room; the resolver refuses + // rooms outside this scope's subscription set, which is the right refusal here + // too — advancing a read cursor in a room the caller isn't part of would mint + // state about a conversation they never joined. + let room_handle = match airc + .room_by_name_or_channel(&room.to_string(), "advance read cursor in") + .await + { + Ok(r) => r, + Err(err) => { + tracing::warn!(error = %err, caller = %caller, room = %room, "nav: cursor room resolve failed"); + return 0; + } + }; + let events = match airc.page_recent_in(&room_handle, 256).await { Ok(e) => e, Err(err) => { tracing::warn!(error = %err, caller = %caller, room = %room, "nav: cursor page failed"); diff --git a/core/continuum-core/src/persona/airc_citizen.rs b/core/continuum-core/src/persona/airc_citizen.rs index 270c7881f..c1b7e3408 100644 --- a/core/continuum-core/src/persona/airc_citizen.rs +++ b/core/continuum-core/src/persona/airc_citizen.rs @@ -188,8 +188,19 @@ pub trait AircCitizen: pub(crate) async fn subscribe_every_room( airc: &airc_lib::Airc, ) -> Result { - airc.subscribe_subscribed_filtered(airc_lib::EventFilter::default()) - .await + // #445: liveness beacons were 84% of a persona subscription's inbound + // (measured 2026-08-15: 149 of 177 events in the window, 100% discarded + // post-decode as no_continuum_body_hint). Heartbeats already stamp their + // class header at publish, so exclude them at the ROUTER — they never + // cross the socket, never cost a decode. Exclusion, never an allowlist: + // unstamped events keep flowing, so nothing a publisher hasn't classified + // yet can be silently dropped. The same filter still applies client-side + // on the in-process (non-daemon) fallback, which has no router. + let mut filter = airc_lib::EventFilter::default(); + filter.headers_filter = airc_core::HeaderFilter::Not(Box::new(airc_core::HeaderFilter::Has { + key: airc_lib::HEADER_HEARTBEAT_KIND.to_string(), + })); + airc.subscribe_subscribed_filtered(filter).await } /// Where a reply for `room_id` should be published — the ONE place