open-standards/joule-code/joule-code-rs/crates/joule-moq/src/endpoint.rs
Transaction Science 08c499f8ca joule-code: port CommunicationOS transport (jwp + joule-moq) into the public standard
The MoQT transport that joule-code-collab's network rung needs lived in the
private joulesperbit repo (as inv-moq + jwp), so a public consumer couldn't
depend on it. Port both into the public open-standards joule-code workspace,
self-contained (concrete dep versions, not workspace-inherited) so a bare git
clone builds them:

- crates/jwp: Joule Wire Protocol — energy-aware binary framing (CBOR, zstd/lz4,
  TCP + optional QUIC). Self-contained, external deps only.
- crates/joule-moq: MoQT (Media-over-QUIC) wire protocol + JWP signed-receipt
  extension for CommunicationOS — Publisher/Subscriber/Relay traits, MockFabric
  (in-process) + a production QUIC path (quic feature). Renamed from inv-moq
  to drop the invisible-* namespace and match the joule-code branding; depends
  only on jwp.

Both build (default + quic) and pass their tests (joule-moq 29, jwp 152) in the
new home. The transport-moqt example now path-deps the public joule-moq instead
of reaching into joulesperbit, and its end-to-end BFT-ops-over-MoQT-with-signed-
receipts tests pass. This unblocks public consumers (agent-zed) from git-depping
the transport for cross-machine fleet coordination.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-13 21:29:05 -04:00

129 lines
4.5 KiB
Rust

//! Endpoint trait surface.
//!
//! Three role-specific traits compose the public API:
//!
//! - [`Publisher`] — announces tracks, produces signed groups, emits objects.
//! - [`Subscriber`] — subscribes to tracks, receives objects, verifies receipts.
//! - [`Relay`] — forwards objects between peers, optionally re-signs receipts
//! when serving as the publisher's authorized agent.
//!
//! [`MoqEndpoint`] is the cross-cutting supertrait used by code that doesn't
//! care which role a peer plays.
//!
//! All operations are async because production transports are network-bound.
use crate::error::MoqResult;
use crate::messages::ObjectHeader;
use crate::receipt::{GroupReceipt, ObjectReceipt};
use crate::track::{GroupId, ObjectId, Track, TrackAlias, TrackName, TrackNamespace};
use async_trait::async_trait;
use bytes::Bytes;
use tokio::sync::mpsc;
/// Cross-role supertrait: every endpoint can identify itself and report
/// session-level state.
#[async_trait]
pub trait MoqEndpoint: Send + Sync {
/// Negotiated MoQT draft version after handshake.
fn version(&self) -> Option<u32>;
/// Whether this endpoint is a publisher, subscriber, pubsub, or relay.
fn role_label(&self) -> &'static str;
/// Whether the JWP receipt extension was negotiated successfully.
fn jwp_receipts_enabled(&self) -> bool;
/// Initiate a graceful close.
async fn close(&mut self) -> MoqResult<()>;
}
/// Publisher: announces and produces tracks.
#[async_trait]
pub trait Publisher: MoqEndpoint {
/// Announce a namespace as a producer.
async fn announce(&mut self, namespace: TrackNamespace) -> MoqResult<()>;
/// Open a new group on a track. Returns a [`GroupHandle`] used to push
/// objects and finalize the group's signed receipt.
async fn open_group(
&mut self,
namespace: &TrackNamespace,
track_name: &TrackName,
group_id: GroupId,
) -> MoqResult<Box<dyn GroupHandle>>;
/// Catalog all locally-known tracks for ANNOUNCE flows.
fn local_tracks(&self) -> Vec<Track>;
}
/// Group-scoped handle for publishers. Push objects, then `finalize` to
/// emit the group receipt covering all of them.
#[async_trait]
pub trait GroupHandle: Send + Sync {
/// Push an object payload. Returns the (object_id, content_hash, joules).
async fn push_object(
&mut self,
payload: Bytes,
joules_micro: u64,
) -> MoqResult<(ObjectId, [u8; 32])>;
/// Finalize the group: emit the signed [`GroupReceipt`] covering every
/// pushed object and close the data stream.
async fn finalize(self: Box<Self>) -> MoqResult<GroupReceipt>;
}
/// Subscriber: requests tracks and receives objects with receipts.
#[async_trait]
pub trait Subscriber: MoqEndpoint {
/// Subscribe to a track. Returns a handle whose receiver yields
/// `(ObjectHeader, payload, ObjectReceipt)` triples.
async fn subscribe(
&mut self,
namespace: TrackNamespace,
track_name: TrackName,
require_receipts: bool,
) -> MoqResult<SubscriptionHandle>;
/// Cancel an active subscription.
async fn unsubscribe(&mut self, subscribe_id: u64) -> MoqResult<()>;
}
/// Returned to the subscriber. The caller drains `rx` to receive objects.
pub struct SubscriptionHandle {
pub subscribe_id: u64,
pub track_alias: TrackAlias,
pub rx: mpsc::Receiver<DeliveredObject>,
}
/// One delivered object plus its verifiable receipt.
#[derive(Clone, Debug)]
pub struct DeliveredObject {
pub header: ObjectHeader,
pub payload: Bytes,
pub receipt: Option<ObjectReceipt>,
}
/// Relay: forwards objects between publishers and subscribers.
///
/// In the JWP-MoQ extension, a relay is a *receipt-preserving* forwarder
/// by default — it MUST NOT modify object payloads, and it MAY append its
/// own observation receipt (witnessing forwarding without altering the
/// original publisher's signed root).
#[async_trait]
pub trait Relay: MoqEndpoint {
/// Register a downstream subscriber. The relay handles fan-out to all
/// downstream subscribers of an upstream track.
async fn register_downstream(
&mut self,
namespace: TrackNamespace,
track_name: TrackName,
downstream: mpsc::Sender<DeliveredObject>,
) -> MoqResult<u64>;
/// Unregister a downstream by handle.
async fn unregister_downstream(&mut self, handle: u64) -> MoqResult<()>;
/// Joules consumed by this relay forwarding objects (used for joule
/// receipts on egress).
fn relay_joules_consumed_micro(&self) -> u64;
}