fix(core): ack only observations the store kept

Exchange::accept enqueued a pending ack whenever
!(duplicate && addressed && released) held, even when the backend
store.insert returned Err (store full / backend error). On Err the
released flag computed to false, so an ack was still enqueued: a
receiver that did not keep the message acked it. For an addressed
offer that releases the sender's custody for a message nobody holds;
for a broadcast it fires a false confidence event at the sender.

Gate the ack enqueue on res.is_ok(): enqueue only when the store
actually retained the observation. The existing released-holder
exception still applies among kept copies. No bounce is recorded on
Err (already the case).

Adds a unit test exercising a capped store (StoreError::Full) for
both a broadcast and an addressed offer, and a tines-sim property
that drives custody with CappedStore-backed nodes and asserts the
"at least one holder" invariant still holds when stores fill.
This commit is contained in:
2026-10-19 12:35:17 -07:00
parent 836ab74e81
commit 2738de4959
5 changed files with 299 additions and 14 deletions
+18 -3
View File
@@ -19,15 +19,30 @@ use tines_core::{MemStore, Node, NodeId, ObservationId, Store};
pub type SimNode = Node<MemStore, SimTransport>;
/// A run of the simulation, owned by the test.
pub struct Sim {
///
/// Generic over the store backend `S` so tests can drive custody with a store
/// that fills (see [`CappedStore`](crate::CappedStore)); the default is the
/// unbounded [`MemStore`].
pub struct Sim<S = MemStore>
where
S: Store,
{
pub world: Rc<RefCell<SimWorld>>,
pub nodes: Vec<SimNode>,
pub nodes: Vec<Node<S, SimTransport>>,
pub t: u64,
pub step_ms: u64,
}
impl Sim {
/// A run over `nodes` nodes, each with its own unbounded [`MemStore`].
pub fn new(cfg: WorldConfig, trace: ContactTrace) -> Sim {
Sim::with_store(cfg, trace)
}
}
impl<S: Store + Default> Sim<S> {
/// A run over `nodes` nodes, each given a fresh `S::default()` store.
pub fn with_store(cfg: WorldConfig, trace: ContactTrace) -> Sim<S> {
let n = cfg.nodes;
let world = SimWorld::new(cfg, trace);
let mut nodes = Vec::with_capacity(n);
@@ -35,7 +50,7 @@ impl Sim {
nodes.push(Node::new(
[i as u8; 16],
0,
MemStore::new(),
S::default(),
SimTransport::new(world.clone(), i),
));
}