2026-08-15 08:17:48 +00:00
|
|
|
//! Iroh/QUIC transport adapter for telemetry subscriptions.
|
2026-06-25 12:30:18 +00:00
|
|
|
|
|
|
|
|
use std::error::Error;
|
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
use std::time::Duration;
|
|
|
|
|
|
2026-07-21 06:25:52 +00:00
|
|
|
use crossbeam_channel::TryRecvError;
|
2026-06-25 12:30:18 +00:00
|
|
|
use iroh::endpoint::{Connection, RecvStream, SendStream};
|
|
|
|
|
use iroh::{Endpoint, EndpointAddr};
|
feat(myelin): enforce actor-owned control flow
Architecture enforcement:
- Install a repository-owned rustc wrapper for ordinary cargo check,
build, and test commands. Resolve compiler item identities so renamed
imports and helper wrappers cannot hide spawning, timing, blocking,
polling, thread, or runtime-driving capabilities.
- Define the execution-owner crates and reject dependencies from those
substrates back into Myelin policy. Add compile-pass and compile-fail
contracts for actor helpers, execution owners, test waits, forbidden
capabilities, suppression attempts, and owner dependency inversions.
Execution ownership:
- Add engine-owned actor timers with cancellation and generation identity,
then migrate lifecycle deadlines and protocol ticks off application
tasks. Keep networking, process output, telemetry, and blocking provider
calls in their approved I/O substrates.
- Move process spawn, wait, signal, Unix listener, and output-following
mechanics into swactor-process. Isolate Vast.ai blocking HTTP mechanics
behind its adapter while actors retain retry, recovery, and provisioning
decisions.
Myelin control flow:
- Rework manual control, worker lifecycle, provisioning, provider recovery,
job deployment, distribution, edge orchestration, and shutdown as actor
state transitions and typed effects. Preserve durable provider adoption
and command outcomes across graceful and abrupt restarts.
- Replace controller loops and timer-forwarding tasks with actor messages;
leave substrate tasks as cancellable observation streams with no durable
policy state.
Properties and resource ownership:
- Add deterministic engine and component properties, a stateful mock-VastAI
lifecycle model, persisted regression cases, controlled fault injection,
and a bounded nightly workflow covering restart and teardown behavior.
- Terminate reply observers, cancel telemetry collectors, bound dashboard
projections, and release child observers, file descriptors, process
records, and inode-verified Unix sockets on every terminal path.
Verified with the compiler-policy contracts, 105 Myelin library tests, 32
swactor-process tests, telemetry cancellation contracts, randomized
stateful restart cases, cargo check, and formatting checks.
2026-08-19 21:38:14 +00:00
|
|
|
use swactor::actor::ActorAddress;
|
|
|
|
|
use swactor::runtime::ExternalSender;
|
feat(engine): substrate-neutral execution engine abstraction
Introduce the swactor engine: a swactor-owned composite that retains a
selected execution substrate, drives the core runtime, and hosts the
async/blocking/timer work that backs actors. Integrations receive one
cloneable EngineHandle and never construct or borrow a raw Tokio
runtime/handle.
Engine crate (crates/engine):
- The contract: spawn / spawn_blocking / timer / interval / now, a
per-implementation capability model with construction-time binding
(require()), and engine-owned time. The engine owns all progression;
actor handlers stay synchronous and never .await.
- TokioBackend owns the Tokio runtime and schedules core ticks and
supporting futures on it; SteppingBackend is a single-threaded
deterministic scheduler with virtual time (the non-Tokio portability
proof). Core is driven through its existing tick() surface; a
self-rescheduling CoreDriver is installed at construction and is the
sole place permitted to call try_tick.
iroh-driver:
- Receives an EngineHandle instead of a raw Tokio Handle. Accepts,
reads, dials, writes, endpoint construction, and teardown schedule
through it; required capabilities (tasks/timers/io) are validated
before the endpoint binds. Engine-hosted interval pumps drive
actor-bridge, datastream, and edge ingress.
myelin:
- One node/orchestrator engine owns core, protocol tick injection, and
transport progression; the application loop only drains
integration-owned queues. Stage-shard process readers, delayed actor
messages, helper stdout/stderr, prompt RPC, and CPU sampling all
schedule through the engine (spawn_blocking / engine tasks / timers).
- Removed the split-engine APIs: install_actor_bridge_pump(period) and
spawn_protocol_ticker(period) use each component's stored engine;
deleted the no-op pump_network callback and its plumbing; deleted the
dashboard raw-Tokio/standalone-runtime conveniences.
Enforcement:
- A clippy disallowed-methods boundary forbids direct runtime/scheduling/
time/core-driving bypasses, denied in swactor-engine, iroh-driver, and
myelin. Retained excluded uses (VastAI provider, provider process
supervision/log capture, OS-signal/stdin/process-control sequencing)
carry narrow allowances with reasons.
Verification:
- Engine contract + unit tests (incl. the SteppingBackend portability
proof), iroh integration tests (capability rejection before binding,
multi-node actor behavior), and a production execution-composition
smoke test that observes engine-driven actor progress with no ambient
Tokio runtime and no manual tick/pump. Workspace all-target/all-feature
clippy and tests are green.
Specs co-located with their crates: ENGINE_SPEC.md in crates/engine,
IROH_DRIVER_SPEC.md in crates/iroh-driver. VastAI remains explicitly out
of scope pending its separate redesign.
2026-08-10 20:23:03 +00:00
|
|
|
use swactor_engine::EngineHandle;
|
provisioning-reconciler-demo: wire-announce readiness + --docker node kind
Replace the per-attempt key-file side channel (DEMO_NODE_KEY_FILE,
read_key_report, JoinCheck) with a control-plane announce: node roles
send a tagged gossip frame {attempt, logical_node, key_hex,
endpoint_addr_json} after joining and every heartbeat thereafter.
- iroh-driver: IrohDriver::send_tagged_gossip — fire-and-forget
tag-routed gossip egress for bridge-less clients (reuses cached/join
connections, dials with backoff).
- provisioning: BootstrapMsg::Announce — first delivery while
bootstrapping completes the attempt (collector + exactly-once
Bootstrapped report); duplicates, misrouted attempts, and
terminal-phase announces drop. Unit-tested.
- xtask demo: AnnounceActor decodes the tag-routed frame and forwards
by attempt to the owning bootstrap actor; last_announce_ms is the
wire heartbeat. LocalProcessLogic keeps only process lifecycle.
- --docker: DockerProcessLogic (kind "docker") — attached
"docker run --rm" child on a per-run labeled bridge network
(foreign-node masking: per-container IPs, gateway-dialed
supervisor). Standalone scratch image from the static-musl xtask
binary (37MB), staged one-file build context. The container is
force-removed on every terminal path so a SIGKILLed docker CLI
cannot orphan a running container.
- Cleanup: no volumes/mounts; label-filtered exit sweep (SIGINT and
SIGTERM both drain first) + startup sweep of stale demo resources;
images persist per run token.
Verified live: process kind (kill -> replacement in 3.4s, provision/
remove/kill waves, zero orphans) and docker kind (8-node abuse waves
across docker kill, mid-provision control kills, CLI SIGKILL orphans
force-removed, SIGKILL-crash leftovers swept on restart, clean exits
leave zero containers/networks/CLIs). provisioning 22 + iroh-driver
13 tests pass.
2026-08-16 16:30:52 +00:00
|
|
|
use telemetry::frame::{
|
|
|
|
|
ChannelDescriptor, ChannelId, ChannelRef, FrameDelivery, Position, StreamDescriptor,
|
|
|
|
|
TelemetryEvent,
|
|
|
|
|
};
|
|
|
|
|
use telemetry::{TelemetrySnapshot, TelemetrySubscription};
|
2026-06-25 12:30:18 +00:00
|
|
|
|
2026-08-15 08:17:48 +00:00
|
|
|
pub const TELEMETRY_ALPN: &[u8] = b"swactor/telemetry/0";
|
2026-06-25 12:30:18 +00:00
|
|
|
|
2026-07-12 06:14:34 +00:00
|
|
|
const MAGIC: &[u8; 4] = b"DSQ1";
|
|
|
|
|
const TAG_CHANNEL_DECLARED: u8 = 0x01;
|
|
|
|
|
const TAG_FRAME: u8 = 0x02;
|
|
|
|
|
const TAG_STREAM_ENDED: u8 = 0x03;
|
provisioning-reconciler-demo: wire-announce readiness + --docker node kind
Replace the per-attempt key-file side channel (DEMO_NODE_KEY_FILE,
read_key_report, JoinCheck) with a control-plane announce: node roles
send a tagged gossip frame {attempt, logical_node, key_hex,
endpoint_addr_json} after joining and every heartbeat thereafter.
- iroh-driver: IrohDriver::send_tagged_gossip — fire-and-forget
tag-routed gossip egress for bridge-less clients (reuses cached/join
connections, dials with backoff).
- provisioning: BootstrapMsg::Announce — first delivery while
bootstrapping completes the attempt (collector + exactly-once
Bootstrapped report); duplicates, misrouted attempts, and
terminal-phase announces drop. Unit-tested.
- xtask demo: AnnounceActor decodes the tag-routed frame and forwards
by attempt to the owning bootstrap actor; last_announce_ms is the
wire heartbeat. LocalProcessLogic keeps only process lifecycle.
- --docker: DockerProcessLogic (kind "docker") — attached
"docker run --rm" child on a per-run labeled bridge network
(foreign-node masking: per-container IPs, gateway-dialed
supervisor). Standalone scratch image from the static-musl xtask
binary (37MB), staged one-file build context. The container is
force-removed on every terminal path so a SIGKILLed docker CLI
cannot orphan a running container.
- Cleanup: no volumes/mounts; label-filtered exit sweep (SIGINT and
SIGTERM both drain first) + startup sweep of stale demo resources;
images persist per run token.
Verified live: process kind (kill -> replacement in 3.4s, provision/
remove/kill waves, zero orphans) and docker kind (8-node abuse waves
across docker kill, mid-provision control kills, CLI SIGKILL orphans
force-removed, SIGKILL-crash leftovers swept on restart, clean exits
leave zero containers/networks/CLIs). provisioning 22 + iroh-driver
13 tests pass.
2026-08-16 16:30:52 +00:00
|
|
|
|
|
|
|
|
// ─── Pull model: collector-initiated subscriptions ───────────────────────────
|
|
|
|
|
//
|
|
|
|
|
// A supervising node dials a freshly-bootstrapped node on `TELEMETRY_ALPN`,
|
|
|
|
|
// sends one subscription request on the first uni stream, and the node answers
|
|
|
|
|
// by writing the existing header+events stream shape on a uni stream of the
|
|
|
|
|
// same connection. Subscription lifetime = connection lifetime.
|
|
|
|
|
|
|
|
|
|
/// Write a pull request (magic + flow id + token + `SubscriptionRequest`) and
|
|
|
|
|
/// finish the stream so the serving side's read completes.
|
|
|
|
|
const REQUEST_MAGIC: &[u8; 4] = b"DSQR";
|
|
|
|
|
pub async fn write_pull_request(
|
|
|
|
|
send: &mut SendStream,
|
|
|
|
|
flow_id: [u8; 16],
|
|
|
|
|
token: &[u8],
|
|
|
|
|
request: &telemetry::SubscriptionRequest,
|
|
|
|
|
) -> Result<(), BoxError> {
|
|
|
|
|
if token.len() > u16::MAX as usize {
|
|
|
|
|
return Err("telemetry pull token exceeds u16 length prefix".into());
|
|
|
|
|
}
|
|
|
|
|
send.write_all(REQUEST_MAGIC).await?;
|
|
|
|
|
send.write_all(&flow_id).await?;
|
|
|
|
|
send.write_all(&(token.len() as u16).to_le_bytes()).await?;
|
|
|
|
|
send.write_all(token).await?;
|
|
|
|
|
write_json(send, request).await?;
|
|
|
|
|
send.finish()?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Read a pull request written by [`write_pull_request`].
|
|
|
|
|
pub async fn read_pull_request(
|
|
|
|
|
recv: &mut RecvStream,
|
|
|
|
|
) -> Result<([u8; 16], Vec<u8>, telemetry::SubscriptionRequest), BoxError> {
|
|
|
|
|
let mut magic = [0u8; 4];
|
|
|
|
|
recv.read_exact(&mut magic).await?;
|
|
|
|
|
if &magic != REQUEST_MAGIC {
|
|
|
|
|
return Err("invalid telemetry pull request magic".into());
|
|
|
|
|
}
|
|
|
|
|
let mut flow_id = [0u8; 16];
|
|
|
|
|
recv.read_exact(&mut flow_id).await?;
|
|
|
|
|
let mut token_len = [0u8; 2];
|
|
|
|
|
recv.read_exact(&mut token_len).await?;
|
|
|
|
|
let token_len = u16::from_le_bytes(token_len) as usize;
|
|
|
|
|
let mut token = vec![0u8; token_len];
|
|
|
|
|
recv.read_exact(&mut token).await?;
|
|
|
|
|
let request = read_json(recv).await?;
|
|
|
|
|
Ok((flow_id, token, request))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Node side: serve one accepted `TELEMETRY_ALPN` connection. Reads the pull
|
|
|
|
|
/// request from the first uni stream, subscribes the local endpoint, and
|
|
|
|
|
/// writes the answering subscription stream (header + events, until the
|
|
|
|
|
/// subscription ends or the connection drops) on a uni stream of the same
|
|
|
|
|
/// connection. One request per connection; the task exits when the writer
|
|
|
|
|
/// ends or the connection fails.
|
|
|
|
|
pub fn spawn_pull_server(
|
|
|
|
|
engine: &EngineHandle,
|
|
|
|
|
conn: Connection,
|
|
|
|
|
endpoint: std::sync::Arc<telemetry::TelemetryEndpoint>,
|
|
|
|
|
idle_sleep: Duration,
|
|
|
|
|
) {
|
|
|
|
|
let engine_handle = engine.clone();
|
|
|
|
|
engine.spawn(async move {
|
|
|
|
|
let Ok(mut recv) = conn.accept_uni().await else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
let Ok((_flow_id, token, request)) = read_pull_request(&mut recv).await else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
let subscription = endpoint.subscribe("supervisor-pull", request);
|
|
|
|
|
let header =
|
|
|
|
|
match TelemetryQuicHeader::from_snapshot(_flow_id, token, subscription.snapshot()) {
|
|
|
|
|
Ok(header) => header,
|
|
|
|
|
Err(_) => return,
|
|
|
|
|
};
|
|
|
|
|
let Ok(send) = conn.open_uni().await else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
let _ =
|
|
|
|
|
write_subscription_until_closed(&engine_handle, send, header, subscription, idle_sleep)
|
|
|
|
|
.await;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
feat(myelin): enforce actor-owned control flow
Architecture enforcement:
- Install a repository-owned rustc wrapper for ordinary cargo check,
build, and test commands. Resolve compiler item identities so renamed
imports and helper wrappers cannot hide spawning, timing, blocking,
polling, thread, or runtime-driving capabilities.
- Define the execution-owner crates and reject dependencies from those
substrates back into Myelin policy. Add compile-pass and compile-fail
contracts for actor helpers, execution owners, test waits, forbidden
capabilities, suppression attempts, and owner dependency inversions.
Execution ownership:
- Add engine-owned actor timers with cancellation and generation identity,
then migrate lifecycle deadlines and protocol ticks off application
tasks. Keep networking, process output, telemetry, and blocking provider
calls in their approved I/O substrates.
- Move process spawn, wait, signal, Unix listener, and output-following
mechanics into swactor-process. Isolate Vast.ai blocking HTTP mechanics
behind its adapter while actors retain retry, recovery, and provisioning
decisions.
Myelin control flow:
- Rework manual control, worker lifecycle, provisioning, provider recovery,
job deployment, distribution, edge orchestration, and shutdown as actor
state transitions and typed effects. Preserve durable provider adoption
and command outcomes across graceful and abrupt restarts.
- Replace controller loops and timer-forwarding tasks with actor messages;
leave substrate tasks as cancellable observation streams with no durable
policy state.
Properties and resource ownership:
- Add deterministic engine and component properties, a stateful mock-VastAI
lifecycle model, persisted regression cases, controlled fault injection,
and a bounded nightly workflow covering restart and teardown behavior.
- Terminate reply observers, cancel telemetry collectors, bound dashboard
projections, and release child observers, file descriptors, process
records, and inode-verified Unix sockets on every terminal path.
Verified with the compiler-policy contracts, 105 Myelin library tests, 32
swactor-process tests, telemetry cancellation contracts, randomized
stateful restart cases, cargo check, and formatting checks.
2026-08-19 21:38:14 +00:00
|
|
|
/// Cancellation handle for one collector-initiated telemetry subscription.
|
|
|
|
|
///
|
|
|
|
|
/// Cancellation is idempotent and immediately interrupts network I/O,
|
|
|
|
|
/// reconnect backoff, and all subsequent reconnect attempts.
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub struct PullCollectorHandle {
|
|
|
|
|
cancellation: tokio::sync::watch::Sender<bool>,
|
|
|
|
|
completion: tokio::sync::watch::Receiver<bool>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PullCollectorHandle {
|
|
|
|
|
pub fn cancel(&self) {
|
|
|
|
|
self.cancellation.send_replace(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn is_cancelled(&self) -> bool {
|
|
|
|
|
*self.cancellation.borrow()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn is_finished(&self) -> bool {
|
|
|
|
|
*self.completion.borrow()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Drop for PullCollectorHandle {
|
|
|
|
|
fn drop(&mut self) {
|
|
|
|
|
self.cancel();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct PullCollectorCompletion(tokio::sync::watch::Sender<bool>);
|
|
|
|
|
|
|
|
|
|
impl Drop for PullCollectorCompletion {
|
|
|
|
|
fn drop(&mut self) {
|
|
|
|
|
self.0.send_replace(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-22 17:16:56 +00:00
|
|
|
pub struct PullCollectorConfig {
|
|
|
|
|
pub endpoint: Endpoint,
|
|
|
|
|
pub peer: EndpointAddr,
|
|
|
|
|
pub flow_id: [u8; 16],
|
|
|
|
|
pub token: Vec<u8>,
|
|
|
|
|
pub request: telemetry::SubscriptionRequest,
|
|
|
|
|
pub fanout: std::sync::Arc<telemetry::DeliveryFanout>,
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-18 10:17:04 +00:00
|
|
|
/// Supervisor side: retain a pull subscription to a node on `TELEMETRY_ALPN`.
|
|
|
|
|
///
|
|
|
|
|
/// A transport interruption reconnects with bounded backoff. Returning after
|
|
|
|
|
/// the first EOF leaves a healthy node permanently stale, which is especially
|
|
|
|
|
/// easy to trigger while several freshly-bootstrapped nodes answer at once.
|
provisioning-reconciler-demo: wire-announce readiness + --docker node kind
Replace the per-attempt key-file side channel (DEMO_NODE_KEY_FILE,
read_key_report, JoinCheck) with a control-plane announce: node roles
send a tagged gossip frame {attempt, logical_node, key_hex,
endpoint_addr_json} after joining and every heartbeat thereafter.
- iroh-driver: IrohDriver::send_tagged_gossip — fire-and-forget
tag-routed gossip egress for bridge-less clients (reuses cached/join
connections, dials with backoff).
- provisioning: BootstrapMsg::Announce — first delivery while
bootstrapping completes the attempt (collector + exactly-once
Bootstrapped report); duplicates, misrouted attempts, and
terminal-phase announces drop. Unit-tested.
- xtask demo: AnnounceActor decodes the tag-routed frame and forwards
by attempt to the owning bootstrap actor; last_announce_ms is the
wire heartbeat. LocalProcessLogic keeps only process lifecycle.
- --docker: DockerProcessLogic (kind "docker") — attached
"docker run --rm" child on a per-run labeled bridge network
(foreign-node masking: per-container IPs, gateway-dialed
supervisor). Standalone scratch image from the static-musl xtask
binary (37MB), staged one-file build context. The container is
force-removed on every terminal path so a SIGKILLed docker CLI
cannot orphan a running container.
- Cleanup: no volumes/mounts; label-filtered exit sweep (SIGINT and
SIGTERM both drain first) + startup sweep of stale demo resources;
images persist per run token.
Verified live: process kind (kill -> replacement in 3.4s, provision/
remove/kill waves, zero orphans) and docker kind (8-node abuse waves
across docker kill, mid-provision control kills, CLI SIGKILL orphans
force-removed, SIGKILL-crash leftovers swept on restart, clean exits
leave zero containers/networks/CLIs). provisioning 22 + iroh-driver
13 tests pass.
2026-08-16 16:30:52 +00:00
|
|
|
pub fn spawn_pull_collector(
|
|
|
|
|
engine: &EngineHandle,
|
2026-08-22 17:16:56 +00:00
|
|
|
config: PullCollectorConfig,
|
provisioning-reconciler-demo: wire-announce readiness + --docker node kind
Replace the per-attempt key-file side channel (DEMO_NODE_KEY_FILE,
read_key_report, JoinCheck) with a control-plane announce: node roles
send a tagged gossip frame {attempt, logical_node, key_hex,
endpoint_addr_json} after joining and every heartbeat thereafter.
- iroh-driver: IrohDriver::send_tagged_gossip — fire-and-forget
tag-routed gossip egress for bridge-less clients (reuses cached/join
connections, dials with backoff).
- provisioning: BootstrapMsg::Announce — first delivery while
bootstrapping completes the attempt (collector + exactly-once
Bootstrapped report); duplicates, misrouted attempts, and
terminal-phase announces drop. Unit-tested.
- xtask demo: AnnounceActor decodes the tag-routed frame and forwards
by attempt to the owning bootstrap actor; last_announce_ms is the
wire heartbeat. LocalProcessLogic keeps only process lifecycle.
- --docker: DockerProcessLogic (kind "docker") — attached
"docker run --rm" child on a per-run labeled bridge network
(foreign-node masking: per-container IPs, gateway-dialed
supervisor). Standalone scratch image from the static-musl xtask
binary (37MB), staged one-file build context. The container is
force-removed on every terminal path so a SIGKILLed docker CLI
cannot orphan a running container.
- Cleanup: no volumes/mounts; label-filtered exit sweep (SIGINT and
SIGTERM both drain first) + startup sweep of stale demo resources;
images persist per run token.
Verified live: process kind (kill -> replacement in 3.4s, provision/
remove/kill waves, zero orphans) and docker kind (8-node abuse waves
across docker kill, mid-provision control kills, CLI SIGKILL orphans
force-removed, SIGKILL-crash leftovers swept on restart, clean exits
leave zero containers/networks/CLIs). provisioning 22 + iroh-driver
13 tests pass.
2026-08-16 16:30:52 +00:00
|
|
|
on_header: std::sync::mpsc::Sender<TelemetryQuicHeader>,
|
feat(myelin): enforce actor-owned control flow
Architecture enforcement:
- Install a repository-owned rustc wrapper for ordinary cargo check,
build, and test commands. Resolve compiler item identities so renamed
imports and helper wrappers cannot hide spawning, timing, blocking,
polling, thread, or runtime-driving capabilities.
- Define the execution-owner crates and reject dependencies from those
substrates back into Myelin policy. Add compile-pass and compile-fail
contracts for actor helpers, execution owners, test waits, forbidden
capabilities, suppression attempts, and owner dependency inversions.
Execution ownership:
- Add engine-owned actor timers with cancellation and generation identity,
then migrate lifecycle deadlines and protocol ticks off application
tasks. Keep networking, process output, telemetry, and blocking provider
calls in their approved I/O substrates.
- Move process spawn, wait, signal, Unix listener, and output-following
mechanics into swactor-process. Isolate Vast.ai blocking HTTP mechanics
behind its adapter while actors retain retry, recovery, and provisioning
decisions.
Myelin control flow:
- Rework manual control, worker lifecycle, provisioning, provider recovery,
job deployment, distribution, edge orchestration, and shutdown as actor
state transitions and typed effects. Preserve durable provider adoption
and command outcomes across graceful and abrupt restarts.
- Replace controller loops and timer-forwarding tasks with actor messages;
leave substrate tasks as cancellable observation streams with no durable
policy state.
Properties and resource ownership:
- Add deterministic engine and component properties, a stateful mock-VastAI
lifecycle model, persisted regression cases, controlled fault injection,
and a bounded nightly workflow covering restart and teardown behavior.
- Terminate reply observers, cancel telemetry collectors, bound dashboard
projections, and release child observers, file descriptors, process
records, and inode-verified Unix sockets on every terminal path.
Verified with the compiler-policy contracts, 105 Myelin library tests, 32
swactor-process tests, telemetry cancellation contracts, randomized
stateful restart cases, cargo check, and formatting checks.
2026-08-19 21:38:14 +00:00
|
|
|
) -> PullCollectorHandle {
|
2026-08-22 17:16:56 +00:00
|
|
|
spawn_pull_collector_with_sink(engine, config, PullHeaderSink::Channel(on_header))
|
feat(myelin): enforce actor-owned control flow
Architecture enforcement:
- Install a repository-owned rustc wrapper for ordinary cargo check,
build, and test commands. Resolve compiler item identities so renamed
imports and helper wrappers cannot hide spawning, timing, blocking,
polling, thread, or runtime-driving capabilities.
- Define the execution-owner crates and reject dependencies from those
substrates back into Myelin policy. Add compile-pass and compile-fail
contracts for actor helpers, execution owners, test waits, forbidden
capabilities, suppression attempts, and owner dependency inversions.
Execution ownership:
- Add engine-owned actor timers with cancellation and generation identity,
then migrate lifecycle deadlines and protocol ticks off application
tasks. Keep networking, process output, telemetry, and blocking provider
calls in their approved I/O substrates.
- Move process spawn, wait, signal, Unix listener, and output-following
mechanics into swactor-process. Isolate Vast.ai blocking HTTP mechanics
behind its adapter while actors retain retry, recovery, and provisioning
decisions.
Myelin control flow:
- Rework manual control, worker lifecycle, provisioning, provider recovery,
job deployment, distribution, edge orchestration, and shutdown as actor
state transitions and typed effects. Preserve durable provider adoption
and command outcomes across graceful and abrupt restarts.
- Replace controller loops and timer-forwarding tasks with actor messages;
leave substrate tasks as cancellable observation streams with no durable
policy state.
Properties and resource ownership:
- Add deterministic engine and component properties, a stateful mock-VastAI
lifecycle model, persisted regression cases, controlled fault injection,
and a bounded nightly workflow covering restart and teardown behavior.
- Terminate reply observers, cancel telemetry collectors, bound dashboard
projections, and release child observers, file descriptors, process
records, and inode-verified Unix sockets on every terminal path.
Verified with the compiler-policy contracts, 105 Myelin library tests, 32
swactor-process tests, telemetry cancellation contracts, randomized
stateful restart cases, cargo check, and formatting checks.
2026-08-19 21:38:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Supervisor side variant that delivers each connection header directly to
|
|
|
|
|
/// an actor. Transport owns the subscription task; the actor owns how the
|
|
|
|
|
/// stream identity changes domain state.
|
|
|
|
|
pub fn spawn_pull_collector_to_actor(
|
|
|
|
|
engine: &EngineHandle,
|
2026-08-22 17:16:56 +00:00
|
|
|
config: PullCollectorConfig,
|
feat(myelin): enforce actor-owned control flow
Architecture enforcement:
- Install a repository-owned rustc wrapper for ordinary cargo check,
build, and test commands. Resolve compiler item identities so renamed
imports and helper wrappers cannot hide spawning, timing, blocking,
polling, thread, or runtime-driving capabilities.
- Define the execution-owner crates and reject dependencies from those
substrates back into Myelin policy. Add compile-pass and compile-fail
contracts for actor helpers, execution owners, test waits, forbidden
capabilities, suppression attempts, and owner dependency inversions.
Execution ownership:
- Add engine-owned actor timers with cancellation and generation identity,
then migrate lifecycle deadlines and protocol ticks off application
tasks. Keep networking, process output, telemetry, and blocking provider
calls in their approved I/O substrates.
- Move process spawn, wait, signal, Unix listener, and output-following
mechanics into swactor-process. Isolate Vast.ai blocking HTTP mechanics
behind its adapter while actors retain retry, recovery, and provisioning
decisions.
Myelin control flow:
- Rework manual control, worker lifecycle, provisioning, provider recovery,
job deployment, distribution, edge orchestration, and shutdown as actor
state transitions and typed effects. Preserve durable provider adoption
and command outcomes across graceful and abrupt restarts.
- Replace controller loops and timer-forwarding tasks with actor messages;
leave substrate tasks as cancellable observation streams with no durable
policy state.
Properties and resource ownership:
- Add deterministic engine and component properties, a stateful mock-VastAI
lifecycle model, persisted regression cases, controlled fault injection,
and a bounded nightly workflow covering restart and teardown behavior.
- Terminate reply observers, cancel telemetry collectors, bound dashboard
projections, and release child observers, file descriptors, process
records, and inode-verified Unix sockets on every terminal path.
Verified with the compiler-policy contracts, 105 Myelin library tests, 32
swactor-process tests, telemetry cancellation contracts, randomized
stateful restart cases, cargo check, and formatting checks.
2026-08-19 21:38:14 +00:00
|
|
|
sender: ExternalSender,
|
|
|
|
|
actor: ActorAddress,
|
|
|
|
|
) -> PullCollectorHandle {
|
2026-08-22 17:16:56 +00:00
|
|
|
spawn_pull_collector_with_sink(engine, config, PullHeaderSink::Actor { sender, actor })
|
feat(myelin): enforce actor-owned control flow
Architecture enforcement:
- Install a repository-owned rustc wrapper for ordinary cargo check,
build, and test commands. Resolve compiler item identities so renamed
imports and helper wrappers cannot hide spawning, timing, blocking,
polling, thread, or runtime-driving capabilities.
- Define the execution-owner crates and reject dependencies from those
substrates back into Myelin policy. Add compile-pass and compile-fail
contracts for actor helpers, execution owners, test waits, forbidden
capabilities, suppression attempts, and owner dependency inversions.
Execution ownership:
- Add engine-owned actor timers with cancellation and generation identity,
then migrate lifecycle deadlines and protocol ticks off application
tasks. Keep networking, process output, telemetry, and blocking provider
calls in their approved I/O substrates.
- Move process spawn, wait, signal, Unix listener, and output-following
mechanics into swactor-process. Isolate Vast.ai blocking HTTP mechanics
behind its adapter while actors retain retry, recovery, and provisioning
decisions.
Myelin control flow:
- Rework manual control, worker lifecycle, provisioning, provider recovery,
job deployment, distribution, edge orchestration, and shutdown as actor
state transitions and typed effects. Preserve durable provider adoption
and command outcomes across graceful and abrupt restarts.
- Replace controller loops and timer-forwarding tasks with actor messages;
leave substrate tasks as cancellable observation streams with no durable
policy state.
Properties and resource ownership:
- Add deterministic engine and component properties, a stateful mock-VastAI
lifecycle model, persisted regression cases, controlled fault injection,
and a bounded nightly workflow covering restart and teardown behavior.
- Terminate reply observers, cancel telemetry collectors, bound dashboard
projections, and release child observers, file descriptors, process
records, and inode-verified Unix sockets on every terminal path.
Verified with the compiler-policy contracts, 105 Myelin library tests, 32
swactor-process tests, telemetry cancellation contracts, randomized
stateful restart cases, cargo check, and formatting checks.
2026-08-19 21:38:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum PullHeaderSink {
|
|
|
|
|
Channel(std::sync::mpsc::Sender<TelemetryQuicHeader>),
|
|
|
|
|
Actor {
|
|
|
|
|
sender: ExternalSender,
|
|
|
|
|
actor: ActorAddress,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PullHeaderSink {
|
|
|
|
|
fn deliver(&self, header: TelemetryQuicHeader) -> bool {
|
|
|
|
|
match self {
|
|
|
|
|
Self::Channel(sender) => sender.send(header).is_ok(),
|
|
|
|
|
Self::Actor { sender, actor } => sender.send_to(*actor, header).is_ok(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn spawn_pull_collector_with_sink(
|
|
|
|
|
engine: &EngineHandle,
|
2026-08-22 17:16:56 +00:00
|
|
|
config: PullCollectorConfig,
|
feat(myelin): enforce actor-owned control flow
Architecture enforcement:
- Install a repository-owned rustc wrapper for ordinary cargo check,
build, and test commands. Resolve compiler item identities so renamed
imports and helper wrappers cannot hide spawning, timing, blocking,
polling, thread, or runtime-driving capabilities.
- Define the execution-owner crates and reject dependencies from those
substrates back into Myelin policy. Add compile-pass and compile-fail
contracts for actor helpers, execution owners, test waits, forbidden
capabilities, suppression attempts, and owner dependency inversions.
Execution ownership:
- Add engine-owned actor timers with cancellation and generation identity,
then migrate lifecycle deadlines and protocol ticks off application
tasks. Keep networking, process output, telemetry, and blocking provider
calls in their approved I/O substrates.
- Move process spawn, wait, signal, Unix listener, and output-following
mechanics into swactor-process. Isolate Vast.ai blocking HTTP mechanics
behind its adapter while actors retain retry, recovery, and provisioning
decisions.
Myelin control flow:
- Rework manual control, worker lifecycle, provisioning, provider recovery,
job deployment, distribution, edge orchestration, and shutdown as actor
state transitions and typed effects. Preserve durable provider adoption
and command outcomes across graceful and abrupt restarts.
- Replace controller loops and timer-forwarding tasks with actor messages;
leave substrate tasks as cancellable observation streams with no durable
policy state.
Properties and resource ownership:
- Add deterministic engine and component properties, a stateful mock-VastAI
lifecycle model, persisted regression cases, controlled fault injection,
and a bounded nightly workflow covering restart and teardown behavior.
- Terminate reply observers, cancel telemetry collectors, bound dashboard
projections, and release child observers, file descriptors, process
records, and inode-verified Unix sockets on every terminal path.
Verified with the compiler-policy contracts, 105 Myelin library tests, 32
swactor-process tests, telemetry cancellation contracts, randomized
stateful restart cases, cargo check, and formatting checks.
2026-08-19 21:38:14 +00:00
|
|
|
on_header: PullHeaderSink,
|
|
|
|
|
) -> PullCollectorHandle {
|
2026-08-22 17:16:56 +00:00
|
|
|
let PullCollectorConfig {
|
|
|
|
|
endpoint,
|
|
|
|
|
peer,
|
|
|
|
|
flow_id,
|
|
|
|
|
token,
|
|
|
|
|
request,
|
|
|
|
|
fanout,
|
|
|
|
|
} = config;
|
feat(myelin): enforce actor-owned control flow
Architecture enforcement:
- Install a repository-owned rustc wrapper for ordinary cargo check,
build, and test commands. Resolve compiler item identities so renamed
imports and helper wrappers cannot hide spawning, timing, blocking,
polling, thread, or runtime-driving capabilities.
- Define the execution-owner crates and reject dependencies from those
substrates back into Myelin policy. Add compile-pass and compile-fail
contracts for actor helpers, execution owners, test waits, forbidden
capabilities, suppression attempts, and owner dependency inversions.
Execution ownership:
- Add engine-owned actor timers with cancellation and generation identity,
then migrate lifecycle deadlines and protocol ticks off application
tasks. Keep networking, process output, telemetry, and blocking provider
calls in their approved I/O substrates.
- Move process spawn, wait, signal, Unix listener, and output-following
mechanics into swactor-process. Isolate Vast.ai blocking HTTP mechanics
behind its adapter while actors retain retry, recovery, and provisioning
decisions.
Myelin control flow:
- Rework manual control, worker lifecycle, provisioning, provider recovery,
job deployment, distribution, edge orchestration, and shutdown as actor
state transitions and typed effects. Preserve durable provider adoption
and command outcomes across graceful and abrupt restarts.
- Replace controller loops and timer-forwarding tasks with actor messages;
leave substrate tasks as cancellable observation streams with no durable
policy state.
Properties and resource ownership:
- Add deterministic engine and component properties, a stateful mock-VastAI
lifecycle model, persisted regression cases, controlled fault injection,
and a bounded nightly workflow covering restart and teardown behavior.
- Terminate reply observers, cancel telemetry collectors, bound dashboard
projections, and release child observers, file descriptors, process
records, and inode-verified Unix sockets on every terminal path.
Verified with the compiler-policy contracts, 105 Myelin library tests, 32
swactor-process tests, telemetry cancellation contracts, randomized
stateful restart cases, cargo check, and formatting checks.
2026-08-19 21:38:14 +00:00
|
|
|
let (cancellation, mut cancellation_rx) = tokio::sync::watch::channel(false);
|
|
|
|
|
let (completion, completion_rx) = tokio::sync::watch::channel(false);
|
2026-08-18 10:17:04 +00:00
|
|
|
let engine_handle = engine.clone();
|
provisioning-reconciler-demo: wire-announce readiness + --docker node kind
Replace the per-attempt key-file side channel (DEMO_NODE_KEY_FILE,
read_key_report, JoinCheck) with a control-plane announce: node roles
send a tagged gossip frame {attempt, logical_node, key_hex,
endpoint_addr_json} after joining and every heartbeat thereafter.
- iroh-driver: IrohDriver::send_tagged_gossip — fire-and-forget
tag-routed gossip egress for bridge-less clients (reuses cached/join
connections, dials with backoff).
- provisioning: BootstrapMsg::Announce — first delivery while
bootstrapping completes the attempt (collector + exactly-once
Bootstrapped report); duplicates, misrouted attempts, and
terminal-phase announces drop. Unit-tested.
- xtask demo: AnnounceActor decodes the tag-routed frame and forwards
by attempt to the owning bootstrap actor; last_announce_ms is the
wire heartbeat. LocalProcessLogic keeps only process lifecycle.
- --docker: DockerProcessLogic (kind "docker") — attached
"docker run --rm" child on a per-run labeled bridge network
(foreign-node masking: per-container IPs, gateway-dialed
supervisor). Standalone scratch image from the static-musl xtask
binary (37MB), staged one-file build context. The container is
force-removed on every terminal path so a SIGKILLed docker CLI
cannot orphan a running container.
- Cleanup: no volumes/mounts; label-filtered exit sweep (SIGINT and
SIGTERM both drain first) + startup sweep of stale demo resources;
images persist per run token.
Verified live: process kind (kill -> replacement in 3.4s, provision/
remove/kill waves, zero orphans) and docker kind (8-node abuse waves
across docker kill, mid-provision control kills, CLI SIGKILL orphans
force-removed, SIGKILL-crash leftovers swept on restart, clean exits
leave zero containers/networks/CLIs). provisioning 22 + iroh-driver
13 tests pass.
2026-08-16 16:30:52 +00:00
|
|
|
engine.spawn(async move {
|
feat(myelin): enforce actor-owned control flow
Architecture enforcement:
- Install a repository-owned rustc wrapper for ordinary cargo check,
build, and test commands. Resolve compiler item identities so renamed
imports and helper wrappers cannot hide spawning, timing, blocking,
polling, thread, or runtime-driving capabilities.
- Define the execution-owner crates and reject dependencies from those
substrates back into Myelin policy. Add compile-pass and compile-fail
contracts for actor helpers, execution owners, test waits, forbidden
capabilities, suppression attempts, and owner dependency inversions.
Execution ownership:
- Add engine-owned actor timers with cancellation and generation identity,
then migrate lifecycle deadlines and protocol ticks off application
tasks. Keep networking, process output, telemetry, and blocking provider
calls in their approved I/O substrates.
- Move process spawn, wait, signal, Unix listener, and output-following
mechanics into swactor-process. Isolate Vast.ai blocking HTTP mechanics
behind its adapter while actors retain retry, recovery, and provisioning
decisions.
Myelin control flow:
- Rework manual control, worker lifecycle, provisioning, provider recovery,
job deployment, distribution, edge orchestration, and shutdown as actor
state transitions and typed effects. Preserve durable provider adoption
and command outcomes across graceful and abrupt restarts.
- Replace controller loops and timer-forwarding tasks with actor messages;
leave substrate tasks as cancellable observation streams with no durable
policy state.
Properties and resource ownership:
- Add deterministic engine and component properties, a stateful mock-VastAI
lifecycle model, persisted regression cases, controlled fault injection,
and a bounded nightly workflow covering restart and teardown behavior.
- Terminate reply observers, cancel telemetry collectors, bound dashboard
projections, and release child observers, file descriptors, process
records, and inode-verified Unix sockets on every terminal path.
Verified with the compiler-policy contracts, 105 Myelin library tests, 32
swactor-process tests, telemetry cancellation contracts, randomized
stateful restart cases, cargo check, and formatting checks.
2026-08-19 21:38:14 +00:00
|
|
|
let _completion = PullCollectorCompletion(completion);
|
provisioning-reconciler-demo: wire-announce readiness + --docker node kind
Replace the per-attempt key-file side channel (DEMO_NODE_KEY_FILE,
read_key_report, JoinCheck) with a control-plane announce: node roles
send a tagged gossip frame {attempt, logical_node, key_hex,
endpoint_addr_json} after joining and every heartbeat thereafter.
- iroh-driver: IrohDriver::send_tagged_gossip — fire-and-forget
tag-routed gossip egress for bridge-less clients (reuses cached/join
connections, dials with backoff).
- provisioning: BootstrapMsg::Announce — first delivery while
bootstrapping completes the attempt (collector + exactly-once
Bootstrapped report); duplicates, misrouted attempts, and
terminal-phase announces drop. Unit-tested.
- xtask demo: AnnounceActor decodes the tag-routed frame and forwards
by attempt to the owning bootstrap actor; last_announce_ms is the
wire heartbeat. LocalProcessLogic keeps only process lifecycle.
- --docker: DockerProcessLogic (kind "docker") — attached
"docker run --rm" child on a per-run labeled bridge network
(foreign-node masking: per-container IPs, gateway-dialed
supervisor). Standalone scratch image from the static-musl xtask
binary (37MB), staged one-file build context. The container is
force-removed on every terminal path so a SIGKILLed docker CLI
cannot orphan a running container.
- Cleanup: no volumes/mounts; label-filtered exit sweep (SIGINT and
SIGTERM both drain first) + startup sweep of stale demo resources;
images persist per run token.
Verified live: process kind (kill -> replacement in 3.4s, provision/
remove/kill waves, zero orphans) and docker kind (8-node abuse waves
across docker kill, mid-provision control kills, CLI SIGKILL orphans
force-removed, SIGKILL-crash leftovers swept on restart, clean exits
leave zero containers/networks/CLIs). provisioning 22 + iroh-driver
13 tests pass.
2026-08-16 16:30:52 +00:00
|
|
|
let peer_id = peer.id.to_string();
|
2026-08-18 10:17:04 +00:00
|
|
|
let mut retry_delay = Duration::from_millis(250);
|
|
|
|
|
loop {
|
feat(myelin): enforce actor-owned control flow
Architecture enforcement:
- Install a repository-owned rustc wrapper for ordinary cargo check,
build, and test commands. Resolve compiler item identities so renamed
imports and helper wrappers cannot hide spawning, timing, blocking,
polling, thread, or runtime-driving capabilities.
- Define the execution-owner crates and reject dependencies from those
substrates back into Myelin policy. Add compile-pass and compile-fail
contracts for actor helpers, execution owners, test waits, forbidden
capabilities, suppression attempts, and owner dependency inversions.
Execution ownership:
- Add engine-owned actor timers with cancellation and generation identity,
then migrate lifecycle deadlines and protocol ticks off application
tasks. Keep networking, process output, telemetry, and blocking provider
calls in their approved I/O substrates.
- Move process spawn, wait, signal, Unix listener, and output-following
mechanics into swactor-process. Isolate Vast.ai blocking HTTP mechanics
behind its adapter while actors retain retry, recovery, and provisioning
decisions.
Myelin control flow:
- Rework manual control, worker lifecycle, provisioning, provider recovery,
job deployment, distribution, edge orchestration, and shutdown as actor
state transitions and typed effects. Preserve durable provider adoption
and command outcomes across graceful and abrupt restarts.
- Replace controller loops and timer-forwarding tasks with actor messages;
leave substrate tasks as cancellable observation streams with no durable
policy state.
Properties and resource ownership:
- Add deterministic engine and component properties, a stateful mock-VastAI
lifecycle model, persisted regression cases, controlled fault injection,
and a bounded nightly workflow covering restart and teardown behavior.
- Terminate reply observers, cancel telemetry collectors, bound dashboard
projections, and release child observers, file descriptors, process
records, and inode-verified Unix sockets on every terminal path.
Verified with the compiler-policy contracts, 105 Myelin library tests, 32
swactor-process tests, telemetry cancellation contracts, randomized
stateful restart cases, cargo check, and formatting checks.
2026-08-19 21:38:14 +00:00
|
|
|
if *cancellation_rx.borrow() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let result = tokio::select! {
|
|
|
|
|
_ = cancellation_rx.changed() => return,
|
|
|
|
|
result = collect_pull_once(
|
|
|
|
|
&endpoint, &peer, flow_id, &token, &request, &fanout, &on_header,
|
|
|
|
|
) => result,
|
|
|
|
|
};
|
|
|
|
|
match result {
|
2026-08-18 10:17:04 +00:00
|
|
|
Ok(()) => return,
|
|
|
|
|
Err(error) => {
|
|
|
|
|
eprintln!(
|
|
|
|
|
"telemetry-pull: {peer_id}: {error}; retrying in {} ms",
|
|
|
|
|
retry_delay.as_millis()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
feat(myelin): enforce actor-owned control flow
Architecture enforcement:
- Install a repository-owned rustc wrapper for ordinary cargo check,
build, and test commands. Resolve compiler item identities so renamed
imports and helper wrappers cannot hide spawning, timing, blocking,
polling, thread, or runtime-driving capabilities.
- Define the execution-owner crates and reject dependencies from those
substrates back into Myelin policy. Add compile-pass and compile-fail
contracts for actor helpers, execution owners, test waits, forbidden
capabilities, suppression attempts, and owner dependency inversions.
Execution ownership:
- Add engine-owned actor timers with cancellation and generation identity,
then migrate lifecycle deadlines and protocol ticks off application
tasks. Keep networking, process output, telemetry, and blocking provider
calls in their approved I/O substrates.
- Move process spawn, wait, signal, Unix listener, and output-following
mechanics into swactor-process. Isolate Vast.ai blocking HTTP mechanics
behind its adapter while actors retain retry, recovery, and provisioning
decisions.
Myelin control flow:
- Rework manual control, worker lifecycle, provisioning, provider recovery,
job deployment, distribution, edge orchestration, and shutdown as actor
state transitions and typed effects. Preserve durable provider adoption
and command outcomes across graceful and abrupt restarts.
- Replace controller loops and timer-forwarding tasks with actor messages;
leave substrate tasks as cancellable observation streams with no durable
policy state.
Properties and resource ownership:
- Add deterministic engine and component properties, a stateful mock-VastAI
lifecycle model, persisted regression cases, controlled fault injection,
and a bounded nightly workflow covering restart and teardown behavior.
- Terminate reply observers, cancel telemetry collectors, bound dashboard
projections, and release child observers, file descriptors, process
records, and inode-verified Unix sockets on every terminal path.
Verified with the compiler-policy contracts, 105 Myelin library tests, 32
swactor-process tests, telemetry cancellation contracts, randomized
stateful restart cases, cargo check, and formatting checks.
2026-08-19 21:38:14 +00:00
|
|
|
tokio::select! {
|
|
|
|
|
_ = cancellation_rx.changed() => return,
|
|
|
|
|
_ = engine_handle.timer(retry_delay) => {}
|
|
|
|
|
}
|
2026-08-18 10:17:04 +00:00
|
|
|
retry_delay = retry_delay
|
|
|
|
|
.checked_mul(2)
|
|
|
|
|
.unwrap_or(Duration::from_secs(5))
|
|
|
|
|
.min(Duration::from_secs(5));
|
provisioning-reconciler-demo: wire-announce readiness + --docker node kind
Replace the per-attempt key-file side channel (DEMO_NODE_KEY_FILE,
read_key_report, JoinCheck) with a control-plane announce: node roles
send a tagged gossip frame {attempt, logical_node, key_hex,
endpoint_addr_json} after joining and every heartbeat thereafter.
- iroh-driver: IrohDriver::send_tagged_gossip — fire-and-forget
tag-routed gossip egress for bridge-less clients (reuses cached/join
connections, dials with backoff).
- provisioning: BootstrapMsg::Announce — first delivery while
bootstrapping completes the attempt (collector + exactly-once
Bootstrapped report); duplicates, misrouted attempts, and
terminal-phase announces drop. Unit-tested.
- xtask demo: AnnounceActor decodes the tag-routed frame and forwards
by attempt to the owning bootstrap actor; last_announce_ms is the
wire heartbeat. LocalProcessLogic keeps only process lifecycle.
- --docker: DockerProcessLogic (kind "docker") — attached
"docker run --rm" child on a per-run labeled bridge network
(foreign-node masking: per-container IPs, gateway-dialed
supervisor). Standalone scratch image from the static-musl xtask
binary (37MB), staged one-file build context. The container is
force-removed on every terminal path so a SIGKILLed docker CLI
cannot orphan a running container.
- Cleanup: no volumes/mounts; label-filtered exit sweep (SIGINT and
SIGTERM both drain first) + startup sweep of stale demo resources;
images persist per run token.
Verified live: process kind (kill -> replacement in 3.4s, provision/
remove/kill waves, zero orphans) and docker kind (8-node abuse waves
across docker kill, mid-provision control kills, CLI SIGKILL orphans
force-removed, SIGKILL-crash leftovers swept on restart, clean exits
leave zero containers/networks/CLIs). provisioning 22 + iroh-driver
13 tests pass.
2026-08-16 16:30:52 +00:00
|
|
|
}
|
|
|
|
|
});
|
feat(myelin): enforce actor-owned control flow
Architecture enforcement:
- Install a repository-owned rustc wrapper for ordinary cargo check,
build, and test commands. Resolve compiler item identities so renamed
imports and helper wrappers cannot hide spawning, timing, blocking,
polling, thread, or runtime-driving capabilities.
- Define the execution-owner crates and reject dependencies from those
substrates back into Myelin policy. Add compile-pass and compile-fail
contracts for actor helpers, execution owners, test waits, forbidden
capabilities, suppression attempts, and owner dependency inversions.
Execution ownership:
- Add engine-owned actor timers with cancellation and generation identity,
then migrate lifecycle deadlines and protocol ticks off application
tasks. Keep networking, process output, telemetry, and blocking provider
calls in their approved I/O substrates.
- Move process spawn, wait, signal, Unix listener, and output-following
mechanics into swactor-process. Isolate Vast.ai blocking HTTP mechanics
behind its adapter while actors retain retry, recovery, and provisioning
decisions.
Myelin control flow:
- Rework manual control, worker lifecycle, provisioning, provider recovery,
job deployment, distribution, edge orchestration, and shutdown as actor
state transitions and typed effects. Preserve durable provider adoption
and command outcomes across graceful and abrupt restarts.
- Replace controller loops and timer-forwarding tasks with actor messages;
leave substrate tasks as cancellable observation streams with no durable
policy state.
Properties and resource ownership:
- Add deterministic engine and component properties, a stateful mock-VastAI
lifecycle model, persisted regression cases, controlled fault injection,
and a bounded nightly workflow covering restart and teardown behavior.
- Terminate reply observers, cancel telemetry collectors, bound dashboard
projections, and release child observers, file descriptors, process
records, and inode-verified Unix sockets on every terminal path.
Verified with the compiler-policy contracts, 105 Myelin library tests, 32
swactor-process tests, telemetry cancellation contracts, randomized
stateful restart cases, cargo check, and formatting checks.
2026-08-19 21:38:14 +00:00
|
|
|
PullCollectorHandle {
|
|
|
|
|
cancellation,
|
|
|
|
|
completion: completion_rx,
|
|
|
|
|
}
|
provisioning-reconciler-demo: wire-announce readiness + --docker node kind
Replace the per-attempt key-file side channel (DEMO_NODE_KEY_FILE,
read_key_report, JoinCheck) with a control-plane announce: node roles
send a tagged gossip frame {attempt, logical_node, key_hex,
endpoint_addr_json} after joining and every heartbeat thereafter.
- iroh-driver: IrohDriver::send_tagged_gossip — fire-and-forget
tag-routed gossip egress for bridge-less clients (reuses cached/join
connections, dials with backoff).
- provisioning: BootstrapMsg::Announce — first delivery while
bootstrapping completes the attempt (collector + exactly-once
Bootstrapped report); duplicates, misrouted attempts, and
terminal-phase announces drop. Unit-tested.
- xtask demo: AnnounceActor decodes the tag-routed frame and forwards
by attempt to the owning bootstrap actor; last_announce_ms is the
wire heartbeat. LocalProcessLogic keeps only process lifecycle.
- --docker: DockerProcessLogic (kind "docker") — attached
"docker run --rm" child on a per-run labeled bridge network
(foreign-node masking: per-container IPs, gateway-dialed
supervisor). Standalone scratch image from the static-musl xtask
binary (37MB), staged one-file build context. The container is
force-removed on every terminal path so a SIGKILLed docker CLI
cannot orphan a running container.
- Cleanup: no volumes/mounts; label-filtered exit sweep (SIGINT and
SIGTERM both drain first) + startup sweep of stale demo resources;
images persist per run token.
Verified live: process kind (kill -> replacement in 3.4s, provision/
remove/kill waves, zero orphans) and docker kind (8-node abuse waves
across docker kill, mid-provision control kills, CLI SIGKILL orphans
force-removed, SIGKILL-crash leftovers swept on restart, clean exits
leave zero containers/networks/CLIs). provisioning 22 + iroh-driver
13 tests pass.
2026-08-16 16:30:52 +00:00
|
|
|
}
|
2026-08-18 10:17:04 +00:00
|
|
|
|
|
|
|
|
async fn collect_pull_once(
|
|
|
|
|
endpoint: &Endpoint,
|
|
|
|
|
peer: &EndpointAddr,
|
|
|
|
|
flow_id: [u8; 16],
|
|
|
|
|
token: &[u8],
|
|
|
|
|
request: &telemetry::SubscriptionRequest,
|
|
|
|
|
fanout: &telemetry::DeliveryFanout,
|
feat(myelin): enforce actor-owned control flow
Architecture enforcement:
- Install a repository-owned rustc wrapper for ordinary cargo check,
build, and test commands. Resolve compiler item identities so renamed
imports and helper wrappers cannot hide spawning, timing, blocking,
polling, thread, or runtime-driving capabilities.
- Define the execution-owner crates and reject dependencies from those
substrates back into Myelin policy. Add compile-pass and compile-fail
contracts for actor helpers, execution owners, test waits, forbidden
capabilities, suppression attempts, and owner dependency inversions.
Execution ownership:
- Add engine-owned actor timers with cancellation and generation identity,
then migrate lifecycle deadlines and protocol ticks off application
tasks. Keep networking, process output, telemetry, and blocking provider
calls in their approved I/O substrates.
- Move process spawn, wait, signal, Unix listener, and output-following
mechanics into swactor-process. Isolate Vast.ai blocking HTTP mechanics
behind its adapter while actors retain retry, recovery, and provisioning
decisions.
Myelin control flow:
- Rework manual control, worker lifecycle, provisioning, provider recovery,
job deployment, distribution, edge orchestration, and shutdown as actor
state transitions and typed effects. Preserve durable provider adoption
and command outcomes across graceful and abrupt restarts.
- Replace controller loops and timer-forwarding tasks with actor messages;
leave substrate tasks as cancellable observation streams with no durable
policy state.
Properties and resource ownership:
- Add deterministic engine and component properties, a stateful mock-VastAI
lifecycle model, persisted regression cases, controlled fault injection,
and a bounded nightly workflow covering restart and teardown behavior.
- Terminate reply observers, cancel telemetry collectors, bound dashboard
projections, and release child observers, file descriptors, process
records, and inode-verified Unix sockets on every terminal path.
Verified with the compiler-policy contracts, 105 Myelin library tests, 32
swactor-process tests, telemetry cancellation contracts, randomized
stateful restart cases, cargo check, and formatting checks.
2026-08-19 21:38:14 +00:00
|
|
|
on_header: &PullHeaderSink,
|
2026-08-18 10:17:04 +00:00
|
|
|
) -> Result<(), String> {
|
|
|
|
|
let conn = endpoint
|
|
|
|
|
.connect(peer.clone(), TELEMETRY_ALPN)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|error| format!("connect failed: {error}"))?;
|
|
|
|
|
let mut req = conn
|
|
|
|
|
.open_uni()
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|error| format!("open request stream failed: {error}"))?;
|
|
|
|
|
write_pull_request(&mut req, flow_id, token, request)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|error| format!("write request failed: {error}"))?;
|
|
|
|
|
let mut recv = conn
|
|
|
|
|
.accept_uni()
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|error| format!("no answer stream: {error}"))?;
|
|
|
|
|
let header = read_header(&mut recv)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|error| format!("answer header unreadable: {error}"))?;
|
feat(myelin): enforce actor-owned control flow
Architecture enforcement:
- Install a repository-owned rustc wrapper for ordinary cargo check,
build, and test commands. Resolve compiler item identities so renamed
imports and helper wrappers cannot hide spawning, timing, blocking,
polling, thread, or runtime-driving capabilities.
- Define the execution-owner crates and reject dependencies from those
substrates back into Myelin policy. Add compile-pass and compile-fail
contracts for actor helpers, execution owners, test waits, forbidden
capabilities, suppression attempts, and owner dependency inversions.
Execution ownership:
- Add engine-owned actor timers with cancellation and generation identity,
then migrate lifecycle deadlines and protocol ticks off application
tasks. Keep networking, process output, telemetry, and blocking provider
calls in their approved I/O substrates.
- Move process spawn, wait, signal, Unix listener, and output-following
mechanics into swactor-process. Isolate Vast.ai blocking HTTP mechanics
behind its adapter while actors retain retry, recovery, and provisioning
decisions.
Myelin control flow:
- Rework manual control, worker lifecycle, provisioning, provider recovery,
job deployment, distribution, edge orchestration, and shutdown as actor
state transitions and typed effects. Preserve durable provider adoption
and command outcomes across graceful and abrupt restarts.
- Replace controller loops and timer-forwarding tasks with actor messages;
leave substrate tasks as cancellable observation streams with no durable
policy state.
Properties and resource ownership:
- Add deterministic engine and component properties, a stateful mock-VastAI
lifecycle model, persisted regression cases, controlled fault injection,
and a bounded nightly workflow covering restart and teardown behavior.
- Terminate reply observers, cancel telemetry collectors, bound dashboard
projections, and release child observers, file descriptors, process
records, and inode-verified Unix sockets on every terminal path.
Verified with the compiler-policy contracts, 105 Myelin library tests, 32
swactor-process tests, telemetry cancellation contracts, randomized
stateful restart cases, cargo check, and formatting checks.
2026-08-19 21:38:14 +00:00
|
|
|
if !on_header.deliver(header.clone()) {
|
2026-08-18 10:17:04 +00:00
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
let stream = header.stream;
|
|
|
|
|
loop {
|
|
|
|
|
match read_next_event(&mut recv, &stream).await {
|
|
|
|
|
Ok(Some(event)) => {
|
feat(myelin): enforce actor-owned control flow
Architecture enforcement:
- Install a repository-owned rustc wrapper for ordinary cargo check,
build, and test commands. Resolve compiler item identities so renamed
imports and helper wrappers cannot hide spawning, timing, blocking,
polling, thread, or runtime-driving capabilities.
- Define the execution-owner crates and reject dependencies from those
substrates back into Myelin policy. Add compile-pass and compile-fail
contracts for actor helpers, execution owners, test waits, forbidden
capabilities, suppression attempts, and owner dependency inversions.
Execution ownership:
- Add engine-owned actor timers with cancellation and generation identity,
then migrate lifecycle deadlines and protocol ticks off application
tasks. Keep networking, process output, telemetry, and blocking provider
calls in their approved I/O substrates.
- Move process spawn, wait, signal, Unix listener, and output-following
mechanics into swactor-process. Isolate Vast.ai blocking HTTP mechanics
behind its adapter while actors retain retry, recovery, and provisioning
decisions.
Myelin control flow:
- Rework manual control, worker lifecycle, provisioning, provider recovery,
job deployment, distribution, edge orchestration, and shutdown as actor
state transitions and typed effects. Preserve durable provider adoption
and command outcomes across graceful and abrupt restarts.
- Replace controller loops and timer-forwarding tasks with actor messages;
leave substrate tasks as cancellable observation streams with no durable
policy state.
Properties and resource ownership:
- Add deterministic engine and component properties, a stateful mock-VastAI
lifecycle model, persisted regression cases, controlled fault injection,
and a bounded nightly workflow covering restart and teardown behavior.
- Terminate reply observers, cancel telemetry collectors, bound dashboard
projections, and release child observers, file descriptors, process
records, and inode-verified Unix sockets on every terminal path.
Verified with the compiler-policy contracts, 105 Myelin library tests, 32
swactor-process tests, telemetry cancellation contracts, randomized
stateful restart cases, cargo check, and formatting checks.
2026-08-19 21:38:14 +00:00
|
|
|
let ended = matches!(event, TelemetryEvent::StreamEnded(_));
|
2026-08-18 10:17:04 +00:00
|
|
|
fanout.publish(event);
|
feat(myelin): enforce actor-owned control flow
Architecture enforcement:
- Install a repository-owned rustc wrapper for ordinary cargo check,
build, and test commands. Resolve compiler item identities so renamed
imports and helper wrappers cannot hide spawning, timing, blocking,
polling, thread, or runtime-driving capabilities.
- Define the execution-owner crates and reject dependencies from those
substrates back into Myelin policy. Add compile-pass and compile-fail
contracts for actor helpers, execution owners, test waits, forbidden
capabilities, suppression attempts, and owner dependency inversions.
Execution ownership:
- Add engine-owned actor timers with cancellation and generation identity,
then migrate lifecycle deadlines and protocol ticks off application
tasks. Keep networking, process output, telemetry, and blocking provider
calls in their approved I/O substrates.
- Move process spawn, wait, signal, Unix listener, and output-following
mechanics into swactor-process. Isolate Vast.ai blocking HTTP mechanics
behind its adapter while actors retain retry, recovery, and provisioning
decisions.
Myelin control flow:
- Rework manual control, worker lifecycle, provisioning, provider recovery,
job deployment, distribution, edge orchestration, and shutdown as actor
state transitions and typed effects. Preserve durable provider adoption
and command outcomes across graceful and abrupt restarts.
- Replace controller loops and timer-forwarding tasks with actor messages;
leave substrate tasks as cancellable observation streams with no durable
policy state.
Properties and resource ownership:
- Add deterministic engine and component properties, a stateful mock-VastAI
lifecycle model, persisted regression cases, controlled fault injection,
and a bounded nightly workflow covering restart and teardown behavior.
- Terminate reply observers, cancel telemetry collectors, bound dashboard
projections, and release child observers, file descriptors, process
records, and inode-verified Unix sockets on every terminal path.
Verified with the compiler-policy contracts, 105 Myelin library tests, 32
swactor-process tests, telemetry cancellation contracts, randomized
stateful restart cases, cargo check, and formatting checks.
2026-08-19 21:38:14 +00:00
|
|
|
if ended {
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
2026-08-18 10:17:04 +00:00
|
|
|
}
|
|
|
|
|
Ok(None) => return Err("answer stream closed".to_owned()),
|
|
|
|
|
Err(error) => return Err(format!("read answer stream failed: {error}")),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-06-25 12:30:18 +00:00
|
|
|
const MAX_RECORD_BYTES: usize = 16 * 1024 * 1024;
|
|
|
|
|
|
|
|
|
|
type BoxError = Box<dyn Error + Send + Sync + 'static>;
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
2026-08-15 08:17:48 +00:00
|
|
|
pub struct TelemetryQuicHeader {
|
2026-06-25 12:30:18 +00:00
|
|
|
pub flow_id: [u8; 16],
|
|
|
|
|
pub token: Vec<u8>,
|
2026-07-12 06:14:34 +00:00
|
|
|
pub stream: StreamDescriptor,
|
|
|
|
|
pub channels: Vec<ChannelDescriptor>,
|
2026-06-25 12:30:18 +00:00
|
|
|
}
|
|
|
|
|
|
2026-08-15 08:17:48 +00:00
|
|
|
impl TelemetryQuicHeader {
|
2026-07-12 06:14:34 +00:00
|
|
|
pub fn new(
|
|
|
|
|
flow_id: [u8; 16],
|
|
|
|
|
token: impl Into<Vec<u8>>,
|
|
|
|
|
stream: StreamDescriptor,
|
|
|
|
|
channels: Vec<ChannelDescriptor>,
|
|
|
|
|
) -> Self {
|
2026-06-25 12:30:18 +00:00
|
|
|
Self {
|
|
|
|
|
flow_id,
|
|
|
|
|
token: token.into(),
|
2026-07-12 06:14:34 +00:00
|
|
|
stream,
|
|
|
|
|
channels,
|
2026-06-25 12:30:18 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-12 06:14:34 +00:00
|
|
|
pub fn from_snapshot(
|
|
|
|
|
flow_id: [u8; 16],
|
|
|
|
|
token: impl Into<Vec<u8>>,
|
2026-08-15 08:17:48 +00:00
|
|
|
snapshot: &TelemetrySnapshot,
|
2026-07-12 06:14:34 +00:00
|
|
|
) -> Result<Self, BoxError> {
|
|
|
|
|
let stream = snapshot
|
|
|
|
|
.streams
|
|
|
|
|
.first()
|
|
|
|
|
.cloned()
|
2026-08-15 08:17:48 +00:00
|
|
|
.ok_or("telemetry subscription snapshot has no stream")?;
|
2026-07-12 06:14:34 +00:00
|
|
|
Ok(Self::new(flow_id, token, stream, snapshot.channels.clone()))
|
2026-06-25 12:30:18 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
2026-08-15 08:17:48 +00:00
|
|
|
pub struct TelemetryQuicWriteStats {
|
2026-07-12 06:14:34 +00:00
|
|
|
pub events: usize,
|
2026-06-25 12:30:18 +00:00
|
|
|
pub bytes: usize,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
2026-08-15 08:17:48 +00:00
|
|
|
pub struct TelemetryQuicRead {
|
|
|
|
|
pub header: TelemetryQuicHeader,
|
|
|
|
|
pub events: Vec<TelemetryEvent>,
|
2026-06-25 12:30:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn spawn_subscription_writer(
|
feat(engine): substrate-neutral execution engine abstraction
Introduce the swactor engine: a swactor-owned composite that retains a
selected execution substrate, drives the core runtime, and hosts the
async/blocking/timer work that backs actors. Integrations receive one
cloneable EngineHandle and never construct or borrow a raw Tokio
runtime/handle.
Engine crate (crates/engine):
- The contract: spawn / spawn_blocking / timer / interval / now, a
per-implementation capability model with construction-time binding
(require()), and engine-owned time. The engine owns all progression;
actor handlers stay synchronous and never .await.
- TokioBackend owns the Tokio runtime and schedules core ticks and
supporting futures on it; SteppingBackend is a single-threaded
deterministic scheduler with virtual time (the non-Tokio portability
proof). Core is driven through its existing tick() surface; a
self-rescheduling CoreDriver is installed at construction and is the
sole place permitted to call try_tick.
iroh-driver:
- Receives an EngineHandle instead of a raw Tokio Handle. Accepts,
reads, dials, writes, endpoint construction, and teardown schedule
through it; required capabilities (tasks/timers/io) are validated
before the endpoint binds. Engine-hosted interval pumps drive
actor-bridge, datastream, and edge ingress.
myelin:
- One node/orchestrator engine owns core, protocol tick injection, and
transport progression; the application loop only drains
integration-owned queues. Stage-shard process readers, delayed actor
messages, helper stdout/stderr, prompt RPC, and CPU sampling all
schedule through the engine (spawn_blocking / engine tasks / timers).
- Removed the split-engine APIs: install_actor_bridge_pump(period) and
spawn_protocol_ticker(period) use each component's stored engine;
deleted the no-op pump_network callback and its plumbing; deleted the
dashboard raw-Tokio/standalone-runtime conveniences.
Enforcement:
- A clippy disallowed-methods boundary forbids direct runtime/scheduling/
time/core-driving bypasses, denied in swactor-engine, iroh-driver, and
myelin. Retained excluded uses (VastAI provider, provider process
supervision/log capture, OS-signal/stdin/process-control sequencing)
carry narrow allowances with reasons.
Verification:
- Engine contract + unit tests (incl. the SteppingBackend portability
proof), iroh integration tests (capability rejection before binding,
multi-node actor behavior), and a production execution-composition
smoke test that observes engine-driven actor progress with no ambient
Tokio runtime and no manual tick/pump. Workspace all-target/all-feature
clippy and tests are green.
Specs co-located with their crates: ENGINE_SPEC.md in crates/engine,
IROH_DRIVER_SPEC.md in crates/iroh-driver. VastAI remains explicitly out
of scope pending its separate redesign.
2026-08-10 20:23:03 +00:00
|
|
|
engine: &EngineHandle,
|
2026-06-25 12:30:18 +00:00
|
|
|
endpoint: Endpoint,
|
|
|
|
|
peer: EndpointAddr,
|
2026-08-15 08:17:48 +00:00
|
|
|
header: TelemetryQuicHeader,
|
|
|
|
|
subscription: TelemetrySubscription,
|
2026-06-25 12:30:18 +00:00
|
|
|
idle_sleep: Duration,
|
feat(engine): substrate-neutral execution engine abstraction
Introduce the swactor engine: a swactor-owned composite that retains a
selected execution substrate, drives the core runtime, and hosts the
async/blocking/timer work that backs actors. Integrations receive one
cloneable EngineHandle and never construct or borrow a raw Tokio
runtime/handle.
Engine crate (crates/engine):
- The contract: spawn / spawn_blocking / timer / interval / now, a
per-implementation capability model with construction-time binding
(require()), and engine-owned time. The engine owns all progression;
actor handlers stay synchronous and never .await.
- TokioBackend owns the Tokio runtime and schedules core ticks and
supporting futures on it; SteppingBackend is a single-threaded
deterministic scheduler with virtual time (the non-Tokio portability
proof). Core is driven through its existing tick() surface; a
self-rescheduling CoreDriver is installed at construction and is the
sole place permitted to call try_tick.
iroh-driver:
- Receives an EngineHandle instead of a raw Tokio Handle. Accepts,
reads, dials, writes, endpoint construction, and teardown schedule
through it; required capabilities (tasks/timers/io) are validated
before the endpoint binds. Engine-hosted interval pumps drive
actor-bridge, datastream, and edge ingress.
myelin:
- One node/orchestrator engine owns core, protocol tick injection, and
transport progression; the application loop only drains
integration-owned queues. Stage-shard process readers, delayed actor
messages, helper stdout/stderr, prompt RPC, and CPU sampling all
schedule through the engine (spawn_blocking / engine tasks / timers).
- Removed the split-engine APIs: install_actor_bridge_pump(period) and
spawn_protocol_ticker(period) use each component's stored engine;
deleted the no-op pump_network callback and its plumbing; deleted the
dashboard raw-Tokio/standalone-runtime conveniences.
Enforcement:
- A clippy disallowed-methods boundary forbids direct runtime/scheduling/
time/core-driving bypasses, denied in swactor-engine, iroh-driver, and
myelin. Retained excluded uses (VastAI provider, provider process
supervision/log capture, OS-signal/stdin/process-control sequencing)
carry narrow allowances with reasons.
Verification:
- Engine contract + unit tests (incl. the SteppingBackend portability
proof), iroh integration tests (capability rejection before binding,
multi-node actor behavior), and a production execution-composition
smoke test that observes engine-driven actor progress with no ambient
Tokio runtime and no manual tick/pump. Workspace all-target/all-feature
clippy and tests are green.
Specs co-located with their crates: ENGINE_SPEC.md in crates/engine,
IROH_DRIVER_SPEC.md in crates/iroh-driver. VastAI remains explicitly out
of scope pending its separate redesign.
2026-08-10 20:23:03 +00:00
|
|
|
) {
|
|
|
|
|
let engine_handle = engine.clone();
|
|
|
|
|
engine.spawn(async move {
|
2026-08-15 08:17:48 +00:00
|
|
|
let Ok(conn) = endpoint.connect(peer, TELEMETRY_ALPN).await else {
|
feat(engine): substrate-neutral execution engine abstraction
Introduce the swactor engine: a swactor-owned composite that retains a
selected execution substrate, drives the core runtime, and hosts the
async/blocking/timer work that backs actors. Integrations receive one
cloneable EngineHandle and never construct or borrow a raw Tokio
runtime/handle.
Engine crate (crates/engine):
- The contract: spawn / spawn_blocking / timer / interval / now, a
per-implementation capability model with construction-time binding
(require()), and engine-owned time. The engine owns all progression;
actor handlers stay synchronous and never .await.
- TokioBackend owns the Tokio runtime and schedules core ticks and
supporting futures on it; SteppingBackend is a single-threaded
deterministic scheduler with virtual time (the non-Tokio portability
proof). Core is driven through its existing tick() surface; a
self-rescheduling CoreDriver is installed at construction and is the
sole place permitted to call try_tick.
iroh-driver:
- Receives an EngineHandle instead of a raw Tokio Handle. Accepts,
reads, dials, writes, endpoint construction, and teardown schedule
through it; required capabilities (tasks/timers/io) are validated
before the endpoint binds. Engine-hosted interval pumps drive
actor-bridge, datastream, and edge ingress.
myelin:
- One node/orchestrator engine owns core, protocol tick injection, and
transport progression; the application loop only drains
integration-owned queues. Stage-shard process readers, delayed actor
messages, helper stdout/stderr, prompt RPC, and CPU sampling all
schedule through the engine (spawn_blocking / engine tasks / timers).
- Removed the split-engine APIs: install_actor_bridge_pump(period) and
spawn_protocol_ticker(period) use each component's stored engine;
deleted the no-op pump_network callback and its plumbing; deleted the
dashboard raw-Tokio/standalone-runtime conveniences.
Enforcement:
- A clippy disallowed-methods boundary forbids direct runtime/scheduling/
time/core-driving bypasses, denied in swactor-engine, iroh-driver, and
myelin. Retained excluded uses (VastAI provider, provider process
supervision/log capture, OS-signal/stdin/process-control sequencing)
carry narrow allowances with reasons.
Verification:
- Engine contract + unit tests (incl. the SteppingBackend portability
proof), iroh integration tests (capability rejection before binding,
multi-node actor behavior), and a production execution-composition
smoke test that observes engine-driven actor progress with no ambient
Tokio runtime and no manual tick/pump. Workspace all-target/all-feature
clippy and tests are green.
Specs co-located with their crates: ENGINE_SPEC.md in crates/engine,
IROH_DRIVER_SPEC.md in crates/iroh-driver. VastAI remains explicitly out
of scope pending its separate redesign.
2026-08-10 20:23:03 +00:00
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
let Ok(send) = conn.open_uni().await else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
provisioning-reconciler-demo: wire-announce readiness + --docker node kind
Replace the per-attempt key-file side channel (DEMO_NODE_KEY_FILE,
read_key_report, JoinCheck) with a control-plane announce: node roles
send a tagged gossip frame {attempt, logical_node, key_hex,
endpoint_addr_json} after joining and every heartbeat thereafter.
- iroh-driver: IrohDriver::send_tagged_gossip — fire-and-forget
tag-routed gossip egress for bridge-less clients (reuses cached/join
connections, dials with backoff).
- provisioning: BootstrapMsg::Announce — first delivery while
bootstrapping completes the attempt (collector + exactly-once
Bootstrapped report); duplicates, misrouted attempts, and
terminal-phase announces drop. Unit-tested.
- xtask demo: AnnounceActor decodes the tag-routed frame and forwards
by attempt to the owning bootstrap actor; last_announce_ms is the
wire heartbeat. LocalProcessLogic keeps only process lifecycle.
- --docker: DockerProcessLogic (kind "docker") — attached
"docker run --rm" child on a per-run labeled bridge network
(foreign-node masking: per-container IPs, gateway-dialed
supervisor). Standalone scratch image from the static-musl xtask
binary (37MB), staged one-file build context. The container is
force-removed on every terminal path so a SIGKILLed docker CLI
cannot orphan a running container.
- Cleanup: no volumes/mounts; label-filtered exit sweep (SIGINT and
SIGTERM both drain first) + startup sweep of stale demo resources;
images persist per run token.
Verified live: process kind (kill -> replacement in 3.4s, provision/
remove/kill waves, zero orphans) and docker kind (8-node abuse waves
across docker kill, mid-provision control kills, CLI SIGKILL orphans
force-removed, SIGKILL-crash leftovers swept on restart, clean exits
leave zero containers/networks/CLIs). provisioning 22 + iroh-driver
13 tests pass.
2026-08-16 16:30:52 +00:00
|
|
|
let _ =
|
|
|
|
|
write_subscription_until_closed(&engine_handle, send, header, subscription, idle_sleep)
|
|
|
|
|
.await;
|
feat(engine): substrate-neutral execution engine abstraction
Introduce the swactor engine: a swactor-owned composite that retains a
selected execution substrate, drives the core runtime, and hosts the
async/blocking/timer work that backs actors. Integrations receive one
cloneable EngineHandle and never construct or borrow a raw Tokio
runtime/handle.
Engine crate (crates/engine):
- The contract: spawn / spawn_blocking / timer / interval / now, a
per-implementation capability model with construction-time binding
(require()), and engine-owned time. The engine owns all progression;
actor handlers stay synchronous and never .await.
- TokioBackend owns the Tokio runtime and schedules core ticks and
supporting futures on it; SteppingBackend is a single-threaded
deterministic scheduler with virtual time (the non-Tokio portability
proof). Core is driven through its existing tick() surface; a
self-rescheduling CoreDriver is installed at construction and is the
sole place permitted to call try_tick.
iroh-driver:
- Receives an EngineHandle instead of a raw Tokio Handle. Accepts,
reads, dials, writes, endpoint construction, and teardown schedule
through it; required capabilities (tasks/timers/io) are validated
before the endpoint binds. Engine-hosted interval pumps drive
actor-bridge, datastream, and edge ingress.
myelin:
- One node/orchestrator engine owns core, protocol tick injection, and
transport progression; the application loop only drains
integration-owned queues. Stage-shard process readers, delayed actor
messages, helper stdout/stderr, prompt RPC, and CPU sampling all
schedule through the engine (spawn_blocking / engine tasks / timers).
- Removed the split-engine APIs: install_actor_bridge_pump(period) and
spawn_protocol_ticker(period) use each component's stored engine;
deleted the no-op pump_network callback and its plumbing; deleted the
dashboard raw-Tokio/standalone-runtime conveniences.
Enforcement:
- A clippy disallowed-methods boundary forbids direct runtime/scheduling/
time/core-driving bypasses, denied in swactor-engine, iroh-driver, and
myelin. Retained excluded uses (VastAI provider, provider process
supervision/log capture, OS-signal/stdin/process-control sequencing)
carry narrow allowances with reasons.
Verification:
- Engine contract + unit tests (incl. the SteppingBackend portability
proof), iroh integration tests (capability rejection before binding,
multi-node actor behavior), and a production execution-composition
smoke test that observes engine-driven actor progress with no ambient
Tokio runtime and no manual tick/pump. Workspace all-target/all-feature
clippy and tests are green.
Specs co-located with their crates: ENGINE_SPEC.md in crates/engine,
IROH_DRIVER_SPEC.md in crates/iroh-driver. VastAI remains explicitly out
of scope pending its separate redesign.
2026-08-10 20:23:03 +00:00
|
|
|
});
|
2026-06-25 12:30:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn write_available_subscription(
|
feat(engine): substrate-neutral execution engine abstraction
Introduce the swactor engine: a swactor-owned composite that retains a
selected execution substrate, drives the core runtime, and hosts the
async/blocking/timer work that backs actors. Integrations receive one
cloneable EngineHandle and never construct or borrow a raw Tokio
runtime/handle.
Engine crate (crates/engine):
- The contract: spawn / spawn_blocking / timer / interval / now, a
per-implementation capability model with construction-time binding
(require()), and engine-owned time. The engine owns all progression;
actor handlers stay synchronous and never .await.
- TokioBackend owns the Tokio runtime and schedules core ticks and
supporting futures on it; SteppingBackend is a single-threaded
deterministic scheduler with virtual time (the non-Tokio portability
proof). Core is driven through its existing tick() surface; a
self-rescheduling CoreDriver is installed at construction and is the
sole place permitted to call try_tick.
iroh-driver:
- Receives an EngineHandle instead of a raw Tokio Handle. Accepts,
reads, dials, writes, endpoint construction, and teardown schedule
through it; required capabilities (tasks/timers/io) are validated
before the endpoint binds. Engine-hosted interval pumps drive
actor-bridge, datastream, and edge ingress.
myelin:
- One node/orchestrator engine owns core, protocol tick injection, and
transport progression; the application loop only drains
integration-owned queues. Stage-shard process readers, delayed actor
messages, helper stdout/stderr, prompt RPC, and CPU sampling all
schedule through the engine (spawn_blocking / engine tasks / timers).
- Removed the split-engine APIs: install_actor_bridge_pump(period) and
spawn_protocol_ticker(period) use each component's stored engine;
deleted the no-op pump_network callback and its plumbing; deleted the
dashboard raw-Tokio/standalone-runtime conveniences.
Enforcement:
- A clippy disallowed-methods boundary forbids direct runtime/scheduling/
time/core-driving bypasses, denied in swactor-engine, iroh-driver, and
myelin. Retained excluded uses (VastAI provider, provider process
supervision/log capture, OS-signal/stdin/process-control sequencing)
carry narrow allowances with reasons.
Verification:
- Engine contract + unit tests (incl. the SteppingBackend portability
proof), iroh integration tests (capability rejection before binding,
multi-node actor behavior), and a production execution-composition
smoke test that observes engine-driven actor progress with no ambient
Tokio runtime and no manual tick/pump. Workspace all-target/all-feature
clippy and tests are green.
Specs co-located with their crates: ENGINE_SPEC.md in crates/engine,
IROH_DRIVER_SPEC.md in crates/iroh-driver. VastAI remains explicitly out
of scope pending its separate redesign.
2026-08-10 20:23:03 +00:00
|
|
|
engine: &EngineHandle,
|
2026-06-25 12:30:18 +00:00
|
|
|
send: SendStream,
|
2026-08-15 08:17:48 +00:00
|
|
|
header: &TelemetryQuicHeader,
|
|
|
|
|
subscription: &TelemetrySubscription,
|
|
|
|
|
) -> Result<TelemetryQuicWriteStats, BoxError> {
|
feat(engine): substrate-neutral execution engine abstraction
Introduce the swactor engine: a swactor-owned composite that retains a
selected execution substrate, drives the core runtime, and hosts the
async/blocking/timer work that backs actors. Integrations receive one
cloneable EngineHandle and never construct or borrow a raw Tokio
runtime/handle.
Engine crate (crates/engine):
- The contract: spawn / spawn_blocking / timer / interval / now, a
per-implementation capability model with construction-time binding
(require()), and engine-owned time. The engine owns all progression;
actor handlers stay synchronous and never .await.
- TokioBackend owns the Tokio runtime and schedules core ticks and
supporting futures on it; SteppingBackend is a single-threaded
deterministic scheduler with virtual time (the non-Tokio portability
proof). Core is driven through its existing tick() surface; a
self-rescheduling CoreDriver is installed at construction and is the
sole place permitted to call try_tick.
iroh-driver:
- Receives an EngineHandle instead of a raw Tokio Handle. Accepts,
reads, dials, writes, endpoint construction, and teardown schedule
through it; required capabilities (tasks/timers/io) are validated
before the endpoint binds. Engine-hosted interval pumps drive
actor-bridge, datastream, and edge ingress.
myelin:
- One node/orchestrator engine owns core, protocol tick injection, and
transport progression; the application loop only drains
integration-owned queues. Stage-shard process readers, delayed actor
messages, helper stdout/stderr, prompt RPC, and CPU sampling all
schedule through the engine (spawn_blocking / engine tasks / timers).
- Removed the split-engine APIs: install_actor_bridge_pump(period) and
spawn_protocol_ticker(period) use each component's stored engine;
deleted the no-op pump_network callback and its plumbing; deleted the
dashboard raw-Tokio/standalone-runtime conveniences.
Enforcement:
- A clippy disallowed-methods boundary forbids direct runtime/scheduling/
time/core-driving bypasses, denied in swactor-engine, iroh-driver, and
myelin. Retained excluded uses (VastAI provider, provider process
supervision/log capture, OS-signal/stdin/process-control sequencing)
carry narrow allowances with reasons.
Verification:
- Engine contract + unit tests (incl. the SteppingBackend portability
proof), iroh integration tests (capability rejection before binding,
multi-node actor behavior), and a production execution-composition
smoke test that observes engine-driven actor progress with no ambient
Tokio runtime and no manual tick/pump. Workspace all-target/all-feature
clippy and tests are green.
Specs co-located with their crates: ENGINE_SPEC.md in crates/engine,
IROH_DRIVER_SPEC.md in crates/iroh-driver. VastAI remains explicitly out
of scope pending its separate redesign.
2026-08-10 20:23:03 +00:00
|
|
|
write_subscription_inner(engine, send, header, subscription, None).await
|
2026-06-25 12:30:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn write_subscription_until_closed(
|
feat(engine): substrate-neutral execution engine abstraction
Introduce the swactor engine: a swactor-owned composite that retains a
selected execution substrate, drives the core runtime, and hosts the
async/blocking/timer work that backs actors. Integrations receive one
cloneable EngineHandle and never construct or borrow a raw Tokio
runtime/handle.
Engine crate (crates/engine):
- The contract: spawn / spawn_blocking / timer / interval / now, a
per-implementation capability model with construction-time binding
(require()), and engine-owned time. The engine owns all progression;
actor handlers stay synchronous and never .await.
- TokioBackend owns the Tokio runtime and schedules core ticks and
supporting futures on it; SteppingBackend is a single-threaded
deterministic scheduler with virtual time (the non-Tokio portability
proof). Core is driven through its existing tick() surface; a
self-rescheduling CoreDriver is installed at construction and is the
sole place permitted to call try_tick.
iroh-driver:
- Receives an EngineHandle instead of a raw Tokio Handle. Accepts,
reads, dials, writes, endpoint construction, and teardown schedule
through it; required capabilities (tasks/timers/io) are validated
before the endpoint binds. Engine-hosted interval pumps drive
actor-bridge, datastream, and edge ingress.
myelin:
- One node/orchestrator engine owns core, protocol tick injection, and
transport progression; the application loop only drains
integration-owned queues. Stage-shard process readers, delayed actor
messages, helper stdout/stderr, prompt RPC, and CPU sampling all
schedule through the engine (spawn_blocking / engine tasks / timers).
- Removed the split-engine APIs: install_actor_bridge_pump(period) and
spawn_protocol_ticker(period) use each component's stored engine;
deleted the no-op pump_network callback and its plumbing; deleted the
dashboard raw-Tokio/standalone-runtime conveniences.
Enforcement:
- A clippy disallowed-methods boundary forbids direct runtime/scheduling/
time/core-driving bypasses, denied in swactor-engine, iroh-driver, and
myelin. Retained excluded uses (VastAI provider, provider process
supervision/log capture, OS-signal/stdin/process-control sequencing)
carry narrow allowances with reasons.
Verification:
- Engine contract + unit tests (incl. the SteppingBackend portability
proof), iroh integration tests (capability rejection before binding,
multi-node actor behavior), and a production execution-composition
smoke test that observes engine-driven actor progress with no ambient
Tokio runtime and no manual tick/pump. Workspace all-target/all-feature
clippy and tests are green.
Specs co-located with their crates: ENGINE_SPEC.md in crates/engine,
IROH_DRIVER_SPEC.md in crates/iroh-driver. VastAI remains explicitly out
of scope pending its separate redesign.
2026-08-10 20:23:03 +00:00
|
|
|
engine: &EngineHandle,
|
2026-06-25 12:30:18 +00:00
|
|
|
mut send: SendStream,
|
2026-08-15 08:17:48 +00:00
|
|
|
header: TelemetryQuicHeader,
|
|
|
|
|
subscription: TelemetrySubscription,
|
2026-06-25 12:30:18 +00:00
|
|
|
idle_sleep: Duration,
|
2026-08-15 08:17:48 +00:00
|
|
|
) -> Result<TelemetryQuicWriteStats, BoxError> {
|
2026-06-25 12:30:18 +00:00
|
|
|
write_header(&mut send, &header).await?;
|
2026-08-15 08:17:48 +00:00
|
|
|
let mut stats = TelemetryQuicWriteStats::default();
|
2026-06-25 12:30:18 +00:00
|
|
|
loop {
|
|
|
|
|
match subscription.try_recv() {
|
2026-07-12 06:14:34 +00:00
|
|
|
Ok(event) => {
|
|
|
|
|
let bytes = write_event(&mut send, &event).await?;
|
|
|
|
|
if bytes > 0 {
|
|
|
|
|
stats.bytes += bytes;
|
|
|
|
|
stats.events += 1;
|
|
|
|
|
}
|
2026-06-25 12:30:18 +00:00
|
|
|
}
|
2026-07-21 06:25:52 +00:00
|
|
|
Err(TryRecvError::Empty) => {
|
feat(engine): substrate-neutral execution engine abstraction
Introduce the swactor engine: a swactor-owned composite that retains a
selected execution substrate, drives the core runtime, and hosts the
async/blocking/timer work that backs actors. Integrations receive one
cloneable EngineHandle and never construct or borrow a raw Tokio
runtime/handle.
Engine crate (crates/engine):
- The contract: spawn / spawn_blocking / timer / interval / now, a
per-implementation capability model with construction-time binding
(require()), and engine-owned time. The engine owns all progression;
actor handlers stay synchronous and never .await.
- TokioBackend owns the Tokio runtime and schedules core ticks and
supporting futures on it; SteppingBackend is a single-threaded
deterministic scheduler with virtual time (the non-Tokio portability
proof). Core is driven through its existing tick() surface; a
self-rescheduling CoreDriver is installed at construction and is the
sole place permitted to call try_tick.
iroh-driver:
- Receives an EngineHandle instead of a raw Tokio Handle. Accepts,
reads, dials, writes, endpoint construction, and teardown schedule
through it; required capabilities (tasks/timers/io) are validated
before the endpoint binds. Engine-hosted interval pumps drive
actor-bridge, datastream, and edge ingress.
myelin:
- One node/orchestrator engine owns core, protocol tick injection, and
transport progression; the application loop only drains
integration-owned queues. Stage-shard process readers, delayed actor
messages, helper stdout/stderr, prompt RPC, and CPU sampling all
schedule through the engine (spawn_blocking / engine tasks / timers).
- Removed the split-engine APIs: install_actor_bridge_pump(period) and
spawn_protocol_ticker(period) use each component's stored engine;
deleted the no-op pump_network callback and its plumbing; deleted the
dashboard raw-Tokio/standalone-runtime conveniences.
Enforcement:
- A clippy disallowed-methods boundary forbids direct runtime/scheduling/
time/core-driving bypasses, denied in swactor-engine, iroh-driver, and
myelin. Retained excluded uses (VastAI provider, provider process
supervision/log capture, OS-signal/stdin/process-control sequencing)
carry narrow allowances with reasons.
Verification:
- Engine contract + unit tests (incl. the SteppingBackend portability
proof), iroh integration tests (capability rejection before binding,
multi-node actor behavior), and a production execution-composition
smoke test that observes engine-driven actor progress with no ambient
Tokio runtime and no manual tick/pump. Workspace all-target/all-feature
clippy and tests are green.
Specs co-located with their crates: ENGINE_SPEC.md in crates/engine,
IROH_DRIVER_SPEC.md in crates/iroh-driver. VastAI remains explicitly out
of scope pending its separate redesign.
2026-08-10 20:23:03 +00:00
|
|
|
engine.timer(idle_sleep).await;
|
2026-06-25 12:30:18 +00:00
|
|
|
}
|
2026-07-21 06:25:52 +00:00
|
|
|
Err(TryRecvError::Disconnected) => break,
|
2026-06-25 12:30:18 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
send.finish()?;
|
|
|
|
|
Ok(stats)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn write_subscription_inner(
|
feat(engine): substrate-neutral execution engine abstraction
Introduce the swactor engine: a swactor-owned composite that retains a
selected execution substrate, drives the core runtime, and hosts the
async/blocking/timer work that backs actors. Integrations receive one
cloneable EngineHandle and never construct or borrow a raw Tokio
runtime/handle.
Engine crate (crates/engine):
- The contract: spawn / spawn_blocking / timer / interval / now, a
per-implementation capability model with construction-time binding
(require()), and engine-owned time. The engine owns all progression;
actor handlers stay synchronous and never .await.
- TokioBackend owns the Tokio runtime and schedules core ticks and
supporting futures on it; SteppingBackend is a single-threaded
deterministic scheduler with virtual time (the non-Tokio portability
proof). Core is driven through its existing tick() surface; a
self-rescheduling CoreDriver is installed at construction and is the
sole place permitted to call try_tick.
iroh-driver:
- Receives an EngineHandle instead of a raw Tokio Handle. Accepts,
reads, dials, writes, endpoint construction, and teardown schedule
through it; required capabilities (tasks/timers/io) are validated
before the endpoint binds. Engine-hosted interval pumps drive
actor-bridge, datastream, and edge ingress.
myelin:
- One node/orchestrator engine owns core, protocol tick injection, and
transport progression; the application loop only drains
integration-owned queues. Stage-shard process readers, delayed actor
messages, helper stdout/stderr, prompt RPC, and CPU sampling all
schedule through the engine (spawn_blocking / engine tasks / timers).
- Removed the split-engine APIs: install_actor_bridge_pump(period) and
spawn_protocol_ticker(period) use each component's stored engine;
deleted the no-op pump_network callback and its plumbing; deleted the
dashboard raw-Tokio/standalone-runtime conveniences.
Enforcement:
- A clippy disallowed-methods boundary forbids direct runtime/scheduling/
time/core-driving bypasses, denied in swactor-engine, iroh-driver, and
myelin. Retained excluded uses (VastAI provider, provider process
supervision/log capture, OS-signal/stdin/process-control sequencing)
carry narrow allowances with reasons.
Verification:
- Engine contract + unit tests (incl. the SteppingBackend portability
proof), iroh integration tests (capability rejection before binding,
multi-node actor behavior), and a production execution-composition
smoke test that observes engine-driven actor progress with no ambient
Tokio runtime and no manual tick/pump. Workspace all-target/all-feature
clippy and tests are green.
Specs co-located with their crates: ENGINE_SPEC.md in crates/engine,
IROH_DRIVER_SPEC.md in crates/iroh-driver. VastAI remains explicitly out
of scope pending its separate redesign.
2026-08-10 20:23:03 +00:00
|
|
|
engine: &EngineHandle,
|
2026-06-25 12:30:18 +00:00
|
|
|
mut send: SendStream,
|
2026-08-15 08:17:48 +00:00
|
|
|
header: &TelemetryQuicHeader,
|
|
|
|
|
subscription: &TelemetrySubscription,
|
2026-06-25 12:30:18 +00:00
|
|
|
idle_sleep: Option<Duration>,
|
2026-08-15 08:17:48 +00:00
|
|
|
) -> Result<TelemetryQuicWriteStats, BoxError> {
|
2026-06-25 12:30:18 +00:00
|
|
|
write_header(&mut send, header).await?;
|
2026-08-15 08:17:48 +00:00
|
|
|
let mut stats = TelemetryQuicWriteStats::default();
|
2026-06-25 12:30:18 +00:00
|
|
|
loop {
|
|
|
|
|
match subscription.try_recv() {
|
2026-07-12 06:14:34 +00:00
|
|
|
Ok(event) => {
|
|
|
|
|
let bytes = write_event(&mut send, &event).await?;
|
|
|
|
|
if bytes > 0 {
|
|
|
|
|
stats.bytes += bytes;
|
|
|
|
|
stats.events += 1;
|
|
|
|
|
}
|
2026-06-25 12:30:18 +00:00
|
|
|
}
|
2026-07-21 06:25:52 +00:00
|
|
|
Err(TryRecvError::Empty) => match idle_sleep {
|
feat(engine): substrate-neutral execution engine abstraction
Introduce the swactor engine: a swactor-owned composite that retains a
selected execution substrate, drives the core runtime, and hosts the
async/blocking/timer work that backs actors. Integrations receive one
cloneable EngineHandle and never construct or borrow a raw Tokio
runtime/handle.
Engine crate (crates/engine):
- The contract: spawn / spawn_blocking / timer / interval / now, a
per-implementation capability model with construction-time binding
(require()), and engine-owned time. The engine owns all progression;
actor handlers stay synchronous and never .await.
- TokioBackend owns the Tokio runtime and schedules core ticks and
supporting futures on it; SteppingBackend is a single-threaded
deterministic scheduler with virtual time (the non-Tokio portability
proof). Core is driven through its existing tick() surface; a
self-rescheduling CoreDriver is installed at construction and is the
sole place permitted to call try_tick.
iroh-driver:
- Receives an EngineHandle instead of a raw Tokio Handle. Accepts,
reads, dials, writes, endpoint construction, and teardown schedule
through it; required capabilities (tasks/timers/io) are validated
before the endpoint binds. Engine-hosted interval pumps drive
actor-bridge, datastream, and edge ingress.
myelin:
- One node/orchestrator engine owns core, protocol tick injection, and
transport progression; the application loop only drains
integration-owned queues. Stage-shard process readers, delayed actor
messages, helper stdout/stderr, prompt RPC, and CPU sampling all
schedule through the engine (spawn_blocking / engine tasks / timers).
- Removed the split-engine APIs: install_actor_bridge_pump(period) and
spawn_protocol_ticker(period) use each component's stored engine;
deleted the no-op pump_network callback and its plumbing; deleted the
dashboard raw-Tokio/standalone-runtime conveniences.
Enforcement:
- A clippy disallowed-methods boundary forbids direct runtime/scheduling/
time/core-driving bypasses, denied in swactor-engine, iroh-driver, and
myelin. Retained excluded uses (VastAI provider, provider process
supervision/log capture, OS-signal/stdin/process-control sequencing)
carry narrow allowances with reasons.
Verification:
- Engine contract + unit tests (incl. the SteppingBackend portability
proof), iroh integration tests (capability rejection before binding,
multi-node actor behavior), and a production execution-composition
smoke test that observes engine-driven actor progress with no ambient
Tokio runtime and no manual tick/pump. Workspace all-target/all-feature
clippy and tests are green.
Specs co-located with their crates: ENGINE_SPEC.md in crates/engine,
IROH_DRIVER_SPEC.md in crates/iroh-driver. VastAI remains explicitly out
of scope pending its separate redesign.
2026-08-10 20:23:03 +00:00
|
|
|
Some(delay) => engine.timer(delay).await,
|
2026-06-25 12:30:18 +00:00
|
|
|
None => break,
|
|
|
|
|
},
|
2026-07-21 06:25:52 +00:00
|
|
|
Err(TryRecvError::Disconnected) => break,
|
2026-06-25 12:30:18 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
send.finish()?;
|
|
|
|
|
Ok(stats)
|
|
|
|
|
}
|
|
|
|
|
|
provisioning-reconciler-demo: wire-announce readiness + --docker node kind
Replace the per-attempt key-file side channel (DEMO_NODE_KEY_FILE,
read_key_report, JoinCheck) with a control-plane announce: node roles
send a tagged gossip frame {attempt, logical_node, key_hex,
endpoint_addr_json} after joining and every heartbeat thereafter.
- iroh-driver: IrohDriver::send_tagged_gossip — fire-and-forget
tag-routed gossip egress for bridge-less clients (reuses cached/join
connections, dials with backoff).
- provisioning: BootstrapMsg::Announce — first delivery while
bootstrapping completes the attempt (collector + exactly-once
Bootstrapped report); duplicates, misrouted attempts, and
terminal-phase announces drop. Unit-tested.
- xtask demo: AnnounceActor decodes the tag-routed frame and forwards
by attempt to the owning bootstrap actor; last_announce_ms is the
wire heartbeat. LocalProcessLogic keeps only process lifecycle.
- --docker: DockerProcessLogic (kind "docker") — attached
"docker run --rm" child on a per-run labeled bridge network
(foreign-node masking: per-container IPs, gateway-dialed
supervisor). Standalone scratch image from the static-musl xtask
binary (37MB), staged one-file build context. The container is
force-removed on every terminal path so a SIGKILLed docker CLI
cannot orphan a running container.
- Cleanup: no volumes/mounts; label-filtered exit sweep (SIGINT and
SIGTERM both drain first) + startup sweep of stale demo resources;
images persist per run token.
Verified live: process kind (kill -> replacement in 3.4s, provision/
remove/kill waves, zero orphans) and docker kind (8-node abuse waves
across docker kill, mid-provision control kills, CLI SIGKILL orphans
force-removed, SIGKILL-crash leftovers swept on restart, clean exits
leave zero containers/networks/CLIs). provisioning 22 + iroh-driver
13 tests pass.
2026-08-16 16:30:52 +00:00
|
|
|
pub async fn write_event(send: &mut SendStream, event: &TelemetryEvent) -> Result<usize, BoxError> {
|
2026-07-12 06:14:34 +00:00
|
|
|
let mut bytes = Vec::new();
|
|
|
|
|
match event {
|
2026-08-15 08:17:48 +00:00
|
|
|
TelemetryEvent::StreamDeclared(_) => return Ok(0),
|
|
|
|
|
TelemetryEvent::ChannelDeclared(descriptor) => {
|
2026-07-12 06:14:34 +00:00
|
|
|
bytes.push(TAG_CHANNEL_DECLARED);
|
|
|
|
|
put_json(&mut bytes, descriptor)?;
|
|
|
|
|
}
|
2026-08-15 08:17:48 +00:00
|
|
|
TelemetryEvent::Frame(delivery) => {
|
2026-07-12 06:14:34 +00:00
|
|
|
bytes.push(TAG_FRAME);
|
|
|
|
|
bytes.extend_from_slice(&delivery.channel.channel.0.to_le_bytes());
|
|
|
|
|
bytes.extend_from_slice(&delivery.position.0.to_le_bytes());
|
|
|
|
|
put_bytes(&mut bytes, &delivery.payload)?;
|
|
|
|
|
}
|
2026-08-15 08:17:48 +00:00
|
|
|
TelemetryEvent::StreamEnded(_) => {
|
2026-07-12 06:14:34 +00:00
|
|
|
bytes.push(TAG_STREAM_ENDED);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if bytes.len() > MAX_RECORD_BYTES {
|
2026-08-15 08:17:48 +00:00
|
|
|
return Err("telemetry QUIC record exceeds max size".into());
|
2026-06-25 12:30:18 +00:00
|
|
|
}
|
|
|
|
|
send.write_all(&(bytes.len() as u32).to_le_bytes()).await?;
|
|
|
|
|
send.write_all(&bytes).await?;
|
|
|
|
|
Ok(4 + bytes.len())
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-15 08:17:48 +00:00
|
|
|
pub async fn read_stream_header(recv: &mut RecvStream) -> Result<TelemetryQuicHeader, BoxError> {
|
2026-07-12 06:14:34 +00:00
|
|
|
read_header(recv).await
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-15 08:17:48 +00:00
|
|
|
pub async fn read_events_from_stream(mut recv: RecvStream) -> Result<TelemetryQuicRead, BoxError> {
|
2026-06-25 12:30:18 +00:00
|
|
|
let header = read_header(&mut recv).await?;
|
2026-07-12 06:14:34 +00:00
|
|
|
let mut events = Vec::new();
|
2026-08-22 17:16:56 +00:00
|
|
|
while let Some(event) = read_next_event(&mut recv, &header.stream).await? {
|
|
|
|
|
events.push(event);
|
2026-06-25 12:30:18 +00:00
|
|
|
}
|
2026-08-15 08:17:48 +00:00
|
|
|
Ok(TelemetryQuicRead { header, events })
|
2026-06-25 12:30:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn read_next_uni_from_connection(
|
|
|
|
|
conn: &Connection,
|
2026-08-15 08:17:48 +00:00
|
|
|
) -> Result<TelemetryQuicRead, BoxError> {
|
2026-06-25 12:30:18 +00:00
|
|
|
let recv = conn.accept_uni().await?;
|
2026-07-12 06:14:34 +00:00
|
|
|
read_events_from_stream(recv).await
|
2026-06-25 12:30:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn spawn_connection_reader(
|
feat(engine): substrate-neutral execution engine abstraction
Introduce the swactor engine: a swactor-owned composite that retains a
selected execution substrate, drives the core runtime, and hosts the
async/blocking/timer work that backs actors. Integrations receive one
cloneable EngineHandle and never construct or borrow a raw Tokio
runtime/handle.
Engine crate (crates/engine):
- The contract: spawn / spawn_blocking / timer / interval / now, a
per-implementation capability model with construction-time binding
(require()), and engine-owned time. The engine owns all progression;
actor handlers stay synchronous and never .await.
- TokioBackend owns the Tokio runtime and schedules core ticks and
supporting futures on it; SteppingBackend is a single-threaded
deterministic scheduler with virtual time (the non-Tokio portability
proof). Core is driven through its existing tick() surface; a
self-rescheduling CoreDriver is installed at construction and is the
sole place permitted to call try_tick.
iroh-driver:
- Receives an EngineHandle instead of a raw Tokio Handle. Accepts,
reads, dials, writes, endpoint construction, and teardown schedule
through it; required capabilities (tasks/timers/io) are validated
before the endpoint binds. Engine-hosted interval pumps drive
actor-bridge, datastream, and edge ingress.
myelin:
- One node/orchestrator engine owns core, protocol tick injection, and
transport progression; the application loop only drains
integration-owned queues. Stage-shard process readers, delayed actor
messages, helper stdout/stderr, prompt RPC, and CPU sampling all
schedule through the engine (spawn_blocking / engine tasks / timers).
- Removed the split-engine APIs: install_actor_bridge_pump(period) and
spawn_protocol_ticker(period) use each component's stored engine;
deleted the no-op pump_network callback and its plumbing; deleted the
dashboard raw-Tokio/standalone-runtime conveniences.
Enforcement:
- A clippy disallowed-methods boundary forbids direct runtime/scheduling/
time/core-driving bypasses, denied in swactor-engine, iroh-driver, and
myelin. Retained excluded uses (VastAI provider, provider process
supervision/log capture, OS-signal/stdin/process-control sequencing)
carry narrow allowances with reasons.
Verification:
- Engine contract + unit tests (incl. the SteppingBackend portability
proof), iroh integration tests (capability rejection before binding,
multi-node actor behavior), and a production execution-composition
smoke test that observes engine-driven actor progress with no ambient
Tokio runtime and no manual tick/pump. Workspace all-target/all-feature
clippy and tests are green.
Specs co-located with their crates: ENGINE_SPEC.md in crates/engine,
IROH_DRIVER_SPEC.md in crates/iroh-driver. VastAI remains explicitly out
of scope pending its separate redesign.
2026-08-10 20:23:03 +00:00
|
|
|
engine: &EngineHandle,
|
2026-06-25 12:30:18 +00:00
|
|
|
conn: Connection,
|
2026-08-15 08:17:48 +00:00
|
|
|
sink: std::sync::mpsc::Sender<TelemetryEvent>,
|
feat(engine): substrate-neutral execution engine abstraction
Introduce the swactor engine: a swactor-owned composite that retains a
selected execution substrate, drives the core runtime, and hosts the
async/blocking/timer work that backs actors. Integrations receive one
cloneable EngineHandle and never construct or borrow a raw Tokio
runtime/handle.
Engine crate (crates/engine):
- The contract: spawn / spawn_blocking / timer / interval / now, a
per-implementation capability model with construction-time binding
(require()), and engine-owned time. The engine owns all progression;
actor handlers stay synchronous and never .await.
- TokioBackend owns the Tokio runtime and schedules core ticks and
supporting futures on it; SteppingBackend is a single-threaded
deterministic scheduler with virtual time (the non-Tokio portability
proof). Core is driven through its existing tick() surface; a
self-rescheduling CoreDriver is installed at construction and is the
sole place permitted to call try_tick.
iroh-driver:
- Receives an EngineHandle instead of a raw Tokio Handle. Accepts,
reads, dials, writes, endpoint construction, and teardown schedule
through it; required capabilities (tasks/timers/io) are validated
before the endpoint binds. Engine-hosted interval pumps drive
actor-bridge, datastream, and edge ingress.
myelin:
- One node/orchestrator engine owns core, protocol tick injection, and
transport progression; the application loop only drains
integration-owned queues. Stage-shard process readers, delayed actor
messages, helper stdout/stderr, prompt RPC, and CPU sampling all
schedule through the engine (spawn_blocking / engine tasks / timers).
- Removed the split-engine APIs: install_actor_bridge_pump(period) and
spawn_protocol_ticker(period) use each component's stored engine;
deleted the no-op pump_network callback and its plumbing; deleted the
dashboard raw-Tokio/standalone-runtime conveniences.
Enforcement:
- A clippy disallowed-methods boundary forbids direct runtime/scheduling/
time/core-driving bypasses, denied in swactor-engine, iroh-driver, and
myelin. Retained excluded uses (VastAI provider, provider process
supervision/log capture, OS-signal/stdin/process-control sequencing)
carry narrow allowances with reasons.
Verification:
- Engine contract + unit tests (incl. the SteppingBackend portability
proof), iroh integration tests (capability rejection before binding,
multi-node actor behavior), and a production execution-composition
smoke test that observes engine-driven actor progress with no ambient
Tokio runtime and no manual tick/pump. Workspace all-target/all-feature
clippy and tests are green.
Specs co-located with their crates: ENGINE_SPEC.md in crates/engine,
IROH_DRIVER_SPEC.md in crates/iroh-driver. VastAI remains explicitly out
of scope pending its separate redesign.
2026-08-10 20:23:03 +00:00
|
|
|
) {
|
|
|
|
|
engine.spawn(async move {
|
2026-06-25 12:30:18 +00:00
|
|
|
loop {
|
|
|
|
|
let recv = match conn.accept_uni().await {
|
|
|
|
|
Ok(recv) => recv,
|
feat(engine): substrate-neutral execution engine abstraction
Introduce the swactor engine: a swactor-owned composite that retains a
selected execution substrate, drives the core runtime, and hosts the
async/blocking/timer work that backs actors. Integrations receive one
cloneable EngineHandle and never construct or borrow a raw Tokio
runtime/handle.
Engine crate (crates/engine):
- The contract: spawn / spawn_blocking / timer / interval / now, a
per-implementation capability model with construction-time binding
(require()), and engine-owned time. The engine owns all progression;
actor handlers stay synchronous and never .await.
- TokioBackend owns the Tokio runtime and schedules core ticks and
supporting futures on it; SteppingBackend is a single-threaded
deterministic scheduler with virtual time (the non-Tokio portability
proof). Core is driven through its existing tick() surface; a
self-rescheduling CoreDriver is installed at construction and is the
sole place permitted to call try_tick.
iroh-driver:
- Receives an EngineHandle instead of a raw Tokio Handle. Accepts,
reads, dials, writes, endpoint construction, and teardown schedule
through it; required capabilities (tasks/timers/io) are validated
before the endpoint binds. Engine-hosted interval pumps drive
actor-bridge, datastream, and edge ingress.
myelin:
- One node/orchestrator engine owns core, protocol tick injection, and
transport progression; the application loop only drains
integration-owned queues. Stage-shard process readers, delayed actor
messages, helper stdout/stderr, prompt RPC, and CPU sampling all
schedule through the engine (spawn_blocking / engine tasks / timers).
- Removed the split-engine APIs: install_actor_bridge_pump(period) and
spawn_protocol_ticker(period) use each component's stored engine;
deleted the no-op pump_network callback and its plumbing; deleted the
dashboard raw-Tokio/standalone-runtime conveniences.
Enforcement:
- A clippy disallowed-methods boundary forbids direct runtime/scheduling/
time/core-driving bypasses, denied in swactor-engine, iroh-driver, and
myelin. Retained excluded uses (VastAI provider, provider process
supervision/log capture, OS-signal/stdin/process-control sequencing)
carry narrow allowances with reasons.
Verification:
- Engine contract + unit tests (incl. the SteppingBackend portability
proof), iroh integration tests (capability rejection before binding,
multi-node actor behavior), and a production execution-composition
smoke test that observes engine-driven actor progress with no ambient
Tokio runtime and no manual tick/pump. Workspace all-target/all-feature
clippy and tests are green.
Specs co-located with their crates: ENGINE_SPEC.md in crates/engine,
IROH_DRIVER_SPEC.md in crates/iroh-driver. VastAI remains explicitly out
of scope pending its separate redesign.
2026-08-10 20:23:03 +00:00
|
|
|
Err(_) => return,
|
|
|
|
|
};
|
|
|
|
|
let Ok(read) = read_events_from_stream(recv).await else {
|
|
|
|
|
continue;
|
2026-06-25 12:30:18 +00:00
|
|
|
};
|
2026-07-12 06:14:34 +00:00
|
|
|
for event in read.events {
|
|
|
|
|
if sink.send(event).is_err() {
|
feat(engine): substrate-neutral execution engine abstraction
Introduce the swactor engine: a swactor-owned composite that retains a
selected execution substrate, drives the core runtime, and hosts the
async/blocking/timer work that backs actors. Integrations receive one
cloneable EngineHandle and never construct or borrow a raw Tokio
runtime/handle.
Engine crate (crates/engine):
- The contract: spawn / spawn_blocking / timer / interval / now, a
per-implementation capability model with construction-time binding
(require()), and engine-owned time. The engine owns all progression;
actor handlers stay synchronous and never .await.
- TokioBackend owns the Tokio runtime and schedules core ticks and
supporting futures on it; SteppingBackend is a single-threaded
deterministic scheduler with virtual time (the non-Tokio portability
proof). Core is driven through its existing tick() surface; a
self-rescheduling CoreDriver is installed at construction and is the
sole place permitted to call try_tick.
iroh-driver:
- Receives an EngineHandle instead of a raw Tokio Handle. Accepts,
reads, dials, writes, endpoint construction, and teardown schedule
through it; required capabilities (tasks/timers/io) are validated
before the endpoint binds. Engine-hosted interval pumps drive
actor-bridge, datastream, and edge ingress.
myelin:
- One node/orchestrator engine owns core, protocol tick injection, and
transport progression; the application loop only drains
integration-owned queues. Stage-shard process readers, delayed actor
messages, helper stdout/stderr, prompt RPC, and CPU sampling all
schedule through the engine (spawn_blocking / engine tasks / timers).
- Removed the split-engine APIs: install_actor_bridge_pump(period) and
spawn_protocol_ticker(period) use each component's stored engine;
deleted the no-op pump_network callback and its plumbing; deleted the
dashboard raw-Tokio/standalone-runtime conveniences.
Enforcement:
- A clippy disallowed-methods boundary forbids direct runtime/scheduling/
time/core-driving bypasses, denied in swactor-engine, iroh-driver, and
myelin. Retained excluded uses (VastAI provider, provider process
supervision/log capture, OS-signal/stdin/process-control sequencing)
carry narrow allowances with reasons.
Verification:
- Engine contract + unit tests (incl. the SteppingBackend portability
proof), iroh integration tests (capability rejection before binding,
multi-node actor behavior), and a production execution-composition
smoke test that observes engine-driven actor progress with no ambient
Tokio runtime and no manual tick/pump. Workspace all-target/all-feature
clippy and tests are green.
Specs co-located with their crates: ENGINE_SPEC.md in crates/engine,
IROH_DRIVER_SPEC.md in crates/iroh-driver. VastAI remains explicitly out
of scope pending its separate redesign.
2026-08-10 20:23:03 +00:00
|
|
|
return;
|
2026-06-25 12:30:18 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
feat(engine): substrate-neutral execution engine abstraction
Introduce the swactor engine: a swactor-owned composite that retains a
selected execution substrate, drives the core runtime, and hosts the
async/blocking/timer work that backs actors. Integrations receive one
cloneable EngineHandle and never construct or borrow a raw Tokio
runtime/handle.
Engine crate (crates/engine):
- The contract: spawn / spawn_blocking / timer / interval / now, a
per-implementation capability model with construction-time binding
(require()), and engine-owned time. The engine owns all progression;
actor handlers stay synchronous and never .await.
- TokioBackend owns the Tokio runtime and schedules core ticks and
supporting futures on it; SteppingBackend is a single-threaded
deterministic scheduler with virtual time (the non-Tokio portability
proof). Core is driven through its existing tick() surface; a
self-rescheduling CoreDriver is installed at construction and is the
sole place permitted to call try_tick.
iroh-driver:
- Receives an EngineHandle instead of a raw Tokio Handle. Accepts,
reads, dials, writes, endpoint construction, and teardown schedule
through it; required capabilities (tasks/timers/io) are validated
before the endpoint binds. Engine-hosted interval pumps drive
actor-bridge, datastream, and edge ingress.
myelin:
- One node/orchestrator engine owns core, protocol tick injection, and
transport progression; the application loop only drains
integration-owned queues. Stage-shard process readers, delayed actor
messages, helper stdout/stderr, prompt RPC, and CPU sampling all
schedule through the engine (spawn_blocking / engine tasks / timers).
- Removed the split-engine APIs: install_actor_bridge_pump(period) and
spawn_protocol_ticker(period) use each component's stored engine;
deleted the no-op pump_network callback and its plumbing; deleted the
dashboard raw-Tokio/standalone-runtime conveniences.
Enforcement:
- A clippy disallowed-methods boundary forbids direct runtime/scheduling/
time/core-driving bypasses, denied in swactor-engine, iroh-driver, and
myelin. Retained excluded uses (VastAI provider, provider process
supervision/log capture, OS-signal/stdin/process-control sequencing)
carry narrow allowances with reasons.
Verification:
- Engine contract + unit tests (incl. the SteppingBackend portability
proof), iroh integration tests (capability rejection before binding,
multi-node actor behavior), and a production execution-composition
smoke test that observes engine-driven actor progress with no ambient
Tokio runtime and no manual tick/pump. Workspace all-target/all-feature
clippy and tests are green.
Specs co-located with their crates: ENGINE_SPEC.md in crates/engine,
IROH_DRIVER_SPEC.md in crates/iroh-driver. VastAI remains explicitly out
of scope pending its separate redesign.
2026-08-10 20:23:03 +00:00
|
|
|
});
|
2026-06-25 12:30:18 +00:00
|
|
|
}
|
|
|
|
|
|
provisioning-reconciler-demo: wire-announce readiness + --docker node kind
Replace the per-attempt key-file side channel (DEMO_NODE_KEY_FILE,
read_key_report, JoinCheck) with a control-plane announce: node roles
send a tagged gossip frame {attempt, logical_node, key_hex,
endpoint_addr_json} after joining and every heartbeat thereafter.
- iroh-driver: IrohDriver::send_tagged_gossip — fire-and-forget
tag-routed gossip egress for bridge-less clients (reuses cached/join
connections, dials with backoff).
- provisioning: BootstrapMsg::Announce — first delivery while
bootstrapping completes the attempt (collector + exactly-once
Bootstrapped report); duplicates, misrouted attempts, and
terminal-phase announces drop. Unit-tested.
- xtask demo: AnnounceActor decodes the tag-routed frame and forwards
by attempt to the owning bootstrap actor; last_announce_ms is the
wire heartbeat. LocalProcessLogic keeps only process lifecycle.
- --docker: DockerProcessLogic (kind "docker") — attached
"docker run --rm" child on a per-run labeled bridge network
(foreign-node masking: per-container IPs, gateway-dialed
supervisor). Standalone scratch image from the static-musl xtask
binary (37MB), staged one-file build context. The container is
force-removed on every terminal path so a SIGKILLed docker CLI
cannot orphan a running container.
- Cleanup: no volumes/mounts; label-filtered exit sweep (SIGINT and
SIGTERM both drain first) + startup sweep of stale demo resources;
images persist per run token.
Verified live: process kind (kill -> replacement in 3.4s, provision/
remove/kill waves, zero orphans) and docker kind (8-node abuse waves
across docker kill, mid-provision control kills, CLI SIGKILL orphans
force-removed, SIGKILL-crash leftovers swept on restart, clean exits
leave zero containers/networks/CLIs). provisioning 22 + iroh-driver
13 tests pass.
2026-08-16 16:30:52 +00:00
|
|
|
async fn write_header(send: &mut SendStream, header: &TelemetryQuicHeader) -> Result<(), BoxError> {
|
2026-06-25 12:30:18 +00:00
|
|
|
if header.token.len() > u16::MAX as usize {
|
2026-08-15 08:17:48 +00:00
|
|
|
return Err("telemetry token exceeds u16 length prefix".into());
|
2026-06-25 12:30:18 +00:00
|
|
|
}
|
|
|
|
|
send.write_all(MAGIC).await?;
|
|
|
|
|
send.write_all(&header.flow_id).await?;
|
|
|
|
|
send.write_all(&(header.token.len() as u16).to_le_bytes())
|
|
|
|
|
.await?;
|
|
|
|
|
send.write_all(&header.token).await?;
|
2026-07-12 06:14:34 +00:00
|
|
|
write_json(send, &header.stream).await?;
|
|
|
|
|
write_json(send, &header.channels).await?;
|
2026-06-25 12:30:18 +00:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-15 08:17:48 +00:00
|
|
|
async fn read_header(recv: &mut RecvStream) -> Result<TelemetryQuicHeader, BoxError> {
|
2026-06-25 12:30:18 +00:00
|
|
|
let mut magic = [0u8; 4];
|
|
|
|
|
recv.read_exact(&mut magic).await?;
|
|
|
|
|
if &magic != MAGIC {
|
2026-08-15 08:17:48 +00:00
|
|
|
return Err("invalid telemetry QUIC magic".into());
|
2026-06-25 12:30:18 +00:00
|
|
|
}
|
|
|
|
|
let mut flow_id = [0u8; 16];
|
|
|
|
|
recv.read_exact(&mut flow_id).await?;
|
|
|
|
|
let mut token_len = [0u8; 2];
|
|
|
|
|
recv.read_exact(&mut token_len).await?;
|
|
|
|
|
let token_len = u16::from_le_bytes(token_len) as usize;
|
|
|
|
|
let mut token = vec![0u8; token_len];
|
|
|
|
|
recv.read_exact(&mut token).await?;
|
2026-07-12 06:14:34 +00:00
|
|
|
let stream = read_json(recv).await?;
|
|
|
|
|
let channels = read_json(recv).await?;
|
2026-08-15 08:17:48 +00:00
|
|
|
Ok(TelemetryQuicHeader {
|
2026-07-12 06:14:34 +00:00
|
|
|
flow_id,
|
|
|
|
|
token,
|
|
|
|
|
stream,
|
|
|
|
|
channels,
|
|
|
|
|
})
|
2026-06-25 12:30:18 +00:00
|
|
|
}
|
|
|
|
|
|
2026-07-12 06:14:34 +00:00
|
|
|
pub async fn read_next_event(
|
|
|
|
|
recv: &mut RecvStream,
|
|
|
|
|
stream: &StreamDescriptor,
|
2026-08-15 08:17:48 +00:00
|
|
|
) -> Result<Option<TelemetryEvent>, BoxError> {
|
2026-06-25 12:30:18 +00:00
|
|
|
let mut len = [0u8; 4];
|
|
|
|
|
if recv.read_exact(&mut len).await.is_err() {
|
|
|
|
|
return Ok(None);
|
|
|
|
|
}
|
|
|
|
|
let len = u32::from_le_bytes(len) as usize;
|
|
|
|
|
if len > MAX_RECORD_BYTES {
|
2026-08-15 08:17:48 +00:00
|
|
|
return Err("telemetry QUIC record exceeds max size".into());
|
2026-06-25 12:30:18 +00:00
|
|
|
}
|
|
|
|
|
let mut buf = vec![0u8; len];
|
|
|
|
|
recv.read_exact(&mut buf).await?;
|
2026-07-12 06:14:34 +00:00
|
|
|
decode_record(&buf, stream).map(Some)
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-15 08:17:48 +00:00
|
|
|
fn decode_record(buf: &[u8], stream: &StreamDescriptor) -> Result<TelemetryEvent, BoxError> {
|
2026-07-12 06:14:34 +00:00
|
|
|
if buf.is_empty() {
|
2026-08-15 08:17:48 +00:00
|
|
|
return Err("empty telemetry QUIC record".into());
|
2026-07-12 06:14:34 +00:00
|
|
|
}
|
|
|
|
|
match buf[0] {
|
|
|
|
|
TAG_CHANNEL_DECLARED => {
|
|
|
|
|
let descriptor: ChannelDescriptor = serde_json::from_slice(&buf[1..])?;
|
2026-08-15 08:17:48 +00:00
|
|
|
Ok(TelemetryEvent::ChannelDeclared(descriptor))
|
2026-07-12 06:14:34 +00:00
|
|
|
}
|
|
|
|
|
TAG_FRAME => {
|
|
|
|
|
if buf.len() < 1 + 4 + 8 + 4 {
|
2026-08-15 08:17:48 +00:00
|
|
|
return Err("telemetry QUIC frame record truncated".into());
|
2026-07-12 06:14:34 +00:00
|
|
|
}
|
|
|
|
|
let channel = ChannelId(u32::from_le_bytes([buf[1], buf[2], buf[3], buf[4]]));
|
|
|
|
|
let position = Position(u64::from_le_bytes([
|
|
|
|
|
buf[5], buf[6], buf[7], buf[8], buf[9], buf[10], buf[11], buf[12],
|
|
|
|
|
]));
|
|
|
|
|
let mut len = [0u8; 4];
|
|
|
|
|
len.copy_from_slice(&buf[13..17]);
|
|
|
|
|
let payload_len = u32::from_le_bytes(len) as usize;
|
|
|
|
|
let payload = buf
|
|
|
|
|
.get(17..17 + payload_len)
|
2026-08-15 08:17:48 +00:00
|
|
|
.ok_or("telemetry QUIC frame payload truncated")?;
|
2026-07-12 06:14:34 +00:00
|
|
|
if 17 + payload_len != buf.len() {
|
2026-08-15 08:17:48 +00:00
|
|
|
return Err("bytes remain after telemetry QUIC frame record".into());
|
2026-07-12 06:14:34 +00:00
|
|
|
}
|
2026-08-15 08:17:48 +00:00
|
|
|
Ok(TelemetryEvent::Frame(FrameDelivery {
|
2026-07-12 06:14:34 +00:00
|
|
|
channel: ChannelRef {
|
|
|
|
|
stream: stream.stream.clone(),
|
|
|
|
|
channel,
|
|
|
|
|
},
|
|
|
|
|
position,
|
|
|
|
|
payload: payload.to_vec(),
|
|
|
|
|
}))
|
|
|
|
|
}
|
2026-08-15 08:17:48 +00:00
|
|
|
TAG_STREAM_ENDED => Ok(TelemetryEvent::StreamEnded(stream.stream.clone())),
|
|
|
|
|
_ => Err("unknown telemetry QUIC record tag".into()),
|
2026-07-12 06:14:34 +00:00
|
|
|
}
|
2026-06-25 12:30:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn read_stream_into_fanout(
|
|
|
|
|
recv: RecvStream,
|
2026-08-15 08:17:48 +00:00
|
|
|
fanout: Arc<telemetry::DeliveryFanout>,
|
|
|
|
|
) -> Result<TelemetryQuicHeader, BoxError> {
|
2026-07-12 06:14:34 +00:00
|
|
|
let read = read_events_from_stream(recv).await?;
|
2026-07-21 06:25:52 +00:00
|
|
|
fanout.publish_batch(read.events);
|
2026-06-25 12:30:18 +00:00
|
|
|
Ok(read.header)
|
|
|
|
|
}
|
2026-07-12 06:14:34 +00:00
|
|
|
|
|
|
|
|
fn put_json<T: serde::Serialize>(out: &mut Vec<u8>, value: &T) -> Result<(), BoxError> {
|
|
|
|
|
out.extend_from_slice(&serde_json::to_vec(value)?);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn put_bytes(out: &mut Vec<u8>, bytes: &[u8]) -> Result<(), BoxError> {
|
|
|
|
|
if bytes.len() > u32::MAX as usize {
|
2026-08-15 08:17:48 +00:00
|
|
|
return Err("telemetry delivery exceeds u32 length prefix".into());
|
2026-07-12 06:14:34 +00:00
|
|
|
}
|
|
|
|
|
out.extend_from_slice(&(bytes.len() as u32).to_le_bytes());
|
|
|
|
|
out.extend_from_slice(bytes);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn write_json<T: serde::Serialize>(send: &mut SendStream, value: &T) -> Result<(), BoxError> {
|
|
|
|
|
let bytes = serde_json::to_vec(value)?;
|
|
|
|
|
if bytes.len() > u32::MAX as usize {
|
2026-08-15 08:17:48 +00:00
|
|
|
return Err("telemetry header JSON exceeds u32 length prefix".into());
|
2026-07-12 06:14:34 +00:00
|
|
|
}
|
|
|
|
|
send.write_all(&(bytes.len() as u32).to_le_bytes()).await?;
|
|
|
|
|
send.write_all(&bytes).await?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn read_json<T: serde::de::DeserializeOwned>(recv: &mut RecvStream) -> Result<T, BoxError> {
|
|
|
|
|
let mut len = [0u8; 4];
|
|
|
|
|
recv.read_exact(&mut len).await?;
|
|
|
|
|
let len = u32::from_le_bytes(len) as usize;
|
|
|
|
|
if len > MAX_RECORD_BYTES {
|
2026-08-15 08:17:48 +00:00
|
|
|
return Err("telemetry QUIC record exceeds max size".into());
|
2026-07-12 06:14:34 +00:00
|
|
|
}
|
|
|
|
|
let mut bytes = vec![0u8; len];
|
|
|
|
|
recv.read_exact(&mut bytes).await?;
|
|
|
|
|
Ok(serde_json::from_slice(&bytes)?)
|
|
|
|
|
}
|