diff --git a/Cargo.lock b/Cargo.lock
index 2de3a1b..3079515 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1327,13 +1327,12 @@ dependencies = [
name = "distribution"
version = "0.1.0"
dependencies = [
- "ed25519-dalek 2.2.0",
"iroh",
"iroh-relay",
- "rand_core 0.6.4",
"serde",
"serde_json",
"swactor",
+ "swactor-transport",
"tokio",
]
@@ -5337,16 +5336,15 @@ dependencies = [
"clap",
"crossbeam-queue",
"ctrlc",
- "ed25519-dalek 2.2.0",
"getrandom 0.2.17",
"iroh",
"proptest",
"proptest-state-machine",
- "rand_core 0.6.4",
"serde",
"serde_json",
"stateright",
"swactor",
+ "swactor-transport",
"tempfile",
"tiny_http",
"tokio",
diff --git a/crates/dashboard/src/actor_detail_html.rs b/crates/dashboard/src/actor_detail_html.rs
deleted file mode 100644
index 6364cee..0000000
--- a/crates/dashboard/src/actor_detail_html.rs
+++ /dev/null
@@ -1,416 +0,0 @@
-pub const ACTOR_DETAIL_HTML: &str = r##"
-
-
-
-
-Actor Detail — Swactor Dashboard
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Message Rate
-
-
-
-
-
Mailbox Depth
-
-
-
-
-
-
-
- Logs
- (0)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-"##;
diff --git a/crates/dashboard/src/actors_html.rs b/crates/dashboard/src/actors_html.rs
deleted file mode 100644
index f4fe739..0000000
--- a/crates/dashboard/src/actors_html.rs
+++ /dev/null
@@ -1,807 +0,0 @@
-pub const ACTORS_HTML: &str = r##"
-
-
-
-
-Swactor Runtime – Actors
-
-
-
-
-
-
-
-
-
-
-
-
-
Mailbox Depth Distribution
-
-
-
-
-
-
Actors per Worker
-
-
-
-
-
-
All Actors
-
-
-
-
-
-
-
-
-
-
-
- | Address |
- Worker |
- Mailbox |
- Msgs |
- Last Msg |
- Depth |
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Mailbox History
-
-
-
-
-
-
-"##;
diff --git a/crates/dashboard/src/command/mod.rs b/crates/dashboard/src/command/mod.rs
index ad1e2b3..0ec0cec 100644
--- a/crates/dashboard/src/command/mod.rs
+++ b/crates/dashboard/src/command/mod.rs
@@ -16,9 +16,6 @@
//! ```
pub mod builtins;
-mod parse;
-
-pub use parse::{from_query_params, parse_line};
use std::collections::HashMap;
use std::sync::Arc;
@@ -246,3 +243,78 @@ impl CommandRouter {
names
}
}
+
+// ─── Input Parsers ──────────────────────────────────────────────────────────
+
+/// Parse a REPL text line into a [`CommandRequest`].
+///
+/// Handles `--flag value` pairs and maps positional arguments to
+/// command-specific named parameters.
+pub fn parse_line(line: &str) -> CommandRequest {
+ let parts: Vec<&str> = line.split_whitespace().collect();
+ if parts.is_empty() {
+ return CommandRequest {
+ command: "help".to_string(),
+ args: HashMap::new(),
+ };
+ }
+ let command = parts[0].to_string();
+ let rest = &parts[1..];
+
+ let mut args = HashMap::new();
+ let mut i = 0;
+ let mut positional = 0;
+
+ while i < rest.len() {
+ if let Some(key) = rest[i].strip_prefix("--") {
+ if i + 1 < rest.len() && !rest[i + 1].starts_with("--") {
+ args.insert(
+ key.to_string(),
+ serde_json::Value::String(rest[i + 1].to_string()),
+ );
+ i += 2;
+ } else {
+ args.insert(key.to_string(), serde_json::Value::Bool(true));
+ i += 1;
+ }
+ } else {
+ let name = positional_arg_name(&command, positional);
+ if !name.is_empty() {
+ args.insert(
+ name.to_string(),
+ serde_json::Value::String(rest[i].to_string()),
+ );
+ }
+ positional += 1;
+ i += 1;
+ }
+ }
+
+ CommandRequest { command, args }
+}
+
+/// Convert HTTP query parameters to a [`CommandRequest`].
+pub fn from_query_params(params: &HashMap) -> CommandRequest {
+ let command = params
+ .get("cmd")
+ .cloned()
+ .unwrap_or_else(|| "help".into());
+ let args: HashMap = params
+ .iter()
+ .filter(|(k, _)| *k != "cmd")
+ .map(|(k, v)| (k.clone(), serde_json::Value::String(v.clone())))
+ .collect();
+ CommandRequest { command, args }
+}
+
+/// Map positional argument index to the named parameter for each command.
+fn positional_arg_name(command: &str, position: usize) -> &'static str {
+ match (command, position) {
+ ("worker", 0) => "id",
+ ("actor", 0) => "prefix",
+ ("hot", 0) => "n",
+ ("phases", 0) => "worker",
+ ("diff", 0) => "seconds",
+ _ => "",
+ }
+}
diff --git a/crates/dashboard/src/command/parse.rs b/crates/dashboard/src/command/parse.rs
deleted file mode 100644
index 4809bde..0000000
--- a/crates/dashboard/src/command/parse.rs
+++ /dev/null
@@ -1,90 +0,0 @@
-//! Input parsers for REPL lines and HTTP query parameters.
-
-use std::collections::HashMap;
-
-use super::CommandRequest;
-
-/// Parse a REPL text line into a [`CommandRequest`].
-///
-/// Handles `--flag value` pairs and maps positional arguments to
-/// command-specific named parameters.
-///
-/// # Examples
-///
-/// ```text
-/// "overview" → { command: "overview", args: {} }
-/// "worker 3" → { command: "worker", args: { "id": "3" } }
-/// "actors --sort mailbox" → { command: "actors", args: { "sort": "mailbox" } }
-/// "hot 5" → { command: "hot", args: { "n": "5" } }
-/// ```
-pub fn parse_line(line: &str) -> CommandRequest {
- let parts: Vec<&str> = line.split_whitespace().collect();
- if parts.is_empty() {
- return CommandRequest {
- command: "help".to_string(),
- args: HashMap::new(),
- };
- }
- let command = parts[0].to_string();
- let rest = &parts[1..];
-
- let mut args = HashMap::new();
- let mut i = 0;
- let mut positional = 0;
-
- while i < rest.len() {
- if let Some(key) = rest[i].strip_prefix("--") {
- if i + 1 < rest.len() && !rest[i + 1].starts_with("--") {
- args.insert(
- key.to_string(),
- serde_json::Value::String(rest[i + 1].to_string()),
- );
- i += 2;
- } else {
- args.insert(key.to_string(), serde_json::Value::Bool(true));
- i += 1;
- }
- } else {
- let name = positional_arg_name(&command, positional);
- if !name.is_empty() {
- args.insert(
- name.to_string(),
- serde_json::Value::String(rest[i].to_string()),
- );
- }
- positional += 1;
- i += 1;
- }
- }
-
- CommandRequest { command, args }
-}
-
-/// Convert HTTP query parameters to a [`CommandRequest`].
-///
-/// The `cmd` parameter becomes the command name; all other parameters
-/// become string-valued arguments.
-pub fn from_query_params(params: &HashMap) -> CommandRequest {
- let command = params
- .get("cmd")
- .cloned()
- .unwrap_or_else(|| "help".into());
- let args: HashMap = params
- .iter()
- .filter(|(k, _)| *k != "cmd")
- .map(|(k, v)| (k.clone(), serde_json::Value::String(v.clone())))
- .collect();
- CommandRequest { command, args }
-}
-
-/// Map positional argument index to the named parameter for each command.
-fn positional_arg_name(command: &str, position: usize) -> &'static str {
- match (command, position) {
- ("worker", 0) => "id",
- ("actor", 0) => "prefix",
- ("hot", 0) => "n",
- ("phases", 0) => "worker",
- ("diff", 0) => "seconds",
- _ => "",
- }
-}
diff --git a/crates/dashboard/src/dashboard_html.rs b/crates/dashboard/src/dashboard_html.rs
deleted file mode 100644
index b3a3a1b..0000000
--- a/crates/dashboard/src/dashboard_html.rs
+++ /dev/null
@@ -1,663 +0,0 @@
-pub const DASHBOARD_HTML: &str = r##"
-
-
-
-
-Swactor Runtime Dashboard
-
-
-
-
-
-
-
-
-
-
Worker Utilization
-
-
- \u25A0 processing
- \u25A0 delivery
- \u25A0 spawns
- \u25A0 overhead
-
-
-
-
-
-
-
-
-
-
-
Activity Log
-
-
- | Seq | Time | Level | Worker | Message | Fields |
-
-
-
-
-
-
-
-
-
-"##;
diff --git a/crates/dashboard/src/html.rs b/crates/dashboard/src/html.rs
new file mode 100644
index 0000000..d4edf36
--- /dev/null
+++ b/crates/dashboard/src/html.rs
@@ -0,0 +1,2168 @@
+pub const ACTOR_DETAIL_HTML: &str = r##"
+
+
+
+
+Actor Detail — Swactor Dashboard
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Message Rate
+
+
+
+
+
Mailbox Depth
+
+
+
+
+
+
+
+ Logs
+ (0)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+"##;
+pub const ACTORS_HTML: &str = r##"
+
+
+
+
+Swactor Runtime – Actors
+
+
+
+
+
+
+
+
+
+
+
+
+
Mailbox Depth Distribution
+
+
+
+
+
+
Actors per Worker
+
+
+
+
+
+
All Actors
+
+
+
+
+
+
+
+
+
+
+
+ | Address |
+ Worker |
+ Mailbox |
+ Msgs |
+ Last Msg |
+ Depth |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Mailbox History
+
+
+
+
+
+
+"##;
+pub const DASHBOARD_HTML: &str = r##"
+
+
+
+
+Swactor Runtime Dashboard
+
+
+
+
+
+
+
+
+
+
Worker Utilization
+
+
+ \u25A0 processing
+ \u25A0 delivery
+ \u25A0 spawns
+ \u25A0 overhead
+
+
+
+
+
+
+
+
+
+
+
Activity Log
+
+
+ | Seq | Time | Level | Worker | Message | Fields |
+
+
+
+
+
+
+
+
+
+"##;
+pub const TOPOLOGY_HTML: &str = r##"
+
+
+
+
+Topology — Swactor Dashboard
+
+
+
+
+
+
+
+
+ Node size = actor count. Edge thickness = message volume. Green = local sends. Blue = cross-worker sends.
+
+
+
+
+
+
+"##;
diff --git a/crates/dashboard/src/lib.rs b/crates/dashboard/src/lib.rs
index 948bd81..daa7b9f 100644
--- a/crates/dashboard/src/lib.rs
+++ b/crates/dashboard/src/lib.rs
@@ -4,14 +4,10 @@ pub mod history;
pub mod investigate;
pub mod layer;
pub mod plugin;
-pub mod trace;
pub mod warnings;
-mod actor_detail_html;
-mod actors_html;
-mod dashboard_html;
+mod html;
mod server;
pub mod topology;
-mod topology_html;
#[cfg(feature = "tui")]
pub mod tui;
@@ -28,11 +24,29 @@ use swactor::runtime::{Runtime, RuntimeHandle};
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
+use serde::{Deserialize, Serialize};
+use swactor::stats::RuntimeStats;
+
use crate::collector::StatsCollector;
use crate::history::{DashboardHistory, HistoryConfig};
-use crate::layer::{now_ms, DashboardLayer, EventStore};
+use crate::layer::{now_ms, DashboardEvent, DashboardLayer, EventStore};
use crate::plugin::PluginRegistry;
-use crate::trace::{RuntimeTrace, TimestampedStats};
+
+// ─── Trace Types ────────────────────────────────────────────────────────────
+
+/// Complete trace of a runtime execution, suitable for saving/loading.
+#[derive(Debug, Serialize, Deserialize)]
+pub struct RuntimeTrace {
+ pub events: Vec,
+ pub stats_timeline: Vec,
+}
+
+/// A stats snapshot with a wall-clock timestamp.
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct TimestampedStats {
+ pub timestamp_ms: u64,
+ pub stats: RuntimeStats,
+}
/// Peer info sent through the join channel.
pub struct JoinPeerInfo {
@@ -150,7 +164,7 @@ impl DashboardHandle {
}
/// Start the HTTP server on a standalone tokio runtime (1 worker thread).
- /// Use this when no external tokio runtime is available (e.g. TCP transport).
+ /// Use this when no external tokio runtime is available (e.g. non-async transport).
pub fn start_http_standalone(&self) {
let rt = tokio::runtime::Builder::new_multi_thread()
.worker_threads(1)
diff --git a/crates/dashboard/src/server.rs b/crates/dashboard/src/server.rs
index 79588c5..7b94abf 100644
--- a/crates/dashboard/src/server.rs
+++ b/crates/dashboard/src/server.rs
@@ -16,20 +16,17 @@ use tokio_stream::StreamExt;
use swactor::runtime::Runtime;
-use crate::actor_detail_html::ACTOR_DETAIL_HTML;
-use crate::actors_html::ACTORS_HTML;
use crate::collector::StatsCollector;
use crate::command::CommandRouter;
-use crate::dashboard_html::DASHBOARD_HTML;
use crate::history::DashboardHistory;
+use crate::html::{ACTOR_DETAIL_HTML, ACTORS_HTML, DASHBOARD_HTML, TOPOLOGY_HTML};
use crate::layer::EventStore;
use crate::topology;
-use crate::topology_html::TOPOLOGY_HTML;
use crate::warnings::{WarningConfig, WarningDetector};
use crate::plugin::PluginRegistry;
-use crate::trace::RuntimeTrace;
+use crate::RuntimeTrace;
/// Format a server-sent event.
fn format_sse(event: &str, data: &str) -> Event {
diff --git a/crates/dashboard/src/topology_html.rs b/crates/dashboard/src/topology_html.rs
deleted file mode 100644
index f9f5f07..0000000
--- a/crates/dashboard/src/topology_html.rs
+++ /dev/null
@@ -1,282 +0,0 @@
-pub const TOPOLOGY_HTML: &str = r##"
-
-
-
-
-Topology — Swactor Dashboard
-
-
-
-
-
-
-
-
- Node size = actor count. Edge thickness = message volume. Green = local sends. Blue = cross-worker sends.
-
-
-
-
-
-
-"##;
diff --git a/crates/dashboard/src/trace.rs b/crates/dashboard/src/trace.rs
deleted file mode 100644
index 09505d5..0000000
--- a/crates/dashboard/src/trace.rs
+++ /dev/null
@@ -1,18 +0,0 @@
-use serde::{Deserialize, Serialize};
-use swactor::stats::RuntimeStats;
-
-use crate::layer::DashboardEvent;
-
-/// Complete trace of a runtime execution, suitable for saving/loading.
-#[derive(Debug, Serialize, Deserialize)]
-pub struct RuntimeTrace {
- pub events: Vec,
- pub stats_timeline: Vec,
-}
-
-/// A stats snapshot with a wall-clock timestamp.
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct TimestampedStats {
- pub timestamp_ms: u64,
- pub stats: RuntimeStats,
-}
diff --git a/crates/dashboard/tests/dashboard_core.rs b/crates/dashboard/tests/dashboard_core.rs
index 1beff8f..9b18f56 100644
--- a/crates/dashboard/tests/dashboard_core.rs
+++ b/crates/dashboard/tests/dashboard_core.rs
@@ -2,7 +2,7 @@ use std::sync::Arc;
use dashboard::collector::StatsCollector;
use dashboard::layer::{DashboardEvent, EventStore};
-use dashboard::trace::RuntimeTrace;
+use dashboard::RuntimeTrace;
use swactor::actor::ActorAddress;
use swactor::stats::{ActorSnapshot, StatsHook};
diff --git a/crates/datastore/Cargo.toml b/crates/datastore/Cargo.toml
index cfa67e5..854a647 100644
--- a/crates/datastore/Cargo.toml
+++ b/crates/datastore/Cargo.toml
@@ -8,8 +8,7 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(kani)'] }
[dependencies]
swactor = { path = "../..", features = ["serde", "transport"] }
-ed25519-dalek = { version = "2", features = ["rand_core"] }
-rand_core = { version = "0.6", features = ["getrandom"] }
+swactor-transport = { path = "../transport" }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
blake3 = "1"
@@ -35,6 +34,7 @@ stateright = "0.31"
[features]
node = ["dep:tiny_http", "dep:clap", "dep:ctrlc", "dep:toml"]
cli = ["dep:clap", "dep:ureq"]
+formal-verification = []
[[bin]]
name = "swactor-store"
diff --git a/crates/datastore/src/bin/store_cli.rs b/crates/datastore/src/bin/store_cli.rs
index a1a2568..c4d5ebd 100644
--- a/crates/datastore/src/bin/store_cli.rs
+++ b/crates/datastore/src/bin/store_cli.rs
@@ -316,75 +316,6 @@ fn resolve_authorized_key(base: &str, name_input: &str, kp: &Keypair) -> String
}
}
-// ── Key file helpers ────────────────────────────────────────────────────────
-
-fn hex_decode(hex: &str) -> Option> {
- if hex.len() % 2 != 0 {
- return None;
- }
- let mut bytes = Vec::with_capacity(hex.len() / 2);
- for chunk in hex.as_bytes().chunks(2) {
- let hi = hex_digit(chunk[0])?;
- let lo = hex_digit(chunk[1])?;
- bytes.push((hi << 4) | lo);
- }
- Some(bytes)
-}
-
-fn hex_digit(b: u8) -> Option {
- match b {
- b'0'..=b'9' => Some(b - b'0'),
- b'a'..=b'f' => Some(b - b'a' + 10),
- b'A'..=b'F' => Some(b - b'A' + 10),
- _ => None,
- }
-}
-
-fn load_keypair(path: &std::path::Path) -> Keypair {
- let data = fs::read_to_string(path).unwrap_or_else(|e| {
- eprintln!("Error reading key file {}: {e}", path.display());
- std::process::exit(1);
- });
- let json: serde_json::Value = serde_json::from_str(&data).unwrap_or_else(|e| {
- eprintln!("Error parsing key file: {e}");
- std::process::exit(1);
- });
- let secret_hex = json
- .get("secret_key")
- .and_then(|v| v.as_str())
- .unwrap_or_else(|| {
- eprintln!("Key file missing secret_key field");
- std::process::exit(1);
- });
- let secret_bytes = hex_decode(secret_hex).unwrap_or_else(|| {
- eprintln!("Invalid secret_key hex in key file");
- std::process::exit(1);
- });
- let secret: [u8; 32] = secret_bytes.try_into().unwrap_or_else(|_| {
- eprintln!("secret_key must be exactly 32 bytes");
- std::process::exit(1);
- });
- Keypair::from_bytes(&secret)
-}
-
-// ── Auth signing ────────────────────────────────────────────────────────────
-
-fn sign_action(keypair: &Keypair, action: DatastoreAction) -> String {
- let timestamp = SystemTime::now()
- .duration_since(UNIX_EPOCH)
- .unwrap()
- .as_secs();
- let mut nonce = [0u8; 16];
- getrandom::getrandom(&mut nonce).expect("failed to generate random nonce");
- let payload = SignedRequestPayload {
- action,
- timestamp,
- nonce,
- };
- let signed = sign_request(keypair, payload);
- serde_json::to_string(&signed).expect("SignedRequest is always serializable")
-}
-
fn main() {
let args = Args::parse();
let base = args.url.trim_end_matches('/');
diff --git a/crates/datastore/src/cli.rs b/crates/datastore/src/cli.rs
deleted file mode 100644
index 8c126c7..0000000
--- a/crates/datastore/src/cli.rs
+++ /dev/null
@@ -1,72 +0,0 @@
-//! CLI command type definitions for `swactor-store`.
-//!
-//! Types only — no implementation. These define the CLI interface that will
-//! be wired to the actor system in a future milestone.
-
-use std::collections::BTreeMap;
-use std::path::PathBuf;
-
-use swactor::transport::NodeId;
-
-/// Top-level CLI commands for `swactor-store`.
-#[derive(Debug, Clone)]
-pub enum CliCommand {
- /// Store a local file as a distributed object.
- ///
- /// ```text
- /// swactor-store put [--name