Skip to content
Merged
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed (breaking)

- **Design 034 Phase 1 — mechanical debt cleanup (Issues #129, #132, [review doc](docs/design/034-technical-debt-review.md)).** `DbError` is unified on `alloc::string::String`: every variant has one shape on all targets, `thiserror` derives `Display`/`Error` unconditionally (now a mandatory no_std dependency of `aimdb-core`), and no_std builds produce the same error messages as std builds instead of `Error 0xNNNN` codes — on no_std the `_field: ()` placeholders become the real `String` fields. The dead `Database<A>` wrapper, the `TokioDatabase`/`EmbassyDatabase` aliases, and the deprecated `RecordRegistrar::link()` are removed. `ConsumerTrait::subscribe_any` is infallible (returns `Box<dyn AnyReader>`), `OutboundRoute` is a struct with named fields, and `aimdb-core`'s `std` feature no longer pulls a `tokio` dependency (tests cover it via dev-deps). Internally: all dual std/alloc import pairs and the duplicated `RuntimeContext` impl blocks are collapsed, and crate-private `log_*!` macros replace the 62 per-call-site `#[cfg(feature = "tracing")]` gates. The `aimdb-wasm-adapter` ring-lag error now carries a `buffer_name` like the other adapters. ([aimdb-core](aimdb-core/CHANGELOG.md), [aimdb-tokio-adapter](aimdb-tokio-adapter/CHANGELOG.md), [aimdb-embassy-adapter](aimdb-embassy-adapter/CHANGELOG.md), [aimdb-mqtt-connector](aimdb-mqtt-connector/CHANGELOG.md), [aimdb-knx-connector](aimdb-knx-connector/CHANGELOG.md))

- **CLI/MCP endpoint surface reworked to `--connect <url>` (Issue #123).** `aimdb`'s per-command `--socket <path>` is replaced by a global `--connect <endpoint>` (`AIMDB_CONNECT` env; bare paths still work). `aimdb-mcp` renames the tools' `socket_path` param to `endpoint`, the startup `--socket` to `--connect`, and `AIMDB_SOCKET` to `AIMDB_CONNECT`; the pool is keyed by endpoint URL. `aimdb-client`'s `AimxConnection::connect` takes a `&str` endpoint (was a path) and `ClientError::ConnectionFailed.socket` is renamed `endpoint`. Serial endpoints (`serial://…`) require building the CLI/MCP with `--features transport-serial`. ([aimdb-client](aimdb-client/CHANGELOG.md), [tools/aimdb-cli](tools/aimdb-cli/CHANGELOG.md), [tools/aimdb-mcp](tools/aimdb-mcp/CHANGELOG.md))

- **`AimDbBuilder::with_remote_access(config)` removed — remote-access servers are now registered like any other connector (Issue #39).** Replace `.with_remote_access(config)` with `.with_connector(aimdb_uds_connector::UdsServer::from_config(config))`. The AimX wire was reshaped to **v2** (NDJSON tagged frames mapping onto the engine's role-neutral message set) and is **not** backward-compatible with the legacy AimX v1 framing; the bundled `aimdb-client` / CLI / MCP speak v2. The UDS transport types moved out of `aimdb-core` into `aimdb-uds-connector`. ([aimdb-core](aimdb-core/CHANGELOG.md), [aimdb-uds-connector](aimdb-uds-connector/CHANGELOG.md), [aimdb-client](aimdb-client/CHANGELOG.md))
Expand Down
16 changes: 16 additions & 0 deletions aimdb-core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Internal refactors

- **Phase 1 mechanical cleanup (Issue #132, [design doc](../docs/design/034-technical-debt-review.md)).** No behavior change:
- All dual `#[cfg(feature = "std")] use std::… / #[cfg(not(…))] use alloc::…` import pairs replaced by single unconditional `use alloc::…` imports; redundant per-module `extern crate alloc;` declarations dropped (the crate root has one). The duplicated std/no_std `RuntimeContext` impl blocks in `context.rs` (character-identical except for the `Arc` path) are merged into one.
- New crate-private `log_debug!`/`log_info!`/`log_warn!`/`log_error!` macros (`src/log.rs`) forward to `tracing` when the feature is on and expand to an argument-borrowing no-op otherwise — deleting all 62 per-call-site `#[cfg(feature = "tracing")]` gates. `defmt` is deliberately not folded in (most sites use `{:?}` with non-`defmt::Format` types); router.rs keeps its paired explicit defmt gates.
- `build()` resolves each topo-order key with one O(1) `by_key.get_key_value(&str)` lookup (via `StringKey: Borrow<str>`) instead of an O(n) `keys().find()` scan plus a redundant second resolve.

- **`RecordMetadataTracker` deleted; `TypedRecord` keeps only a bare `writable` flag (Issue #120).** The per-record tracker (an `Arc<Mutex<Option<timestamp>>>` + `Arc<AtomicBool>` + `SystemTime::now()` on every `produce()` via `RecordWriter::push`) is gone; the surviving `writable` bit is now a single `portable_atomic::AtomicBool` field on `TypedRecord`, and `collect_metadata` reads the type name + `writable` directly with no shared state. New `pub(crate)` `DbError::{runtime_error, permission_denied, record_key_not_found}` constructors let the JSON/remote paths write one error expression across `std` (message carried) and `no_std` (unit placeholder) — replacing the inline `#[cfg]` splits at each call site. The `graph` types' `serde` derives (`RecordOrigin` / `GraphNode` / `GraphEdge` / `EdgeType`) move from the `std` gate to the `serde` feature (always on with `alloc`), so `RecordMetadata` can serialize on `no_std`.

- **AimX server/client ported onto the shared session engine; the hand-rolled loops deleted (Issue #39, [design doc](../docs/design/remote-access-via-connectors.md)).** Building on the spawn-free work below, `remote/handler.rs` (the per-connection `select!` loop) and `remote/supervisor.rs` (the accept loop) are **removed** — their behavior is now `run_session` + `serve` in `session`, driven by the AimX-v2 `AimxDispatch`/`AimxCodec`. `remote/stream.rs`'s `stream_record_updates` survives and is reused by `AimxSession::subscribe`. The UDS transport (socket bind/connect, NDJSON framing) relocated out of core into the new `aimdb-uds-connector` crate; core keeps only the protocol (codec + dispatch) and the generic session connectors. The query handler type-erasure moved to `remote/query.rs` (`QueryHandlerFn`/`QueryHandlerParams`). New dependencies: `async-channel` (runtime-neutral mpsc), `futures-channel` (oneshot), `futures-util`'s `async-await-macro` (`select_biased!`), and `serde_json`'s `raw_value` feature — all `no_std + alloc`-compatible, none entering the no_std contracts build.
Expand All @@ -36,6 +41,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed (breaking)

- **`DbError` unified on `alloc::string::String` — one shape per variant on every target (Issue #129, [design doc §3.1.1](../docs/design/034-technical-debt-review.md)).** The std/no_std dual-field design (`key: String` under `std`, `_key: ()` otherwise) is gone: every context field is a `String` unconditionally (the crate already requires `alloc` everywhere), and `Display`/`Error` derive from `thiserror` on every target (thiserror 2.x is no_std-capable, now a mandatory `default-features = false` dependency; the `remote-access` feature no longer lists it). Consequences:
- **no_std only:** field renames (`_endpoint`/`_reason`/`_buffer_name`/`_key`/… `: ()` → the real `String` fields); `Display` now prints the same full messages as std builds instead of the `Error 0xNNNN` numeric-code table (which is deleted); `DbError` now implements `core::error::Error`. `error_code()` / `error_category()` are unchanged.
- **std:** no change to variant shapes or messages.
- The helper constructors `DbError::runtime_error` / `permission_denied` / `record_key_not_found` are now **public**, un-gated from `remote-access`, single-bodied (the message is carried on every target), and joined by a new `DbError::missing_configuration`. Every dual `#[cfg]` construction branch in core, the adapters, and the MQTT/KNX connectors is collapsed.
- **`ConsumerTrait::subscribe_any` is infallible (Issue #132).** The future now resolves to `Box<dyn AnyReader>` instead of `DbResult<Box<dyn AnyReader>>` — subscription has been infallible since M14 (pre-resolved buffer handle); the `Result` was kept only for caller compatibility. Implementors drop the `Ok(...)` wrap; callers drop the dead `Err` arm.
- **`OutboundRoute` is a struct (Issue #132).** Was a 5-tuple type alias destructured positionally; now named fields: `topic`, `consumer`, `serializer`, `config`, `topic_provider`.
- **The `std` feature no longer enables a `tokio` dependency (Issue #132).** Since the session-engine refactor (030/033) the only `tokio::` references in `aimdb-core/src` are in `#[cfg(test)]` modules and doc comments — covered by the dev-dependency. Core no longer pulls tokio (`net`, `io-util`, `sync`, `time`) into every std build; depend on tokio directly if you relied on the transitive dependency.

- **`RecordMetadata` drops `created_at` / `last_update`; `AimxConfig.socket_path` is now `String` (Issue #120).** Part of de-std'ing the remote data model for the `no_std` AimX server. Core no longer keeps per-record timestamp state — the `std::sync::Mutex`-+-`SystemTime`-backed `RecordMetadataTracker` is deleted — so `RecordMetadata` loses the `created_at` and `last_update` fields, `RecordMetadata::new` loses its `created_at` parameter, and `with_last_update` / `with_last_update_opt` are removed. AimX `record.list` (and downstream MCP / CLI output derived from it) therefore no longer carry those two fields; every other metadata field is unchanged. `AimxConfig.socket_path` becomes `String` (was `PathBuf`) and `AimxConfig::socket_path(impl Into<String>)` replaces `impl Into<PathBuf>` — `&str` / `String` callers are unaffected, `&Path` / `PathBuf` callers must convert (the UDS transport in `aimdb-uds-connector` does the `Path`-based bind). `SecurityPolicy`'s writable set and the `remote` module's collections move from `std::collections` to `hashbrown`.

- **`AimDbBuilder::with_remote_access(config)` removed (Issue #39).** Register a session connector instead: `.with_connector(aimdb_uds_connector::UdsServer::from_config(config))`. The connector binds its transport at `build` time (bind errors surface synchronously, as before), applies the security policy's writable-record marking, and drives the shared `serve` engine. The builder's private `remote_config` field is gone; the per-record `TypedRecord::with_remote_access()` is unrelated and unchanged. The reshaped **AimX-v2** wire is not backward-compatible with the legacy v1 framing.
Expand Down Expand Up @@ -82,6 +95,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Removed (breaking)

- **`Database<A>` wrapper removed (Issue #132, design doc §3.8).** The thin façade over `AimDb` (and its `pub use database::Database` re-export) had no users beyond the `TokioDatabase`/`EmbassyDatabase` adapter aliases, themselves used nowhere — both aliases are removed from the adapters in the same change. Use `AimDb<R>` via `AimDbBuilder` directly.
- **Deprecated `RecordRegistrar::link()` removed (Issue #132).** Deprecated since 0.2.0; use `.link_to()` / `.link_from()`.

- **`TypedRecord::produce` removed (M15, Design 031).** The M14 step (above) made it sync; M15 removes it entirely. All writes now go through `WriteHandle::push` via `TypedRecord::writer_handle()`. `AimDb::produce` and AimX `set_from_json` route through it; as a side effect `set_from_json` now marks record metadata as updated (previously skipped on that path). `WriteHandle` / `RecordWriter` no longer carry the snapshot mutex.
- **`with_read_only_serialization()` removed (M16, Design 032).** A `Serialize`-only record can no longer be exposed read-only over remote access. Use `with_remote_access()`, which additionally requires `DeserializeOwned`. No in-tree callers existed.

Expand Down
28 changes: 10 additions & 18 deletions aimdb-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ std = [
"serde",
"anyhow",
"remote-access",
"tokio",
"aimdb-executor/std",
# AimX remote access now rides the shared session engine: `with_remote_access`
# builds the `session::aimx` server, so std pulls in the connector-session module.
Expand All @@ -42,11 +41,11 @@ json-serialize = ["alloc", "serde_json"]
# AimX remote-access data model (`crate::remote`): record metadata +
# introspection (`RecordMetadata`, `AimDb::list_records`), the JSON
# read/write/subscribe API (`try_latest_as_json` / `set_record_from_json` /
# `JsonBufferReader`), and the wire protocol/config/security/error types. Builds on
# the `json-serialize` value codec and adds `thiserror` (no_std-capable; see below)
# for `RemoteError`. The AimX *server* dispatch (`session::aimx`) additionally
# needs `connector-session`. All compiles on `no_std + alloc`.
remote-access = ["json-serialize", "thiserror"]
# `JsonBufferReader`), and the wire protocol/config/security/error types. Builds
# on the `json-serialize` value codec. The AimX *server* dispatch
# (`session::aimx`) additionally needs `connector-session`. All compiles on
# `no_std + alloc`.
remote-access = ["json-serialize"]

# The connector-session substrate (`crate::session`): the dyn-safe trait set
# (Connection/Listener/Dialer, Dispatch/EnvelopeCodec, Source + shared types) and
Expand Down Expand Up @@ -117,23 +116,15 @@ async-channel = { version = "2", default-features = false }
# Serialization (optional)
serde = { workspace = true, optional = true }

# Error handling
thiserror = { version = "2.0.16", default-features = false, optional = true }
# Error handling (thiserror 2.x is no_std-capable; DbError derives it on every target)
thiserror = { version = "2.0.16", default-features = false }
anyhow = { workspace = true, optional = true }
# `raw_value` lets the connector-session `EnvelopeCodec` splice an already-
# serialized record-value `Payload` into a JSON envelope without re-escaping
# (037 Decision 1). Only compiled when `serde_json` is pulled in (std /
# json-serialize); the no_std `connector-session` contracts build never sees it.
serde_json = { workspace = true, optional = true, features = ["raw_value"] }

# Async runtime - only for std environments with remote access
tokio = { workspace = true, features = [
"net",
"io-util",
"sync",
"time",
], optional = true }

# Atomic operations for all platforms
portable-atomic = { version = "1.9", default-features = false }

Expand All @@ -155,6 +146,7 @@ hashbrown = { version = "0.15", default-features = false, features = [
[dev-dependencies]
# For no_std testing
heapless = "0.9.1"
# For async testing
tokio = { workspace = true, features = ["macros", "rt", "time"] }
# For async testing (`sync` covers tokio::sync::mpsc in tests/session_engine.rs,
# which previously leaked in from the optional [dependencies] entry)
tokio = { workspace = true, features = ["macros", "rt", "time", "sync"] }
futures = { version = "0.3", default-features = false, features = ["alloc"] }
3 changes: 0 additions & 3 deletions aimdb-core/src/buffer/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@

use core::fmt;

#[cfg(not(feature = "std"))]
extern crate alloc;

/// Buffer configuration for a record type
///
/// Selects buffering strategy: SPMC Ring (backlog), SingleLatest (state sync), or Mailbox (commands).
Expand Down
3 changes: 0 additions & 3 deletions aimdb-core/src/buffer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,6 @@
//! .tap(|em, cfg| async { ... });
//! ```

#[cfg(not(feature = "std"))]
extern crate alloc;

// Module structure
mod cfg;
#[cfg(feature = "metrics")]
Expand Down
11 changes: 1 addition & 10 deletions aimdb-core/src/buffer/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,8 @@
use core::future::Future;
use core::pin::Pin;

#[cfg(not(feature = "std"))]
extern crate alloc;

#[cfg(not(feature = "std"))]
use alloc::boxed::Box;

#[cfg(feature = "std")]
use std::boxed::Box;

use super::BufferCfg;
use crate::DbError;

Expand Down Expand Up @@ -265,6 +258,7 @@ pub trait BufferMetrics {
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;

// Mock implementation for testing trait bounds
struct MockBuffer<T: Clone + Send + Sync> {
Expand Down Expand Up @@ -321,10 +315,7 @@ mod tests {
Box::pin(async {
// Return closed for testing
Err(DbError::BufferClosed {
#[cfg(feature = "std")]
buffer_name: "mock".to_string(),
#[cfg(not(feature = "std"))]
_buffer_name: (),
})
})
}
Expand Down
7 changes: 0 additions & 7 deletions aimdb-core/src/buffer/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,8 @@
//! Pre-binds the buffer so `Producer<T>` can push values without holding an
//! `Arc<AimDb<R>>` or running a `HashMap` lookup per call.

#[cfg(not(feature = "std"))]
extern crate alloc;

#[cfg(not(feature = "std"))]
use alloc::sync::Arc;

#[cfg(feature = "std")]
use std::sync::Arc;

use super::traits::{DynBuffer, WriteHandle};

pub(crate) struct RecordWriter<T: Clone + Send + 'static> {
Expand Down
Loading