Commit 7fe5e88
Design 039: no_std typed migrations (lift std gate from migratable) (#164)
* docs: add design 039 — no_std typed migrations (lift std gate from migratable)
The migratable feature of aimdb-data-contracts is gated on std, but an
audit shows nothing in the migration engine needs it: the std gate is an
artifact of a locally-declared serde_json dependency with default
features. Design 039 describes the change set to make migration_chain!
work on no_std + alloc targets: adopt the workspace serde_json dep,
re-plumb the feature to alloc, add $crate::__private macro hygiene, and
extend the existing no_std CI lanes. Includes a follow-up proposal for a
Value-free version probe to cut peak heap on MCUs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V6pVffxk1yU9sspKjUumAc
* docs: add design 040 — WASM SingleLatest fresh-subscriber parity
The WASM adapter is the only runtime where a fresh SingleLatest
subscriber does not observe the buffer's current value (it waits for the
next push), diverging from Tokio (mark_changed) and Embassy (native
watch seen-ID) semantics. Design 040 pins the root cause to the reader
initialization in WasmBuffer::subscribe, specifies the one-line fix plus
the missing peek() override, adds host-run regression tests (the buffer
module is pure core+alloc, no browser needed), and proposes codifying
the fresh-subscriber rule in core docs plus a shared cross-adapter
buffer contract suite mirroring executor.rs test_support.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V6pVffxk1yU9sspKjUumAc
* docs: update design 039 — clarify PR structure and no_std compatibility details
* docs: design 039 review fixes — correct criterion 3, PR sequencing table, test ordering
Apply review findings on the updated 039: acceptance criterion 3 no
longer asserts serde_json is absent from the dependency graph (it stays
transitive via aimdb-data-contracts by design; verify the direct edge
and std feature via cargo tree -i instead); the round-trip test suite
moves into PR 1/W3 so the PR 2 proc-macro rewrite lands against
pre-existing green tests; generated helpers get collision-safe const-
block emission; the risk table's stale W4 pointer now references the
PR 3 probe; aimdb-derive's own 0.2.0 release-ordering requirement is
recorded; and the probe is promoted to an explicit PR 3 with a hand-off
sequencing table in the status header.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V6pVffxk1yU9sspKjUumAc
* feat(data-contracts): lift std gate from migratable (design 039 W1)
migratable now requires only alloc + serde_json instead of std:
serde_json switches to the workspace pin (default-features = false,
features = ["alloc"]), and std gains serde_json?/std as a weak
dependency feature so std builds keep serde_json's std impl without
forcing the dependency on. No source change — migratable.rs has no
std:: path today; the gate existed only in Cargo.toml.
Bumps aimdb-data-contracts to 0.2.0 (breaking: a consumer that enabled
only migratable and relied on it transitively activating std must now
enable std explicitly) and updates the two versioned consumer pins
(aimdb-wasm-adapter, aimdb-websocket-connector) so the workspace still
resolves.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* refactor(data-contracts): route migration_chain! through $crate::__private (design 039 W2)
The macro_rules! expansion referenced bare serde_json:: and alloc::
paths, which resolve in the *caller's* namespace — every consumer had
to depend on serde_json directly and declare extern crate alloc, and
a caller's own plain `serde_json = "1"` would silently re-enable std
via feature unification, breaking no_std targets.
Route the expansion through $crate::__private::{serde_json, alloc}
(re-exported from lib.rs, defining crate) instead. Pure hygiene —
expansion behavior is identical, and existing callers keep working
(their own serde_json dep becomes unnecessary rather than wrong).
Drops the now-redundant `extern crate alloc;` from the module-doc
example, which no longer needs it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* test(data-contracts): no_std CI lanes + migration round-trip suite (design 039 W3)
Extend the Makefile build/test/clippy lanes to cover
alloc,linkable,migratable (no dev-dependency serde_json to unify to
std there, unlike the plain `test` lane), and add thumbv7em-none-eabihf
target-check lines for aimdb-data-contracts and weather-mesh-common (the
latter goes fully green once W5 drops its own serde_json default-std dep).
Add tests/migration_roundtrip.rs: 1-, 2-, and 3-step chains (matching
the three hand-unrolled macro_rules arities), each upgrading from every
historical version and downgrading to every target version, plus the
error paths (MissingVersion, VersionTooNew, VersionTooOld, malformed
JSON). Lives under tests/ (a separate crate) rather than a #[cfg(test)]
module inside src/migratable.rs, since PR2's proc-macro rewrite will
emit absolute ::aimdb_data_contracts::... paths that only resolve from
outside the defining crate — this suite is the regression pin that
rewrite must reproduce unchanged.
Also fixes a latent no_std bug the new linkable+migratable lane
surfaces: Linkable::from_bytes/to_bytes used bare String/Vec, which
only resolved under std (via the automatic std prelude) — linkable was
never actually exercised under no_std before (serde_json/std was always
pulled in transitively, masking it). Qualify both as
alloc::string::String / alloc::vec::Vec.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* docs: note migratable's no_std support (design 039 W4)
lib.rs's SchemaType compatibility-rules doc now notes migration_chain!
only needs alloc and works identically on std and no_std targets.
README's capability-trait table gains a Runtimes column, same std/no_std
notation the connector table already uses further down the page.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(weather-mesh-common): drop consumer serde_json dep, fix no_std linkable (design 039 W5)
migratable = ["aimdb-data-contracts/migratable"] — the macro no longer
needs a consumer serde_json dep after W2's $crate::__private routing.
serde_json itself switches to the workspace pin (alloc, no default
std), which also fixes the linkable feature for no_std as a side
effect (it silently dragged serde_json/std along before).
That side effect exposed the same latent bug W3 found and fixed in
aimdb-data-contracts's own Linkable trait: GpsLocation's and Humidity's
hand-written Linkable impls called bare `e.to_string()`, which only
resolves under std's automatic prelude. temperature.rs and dew_point.rs
already fully-qualified this (alloc::string::ToString::to_string);
apply the same fix here so `cargo check -p weather-mesh-common
--no-default-features --features linkable --target thumbv7em-none-eabihf`
— PR1 acceptance criterion 4 — actually compiles.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* feat(derive): variable-arity migration_chain! proc-macro (design 039 W6)
Adds #[proc_macro] migration_chain in aimdb-derive: a custom
syn::parse::Parse impl reads the existing grammar (unchanged) into a
Vec<StepDef>, then generates, for arbitrary N:
- the same const-assertion validation the old macro_rules produced
per hand-unrolled arity (steps increment by 1, are sequential, start
at 1, end at Current::VERSION);
- one __up_k / __down_k free function per step (k = FROM_VERSION),
each applying its step and recursing into __up_{k+1}/__down_{k+1} —
O(N) generated code, not O(N²) (no arm re-inlines the full chain
walk); and
- the MigrationChain impl, with one O(1) match arm per historical
version calling the matching helper.
Everything (asserts, helpers, impl) lands in a single `const _: () =
{ ... };` block, the serde-derive collision-avoidance pattern, so
multiple chains in one module can't collide on helper names.
Generated code hardcodes absolute ::aimdb_data_contracts::... paths
(matching the RecordKey -> aimdb_core precedent already in this crate —
a proc-macro has no $crate, and aimdb-derive still doesn't depend on
aimdb-data-contracts, no cycle). Not yet wired up: aimdb-data-contracts
still exports the old macro_rules; the cutover is next.
Bumps aimdb-derive to 0.2.0 (new public proc-macro) and aimdb-core's
version requirement on it to match.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* refactor(data-contracts): cut over migration_chain! to the aimdb-derive proc-macro (design 039 W7)
migratable = ["alloc", "serde_json", "dep:aimdb-derive"] — build-time
only, no target/runtime/no_std impact, no cycle (aimdb-derive emits
token paths to this crate but doesn't depend on it, same as RecordKey
-> aimdb_core). lib.rs re-exports aimdb_derive::migration_chain, so the
aimdb_data_contracts::migration_chain! call path and grammar are
unchanged for every existing caller. Deletes the old macro_rules from
migratable.rs; MigrationStep/MigrationChain/MigrationError stay
hand-written and untouched.
The full W3 round-trip suite (1/2/3-step chains, error paths) passes
unchanged against the new proc-macro — the regression gate for this
rewrite.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* test(data-contracts): prove migration_chain! arity is unbounded (design 039 W8)
Extends tests/migration_roundtrip.rs with 4-step (Sprocket, 5 versions)
and 5-step (Doohickey, 6 versions) chains — the pre-existing 1/2/3-step
cases (Widget/Gadget/Gizmo) pass unchanged, pinning the rewrite's
behavior across the old 3-step ceiling.
Adds an on-target arity proof: cargo check --target thumbv7em-none-eabihf
can't compile tests/*.rs at all (no std test harness on a bare-metal
target), so a 4-step chain compiling there needs to live in src/ instead.
migratable.rs gains an internal, non-test-gated `arity_check` module (a
5-version/4-step fixture, #[allow(dead_code)], never executed — runtime
correctness is proven on host by the tests/ suite). Since it invokes
migration_chain! from inside this crate, and the macro hardcodes absolute
::aimdb_data_contracts::... paths, lib.rs adds `extern crate self as
aimdb_data_contracts;` (RFC 2126) so that self-reference resolves; this
reuses the existing no_std/linkable/migratable thumbv7em check lane from
W3, no new Makefile line needed.
Adds trybuild as a dev-dependency and tests/compile-fail/*.rs +
tests/compile_fail.rs: the four malformed-chain shapes (gap, wrong
start, non-sequential, wrong end) still fail at compile time with the
existing assertion messages. chain_non_sequential.rs is deliberately
built so the step *types* still chain correctly (only the declared
FROM_VERSION/TO_VERSION disagree) — otherwise it would also hit a
mismatched-types error ahead of the intended const-eval assertion,
polluting the pinned stderr snapshot.
cargo expand confirms the O(N) codegen shape: one __up_k/__down_k
helper per step (each a single call plus one recursive call), not the
O(N²) a naive per-arm unroll would produce.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* docs: note migration_chain! now lives in aimdb-derive, arity unbounded (design 039 W9)
migratable.rs's module doc now describes migration_chain! as a
proc-macro (defined in aimdb-derive, re-exported here) rather than the
deleted declarative macro, and notes the generated MigrationChain impl
is O(N) in code size for a chain of any length. Also de-links two
now-broken intra-doc references to `migration_chain!` (it's no longer
defined in this module, so `[`migration_chain!`]` doesn't resolve —
cargo doc confirmed clean after this fix).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* perf(derive): tree-free version probe for migrate_from_bytes (design 039 PR3)
migrate_from_bytes previously parsed the whole payload into a
serde_json::Value tree just to read one integer field, then converted
into the matched concrete type with from_value — peak allocation
O(payload tree).
Replace it with a two-pass, tree-free scan: a private
#[derive(serde::Deserialize)] probe struct reads only the version
field (pass 1), then the matched arm's serde_json::from_slice parses
the same bytes directly into the concrete Older/Current type (pass 2).
Peak allocation drops to O(concrete struct). serde_json::Error::is_data()
distinguishes "well-formed JSON, missing/wrong-type version field"
(MissingVersion) from "malformed JSON" (DeserializationFailed), matching
the old Value-based behavior exactly — confirmed by the full
migration_roundtrip suite (1-5 step chains + error paths) passing
unchanged, and by the compile-fail suite (const-eval happens before
migrate_from_bytes's body is relevant, so those snapshots are
untouched).
This rewrites the same migrate_from_bytes body PR2 relocated into the
proc-macro, so it lands after PR2 rather than against the old
macro_rules, per the design doc's sequencing note.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* bench(bench): record migrate_from_bytes allocation baseline (design 039 PR3)
New b0_alloc_migration bench, following the b0_alloc_tokio.rs shape
(own CountingAllocator<System>, warm up, reset, batch, snapshot,
write_reports). Fixture is a 2-version schema with a label: String
field — an all-primitive struct would parse allocation-free regardless
of version-scan strategy and wouldn't show anything, so this keeps the
bench representative of real contracts (e.g. weather-mesh-common's
TemperatureV1.unit).
Measured: 1 alloc/msg, 15 B/msg (the label field only) — confirms the
PR3 probe rewrite is genuinely O(concrete struct), not O(payload
tree); a regression back to Value-tree parsing would show up here as
more than one allocation per call. aimdb-data-contracts (alloc,
migratable) joins aimdb-bench's std-gated dependency set, same
treatment as criterion/serde_json — the no_std lane the stm32h5 B3 rig
depends on is untouched.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(migration-chain): handle error for source version too old in migration logic
* docs(changelog): record design 039 no_std migrations + VersionTooOld fix
Add a Design 039 aggregate subsection to the root changelog (matching the
Design 038 pattern) and a Fixed entry to aimdb-derive for the
migrate_from_bytes VersionTooOld classification correction.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: update changelogs and comments for clarity and consistency
* Claude/design doc 40 wasm parity impl (#165)
* fix(wasm): deliver current value to fresh SingleLatest subscribers (design 040)
WASM's SingleLatest buffer diverged from tokio/embassy: a subscriber
created after a value was published observed nothing until the next
push, so a browser dashboard subscribing to config after publish
rendered blank until the next change. WasmBuffer::subscribe() now starts
the reader as never-having-seen a value (last_seen_version: 0), so an
already-published value is delivered once — matching the documented
cross-runtime contract.
Alongside the fix:
- Add WasmBuffer DynBuffer::peek() (SingleLatest value / Mailbox slot /
None for SpmcRing), the buffer-native read AimX record.get relies on;
it previously returned None on WASM.
- Add host-run WASM buffer unit tests plus a shared aimdb-core
buffer::test_support conformance suite exercised by all three adapters
(tokio #[tokio::test], embassy/wasm block_on), so buffer parity is
enforced in CI rather than left to per-adapter discipline.
- Document the fresh-subscriber rule on Buffer::subscribe and
BufferCfg::SingleLatest (was folklore in a tokio comment).
- Wire the WASM host lib test lane into `make test` (it was never run in
CI) and add a wasm32-excluded `futures` dev-dep for the async suite.
- Bump aimdb-wasm-adapter 0.2.0 -> 0.3.0 with a changelog entry.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012JRUMZWyUJ7PHLPCmrbnY1
* format
---------
Co-authored-by: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 4b8aaa4 commit 7fe5e88
41 files changed
Lines changed: 2793 additions & 493 deletions
File tree
- aimdb-bench
- benches
- src
- aimdb-core
- src/buffer
- aimdb-data-contracts
- src
- tests
- compile-fail
- aimdb-derive
- src
- aimdb-embassy-adapter/src
- aimdb-tokio-adapter/src
- aimdb-wasm-adapter
- src
- aimdb-websocket-connector
- docs/design
- examples/weather-mesh-demo/weather-mesh-common
- src/contracts
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
80 | 80 | | |
81 | 81 | | |
82 | 82 | | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
83 | 106 | | |
84 | 107 | | |
85 | 108 | | |
| |||
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
66 | 66 | | |
67 | 67 | | |
68 | 68 | | |
| 69 | + | |
| 70 | + | |
69 | 71 | | |
70 | 72 | | |
71 | 73 | | |
| |||
109 | 111 | | |
110 | 112 | | |
111 | 113 | | |
| 114 | + | |
| 115 | + | |
112 | 116 | | |
113 | 117 | | |
114 | 118 | | |
| |||
135 | 139 | | |
136 | 140 | | |
137 | 141 | | |
| 142 | + | |
| 143 | + | |
138 | 144 | | |
139 | 145 | | |
140 | 146 | | |
| |||
196 | 202 | | |
197 | 203 | | |
198 | 204 | | |
| 205 | + | |
| 206 | + | |
199 | 207 | | |
200 | 208 | | |
201 | 209 | | |
| |||
306 | 314 | | |
307 | 315 | | |
308 | 316 | | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
309 | 321 | | |
310 | 322 | | |
311 | 323 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
130 | 130 | | |
131 | 131 | | |
132 | 132 | | |
133 | | - | |
134 | | - | |
135 | | - | |
136 | | - | |
137 | | - | |
138 | | - | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
139 | 139 | | |
140 | 140 | | |
141 | 141 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
36 | 36 | | |
37 | 37 | | |
38 | 38 | | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
39 | 43 | | |
40 | 44 | | |
41 | 45 | | |
| |||
49 | 53 | | |
50 | 54 | | |
51 | 55 | | |
| 56 | + | |
52 | 57 | | |
53 | 58 | | |
54 | 59 | | |
| |||
80 | 85 | | |
81 | 86 | | |
82 | 87 | | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
83 | 95 | | |
84 | 96 | | |
85 | 97 | | |
| |||
0 commit comments