flybrain/services/flysim/crates/flybus/tests/conformance_artifacts.rs
dev f2dc5d434a flybus: assert the latest-mode guarantee, not the machine's timing
session_over_one_router asserted that a latest subscriber must drop
snapshots. bus-v1 section 7 says a latest subscription replaces an
undelivered value; it lets a consumer miss values, it does not oblige it
to. Under contention the publisher was slow enough that the renderer kept
up, saw all twenty snapshots and the test failed on conforming behaviour:
16 of 40 runs beside four busy loops, and 9 of 20 whole-crate runs.

The renderer is now held until the publisher's completion is observed
rather than until a timer expires, so the coalescing is forced instead of
raced for: the subscription keeps the one delivery in flight and one
replaceable queued value, and the renderer receives snapshots 1 and 20 of
20. The assertions are the guarantees that hold -- what arrives is in
publication order, the last value is the latest published, the stalled
spectator never refuses a publication or drops out of the fan-out, and
every snapshot the renderer missed is counted as a replacement to the
publisher at admission and to the renderer on delivery, so nothing is
lost silently.

Three more tests in the crate asserted the same kind of race:

- latest_replay_is_ordered_ahead_of_a_racing_publish demanded the
  non-coalesced outcome of a race section 7 allows either way ("bounded
  mode preserves that order, while latest mode may coalesce it"). It now
  puts one racing publication to a bounded and a latest subscription at
  once: bounded must deliver the replay and then the publication, and the
  latest branch is chosen by that publication's own replaced count.

- collection_waits_for_every_retained_owner, and the two disconnect
  cleanup tests beside it, read the store directory for the unlink that
  follows the registry update outside the router lock. They use
  settle_files, like every other unlink check in the suite.

- The demo example printed a root count taken before the producer's own
  release had reached the router, so the line the guide quotes was a
  race. It waits for the release, the same way it already waits for
  collection; the printed output is unchanged.

Nothing under flybus/src is touched: no routing defect was found. The
conformance rows for the credit/queue split, the replaced count, the
replay ordering and the latest spectator that cannot refuse a publication
now cite what each rewritten test actually varies.
2026-09-22 17:05:25 +00:00

790 lines
27 KiB
Rust

//! Black-box conformance tests for artifact ownership (bus-v1 section 8): allocate/seal/open,
//! immutability past a stale writable handle, ownership surviving message drop, explicit
//! retain/release, final-owner collection, root accounting across queued/latest/retained
//! delivery, forward-before-release ordering, admission rollback, disconnect cleanup, and
//! rejection of stale or forged references and owners. Every test runs over both transports.
//!
//! These tests use only the public `flybus` API (plus the `common::Raw` protocol harness for
//! adversarial frames no well-behaved SDK client would send) and generic bytes over synthetic
//! messages; nothing here depends on any application on top of the bus.
mod common;
use std::io::Write;
use common::{Via, code, env, env_with, obj, sealed, within};
use flybus::{ErrorCode, Limits, Policy, Retained, ServiceConfig, SubscriptionConfig};
use serde_json::{Value, json};
// -------------------------------------------------------------------------------------------
// Allocate / seal / open
async fn allocate_write_seal_open_roundtrip_and_mismatches(via: Via) {
let e = env(via).await;
let c = e.client("c").await;
let mut w = c.artifacts().allocate(5, "text/plain").await.unwrap();
assert_eq!(w.byte_length(), 5);
w.write_all(b"hello").unwrap();
assert!(
w.write(b"!").is_err(),
"a write past the declared length must fail locally"
);
let art = w.seal().await.unwrap();
assert_eq!(art.reference().byte_length, 5);
assert_eq!(art.reference().content_type, "text/plain");
assert_eq!(art.read_all().await.unwrap(), b"hello");
drop(art);
e.settle("sealed object collected", |s| s.sealed_artifacts == 0)
.await;
// Sealing declares more bytes than were actually staged: rejected, not silently truncated.
let mut raw = e.raw_hello("raw").await;
let (alloc, path) = raw.allocate(10).await;
std::fs::write(&path, b"short").unwrap();
assert_eq!(
code(&raw.seal(&alloc, Value::Null).await),
"ARTIFACT_MISMATCH"
);
// A syntactically valid but wrong digest is refused too.
let (alloc2, path2) = raw.allocate(4).await;
std::fs::write(&path2, b"data").unwrap();
assert_eq!(
code(&raw.seal(&alloc2, json!("0".repeat(64))).await),
"ARTIFACT_MISMATCH"
);
}
/// Sealing copies staging into a fresh inode; a producer's writable handle that survives the
/// seal (or a duplicate of it) can only reach the abandoned staging file, never the sealed
/// bytes (bus-v1 section 8.1 and the store module's own contract).
async fn seal_is_immutable_despite_a_stale_writable_handle(via: Via) {
let e = env(via).await;
let mut raw = e.raw_hello("producer").await;
let (alloc, path) = raw.allocate(7).await;
let mut stale = std::fs::OpenOptions::new().write(true).open(&path).unwrap();
stale.write_all(b"before1").unwrap();
stale.flush().unwrap();
let sealed_reply = raw.seal(&alloc, Value::Null).await.unwrap();
// Tamper through the handle only after the router has answered the seal: the sealed copy
// already exists and the staging file is already unlinked.
stale.write_all(b"AFTER!!").unwrap();
let open_reply = raw
.call(
"artifact.open",
json!({"ref": sealed_reply["ref"], "ownerId": sealed_reply["ownerId"]}),
)
.await
.unwrap();
let sealed_path = raw.path(&open_reply["readLocation"]);
assert_eq!(
std::fs::read(sealed_path).unwrap(),
b"before1",
"a post-seal write must never reach the sealed copy"
);
}
// -------------------------------------------------------------------------------------------
// Ownership surviving drop, explicit retain/release, final-owner collection
/// The doc's own illustrative snippet (bus-v1 section 2): `drop(message)` alone must not
/// consume the delivery while an artifact extracted from it is still held.
async fn extracted_artifact_outlives_the_message_it_came_from(via: Via) {
let e = env(via).await;
let camera = e.client("camera").await;
let viewer = e.client("viewer").await;
camera
.declare_topic("world.demo.frame", Retained::None)
.await
.unwrap();
let mut sub = viewer
.subscribe("world.demo.frame", SubscriptionConfig::bounded())
.await
.unwrap();
let frame = sealed(&camera, b"pixels", "image/x-rgba").await;
camera
.publish("world.demo.frame", obj(json!({})), &[("frame", &frame)])
.await
.unwrap();
drop(frame);
let message = within("frame delivered", sub.next()).await.unwrap();
let image = message.artifact("frame").unwrap();
drop(message);
e.settle("the delivery still owns its artifact", |s| {
s.owners == 1 && s.sealed_artifacts == 1
})
.await;
assert_eq!(
image.read_all().await.unwrap(),
b"pixels",
"the surviving handle still reads fine"
);
drop(image);
e.settle("dropping the last handle finally consumes it", |s| {
s.owners == 0 && s.sealed_artifacts == 0
})
.await;
}
async fn explicit_retain_outlives_the_original_hold(via: Via) {
let e = env(via).await;
let c = e.client("c").await;
let original = sealed(&c, b"payload", "application/octet-stream").await;
let retained = original.retain().await.unwrap();
assert_eq!(
retained.reference().artifact_id,
original.reference().artifact_id
);
assert_ne!(
retained.owner_id(),
original.owner_id(),
"retain creates an independent owner"
);
drop(original);
e.settle("the retained hold keeps it sealed", |s| {
s.sealed_artifacts == 1 && s.artifact_roots == 1
})
.await;
assert_eq!(retained.read_all().await.unwrap(), b"payload");
drop(retained);
e.settle("collected once the last owner drops", |s| {
s.sealed_artifacts == 0 && s.owners == 0
})
.await;
}
async fn collection_waits_for_every_retained_owner(via: Via) {
let e = env(via).await;
let c = e.client("c").await;
let a = sealed(&c, b"payload", "application/octet-stream").await;
let h1 = a.retain().await.unwrap();
let h2 = a.retain().await.unwrap();
drop(a);
e.settle("two independent holds remain", |s| {
s.owners == 2 && s.sealed_artifacts == 1
})
.await;
drop(h1);
e.settle("one hold remains", |s| {
s.owners == 1 && s.sealed_artifacts == 1
})
.await;
drop(h2);
e.settle("the final owner's drop collects it", |s| {
s.owners == 0 && s.sealed_artifacts == 0 && s.store_bytes == 0
})
.await;
// The unlink follows the registry update, outside the router lock: wait for the file to
// go rather than assume the two happen together.
e.settle_files("sealed", 0).await;
}
// -------------------------------------------------------------------------------------------
// Root accounting across queued, latest and retained delivery (bus-v1 section 8.2)
/// A message queued behind a saturated credit already holds a root at admission time, before
/// it is ever dispatched to the client; dispatch transfers that root into an owner, it does
/// not add a second one.
async fn queued_deliveries_hold_roots_before_dispatch(via: Via) {
let e = env(via).await;
let p = e.client("p").await;
let r = e.client("r").await;
p.declare_topic("t.queued", Retained::None).await.unwrap();
let mut sub = r
.subscribe(
"t.queued",
SubscriptionConfig::bounded().in_flight(1).queued(4),
)
.await
.unwrap();
let a1 = sealed(&p, b"m1", "application/octet-stream").await;
let a2 = sealed(&p, b"m2", "application/octet-stream").await;
p.publish("t.queued", obj(json!({})), &[("x", &a1)])
.await
.unwrap();
p.publish("t.queued", obj(json!({})), &[("x", &a2)])
.await
.unwrap();
drop((a1, a2));
e.settle("one dispatched owner, two roots", |s| {
s.artifact_roots == 2 && s.owners == 1 && s.sealed_artifacts == 2
})
.await;
let held = within("dispatched message", sub.next()).await.unwrap();
assert_eq!(held.artifact("x").unwrap().read_all().await.unwrap(), b"m1");
drop(held);
let second = within("previously queued message", sub.next())
.await
.unwrap();
assert_eq!(
second.artifact("x").unwrap().read_all().await.unwrap(),
b"m2"
);
e.settle("dispatch moved the root, it did not add one", |s| {
s.artifact_roots == 1 && s.owners == 1
})
.await;
drop(second);
e.settle("fully collected", |s| {
s.artifact_roots == 0 && s.owners == 0 && s.sealed_artifacts == 0
})
.await;
}
/// `latest` never queues more than one undelivered value; coalescing releases the replaced
/// root immediately, and the already-dispatched value stays independent of it.
async fn latest_mode_holds_at_most_two_roots_delivered_plus_queued(via: Via) {
let e = env(via).await;
let p = e.client("p").await;
let r = e.client("r").await;
p.declare_topic("t.latest", Retained::None).await.unwrap();
let mut sub = r
.subscribe("t.latest", SubscriptionConfig::latest().in_flight(1))
.await
.unwrap();
for i in 0..3u8 {
let a = sealed(&p, &[i], "application/octet-stream").await;
p.publish("t.latest", obj(json!({})), &[("x", &a)])
.await
.unwrap();
}
// i=0 was already dispatched (an owner); i=1 was coalesced away by i=2 while still queued.
e.settle(
"a delivered root plus one coalesced-survivor root, no more",
|s| s.artifact_roots == 2 && s.owners == 1 && s.sealed_artifacts == 2,
)
.await;
let first = within("delivered", sub.next()).await.unwrap();
assert_eq!(first.artifact("x").unwrap().read_all().await.unwrap(), &[0]);
drop(first);
let last = within("coalesced survivor", sub.next()).await.unwrap();
assert_eq!(last.artifact("x").unwrap().read_all().await.unwrap(), &[2]);
drop(last);
e.settle("fully collected", |s| {
s.artifact_roots == 0 && s.owners == 0 && s.sealed_artifacts == 0
})
.await;
}
/// A retained topic value is a root the router itself keeps; it has no per-connection owner,
/// so nothing about a subscriber connecting, reading or disconnecting can move it.
async fn retained_topic_value_holds_a_root_independent_of_subscribers(via: Via) {
let e = env(via).await;
let p = e.client("p").await;
p.declare_topic("t.retained", Retained::Latest)
.await
.unwrap();
let a = sealed(&p, b"snapshot", "application/octet-stream").await;
let r = p
.publish("t.retained", obj(json!({})), &[("x", &a)])
.await
.unwrap();
assert_eq!(r.subscribers, 0);
drop(a);
e.settle("a retained root with no owner", |s| {
s.artifact_roots == 1 && s.owners == 0 && s.sealed_artifacts == 1
})
.await;
assert!(p.clear_topic("t.retained").await.unwrap());
e.settle("clearing releases the retained root", |s| {
s.artifact_roots == 0 && s.sealed_artifacts == 0
})
.await;
}
// -------------------------------------------------------------------------------------------
// Forward before release (bus-v1 section 8.2: destination roots before source release)
/// A forward or reply must use a still-live source owner; once a delivery is released its
/// artifacts cannot source a new admission, but forwarding *before* releasing is exactly what
/// keeps bytes alive once the original delivery finally goes.
async fn forward_requires_the_source_owner_still_live(via: Via) {
let e = env(via).await;
let caller = e.client("caller").await;
let consumer = e.client("consumer").await;
consumer
.declare_topic("t.relay", Retained::None)
.await
.unwrap();
let mut sub = consumer
.subscribe("t.relay", SubscriptionConfig::bounded())
.await
.unwrap();
let mut server = e.raw_hello("server").await;
assert_eq!(
code(
&server
.call(
"service.register",
json!({"name": "agent.relay", "maxQueued": 4, "maxInFlight": 4})
)
.await
),
"OK"
);
// Round 1: release the request delivery, then try to forward using its stale owner.
let art1 = sealed(&caller, b"payload-1", "application/octet-stream").await;
let _c1 = caller
.call(
"agent.relay",
None,
"Relay",
obj(json!({})),
&[("in", &art1)],
)
.await
.unwrap();
let req1 = within("request 1", server.event()).await;
let delivery1 = req1.body["deliveryId"].as_str().unwrap().to_owned();
let att1 = req1.attachments[0].clone();
assert_eq!(
code(
&server
.call("delivery.consumed", json!({"deliveryIds": [delivery1]}))
.await
),
"OK"
);
e.settle("the request delivery is released", |s| s.owners == 1)
.await; // only art1's own hold remains
let refused = server
.call_with(
"publish",
json!({"topic": "t.relay", "payload": {}}),
json!([{"name": "out", "ref": att1.reference.to_json(), "ownerId": att1.owner_id}]),
)
.await;
assert_eq!(
code(&refused),
"OWNER_INVALID",
"a released delivery cannot source a forward"
);
drop(art1);
// Round 2: forward while the request delivery is still live, then release it.
let art2 = sealed(&caller, b"payload-2", "application/octet-stream").await;
let _c2 = caller
.call(
"agent.relay",
None,
"Relay",
obj(json!({})),
&[("in", &art2)],
)
.await
.unwrap();
let req2 = within("request 2", server.event()).await;
let delivery2 = req2.body["deliveryId"].as_str().unwrap().to_owned();
let att2 = req2.attachments[0].clone();
let forwarded = server
.call_with(
"publish",
json!({"topic": "t.relay", "payload": {}}),
json!([{"name": "out", "ref": att2.reference.to_json(), "ownerId": att2.owner_id}]),
)
.await
.unwrap();
assert_eq!(forwarded["subscribers"], "1");
assert_eq!(
code(
&server
.call("delivery.consumed", json!({"deliveryIds": [delivery2]}))
.await
),
"OK"
);
drop(art2);
let msg = within("relayed", sub.next()).await.unwrap();
assert_eq!(
msg.artifact("out").unwrap().read_all().await.unwrap(),
b"payload-2"
);
}
// -------------------------------------------------------------------------------------------
// Failed admission rollback (bus-v1 section 6/7: rejection establishes no root)
async fn rejected_publish_creates_no_roots(via: Via) {
let e = env(via).await;
let p = e.client("p").await;
let r = e.client("r").await;
p.declare_topic("t.rollback", Retained::None).await.unwrap();
let mut sub = r
.subscribe(
"t.rollback",
SubscriptionConfig::bounded().queued(1).in_flight(1),
)
.await
.unwrap();
p.publish("t.rollback", obj(json!({})), &[]).await.unwrap();
let held = within("first delivered", sub.next()).await.unwrap(); // consumes the one in-flight credit
p.publish("t.rollback", obj(json!({})), &[]).await.unwrap(); // fills the one queue slot
let art = sealed(&p, b"payload", "application/octet-stream").await;
let before = e.stats();
let refused = p
.publish("t.rollback", obj(json!({})), &[("x", &art)])
.await
.unwrap_err();
assert_eq!(refused.code, ErrorCode::Backpressure);
assert_eq!(
e.stats(),
before,
"a refused publish must not touch any existing root, owner or byte count"
);
drop(held);
p.declare_topic("t.rollback.ok", Retained::None)
.await
.unwrap();
let receipt = p
.publish("t.rollback.ok", obj(json!({})), &[("x", &art)])
.await
.unwrap();
assert_eq!(
receipt.subscribers, 0,
"the untouched artifact is still usable after the rejection"
);
}
async fn rejected_call_creates_no_roots(via: Via) {
let e = env(via).await;
let server = e.client("server").await;
let caller = e.client("caller").await;
let mut svc = server
.register(
"agent.rollback",
ServiceConfig {
max_queued: 1,
max_in_flight: 1,
},
)
.await
.unwrap();
let _c1 = caller
.call("agent.rollback", None, "Work", obj(json!({})), &[])
.await
.unwrap();
let dispatched = within("dispatched", svc.next()).await.unwrap();
let _c2 = caller
.call("agent.rollback", None, "Work", obj(json!({})), &[])
.await
.unwrap(); // fills the queue
let art = sealed(&caller, b"payload", "application/octet-stream").await;
let before = e.stats();
let refused = caller
.call(
"agent.rollback",
None,
"Work",
obj(json!({})),
&[("x", &art)],
)
.await
.unwrap_err();
assert_eq!(refused.code, ErrorCode::Backpressure);
assert_eq!(
e.stats(),
before,
"a refused call must not touch any existing root, owner or byte count"
);
drop(dispatched);
}
// -------------------------------------------------------------------------------------------
// Disconnect cleanup (bus-v1 section 8.4)
/// An abrupt disconnect (no `artifact.release` ever sent) must still abandon an unsealed
/// writer's staging reservation.
async fn disconnect_abandons_an_unsealed_writer(via: Via) {
let e = env(via).await;
{
let mut raw = e.raw_hello("producer").await;
let (_alloc, path) = raw.allocate(64).await;
std::fs::write(&path, [7u8; 64]).unwrap();
e.settle("writer visible", |s| {
s.artifacts == 1 && s.store_bytes == 64
})
.await;
// `raw` drops here without ever releasing anything.
}
e.settle("disconnect abandoned the writer", |s| {
s.artifacts == 0 && s.store_bytes == 0 && s.owners == 0
})
.await;
e.settle_files("staging", 0).await;
}
/// An abrupt disconnect must release an explicit hold too, when it was the object's only root.
async fn disconnect_releases_an_explicit_hold(via: Via) {
let e = env(via).await;
{
let mut raw = e.raw_hello("producer").await;
let (alloc, path) = raw.allocate(5).await;
std::fs::write(&path, b"hello").unwrap();
let reply = raw.seal(&alloc, Value::Null).await.unwrap();
assert!(reply.contains_key("ref"));
}
e.settle("disconnect released the only hold", |s| {
s.sealed_artifacts == 0 && s.owners == 0 && s.store_bytes == 0
})
.await;
e.settle_files("sealed", 0).await;
}
/// A vanished subscriber must give up both a delivery it already holds and one still queued
/// behind it, never issued to the wire.
async fn disconnect_releases_queued_and_dispatched_deliveries(via: Via) {
let e = env(via).await;
let p = e.client("p").await;
p.declare_topic("t.gone", Retained::None).await.unwrap();
{
let mut raw = e.raw_hello("subscriber").await;
assert_eq!(
code(&raw.call("subscribe", json!({"topic": "t.gone", "mode": "bounded", "maxQueued": 4, "maxInFlight": 1, "replayLatest": false})).await),
"OK"
);
let a1 = sealed(&p, b"m1", "application/octet-stream").await;
let a2 = sealed(&p, b"m2", "application/octet-stream").await;
p.publish("t.gone", obj(json!({})), &[("x", &a1)])
.await
.unwrap();
p.publish("t.gone", obj(json!({})), &[("x", &a2)])
.await
.unwrap();
drop((a1, a2));
let delivered = within("dispatched delivery", raw.event()).await;
assert_eq!(delivered.op, "topic.message");
e.settle("one dispatched, one still queued", |s| {
s.artifact_roots == 2 && s.sealed_artifacts == 2
})
.await;
// `raw` drops here without ever consuming the dispatched delivery.
}
e.settle("disconnect released both", |s| {
s.artifact_roots == 0 && s.owners == 0 && s.sealed_artifacts == 0 && s.store_bytes == 0
})
.await;
}
// -------------------------------------------------------------------------------------------
// Stale references, stale generations and forged/borrowed owners
async fn stale_store_incarnation_and_generation_are_rejected(via: Via) {
let e = env(via).await;
let mut raw = e.raw_hello("c").await;
let (alloc, path) = raw.allocate(4).await;
std::fs::write(&path, b"data").unwrap();
let sealed_reply = raw.seal(&alloc, Value::Null).await.unwrap();
let mut bad_store_ref = sealed_reply["ref"].clone();
bad_store_ref["storeId"] = json!("store-does-not-exist");
let r = raw
.call(
"artifact.open",
json!({"ref": bad_store_ref, "ownerId": sealed_reply["ownerId"]}),
)
.await;
assert_eq!(
code(&r),
"ARTIFACT_GONE",
"a reference naming another store incarnation is stale"
);
let (alloc2, path2) = raw.allocate(4).await;
std::fs::write(&path2, b"more!").unwrap();
let bad_generation = raw
.call("artifact.seal", json!({"artifactId": alloc2["artifactId"], "generation": "2", "ownerId": alloc2["ownerId"], "digest": Value::Null}))
.await;
assert_eq!(
code(&bad_generation),
"ARTIFACT_GONE",
"v1 has exactly one generation"
);
}
async fn owner_ids_are_scoped_to_their_connection(via: Via) {
let e = env(via).await;
let mut a = e.raw_hello("a").await;
let mut b = e.raw_hello("b").await;
let (alloc, path) = a.allocate(4).await;
std::fs::write(&path, b"data").unwrap();
let sealed_reply = a.seal(&alloc, Value::Null).await.unwrap();
let (reference, owner_id) = (sealed_reply["ref"].clone(), sealed_reply["ownerId"].clone());
// `b` was never issued this owner id; a real owner id from another connection is just as
// invalid as one that was never issued at all.
assert_eq!(
code(
&b.call(
"artifact.open",
json!({"ref": reference, "ownerId": owner_id})
)
.await
),
"OWNER_INVALID"
);
assert_eq!(
code(
&b.call(
"artifact.retain",
json!({"ref": reference, "ownerId": owner_id})
)
.await
),
"OWNER_INVALID"
);
// The writer's own owner id cannot open before sealing.
let (alloc2, path2) = a.allocate(4).await;
std::fs::write(&path2, b"data").unwrap();
let unsealed_ref = json!({
"storeId": alloc2["writeLocation"]["storeId"], "artifactId": alloc2["artifactId"], "generation": "1",
"byteLength": "4", "contentType": "application/octet-stream", "digest": Value::Null,
});
assert_eq!(
code(
&a.call(
"artifact.open",
json!({"ref": unsealed_ref, "ownerId": alloc2["ownerId"]})
)
.await
),
"ARTIFACT_UNSEALED"
);
// A malformed owner id is refused outright, not treated as an unknown serial.
assert_eq!(
code(
&a.call(
"artifact.open",
json!({"ref": reference, "ownerId": "not-an-owner"})
)
.await
),
"OWNER_INVALID"
);
}
// -------------------------------------------------------------------------------------------
// Bounds, quotas and path containment (bus-v1 section 4/9)
async fn artifact_bounds_and_owner_budget_are_enforced(via: Via) {
let limits = Limits {
max_artifact_bytes: 100,
max_store_bytes: 160,
max_owners_per_client: 4,
reserved_owners_per_client: 1,
..Limits::default()
};
let e = env_with(via, limits, Policy::open()).await;
let c = e.client("c").await;
let too_big = c
.artifacts()
.allocate(101, "application/octet-stream")
.await
.unwrap_err();
assert_eq!(
too_big.code,
ErrorCode::QuotaExceeded,
"byteLength exceeds the per-object limit"
);
let a = sealed(&c, &[0u8; 80], "application/octet-stream").await;
let before = e.stats();
let no_room = c
.artifacts()
.allocate(90, "application/octet-stream")
.await
.unwrap_err();
assert_eq!(no_room.code, ErrorCode::QuotaExceeded, "the store is full");
assert_eq!(
e.stats(),
before,
"a rejected allocation must not charge the store"
);
// Owner budget: 4 total minus 1 reserved leaves room for 3 ordinary owners; `a` itself is
// already one of them, so exactly two more retains fit.
let h1 = a.retain().await.unwrap();
let h2 = a.retain().await.unwrap();
let over = a.retain().await.unwrap_err();
assert_eq!(
over.code,
ErrorCode::QuotaExceeded,
"owner budget exhausted"
);
drop((h1, h2, a));
}
/// Every location the router issues stays a plain relative path under the store; the router
/// never hands the client anything to escape with (store.rs enforces this on the resolving
/// side; this checks the issuing side never even offers an unsafe shape).
async fn issued_locations_are_relative_and_contained(via: Via) {
let e = env(via).await;
let mut raw = e.raw_hello("c").await;
let (alloc, path) = raw.allocate(4).await;
let assert_safe = |loc: &Value| {
assert_eq!(loc["storeId"].as_str().unwrap(), e.router.store_id());
let rel = loc["relativePath"].as_str().unwrap();
assert!(
!rel.starts_with('/') && !rel.contains(".."),
"{rel:?} escapes the store"
);
assert!(
rel.bytes().all(|b| b.is_ascii_lowercase()
|| b.is_ascii_digit()
|| matches!(b, b'.' | b'_' | b'-' | b'/')),
"{rel:?} has an unexpected character"
);
};
assert_safe(&alloc["writeLocation"]);
std::fs::write(&path, b"data").unwrap();
let sealed_reply = raw.seal(&alloc, Value::Null).await.unwrap();
let open_reply = raw
.call(
"artifact.open",
json!({"ref": sealed_reply["ref"], "ownerId": sealed_reply["ownerId"]}),
)
.await
.unwrap();
assert_safe(&open_reply["readLocation"]);
assert_eq!(
std::fs::read(raw.path(&open_reply["readLocation"])).unwrap(),
b"data"
);
}
both_transports!(
allocate_write_seal_open_roundtrip_and_mismatches,
seal_is_immutable_despite_a_stale_writable_handle,
extracted_artifact_outlives_the_message_it_came_from,
explicit_retain_outlives_the_original_hold,
collection_waits_for_every_retained_owner,
queued_deliveries_hold_roots_before_dispatch,
latest_mode_holds_at_most_two_roots_delivered_plus_queued,
retained_topic_value_holds_a_root_independent_of_subscribers,
forward_requires_the_source_owner_still_live,
rejected_publish_creates_no_roots,
rejected_call_creates_no_roots,
disconnect_abandons_an_unsealed_writer,
disconnect_releases_an_explicit_hold,
disconnect_releases_queued_and_dispatched_deliveries,
stale_store_incarnation_and_generation_are_rejected,
owner_ids_are_scoped_to_their_connection,
artifact_bounds_and_owner_budget_are_enforced,
issued_locations_are_relative_and_contained,
);