Skip to content

feat/advanced-weather-mesh#173

Closed
lxsaah wants to merge 35 commits into
mainfrom
claude/new-vertical-hosting-plan-d52px3
Closed

feat/advanced-weather-mesh#173
lxsaah wants to merge 35 commits into
mainfrom
claude/new-vertical-hosting-plan-d52px3

Conversation

@lxsaah

@lxsaah lxsaah commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

No description provided.

lxsaah and others added 30 commits July 6, 2026 04:39
…ate() verb (design 041 §3.1)

Reshape Simulatable to the one-verb-per-contract model and make simulation a
compile-time fact, not a runtime flag:

- Trait: add `type Params`; `simulate(params, previous, rng, timestamp_ms)`.
  Delete `SimulationConfig` (incl. `enabled`) and `SimulationParams`; add
  `SimProfile<P>` + off-the-shelf `RandomWalkParams`.
- New `SimulatableRegistrarExt::simulate(profile, rng)` installs a source loop
  over `ctx.time()` (runtime-neutral, single-writer enforced by build()).
- Cargo: `simulatable = ["rand", "aimdb-core"]`; `rand` drops `std_rng`
  (caller-supplied RNG; SmallRng needs no feature).

D1 callers:
- weather-mesh-common temp/humidity/location impls → `type Params = RandomWalkParams`.
- weather-station-beta (std) + gamma (no_std): `sim` feature gates
  `.simulate()`; default (prod/flash) build reads a hardware source and is
  rand-free — the canonical §3.1.4 `#[cfg]` sim-to-real pattern.

CI: `make check-no-sim` proves each station's production graph carries no
`rand` (the tracer) and that `simulatable` is never a default feature; wired
into the CI makefile-build job.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Removed `ICON` and `format_log` from the `Observable` trait, simplifying the interface.
- Introduced `SignalGaugeHandle` for managing signal statistics, allowing for last/min/max/mean tracking.
- Added `SignalStats` struct to encapsulate signal statistics with atomic operations for thread safety.
- Updated `RecordProfilingMetrics` to include signal gauges and their statistics.
- Enhanced `RecordMetadata` to optionally include signal statistics when the observability feature is enabled.
- Modified `ObservableRegistrarExt` to support signal observation and logging.
- Updated various schemas (e.g., `Temperature`, `Humidity`, `DewPoint`, `GpsLocation`) to align with the new observability model.
- Adjusted example applications to utilize the new signal gauge functionality for metrics and logging.
…ign 041 §3.3)

Implementing `Linkable` today changed nothing — every example hand-wired
`with_deserializer`/`with_serializer` closures. Fix:

- `LinkableRegistrarExt::linked_from`/`linked_to` install `.link_from()`/
  `.link_to()` with the codec defaulted to `T::from_bytes`/`T::to_bytes`; the
  raw builders remain the escape hatch for per-link options (QoS, topic
  providers/resolvers).
- `linkable` feature now depends on `aimdb-core` (same wiring as `observable`)
  for the registrar/connector-builder types the ext needs.
- `#[derive(Linkable)]` in `aimdb-derive` emits `serde_json::to_vec`/
  `from_slice` — no_std + alloc compatible (serde_json here is alloc-only, no
  `std` required), removing hand-written JSON boilerplate.

D1 caller: tokio-mqtt-connector-demo's TempOutdoor/TempServerRoom (outbound)
and CommandKey::TempOutdoor (inbound) now use `#[derive(Linkable)]` +
`.linked_to`/`.linked_from`; TempIndoor keeps the raw builder since it needs
QoS/retain options the ext doesn't cover. mqtt-connector-demo-common gains an
opt-in `data-contracts` feature (alloc-only) so the embassy demo's hand-rolled
no_std JSON path stays untouched — it doesn't enable the new feature.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`Settable` was built for the sync bridge, but `aimdb-sync` never referenced
it — `SyncProducer::set(value: T)` takes a fully constructed record, so every
outside-the-thread caller hand-assembled the struct.

- `Settable` moves behind a `settable` feature in `aimdb-data-contracts` for
  tier symmetry with the other wire contracts (pure trait, no dependencies of
  its own). weather-mesh-common enables it unconditionally (used by all three
  contracts); codegen's `generate_cargo_toml`/import emission gained the same
  `has_settable` detection already used for `has_observable`.
- `aimdb-sync` gains a `data-contracts` feature -> optional
  `aimdb-data-contracts/settable` dep (dependency direction stays
  contracts -> sync, no `sync` feature added to the contracts crate).
  `SyncProducer<T: Settable>::set_value`/`try_set_value`/`set_value_at`
  construct via `T::set(value, timestamp)` and send; `set_value`/
  `try_set_value` stamp the caller's `SystemTime`, `set_value_at` takes an
  explicit timestamp for replay/testing.
- Doctest + integration tests (aimdb-sync/tests/settable_integration.rs)
  exercise `set_value`/`try_set_value`/`set_value_at` end-to-end
  (construct -> produce -> consume).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…n 041 §6-7)

- README: the 4-trait capability table becomes the §2 verb/tier table
  (Contract / Implement when / Verb / Tier), documents the `Simulatable`
  dev-tier "never ships in prod" call-out with the `#[cfg]` sim-to-real
  pattern, and adds the `Settable` row.
- CHANGELOG entries for all four touched crates (aimdb-data-contracts,
  aimdb-core, aimdb-derive, aimdb-sync) describing the trait reshapes, new
  registrar ext traits, the signal-gauge surface, and the derive macro.
- Version bumps: aimdb-data-contracts 0.2.0 -> 0.3.0 (breaking: Simulatable/
  Observable reshaped, Settable feature-gated), aimdb-core 1.1.0 -> 1.2.0
  (additive: signal_gauge surface), aimdb-derive 0.2.0 -> 0.3.0 (additive:
  #[derive(Linkable)]), aimdb-sync 0.5.0 -> 0.6.0 (additive: set_value
  family). Dependents pinning an exact version (aimdb-sync, aimdb-wasm-
  adapter, aimdb-websocket-connector -> data-contracts; aimdb-core,
  aimdb-data-contracts -> aimdb-derive) updated to match; aimdb-core's own
  0.x-style dependents needed no change (^1.1.0 already permits 1.2.0).

Verified: `make check` and `make build` green in aimdb; `make check`/`make
all` green in aimdb-pro against the bumped versions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(review): SignalStatsInfo serde symmetry, live hub gauge, gamma fmt

Review follow-ups on the design 041 implementation:

- SignalStatsInfo.unit: add #[serde(default)] to match its
  skip_serializing_if. Observable::UNIT defaults to "", so a gauge
  without a unit serialized record.list/record.get JSON that
  aimdb-client/aimdb-mcp (built with observability) could not
  deserialize back (missing field `unit`). Regression test added.
- weather-hub: enable aimdb-tokio-adapter/observability so .observe()
  gets a live gauge instead of the inert handle — the demo now actually
  surfaces signal stats on record.list/record.get as its comments and
  design 041 §6 claim.
- weather-station-gamma: rustfmt the non-sim read_temperature signature
  (make fmt-check was failing).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017sJzkN61UVQboWuHVHwSKP

* ci: make check-no-sim fail closed + tracer positive control

The guard treated ANY non-zero 'cargo tree -i rand' exit as 'rand is
absent'. An unrelated cargo failure — missing _external submodules, a
typo'd package in SIM_EXAMPLES, registry trouble — therefore passed the
check vacuously (observed: with submodules uninitialized, all four
assertions 'passed' while cargo was erroring on embassy-executor).

Now 'rand-free' is accepted only when cargo tree fails with its specific
'did not match any packages' error; any other failure aborts the check
and prints cargo's output. A positive control per example additionally
asserts the '--features sim' graph DOES find rand, so a silently broken
tracer (e.g. rand dropped from the simulatable feature) can no longer
turn the whole guard into a no-op.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017sJzkN61UVQboWuHVHwSKP

---------

Co-authored-by: Claude <noreply@anthropic.com>
- Updated comments in `linkable.rs`, `observable.rs`, and `simulatable.rs` to improve clarity and remove references to design documents.
- Enhanced documentation in `lib.rs` of `aimdb-derive` to clarify the derive functionality for `Linkable`.
- Revised comments in `Cargo.toml` files across various examples to remove design references and improve readability.
- Adjusted comments in `producer.rs` and integration tests to clarify the functionality of `SyncProducer` and its methods.
- Updated design documentation to reflect changes in the implementation and clarify the purpose of various traits and features.
- Removed unnecessary design references in example projects to streamline the documentation and focus on functionality.
Specifies how the weather-mesh demo becomes a public, joinable flagship
instance: string-keyed slot pool in the hub (no core changes), EMQX Cloud
broker with per-station credentials/ACLs, self-serve admission via GitHub
device flow in a new generic 'aimdb join' CLI subcommand, an open
provisioning endpoint format, privacy/retention policy, and the open
Embassy TLS question.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFw7Pz4cjF6dUT9Zv2T8t9
Add §8.2 describing the hardware-station user flow: aimdb join on the
flashing host, build-time config embedding via MESH_CONFIG in build.rs
with local-demo fallback, and the trust model for credentials in the
firmware image. Ground §8.1 option 1 with the concrete TLS work
(TRNG entropy, SNTP time source) and update sequencing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFw7Pz4cjF6dUT9Zv2T8t9
…hip (#172)

Translates the 042 design spec into sequenced work packages with
file-level detail for the OSS items (hub slot pool, aimdb join,
generic cloud station, gamma build-time config, Embassy TLS) and
checklists for the external ones (EMQX Cloud, provisioning service,
website).


Claude-Session: https://claude.ai/code/session_012h2fegrHHQqRFT48dW8oZW

Co-authored-by: Claude <noreply@anthropic.com>
ConnectorUrl parsing already recognised the mqtts scheme (port default 8883,
URL-embedded credentials), but rumqttc defaults to plain TCP unless a TLS
transport is set explicitly — an mqtts:// broker URL silently spoke cleartext
to port 8883 and failed the handshake. Select Transport::Tls with the
native-tls default configuration (system trust roots) when the scheme is
mqtts, which the public weather mesh hub relies on to reach EMQX Cloud
(design 042 §5).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MESH_SLOTS switches the hub into public-mesh mode (design 042 §4): a bounded
pool of N string-keyed slot records (station.{slot}.temperature/humidity,
default 64) subscribed to station/{slot}/… topics, with DewPoint derived per
slot at the hub via transform_join so the dashboard stays supplied even when
a station publishes only temperature/humidity. Slot records exist from
startup, so admission assigns a slot number instead of requiring an enum
variant and a recompile.

MQTT_URL accepts a full connector URL (mqtts://hub-sub:…@…:8883 for the EMQX
Cloud deployment, credentials redacted from logs); unset, the hub falls back
to the local demo's host-only MQTT_BROKER variable. With MESH_SLOTS unset the
three-enum docker-compose demo configuration is unchanged.

Mesh mode drops the per-value console .log() lines (64 slots of per-message
logging is noise); .observe() signal gauges remain on record list/get.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The open contract for POST /v1/join that aimdb join is built against
(design 042 §6.3): the auth.kind = github | claim-token identity envelope,
the opaque deployment-specific app map, the profile_version-ed response with
the scoped broker credential, the 403/409/503 error bodies whose message the
CLI prints as-is, and the station.toml serialization consumed by
weather-station and gamma's MESH_CONFIG build embedding.

The server side stays in the private ops repo (042 D6); this doc keeps the
format public so the CLI is deployment-agnostic and any AimDB deployment can
implement admission.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New join subcommand speaking the open provisioning format of design 043:

    aimdb join <provisioning-url> [--token <t>] [--out station.toml]

Default auth is the GitHub OAuth device flow (042 §6.1): print the user
code + verification URL, poll for the access token respecting
interval/slow_down, no scopes requested. The public client ID is compiled
in via AIMDB_GITHUB_CLIENT_ID at build time (nothing secret ships in the
CLI); without one the command points at --token. --token switches the
envelope to the claim-token auth kind for deployments without public
self-serve.

The deployment's app fields (v1: station name, city) are collected via
stdin prompts and passed through untouched — no deployment-specific types
in the CLI. On 403/409/503 the service's message is printed as-is; on
success the profile is written as TOML (mode 0600 — it carries the broker
credential) and the assigned station id + name are printed. The CLI never
talks to the broker management API (042 D6/D7).

Dependencies (reqwest with rustls, toml) are gated behind a new join cargo
feature, on by default. Unit tests cover profile JSON→TOML round-trip,
unknown-field tolerance (043 §5), error-body handling, file permissions,
and both paths against a one-shot mock HTTP server.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… WP5)

New weather-station crate — the binary in the design's three-command demo
loop: aimdb join writes station.toml, then
cargo run -p weather-station -- --config station.toml.

The profile (design 043 §4) supplies everything: broker URL + per-slot
credential (embedded into the connector URL; redacted in logs), the assigned
slot (station_id = slot-<n> → station.<n>.* records, station/<n>/… topics),
the station name, and the service-coarsened lat/lon fed to the same
Open-Meteo source as station alpha. DewPoint is derived on the station via
transform_join exactly as alpha does.

Failure/success UX per design §9/§8: a one-shot CONNECT pre-flight turns a
revoked credential into a clear pointer back to aimdb join instead of the
connector's silent retry-forever, and the first successful publish prints
the station's live chart URL (aimdb.dev/mesh/<name>) plus a ready-made MCP
prompt — the shareable moment.

Alpha, beta, and the docker-compose demo are untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…042 WP6)

build.rs reads the station profile at $MESH_CONFIG (the same station.toml
that aimdb join writes, design 043 §4) and generates station_config.rs into
OUT_DIR — broker host/port, credentials, slot, station name, and the
slot-scoped station/<n>/… topics — so the MCU join path is the cloud-station
flow with the flash step swapped in: MESH_CONFIG=../station.toml cargo run
--release (design 042 §8.2). rerun-if-env-changed/rerun-if-changed keep the
generated file honest across builds.

With MESH_CONFIG unset the generated defaults are exactly the previous
hardcoded consts (192.168.1.3:1883, sensors/gamma/… topics), keeping the
tier-1 local demo zero-setup; the split build/flash flow (flash.sh) is
untouched since MESH_CONFIG matters only at build time. main.rs include!s
the generated config, keeps the compile-time enum record keys, and prints
the mesh success banner (live chart URL + MCP prompt) over defmt.

An mqtts:// profile fails the build with a pointer at design 042 §8.1 — the
Embassy client has no TLS yet (WP7), so MESH_CONFIG firmware can only target
a plain-TCP broker until then. The generated credential consts are in place
for WP7 to consume.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…sh hub (042 WP1 follow-up)

Verification of the local drill (plan §End-to-end #1) surfaced three gaps
between the slot pool and the design's demo loop:

- Mesh mode now registers a read-only AimX TcpServer (AIMX_BIND, default
  127.0.0.1:7433) — without it there is no server behind
  'aimdb record list --url tcp://…:7433' (design 042 §7). The local demo
  path is unchanged (no listener).
- Slot records register .with_remote_access(), installing the JSON codec
  that record.get/subscribe and the dashboard read through.
- The mesh deserializers report contract violations through ctx.log() —
  design §9 makes 'a malformed payload is visibly rejected at the hub' a
  feature, and the router's own error path is compiled out without the
  tracing feature. The fallback env filter now includes the runtime-context
  log target (aimdb=info) so the reports actually show on bare cargo run;
  the docker demo always sets RUST_LOG explicitly and is unaffected.

Verified against mosquitto: MESH_SLOTS=4, publishes to station/2/… surface
as station.2.temperature/humidity signal gauges over tcp://127.0.0.1:7433,
the per-slot join derives station.2.dew_point (21.4°C/55% → 12.4°C), and a
malformed payload logs 'station.2.temperature: rejected payload: Migration
error: …' while the slot keeps its last good value.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…follow-up)

The AimX server attaches signal_stats/stage_profiling/buffer metrics to
record.list when built with observability, but those RecordMetadata fields
are themselves cfg-gated — a client compiled without the feature silently
drops them on deserialize, so 'record list --format json' showed no signal
gauges even when the server sent them. Enable aimdb-core/observability for
the CLI so the fields round-trip; this is also how the mesh drill confirms
a slot is updating (ring buffers have no canonical latest for record.get).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…low-up)

Returning Err from main Debug-formats the message — the revoked-slot text
came out as one line of escaped \n. Route run() errors through eprintln
(Display) + exit(1) so the design §9 failure UX reads as written.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…usage

Plan 042 status now reflects the implemented OSS work packages (WP1, WP3's
043 doc, WP4, WP5, WP6 and the verification follow-ups); the demo README
documents the opt-in public-mesh mode (MESH_SLOTS/MQTT_URL/AIMX_BIND, the
weather-station join loop, gamma's MESH_CONFIG build embedding) alongside
the unchanged local demo.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
reqwest's rustls-tls feature bundles webpki-roots, whose
CDLA-Permissive-2.0 license is not on the deny.toml allowlist — make check
failed at cargo-deny. Switch to rustls-tls-native-roots (system trust
store), which also matches how TLS trust works everywhere else in the
workspace. cargo deny is green again; the join unit/mock tests are
unaffected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@lxsaah lxsaah closed this Jul 10, 2026
@lxsaah lxsaah deleted the claude/new-vertical-hosting-plan-d52px3 branch July 10, 2026 19:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants