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
2 changes: 1 addition & 1 deletion Cargo.lock

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

32 changes: 20 additions & 12 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,13 @@ help:
build:
@printf "$(GREEN)Building AimDB (all valid combinations)...$(NC)\n"
@printf "$(YELLOW) → Building aimdb-data-contracts (std)$(NC)\n"
cargo build --package aimdb-data-contracts --features "std,simulatable,migratable,observable"
cargo build --package aimdb-data-contracts --features "std,simulatable,migratable,observable,linkable-json"
@printf "$(YELLOW) → Building aimdb-data-contracts (no_std)$(NC)\n"
cargo build --package aimdb-data-contracts --no-default-features --features alloc
@printf "$(YELLOW) → Building aimdb-data-contracts (no_std + linkable + migratable)$(NC)\n"
cargo build --package aimdb-data-contracts --no-default-features --features alloc,linkable,migratable
@printf "$(YELLOW) → Building aimdb-data-contracts (no_std + format-neutral linkable)$(NC)\n"
cargo build --package aimdb-data-contracts --no-default-features --features alloc,linkable
@printf "$(YELLOW) → Building aimdb-data-contracts (no_std + linkable-json + migratable)$(NC)\n"
cargo build --package aimdb-data-contracts --no-default-features --features alloc,linkable-json,migratable
@printf "$(YELLOW) → Building aimdb-core (no_std + alloc)$(NC)\n"
cargo build --package aimdb-core --no-default-features --features alloc
@printf "$(YELLOW) → Building aimdb-core (std platform)$(NC)\n"
Expand Down Expand Up @@ -112,9 +114,11 @@ build:
test:
@printf "$(GREEN)Running all tests (valid combinations)...$(NC)\n"
@printf "$(YELLOW) → Testing aimdb-data-contracts (std)$(NC)\n"
cargo test --package aimdb-data-contracts --features "std,simulatable,migratable,observable,linkable"
@printf "$(YELLOW) → Testing aimdb-data-contracts (no_std + alloc + linkable + migratable)$(NC)\n"
cargo test --package aimdb-data-contracts --no-default-features --features alloc,linkable,migratable
cargo test --package aimdb-data-contracts --features "std,simulatable,migratable,observable,linkable-json"
@printf "$(YELLOW) → Testing aimdb-data-contracts (no_std + alloc + format-neutral linkable)$(NC)\n"
cargo test --package aimdb-data-contracts --no-default-features --features alloc,linkable
@printf "$(YELLOW) → Testing aimdb-data-contracts (no_std + alloc + linkable-json + migratable)$(NC)\n"
cargo test --package aimdb-data-contracts --no-default-features --features alloc,linkable-json,migratable
@printf "$(YELLOW) → Testing aimdb-core (no_std + alloc)$(NC)\n"
cargo test --package aimdb-core --no-default-features --features alloc
@printf "$(YELLOW) → Testing aimdb-core (std platform)$(NC)\n"
Expand Down Expand Up @@ -205,11 +209,13 @@ clippy:
@printf "$(YELLOW) → Clippy on aimdb-derive$(NC)\n"
cargo clippy --package aimdb-derive --all-targets -- -D warnings
@printf "$(YELLOW) → Clippy on aimdb-data-contracts (std)$(NC)\n"
cargo clippy --package aimdb-data-contracts --features "std,simulatable,migratable,observable" --all-targets -- -D warnings
cargo clippy --package aimdb-data-contracts --features "std,simulatable,migratable,observable,linkable-json" --all-targets -- -D warnings
@printf "$(YELLOW) → Clippy on aimdb-data-contracts (no_std + alloc)$(NC)\n"
cargo clippy --package aimdb-data-contracts --no-default-features --features alloc -- -D warnings
@printf "$(YELLOW) → Clippy on aimdb-data-contracts (no_std + alloc + linkable + migratable)$(NC)\n"
cargo clippy --package aimdb-data-contracts --no-default-features --features alloc,linkable,migratable -- -D warnings
@printf "$(YELLOW) → Clippy on aimdb-data-contracts (no_std + alloc + format-neutral linkable)$(NC)\n"
cargo clippy --package aimdb-data-contracts --no-default-features --features alloc,linkable --all-targets -- -D warnings
@printf "$(YELLOW) → Clippy on aimdb-data-contracts (no_std + alloc + linkable-json + migratable)$(NC)\n"
cargo clippy --package aimdb-data-contracts --no-default-features --features alloc,linkable-json,migratable --all-targets -- -D warnings
@printf "$(YELLOW) → Clippy on aimdb-core (no_std + alloc)$(NC)\n"
cargo clippy --package aimdb-core --no-default-features --features alloc --all-targets -- -D warnings
@printf "$(YELLOW) → Clippy on aimdb-core (no_std + alloc + remote)$(NC)\n"
Expand Down Expand Up @@ -283,7 +289,7 @@ doc:
@mkdir -p target/doc-final/cloud
@mkdir -p target/doc-final/embedded
@printf "$(YELLOW) → Building cloud/edge documentation$(NC)\n"
cargo doc --package aimdb-data-contracts --features "std,simulatable,migratable,observable" --no-deps
cargo doc --package aimdb-data-contracts --features "std,simulatable,migratable,observable,linkable-json" --no-deps
cargo doc --package aimdb-core --features "std,tracing,observability" --no-deps
cargo doc --package aimdb-tokio-adapter --features "tokio-runtime,tracing,observability" --no-deps
cargo doc --package aimdb-sync --no-deps
Expand Down Expand Up @@ -330,8 +336,10 @@ test-embedded:
@printf "$(BLUE)Testing embedded/MCU cross-compilation compatibility...$(NC)\n"
@printf "$(YELLOW) → Checking aimdb-data-contracts (no_std + alloc) on thumbv7em-none-eabihf target$(NC)\n"
cargo check --package aimdb-data-contracts --target thumbv7em-none-eabihf --target-dir $(EMBEDDED_CHECK_TARGET_DIR) --no-default-features --features alloc
@printf "$(YELLOW) → Checking aimdb-data-contracts (no_std + alloc + linkable + migratable) on thumbv7em-none-eabihf target$(NC)\n"
cargo check --package aimdb-data-contracts --target thumbv7em-none-eabihf --target-dir $(EMBEDDED_CHECK_TARGET_DIR) --no-default-features --features alloc,linkable,migratable
@printf "$(YELLOW) → Checking aimdb-data-contracts (no_std + alloc + format-neutral linkable) on thumbv7em-none-eabihf target$(NC)\n"
cargo check --package aimdb-data-contracts --target thumbv7em-none-eabihf --target-dir $(EMBEDDED_CHECK_TARGET_DIR) --no-default-features --features alloc,linkable
@printf "$(YELLOW) → Checking aimdb-data-contracts (no_std + alloc + linkable-json + migratable) on thumbv7em-none-eabihf target$(NC)\n"
cargo check --package aimdb-data-contracts --target thumbv7em-none-eabihf --target-dir $(EMBEDDED_CHECK_TARGET_DIR) --no-default-features --features alloc,linkable-json,migratable
@printf "$(YELLOW) → Checking weather-mesh-common (no_std migratable, real TemperatureV1ToV2 chain, no direct serde_json dep) on thumbv7em-none-eabihf target$(NC)\n"
cargo check --package weather-mesh-common --target thumbv7em-none-eabihf --target-dir $(EMBEDDED_CHECK_TARGET_DIR) --no-default-features --features migratable
@printf "$(YELLOW) → Checking aimdb-core (no_std minimal) on thumbv7em-none-eabihf target$(NC)\n"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ Every capability is an opt-in trait on your schema type: implement it and **exac

| Contract | Implement when… | Verb it unlocks | Tier |
| --- | --- | --- | --- |
| [`Linkable`](https://aimdb.dev/blog/connectors-where-aimdb-meets-the-real-world) | the record is mirrored to/from an endpoint (MQTT, KNX, serial, UDS…) | `.linked_from(url)` / `.linked_to(url)` (`#[derive(Linkable)]` for JSON) | wire (prod) |
| [`Linkable`](https://aimdb.dev/blog/connectors-where-aimdb-meets-the-real-world) | the record is mirrored to/from an endpoint (MQTT, KNX, serial, UDS…) | `.linked_from(url)` / `.linked_to(url)` (`linkable-json` provides the JSON derive; codegen supports Postcard) | wire (prod) |
| [`Streamable`](https://aimdb.dev/blog/streamable-crossing-boundaries) | the record streams to browsers as schema-named JSON | ws-connector `.register::<T>()` | wire (prod) |
| [`Migratable`](https://aimdb.dev/blog/schema-migration-without-ceremony) | the schema evolved across versions | `migration_chain!` | wire (prod) |
| `Settable` | sync code outside AimDB sets the value | `SyncProducer::set_value(v)` | wire (prod) |
Expand Down
4 changes: 4 additions & 0 deletions aimdb-codegen/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Postcard codegen is now exercised end to end by `make codegen-drift`: a generated Postcard-only common crate roundtrips on the host, cross-compiles for `thumbv7em-none-eabihf`, and proves its normal dependency graph is JSON-free. A generated mixed JSON + Postcard crate is also compiled so codec feature/dependency drift fails in CI (#155).

### Fixed

- Generated `Cargo.toml` for manifests mixing `serialization = "json"` and `serialization = "postcard"` records omitted the `serde_json` dependency (postcard presence suppressed it), so the generated crate failed to compile under the `std` feature. `serde_json` is now emitted whenever any record uses JSON serialization, independent of postcard (#155).
Expand Down
23 changes: 23 additions & 0 deletions aimdb-codegen/src/rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2291,6 +2291,29 @@ description = "Reading value"
);
}

#[test]
fn linkable_impl_postcard_generated() {
let mut state = ArchitectureState::from_toml(MIXED_CODEC_TOML).unwrap();
state.records.retain(|r| r.name == "PostcardReading");
let rust = generate_schema_rs(&state);
assert!(
rust.contains("impl Linkable for PostcardReadingValue"),
"Missing Postcard Linkable impl:\n{rust}"
);
assert!(
rust.contains("postcard::to_allocvec(self)"),
"Missing postcard serializer:\n{rust}"
);
assert!(
rust.contains("postcard::from_bytes(data)"),
"Missing postcard deserializer:\n{rust}"
);
assert!(
!rust.contains("serde_json"),
"Postcard-only schema should not reference serde_json:\n{rust}"
);
}

#[test]
fn generate_lib_rs_output() {
let lib = generate_lib_rs();
Expand Down
3 changes: 2 additions & 1 deletion aimdb-data-contracts/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **`Simulatable` reshaped for compile-time-only simulation.** `simulate<R>(config: &SimulationConfig, ...)` → `simulate<R>(params: &Self::Params, ...)` with a new associated `type Params`. `SimulationConfig` (including its runtime `enabled` gate) and `SimulationParams` are removed; `SimProfile<P> { interval_ms, params }` and off-the-shelf `RandomWalkParams` replace them (`type Params = RandomWalkParams` is the migration path for existing scalar-walk impls). `simulatable` now also requires `aimdb-core` (for the new `SimulatableRegistrarExt`) and `rand` drops the `std_rng` feature (RNG is always caller-supplied).
- **`Observable` loses `ICON` and `format_log()`.** The trait is now numeric-projection-only: `type Signal`, `const SIGNAL` (defaults to `Self::NAME`), `const UNIT`, `fn signal()`. Presentation moved to `ObservableRegistrarExt::log(node_id)`.
- **`Settable` moves behind a new `settable` feature** (was compiled unconditionally) for tier symmetry with the other wire contracts.
- **`linkable` is now format-neutral.** It enables only the `Linkable` trait, registrar verbs, `alloc`, and `aimdb-core`; it no longer pulls `serde_json` or re-exports the JSON `#[derive(Linkable)]`. Existing derive users must enable the new `linkable-json` feature. Manual Postcard/custom implementations stay on base `linkable`, so their normal dependency graph is JSON-free (#155).

### Added

- `SimulatableRegistrarExt::simulate(profile, rng)` — installs a `.source()` loop emitting `T::simulate(...)` on a timer.
- `ObservableRegistrarExt::observe()` — feeds `T::signal()` into a core signal gauge (last/min/max/mean), surfaced on `record.list`/`record.get`/stage profiling; `ObservableRegistrarExt::log(node_id)` for the console-logging path (replaces the old `format_log`).
- `LinkableRegistrarExt::linked_from(url)` / `linked_to(url)` — one-line `.link_from()`/`.link_to()` wiring defaulted to `T::from_bytes`/`T::to_bytes`. `linkable` now also requires `aimdb-core`.
- `#[derive(Linkable)]` (via `aimdb-derive`) — emits a JSON `Linkable` impl (`serde_json::to_vec`/`from_slice`), `no_std + alloc` compatible.
- `linkable-json` — layers `serde_json` and the JSON `#[derive(Linkable)]` convenience over the format-neutral `linkable` contract; `no_std + alloc` compatible.

### Changed

Expand Down
11 changes: 7 additions & 4 deletions aimdb-data-contracts/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "aimdb-data-contracts"
version = "0.3.0"
version = "0.4.0"
edition.workspace = true
authors.workspace = true
license.workspace = true
Expand All @@ -14,9 +14,12 @@ categories = ["embedded", "no-std"]
default = ["std"]
alloc = ["serde/alloc"]
std = ["alloc", "serde/std", "serde_json?/std"]
# The ext trait needs core's registrar/connector-builder types (same wiring as
# `observable`); `dep:aimdb-derive` powers `#[derive(Linkable)]`.
linkable = ["alloc", "serde_json", "aimdb-core", "dep:aimdb-derive"]
# Format-neutral connector contract plus registrar verbs. Concrete codecs live
# at the application/codegen edge, so Postcard/custom builds stay JSON-free.
linkable = ["alloc", "aimdb-core"]
# JSON convenience layered over the format-neutral contract. The derive emits
# serde_json calls through this crate's private re-export.
linkable-json = ["linkable", "serde_json", "dep:aimdb-derive"]
# Dev-tier — never a default feature. The ext trait needs core's registrar,
# same coupling `observable` has. `rand` is the tracer CI uses to prove a
# production graph is sim-free (`make check-no-sim`).
Expand Down
16 changes: 11 additions & 5 deletions aimdb-data-contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,12 @@ extern crate self as aimdb_data_contracts;

/// Re-exports used by macro-generated code, kept out of the crate's own
/// namespace so a caller's own `serde_json`/`alloc` (if any) never shadows
/// or gets shadowed by them — see `migration_chain!`'s expansion.
/// or gets shadowed by them — see the `Linkable` and `migration_chain!`
/// expansions.
#[doc(hidden)]
pub mod __private {
pub extern crate alloc;
#[cfg(any(feature = "linkable", feature = "migratable"))]
#[cfg(any(feature = "linkable-json", feature = "migratable"))]
pub use serde_json;
}

Expand All @@ -74,13 +75,14 @@ mod linkable;
#[cfg(feature = "linkable")]
pub use linkable::LinkableRegistrarExt;

/// `#[derive(Linkable)]` — see [`Linkable`] and `aimdb_derive::Linkable`'s docs.
/// JSON `#[derive(Linkable)]` (feature `linkable-json`) — see [`Linkable`] and
/// `aimdb_derive::Linkable`'s docs.
///
/// Re-exported under the same name as the trait, following the trait+derive
/// pairing convention (`serde::Serialize`, `aimdb_derive::RecordKey`) — proc-macro
/// derive names live in a separate namespace from traits, so this is not a
/// conflict.
#[cfg(feature = "linkable")]
#[cfg(feature = "linkable-json")]
pub use aimdb_derive::Linkable;

#[cfg(feature = "observable")]
Expand Down Expand Up @@ -222,12 +224,16 @@ pub trait Observable: SchemaType {
/// Types that can be serialized/deserialized for connector links.
///
/// Implement this trait, then call
/// [`LinkableRegistrarExt::linked_from`](linkable::LinkableRegistrarExt::linked_from) /
/// [`LinkableRegistrarExt::linked_from`] /
/// [`linked_to`](linkable::LinkableRegistrarExt::linked_to) to wire `link_from`
/// / `link_to` in AimDB connectors (MQTT, KNX, etc.) with the codec defaulted to
/// `from_bytes`/`to_bytes`. This provides the wire format for transporting
/// schema types across network boundaries.
///
/// The `linkable` feature is format-neutral. Enable `linkable-json` for the
/// JSON `#[derive(Linkable)]` convenience, or implement these methods directly
/// for Postcard and other shared-contract formats.
///
/// # Example
///
/// Not compiled: the snippet needs `aimdb-core`'s builder types in scope.
Expand Down
2 changes: 1 addition & 1 deletion aimdb-data-contracts/tests/linkable_derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//! `::aimdb_data_contracts::...` paths (matching the `migration_chain!` /
//! `RecordKey` precedent), which only resolve from outside the defining crate.

#![cfg(feature = "linkable")]
#![cfg(feature = "linkable-json")]

use aimdb_data_contracts::{Linkable, SchemaType};
use serde::{Deserialize, Serialize};
Expand Down
2 changes: 1 addition & 1 deletion aimdb-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ pub fn migration_chain(input: TokenStream) -> TokenStream {
/// Emits `from_bytes`/`to_bytes` via `serde_json::from_slice`/`to_vec` — the
/// boilerplate every hand-written `impl Linkable` for a JSON-wire type repeats.
/// Requires `T: Serialize + DeserializeOwned` (a normal
/// compile error surfaces if it's missing) and the `linkable` feature of
/// compile error surfaces if it's missing) and the `linkable-json` feature of
/// `aimdb-data-contracts` (for the `Linkable` trait and its `__private::serde_json`
/// re-export). Binary formats (e.g. KNX DPT codecs) still implement `Linkable`
/// by hand — this derive is JSON-only.
Expand Down
2 changes: 1 addition & 1 deletion aimdb-sync/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ thiserror = "1.0"
# Optional: Settable::set (feature `data-contracts`) for the `set_value` family.
# aimdb-sync depends on the contracts crate, never the reverse — the contracts
# crate has no `sync` feature.
aimdb-data-contracts = { path = "../aimdb-data-contracts", version = "0.3.0", optional = true, default-features = false, features = [
aimdb-data-contracts = { path = "../aimdb-data-contracts", version = "0.4.0", optional = true, default-features = false, features = [
"settable",
] }

Expand Down
2 changes: 1 addition & 1 deletion aimdb-wasm-adapter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ aimdb-core = { version = "1.1.0", path = "../aimdb-core", default-features = fal
aimdb-ws-protocol = { version = "0.1.0", path = "../aimdb-ws-protocol" }

# Data contracts (alloc only — no std)
aimdb-data-contracts = { version = "0.3.0", path = "../aimdb-data-contracts", default-features = false, features = [
aimdb-data-contracts = { version = "0.4.0", path = "../aimdb-data-contracts", default-features = false, features = [
"alloc",
] }

Expand Down
2 changes: 1 addition & 1 deletion aimdb-websocket-connector/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ tracing = ["dep:tracing"]

[dependencies]
aimdb-core = { version = "1.1.0", path = "../aimdb-core", default-features = false }
aimdb-data-contracts = { version = "0.3.0", path = "../aimdb-data-contracts", default-features = false }
aimdb-data-contracts = { version = "0.4.0", path = "../aimdb-data-contracts", default-features = false }
aimdb-ws-protocol = { version = "0.1.0", path = "../aimdb-ws-protocol" }

# Async runtime
Expand Down
7 changes: 5 additions & 2 deletions docs/design/024-M11-codegen-common-crate.md
Original file line number Diff line number Diff line change
Expand Up @@ -713,8 +713,11 @@ impl for connector wiring.
## 11. Open Questions

1. **Should `postcard` be a first-class serialisation target?**
It's the natural choice for `no_std` / Embassy. The TOML field supports it,
but implementation can be deferred to Phase 2+.
*Resolved — yes.* `serialization = "postcard"` emits
`postcard::to_allocvec` / `postcard::from_bytes`. Issue #155 split the
format-neutral `linkable` feature from the `linkable-json` convenience and
added host roundtrip, mixed-codec compile, JSON-free dependency, and
`thumbv7em-none-eabihf` drift checks for the generated Postcard path.

2. **Should the codegen generate `Debug` formatting for key enums?**
Currently missing from the derive list. The hand-written crates include it.
Expand Down
Loading