Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.

Each entry lists the date and the crate versions that were released.

## 2026-05-22 — mqdb-agent 0.8.1, mqdb-cluster 0.3.6, mqdb-cli 0.7.7

### Fixed

- `http-api` feature flag was effectively a lie at every layer: declared in `mqdb-agent`'s `Cargo.toml` with `default = ["http-api"]`, but three pieces of `MqdbAgent` referenced `crate::http::*` unconditionally — the `http_config` field, the `with_http_config` builder, and the two call sites of `spawn_http_task` in `run` / `start`. `cargo check -p mqdb-agent --no-default-features` failed with five compile errors (`E0433` on the missing `crate::http`, `E0609` on the gated `identity_crypto` field, and an `E0282` inference failure cascading from those). Even after gating those, the flag was still meaningless at the CLI level because `mqdb-cli`, `mqdb-cluster`, and `mqdb-vault` all depended on `mqdb-agent` without `default-features = false` — so the HTTP server was always compiled in regardless of what `mqdb-cli --features` said. Fixed by: (1) gating the three `mqdb-agent` items behind `#[cfg(feature = "http-api")]`; (2) extracting `RateLimiter` out of `mqdb_agent::http::rate_limiter` to a top-level `mqdb_agent::rate_limiter` module so `mqdb-vault` no longer needs `http-api`; (3) flipping `mqdb-cli`, `mqdb-cluster`, and `mqdb-vault` to `default-features = false` on their `mqdb-agent` dep; (4) feature-gating the HTTP wiring in `mqdb-cluster` (`http_config` fields, `with_http_config`, `spawn_http_task`) and `mqdb-cli` (`build_http_config`, `build_identity_crypto`, both `--http-bind` call sites). `cargo build --bin mqdb --no-default-features` now actually drops the HTTP server: `jsonwebtoken` symbols go from present to zero, and `hyper`-related code drops ~60% (residual `hyper`/`argon2` come from `mqtt5`, not our HTTP server). `--http-bind` without `http-api` logs a warning and is otherwise ignored. Added a `cargo make clippy-no-default` task wired into the default `clippy`/`ci` chain so this regression cannot reach `main` silently again.

### Docs

- Corrected stale feature-flag names in user-facing docs. `README.md`, `docs/distributed-design.md`, and `docs/testing/01-setup.md` all referenced `--features agent-only` and a `native` feature flag that don't exist on the `mqdb` CLI or `mqdb-agent`. (A `native` feature does exist in `mqdb-core/Cargo.toml`, but it gates `tokio` for non-native targets and is unrelated to the agent/cluster split the README described.) The actual `mqdb` CLI features are `cluster` (default), `http-api` (default), `opentelemetry`, and `dev-insecure`; agent-only builds use `--no-default-features` (optionally adding `--features http-api`). Added a feature-flag reference table in `README.md` under "CLI Tool → Installation" documenting all four flags and their defaults — the `http-api` flag was previously undocumented anywhere outside its `Cargo.toml` declaration.

## 2026-05-19 — mqdb-cluster 0.3.5

### Removed
Expand Down
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 13 additions & 1 deletion Makefile.toml
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ args = ["install", "--path", "crates/mqdb-cli"]

[tasks.clippy]
description = "Lint all crates with pedantic warnings"
dependencies = ["clippy-native", "clippy-wasm"]
dependencies = ["clippy-native", "clippy-no-default", "clippy-wasm"]

[tasks.clippy-native]
description = "Lint native crate"
Expand All @@ -110,6 +110,18 @@ args = [
"-W", "clippy::pedantic",
]

[tasks.clippy-no-default]
description = "Lint workspace with no default features (catches feature-gating regressions)"
command = "cargo"
args = [
"clippy",
"--workspace",
"--no-default-features",
"--all-targets",
"--",
"-D", "warnings",
]

[tasks.clippy-wasm]
description = "Lint WASM crate"
command = "cargo"
Expand Down
26 changes: 19 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,7 @@ Any OTLP-compatible collector works: Jaeger, Grafana Tempo, Datadog, Honeycomb,

## Distributed Clustering (Native Edition)

> Clustering requires the `native` feature (commercial license). Agent-only builds (`--features agent-only`) do not include cluster code.
> Clustering requires the `cluster` feature on the `mqdb` CLI (enabled by default). To build without cluster code, use `--no-default-features` (and add `--features http-api` to keep the HTTP server).

MQDB supports distributed clustering with automatic failover and partition rebalancing. The cluster distributes data across 256 fixed partitions with a configurable replication factor (RF=2 by default). Raft consensus manages cluster topology and partition ownership. All inter-node communication flows over QUIC streams with mTLS mutual authentication.

Expand Down Expand Up @@ -740,13 +740,25 @@ The `mqdb` CLI provides command-line access to a running MQDB agent.
### Installation

```bash
# Agent-only (open-source edition)
cargo build --release --bin mqdb --features agent-only

# Full build with clustering (commercial edition, default)
# Full build with clustering and HTTP API (default)
cargo build --release --bin mqdb

# Agent-only with HTTP API (no cluster code)
cargo build --release --bin mqdb --no-default-features --features http-api

# Minimal agent (no cluster, no HTTP server)
cargo build --release --bin mqdb --no-default-features
```

**CLI feature flags** (defined in `crates/mqdb-cli/Cargo.toml`):

| Feature | Default | Effect |
|---------|---------|--------|
| `cluster` | yes | Enables `mqdb cluster *` commands and the distributed runtime. |
| `http-api` | yes | Enables the HTTP server used for OAuth, email/password auth, and the vault HTTP API. Without it, the HTTP server is not compiled in; `--http-bind` logs a warning and is ignored. |
| `opentelemetry` | no | Enables OTLP trace export (see [Observability](#observability)). |
| `dev-insecure` | no | Test-only shortcuts (anonymous mode, dev-login bypass). Never enable in production. |

### Environment Variables

Every CLI flag can be set via environment variable. This is the primary configuration method for Docker and ECS deployments. CLI flags take precedence over env vars when both are set.
Expand Down Expand Up @@ -994,11 +1006,11 @@ Via MQTT, include `"projection"` in the request payload:
## Testing

```bash
# Run all tests (default features = native)
# Run all tests (default features: cluster + http-api)
cargo test

# Run agent-only tests (no cluster tests)
cargo test --features agent-only
cargo test --no-default-features --features http-api

cargo test --test integration_test
```
Expand Down
2 changes: 1 addition & 1 deletion crates/mqdb-agent/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mqdb-agent"
version = "0.8.0"
version = "0.8.1"
edition.workspace = true
license = "Apache-2.0"
authors.workspace = true
Expand Down
2 changes: 1 addition & 1 deletion crates/mqdb-agent/src/agent/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use std::path::Path;
use tracing::{error, info_span, warn};

#[cfg(feature = "http-api")]
use crate::http::rate_limiter::RateLimiter;
use crate::rate_limiter::RateLimiter;

#[cfg(feature = "opentelemetry")]
use mqtt5::telemetry::propagation;
Expand Down
43 changes: 32 additions & 11 deletions crates/mqdb-agent/src/agent/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use tokio::sync::{broadcast, oneshot, watch};
use tracing::info;

#[cfg(feature = "http-api")]
use crate::http::rate_limiter::RateLimiter;
use crate::rate_limiter::RateLimiter;

pub use mqdb_core::protocol::{
AdminOperation, DbOperation, build_request, parse_admin_topic, parse_db_topic,
Expand All @@ -34,6 +34,7 @@ pub struct MqdbAgent {
pub(super) quic_cert_file: Option<PathBuf>,
pub(super) quic_key_file: Option<PathBuf>,
pub(super) ws_bind_address: Option<SocketAddr>,
#[cfg(feature = "http-api")]
pub(super) http_config: std::sync::Mutex<Option<crate::http::HttpServerConfig>>,
pub(super) ownership_config: Arc<mqdb_core::types::OwnershipConfig>,
pub(super) scope_config: Arc<mqdb_core::types::ScopeConfig>,
Expand Down Expand Up @@ -65,6 +66,7 @@ impl MqdbAgent {
quic_cert_file: None,
quic_key_file: None,
ws_bind_address: None,
#[cfg(feature = "http-api")]
http_config: std::sync::Mutex::new(None),
ownership_config: Arc::new(mqdb_core::types::OwnershipConfig::default()),
scope_config: Arc::new(mqdb_core::types::ScopeConfig::default()),
Expand Down Expand Up @@ -218,6 +220,7 @@ impl MqdbAgent {
Arc::clone(&self.vault_backend)
}

#[cfg(feature = "http-api")]
#[must_use]
#[allow(clippy::missing_panics_doc)]
pub fn with_http_config(mut self, config: crate::http::HttpServerConfig) -> Self {
Expand Down Expand Up @@ -264,11 +267,20 @@ impl MqdbAgent {
service_username.clone(),
service_password.clone(),
);
let http_task = self.spawn_http_task(
bind_addr,
service_username.as_ref(),
service_password.as_ref(),
);
let http_task: Option<tokio::task::JoinHandle<()>> = {
#[cfg(feature = "http-api")]
{
self.spawn_http_task(
bind_addr,
service_username.as_ref(),
service_password.as_ref(),
)
}
#[cfg(not(feature = "http-api"))]
{
None
}
};
let license_task = self.spawn_license_check_task();

broker.run().await?;
Expand Down Expand Up @@ -339,11 +351,20 @@ impl MqdbAgent {
service_username.clone(),
service_password.clone(),
);
let http_task = self.spawn_http_task(
bind_addr,
service_username.as_ref(),
service_password.as_ref(),
);
let http_task: Option<tokio::task::JoinHandle<()>> = {
#[cfg(feature = "http-api")]
{
self.spawn_http_task(
bind_addr,
service_username.as_ref(),
service_password.as_ref(),
)
}
#[cfg(not(feature = "http-api"))]
{
None
}
};
let license_task = self.spawn_license_check_task();

tokio::spawn(async move {
Expand Down
1 change: 1 addition & 0 deletions crates/mqdb-agent/src/agent/tasks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ impl MqdbAgent {
})
}

#[cfg(feature = "http-api")]
pub(super) fn spawn_http_task(
&self,
bind_addr: SocketAddr,
Expand Down
2 changes: 1 addition & 1 deletion crates/mqdb-agent/src/http/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use super::identity_crypto::IdentityCrypto;
use super::jwt_signer::{JwtSigningConfig, sign_jwt, verify_jwt_ignore_expiry};
use super::pkce::PkceCache;
use super::providers::{ProviderIdentity, ProviderRegistry};
use super::rate_limiter::RateLimiter;
use super::session_store::{JtiRevocationStore, NewSession, SessionStore};
use crate::rate_limiter::RateLimiter;
use crate::vault_backend::{DbAccess, VaultBackend, VaultError};
use base64::Engine;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
Expand Down
2 changes: 0 additions & 2 deletions crates/mqdb-agent/src/http/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@ pub mod identity_crypto;
mod jwt_signer;
mod pkce;
pub mod providers;
pub mod rate_limiter;
mod server;
mod session_store;

pub use identity_crypto::IdentityCrypto;
pub use jwt_signer::{JwtSigningAlgorithm, JwtSigningConfig};
pub use providers::google::GoogleProvider;
pub use providers::{Provider, ProviderConfig, ProviderRegistry};
pub use rate_limiter::RateLimiter;
pub use server::{HttpServer, HttpServerConfig};
pub use session_store::SessionStore;
2 changes: 1 addition & 1 deletion crates/mqdb-agent/src/http/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use super::identity_crypto::IdentityCrypto;
use super::jwt_signer::JwtSigningConfig;
use super::pkce::PkceCache;
use super::providers::ProviderRegistry;
use super::rate_limiter::RateLimiter;
use super::session_store::{JtiRevocationStore, SessionStore};
use crate::rate_limiter::RateLimiter;
use crate::vault_backend::{DbAccess, NoopVaultBackend, VaultBackend};
use http_body_util::BodyExt;
use http_body_util::Full;
Expand Down
1 change: 1 addition & 0 deletions crates/mqdb-agent/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub mod db_helpers;
pub mod dedup;
pub mod dispatcher;
pub mod outbox_processor;
pub mod rate_limiter;
pub mod runtime;
pub mod session;
pub mod subscription_registry;
Expand Down
6 changes: 3 additions & 3 deletions crates/mqdb-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mqdb-cli"
version = "0.7.6"
version = "0.7.7"
publish = false
edition.workspace = true
license = "AGPL-3.0-only"
Expand All @@ -14,7 +14,7 @@ path = "src/main.rs"

[dependencies]
mqdb-core = { path = "../mqdb-core", features = ["fjall-backend"] }
mqdb-agent = { path = "../mqdb-agent" }
mqdb-agent = { path = "../mqdb-agent", default-features = false }
mqdb-cluster = { path = "../mqdb-cluster", optional = true }

serde.workspace = true
Expand All @@ -39,7 +39,7 @@ mqdb-vault = { version = "0.1.0", path = "../mqdb-vault" }
[features]
default = ["cluster", "http-api"]
cluster = ["dep:mqdb-cluster", "dep:rustls"]
http-api = ["mqdb-agent/http-api"]
http-api = ["mqdb-agent/http-api", "mqdb-cluster?/http-api"]
dev-insecure = ["mqdb-agent/dev-insecure", "mqdb-cluster?/dev-insecure"]
opentelemetry = ["mqdb-agent/opentelemetry"]

Expand Down
42 changes: 29 additions & 13 deletions crates/mqdb-cli/src/commands/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// SPDX-License-Identifier: AGPL-3.0-only

use std::net::SocketAddr;
use std::path::{Path, PathBuf};
#[cfg(feature = "http-api")]
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;

use mqdb_agent::{Database, MqdbAgent};
Expand Down Expand Up @@ -164,21 +166,31 @@ pub(crate) async fn cmd_agent_start(
}

if let Some(http_bind) = args.oauth.http_bind {
let ownership_for_http = agent.ownership_config_arc();
let http_config = build_http_config(
http_bind,
&args.auth,
&args.oauth,
federated_content.as_deref(),
ownership_for_http,
&args.db_path,
)?;
if !http_config.cookie_secure {
#[cfg(feature = "http-api")]
{
let ownership_for_http = agent.ownership_config_arc();
let http_config = build_http_config(
http_bind,
&args.auth,
&args.oauth,
federated_content.as_deref(),
ownership_for_http,
&args.db_path,
)?;
if !http_config.cookie_secure {
tracing::warn!(
"session cookies will be sent without Secure flag — use --cookie-secure in production"
);
}
agent = agent.with_http_config(http_config);
}
#[cfg(not(feature = "http-api"))]
{
let _ = http_bind;
tracing::warn!(
"session cookies will be sent without Secure flag — use --cookie-secure in production"
"--http-bind ignored: build with --features http-api to enable the HTTP server"
);
}
agent = agent.with_http_config(http_config);
}

let agent = Arc::new(agent);
Expand Down Expand Up @@ -302,6 +314,7 @@ pub(crate) fn build_auth_setup_config(
})
}

#[cfg(feature = "http-api")]
pub(crate) fn build_http_config(
http_bind: SocketAddr,
auth: &AuthArgs,
Expand Down Expand Up @@ -410,6 +423,7 @@ pub(crate) fn build_http_config(
})
}

#[cfg(feature = "http-api")]
fn build_identity_crypto(
oauth: &OAuthArgs,
db_path: &Path,
Expand Down Expand Up @@ -499,6 +513,7 @@ fn verify_and_log_license(
}
}

#[cfg(feature = "http-api")]
fn serialize_key_material(salt: &[u8], wrapped_key: &[u8]) -> Vec<u8> {
let salt_len: u32 = salt.len().try_into().expect("salt length fits in u32");
let mut out = Vec::with_capacity(4 + salt.len() + wrapped_key.len());
Expand All @@ -508,6 +523,7 @@ fn serialize_key_material(salt: &[u8], wrapped_key: &[u8]) -> Vec<u8> {
out
}

#[cfg(feature = "http-api")]
fn deserialize_key_material(data: &[u8]) -> Result<(&[u8], &[u8]), String> {
if data.len() < 4 {
return Err("identity key file too short".into());
Expand Down
Loading
Loading