2026-08-15 14:10:52 +00:00
|
|
|
//! The provisioning supervisor: an actor that owns the `ClusterDriver`, the
|
|
|
|
|
//! demo provider, the desired shape, and the 250ms tick.
|
|
|
|
|
//!
|
|
|
|
|
//! Each tick mirrors the production `ClusterReconciler` poll semantics:
|
|
|
|
|
//! drain executor results (closing bootstrap sessions after convergence),
|
|
|
|
|
//! classify due operations, requeue, drive until blocked — then feeds the
|
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
|
|
|
//! real world back in (wire announces, child exits), emits
|
|
|
|
|
//! `prov.reconciler.*` telemetry, and drains every telemetry endpoint into
|
|
|
|
|
//! the dashboard.
|
2026-08-15 14:10:52 +00:00
|
|
|
|
|
|
|
|
use std::collections::BTreeMap;
|
|
|
|
|
use std::time::{Duration, SystemTime};
|
|
|
|
|
|
|
|
|
|
use provisioning::executor::{
|
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
|
|
|
BlockingEffectSpawner, BlockingEffectWork, ExecutorOperationStatus, IdempotentEffectExecutor,
|
2026-08-15 14:10:52 +00:00
|
|
|
};
|
|
|
|
|
use provisioning::node::{
|
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
|
|
|
BootstrapObservation, BootstrapStage, NodeGroupId, NodeStage, RoleId, RunId, SwactorId,
|
2026-08-15 14:10:52 +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
|
|
|
use provisioning::reconciler::{ClusterDriver, EffectExecutor};
|
2026-08-15 14:10:52 +00:00
|
|
|
use provisioning::reconciler::{
|
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
|
|
|
ClusterShape, NodeObservation, OperationOutcome, PlannedEffect, RetryPolicy,
|
2026-08-15 14:10:52 +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
|
|
|
use serde_json::json;
|
|
|
|
|
use swactor::actor::{ActorInterface, Ctx};
|
|
|
|
|
use swactor_engine::EngineHandle;
|
2026-08-15 14:10:52 +00:00
|
|
|
use telemetry::{ChannelContent, StreamDescriptor, TelemetryEndpoint, TelemetryProducer};
|
|
|
|
|
|
|
|
|
|
use crate::provisioning_demo::provider::{
|
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
|
|
|
DemoBackend, NodeManager, NodeTelemetry, register_node_channels, unix_ms,
|
2026-08-15 14:10:52 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// Supervisor telemetry: channels + name resolution for the dashboard path.
|
|
|
|
|
pub struct SupervisorTelemetry {
|
|
|
|
|
pub endpoint: TelemetryEndpoint,
|
|
|
|
|
pub producer: TelemetryProducer,
|
feat(dashboard): Segment Mask visual world and merged Fleet Control
Fleet page: drop the aggregate totals row and bar skeletons — cards carry
identity, liveness, and runtime summary; hardware detail stays one click
down. Stream descriptors (origin/label) now ride FrameEvents into the
fleet view, so the orchestrator renders as a full-width amber-framed
module pinned above the grid. Cards are real links; roster rows are
keyboard-operable.
Fleet Control: /view/reconciler folds into /view/demo-control as one
control bench — ghost-segment ready/desired counter, generation digit,
unified node rows (reconciler stage + pid + state + kill), activity feeds
demoted to a collapsed tail. The standalone reconciler page is retired;
its API stays live to feed the merge.
Visual world (both themes, nav toggle, persisted, prefers-color-scheme
default): dark = Bloomberg night housing (black ground, navy panels,
amber data register); light = Atom One Light. 2px corners, monospace
data, outline chips for states, cyan as the only interactive voice,
ghost-eight segments for counters, blink reserved for unresolved states,
depressing controls, reduced-motion collapse.
2026-08-16 11:16:30 +00:00
|
|
|
/// Descriptor metadata mirrored onto every published frame so the
|
|
|
|
|
/// dashboard can classify the stream without a catalog.
|
|
|
|
|
pub origin: &'static str,
|
|
|
|
|
pub label: &'static str,
|
2026-08-15 14:10:52 +00:00
|
|
|
names: BTreeMap<telemetry::ChannelId, String>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl SupervisorTelemetry {
|
|
|
|
|
pub fn new(node: &str) -> Self {
|
|
|
|
|
let stream = telemetry::frame::StreamId::new(
|
|
|
|
|
telemetry::frame::NodeId::new(node),
|
|
|
|
|
telemetry::frame::Lifetime(1),
|
|
|
|
|
);
|
|
|
|
|
let endpoint = TelemetryEndpoint::with_descriptor(
|
|
|
|
|
StreamDescriptor {
|
|
|
|
|
stream,
|
|
|
|
|
label: Some("provisioning supervisor".to_owned()),
|
|
|
|
|
origin: telemetry::frame::StreamOrigin::Orchestrator,
|
|
|
|
|
},
|
|
|
|
|
512,
|
|
|
|
|
16,
|
|
|
|
|
);
|
|
|
|
|
let producer = endpoint.producer();
|
|
|
|
|
Self {
|
|
|
|
|
endpoint,
|
|
|
|
|
producer,
|
feat(dashboard): Segment Mask visual world and merged Fleet Control
Fleet page: drop the aggregate totals row and bar skeletons — cards carry
identity, liveness, and runtime summary; hardware detail stays one click
down. Stream descriptors (origin/label) now ride FrameEvents into the
fleet view, so the orchestrator renders as a full-width amber-framed
module pinned above the grid. Cards are real links; roster rows are
keyboard-operable.
Fleet Control: /view/reconciler folds into /view/demo-control as one
control bench — ghost-segment ready/desired counter, generation digit,
unified node rows (reconciler stage + pid + state + kill), activity feeds
demoted to a collapsed tail. The standalone reconciler page is retired;
its API stays live to feed the merge.
Visual world (both themes, nav toggle, persisted, prefers-color-scheme
default): dark = Bloomberg night housing (black ground, navy panels,
amber data register); light = Atom One Light. 2px corners, monospace
data, outline chips for states, cyan as the only interactive voice,
ghost-eight segments for counters, blink reserved for unresolved states,
depressing controls, reduced-motion collapse.
2026-08-16 11:16:30 +00:00
|
|
|
origin: "orchestrator",
|
|
|
|
|
label: "provisioning supervisor",
|
2026-08-15 14:10:52 +00:00
|
|
|
names: BTreeMap::new(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
pub fn register(&mut self, name: &str) -> telemetry::ChannelId {
|
|
|
|
|
let id = self.endpoint.register_channel(
|
|
|
|
|
name,
|
|
|
|
|
ChannelContent::JsonRecord {
|
|
|
|
|
schema: Some("demo.prov.v1".to_owned()),
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
self.names.insert(id, name.to_owned());
|
|
|
|
|
id
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Engine-backed spawner for executor blocking work.
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
pub struct EngineSpawner {
|
|
|
|
|
engine: EngineHandle,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl EngineSpawner {
|
|
|
|
|
pub fn new(engine: EngineHandle) -> Self {
|
|
|
|
|
Self { engine }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl BlockingEffectSpawner for EngineSpawner {
|
|
|
|
|
type SpawnError = std::convert::Infallible;
|
|
|
|
|
|
|
|
|
|
fn spawn_blocking(&self, work: BlockingEffectWork) -> Result<(), Self::SpawnError> {
|
|
|
|
|
self.engine.spawn_blocking(move || work());
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Wrapper that records every dispatched effect for the command feed.
|
|
|
|
|
struct FeedExecutor<'a> {
|
|
|
|
|
inner: &'a mut IdempotentEffectExecutor<DemoBackend, EngineSpawner>,
|
|
|
|
|
dispatched: Vec<String>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl EffectExecutor for FeedExecutor<'_> {
|
|
|
|
|
type SubmitError = provisioning::executor::ExecutorSubmitError;
|
|
|
|
|
|
|
|
|
|
fn submit(&mut self, effect: &PlannedEffect) -> Result<(), Self::SubmitError> {
|
|
|
|
|
use provisioning::node::NodeManagerCommand;
|
|
|
|
|
let command = match &effect.command {
|
|
|
|
|
NodeManagerCommand::CreateLease(_) => "CreateLease".to_owned(),
|
|
|
|
|
NodeManagerCommand::LookupEndpoint(_) => "LookupEndpoint".to_owned(),
|
|
|
|
|
NodeManagerCommand::StartBootstrap(_) => "StartBootstrap".to_owned(),
|
|
|
|
|
NodeManagerCommand::BootstrapConvergenceObserved { .. } => {
|
|
|
|
|
"BootstrapConvergenceObserved".to_owned()
|
|
|
|
|
}
|
|
|
|
|
NodeManagerCommand::CancelBootstrap { .. } => "CancelBootstrap".to_owned(),
|
|
|
|
|
NodeManagerCommand::DestroyLease(_) => "DestroyLease".to_owned(),
|
|
|
|
|
};
|
|
|
|
|
self.dispatched
|
|
|
|
|
.push(format!("{} → {}", effect.node.0, command));
|
|
|
|
|
self.inner.submit(effect)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
pub enum SupervisorMsg {
|
|
|
|
|
Tick,
|
|
|
|
|
Control(dashboard::control::ControlCommand),
|
|
|
|
|
Spawn(crate::provisioning_demo::provider::SpawnNodeRequest),
|
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
|
|
|
/// Event from a per-node bootstrap actor.
|
|
|
|
|
Bootstrap(provisioning::BootstrapEvent),
|
|
|
|
|
/// A remote node's telemetry pull stream registered its header (stream
|
|
|
|
|
/// descriptor + channel catalog), associated with its logical node and
|
|
|
|
|
/// provision attempt. Frames are fused onto the logical node's stream.
|
|
|
|
|
NodeStream {
|
|
|
|
|
header: iroh_driver::TelemetryQuicHeader,
|
|
|
|
|
logical_node: String,
|
|
|
|
|
attempt: u64,
|
|
|
|
|
},
|
2026-08-15 14:10:52 +00:00
|
|
|
/// Drain the cluster: desired → empty, stop every child, flag when done.
|
|
|
|
|
Shutdown {
|
|
|
|
|
drained: std::sync::Arc<std::sync::atomic::AtomicBool>,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
/// Metadata for one remote node telemetry pull, keyed by the node's hex
|
|
|
|
|
/// transport key: channel catalog + which logical node stream its frames
|
|
|
|
|
/// are fused onto.
|
|
|
|
|
#[derive(Clone, Default)]
|
|
|
|
|
struct RemoteStreamMeta {
|
|
|
|
|
logical_node: String,
|
|
|
|
|
attempt: u64,
|
|
|
|
|
channels: BTreeMap<telemetry::ChannelId, String>,
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-15 14:10:52 +00:00
|
|
|
/// Per-node telemetry handle kept while the attempt is live.
|
|
|
|
|
struct NodeStreams {
|
|
|
|
|
telemetry: NodeTelemetry,
|
|
|
|
|
status_channel: telemetry::ChannelId,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The provisioning supervisor actor.
|
|
|
|
|
pub struct SupervisorActor {
|
|
|
|
|
pub driver: ClusterDriver,
|
|
|
|
|
pub executor: IdempotentEffectExecutor<DemoBackend, EngineSpawner>,
|
|
|
|
|
pub manager: NodeManager,
|
|
|
|
|
pub driver_handle: std::sync::Arc<crate::provisioning_demo::DemoDriverHandle>,
|
|
|
|
|
pub telemetry: SupervisorTelemetry,
|
|
|
|
|
pub events_channel: telemetry::ChannelId,
|
|
|
|
|
pub snapshot_channel: telemetry::ChannelId,
|
|
|
|
|
pub sender: swactor::runtime::ExternalSender,
|
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
|
|
|
/// Bootstrap kind registry (spec.kind → logic).
|
|
|
|
|
registry: provisioning::BootstrapRegistry,
|
|
|
|
|
/// Pull collector fired by bootstrap actors on Bootstrapped.
|
|
|
|
|
collector: std::sync::Arc<dyn provisioning::NodeTelemetryCollector>,
|
|
|
|
|
/// Engine handle for bootstrap actor probe intervals.
|
|
|
|
|
engine: EngineHandle,
|
|
|
|
|
/// Subscription draining the remote-stream fanout into the dashboard.
|
|
|
|
|
remote_sub: telemetry::TelemetrySubscription,
|
|
|
|
|
remote_streams: BTreeMap<String, RemoteStreamMeta>,
|
|
|
|
|
/// Bootstrap reports parked until the reconciler opens the attempt's
|
|
|
|
|
/// bootstrap session (early joins / failures).
|
|
|
|
|
pending_reports: BTreeMap<u64, provisioning::BootstrapEvent>,
|
2026-08-15 14:10:52 +00:00
|
|
|
/// Desired shape slots: singleton groups, one per logical node.
|
|
|
|
|
slots: Vec<String>,
|
|
|
|
|
slot_seq: u64,
|
|
|
|
|
run_id: RunId,
|
|
|
|
|
nodes: BTreeMap<u64, NodeStreams>,
|
|
|
|
|
node_life: u64,
|
|
|
|
|
last_stages: BTreeMap<String, (NodeStage, Option<BootstrapStage>)>,
|
|
|
|
|
pub dashboard: dashboard::DashboardHandle,
|
|
|
|
|
status_tick: u64,
|
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
|
|
|
launch: crate::provisioning_demo::LaunchStyle,
|
2026-08-15 14:10:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl SupervisorActor {
|
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
|
|
|
pub fn new(
|
|
|
|
|
driver: ClusterDriver,
|
|
|
|
|
executor: IdempotentEffectExecutor<DemoBackend, EngineSpawner>,
|
|
|
|
|
manager: NodeManager,
|
|
|
|
|
driver_handle: std::sync::Arc<crate::provisioning_demo::DemoDriverHandle>,
|
|
|
|
|
mut telemetry: SupervisorTelemetry,
|
|
|
|
|
dashboard: dashboard::DashboardHandle,
|
|
|
|
|
sender: swactor::runtime::ExternalSender,
|
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
|
|
|
registry: provisioning::BootstrapRegistry,
|
|
|
|
|
collector: std::sync::Arc<dyn provisioning::NodeTelemetryCollector>,
|
|
|
|
|
engine: EngineHandle,
|
|
|
|
|
remote_sub: telemetry::TelemetrySubscription,
|
2026-08-15 14:10:52 +00:00
|
|
|
initial_slots: Vec<String>,
|
|
|
|
|
run_id: RunId,
|
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
|
|
|
launch: crate::provisioning_demo::LaunchStyle,
|
2026-08-15 14:10:52 +00:00
|
|
|
) -> Self {
|
|
|
|
|
let events_channel = telemetry.register("prov.reconciler.events");
|
|
|
|
|
let snapshot_channel = telemetry.register("prov.reconciler.snapshot");
|
|
|
|
|
Self {
|
|
|
|
|
driver,
|
|
|
|
|
executor,
|
|
|
|
|
manager,
|
|
|
|
|
driver_handle,
|
|
|
|
|
telemetry,
|
|
|
|
|
events_channel,
|
|
|
|
|
snapshot_channel,
|
|
|
|
|
sender,
|
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
|
|
|
registry,
|
|
|
|
|
collector,
|
|
|
|
|
engine,
|
|
|
|
|
remote_sub,
|
|
|
|
|
remote_streams: BTreeMap::new(),
|
|
|
|
|
pending_reports: BTreeMap::new(),
|
2026-08-15 14:10:52 +00:00
|
|
|
nodes: BTreeMap::new(),
|
|
|
|
|
run_id,
|
|
|
|
|
slot_seq: initial_slots.len() as u64,
|
|
|
|
|
slots: initial_slots,
|
|
|
|
|
node_life: 0,
|
|
|
|
|
last_stages: BTreeMap::new(),
|
|
|
|
|
dashboard,
|
|
|
|
|
status_tick: 0,
|
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
|
|
|
launch,
|
2026-08-15 14:10:52 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn desired_shape(&self, generation: u64) -> ClusterShape {
|
|
|
|
|
ClusterShape {
|
|
|
|
|
run_id: self.run_id.clone(),
|
|
|
|
|
generation,
|
|
|
|
|
groups: self.slots.iter().map(|slot| slot_group(slot)).collect(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn emit_event(&mut self, kind: &str, node: &str, detail: String) {
|
|
|
|
|
let payload = json!({
|
|
|
|
|
"at_ms": unix_ms(SystemTime::now()),
|
|
|
|
|
"kind": kind,
|
|
|
|
|
"node": node,
|
|
|
|
|
"detail": detail,
|
|
|
|
|
});
|
|
|
|
|
let bytes = serde_json::to_vec(&payload).expect("event serializes");
|
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
|
|
|
self.telemetry
|
|
|
|
|
.producer
|
|
|
|
|
.submit_bytes(self.events_channel, bytes);
|
2026-08-15 14:10:52 +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
|
|
|
/// Handle a spawn request from the provider (runs in actor context):
|
|
|
|
|
/// spawn the per-attempt bootstrap actor through the registry — the
|
|
|
|
|
/// supervision never launches nodes directly.
|
|
|
|
|
fn spawn_node(
|
|
|
|
|
&mut self,
|
|
|
|
|
ctx: &Ctx,
|
|
|
|
|
request: crate::provisioning_demo::provider::SpawnNodeRequest,
|
|
|
|
|
) {
|
2026-08-15 14:10:52 +00:00
|
|
|
let attempt = request.attempt;
|
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
|
|
|
|
|
|
|
|
// Supervisor-authored lifecycle stream (kept alive independent of
|
|
|
|
|
// the node: it reports death even when the node cannot).
|
|
|
|
|
self.node_life += 1;
|
|
|
|
|
let telemetry = NodeTelemetry::new(&request.logical_node, self.node_life);
|
|
|
|
|
let status_channel = register_node_channels(&telemetry.producer);
|
|
|
|
|
|
|
|
|
|
let (kind, argv, mut env) = match &self.launch {
|
|
|
|
|
crate::provisioning_demo::LaunchStyle::Process { exe } => (
|
|
|
|
|
"process",
|
|
|
|
|
vec![
|
|
|
|
|
exe.to_string_lossy().to_string(),
|
|
|
|
|
"provisioning-reconciler-demo".to_owned(),
|
|
|
|
|
"--demo-node".to_owned(),
|
|
|
|
|
self.driver_handle.supervisor_addr_json.clone(),
|
|
|
|
|
"--demo-attempt".to_owned(),
|
|
|
|
|
attempt.to_string(),
|
|
|
|
|
],
|
|
|
|
|
Vec::new(),
|
|
|
|
|
),
|
|
|
|
|
crate::provisioning_demo::LaunchStyle::Docker(docker) => (
|
|
|
|
|
"docker",
|
|
|
|
|
vec![
|
|
|
|
|
"docker".to_owned(),
|
|
|
|
|
"run".to_owned(),
|
|
|
|
|
"--rm".to_owned(),
|
|
|
|
|
"--name".to_owned(),
|
|
|
|
|
crate::provisioning_demo::docker::container_name(attempt),
|
|
|
|
|
"--label".to_owned(),
|
|
|
|
|
format!("{}=1", crate::provisioning_demo::docker::SWEEP_LABEL),
|
|
|
|
|
"--label".to_owned(),
|
|
|
|
|
format!(
|
|
|
|
|
"{}={}",
|
|
|
|
|
crate::provisioning_demo::docker::RUN_LABEL,
|
|
|
|
|
docker.run_token
|
|
|
|
|
),
|
|
|
|
|
"--network".to_owned(),
|
|
|
|
|
docker.network.clone(),
|
|
|
|
|
docker.image.clone(),
|
|
|
|
|
"provisioning-reconciler-demo".to_owned(),
|
|
|
|
|
"--demo-node".to_owned(),
|
|
|
|
|
docker.supervisor_addr_json.clone(),
|
|
|
|
|
"--demo-attempt".to_owned(),
|
|
|
|
|
attempt.to_string(),
|
|
|
|
|
],
|
|
|
|
|
Vec::new(),
|
|
|
|
|
),
|
2026-08-15 14:10:52 +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
|
|
|
env.push(("DEMO_NODE_ID".to_owned(), request.logical_node.clone()));
|
2026-08-15 14:10:52 +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
|
|
|
let spec = provisioning::NodeLaunchSpec {
|
|
|
|
|
kind: kind.to_owned(),
|
|
|
|
|
attempt,
|
|
|
|
|
logical_node: request.logical_node.clone(),
|
|
|
|
|
argv,
|
|
|
|
|
env,
|
|
|
|
|
workdir: None,
|
2026-08-15 14:10:52 +00:00
|
|
|
label: Some(request.logical_node.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
|
|
|
let reporter: provisioning::BootstrapReporter = {
|
|
|
|
|
let sender = self.sender.clone();
|
|
|
|
|
let supervisor = ctx.self_addr();
|
|
|
|
|
std::sync::Arc::new(move |event| {
|
|
|
|
|
let _ = sender.send_to(supervisor, SupervisorMsg::Bootstrap(event));
|
|
|
|
|
})
|
|
|
|
|
};
|
|
|
|
|
let config = provisioning::BootstrapConfig {
|
|
|
|
|
reporter,
|
|
|
|
|
sender: self.sender.clone(),
|
|
|
|
|
collector: Some(std::sync::Arc::clone(&self.collector)),
|
|
|
|
|
probe_period: provisioning::DEFAULT_PROBE_PERIOD,
|
|
|
|
|
spec: spec.clone(),
|
|
|
|
|
};
|
|
|
|
|
let logic = match self.registry.create(&spec) {
|
|
|
|
|
Ok(logic) => logic,
|
|
|
|
|
Err(error) => {
|
|
|
|
|
let _ = request.reply.send(Err(format!("bootstrap logic: {error}")));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
match provisioning::spawn_bootstrap_actor(ctx, &self.engine, logic, config) {
|
|
|
|
|
Ok(bootstrap) => {
|
2026-08-15 14:10:52 +00:00
|
|
|
self.nodes.insert(
|
|
|
|
|
attempt,
|
|
|
|
|
NodeStreams {
|
|
|
|
|
telemetry,
|
|
|
|
|
status_channel,
|
|
|
|
|
},
|
|
|
|
|
);
|
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 runtime = crate::provisioning_demo::provider::NodeRuntime {
|
|
|
|
|
attempt,
|
|
|
|
|
logical_node: request.logical_node.clone(),
|
|
|
|
|
bootstrap,
|
|
|
|
|
pid: None,
|
|
|
|
|
exited: None,
|
|
|
|
|
spawn_failed: None,
|
|
|
|
|
last_announce_ms: None,
|
|
|
|
|
};
|
|
|
|
|
let _ = request.reply.send(Ok(runtime));
|
2026-08-15 14:10:52 +00:00
|
|
|
}
|
|
|
|
|
Err(error) => {
|
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 _ = request
|
|
|
|
|
.reply
|
|
|
|
|
.send(Err(format!("spawn bootstrap actor: {error}")));
|
2026-08-15 14:10:52 +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
|
|
|
/// Feed bootstrap-progress observations into the driver. Join detection
|
|
|
|
|
/// and exit classification moved into the per-node bootstrap actors
|
2026-08-15 14:10:52 +00:00
|
|
|
fn observe_world(&mut self, now: SystemTime) {
|
|
|
|
|
let node_ids: Vec<String> = self
|
|
|
|
|
.driver
|
|
|
|
|
.state()
|
|
|
|
|
.nodes
|
|
|
|
|
.keys()
|
|
|
|
|
.map(|id| id.0.clone())
|
|
|
|
|
.collect();
|
|
|
|
|
for node_id in node_ids {
|
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 Some(managed) = self
|
|
|
|
|
.driver
|
|
|
|
|
.state()
|
|
|
|
|
.nodes
|
|
|
|
|
.get(&provisioning::node::LogicalNodeId(node_id.clone()))
|
|
|
|
|
else {
|
2026-08-15 14:10:52 +00:00
|
|
|
continue;
|
|
|
|
|
};
|
|
|
|
|
let attempt = managed.attempt;
|
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 Some(session_id) = managed.active_bootstrap else {
|
|
|
|
|
continue;
|
2026-08-15 14:10:52 +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
|
|
|
let Some(runtime) = self.manager.get(attempt.0) else {
|
|
|
|
|
continue;
|
|
|
|
|
};
|
|
|
|
|
// Stage evidence: the supervised child started (process pid /
|
|
|
|
|
// docker CLI pid observed) means the node runtime is coming up
|
|
|
|
|
// and we are waiting for its control-plane announce; before
|
|
|
|
|
// that the lease exists but the foreign process is not up yet.
|
|
|
|
|
let stage_seen = if runtime.pid.is_some() {
|
|
|
|
|
BootstrapStage::WaitingForSwactorJoin
|
|
|
|
|
} else {
|
|
|
|
|
BootstrapStage::SshReady
|
|
|
|
|
};
|
|
|
|
|
self.driver.apply_observation(
|
|
|
|
|
&provisioning::node::LogicalNodeId(node_id.clone()),
|
|
|
|
|
attempt,
|
|
|
|
|
NodeObservation::BootstrapObserved {
|
|
|
|
|
session_id,
|
|
|
|
|
observation: BootstrapObservation::stage(stage_seen),
|
|
|
|
|
},
|
|
|
|
|
now,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-08-15 14:10:52 +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
|
|
|
/// Fold bootstrap-actor events into the reconciler. A report that
|
|
|
|
|
/// arrives before the reconciler has an active bootstrap session for
|
|
|
|
|
/// the attempt (the actor starts at spawn, `StartBootstrap` comes
|
|
|
|
|
/// later) is parked and re-delivered on the next tick — the join signal
|
|
|
|
|
/// is exactly-once, so dropping an early one wedges the session.
|
|
|
|
|
fn handle_bootstrap_event(&mut self, now: SystemTime, event: provisioning::BootstrapEvent) {
|
|
|
|
|
let attempt = match &event {
|
|
|
|
|
provisioning::BootstrapEvent::Bootstrapped(identity) => identity.attempt,
|
|
|
|
|
provisioning::BootstrapEvent::Failed { attempt, .. }
|
|
|
|
|
| provisioning::BootstrapEvent::Exited { attempt, .. } => *attempt,
|
|
|
|
|
};
|
|
|
|
|
if !self.deliver_bootstrap_report(now, event.clone()) {
|
|
|
|
|
self.pending_reports.insert(attempt, event);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Deliver one bootstrap report to the reconciler driver. Returns false
|
|
|
|
|
/// when the attempt has no active bootstrap session yet.
|
|
|
|
|
fn deliver_bootstrap_report(
|
|
|
|
|
&mut self,
|
|
|
|
|
now: SystemTime,
|
|
|
|
|
event: provisioning::BootstrapEvent,
|
|
|
|
|
) -> bool {
|
|
|
|
|
match event {
|
|
|
|
|
provisioning::BootstrapEvent::Bootstrapped(identity) => {
|
|
|
|
|
let Some((node_id, session_id)) = self.node_for_attempt(identity.attempt) else {
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
let key_prefix = &identity.key_hex[..8.min(identity.key_hex.len())];
|
|
|
|
|
self.emit_event(
|
|
|
|
|
"observation",
|
|
|
|
|
&node_id,
|
|
|
|
|
format!("swactor join confirmed (key {key_prefix}…)"),
|
|
|
|
|
);
|
|
|
|
|
self.driver.apply_observation(
|
|
|
|
|
&provisioning::node::LogicalNodeId(node_id),
|
|
|
|
|
provisioning::NodeAttemptId(identity.attempt),
|
|
|
|
|
NodeObservation::SwactorJoined {
|
|
|
|
|
session_id,
|
|
|
|
|
swactor_id: SwactorId(identity.key_hex),
|
|
|
|
|
},
|
|
|
|
|
now,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
provisioning::BootstrapEvent::Failed { attempt, reason } => {
|
|
|
|
|
let Some((node_id, session_id)) = self.node_for_attempt(attempt) else {
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
self.emit_event("observation", &node_id, reason.clone());
|
|
|
|
|
self.driver.apply_observation(
|
|
|
|
|
&provisioning::node::LogicalNodeId(node_id),
|
|
|
|
|
provisioning::NodeAttemptId(attempt),
|
|
|
|
|
NodeObservation::BootstrapFailed { session_id, reason },
|
|
|
|
|
now,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
provisioning::BootstrapEvent::Exited { attempt, reason } => {
|
|
|
|
|
// An exit while a session is still open is a bootstrap
|
|
|
|
|
// failure (death before join); otherwise it is a plain
|
|
|
|
|
// death observation.
|
|
|
|
|
if let Some((node_id, session_id)) = self.node_for_attempt(attempt) {
|
|
|
|
|
self.emit_event("observation", &node_id, format!("node runtime: {reason}"));
|
2026-08-15 14:10:52 +00:00
|
|
|
self.driver.apply_observation(
|
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
|
|
|
&provisioning::node::LogicalNodeId(node_id),
|
|
|
|
|
provisioning::NodeAttemptId(attempt),
|
2026-08-15 14:10:52 +00:00
|
|
|
NodeObservation::BootstrapFailed {
|
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
|
|
|
session_id,
|
|
|
|
|
reason: format!("node process exited: {reason}"),
|
2026-08-15 14:10:52 +00:00
|
|
|
},
|
|
|
|
|
now,
|
|
|
|
|
);
|
|
|
|
|
}
|
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
|
|
|
// Publish the terminal status now: once the reconciler
|
|
|
|
|
// destroys the lease the runtime is deregistered, and the
|
|
|
|
|
// Fleet Control table would otherwise keep its last
|
|
|
|
|
// "running" state forever. Derive the logical name from the
|
|
|
|
|
// lifecycle stream itself — the registry entry can already
|
|
|
|
|
// be gone (lease destroy races the bootstrap probe).
|
|
|
|
|
if let Some(streams) = self.nodes.get(&attempt) {
|
|
|
|
|
let logical = streams
|
|
|
|
|
.telemetry
|
|
|
|
|
.endpoint
|
|
|
|
|
.stream_id()
|
|
|
|
|
.node
|
|
|
|
|
.as_str()
|
|
|
|
|
.to_owned();
|
|
|
|
|
let pid = self.manager.get(attempt).and_then(|r| r.pid);
|
|
|
|
|
let payload = json!({
|
|
|
|
|
"at_ms": unix_ms(now),
|
|
|
|
|
"node": logical,
|
|
|
|
|
"alive": false,
|
|
|
|
|
"pid": pid,
|
|
|
|
|
"event": "exited",
|
|
|
|
|
});
|
|
|
|
|
if let Ok(bytes) = serde_json::to_vec(&payload) {
|
|
|
|
|
streams
|
|
|
|
|
.telemetry
|
|
|
|
|
.producer
|
|
|
|
|
.submit_bytes(streams.status_channel, bytes);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Death-replacement is shape logic: `replace_dead_ready_nodes`
|
|
|
|
|
// reads the exit from the shared registry.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Re-deliver parked bootstrap reports whose session has appeared.
|
|
|
|
|
fn deliver_pending_reports(&mut self, now: SystemTime) {
|
|
|
|
|
let attempts: Vec<u64> = self.pending_reports.keys().copied().collect();
|
|
|
|
|
for attempt in attempts {
|
|
|
|
|
let Some(event) = self.pending_reports.get(&attempt).cloned() else {
|
2026-08-15 14:10:52 +00:00
|
|
|
continue;
|
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
|
|
|
};
|
|
|
|
|
if self.deliver_bootstrap_report(now, event) {
|
|
|
|
|
self.pending_reports.remove(&attempt);
|
2026-08-15 14:10:52 +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
|
|
|
}
|
|
|
|
|
}
|
2026-08-15 14:10:52 +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
|
|
|
/// Resolve a driver node (id + active bootstrap session) by attempt.
|
|
|
|
|
fn node_for_attempt(
|
|
|
|
|
&self,
|
|
|
|
|
attempt: u64,
|
|
|
|
|
) -> Option<(String, provisioning::node::BootstrapSessionId)> {
|
|
|
|
|
self.driver
|
|
|
|
|
.state()
|
|
|
|
|
.nodes
|
|
|
|
|
.iter()
|
|
|
|
|
.find(|(_, managed)| managed.attempt.0 == attempt)
|
|
|
|
|
.and_then(|(id, managed)| {
|
|
|
|
|
managed
|
|
|
|
|
.active_bootstrap
|
|
|
|
|
.map(|session| (id.0.clone(), session))
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Register a remote node telemetry stream header (pull side).
|
|
|
|
|
fn register_remote_stream(
|
|
|
|
|
&mut self,
|
|
|
|
|
header: iroh_driver::TelemetryQuicHeader,
|
|
|
|
|
logical_node: String,
|
|
|
|
|
attempt: u64,
|
|
|
|
|
) {
|
|
|
|
|
let key = header.stream.stream.node.as_str().to_string();
|
|
|
|
|
let meta = self.remote_streams.entry(key.clone()).or_default();
|
|
|
|
|
meta.logical_node = logical_node.clone();
|
|
|
|
|
meta.attempt = attempt;
|
|
|
|
|
for channel in &header.channels {
|
|
|
|
|
meta.channels.insert(channel.id, channel.name.clone());
|
|
|
|
|
}
|
|
|
|
|
println!(
|
|
|
|
|
"demo: fusing telemetry of node {logical_node} (stream {key}, {} channels)",
|
|
|
|
|
meta.channels.len()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
/// Drain remote-node telemetry frames into the dashboard, fusing them
|
|
|
|
|
/// onto the logical node's stream: one card per node, carrying both the
|
|
|
|
|
/// supervisor-authored lifecycle channels and the node's real runtime
|
|
|
|
|
/// channels (runtime.actors, node.beat, node.status).
|
|
|
|
|
fn flush_remote_streams(&mut self) {
|
|
|
|
|
for event in self.remote_sub.drain_available() {
|
|
|
|
|
match event {
|
|
|
|
|
telemetry::frame::TelemetryEvent::Frame(delivery) => {
|
|
|
|
|
let key = delivery.channel.stream.node.as_str().to_string();
|
|
|
|
|
let Some(meta) = self.remote_streams.get(&key).cloned() else {
|
2026-08-15 14:10:52 +00:00
|
|
|
continue;
|
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 Some(node_stream) = self.nodes.get(&meta.attempt) else {
|
|
|
|
|
continue;
|
|
|
|
|
};
|
|
|
|
|
// Fuse: republish on the logical node's stream.
|
|
|
|
|
let target_stream = node_stream.telemetry.endpoint.stream_id().clone();
|
|
|
|
|
let origin = node_stream.telemetry.origin;
|
|
|
|
|
let label = node_stream.telemetry.label.clone();
|
|
|
|
|
let channel_name = meta
|
|
|
|
|
.channels
|
|
|
|
|
.get(&delivery.channel.channel)
|
|
|
|
|
.cloned()
|
|
|
|
|
.unwrap_or_else(|| format!("channel#{}", delivery.channel.channel.0));
|
|
|
|
|
let frame = telemetry::frame::Frame {
|
|
|
|
|
channel: delivery.channel.channel,
|
|
|
|
|
position: delivery.position,
|
|
|
|
|
payload: delivery.payload,
|
|
|
|
|
};
|
|
|
|
|
publish_frame(
|
|
|
|
|
&self.dashboard,
|
|
|
|
|
&target_stream,
|
|
|
|
|
&channel_name,
|
|
|
|
|
&frame,
|
|
|
|
|
origin,
|
|
|
|
|
&label,
|
|
|
|
|
);
|
2026-08-15 14:10:52 +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
|
|
|
telemetry::frame::TelemetryEvent::ChannelDeclared(descriptor) => {
|
|
|
|
|
let key = descriptor.stream.node.as_str().to_string();
|
|
|
|
|
self.remote_streams
|
|
|
|
|
.entry(key)
|
|
|
|
|
.or_default()
|
|
|
|
|
.channels
|
|
|
|
|
.insert(descriptor.id, descriptor.name);
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
2026-08-15 14:10:52 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Replace ready nodes whose child has died (shape-native replacement:
|
|
|
|
|
/// retire the dead singleton slot and add a fresh one).
|
|
|
|
|
fn replace_dead_ready_nodes(&mut self, now: SystemTime) {
|
|
|
|
|
let state = self.driver.state().clone();
|
|
|
|
|
let mut replacements: Vec<(String, String)> = Vec::new();
|
|
|
|
|
for (id, managed) in &state.nodes {
|
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
|
|
|
if managed.record.ready
|
|
|
|
|
&& managed.intent == provisioning::reconciler::NodeIntent::Active
|
2026-08-15 14:10:52 +00:00
|
|
|
{
|
|
|
|
|
let Some(runtime) = self.manager.get(managed.attempt.0) else {
|
|
|
|
|
continue;
|
|
|
|
|
};
|
|
|
|
|
if runtime.exited.is_some() {
|
|
|
|
|
self.slot_seq += 1;
|
|
|
|
|
let fresh = format!("node-{}", self.slot_seq);
|
|
|
|
|
replacements.push((id.0.clone(), fresh));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if replacements.is_empty() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
for (dead, fresh) in &replacements {
|
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
|
|
|
self.emit_event(
|
|
|
|
|
"control",
|
|
|
|
|
dead,
|
|
|
|
|
format!("runtime death; replacing as {fresh}"),
|
|
|
|
|
);
|
2026-08-15 14:10:52 +00:00
|
|
|
self.slots.retain(|slot| slot_group_id(slot) != *dead);
|
|
|
|
|
self.slots.push(fresh.clone());
|
|
|
|
|
}
|
|
|
|
|
let generation = self.driver.desired().generation.saturating_add(1);
|
|
|
|
|
if let Err(error) = self.driver.update_desired(self.desired_shape(generation)) {
|
|
|
|
|
eprintln!("demo: replace update_desired failed: {error}");
|
|
|
|
|
}
|
|
|
|
|
let _ = now;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// One poll pass mirroring the production reconciler loop.
|
|
|
|
|
fn poll(&mut self, now: SystemTime) {
|
|
|
|
|
// 1. Drain executor results; close bootstrap after convergence.
|
|
|
|
|
for result in self.executor.drain_results() {
|
|
|
|
|
let close = matches!(
|
|
|
|
|
result.result,
|
|
|
|
|
Ok(OperationOutcome::BootstrapConvergenceAccepted)
|
|
|
|
|
)
|
|
|
|
|
.then_some((result.node.clone(), result.operation.attempt));
|
|
|
|
|
let detail = match &result.result {
|
|
|
|
|
Ok(outcome) => format!("{outcome:?}"),
|
|
|
|
|
Err(error) => format!("failed: {}", error.reason),
|
|
|
|
|
};
|
|
|
|
|
let node = result.node.0.clone();
|
|
|
|
|
let applied = self.driver.apply_executor_result(result, now);
|
|
|
|
|
self.emit_event(
|
|
|
|
|
"result",
|
|
|
|
|
&node,
|
|
|
|
|
format!("{detail}{}", if applied { "" } else { " (stale)" }),
|
|
|
|
|
);
|
|
|
|
|
if applied && let Some((node, attempt)) = close {
|
|
|
|
|
self.driver.apply_observation(
|
|
|
|
|
&node.clone(),
|
|
|
|
|
attempt,
|
|
|
|
|
NodeObservation::BootstrapClosed {
|
|
|
|
|
session_id: provisioning::node::BootstrapSessionId(attempt.0),
|
|
|
|
|
},
|
|
|
|
|
now,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. Classify due operations.
|
|
|
|
|
for operation in self.driver.pending_operations_due(now) {
|
|
|
|
|
match self.executor.operation_status(operation.operation) {
|
|
|
|
|
ExecutorOperationStatus::Unknown => {
|
|
|
|
|
self.driver.operation_timed_out(
|
|
|
|
|
&operation,
|
|
|
|
|
"executor lost pending operation",
|
|
|
|
|
now,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
ExecutorOperationStatus::InFlight => {
|
|
|
|
|
self.executor.expire(
|
|
|
|
|
operation.operation,
|
|
|
|
|
"executor operation timed out with an ambiguous outcome",
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
ExecutorOperationStatus::Completed => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. Drive the state machine, recording dispatched commands.
|
|
|
|
|
self.driver.trigger_if_due(now);
|
|
|
|
|
let mut feed = FeedExecutor {
|
|
|
|
|
inner: &mut self.executor,
|
|
|
|
|
dispatched: Vec::new(),
|
|
|
|
|
};
|
|
|
|
|
match self.driver.drive_until_blocked(now, &mut feed) {
|
|
|
|
|
Ok(_) => {
|
|
|
|
|
for line in feed.dispatched {
|
|
|
|
|
self.emit_event("command", "", line);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Err(error) => {
|
|
|
|
|
self.emit_event("error", "", format!("{error}"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/// Per-node liveness heartbeat on the `node.status` channel so fleet
|
|
|
|
|
/// cards and the control view stay live between lifecycle transitions.
|
|
|
|
|
fn emit_node_status(&mut self, now: SystemTime) {
|
|
|
|
|
self.status_tick = self.status_tick.wrapping_add(1);
|
|
|
|
|
if self.status_tick % 4 != 0 {
|
|
|
|
|
return; // 250ms ticks → heartbeat every second
|
|
|
|
|
}
|
|
|
|
|
let attempts: Vec<u64> = self.nodes.keys().copied().collect();
|
|
|
|
|
for attempt in attempts {
|
|
|
|
|
let Some(streams) = self.nodes.get(&attempt) else {
|
|
|
|
|
continue;
|
|
|
|
|
};
|
|
|
|
|
let Some(runtime) = self.manager.get(attempt) else {
|
|
|
|
|
continue;
|
|
|
|
|
};
|
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
|
|
|
// Wire liveness: the node re-announces every heartbeat period,
|
|
|
|
|
// so the age of the last announce is the control-plane
|
|
|
|
|
// heartbeat. `null` until the first announce.
|
|
|
|
|
let heartbeat_ms_ago = runtime
|
|
|
|
|
.last_announce_ms
|
|
|
|
|
.map(|ms| unix_ms(now).saturating_sub(ms));
|
2026-08-15 14:10:52 +00:00
|
|
|
let payload = json!({
|
|
|
|
|
"at_ms": unix_ms(now),
|
|
|
|
|
"node": runtime.logical_node,
|
|
|
|
|
"alive": runtime.exited.is_none(),
|
|
|
|
|
"pid": runtime.pid,
|
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
|
|
|
// Process state for the Fleet Control table (same shape the
|
|
|
|
|
// process lifecycle mirror used to publish).
|
|
|
|
|
"event": if runtime.exited.is_some() { "exited" } else { "started" },
|
2026-08-15 14:10:52 +00:00
|
|
|
"heartbeat_ms_ago": heartbeat_ms_ago,
|
|
|
|
|
});
|
|
|
|
|
let bytes = serde_json::to_vec(&payload).expect("status serializes");
|
|
|
|
|
streams
|
|
|
|
|
.telemetry
|
|
|
|
|
.producer
|
|
|
|
|
.submit_bytes(streams.status_channel, bytes);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Emit stage transitions and the snapshot.
|
|
|
|
|
fn emit_feed(&mut self, now: SystemTime) {
|
|
|
|
|
let state = self.driver.state().clone();
|
|
|
|
|
let mut nodes_json = Vec::new();
|
|
|
|
|
let mut ready_count = 0_u64;
|
|
|
|
|
for (id, managed) in &state.nodes {
|
|
|
|
|
let bootstrap = managed
|
|
|
|
|
.record
|
|
|
|
|
.bootstrap
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map(|facts| facts.last_stage);
|
|
|
|
|
let current = (managed.record.stage, bootstrap);
|
|
|
|
|
let fmt_boot = |stage: Option<BootstrapStage>| {
|
|
|
|
|
stage
|
|
|
|
|
.map(|stage| format!("{stage:?}"))
|
|
|
|
|
.unwrap_or_else(|| "-".to_owned())
|
|
|
|
|
};
|
|
|
|
|
if let Some(previous) = self.last_stages.get(&id.0) {
|
|
|
|
|
if previous.0 != current.0 || previous.1 != current.1 {
|
|
|
|
|
self.emit_event(
|
|
|
|
|
"transition",
|
|
|
|
|
&id.0,
|
|
|
|
|
format!(
|
|
|
|
|
"{:?} ({}) → {:?} ({})",
|
|
|
|
|
previous.0,
|
|
|
|
|
fmt_boot(previous.1),
|
|
|
|
|
current.0,
|
|
|
|
|
fmt_boot(current.1),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} else if managed.record.stage != NodeStage::New {
|
|
|
|
|
self.emit_event(
|
|
|
|
|
"transition",
|
|
|
|
|
&id.0,
|
|
|
|
|
format!("New → {:?} ({})", current.0, fmt_boot(current.1)),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
self.last_stages.insert(id.0.clone(), current);
|
|
|
|
|
if managed.record.ready {
|
|
|
|
|
ready_count += 1;
|
|
|
|
|
}
|
|
|
|
|
let runtime_pid = self.manager.get(managed.attempt.0).and_then(|r| r.pid);
|
|
|
|
|
nodes_json.push(json!({
|
|
|
|
|
"id": id.0,
|
|
|
|
|
"intent": format!("{:?}", managed.intent),
|
|
|
|
|
"stage": format!("{:?}", managed.record.stage),
|
|
|
|
|
"bootstrap": fmt_boot(current.1),
|
|
|
|
|
"attempt": managed.attempt.0,
|
|
|
|
|
"ready": managed.record.ready,
|
|
|
|
|
"pid": runtime_pid,
|
|
|
|
|
"failure": managed.record.failed_reason,
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let snapshot = json!({
|
|
|
|
|
"at_ms": unix_ms(now),
|
|
|
|
|
"desired": self.slots.len(),
|
|
|
|
|
"ready": ready_count,
|
|
|
|
|
"generation": self.driver.desired().generation,
|
|
|
|
|
"converged": self.driver.is_converged(),
|
|
|
|
|
"nodes": nodes_json,
|
|
|
|
|
});
|
|
|
|
|
let bytes = serde_json::to_vec(&snapshot).expect("snapshot serializes");
|
|
|
|
|
self.telemetry
|
|
|
|
|
.producer
|
|
|
|
|
.submit_bytes(self.snapshot_channel, bytes);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Drain every telemetry endpoint into the dashboard.
|
|
|
|
|
fn flush_telemetry(&mut self) {
|
|
|
|
|
let supervisor_stream = self.telemetry.endpoint.stream_id().clone();
|
|
|
|
|
for frame in self.telemetry.endpoint.mux().drain() {
|
|
|
|
|
let channel = self
|
|
|
|
|
.telemetry
|
|
|
|
|
.names
|
|
|
|
|
.get(&frame.channel)
|
|
|
|
|
.cloned()
|
|
|
|
|
.unwrap_or_else(|| format!("channel#{}", frame.channel.0));
|
feat(dashboard): Segment Mask visual world and merged Fleet Control
Fleet page: drop the aggregate totals row and bar skeletons — cards carry
identity, liveness, and runtime summary; hardware detail stays one click
down. Stream descriptors (origin/label) now ride FrameEvents into the
fleet view, so the orchestrator renders as a full-width amber-framed
module pinned above the grid. Cards are real links; roster rows are
keyboard-operable.
Fleet Control: /view/reconciler folds into /view/demo-control as one
control bench — ghost-segment ready/desired counter, generation digit,
unified node rows (reconciler stage + pid + state + kill), activity feeds
demoted to a collapsed tail. The standalone reconciler page is retired;
its API stays live to feed the merge.
Visual world (both themes, nav toggle, persisted, prefers-color-scheme
default): dark = Bloomberg night housing (black ground, navy panels,
amber data register); light = Atom One Light. 2px corners, monospace
data, outline chips for states, cyan as the only interactive voice,
ghost-eight segments for counters, blink reserved for unresolved states,
depressing controls, reduced-motion collapse.
2026-08-16 11:16:30 +00:00
|
|
|
publish_frame(
|
|
|
|
|
&self.dashboard,
|
|
|
|
|
&supervisor_stream,
|
|
|
|
|
&channel,
|
|
|
|
|
&frame,
|
|
|
|
|
self.telemetry.origin,
|
|
|
|
|
self.telemetry.label,
|
|
|
|
|
);
|
2026-08-15 14:10:52 +00:00
|
|
|
}
|
|
|
|
|
let attempts: Vec<u64> = self.nodes.keys().copied().collect();
|
|
|
|
|
for attempt in attempts {
|
|
|
|
|
let Some(streams) = self.nodes.get(&attempt) else {
|
|
|
|
|
continue;
|
|
|
|
|
};
|
|
|
|
|
let stream = streams.telemetry.endpoint.stream_id().clone();
|
|
|
|
|
let catalog = streams.telemetry.endpoint.catalog_snapshot();
|
|
|
|
|
for frame in streams.telemetry.endpoint.mux().drain() {
|
|
|
|
|
let channel = catalog
|
|
|
|
|
.channels
|
|
|
|
|
.get(&telemetry::frame::ChannelRef {
|
|
|
|
|
stream: stream.clone(),
|
|
|
|
|
channel: frame.channel,
|
|
|
|
|
})
|
|
|
|
|
.map(|descriptor| descriptor.name.clone())
|
|
|
|
|
.unwrap_or_else(|| format!("channel#{}", frame.channel.0));
|
feat(dashboard): Segment Mask visual world and merged Fleet Control
Fleet page: drop the aggregate totals row and bar skeletons — cards carry
identity, liveness, and runtime summary; hardware detail stays one click
down. Stream descriptors (origin/label) now ride FrameEvents into the
fleet view, so the orchestrator renders as a full-width amber-framed
module pinned above the grid. Cards are real links; roster rows are
keyboard-operable.
Fleet Control: /view/reconciler folds into /view/demo-control as one
control bench — ghost-segment ready/desired counter, generation digit,
unified node rows (reconciler stage + pid + state + kill), activity feeds
demoted to a collapsed tail. The standalone reconciler page is retired;
its API stays live to feed the merge.
Visual world (both themes, nav toggle, persisted, prefers-color-scheme
default): dark = Bloomberg night housing (black ground, navy panels,
amber data register); light = Atom One Light. 2px corners, monospace
data, outline chips for states, cyan as the only interactive voice,
ghost-eight segments for counters, blink reserved for unresolved states,
depressing controls, reduced-motion collapse.
2026-08-16 11:16:30 +00:00
|
|
|
publish_frame(
|
|
|
|
|
&self.dashboard,
|
|
|
|
|
&stream,
|
|
|
|
|
&channel,
|
|
|
|
|
&frame,
|
|
|
|
|
streams.telemetry.origin,
|
|
|
|
|
&streams.telemetry.label,
|
|
|
|
|
);
|
2026-08-15 14:10:52 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat(dashboard): Segment Mask visual world and merged Fleet Control
Fleet page: drop the aggregate totals row and bar skeletons — cards carry
identity, liveness, and runtime summary; hardware detail stays one click
down. Stream descriptors (origin/label) now ride FrameEvents into the
fleet view, so the orchestrator renders as a full-width amber-framed
module pinned above the grid. Cards are real links; roster rows are
keyboard-operable.
Fleet Control: /view/reconciler folds into /view/demo-control as one
control bench — ghost-segment ready/desired counter, generation digit,
unified node rows (reconciler stage + pid + state + kill), activity feeds
demoted to a collapsed tail. The standalone reconciler page is retired;
its API stays live to feed the merge.
Visual world (both themes, nav toggle, persisted, prefers-color-scheme
default): dark = Bloomberg night housing (black ground, navy panels,
amber data register); light = Atom One Light. 2px corners, monospace
data, outline chips for states, cyan as the only interactive voice,
ghost-eight segments for counters, blink reserved for unresolved states,
depressing controls, reduced-motion collapse.
2026-08-16 11:16:30 +00:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
2026-08-15 14:10:52 +00:00
|
|
|
fn publish_frame(
|
|
|
|
|
dashboard: &dashboard::DashboardHandle,
|
|
|
|
|
stream: &telemetry::frame::StreamId,
|
|
|
|
|
channel: &str,
|
|
|
|
|
frame: &telemetry::frame::Frame,
|
feat(dashboard): Segment Mask visual world and merged Fleet Control
Fleet page: drop the aggregate totals row and bar skeletons — cards carry
identity, liveness, and runtime summary; hardware detail stays one click
down. Stream descriptors (origin/label) now ride FrameEvents into the
fleet view, so the orchestrator renders as a full-width amber-framed
module pinned above the grid. Cards are real links; roster rows are
keyboard-operable.
Fleet Control: /view/reconciler folds into /view/demo-control as one
control bench — ghost-segment ready/desired counter, generation digit,
unified node rows (reconciler stage + pid + state + kill), activity feeds
demoted to a collapsed tail. The standalone reconciler page is retired;
its API stays live to feed the merge.
Visual world (both themes, nav toggle, persisted, prefers-color-scheme
default): dark = Bloomberg night housing (black ground, navy panels,
amber data register); light = Atom One Light. 2px corners, monospace
data, outline chips for states, cyan as the only interactive voice,
ghost-eight segments for counters, blink reserved for unresolved states,
depressing controls, reduced-motion collapse.
2026-08-16 11:16:30 +00:00
|
|
|
origin: &str,
|
|
|
|
|
label: &str,
|
2026-08-15 14:10:52 +00:00
|
|
|
) {
|
|
|
|
|
dashboard.publish(dashboard::FrameEvent {
|
|
|
|
|
stream: dashboard::StreamEvent {
|
|
|
|
|
node: stream.node.as_str().to_string(),
|
|
|
|
|
life: stream.life.0,
|
feat(dashboard): Segment Mask visual world and merged Fleet Control
Fleet page: drop the aggregate totals row and bar skeletons — cards carry
identity, liveness, and runtime summary; hardware detail stays one click
down. Stream descriptors (origin/label) now ride FrameEvents into the
fleet view, so the orchestrator renders as a full-width amber-framed
module pinned above the grid. Cards are real links; roster rows are
keyboard-operable.
Fleet Control: /view/reconciler folds into /view/demo-control as one
control bench — ghost-segment ready/desired counter, generation digit,
unified node rows (reconciler stage + pid + state + kill), activity feeds
demoted to a collapsed tail. The standalone reconciler page is retired;
its API stays live to feed the merge.
Visual world (both themes, nav toggle, persisted, prefers-color-scheme
default): dark = Bloomberg night housing (black ground, navy panels,
amber data register); light = Atom One Light. 2px corners, monospace
data, outline chips for states, cyan as the only interactive voice,
ghost-eight segments for counters, blink reserved for unresolved states,
depressing controls, reduced-motion collapse.
2026-08-16 11:16:30 +00:00
|
|
|
origin: Some(origin.to_owned()),
|
|
|
|
|
label: Some(label.to_owned()),
|
2026-08-15 14:10:52 +00:00
|
|
|
},
|
|
|
|
|
channel: channel.to_owned(),
|
|
|
|
|
position: frame.position.0,
|
|
|
|
|
payload: frame.payload.clone(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ActorInterface for SupervisorActor {
|
|
|
|
|
type Incoming = SupervisorMsg;
|
|
|
|
|
type Response = ();
|
|
|
|
|
|
|
|
|
|
fn handle(&mut self, ctx: &Ctx, msg: SupervisorMsg) {
|
|
|
|
|
match msg {
|
|
|
|
|
SupervisorMsg::Tick => {
|
|
|
|
|
let now = SystemTime::now();
|
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
|
|
|
self.deliver_pending_reports(now);
|
2026-08-15 14:10:52 +00:00
|
|
|
self.poll(now);
|
|
|
|
|
self.observe_world(now);
|
|
|
|
|
self.replace_dead_ready_nodes(now);
|
|
|
|
|
self.poll(now);
|
|
|
|
|
self.emit_node_status(now);
|
|
|
|
|
self.emit_feed(now);
|
|
|
|
|
self.flush_telemetry();
|
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
|
|
|
self.flush_remote_streams();
|
2026-08-15 14:10:52 +00:00
|
|
|
}
|
|
|
|
|
SupervisorMsg::Control(command) => self.handle_control(command),
|
|
|
|
|
SupervisorMsg::Spawn(request) => self.spawn_node(ctx, request),
|
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
|
|
|
SupervisorMsg::Bootstrap(event) => {
|
|
|
|
|
let now = SystemTime::now();
|
|
|
|
|
self.handle_bootstrap_event(now, event);
|
|
|
|
|
self.poll(now);
|
|
|
|
|
}
|
|
|
|
|
SupervisorMsg::NodeStream {
|
|
|
|
|
header,
|
|
|
|
|
logical_node,
|
|
|
|
|
attempt,
|
|
|
|
|
} => self.register_remote_stream(header, logical_node, attempt),
|
2026-08-15 14:10:52 +00:00
|
|
|
SupervisorMsg::Shutdown { drained } => {
|
|
|
|
|
self.slots.clear();
|
|
|
|
|
let generation = self.driver.desired().generation.saturating_add(1);
|
|
|
|
|
if let Err(error) = self.driver.update_desired(self.desired_shape(generation)) {
|
|
|
|
|
eprintln!("demo: shutdown update_desired failed: {error}");
|
|
|
|
|
}
|
|
|
|
|
self.emit_event("control", "", "shutdown: desired → empty".to_owned());
|
|
|
|
|
drained.store(true, std::sync::atomic::Ordering::SeqCst);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl SupervisorActor {
|
|
|
|
|
fn handle_control(&mut self, command: dashboard::control::ControlCommand) {
|
|
|
|
|
match command {
|
|
|
|
|
dashboard::control::ControlCommand::Kill { node } => {
|
|
|
|
|
match self.manager.find_by_stream_node(&node) {
|
|
|
|
|
Some(runtime) => {
|
|
|
|
|
self.emit_event(
|
|
|
|
|
"control",
|
|
|
|
|
&node,
|
|
|
|
|
format!("kill requested (pid {:?})", runtime.pid),
|
|
|
|
|
);
|
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 _ = self.sender.send_to(
|
|
|
|
|
runtime.bootstrap,
|
|
|
|
|
provisioning::BootstrapMsg::Stop {
|
2026-08-15 14:10:52 +00:00
|
|
|
kill_after: Some(Duration::ZERO),
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
None => self.emit_event("control", &node, "kill: unknown node".to_owned()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
dashboard::control::ControlCommand::Remove { count } => {
|
|
|
|
|
let removed = self.slots.len().min(count as usize);
|
|
|
|
|
if removed == 0 {
|
|
|
|
|
self.emit_event("control", "", "remove: nothing to remove".to_owned());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
for _ in 0..removed {
|
|
|
|
|
self.slots.pop();
|
|
|
|
|
}
|
|
|
|
|
self.emit_event("control", "", format!("remove -{removed}"));
|
|
|
|
|
let generation = self.driver.desired().generation.saturating_add(1);
|
|
|
|
|
if let Err(error) = self.driver.update_desired(self.desired_shape(generation)) {
|
|
|
|
|
eprintln!("demo: remove update_desired failed: {error}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
dashboard::control::ControlCommand::Provision { count } => {
|
|
|
|
|
if count == 0 {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
self.slot_seq += count as u64;
|
|
|
|
|
let start = self.slot_seq - count as u64 + 1;
|
|
|
|
|
for seq in start..=self.slot_seq {
|
|
|
|
|
self.slots.push(format!("node-{seq}"));
|
|
|
|
|
}
|
|
|
|
|
self.emit_event("control", "", format!("provision +{count}"));
|
|
|
|
|
let generation = self.driver.desired().generation.saturating_add(1);
|
|
|
|
|
if let Err(error) = self.driver.update_desired(self.desired_shape(generation)) {
|
|
|
|
|
eprintln!("demo: provision update_desired failed: {error}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn slot_group(slot: &str) -> provisioning::node::RunNodeGroupSpec {
|
|
|
|
|
let mut group = demo_group(slot, 1);
|
|
|
|
|
group.group_id = NodeGroupId(slot.to_owned());
|
|
|
|
|
group
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn slot_group_id(slot: &str) -> String {
|
|
|
|
|
format!("{slot}-0")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn demo_group(id: &str, count: u32) -> provisioning::node::RunNodeGroupSpec {
|
|
|
|
|
provisioning::node::RunNodeGroupSpec {
|
|
|
|
|
run_id: RunId(1),
|
|
|
|
|
group_id: NodeGroupId(id.to_owned()),
|
|
|
|
|
role: RoleId("worker".to_owned()),
|
|
|
|
|
count,
|
|
|
|
|
provider: provisioning::node::ProviderKind::new("demo"),
|
|
|
|
|
shape: provisioning::node::DesiredNodeShape {
|
|
|
|
|
image: "demo-node".to_owned(),
|
|
|
|
|
disk_gb: 1,
|
|
|
|
|
gpu_name: None,
|
|
|
|
|
min_gpu_ram_mb: None,
|
|
|
|
|
min_down_mbps: None,
|
|
|
|
|
min_up_mbps: None,
|
|
|
|
|
min_reliability: None,
|
|
|
|
|
require_verified: false,
|
|
|
|
|
provider_labels: BTreeMap::new(),
|
|
|
|
|
},
|
|
|
|
|
boot: provisioning::node::BootSpec {
|
|
|
|
|
ssh_user: "demo".to_owned(),
|
|
|
|
|
verify_commands: vec!["true".to_owned()],
|
|
|
|
|
start_swactor_command: "xtask provisioning-reconciler-demo".to_owned(),
|
|
|
|
|
stdout_sources: Vec::new(),
|
|
|
|
|
stderr_sources: Vec::new(),
|
|
|
|
|
env: Vec::new(),
|
|
|
|
|
args: Vec::new(),
|
|
|
|
|
mounts: Vec::new(),
|
|
|
|
|
},
|
|
|
|
|
swarm_join: provisioning::node::SwarmJoinTemplate {
|
|
|
|
|
orch_swactor_addr: "127.0.0.1:1".to_owned(),
|
|
|
|
|
join_token_ref: "demo".to_owned(),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Marker helpers used by tests and the module glue.
|
|
|
|
|
pub fn initial_slots(count: u64) -> Vec<String> {
|
|
|
|
|
(0..count).map(|index| format!("node-{index}")).collect()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn demo_retry_policy() -> RetryPolicy {
|
|
|
|
|
RetryPolicy {
|
|
|
|
|
initial_delay: Duration::from_millis(500),
|
|
|
|
|
max_delay: Duration::from_secs(2),
|
|
|
|
|
jitter: Duration::ZERO,
|
|
|
|
|
operation_timeout: Duration::from_secs(10),
|
|
|
|
|
endpoint_probe_interval: Duration::from_secs(1),
|
|
|
|
|
}
|
|
|
|
|
}
|