2026-07-12 06:14:34 +00:00
|
|
|
//! Real-I/O checks for the datastream envelope over loopback UDP.
|
2026-06-05 07:25:43 +00:00
|
|
|
|
|
|
|
|
use std::collections::{HashMap, HashSet};
|
|
|
|
|
use std::net::UdpSocket;
|
|
|
|
|
use std::time::Duration;
|
|
|
|
|
|
enforce datastream telemetry-only invariant: ban frame types from control code
The datastream is metrics/logging only; control decisions must never branch
on a frame. This was a recurring cultural problem with no structural
enforcement. This change makes it a compile-time and CI-enforced fact.
datastream crate (lib.rs):
- Stop re-exporting Frame, DatastreamEvent, FrameDelivery at crate root.
is now a compile error (E0425). These types live
only in datastream::frame::* and are documented as the observer surface.
- Safe identity types (ChannelId, StreamId, Position, Record, etc.) remain
re-exported at root for producer-side callers.
orchestration/app.rs:
- Extracted all frame-touching code (CollectedDatastreamFrame,
drain_datastream_connections, update_load_progress_from_frame,
drain_frames, archive_collected_frame, pump, OrchDatastream,
DashboardSupport) into two new observability modules:
frame_collector.rs and orch_datastream.rs.
- The orchestrator now interacts through a FrameCollector whose
drain/drain_with_progress methods take closures; it never names Frame,
DatastreamEvent, or CollectedDatastreamFrame.
- StageLoadProgress (the one control-relevant signal previously scraped
from frame payloads) is extracted inside FrameCollector and handed to
the control loop as plain data.
xtask:
- New check-telemetry-isolation command scans control-plane modules
(orchestration/, distribution/, data-plane/, provisioning/) for
forbidden frame-type references and fails the build if any are found.
Verified: workspace builds (myelin + dashboard feature), datastream 29
tests pass, myelin 64 lib tests pass, check-telemetry-isolation passes
clean.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-08-12 12:14:57 +00:00
|
|
|
use datastream::frame::Frame;
|
2026-06-09 09:29:07 +00:00
|
|
|
use datastream::ingest::Consumer;
|
2026-07-12 06:14:34 +00:00
|
|
|
use datastream::mux::Mux;
|
2026-06-09 09:29:07 +00:00
|
|
|
use datastream::transport::Delivery;
|
|
|
|
|
use datastream::wire::{decode_delivery, encode_delivery};
|
enforce datastream telemetry-only invariant: ban frame types from control code
The datastream is metrics/logging only; control decisions must never branch
on a frame. This was a recurring cultural problem with no structural
enforcement. This change makes it a compile-time and CI-enforced fact.
datastream crate (lib.rs):
- Stop re-exporting Frame, DatastreamEvent, FrameDelivery at crate root.
is now a compile error (E0425). These types live
only in datastream::frame::* and are documented as the observer surface.
- Safe identity types (ChannelId, StreamId, Position, Record, etc.) remain
re-exported at root for producer-side callers.
orchestration/app.rs:
- Extracted all frame-touching code (CollectedDatastreamFrame,
drain_datastream_connections, update_load_progress_from_frame,
drain_frames, archive_collected_frame, pump, OrchDatastream,
DashboardSupport) into two new observability modules:
frame_collector.rs and orch_datastream.rs.
- The orchestrator now interacts through a FrameCollector whose
drain/drain_with_progress methods take closures; it never names Frame,
DatastreamEvent, or CollectedDatastreamFrame.
- StageLoadProgress (the one control-relevant signal previously scraped
from frame payloads) is extracted inside FrameCollector and handed to
the control loop as plain data.
xtask:
- New check-telemetry-isolation command scans control-plane modules
(orchestration/, distribution/, data-plane/, provisioning/) for
forbidden frame-type references and fails the build if any are found.
Verified: workspace builds (myelin + dashboard feature), datastream 29
tests pass, myelin 64 lib tests pass, check-telemetry-isolation passes
clean.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-08-12 12:14:57 +00:00
|
|
|
use datastream::{ChannelId, Lifetime, NodeId, Position, Record, StreamId};
|
2026-07-12 06:14:34 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
|
|
|
|
|
const RESOURCE_CHANNEL: ChannelId = ChannelId(1);
|
|
|
|
|
const LOG_CHANNEL: ChannelId = ChannelId(2);
|
|
|
|
|
const MEMBERSHIP_CHANNEL: ChannelId = ChannelId(3);
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
|
|
|
struct ResourceSample {
|
|
|
|
|
tick: u64,
|
|
|
|
|
cpu_pct: f32,
|
|
|
|
|
}
|
2026-06-05 07:25:43 +00:00
|
|
|
|
2026-07-12 06:14:34 +00:00
|
|
|
impl Record for ResourceSample {
|
|
|
|
|
const CHANNEL: &'static str = "host.resource";
|
|
|
|
|
}
|
2026-06-05 07:25:43 +00:00
|
|
|
|
|
|
|
|
fn build_stream() -> (StreamId, Vec<Frame>) {
|
|
|
|
|
let id = StreamId::new(NodeId::new("node-real"), Lifetime(1));
|
2026-07-12 06:14:34 +00:00
|
|
|
let mux = Mux::unbounded(id.clone());
|
2026-06-05 07:25:43 +00:00
|
|
|
for tick in 0..20 {
|
2026-07-12 06:14:34 +00:00
|
|
|
mux.submit(
|
|
|
|
|
RESOURCE_CHANNEL,
|
|
|
|
|
ResourceSample {
|
|
|
|
|
tick,
|
|
|
|
|
cpu_pct: 10.0 + tick as f32,
|
|
|
|
|
}
|
|
|
|
|
.encode(),
|
|
|
|
|
);
|
2026-06-05 07:25:43 +00:00
|
|
|
}
|
2026-07-12 06:14:34 +00:00
|
|
|
mux.submit(LOG_CHANNEL, b"epoch 1 complete".to_vec());
|
|
|
|
|
mux.submit(MEMBERSHIP_CHANNEL, b"node-x alive->suspect".to_vec());
|
|
|
|
|
(id, mux.drain())
|
2026-06-05 07:25:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn carry_over_real_socket(stream: &StreamId, frames: &[Frame]) -> Vec<Delivery> {
|
|
|
|
|
let consumer = UdpSocket::bind("127.0.0.1:0").expect("bind consumer socket");
|
2026-06-23 15:42:28 +00:00
|
|
|
consumer
|
|
|
|
|
.set_read_timeout(Some(Duration::from_millis(300)))
|
|
|
|
|
.expect("set timeout");
|
2026-06-05 07:25:43 +00:00
|
|
|
let consumer_addr = consumer.local_addr().expect("consumer addr");
|
|
|
|
|
|
|
|
|
|
let node = UdpSocket::bind("127.0.0.1:0").expect("bind node socket");
|
|
|
|
|
for frame in frames {
|
|
|
|
|
let datagram = encode_delivery(stream, frame);
|
|
|
|
|
let _ = node.send_to(&datagram, consumer_addr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut delivered = Vec::new();
|
|
|
|
|
let mut buf = vec![0u8; 64 * 1024];
|
|
|
|
|
loop {
|
|
|
|
|
match consumer.recv_from(&mut buf) {
|
|
|
|
|
Ok((n, _)) => {
|
|
|
|
|
if let Ok((s, frame)) = decode_delivery(&buf[..n]) {
|
|
|
|
|
delivered.push(Delivery::new(s, frame));
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-12 06:14:34 +00:00
|
|
|
Err(e)
|
2026-06-05 07:25:43 +00:00
|
|
|
if e.kind() == std::io::ErrorKind::WouldBlock
|
|
|
|
|
|| e.kind() == std::io::ErrorKind::TimedOut =>
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
Err(_) => break,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
delivered
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn real_transport_stays_within_the_envelope() {
|
|
|
|
|
let (id, sent) = build_stream();
|
|
|
|
|
let delivered = carry_over_real_socket(&id, &sent);
|
|
|
|
|
|
|
|
|
|
let by_position: HashMap<u64, &Frame> = sent.iter().map(|f| (f.position.0, f)).collect();
|
|
|
|
|
let mut seen = HashSet::new();
|
|
|
|
|
for d in &delivered {
|
|
|
|
|
assert_eq!(d.stream, id, "the carrier did not alter the stream id");
|
|
|
|
|
let original = by_position
|
|
|
|
|
.get(&d.frame.position.0)
|
2026-07-12 06:14:34 +00:00
|
|
|
.expect("a delivered position was never sent");
|
|
|
|
|
assert_eq!(&d.frame, *original);
|
|
|
|
|
assert!(seen.insert(d.frame.position.0), "no duplicate positions");
|
2026-06-05 07:25:43 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn wiring_smoke_some_frames_arrive_and_reconstruct() {
|
|
|
|
|
let (id, sent) = build_stream();
|
|
|
|
|
let delivered = carry_over_real_socket(&id, &sent);
|
|
|
|
|
let mut consumer = Consumer::new();
|
|
|
|
|
consumer.ingest(delivered);
|
|
|
|
|
|
|
|
|
|
let stored = consumer
|
|
|
|
|
.store()
|
|
|
|
|
.stream(&id)
|
2026-07-12 06:14:34 +00:00
|
|
|
.expect("the node's frames reached the consumer");
|
2026-06-23 15:42:28 +00:00
|
|
|
assert!(
|
|
|
|
|
!stored.is_empty(),
|
|
|
|
|
"some frames arrived over the real transport"
|
|
|
|
|
);
|
2026-06-05 07:25:43 +00:00
|
|
|
|
|
|
|
|
let by_position: HashMap<u64, &Frame> = sent.iter().map(|f| (f.position.0, f)).collect();
|
|
|
|
|
let mut prev: Option<u64> = None;
|
|
|
|
|
for frame in stored.frames() {
|
|
|
|
|
assert_eq!(
|
|
|
|
|
frame,
|
2026-07-12 06:14:34 +00:00
|
|
|
*by_position.get(&frame.position.0).expect("sent frame")
|
2026-06-05 07:25:43 +00:00
|
|
|
);
|
|
|
|
|
if let Some(p) = prev {
|
|
|
|
|
assert!(frame.position.0 > p, "reconstructed in position order");
|
|
|
|
|
}
|
|
|
|
|
prev = Some(frame.position.0);
|
|
|
|
|
}
|
|
|
|
|
}
|