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.
- package/.claude/worktrees/adr-120-impl/.claude-flow/data/pending-insights.jsonl +5 -0
- package/.claude/worktrees/adr-120-impl/package.json +1 -1
- package/.claude/worktrees/adr-120-impl/ruflo/package.json +1 -1
- package/.claude/worktrees/adr-120-impl/scripts/audit-fix-invariants.mjs +27 -0
- package/.claude/worktrees/adr-120-impl/v3/@claude-flow/cli/package.json +1 -1
- package/.claude/worktrees/adr-120-impl/v3/@claude-flow/plugin-agent-federation/src/transport/midstream-aware-loader.ts +38 -11
- package/.claude/worktrees/adr-120-impl/v3/crates/ruflo-federation-peer/.claude-flow/data/pending-insights.jsonl +3 -0
- package/.claude/worktrees/adr-120-impl/v3/crates/ruflo-federation-peer/Cargo.lock +3024 -0
- package/.claude/worktrees/adr-120-impl/v3/crates/ruflo-federation-peer/Cargo.toml +53 -0
- package/.claude/worktrees/adr-120-impl/v3/crates/ruflo-federation-peer/src/bin.rs +37 -0
- package/.claude/worktrees/adr-120-impl/v3/crates/ruflo-federation-peer/src/lib.rs +382 -0
- package/package.json +1 -1
- package/v3/@claude-flow/cli/package.json +2 -2
- package/.claude/scheduled_tasks.lock +0 -1
|
@@ -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.
|
|
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.
|
|
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.
|
|
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}
|