Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
3983a2b
feat(core): add Reader::poll_recv (design 054 §4.2)
lxsaah Oct 3, 2026
6f91592
Merge pull request #277 from aimdb-dev/feat/054-s03-reader-poll-recv
lxsaah Oct 3, 2026
c9a248e
feat(core): add InboundDispatch (design 054 §4.1)
lxsaah Oct 3, 2026
a3f905f
Merge pull request #278 from aimdb-dev/feat/054-s04-inbound-dispatch
lxsaah Oct 3, 2026
7b58417
feat(core): add TopicWriter, with_topic_writer and with_topic_fn (des…
lxsaah Oct 3, 2026
c96d184
Merge pull request #279 from aimdb-dev/feat/054-s05-topic-writer
lxsaah Oct 3, 2026
a4b02f9
feat(core): add the lock-free outbound ready set (design 054 §4.2)
lxsaah Oct 3, 2026
d77ca43
refactor(core): harden the outbound ready set (design 054 §4.2)
claude Oct 3, 2026
a9bf646
docs(core): drop the design reference and correct the Tokio wake note…
lxsaah Oct 3, 2026
d251723
fix(core): bound skips in the ready set and document its priority rule
claude Oct 4, 2026
6c1bd9b
refactor(core): build the ready set's open mask one route at a time
claude Oct 4, 2026
6c3085b
Merge pull request #280 from aimdb-dev/feat/054-s06a-ready-set
lxsaah Oct 4, 2026
0b81fb7
feat(core): add OutboundRoutes (design 054 §4.2)
lxsaah Oct 4, 2026
601c0cf
feat(core): add OutboundPayload::as_slice and into_vec
lxsaah Oct 4, 2026
3345a41
fix(core): log OutboundRoutes skips with their route; share serialize…
claude Oct 4, 2026
53ea683
docs(core): drop the design reference from the OutboundRoutes Send check
lxsaah Oct 4, 2026
33aeb21
Merge pull request #281 from aimdb-dev/feat/054-s06b-outbound-routes
lxsaah Oct 4, 2026
8232f1d
bench: measure InboundDispatch and OutboundRoutes (design 054 §5)
lxsaah Oct 4, 2026
d38def7
Merge pull request #282 from aimdb-dev/feat/054-s07-bench
lxsaah Oct 4, 2026
34bc7a3
feat(mqtt): encode embedded packets into one bbqueue write ring (desi…
lxsaah Oct 4, 2026
a11813a
Merge main into feat/054-connector-boundary (#285)
lxsaah Oct 4, 2026
588f682
feat(mqtt): dispatch embedded inbound publishes from the session loop…
lxsaah Oct 4, 2026
6255896
feat(mqtt): pull embedded outbound publishes from OutboundRoutes (des…
lxsaah Oct 5, 2026
ab62c67
feat(mqtt): native backend dispatches inbound and pulls outbound (des…
lxsaah Oct 5, 2026
decce4a
Stage 12: round-trip allocation gate and Embassy outage semantics (de…
lxsaah Oct 5, 2026
bd68be1
fix(mqtt): mask the broker password in native error lines (#291)
lxsaah Oct 5, 2026
d4891f5
feat(core): pump_client pulls from OutboundRoutes in one task (design…
lxsaah Oct 5, 2026
f34ba37
feat(websocket): server dispatches writes and pulls broadcasts (desig…
lxsaah Oct 5, 2026
1a9ea7b
feat(knx)!: connection task dispatches and pulls; drop Channels (desi…
lxsaah Oct 5, 2026
e34e359
refactor(core)!: remove Source, pump_sink, Connector and TopicProvide…
lxsaah Oct 6, 2026
3614d9b
Merge main into feat/054-connector-boundary
lxsaah Oct 6, 2026
a1c02ba
Merge pull request #297 from aimdb-dev/feat/054-merge-main
lxsaah Oct 6, 2026
669bbfb
fix(demo): update binary path to debug build and simplify run command
lxsaah Oct 6, 2026
29b87ca
Add AimDB architecture diagram in SVG format
lxsaah Oct 6, 2026
de99ef0
docs: changelog entries for the 054 connector boundary
lxsaah Oct 6, 2026
4a587a6
docs: drop design references from the changelogs
lxsaah Oct 6, 2026
6833a99
Merge pull request #298 from aimdb-dev/docs/054-changelogs
lxsaah Oct 6, 2026
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
24 changes: 23 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added — Design 055: wildcard inbound links
### Added — wildcard inbound links

- **One inbound link can feed many topics into one record.**
`link_from("mqtt://sensors/{device}/temp")` matches every device;
Expand Down Expand Up @@ -60,6 +60,28 @@ is unchanged. ([aimdb-core](aimdb-core/CHANGELOG.md))
withheld by the broker instead of reconnecting the client forever.
([aimdb-mqtt-connector](aimdb-mqtt-connector/CHANGELOG.md))

### Changed (breaking) — zero-allocation connector boundary

Each connector's own task now drives both directions; core runs no task per
connector or per route, and a steady-state message crosses the boundary
without allocating on a scratch serializer.

- **Core** adds `OutboundRoutes` (one pull for every outbound link of a scheme,
with a lock-free ready set and per-route `RouteStats`) and `InboundDispatch`,
plus `Reader::poll_recv`. `pump_sink`, `pump_source`, `Source`, `Connector`,
`TopicProvider` and `collect_outbound_routes` are removed, and
`with_topic_provider` becomes `with_topic_writer` / `with_topic_fn`, which
write the topic into a bounded buffer. ([aimdb-core](aimdb-core/CHANGELOG.md))
- **MQTT** encodes embedded packets into one `bbqueue` write ring, sized with
`with_write_buffer`, and the native backend moves the topic instead of
cloning it. ([aimdb-mqtt-connector](aimdb-mqtt-connector/CHANGELOG.md))
- **KNX**: `KnxConnector::new` loses its `&'static Channels` argument.
([aimdb-knx-connector](aimdb-knx-connector/CHANGELOG.md))
- **WebSocket** runs one broadcast loop over `OutboundRoutes`.
([aimdb-websocket-connector](aimdb-websocket-connector/CHANGELOG.md))
- **Embassy adapter** drops `EmbassySink` and `EmbassySource`.
([aimdb-embassy-adapter](aimdb-embassy-adapter/CHANGELOG.md))

## [2.0.0] - 2026-09-18

### Added
Expand Down
18 changes: 16 additions & 2 deletions Cargo.lock

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

8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -249,10 +249,14 @@ test:
cargo test --package aimdb-mqtt-connector --no-default-features --features "_test-tokio-broker" --test tokio_broker
@printf "$(YELLOW) → Testing MQTT connector (both backends, one broker, one process)$(NC)\n"
cargo test --package aimdb-mqtt-connector --no-default-features --features "_test-backend-parity" --test backend_parity
@printf "$(YELLOW) → Testing MQTT connector (allocations per round trip, both backends)$(NC)\n"
cargo test --package aimdb-mqtt-connector --no-default-features --features "_test-backend-parity" --test alloc_round_trip
@printf "$(YELLOW) → Testing MQTT connector (mqtts:// against a pinned self-signed root)$(NC)\n"
cargo test --package aimdb-mqtt-connector --no-default-features --features "_test-tls-broker" --test tls_broker
@printf "$(YELLOW) → Testing MQTT connector (event-driven session: wake cadence, partial packets, QoS 1)$(NC)\n"
cargo test --package aimdb-mqtt-connector --no-default-features --features "_test-tokio-broker" --test session_loop
@printf "$(YELLOW) → Testing MQTT connector (embedded packet-size limits against a fake broker)$(NC)\n"
cargo test --package aimdb-mqtt-connector --no-default-features --features "_test-tokio-broker" --test write_ring_proofs
@printf "$(YELLOW) → Testing MQTT connector (the same criteria over mqtts://)$(NC)\n"
cargo test --package aimdb-mqtt-connector --no-default-features --features "_test-tls-broker" --test tls_session
@printf "$(YELLOW) → Testing MQTT connector (no_std unit tests: framing, deadlines, TLS duplex)$(NC)\n"
Expand Down Expand Up @@ -398,10 +402,14 @@ clippy:
cargo clippy --package aimdb-mqtt-connector --no-default-features --features "_test-tokio-broker" --test tokio_broker -- -D warnings
@printf "$(YELLOW) → Clippy on MQTT connector (backend parity)$(NC)\n"
cargo clippy --package aimdb-mqtt-connector --no-default-features --features "_test-backend-parity" --test backend_parity -- -D warnings
@printf "$(YELLOW) → Clippy on MQTT connector (allocations per round trip)$(NC)\n"
cargo clippy --package aimdb-mqtt-connector --no-default-features --features "_test-backend-parity" --test alloc_round_trip -- -D warnings
@printf "$(YELLOW) → Clippy on MQTT connector (mqtts:// host smoke)$(NC)\n"
cargo clippy --package aimdb-mqtt-connector --no-default-features --features "_test-tls-broker" --test tls_broker -- -D warnings
@printf "$(YELLOW) → Clippy on MQTT connector (event-driven session criteria)$(NC)\n"
cargo clippy --package aimdb-mqtt-connector --no-default-features --features "_test-tokio-broker" --test session_loop -- -D warnings
@printf "$(YELLOW) → Clippy on MQTT connector (embedded packet-size limits)$(NC)\n"
cargo clippy --package aimdb-mqtt-connector --no-default-features --features "_test-tokio-broker" --test write_ring_proofs -- -D warnings
@printf "$(YELLOW) → Clippy on MQTT connector (the same criteria over mqtts://)$(NC)\n"
cargo clippy --package aimdb-mqtt-connector --no-default-features --features "_test-tls-broker" --test tls_session -- -D warnings
@printf "$(YELLOW) → Clippy on MQTT connector (no_std unit tests)$(NC)\n"
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,13 @@ docker compose up

## Under the hood

<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="assets/aimdb-architecture-dark.svg">
<img src="assets/aimdb-architecture-light.svg" alt="AimDB architecture: inbound connectors push into typed records, outbound connectors pull from the same buffers, and the aimdb CLI and MCP server reach every record through one AimX server" width="900">
</picture>
</p>

- **The Rust type is the contract.** No IDL, no schema registry. CI cross-compiles the same contracts from Cortex-M to WASM. → [Data contracts](https://aimdb.dev/blog/data-contracts-deep-dive)
- **Buffers decide how data moves.** SPMC Ring for streams, SingleLatest for state, Mailbox for commands. Zero allocations per message, [measured](aimdb-bench/data/baselines). → [Buffers](https://aimdb.dev/docs/getting-started)
- **Optional persistence.** `.persist()` with a SQLite backend keeps history across restarts. → [`aimdb-persistence`](aimdb-persistence)
Expand Down
6 changes: 6 additions & 0 deletions aimdb-bench/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ harness = false
name = "b0_alloc_connector"
harness = false

# Outbound wake-up time per message, 1 to 256 routes.
# Informational: not in `bench-gate`.
[[bench]]
name = "b1_outbound_wakeup"
harness = false

[features]
default = ["std"]
# Gates `profiles`/`reports`/`harness` and their criterion/serde_json/
Expand Down
11 changes: 5 additions & 6 deletions aimdb-bench/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,9 @@ Plus two informational benches that exercise the full runner-driven pipeline.
`b1_b2_remote_json` (host). These compare issue #196's direct JSON bytes with
the compatibility `serde_json::Value` tree through the real typed record,
buffer, `Payload` and AimX envelope. Socket I/O and scheduling are excluded.
- **connector boundary** — `b0_alloc_connector` (host). Baseline for design
054: `Router::route`, `pump_source` with a minimal `Source`, and the
per-message `recv_into` + `Connector::publish` calls of `pump_sink`, on a
no-op connector.
- **connector boundary** — `b0_alloc_connector` (host). The connector
interfaces from design 054: `InboundDispatch::dispatch` and
`OutboundRoutes::next`, on a no-op connector.
- **Embassy** — `b0_alloc_embassy`, `b1_b2_embassy`
(host). These drive the real [`EmbassyBuffer`] backend via
`futures::executor::block_on` over embassy-sync's poll methods — no
Expand Down Expand Up @@ -127,11 +126,11 @@ The committed baseline lives in `data/baselines/b0_alloc_tokio.json`. When a cha
`b0_alloc_embassy` mirrors this against the Embassy buffer backend and writes `data/baselines/b0_alloc_embassy.json` — also **0 allocs/msg** across all three profiles, confirming the Embassy `poll_recv` path is allocation-free on the host. The on-target B3 bench (`examples/embassy-bench-stm32h5`) re-checks the same 0-alloc claim against the real embedded allocator.

`b0_alloc_linkable` warms up for 1,000 iterations, then measures 10,000 generated-shape postcard `Linkable::encode_into` calls into one stack buffer.
The required result is **0 allocation calls and 0 allocated bytes**. It isolates the codec seam: `SerializedReader` still returns a boxed future, dynamic topics may allocate and connector implementations may copy payload ownership after the core pump lends them the scratch slice.
The required result is **0 allocation calls and 0 allocated bytes**. It isolates the codec seam: connector implementations may still copy the payload after `OutboundRoutes` lends them the scratch slice.

`b0_alloc_remote_json` warms the production in-memory `record.get` and subscription-event paths, then compares 5000 tree/direct operations. Its gate is relative: direct JSON must reduce both allocation calls and allocated bytes. It does not require zero allocations because the owned JSON `Vec`, `Arc<[u8]>` payload and AimX envelope serialization still own storage.

`b0_alloc_connector` measures what the connector interfaces cost per message, with no transport. It asserts today's values exactly (0 for `route`, 2 for `pump_source`, 2–3 outbound), so a regression *or* an improvement fails it until `EXPECTED` in the bench and `data/baselines/b0_alloc_connector.json` are updated together. The inbound `pump_source` row is the difference of two runs, so pump setup cancels out. See design 054 for where each allocation comes from.
`b0_alloc_connector` measures what the connector interfaces cost per message, with no transport. It asserts its values exactly (0 everywhere except a new key, 1, and the owned serializer, 1), so a regression *or* an improvement fails it until `EXPECTED` in the bench and `data/baselines/b0_alloc_connector.json` are updated together. `make bench-gate` runs it. See design 054 for where each allocation comes from.

> **Embassy eager registration (design 039 F8/F9).** An Embassy `SpmcRing` reader registers its embassy `Subscriber` eagerly, at `subscribe()` time — matching Tokio's `broadcast` — so no separate priming step is needed before the first `push`.

Expand Down
Loading
Loading