use crate::inproc::InprocRegistry;
use crate::peer_types::ContentShape as PeerContentShape;
use crate::trust::TrustedPeers;
use crate::types::{InboxItem, MessageKind};
use meerkat_core::{
InteractionId, PeerIngressAdmission, PeerIngressAuthDecision, PeerIngressConvention,
PeerIngressEnvelopeFacts, PeerIngressEnvelopeKind, PeerIngressFact, PeerIngressIdentity,
PeerIngressKind, PeerIngressMachinePolicy, PeerIngressPlainEventFacts, PeerInputClass,
TerminalityClass, handles::PeerCommsHandle,
};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use uuid::Uuid;
pub(crate) type PeerCommsHandleSlot = Arc<parking_lot::RwLock<Option<Arc<dyn PeerCommsHandle>>>>;
pub(crate) struct IngressClassificationContext {
pub(crate) require_peer_auth: bool,
pub(crate) trusted_peers: Arc<parking_lot::RwLock<TrustedPeers>>,
pub(crate) ingress_policy: Arc<PeerIngressMachinePolicy>,
pub(crate) peer_comms_handle: PeerCommsHandleSlot,
pub(crate) require_machine_authority: Arc<AtomicBool>,
pub(crate) inproc_namespace: Option<String>,
}
#[cfg(test)]
pub(crate) struct ClassificationResult {
pub(crate) class: PeerInputClass,
pub(crate) auth: PeerIngressAuthDecision,
pub(crate) from_peer: Option<String>,
pub(crate) from_peer_id: Option<String>,
pub(crate) lifecycle_peer: Option<String>,
pub(crate) response_terminality: Option<TerminalityClass>,
}
pub(crate) struct PreparedIngressItem {
pub(crate) item: InboxItem,
pub(crate) raw_item_id: InteractionId,
pub(crate) kind: PeerIngressKind,
pub(crate) class: PeerInputClass,
pub(crate) auth: PeerIngressAuthDecision,
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) trusted_sender: bool,
pub(crate) from_peer: Option<String>,
pub(crate) lifecycle_peer: Option<String>,
pub(crate) response_terminality: Option<TerminalityClass>,
pub(crate) ingress_fact: PeerIngressFact,
pub(crate) text_projection: String,
#[allow(dead_code)]
pub(crate) content_shape: PeerContentShape,
pub(crate) request_id: Option<InteractionId>,
}
fn content_shape_for_text_and_blocks(
body: &str,
blocks: Option<&[meerkat_core::types::ContentBlock]>,
) -> PeerContentShape {
match blocks {
Some(blocks) if !blocks.is_empty() => {
if body.trim().is_empty() {
PeerContentShape::Blocks
} else {
PeerContentShape::Mixed
}
}
_ => PeerContentShape::Text,
}
}
fn parse_admission_interaction_id(raw: Option<&str>) -> Option<InteractionId> {
raw.and_then(|id| Uuid::parse_str(id).ok())
.map(InteractionId)
}
impl IngressClassificationContext {
fn machine_classify_external_envelope(
&self,
facts: PeerIngressEnvelopeFacts,
) -> Option<PeerIngressAdmission> {
let handle = self.peer_comms_handle.read().clone();
let Some(handle) = handle else {
if self.require_machine_authority.load(Ordering::SeqCst) {
tracing::warn!(
"runtime-backed peer ingress has no machine authority; rejecting external envelope"
);
return None;
}
return Some(self.ingress_policy.classify_external_envelope(&facts));
};
match handle.classify_external_envelope(facts) {
Ok(admission) => Some(admission),
Err(error) => {
tracing::warn!(
error = %error,
"classified ingress rejected external envelope before shell auth/routing classification"
);
None
}
}
}
fn machine_classify_plain_event(
&self,
facts: PeerIngressPlainEventFacts,
) -> Option<PeerIngressAdmission> {
let handle = self.peer_comms_handle.read().clone();
let Some(handle) = handle else {
if self.require_machine_authority.load(Ordering::SeqCst) {
tracing::warn!(
"runtime-backed peer ingress has no machine authority; rejecting plain event"
);
return None;
}
return Some(self.ingress_policy.classify_plain_event_facts(&facts));
};
match handle.classify_plain_event(facts) {
Ok(admission) => Some(admission),
Err(error) => {
tracing::warn!(
error = %error,
"classified ingress rejected plain event before shell routing classification"
);
None
}
}
}
fn inproc_display_name_for(&self, pubkey: &crate::identity::PubKey) -> Option<String> {
let registry = InprocRegistry::global();
self.inproc_namespace
.as_deref()
.and_then(|namespace| registry.get_name_by_pubkey_in_namespace(namespace, pubkey))
.or_else(|| registry.get_name_by_pubkey(pubkey))
}
pub(crate) fn prepare(&self, item: InboxItem) -> Option<PreparedIngressItem> {
match item {
InboxItem::External { envelope } => {
let trusted = self.trusted_peers.read();
let from_peer = trusted.get_peer(&envelope.from).map(|p| p.name.clone());
let trusted_sender = from_peer.is_some();
let from_peer_id = envelope.from.to_peer_id();
let registry_name = self.inproc_display_name_for(&envelope.from);
let from_name = registry_name
.or(from_peer)
.unwrap_or_else(|| envelope.from.to_pubkey_string());
let facts = PeerIngressEnvelopeFacts {
item_id: envelope.id.to_string(),
from_peer: from_name.clone(),
from_peer_id,
kind: match &envelope.kind {
MessageKind::Message { body, .. } => {
PeerIngressEnvelopeKind::Message { body: body.clone() }
}
MessageKind::Request { intent, params, .. } => {
PeerIngressEnvelopeKind::Request {
intent: intent.clone(),
params: params.clone(),
}
}
MessageKind::Lifecycle { kind, params } => {
PeerIngressEnvelopeKind::Lifecycle {
kind: *kind,
params: params.clone(),
}
}
MessageKind::Response {
in_reply_to,
status,
result,
..
} => PeerIngressEnvelopeKind::Response {
in_reply_to: in_reply_to.to_string(),
status: (*status).into(),
result: result.clone(),
},
MessageKind::Ack { in_reply_to } => PeerIngressEnvelopeKind::Ack {
in_reply_to: in_reply_to.to_string(),
},
},
};
let admission = self.machine_classify_external_envelope(facts)?;
let request_id = parse_admission_interaction_id(admission.request_id.as_deref());
let classification = admission.classification;
let lifecycle_peer = admission.lifecycle_peer.clone();
let convention = match &envelope.kind {
MessageKind::Message { .. } => PeerIngressConvention::Message,
MessageKind::Request { intent, params, .. } => {
if let Some(kind) = classification.lifecycle_kind {
let peer = lifecycle_peer.clone().unwrap_or_else(|| {
meerkat_core::peer_lifecycle_subject(params, from_name.as_str())
});
PeerIngressConvention::Lifecycle { kind, peer }
} else {
PeerIngressConvention::Request {
request_id: admission
.request_id
.clone()
.unwrap_or_else(|| envelope.id.to_string()),
intent: intent.clone(),
}
}
}
MessageKind::Lifecycle { kind, params } => {
let peer = lifecycle_peer.clone().unwrap_or_else(|| {
meerkat_core::peer_lifecycle_subject(params, from_name.as_str())
});
PeerIngressConvention::Lifecycle { kind: *kind, peer }
}
MessageKind::Response {
in_reply_to,
status,
..
} => PeerIngressConvention::Response {
in_reply_to: request_id.unwrap_or(InteractionId(*in_reply_to)),
status: (*status).into(),
},
MessageKind::Ack { in_reply_to } => PeerIngressConvention::Ack {
in_reply_to: request_id.unwrap_or(InteractionId(*in_reply_to)),
},
};
let ingress_fact = PeerIngressFact::peer(
InteractionId(envelope.id),
classification.class,
classification.kind,
Some(classification.auth),
PeerIngressIdentity::new(from_peer_id, from_name.clone(), convention)
.with_signing_pubkey(envelope.from.0),
);
let content_shape = match &envelope.kind {
MessageKind::Message { body, blocks, .. } => {
content_shape_for_text_and_blocks(body, blocks.as_deref())
}
MessageKind::Request { blocks, .. } => {
content_shape_for_text_and_blocks("", blocks.as_deref())
}
MessageKind::Lifecycle { .. }
| MessageKind::Response { .. }
| MessageKind::Ack { .. } => PeerContentShape::Text,
};
Some(PreparedIngressItem {
raw_item_id: InteractionId(envelope.id),
kind: classification.kind,
class: classification.class,
auth: classification.auth,
trusted_sender,
from_peer: Some(from_name),
lifecycle_peer,
response_terminality: classification.response_terminality,
text_projection: admission.rendered_text,
ingress_fact,
content_shape,
request_id,
item: InboxItem::External { envelope },
})
}
InboxItem::PlainEvent {
body,
source,
handling_mode,
interaction_id,
blocks,
render_metadata,
} => {
let interaction_id = interaction_id.unwrap_or_else(Uuid::new_v4);
let facts = PeerIngressPlainEventFacts {
source_name: source.to_string(),
body: body.clone(),
};
let admission = self.machine_classify_plain_event(facts)?;
let classification = admission.classification;
let content_shape = content_shape_for_text_and_blocks(&body, blocks.as_deref());
let ingress_fact = PeerIngressFact::plain_event(
meerkat_core::InteractionId(interaction_id),
source.to_string(),
classification.class,
classification.kind,
);
Some(PreparedIngressItem {
raw_item_id: InteractionId(interaction_id),
kind: classification.kind,
class: classification.class,
auth: classification.auth,
trusted_sender: true,
from_peer: None,
lifecycle_peer: None,
response_terminality: classification.response_terminality,
text_projection: admission.rendered_text,
ingress_fact,
content_shape,
request_id: None,
item: InboxItem::PlainEvent {
body,
source,
handling_mode,
interaction_id: Some(interaction_id),
blocks,
render_metadata,
},
})
}
}
}
#[cfg(test)]
pub(crate) fn classify(&self, item: &InboxItem) -> Option<ClassificationResult> {
self.prepare(item.clone()).and_then(|prepared| {
let drop_untrusted_external = matches!(prepared.item, InboxItem::External { .. })
&& self.require_peer_auth
&& !prepared.trusted_sender
&& !prepared.auth.is_exempt();
if drop_untrusted_external {
return None;
}
Some(ClassificationResult {
class: prepared.class,
auth: prepared.auth,
from_peer: prepared.from_peer,
from_peer_id: prepared.ingress_fact.canonical_peer_id_string(),
lifecycle_peer: prepared.lifecycle_peer,
response_terminality: prepared.response_terminality,
})
})
}
}
#[cfg(test)]
#[allow(
clippy::expect_used,
clippy::panic,
clippy::redundant_clone,
clippy::unwrap_used
)]
mod tests {
use super::*;
use crate::identity::{Keypair, PubKey, Signature};
use crate::trust::TrustedPeer;
use crate::types::Envelope;
use std::sync::atomic::{AtomicUsize, Ordering};
use uuid::Uuid;
struct RecordingPeerCommsHandle {
external_calls: AtomicUsize,
plain_calls: AtomicUsize,
reject_external: bool,
reject_plain: bool,
policy: PeerIngressMachinePolicy,
external_override: Option<PeerIngressAdmission>,
plain_override: Option<PeerIngressAdmission>,
}
impl RecordingPeerCommsHandle {
fn accepting() -> Arc<Self> {
Self::accepting_with_silent(std::iter::empty::<&str>())
}
fn accepting_with_silent<I, S>(silent_intents: I) -> Arc<Self>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Arc::new(Self {
external_calls: AtomicUsize::new(0),
plain_calls: AtomicUsize::new(0),
reject_external: false,
reject_plain: false,
policy: PeerIngressMachinePolicy::from_silent_intents(silent_intents),
external_override: None,
plain_override: None,
})
}
fn rejecting_external() -> Arc<Self> {
Arc::new(Self {
external_calls: AtomicUsize::new(0),
plain_calls: AtomicUsize::new(0),
reject_external: true,
reject_plain: false,
policy: PeerIngressMachinePolicy::default(),
external_override: None,
plain_override: None,
})
}
fn rejecting_plain() -> Arc<Self> {
Arc::new(Self {
external_calls: AtomicUsize::new(0),
plain_calls: AtomicUsize::new(0),
reject_external: false,
reject_plain: true,
policy: PeerIngressMachinePolicy::default(),
external_override: None,
plain_override: None,
})
}
fn accepting_with_external_override(admission: PeerIngressAdmission) -> Arc<Self> {
Arc::new(Self {
external_calls: AtomicUsize::new(0),
plain_calls: AtomicUsize::new(0),
reject_external: false,
reject_plain: false,
policy: PeerIngressMachinePolicy::default(),
external_override: Some(admission),
plain_override: None,
})
}
fn external_calls(&self) -> usize {
self.external_calls.load(Ordering::SeqCst)
}
fn plain_calls(&self) -> usize {
self.plain_calls.load(Ordering::SeqCst)
}
}
impl meerkat_core::handles::PeerCommsHandle for RecordingPeerCommsHandle {
fn classify_external_envelope(
&self,
facts: PeerIngressEnvelopeFacts,
) -> Result<PeerIngressAdmission, meerkat_core::handles::DslTransitionError> {
self.external_calls.fetch_add(1, Ordering::SeqCst);
if self.reject_external {
Err(meerkat_core::handles::DslTransitionError::guard_rejected(
"test_peer_comms::classify_external_envelope",
"machine rejected external ingress",
))
} else {
Ok(self
.external_override
.clone()
.unwrap_or_else(|| self.policy.classify_external_envelope(&facts)))
}
}
fn classify_plain_event(
&self,
facts: PeerIngressPlainEventFacts,
) -> Result<PeerIngressAdmission, meerkat_core::handles::DslTransitionError> {
self.plain_calls.fetch_add(1, Ordering::SeqCst);
if self.reject_plain {
Err(meerkat_core::handles::DslTransitionError::guard_rejected(
"test_peer_comms::classify_plain_event",
"machine rejected plain ingress",
))
} else {
Ok(self
.plain_override
.clone()
.unwrap_or_else(|| self.policy.classify_plain_event_facts(&facts)))
}
}
fn set_peer_ingress_context(
&self,
_keep_alive: bool,
) -> Result<(), meerkat_core::handles::DslTransitionError> {
Ok(())
}
}
fn make_keypair() -> Keypair {
Keypair::generate()
}
fn make_context(
require_peer_auth: bool,
trusted_peers: TrustedPeers,
silent_intents: Vec<&str>,
) -> IngressClassificationContext {
make_context_with_machine_and_namespace(
require_peer_auth,
trusted_peers,
silent_intents,
None,
None,
)
}
fn make_context_with_namespace(
require_peer_auth: bool,
trusted_peers: TrustedPeers,
silent_intents: Vec<&str>,
inproc_namespace: Option<String>,
) -> IngressClassificationContext {
make_context_with_machine_and_namespace(
require_peer_auth,
trusted_peers,
silent_intents,
None,
inproc_namespace,
)
}
fn make_context_with_machine(
require_peer_auth: bool,
trusted_peers: TrustedPeers,
silent_intents: Vec<&str>,
peer_comms_handle: Option<Arc<dyn meerkat_core::handles::PeerCommsHandle>>,
) -> IngressClassificationContext {
make_context_with_machine_and_namespace(
require_peer_auth,
trusted_peers,
silent_intents,
peer_comms_handle,
None,
)
}
fn make_context_with_machine_and_namespace(
require_peer_auth: bool,
trusted_peers: TrustedPeers,
silent_intents: Vec<&str>,
peer_comms_handle: Option<Arc<dyn meerkat_core::handles::PeerCommsHandle>>,
inproc_namespace: Option<String>,
) -> IngressClassificationContext {
IngressClassificationContext {
require_peer_auth,
trusted_peers: Arc::new(parking_lot::RwLock::new(trusted_peers)),
ingress_policy: Arc::new(PeerIngressMachinePolicy::from_silent_intents(
silent_intents,
)),
peer_comms_handle: Arc::new(parking_lot::RwLock::new(peer_comms_handle)),
require_machine_authority: Arc::new(AtomicBool::new(false)),
inproc_namespace,
}
}
fn make_trusted_peers(name: &str, pubkey: &PubKey) -> TrustedPeers {
TrustedPeers {
peers: vec![TrustedPeer {
name: name.to_string(),
pubkey: *pubkey,
addr: "tcp://127.0.0.1:4200".to_string(),
meta: crate::PeerMeta::default(),
}],
}
}
fn make_envelope(from: &Keypair, kind: MessageKind) -> Envelope {
Envelope {
id: Uuid::new_v4(),
from: from.public_key(),
to: PubKey::new([2u8; 32]),
kind,
sig: Signature::new([0u8; 64]),
}
}
fn facts_for_envelope(from_peer: &str, envelope: &Envelope) -> PeerIngressEnvelopeFacts {
PeerIngressEnvelopeFacts {
item_id: envelope.id.to_string(),
from_peer: from_peer.to_string(),
from_peer_id: envelope.from.to_peer_id(),
kind: match &envelope.kind {
MessageKind::Message { body, .. } => {
PeerIngressEnvelopeKind::Message { body: body.clone() }
}
MessageKind::Request { intent, params, .. } => PeerIngressEnvelopeKind::Request {
intent: intent.clone(),
params: params.clone(),
},
MessageKind::Lifecycle { kind, params } => PeerIngressEnvelopeKind::Lifecycle {
kind: *kind,
params: params.clone(),
},
MessageKind::Response {
in_reply_to,
status,
result,
..
} => PeerIngressEnvelopeKind::Response {
in_reply_to: in_reply_to.to_string(),
status: (*status).into(),
result: result.clone(),
},
MessageKind::Ack { in_reply_to } => PeerIngressEnvelopeKind::Ack {
in_reply_to: in_reply_to.to_string(),
},
},
}
}
fn assert_prepared_matches_admission(
prepared: &PreparedIngressItem,
admission: &PeerIngressAdmission,
) {
assert_eq!(prepared.class, admission.classification.class);
assert_eq!(prepared.kind, admission.classification.kind);
assert_eq!(prepared.auth, admission.classification.auth);
assert_eq!(prepared.lifecycle_peer, admission.lifecycle_peer);
assert_eq!(
prepared.request_id.map(|id| id.to_string()),
admission.request_id
);
assert_eq!(
prepared.response_terminality,
admission.classification.response_terminality
);
assert_eq!(prepared.text_projection, admission.rendered_text);
}
#[test]
fn transport_classifier_uses_machine_admission_output_not_local_policy() {
let sender = make_keypair();
let trusted = make_trusted_peers("sender-agent", &sender.public_key());
let machine_request_id = Uuid::new_v4().to_string();
let override_admission = PeerIngressAdmission {
classification: meerkat_core::PeerIngressClassification::required(
PeerInputClass::SilentRequest,
PeerIngressKind::Request,
),
lifecycle_peer: None,
request_id: Some(machine_request_id),
rendered_text: "machine-rendered-request".to_string(),
};
let machine =
RecordingPeerCommsHandle::accepting_with_external_override(override_admission.clone());
let ctx = make_context_with_machine(
true,
trusted,
vec![],
Some(machine.clone() as Arc<dyn meerkat_core::handles::PeerCommsHandle>),
);
let envelope = make_envelope(
&sender,
MessageKind::Request {
intent: "review".to_string(),
params: serde_json::json!({}),
blocks: None,
handling_mode: None,
},
);
let prepared = ctx
.prepare(InboxItem::External { envelope })
.expect("machine accepted request should prepare");
assert_prepared_matches_admission(&prepared, &override_admission);
assert_eq!(machine.external_calls(), 1);
}
#[test]
fn transport_classifier_parity_matches_machine_policy_for_core_cases() {
let sender = make_keypair();
let trusted = make_trusted_peers("sender-agent", &sender.public_key());
let machine = RecordingPeerCommsHandle::accepting_with_silent(["probe.silent"]);
let ctx = make_context_with_machine(
true,
trusted,
vec![],
Some(machine.clone() as Arc<dyn meerkat_core::handles::PeerCommsHandle>),
);
let policy = PeerIngressMachinePolicy::from_silent_intents(["probe.silent"]);
let response_id = Uuid::new_v4();
let cases = vec![
make_envelope(
&sender,
MessageKind::Message {
blocks: None,
body: "hello".to_string(),
handling_mode: None,
},
),
make_envelope(
&sender,
MessageKind::Request {
intent: "review".to_string(),
params: serde_json::json!({"pr": 42}),
blocks: None,
handling_mode: None,
},
),
make_envelope(
&sender,
MessageKind::Request {
intent: "probe.silent".to_string(),
params: serde_json::json!({}),
blocks: None,
handling_mode: None,
},
),
make_envelope(
&sender,
MessageKind::Request {
intent: "mob.peer_added".to_string(),
params: serde_json::json!({"peer": "worker-1"}),
blocks: None,
handling_mode: None,
},
),
make_envelope(
&sender,
MessageKind::Lifecycle {
kind: meerkat_core::comms::PeerLifecycleKind::PeerRetired,
params: serde_json::json!({"peer": "worker-2"}),
},
),
make_envelope(
&sender,
MessageKind::Request {
intent: meerkat_core::SUPERVISOR_BRIDGE_INTENT.to_string(),
params: serde_json::json!({}),
blocks: None,
handling_mode: None,
},
),
make_envelope(
&sender,
MessageKind::Response {
in_reply_to: response_id,
status: crate::types::Status::Accepted,
result: serde_json::json!(null),
blocks: None,
handling_mode: None,
},
),
make_envelope(
&sender,
MessageKind::Response {
in_reply_to: response_id,
status: crate::types::Status::Completed,
result: serde_json::json!({"ok": true}),
blocks: None,
handling_mode: None,
},
),
make_envelope(
&sender,
MessageKind::Ack {
in_reply_to: response_id,
},
),
];
for envelope in cases {
let expected =
policy.classify_external_envelope(&facts_for_envelope("sender-agent", &envelope));
let prepared = ctx
.prepare(InboxItem::External { envelope })
.expect("machine-accepted ingress should prepare");
assert_prepared_matches_admission(&prepared, &expected);
}
let plain_facts = PeerIngressPlainEventFacts {
source_name: "tcp".to_string(),
body: "plain event".to_string(),
};
let expected_plain = policy.classify_plain_event_facts(&plain_facts);
let prepared_plain = ctx
.prepare(InboxItem::PlainEvent {
blocks: None,
body: plain_facts.body,
source: meerkat_core::PlainEventSource::Tcp,
handling_mode: meerkat_core::types::HandlingMode::Queue,
interaction_id: None,
render_metadata: None,
})
.expect("machine-accepted plain event should prepare");
assert_prepared_matches_admission(&prepared_plain, &expected_plain);
}
#[test]
fn classify_message_as_actionable_message() {
let sender = make_keypair();
let trusted = make_trusted_peers("sender-agent", &sender.public_key());
let ctx = make_context(true, trusted, vec![]);
let envelope = make_envelope(
&sender,
MessageKind::Message {
blocks: None,
body: "hello".to_string(),
handling_mode: None,
},
);
let item = InboxItem::External { envelope };
let result = ctx.classify(&item).expect("should classify");
let expected_peer_id = sender.public_key().to_peer_id().to_string();
assert_eq!(result.class, PeerInputClass::ActionableMessage);
assert_eq!(result.from_peer.as_deref(), Some("sender-agent"));
assert_eq!(
result.from_peer_id.as_deref(),
Some(expected_peer_id.as_str())
);
assert!(result.lifecycle_peer.is_none());
}
#[test]
fn classify_message_prefers_inproc_source_label_for_prompt_display() {
let sender = make_keypair();
let sender_pubkey = sender.public_key();
let canonical_label = sender_pubkey.to_peer_id().to_string();
let source_label = format!("sender-source-{}", Uuid::new_v4().simple());
let namespace = format!("classify-{}", Uuid::new_v4().simple());
let trusted = make_trusted_peers(&canonical_label, &sender_pubkey);
let ctx = make_context_with_namespace(true, trusted, vec![], Some(namespace.clone()));
let (_, inbox_sender) = crate::Inbox::new();
InprocRegistry::global().register_with_meta_in_namespace(
&namespace,
source_label.clone(),
sender_pubkey,
inbox_sender,
crate::PeerMeta::default(),
);
let envelope = make_envelope(
&sender,
MessageKind::Message {
blocks: None,
body: "hello".to_string(),
handling_mode: None,
},
);
let item = InboxItem::External { envelope };
let prepared = ctx.prepare(item).expect("should prepare");
assert!(prepared.trusted_sender);
assert_eq!(prepared.from_peer.as_deref(), Some(source_label.as_str()));
assert_eq!(
prepared.text_projection,
meerkat_core::format_peer_message_projection(&source_label, "hello")
);
assert_eq!(
prepared.ingress_fact.canonical_peer_id_string().as_deref(),
Some(canonical_label.as_str())
);
assert!(InprocRegistry::global().unregister_in_namespace(&namespace, &sender_pubkey));
}
#[test]
fn classify_request_as_actionable_request() {
let sender = make_keypair();
let trusted = make_trusted_peers("sender-agent", &sender.public_key());
let ctx = make_context(true, trusted, vec![]);
let envelope = make_envelope(
&sender,
MessageKind::Request {
intent: "review".to_string(),
params: serde_json::json!({}),
blocks: None,
handling_mode: None,
},
);
let item = InboxItem::External { envelope };
let result = ctx.classify(&item).expect("should classify");
let expected_peer_id = sender.public_key().to_peer_id().to_string();
assert_eq!(result.class, PeerInputClass::ActionableRequest);
assert_eq!(result.from_peer.as_deref(), Some("sender-agent"));
assert_eq!(
result.from_peer_id.as_deref(),
Some(expected_peer_id.as_str())
);
}
#[test]
fn classify_completed_response_as_terminal_response() {
let sender = make_keypair();
let trusted = make_trusted_peers("sender-agent", &sender.public_key());
let ctx = make_context(true, trusted, vec![]);
let envelope = make_envelope(
&sender,
MessageKind::Response {
in_reply_to: Uuid::new_v4(),
status: crate::types::Status::Completed,
result: serde_json::json!({}),
blocks: None,
handling_mode: None,
},
);
let item = InboxItem::External { envelope };
let result = ctx.classify(&item).expect("should classify");
assert_eq!(result.class, PeerInputClass::ResponseTerminal);
let expected_peer_id = sender.public_key().to_peer_id().to_string();
assert_eq!(
result.from_peer_id.as_deref(),
Some(expected_peer_id.as_str())
);
assert_eq!(
result.response_terminality,
Some(TerminalityClass::Terminal {
disposition: meerkat_core::TerminalDisposition::Completed
})
);
}
#[test]
fn classify_ack_as_ack() {
let sender = make_keypair();
let trusted = make_trusted_peers("sender-agent", &sender.public_key());
let ctx = make_context(true, trusted, vec![]);
let envelope = make_envelope(
&sender,
MessageKind::Ack {
in_reply_to: Uuid::new_v4(),
},
);
let item = InboxItem::External { envelope };
let result = ctx.classify(&item).expect("should classify");
assert_eq!(result.class, PeerInputClass::Ack);
}
#[test]
fn classify_peer_added_lifecycle() {
let sender = make_keypair();
let trusted = make_trusted_peers("orchestrator", &sender.public_key());
let ctx = make_context(true, trusted, vec![]);
let envelope = make_envelope(
&sender,
MessageKind::Request {
intent: "mob.peer_added".to_string(),
params: serde_json::json!({"peer": "new-agent"}),
blocks: None,
handling_mode: None,
},
);
let item = InboxItem::External { envelope };
let result = ctx.classify(&item).expect("should classify");
assert_eq!(result.class, PeerInputClass::PeerLifecycleAdded);
assert_eq!(result.lifecycle_peer.as_deref(), Some("new-agent"));
}
#[test]
fn classify_peer_retired_lifecycle() {
let sender = make_keypair();
let trusted = make_trusted_peers("orchestrator", &sender.public_key());
let ctx = make_context(true, trusted, vec![]);
let envelope = make_envelope(
&sender,
MessageKind::Request {
intent: "mob.peer_retired".to_string(),
params: serde_json::json!({"peer": "old-agent"}),
blocks: None,
handling_mode: None,
},
);
let item = InboxItem::External { envelope };
let result = ctx.classify(&item).expect("should classify");
assert_eq!(result.class, PeerInputClass::PeerLifecycleRetired);
assert_eq!(result.lifecycle_peer.as_deref(), Some("old-agent"));
}
#[test]
fn classify_silent_request() {
let sender = make_keypair();
let trusted = make_trusted_peers("sender-agent", &sender.public_key());
let ctx = make_context(true, trusted, vec!["my-silent-intent"]);
let envelope = make_envelope(
&sender,
MessageKind::Request {
intent: "my-silent-intent".to_string(),
params: serde_json::json!({}),
blocks: None,
handling_mode: None,
},
);
let item = InboxItem::External { envelope };
let result = ctx.classify(&item).expect("should classify");
assert_eq!(result.class, PeerInputClass::SilentRequest);
}
#[test]
fn classify_builtin_intent_in_silent_list() {
let sender = make_keypair();
let trusted = make_trusted_peers("sender-agent", &sender.public_key());
let ctx = make_context(true, trusted, vec!["review"]);
let envelope = make_envelope(
&sender,
MessageKind::Request {
intent: "review".to_string(),
params: serde_json::json!({}),
blocks: None,
handling_mode: None,
},
);
let item = InboxItem::External { envelope };
let result = ctx.classify(&item).expect("should classify");
assert_eq!(result.class, PeerInputClass::SilentRequest);
}
#[test]
fn classify_untrusted_sender_auth_required_drops_at_ingress() {
let sender = make_keypair();
let trusted = TrustedPeers::new(); let ctx = make_context(true, trusted, vec![]);
let envelope = make_envelope(
&sender,
MessageKind::Message {
blocks: None,
body: "hello".to_string(),
handling_mode: None,
},
);
let item = InboxItem::External { envelope };
let result = ctx.classify(&item);
assert!(
result.is_none(),
"untrusted input must be dropped at ingress"
);
}
#[test]
fn classify_untrusted_supervisor_bridge_request_remains_admissible() {
let sender = make_keypair();
let trusted = TrustedPeers::new(); let ctx = make_context(true, trusted, vec![]);
let envelope = make_envelope(
&sender,
MessageKind::Request {
intent: meerkat_core::SUPERVISOR_BRIDGE_INTENT.to_string(),
params: serde_json::json!({
"command": "bind_member",
"supervisor": {
"name": "mob/__mob_supervisor__",
"peer_id": sender.public_key().to_peer_id(),
"address": "inproc://mob/__mob_supervisor__"
},
"epoch": 1,
"protocol_version": 1,
"expected_peer_id": "peer-id",
"expected_address": "inproc://peer"
}),
blocks: None,
handling_mode: None,
},
);
let item = InboxItem::External { envelope };
let result = ctx
.classify(&item)
.expect("bind_member should remain admissible");
assert_eq!(result.class, PeerInputClass::ActionableRequest);
assert_eq!(
result.auth,
meerkat_core::PeerIngressAuthDecision::Exempt(
meerkat_core::PeerIngressAuthExemption::SupervisorBridge
)
);
assert_eq!(
result.from_peer,
Some(sender.public_key().to_pubkey_string())
);
let expected_peer_id = sender.public_key().to_peer_id().to_string();
assert_eq!(
result.from_peer_id.as_deref(),
Some(expected_peer_id.as_str())
);
}
#[test]
fn auth_exempt_supervisor_intent_cannot_bypass_machine_rejection() {
let sender = make_keypair();
let machine = RecordingPeerCommsHandle::rejecting_external();
let ctx = make_context_with_machine(
true,
TrustedPeers::new(),
vec![],
Some(machine.clone() as Arc<dyn meerkat_core::handles::PeerCommsHandle>),
);
let envelope = make_envelope(
&sender,
MessageKind::Request {
intent: meerkat_core::SUPERVISOR_BRIDGE_INTENT.to_string(),
params: serde_json::json!({}),
blocks: None,
handling_mode: None,
},
);
let result = ctx.classify(&InboxItem::External { envelope });
assert!(
result.is_none(),
"machine rejection must fail closed before auth exemptions apply"
);
assert_eq!(machine.external_calls(), 1);
}
#[test]
fn runtime_backed_ingress_without_machine_handle_fails_closed() {
let sender = make_keypair();
let trusted = make_trusted_peers("sender-agent", &sender.public_key());
let ctx = make_context(true, trusted, vec!["review"]);
ctx.require_machine_authority.store(true, Ordering::SeqCst);
let envelope = make_envelope(
&sender,
MessageKind::Request {
intent: "review".to_string(),
params: serde_json::json!({}),
blocks: None,
handling_mode: None,
},
);
let result = ctx.classify(&InboxItem::External { envelope });
assert!(
result.is_none(),
"runtime-backed ingress must not silently use standalone compatibility policy"
);
}
#[test]
fn runtime_backed_missing_machine_rejects_auth_lifecycle_response_and_plain_cases() {
let sender = make_keypair();
let trusted = make_trusted_peers("sender-agent", &sender.public_key());
let ctx = make_context(true, trusted, vec!["review"]);
ctx.require_machine_authority.store(true, Ordering::SeqCst);
let response_id = Uuid::new_v4();
let external_cases = [
MessageKind::Request {
intent: meerkat_core::SUPERVISOR_BRIDGE_INTENT.to_string(),
params: serde_json::json!({}),
blocks: None,
handling_mode: None,
},
MessageKind::Request {
intent: "mob.peer_added".to_string(),
params: serde_json::json!({"peer": "new-agent"}),
blocks: None,
handling_mode: None,
},
MessageKind::Response {
in_reply_to: response_id,
status: crate::types::Status::Completed,
result: serde_json::json!({"ok": true}),
blocks: None,
handling_mode: None,
},
];
for kind in external_cases {
let result = ctx.classify(&InboxItem::External {
envelope: make_envelope(&sender, kind),
});
assert!(
result.is_none(),
"runtime-backed ingress must fail closed instead of letting the compatibility classifier decide"
);
}
let plain_result = ctx.classify(&InboxItem::PlainEvent {
blocks: None,
body: "event body".to_string(),
source: meerkat_core::PlainEventSource::Tcp,
handling_mode: meerkat_core::types::HandlingMode::Queue,
interaction_id: None,
render_metadata: None,
});
assert!(
plain_result.is_none(),
"plain event projection must also require machine authority on runtime-backed ingress"
);
}
#[test]
fn machine_result_overrides_local_auth_lifecycle_and_terminality_conventions() {
let sender = make_keypair();
let auth_override = PeerIngressAdmission {
classification: meerkat_core::PeerIngressClassification::required(
PeerInputClass::ActionableRequest,
PeerIngressKind::Request,
),
lifecycle_peer: None,
request_id: None,
rendered_text: "machine-required-supervisor-bridge".to_string(),
};
let auth_machine =
RecordingPeerCommsHandle::accepting_with_external_override(auth_override);
let auth_ctx = make_context_with_machine(
true,
TrustedPeers::new(),
vec![],
Some(auth_machine.clone() as Arc<dyn meerkat_core::handles::PeerCommsHandle>),
);
let auth_result = auth_ctx.classify(&InboxItem::External {
envelope: make_envelope(
&sender,
MessageKind::Request {
intent: meerkat_core::SUPERVISOR_BRIDGE_INTENT.to_string(),
params: serde_json::json!({}),
blocks: None,
handling_mode: None,
},
),
});
assert!(
auth_result.is_none(),
"local supervisor-bridge exemption must not admit when machine returned auth-required"
);
assert_eq!(auth_machine.external_calls(), 1);
let lifecycle_override = PeerIngressAdmission {
classification: meerkat_core::PeerIngressClassification::required(
PeerInputClass::ActionableRequest,
PeerIngressKind::Request,
),
lifecycle_peer: None,
request_id: None,
rendered_text: "machine-actionable-peer-added".to_string(),
};
let lifecycle_machine =
RecordingPeerCommsHandle::accepting_with_external_override(lifecycle_override);
let trusted = make_trusted_peers("orchestrator", &sender.public_key());
let lifecycle_ctx = make_context_with_machine(
true,
trusted,
vec![],
Some(lifecycle_machine.clone() as Arc<dyn meerkat_core::handles::PeerCommsHandle>),
);
let lifecycle = lifecycle_ctx
.classify(&InboxItem::External {
envelope: make_envelope(
&sender,
MessageKind::Request {
intent: "mob.peer_added".to_string(),
params: serde_json::json!({"peer": "new-agent"}),
blocks: None,
handling_mode: None,
},
),
})
.expect("machine-accepted request should classify");
assert_eq!(lifecycle.class, PeerInputClass::ActionableRequest);
assert_eq!(lifecycle.lifecycle_peer, None);
assert_eq!(lifecycle_machine.external_calls(), 1);
let response_override = PeerIngressAdmission {
classification: meerkat_core::PeerIngressClassification {
class: PeerInputClass::ResponseProgress,
kind: PeerIngressKind::Response,
auth: PeerIngressAuthDecision::Required,
lifecycle_kind: None,
response_terminality: Some(TerminalityClass::Progress),
},
lifecycle_peer: None,
request_id: Some(Uuid::new_v4().to_string()),
rendered_text: "machine-progress-response".to_string(),
};
let response_machine =
RecordingPeerCommsHandle::accepting_with_external_override(response_override);
let trusted = make_trusted_peers("sender-agent", &sender.public_key());
let response_ctx = make_context_with_machine(
true,
trusted,
vec![],
Some(response_machine.clone() as Arc<dyn meerkat_core::handles::PeerCommsHandle>),
);
let response = response_ctx
.classify(&InboxItem::External {
envelope: make_envelope(
&sender,
MessageKind::Response {
in_reply_to: Uuid::new_v4(),
status: crate::types::Status::Completed,
result: serde_json::json!({"ok": true}),
blocks: None,
handling_mode: None,
},
),
})
.expect("machine-accepted response should classify");
assert_eq!(response.class, PeerInputClass::ResponseProgress);
assert_eq!(
response.response_terminality,
Some(TerminalityClass::Progress)
);
assert_eq!(response_machine.external_calls(), 1);
}
#[test]
fn classify_untrusted_legacy_bridge_intent_drops_at_ingress() {
let sender = make_keypair();
let trusted = TrustedPeers::new(); let ctx = make_context(true, trusted, vec![]);
let envelope = make_envelope(
&sender,
MessageKind::Request {
intent: "mob.runtime.bind_member".to_string(),
params: serde_json::json!({}),
blocks: None,
handling_mode: None,
},
);
let item = InboxItem::External { envelope };
let result = ctx.classify(&item);
assert!(
result.is_none(),
"legacy bridge intents should no longer bypass auth at ingress"
);
}
#[test]
fn machine_handoff_precedes_silent_routing_and_lifecycle_extraction() {
let sender = make_keypair();
let trusted = make_trusted_peers("orchestrator", &sender.public_key());
let machine = RecordingPeerCommsHandle::accepting_with_silent(["review"]);
let ctx = make_context_with_machine(
true,
trusted,
vec!["review"],
Some(machine.clone() as Arc<dyn meerkat_core::handles::PeerCommsHandle>),
);
let silent = ctx
.classify(&InboxItem::External {
envelope: make_envelope(
&sender,
MessageKind::Request {
intent: "review".to_string(),
params: serde_json::json!({}),
blocks: None,
handling_mode: None,
},
),
})
.expect("machine-accepted silent request should classify");
assert_eq!(silent.class, PeerInputClass::SilentRequest);
let lifecycle = ctx
.classify(&InboxItem::External {
envelope: make_envelope(
&sender,
MessageKind::Request {
intent: "mob.peer_added".to_string(),
params: serde_json::json!({"peer": "new-agent"}),
blocks: None,
handling_mode: None,
},
),
})
.expect("machine-accepted lifecycle request should classify");
assert_eq!(lifecycle.class, PeerInputClass::PeerLifecycleAdded);
assert_eq!(lifecycle.lifecycle_peer.as_deref(), Some("new-agent"));
assert_eq!(machine.external_calls(), 2);
}
#[test]
fn rendered_request_projection_is_created_only_after_machine_accepts() {
let sender = make_keypair();
let trusted = make_trusted_peers("sender-agent", &sender.public_key());
let machine = RecordingPeerCommsHandle::accepting();
let ctx = make_context_with_machine(
true,
trusted,
vec![],
Some(machine.clone() as Arc<dyn meerkat_core::handles::PeerCommsHandle>),
);
let envelope = make_envelope(
&sender,
MessageKind::Request {
intent: "review".to_string(),
params: serde_json::json!({"pr": 42}),
blocks: None,
handling_mode: None,
},
);
let request_id = envelope.id.to_string();
let prepared = ctx
.prepare(InboxItem::External { envelope })
.expect("machine accepted request should prepare");
assert_eq!(prepared.class, PeerInputClass::ActionableRequest);
let prepared_request_id = prepared.request_id.map(|id| id.to_string());
assert_eq!(prepared_request_id.as_deref(), Some(request_id.as_str()));
assert!(prepared.text_projection.starts_with(&format!(
"[COMMS REQUEST from peer_id {} (display_name: sender-agent) (id: {request_id})]\nIntent: review",
sender.public_key().to_peer_id()
)));
assert!(prepared.text_projection.contains("\"peer_id\""));
assert!(!prepared.text_projection.contains("to=\""));
assert_eq!(machine.external_calls(), 1);
}
#[test]
fn classify_plain_event() {
let ctx = make_context(false, TrustedPeers::new(), vec![]);
let item = InboxItem::PlainEvent {
blocks: None,
body: "event".to_string(),
source: meerkat_core::PlainEventSource::Tcp,
handling_mode: meerkat_core::types::HandlingMode::Queue,
interaction_id: None,
render_metadata: None,
};
let result = ctx.classify(&item).expect("should classify");
assert_eq!(result.class, PeerInputClass::PlainEvent);
assert!(result.from_peer.is_none());
}
#[test]
fn plain_event_projection_requires_machine_classification() {
let machine = RecordingPeerCommsHandle::accepting();
let ctx = make_context_with_machine(
false,
TrustedPeers::new(),
vec![],
Some(machine.clone() as Arc<dyn meerkat_core::handles::PeerCommsHandle>),
);
let prepared = ctx
.prepare(InboxItem::PlainEvent {
blocks: None,
body: "event body".to_string(),
source: meerkat_core::PlainEventSource::Tcp,
handling_mode: meerkat_core::types::HandlingMode::Queue,
interaction_id: None,
render_metadata: None,
})
.expect("machine accepted plain event should prepare");
assert_eq!(prepared.class, PeerInputClass::PlainEvent);
assert_eq!(prepared.text_projection, "[EVENT via tcp] event body");
assert_eq!(machine.plain_calls(), 1);
}
#[test]
fn plain_event_machine_rejection_fails_closed() {
let machine = RecordingPeerCommsHandle::rejecting_plain();
let ctx = make_context_with_machine(
false,
TrustedPeers::new(),
vec![],
Some(machine.clone() as Arc<dyn meerkat_core::handles::PeerCommsHandle>),
);
let prepared = ctx.prepare(InboxItem::PlainEvent {
blocks: None,
body: "event body".to_string(),
source: meerkat_core::PlainEventSource::Tcp,
handling_mode: meerkat_core::types::HandlingMode::Queue,
interaction_id: None,
render_metadata: None,
});
assert!(
prepared.is_none(),
"machine rejection must fail closed before plain-event routing"
);
assert_eq!(machine.plain_calls(), 1);
}
#[test]
fn no_lifecycle_leakage_plain_events_remain_the_only_non_peer_inbox_class() {
let ctx = make_context(false, TrustedPeers::new(), vec![]);
let item = InboxItem::PlainEvent {
body: "event".to_string(),
source: meerkat_core::PlainEventSource::Tcp,
handling_mode: meerkat_core::types::HandlingMode::Queue,
interaction_id: None,
blocks: None,
render_metadata: None,
};
let result = ctx.classify(&item).expect("should classify");
assert_eq!(result.class, PeerInputClass::PlainEvent);
}
#[test]
fn classify_lifecycle_without_peer_param_falls_back_to_sender() {
let sender = make_keypair();
let trusted = make_trusted_peers("orchestrator", &sender.public_key());
let ctx = make_context(true, trusted, vec![]);
let envelope = make_envelope(
&sender,
MessageKind::Request {
intent: "mob.peer_added".to_string(),
params: serde_json::json!({}),
blocks: None,
handling_mode: None,
},
);
let item = InboxItem::External { envelope };
let result = ctx.classify(&item).expect("should classify");
assert_eq!(result.class, PeerInputClass::PeerLifecycleAdded);
assert_eq!(result.lifecycle_peer.as_deref(), Some("orchestrator"));
}
#[test]
fn prepare_plain_event_assigns_interaction_id_and_projection() {
let ctx = make_context(false, TrustedPeers::new(), vec![]);
let prepared = ctx
.prepare(InboxItem::PlainEvent {
body: "event body".to_string(),
source: meerkat_core::PlainEventSource::Tcp,
handling_mode: meerkat_core::types::HandlingMode::Queue,
interaction_id: None,
blocks: None,
render_metadata: None,
})
.expect("plain event should prepare");
assert_eq!(prepared.class, PeerInputClass::PlainEvent);
assert_eq!(prepared.kind, PeerIngressKind::PlainEvent);
assert!(
Uuid::parse_str(&prepared.raw_item_id.to_string()).is_ok(),
"plain events should receive a stable generated ingress id"
);
assert_eq!(
prepared.text_projection,
"[EVENT via tcp] event body".to_string()
);
match prepared.item {
InboxItem::PlainEvent { interaction_id, .. } => {
assert_eq!(
interaction_id.map(meerkat_core::InteractionId),
Some(prepared.raw_item_id)
);
}
other => panic!("expected normalized plain event, got {other:?}"),
}
}
}