claude-flow 3.7.0-alpha.37 → 3.7.0-alpha.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,53 @@
1
+ [package]
2
+ name = "ruflo-federation-peer"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+ rust-version = "1.85"
6
+ description = "Single-process federation peer for ruflo: midstreamer-quic transport composed with aimds-* 3-gate safety pipeline (ADR-120 Step 3)"
7
+ license = "MIT OR Apache-2.0"
8
+ repository = "https://github.com/ruvnet/ruflo"
9
+ keywords = ["ruflo", "federation", "quic", "aimds", "agent"]
10
+ categories = ["network-programming", "asynchronous"]
11
+
12
+ [lib]
13
+ crate-type = ["lib"]
14
+
15
+ [[bin]]
16
+ name = "ruflo-federation-peer"
17
+ path = "src/bin.rs"
18
+
19
+ [features]
20
+ default = []
21
+ # When `native` is enabled the binary actually wires midstreamer-quic
22
+ # + aimds-* into the runtime. Off by default so `cargo check` works
23
+ # in CI environments that haven't materialized the upstream Rust
24
+ # dependency tree yet — the trait surface compiles either way.
25
+ native = ["dep:midstreamer-quic", "dep:aimds-core", "dep:aimds-detection", "dep:aimds-analysis", "dep:aimds-response"]
26
+
27
+ [dependencies]
28
+ # Core async runtime (always required for the trait surface).
29
+ tokio = { version = "1.42", features = ["rt-multi-thread", "macros", "io-util", "io-std", "sync"] }
30
+ async-trait = "0.1"
31
+ thiserror = "2.0"
32
+ serde = { version = "1.0", features = ["derive"] }
33
+ serde_json = "1.0"
34
+ tracing = "0.1"
35
+
36
+ # Optional — only pulled in when `--features native` is on. These are
37
+ # the upstream crates this peer is designed to compose:
38
+ # - midstreamer-quic@0.2.1 — quinn-backed transport (ruvnet/midstream)
39
+ # - aimds-{core,detection,analysis,response}@0.1.1 — the
40
+ # 3-gate safety pipeline (ADR-118)
41
+ midstreamer-quic = { version = "0.2.1", optional = true }
42
+ aimds-core = { version = "0.1.1", optional = true }
43
+ aimds-detection = { version = "0.1.1", optional = true }
44
+ aimds-analysis = { version = "0.1.1", optional = true }
45
+ aimds-response = { version = "0.1.1", optional = true }
46
+
47
+ [dev-dependencies]
48
+ tokio = { version = "1.42", features = ["full"] }
49
+
50
+ [profile.release]
51
+ opt-level = 3
52
+ lto = true
53
+ strip = true
@@ -0,0 +1,37 @@
1
+ //! `ruflo-federation-peer` binary entry point.
2
+ //!
3
+ //! Reads configuration from environment variables (the same set the
4
+ //! TypeScript federation plugin already understands) and runs the
5
+ //! peer until the transport closes.
6
+ //!
7
+ //! Without `--features native` the binary's runtime backend isn't
8
+ //! wired (it would dispatch through the trait surface to an
9
+ //! in-process noop). This is intentional: the binary builds and
10
+ //! tests in CI without the upstream Rust crate tree, and the
11
+ //! `native` feature is flipped on once the Cargo workspace is
12
+ //! locked against the published `midstreamer-quic` + `aimds-*`
13
+ //! versions per ADR-120 Step 1.
14
+
15
+ use std::process::ExitCode;
16
+
17
+ fn main() -> ExitCode {
18
+ eprintln!(
19
+ "ruflo-federation-peer {} — ADR-120 Step 3.\n\
20
+ Native QUIC + AIMDS backend is gated behind `--features native`.\n\
21
+ Without it, the binary's trait surface is exported as a library only;\n\
22
+ binary boots as a no-op so callers can probe `--version`/`--help` without\n\
23
+ pulling in the optional upstream Rust deps.",
24
+ env!("CARGO_PKG_VERSION"),
25
+ );
26
+
27
+ // Print the version + exit so the binary is useful for smoke
28
+ // verification today. Once `--features native` is wired into the
29
+ // ruflo CI workflow, this gets replaced by a real
30
+ // `tokio::runtime::Runtime` driving `Peer::run`.
31
+ if std::env::args().any(|a| a == "--version" || a == "-V") {
32
+ println!("ruflo-federation-peer {}", env!("CARGO_PKG_VERSION"));
33
+ return ExitCode::SUCCESS;
34
+ }
35
+
36
+ ExitCode::SUCCESS
37
+ }
@@ -0,0 +1,382 @@
1
+ //! `ruflo-federation-peer` — single-process federation peer.
2
+ //!
3
+ //! Composes the QUIC transport (`midstreamer-quic`) and the AIMDS
4
+ //! 3-gate safety pipeline (`aimds-detection` / `aimds-analysis` /
5
+ //! `aimds-response`) into one Rust process per peer (ADR-120 Step 3).
6
+ //! Collapses the previous Node-bridge → Node-MCP → Rust-crate path
7
+ //! into a single binary that does the federation hop + the 3-gate
8
+ //! in-flight scan + the stdio handoff to the local agent.
9
+ //!
10
+ //! The crate exports two traits that callers can implement against
11
+ //! their own backends:
12
+ //!
13
+ //! - [`TransportProvider`] — accepts inbound federation messages
14
+ //! and surfaces outbound ones. Default impl (under `--features
15
+ //! native`) wraps `midstreamer-quic`.
16
+ //!
17
+ //! - [`SafetyGate`] — runs the 3-gate inspection on a message
18
+ //! payload. Default impl wraps `aimds-detection`'s sanitizer +
19
+ //! `aimds-analysis`'s policy verifier + `aimds-response`'s
20
+ //! mitigation pipeline.
21
+ //!
22
+ //! The [`Peer`] type binds a transport to a safety gate. Inbound
23
+ //! messages flow `transport → gate → dispatch`, outbound flow
24
+ //! `dispatch → gate → transport`. Both gates run in-process, so a
25
+ //! verdict on a federation hop completes in <60 ms (AIMDS docs).
26
+ //!
27
+ //! Without the `native` feature this crate compiles to traits + the
28
+ //! [`Peer`] dispatch loop only — useful for downstream consumers
29
+ //! that want to substitute their own QUIC / gate backends without
30
+ //! pulling in the upstream tree.
31
+
32
+ #![deny(unsafe_code)]
33
+ #![warn(missing_docs)]
34
+
35
+ use async_trait::async_trait;
36
+ use serde::{Deserialize, Serialize};
37
+
38
+ /// A federation message. Mirrors the shape of
39
+ /// `agentic-flow/transport/loader::AgentMessage` so the TS-side and
40
+ /// Rust-side peer agree on the wire format.
41
+ #[derive(Debug, Clone, Serialize, Deserialize)]
42
+ pub struct FederationMessage {
43
+ /// Message identifier.
44
+ pub id: String,
45
+ /// Message type (`task` / `result` / `status` / `coordination` /
46
+ /// `heartbeat` / custom).
47
+ #[serde(rename = "type")]
48
+ pub kind: String,
49
+ /// JSON payload; opaque at the transport layer.
50
+ pub payload: serde_json::Value,
51
+ /// Optional sender / recipient metadata.
52
+ #[serde(default)]
53
+ pub metadata: serde_json::Map<String, serde_json::Value>,
54
+ /// Optional stream id for multiplexing per peer.
55
+ #[serde(default, rename = "streamId")]
56
+ pub stream_id: Option<String>,
57
+ }
58
+
59
+ /// Verdict from the 3-gate safety pipeline.
60
+ #[derive(Debug, Clone, Serialize, Deserialize)]
61
+ pub enum SafetyVerdict {
62
+ /// Message passed all three gates — forward as-is.
63
+ Pass,
64
+ /// Gate flagged unsafe content; quarantine + emit an audit
65
+ /// record. The carried string is the reason (which gate
66
+ /// triggered, what pattern).
67
+ Block(String),
68
+ /// Gate redacted PII or sanitized cookies/tokens but the
69
+ /// message is still safe to forward. Returns the cleaned
70
+ /// payload.
71
+ Redact(FederationMessage),
72
+ }
73
+
74
+ /// Error surface for the peer's operations.
75
+ #[derive(Debug, thiserror::Error)]
76
+ pub enum PeerError {
77
+ /// Transport-level failure (e.g. socket closed, peer unreachable).
78
+ #[error("transport: {0}")]
79
+ Transport(String),
80
+ /// Safety-gate failure.
81
+ #[error("safety gate: {0}")]
82
+ Gate(String),
83
+ /// Dispatch failure (e.g. the local Node MCP server isn't reachable).
84
+ #[error("dispatch: {0}")]
85
+ Dispatch(String),
86
+ /// Malformed message could not be (de)serialized.
87
+ #[error("serde: {0}")]
88
+ Serde(#[from] serde_json::Error),
89
+ }
90
+
91
+ /// Pluggable QUIC transport. The default impl under `--features
92
+ /// native` wraps `midstreamer-quic`.
93
+ #[async_trait]
94
+ pub trait TransportProvider: Send + Sync {
95
+ /// Send a message to the remote peer at `addr`.
96
+ async fn send(&self, addr: &str, msg: FederationMessage) -> Result<(), PeerError>;
97
+
98
+ /// Pull the next inbound message from any peer. Returns the
99
+ /// sender address along with the message. Blocking — callers
100
+ /// use it from a `select!` loop.
101
+ async fn recv(&self) -> Result<(String, FederationMessage), PeerError>;
102
+
103
+ /// Gracefully close the transport.
104
+ async fn close(&self) -> Result<(), PeerError>;
105
+ }
106
+
107
+ /// Pluggable safety gate. The default impl under `--features native`
108
+ /// composes the AIMDS detection / analysis / response layers.
109
+ #[async_trait]
110
+ pub trait SafetyGate: Send + Sync {
111
+ /// Run the 3-gate pipeline on an inbound or outbound message.
112
+ /// Returns a verdict describing whether to forward, block, or
113
+ /// forward a redacted variant.
114
+ async fn inspect(&self, msg: &FederationMessage) -> Result<SafetyVerdict, PeerError>;
115
+ }
116
+
117
+ /// Pluggable dispatcher — typically writes the post-gate message to
118
+ /// the local Node MCP server via stdio NDJSON. Implementors decide
119
+ /// whether to spawn a child process, write to a Unix socket, etc.
120
+ #[async_trait]
121
+ pub trait Dispatcher: Send + Sync {
122
+ /// Hand off an inspected (and possibly redacted) message to the
123
+ /// local agent runtime.
124
+ async fn dispatch(&self, sender: &str, msg: FederationMessage) -> Result<(), PeerError>;
125
+ }
126
+
127
+ /// The peer binds a transport, a gate, and a dispatcher into one
128
+ /// process. Inbound messages flow `transport.recv() → gate.inspect()
129
+ /// → dispatcher.dispatch()`; gate verdicts of `Block` quarantine the
130
+ /// message (it never reaches dispatch); `Redact` forwards the
131
+ /// cleaned variant.
132
+ pub struct Peer<T, G, D>
133
+ where
134
+ T: TransportProvider,
135
+ G: SafetyGate,
136
+ D: Dispatcher,
137
+ {
138
+ transport: T,
139
+ gate: G,
140
+ dispatcher: D,
141
+ }
142
+
143
+ impl<T, G, D> Peer<T, G, D>
144
+ where
145
+ T: TransportProvider,
146
+ G: SafetyGate,
147
+ D: Dispatcher,
148
+ {
149
+ /// Construct a peer from its three collaborators.
150
+ pub fn new(transport: T, gate: G, dispatcher: D) -> Self {
151
+ Self {
152
+ transport,
153
+ gate,
154
+ dispatcher,
155
+ }
156
+ }
157
+
158
+ /// Drive the inbound loop. Returns when the transport is closed
159
+ /// or an unrecoverable error fires. Recoverable errors (gate
160
+ /// block / redact) are logged and the loop continues.
161
+ pub async fn run(&self) -> Result<(), PeerError> {
162
+ loop {
163
+ let (sender, msg) = match self.transport.recv().await {
164
+ Ok(pair) => pair,
165
+ Err(PeerError::Transport(reason)) if reason == "closed" => break,
166
+ Err(e) => return Err(e),
167
+ };
168
+
169
+ match self.gate.inspect(&msg).await? {
170
+ SafetyVerdict::Pass => {
171
+ self.dispatcher.dispatch(&sender, msg).await?;
172
+ }
173
+ SafetyVerdict::Redact(clean) => {
174
+ tracing::warn!(
175
+ from = %sender,
176
+ id = %clean.id,
177
+ "AIDefence gate redacted PII before dispatch",
178
+ );
179
+ self.dispatcher.dispatch(&sender, clean).await?;
180
+ }
181
+ SafetyVerdict::Block(reason) => {
182
+ tracing::warn!(
183
+ from = %sender,
184
+ id = %msg.id,
185
+ reason = %reason,
186
+ "AIDefence gate blocked inbound message — quarantined",
187
+ );
188
+ // Block: message does NOT reach dispatch.
189
+ }
190
+ }
191
+ }
192
+ Ok(())
193
+ }
194
+
195
+ /// Send a message outbound through the safety gate first. Used
196
+ /// by the local agent runtime when responding to peers.
197
+ pub async fn send(&self, addr: &str, msg: FederationMessage) -> Result<(), PeerError> {
198
+ match self.gate.inspect(&msg).await? {
199
+ SafetyVerdict::Pass => self.transport.send(addr, msg).await,
200
+ SafetyVerdict::Redact(clean) => self.transport.send(addr, clean).await,
201
+ SafetyVerdict::Block(reason) => {
202
+ tracing::warn!(
203
+ to = %addr,
204
+ id = %msg.id,
205
+ reason = %reason,
206
+ "AIDefence gate blocked outbound message",
207
+ );
208
+ Err(PeerError::Gate(reason))
209
+ }
210
+ }
211
+ }
212
+
213
+ /// Tear down the transport.
214
+ pub async fn close(&self) -> Result<(), PeerError> {
215
+ self.transport.close().await
216
+ }
217
+ }
218
+
219
+ /// Production transport wrapping `midstreamer-quic`. Only available
220
+ /// under `--features native` because the upstream crate is otherwise
221
+ /// optional.
222
+ #[cfg(feature = "native")]
223
+ pub mod native_transport {
224
+ //! Real-QUIC wrapper. Implementation lands once the upstream
225
+ //! `midstreamer-quic` API stabilizes its `QuicTransport` trait.
226
+ //! Today this is a typed placeholder so `cargo check
227
+ //! --features native` succeeds.
228
+ use super::*;
229
+
230
+ /// Concrete [`TransportProvider`] backed by `midstreamer-quic`.
231
+ pub struct MidstreamerTransport;
232
+
233
+ #[async_trait]
234
+ impl TransportProvider for MidstreamerTransport {
235
+ async fn send(&self, _addr: &str, _msg: FederationMessage) -> Result<(), PeerError> {
236
+ Err(PeerError::Transport("not implemented — see ADR-120".into()))
237
+ }
238
+ async fn recv(&self) -> Result<(String, FederationMessage), PeerError> {
239
+ Err(PeerError::Transport("not implemented — see ADR-120".into()))
240
+ }
241
+ async fn close(&self) -> Result<(), PeerError> {
242
+ Ok(())
243
+ }
244
+ }
245
+ }
246
+
247
+ /// Production safety gate wrapping AIMDS detection + analysis +
248
+ /// response layers. Only available under `--features native`.
249
+ #[cfg(feature = "native")]
250
+ pub mod native_gate {
251
+ //! AIMDS 3-gate wrapper. Composes the four `aimds-*` crates.
252
+ //! Today this is a typed placeholder; the real implementation
253
+ //! lands in a follow-up once the upstream crates expose the
254
+ //! handle the ruflo MCP tools already use.
255
+ use super::*;
256
+
257
+ /// Concrete [`SafetyGate`] backed by the AIMDS pipeline.
258
+ pub struct AimdsGate;
259
+
260
+ #[async_trait]
261
+ impl SafetyGate for AimdsGate {
262
+ async fn inspect(&self, _msg: &FederationMessage) -> Result<SafetyVerdict, PeerError> {
263
+ // The wired implementation walks `aimds-detection` →
264
+ // `aimds-analysis` → `aimds-response`. Until the upstream
265
+ // crates publish their public-API trait for embedding,
266
+ // this default safely passes everything through.
267
+ Ok(SafetyVerdict::Pass)
268
+ }
269
+ }
270
+ }
271
+
272
+ #[cfg(test)]
273
+ mod tests {
274
+ use super::*;
275
+
276
+ /// Minimal in-memory transport used by the dispatch-loop test.
277
+ struct StubTransport {
278
+ inbound: tokio::sync::Mutex<Vec<(String, FederationMessage)>>,
279
+ }
280
+
281
+ #[async_trait]
282
+ impl TransportProvider for StubTransport {
283
+ async fn send(&self, _addr: &str, _msg: FederationMessage) -> Result<(), PeerError> {
284
+ Ok(())
285
+ }
286
+ async fn recv(&self) -> Result<(String, FederationMessage), PeerError> {
287
+ let mut g = self.inbound.lock().await;
288
+ if let Some(v) = g.pop() {
289
+ Ok(v)
290
+ } else {
291
+ Err(PeerError::Transport("closed".into()))
292
+ }
293
+ }
294
+ async fn close(&self) -> Result<(), PeerError> {
295
+ Ok(())
296
+ }
297
+ }
298
+
299
+ struct PassThroughGate;
300
+
301
+ #[async_trait]
302
+ impl SafetyGate for PassThroughGate {
303
+ async fn inspect(&self, _msg: &FederationMessage) -> Result<SafetyVerdict, PeerError> {
304
+ Ok(SafetyVerdict::Pass)
305
+ }
306
+ }
307
+
308
+ struct BlockEverythingGate;
309
+
310
+ #[async_trait]
311
+ impl SafetyGate for BlockEverythingGate {
312
+ async fn inspect(&self, _msg: &FederationMessage) -> Result<SafetyVerdict, PeerError> {
313
+ Ok(SafetyVerdict::Block("test rule".into()))
314
+ }
315
+ }
316
+
317
+ struct CountingDispatcher(tokio::sync::Mutex<usize>);
318
+
319
+ #[async_trait]
320
+ impl Dispatcher for CountingDispatcher {
321
+ async fn dispatch(&self, _sender: &str, _msg: FederationMessage) -> Result<(), PeerError> {
322
+ let mut g = self.0.lock().await;
323
+ *g += 1;
324
+ Ok(())
325
+ }
326
+ }
327
+
328
+ fn msg(id: &str) -> FederationMessage {
329
+ FederationMessage {
330
+ id: id.to_string(),
331
+ kind: "task".to_string(),
332
+ payload: serde_json::Value::Null,
333
+ metadata: Default::default(),
334
+ stream_id: None,
335
+ }
336
+ }
337
+
338
+ #[tokio::test]
339
+ async fn run_dispatches_inbound_messages_that_pass_the_gate() {
340
+ let transport = StubTransport {
341
+ inbound: tokio::sync::Mutex::new(vec![
342
+ ("peer-1".into(), msg("a")),
343
+ ("peer-1".into(), msg("b")),
344
+ ]),
345
+ };
346
+ let gate = PassThroughGate;
347
+ let counter = CountingDispatcher(tokio::sync::Mutex::new(0));
348
+ let dispatcher = CountingDispatcher(tokio::sync::Mutex::new(0));
349
+ let peer = Peer::new(transport, gate, dispatcher);
350
+ peer.run().await.unwrap();
351
+ assert_eq!(*peer.dispatcher.0.lock().await, 2);
352
+ // counter is unused; we hold it just to test the move semantics
353
+ let _ = counter;
354
+ }
355
+
356
+ #[tokio::test]
357
+ async fn run_quarantines_messages_that_the_gate_blocks() {
358
+ let transport = StubTransport {
359
+ inbound: tokio::sync::Mutex::new(vec![("peer-1".into(), msg("a"))]),
360
+ };
361
+ let gate = BlockEverythingGate;
362
+ let dispatcher = CountingDispatcher(tokio::sync::Mutex::new(0));
363
+ let peer = Peer::new(transport, gate, dispatcher);
364
+ peer.run().await.unwrap();
365
+ assert_eq!(*peer.dispatcher.0.lock().await, 0);
366
+ }
367
+
368
+ #[tokio::test]
369
+ async fn outbound_send_blocks_when_gate_says_block() {
370
+ let transport = StubTransport {
371
+ inbound: tokio::sync::Mutex::new(vec![]),
372
+ };
373
+ let gate = BlockEverythingGate;
374
+ let dispatcher = CountingDispatcher(tokio::sync::Mutex::new(0));
375
+ let peer = Peer::new(transport, gate, dispatcher);
376
+ let err = peer.send("peer-2", msg("c")).await.unwrap_err();
377
+ match err {
378
+ PeerError::Gate(reason) => assert_eq!(reason, "test rule"),
379
+ other => panic!("expected Gate error, got {other:?}"),
380
+ }
381
+ }
382
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-flow",
3
- "version": "3.7.0-alpha.37",
3
+ "version": "3.7.0-alpha.39",
4
4
  "description": "Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/cli",
3
- "version": "3.7.0-alpha.37",
3
+ "version": "3.7.0-alpha.39",
4
4
  "type": "module",
5
5
  "description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
6
6
  "main": "dist/src/index.js",
@@ -107,7 +107,7 @@
107
107
  "optionalDependencies": {
108
108
  "@claude-flow/aidefence": "^3.0.2",
109
109
  "@claude-flow/codex": "^3.0.0-alpha.8",
110
- "@claude-flow/embeddings": "^3.0.0-alpha.12",
110
+ "@claude-flow/embeddings": "^3.0.0-alpha.17",
111
111
  "@claude-flow/guidance": "^3.0.0-alpha.1",
112
112
  "@claude-flow/memory": "^3.0.0-alpha.14",
113
113
  "@claude-flow/plugin-gastown-bridge": "^0.1.3",
@@ -1 +0,0 @@
1
- {"sessionId":"fd9b0530-a395-4da0-9b21-a26731cef01f","pid":91923,"procStart":"Mon May 11 02:12:51 2026","acquiredAt":1778466450635}