Skip to content

RuCelium: federated environmental intelligence fabric (ADR-264…269) — runtime, hardening, applications - #2

Draft
ruvnet wants to merge 27 commits into
mainfrom
claude/rumycelium-federated-fabric-205acm
Draft

RuCelium: federated environmental intelligence fabric (ADR-264…269) — runtime, hardening, applications#2
ruvnet wants to merge 27 commits into
mainfrom
claude/rumycelium-federated-fabric-205acm

Conversation

@ruvnet

@ruvnet ruvnet commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Draft — not for merge. Kept draft per reviewer instruction. The security review's merge blockers are all closed and its acceptance test passes, but the honest bar for "product" is a physical pilot (ADR-266 §6), not a simulator.

What this is

RuCelium extends the RuField stack from room-scale sensing to planetary environmental sensing — deliberately not as a flat global peer mesh (which fails on battery, bandwidth, routing, calibration, sovereignty, and compromised nodes) but as a federated fabric with four sovereignty layers:

Layer 4  Planetary federation   discovery + aggregates, no ownership
Layer 3  Biome regions          sovereign owners of data, models, actuators
Layer 2  Rhizome gateways       Rust: verify, calibrate, fuse, buffer, govern
Layer 1  Spore nodes            C: sense, fixed-point calibrate, sign, transmit

C stays confined to the sensor boundary; everything above is safe Rust (unsafe_code = "forbid" workspace-wide — the C wire format is parsed, never transmuted). RF joins as a contextual modality with a hard Advisory severity cap.

Decision records

ADR Subject
264 The fabric: four layers, C↔Rust contract, data economics, trust & governance, §14 acceptance
265 Runtime: gateway daemon, LoRaWAN-fit envelope v2, durable store, no_std ABI
266 Applications: deployment wedges, the biological research track, the physical acceptance bar
267 Long-term provenance: Merkle notarization, post-quantum readiness
268 Integrate with the ruvnet stack (agentdb / agenticow / ruflo) rather than reinvent
269 Push federation; why QUIC belongs at exactly one hop

Crates (14) + 10 worked applications

Model & boundaryrucelium-core (EnvSample with twelve mandatory attributes, calibration lineage, events), rucelium-abi (packed 48-byte rv_env_sample_v1, bounds-checked alloc-free parse, deterministic CBOR, ed25519; no_std capable), rucelium-transport (114-byte envelope v2 + DR0 fragmentation).

Gatewayrucelium-ingest (signature → revocation → anti-replay), rucelium-calibration (signed authorities, drift quarantine), rucelium-worldgraph (evidence + contradiction edges, RF bridge), rucelium-store (durable segmented log), rucelium-policy (governed control path), rucelium-notary (Merkle notarization), rucelium-federation (sovereignty, SensorThings projection), rucelium-gateway (the daemon).

Proof & toolingrucelium-bench (64-node reference-model acceptance), examples/ (10 runnable applications), harness/ (npm Darwin flywheel).

Response to the security review

All eight merge blockers closed:

# Blocker Resolution
1 No gateway runtime; benchmark mislabeled Daemon runs (UDP ingest, HTTP/SensorThings, federation sync); benchmark relabeled fabric reference-model acceptance
2 Replay protection lost on restart Replay windows primed from the durable dedup index; command execute-once via a fsync'd phase journal, fail-closed on interrupted Executing
3 Store dedup contradicted its durability claim Persistent dedup index surviving retention and restart; per-record CRC-32; opt-in fsync; docs state exactly what each mode guarantees
4 Forgeable verified boolean VerifiedEnvSample — non-serializable, no public constructor; forging it fails to compile
5 Calibration lineage unverified CalibrationAuthority registry with modality scopes; every record in a chain must verify
6 Federation identity unbound biome_id → key + epoch; cross-biome claims, duplicate summaries and duplicate events rejected
7 Control path not restart-safe Budget charged after authorization; two-phase execution; ed25519-signed receipt attestations
8 SensorThings not conformant Relabeled -inspired, mandatory fields added, deviations documented

The reviewer's acceptance test — all 7 conditions pass

crates/rucelium-gateway/tests/restart.rs

1. packet seq 100 replayed after kill/restart          rejected
2. command cmd-42 replayed after restart               rejected
3. retention-deleted observation, after restart        rejected
4. serialized sample claiming verified: true           cannot compile
5. registered key claiming another biome identity      rejected
6. duplicated signed regional summary                  rejected
7. corrupted COMPLETE stored record        integrity error, not truncation

Two real bugs found and fixed while building

Exact float parsing is load-bearing. serde_json's default float parser is fast but not exact — measured, 18,496 of 200,000 (9.2%) realistic sensor values return one ULP off after a round-trip. Every signature here is computed over canonical JSON and peers verify by re-serializing what they parsed, so genuine signed summaries could fail verification at the peer — silently, intermittently, in the field only. Existing tests missed it by verifying in-process. Fixed workspace-wide; guarded by a test that does the real peer path and was verified to fail without the fix.

Evidence refs pin identity, not content. EvidenceRef is (node_id, sequence), so editing an observation's value inside an exported evidence bundle left the event signature valid. Added EnvironmentalEvent.evidence_digest (length-prefixed, order-sensitive hash of cited observations' canonical JSON) so the signature now covers content.

Reference-model acceptance (SYNTHETIC)

cargo run -p rucelium-bench — all 8 ADR-264 §14 criteria pass: 92,460 emissions over 30 simulated days, 780/780 tamper/replay/forged/post-revocation attacks rejected, 21,504 samples buffered through a 7-day outage and restored with 0 duplicates, p95 alert latency 0.131 ms, 100% SensorThings + WorldGraph mapping, revocation without interruption, 98.77% usable calibrated observations.

Post-outage restore is now stronger than before the review: buffered envelopes are re-verified cryptographically on restore, so tampering at rest is caught.

Post-quantum readiness (ADR-267)

Environmental evidence outlives its cryptography. Signing each reading post-quantum is infeasible at the radio:

scheme signature envelope LoRaWAN DR0 datagrams
ed25519 64 B 114 B 3
ML-DSA-44 2,420 B ~2,470 B ~49

So authenticity stays ed25519 per observation, and verifiability moves to gateway-side Merkle notarization: one root signature over a batch costs 0.59 bytes per observation at 4,096 leaves, with on-demand inclusion proofs. Honestly labelled: post-quantum ready, not post-quantum — no ML-DSA implementation ships, because a hand-rolled lattice implementation would be worse than none.

Verification

  • 412 tests green across the workspace, cargo clippy --all-targets zero warnings, cargo fmt clean, unsafe_code = "forbid".
  • Ten applications run and self-assert; each proves a guarantee rather than demonstrating a dashboard.

Honest status

~70% as an architectural specification, ~55% as a software platform, ~15% as a deployable physical system. Not shipped: real C firmware, real radios, a conformant SensorThings server, multi-biome field federation, field calibration evidence. Per ADR-266 §6 this becomes a product when one physical biome of 8–16 nodes runs 30 days, survives an outage and a restart, rejects replays after that restart, catches a drifting sensor, preserves signed calibration lineage, and produces one independently verifiable event.

In flight on this branch: ADR-269's push federation + optional QUIC transport is still being implemented (rucelium-gateway); the 412-test figure excludes that crate while it is mid-change.

🤖 Generated with Claude Code

https://claude.ai/code/session_016WNAP3jsEEwgoQLPpMe8kY

claude added 21 commits August 2, 2026 01:17
- docs/ADR-264-rumycelium-federated-fabric.md: federated environmental
  intelligence fabric spec (four layers, data economics, governance,
  acceptance criteria)
- rumycelium-core: EnvSample (twelve mandatory attributes), EnvFrame,
  CalibrationRecord (Q16.16 lineage-chained), EnvironmentalEvent,
  SensorModality registry, GeoPoint with exact privacy coarsening,
  three-tier DataClass residency model
- rumycelium-abi: rv_env_sample_v1 packed LE wire format with
  bounds-checked allocation-free parse (no unsafe), deterministic CBOR
  (canonical heads enforced on decode), COSE-inspired signed envelope,
  ed25519 device signing, shipped C header (rumycelium_env.h)
- workspace scaffolding for ingest/calibration/worldgraph/policy/
  federation/bench crates

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WNAP3jsEEwgoQLPpMe8kY
…lator + README

- rumycelium-ingest: gateway pipeline (envelope decode, registry/revocation,
  ed25519 verify, DTLS-style anti-replay window that forged packets cannot
  advance), per-category reject stats (19 tests)
- rumycelium-calibration: anchor-rooted lineage chains, affine application
  with stated uncertainty, EWMA drift detection with sticky quarantine —
  never silent correction (25 tests)
- rumycelium-worldgraph: typed env WorldGraph (sensor/ecosystem/region/
  anchor nodes, evidence + contradiction edges, haversine queries, JSON
  persistence) + RuView FieldEvent RF-context bridge with hard Advisory
  severity cap and 0.3 evidence-weight cap (12 tests)
- rumycelium-bench: deterministic 64-node biome simulator (diurnal signal
  models, drift/anomaly/outage scenario, tamper/replay/forged-key attack
  stream) + ADR-264 §14 report scaffolding
- dev-profile opt-level=3 for dalek/sha2 so debug tests stay fast
- README: RuMycelium section

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WNAP3jsEEwgoQLPpMe8kY
- rumycelium-policy: typestate control path (proposal -> policy -> safety
  sim -> authority -> signed command -> gateway validation -> receipt);
  skipping a stage is a compile error; 7-stage audit trail; deterministic
  ed25519 command signing; execute-once replay protection (14 tests +
  compile_fail doctest)
- rumycelium-federation: OutageBuffer with dedup surviving serialization,
  Biome sovereignty (global live+replay dedup, unverified-sample rejection,
  signed DeviceRevoked events, disclosure delay + coordinate coarsening),
  signed RegionalSummary + FederationBus, OGC SensorThings 1.1 projection
  with pure-integer RFC3339 (21 tests)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WNAP3jsEEwgoQLPpMe8kY
…arwin-flywheel metaharness

- Rename RuMycelium -> RuCelium everywhere (crates rucelium-*, ADR-264,
  C header rucelium_env.h, spec version rucelium.fabric.v0.1)
- rucelium-bench: deterministic 64-node biome simulator (diurnal signal
  models per modality, drift injection, flood anomaly, 7-day uplink
  outage, tamper/replay/forged-key/post-revocation attack streams) wired
  through the REAL production pipeline: ABI -> ingest -> calibration ->
  WorldGraph + RF context -> biome federation -> SensorThings -> governed
  control path. ADR-264 §14 scorecard: all 8 criteria pass (SYNTHETIC),
  92,460 emissions, 780/780 attacks rejected, 0 restore duplicates,
  98.77% usable calibrated observations, p95 alert 0.24 ms
- Acceptance tests: full §14 run, same-seed determinism fingerprint,
  seed-robustness
- Event-correlated deviations excluded from drift accounting (a flood
  must not quarantine healthy sensors; drift is slow and single-sensor)
- harness/: rucelium-harness npm metaharness — Darwinian flywheel
  (vary -> evaluate -> select -> retain) with fitness from workspace
  tests + clippy + §14 benchmark, generation ledger, strict gate command

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WNAP3jsEEwgoQLPpMe8kY
- docs/ADR-265-rucelium-runtime.md: gateway daemon, compact envelope v2
  (LoRaWAN DR0 fit), segmented durable store, federation-over-network,
  no_std ABI surface decisions
- workspace scaffolding for rucelium-store / rucelium-transport /
  rucelium-gateway (implementations landing in follow-up commits)
- README: runtime section with gateway quickstart + two-biome recipe

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WNAP3jsEEwgoQLPpMe8kY
…-265 §2/§3/§5)

- rucelium-store: append-only segmented JSONL store — dedup index rebuilt
  on open, torn-tail crash recovery (truncate-and-continue), deterministic
  replay, whole-segment retention enforcement, stats (13 tests)
- rucelium-transport: compact envelope v2 (114 B packed, pubkey by
  reference — v1's ~150 B cannot fit LoRaWAN DR0) + 6-byte-header MTU
  fragmentation with loss/dup/reorder-tolerant reassembler; a compact
  envelope is exactly 3 DR0 datagrams (25 tests incl. full tamper sweep)
- rucelium-abi: std default feature; --no-default-features [--features
  alloc] compiles the wire format (+ CBOR with alloc) for no_std spore
  targets; local range constants pinned to the core registry by test

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

Review response, part 1:
- rucelium-ingest: VerifiedEnvSample — non-serializable, no public
  constructor, produced only by the full cryptographic verification paths
  (ingest / reverify_stored); modify() revalidates before committing;
  reverify_stored() re-checks stored envelopes without touching the
  replay window (restore path); prime_from_dedup() rebuilds per-device
  anti-replay windows from a durable dedup index after restart
- rucelium-bench report + README: relabelled as fabric REFERENCE-MODEL
  acceptance — it scores in-memory library components, not the runtime
  path (store/transport/gateway), which has its own e2e tests

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WNAP3jsEEwgoQLPpMe8kY
Review response, part 2 (blocker 5):
- CalibrationAuthority registry with per-modality trust scopes
- deterministic CalibrationSigner (canonical-bytes ed25519, signature
  fields cleared before signing)
- strict CalibrationStore::with_authorities: every record in a lineage
  chain must carry a verifying signature from a signer trusted for its
  modality — roots and children alike; verify_lineage re-checks each link
- the reviewer's attack is a named test: a self-signed record claiming
  method "anchor_reference" with an unregistered key is rejected
- permissive new() retained for tests/simulation, loudly documented
- 41 tests (16 new), clippy clean

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WNAP3jsEEwgoQLPpMe8kY
…DR-266 wedges

Review response, parts 3-4 (blockers 3 and 7) + deployment strategy:

rucelium-store (blocker 3):
- persistent dedup index (dedup.idx) is now authoritative on open, so
  keys survive retention deletion AND restart — the headline flaw
- per-record CRC-32: a newline-terminated record whose CRC fails is a
  hard Corrupt error, never truncated; only a genuinely torn final line
  (no newline / incomplete CRC prefix) is repaired
- opt-in fsync (sync_data on segment + index per append); crate docs now
  state precisely what each mode guarantees
- legacy bare-JSON lines still readable, index backfilled in place
- dedup_keys() exposes the durable replay memory (28 tests)

rucelium-policy (blocker 7):
- safety budget is CHECKED at simulation, CHARGED only on execution, so
  unauthorized proposals can no longer exhaust another actuator's budget
- two-phase execution: Executing -> Executed/Failed; a command in ANY
  phase is refused (a crashed Executing entry fails closed)
- export_phases/restore_phases journal hooks for daemon restart
- receipts are now ed25519 attestations signed by the gateway identity,
  with verify_receipt() (21 tests incl. compile_fail typestate doctest)

docs/ADR-266: deployment wedges (flood/watershed first), the biological
frontier as a research track with capped-evidence discipline, and the
physical 8-16 node acceptance test that supersedes simulation claims

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WNAP3jsEEwgoQLPpMe8kY
…, federation sync

The ADR-265 §4 gateway daemon (verified green when written: 24 tests +
live smoke run showing DR0 fragment reassembly, calibrated observations,
and biome-signed flood alerts):
- UDP ingestion dispatching v1 CBOR / v2 compact / DR0 fragment paths
- calibration + drift quarantine, WorldGraph registration, disk store
- local alert rules emitting biome-signed EnvironmentalEvents
- SensorThings-style HTTP API + admin revocation endpoint
- peer federation poller: verifies summaries and revocations against the
  peer's published biome key before applying them
- --simulate N synthetic spore swarm (rotates all three encodings)
- tests/e2e.rs: two gateways, revocation on A propagates to B, B then
  rejects the revoked node's traffic

WIP NOTE: this commit does not build against the concurrently hardened
store/policy/federation APIs (fsync param, two-phase execution, sealed
VerifiedEnvSample). Rewiring to those APIs — plus restart-safe replay
priming and the kill/restart/resend acceptance test — lands next.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WNAP3jsEEwgoQLPpMe8kY
Review response, part 5 (blockers 4 and 6, plus SensorThings honesty):
- Biome::accept now takes only VerifiedEnvSample; AcceptOutcome::Unverified
  is gone because the state is unrepresentable — a compile_fail doctest
  proves a bare EnvSample (however its verified flag is set) is rejected
  by the type system, not by a runtime boolean check
- OutageBuffer stores the ORIGINAL SIGNED ENVELOPE, not a bare sample:
  restore goes envelope -> IngestPipeline::reverify_stored -> accept, so
  buffered data is re-verified cryptographically after a restart; a
  tampered buffered envelope fails reverify and never enters the biome
- FederationBus binds biome_id -> (pubkey, key_epoch): a registered key
  claiming another biome's id is IdentityMismatch; higher epoch rotates,
  lower/equal with a different key is StaleKeyEpoch; duplicate summary
  windows and duplicate event ids are rejected (bus replay protection)
- SensorThings relabelled a *-inspired projection* (not conformant until
  an external OGC suite passes) and given mandatory fields:
  Datastream.description + observationType, Sensor.description;
  encodingType text/plain with the deviation documented
- 29 tests (28 unit + compile_fail doctest)

Also scaffolds the examples/ workspace member for worked applications.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WNAP3jsEEwgoQLPpMe8kY
examples/ workspace member (rucelium-examples): deterministic Rng, Node
provisioning that signs real 48-byte wire records, a Gateway harness over
the real IngestPipeline, and narrative output helpers. Sensor values are
simulated; the verification machinery is the production code.

4 toolkit tests: sealed sample round-trip, replay rejection, tamper
rejection, PRNG determinism. Ten application scenarios land next.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WNAP3jsEEwgoQLPpMe8kY
The reference-model runner now goes through the post-review surfaces:
- sealed VerifiedEnvSample from ingest; calibration applied via .modify()
- the outage buffer holds ORIGINAL SIGNED ENVELOPES, and every restored
  envelope is re-verified cryptographically (reverify_stored) before the
  biome accepts it — strictly more faithful than the previous path, and
  the duplicate-free assertions still hold
- FederationBus::register_biome with an explicit key epoch
- GatewayValidator with a gateway identity seed; safety budget charged
  only after successful execution (record_execution)

All 3 acceptance tests green: full §14 run, same-seed determinism
fingerprint, seed-robustness.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WNAP3jsEEwgoQLPpMe8kY
Snapshot of the in-flight migration to the hardened APIs so the work is
not lost. New modules landing: journal.rs (durable command-phase journal
for restart-safe execute-once) and control.rs (governed control path
endpoint). Replay-window priming from the store's durable dedup index and
the kill/restart/resend acceptance test are part of this migration.

The build is red at this commit by construction; the completed, verified
migration lands in the next commit.

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

SOTA research outcome. The gap: environmental evidence is retained for
years (compliance disputes, climate baselines), but ed25519 signatures
lose trustworthiness on that horizon. The naive fix does not fit:

  scheme      signature   envelope   LoRaWAN DR0 datagrams
  ed25519         64 B      114 B                        3
  ML-DSA-44    2,420 B    ~2,470 B                      ~49

ML-DSA's speed is fine; SIZE is the binding constraint at the radio.

Decision: split what the signature is doing. Authenticity NOW stays
ed25519 per observation at the node (unchanged radio budget).
Verifiability LATER moves to a gateway-side Merkle notary that signs
only batch ROOTS — amortizing a Dilithium-class signature to well under
a byte per observation (4,096-leaf batch: 2,420 B / 4,096 = 0.6 B each),
with on-demand inclusion proofs instead of per-record overhead.

What ships is algorithm AGILITY, honestly labelled: RuCelium is
post-quantum READY, not post-quantum. No ML-DSA implementation is
included — a hand-rolled lattice implementation would be worse than
none. Roots are self-describing (NotaryAlgorithm recorded inside the
signed structure), migration is hybrid dual-signing, and history gains
the new guarantee by re-notarization (old roots become leaves of a new
PQ-signed tree) without re-signing a single stored observation.

Scaffolds crates/rucelium-notary; implementation lands next.

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

Completes the review response. The gateway is migrated to every hardened
API and the restart-attack surface is now closed:

- replay windows are PRIMED FROM THE DURABLE DEDUP INDEX at startup
  (ingest.prime_from_dedup(store.dedup_keys())), so a restarted gateway
  refuses previously-accepted packets
- command execute-once survives restart via a durable phase journal
  (journal.rs: export_phases/restore_phases, fail-closed on Executing)
- strict signed calibration authorities in the daemon
- POST /api/admin/command drives the full governed control path
- --fsync (default on)

tests/restart.rs — the reviewer's acceptance test, all 7 conditions:
  1. packet seq 100 rejected after restart               PASS
  2. command cmd-42 rejected after restart               PASS
  3. retention-deleted observation still replay-rejected
     after restart (durable dedup index)                 PASS
  4. serialized EnvSample with verified=true cannot
     enter the biome (type-level)                        PASS
  5. registered federation key cannot claim another
     biome identity                                      PASS
  6. duplicated signed regional summary rejected         PASS
  7. corrupted COMPLETE record = integrity error,
     not silent truncation                               PASS

34 unit + 1 e2e + 6 restart tests green.

Also lands the first four application examples (flood-watershed,
irrigation-agriculture, sentinel-forest, ecosystem-immune) — the
remaining six are still being written.

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

Surveyed the published ruvnet packages against what RuCelium built
bottom-up, and found real duplication to stop:

- agentdb ships a single-file .rvf cognitive container with HNSW search,
  causal graph, and provenance — literally the 'RVF buffering' (§13) and
  'RuVector similarity search' (§7) the original ADR named, and exactly
  the shape ecosystem memory (ADR-266 B8) needs. Binding: a thin
  rucelium-memory adapter; the append-only store stays the source of
  truth, the vector container is derived and rebuildable.
- agenticow's copy-on-write vector branching (~0.5 ms, 162 bytes,
  independent of base size) is the right substrate for the ADR-264 §9
  SAFETY SIMULATION stage: branch biome state, simulate the actuation,
  discard the branch — and hand an auditor the branch a decision was
  made on. Deepens one existing stage; adds none.
- the ten Mycelium agents belong on ruflo / agentic-flow. RuCelium ships
  no agent runtime; it ships what makes agents safe (typed proposals,
  deterministic policy, authority, signed commands, receipts) and will
  expose that as an MCP surface.
- harness/ and agentic-flow converged independently on 'freeze the model,
  evolve the harness'; the harness README now credits that explicitly
  instead of implying novelty.

Normative constraint: every binding is ADDITIVE — the §14 acceptance path
must keep passing with none of these installed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WNAP3jsEEwgoQLPpMe8kY
Applications complete and passing (39 tests across them):
- flood-watershed        lead time vs conventional gauge, blocked culvert,
                         storm-displaced sensor excluded, RF contradiction
- irrigation-agriculture full governed actuation, verified signed receipt,
                         unauthorized + over-magnitude proposals stopped
- industrial-compliance  regulator-verifiable evidence bundle, strict signed
                         calibration lineage, independent verifier
- sentinel-forest        per-organism bioelectric baselines, confounder that
                         must NOT escalate, capped evidence
- ecosystem-immune       biofilm corroborated by chemical sensors before
                         escalation, source localization, governed response
- airborne-dna           acoustic/genetic confirmation and contradiction,
                         human-DNA disclosure gate
(four remaining: wildfire-risk, biodiversity-habitat, pollinator-hive,
ecosystem-memory)

Also snapshots in-flight work: rucelium-notary (ADR-267) is mid-write and
its lib.rs references a bundle module not yet on disk — that crate does
not compile at this commit; every other crate is green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WNAP3jsEEwgoQLPpMe8kY
- wildfire-risk: RF-only detection capped at Advisory while physical
  PM + optical smoke evidence reaches Critical; heat-degraded sensor
  excluded AND reported rather than silently dropped
- pollinator-hive: single-hive collapse stays Advisory (one hive failing
  is not a regional signal); only time-correlated multi-hive collapse
  escalates; a merely cold hive produces no event

Remaining: biodiversity-habitat, ecosystem-memory.

Also snapshots rucelium-notary (ADR-267) mid-iteration — 31 of 33 tests
pass; its agent is still fixing the inclusion-proof index/count guard and
one bundle assertion. Every other crate is green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WNAP3jsEEwgoQLPpMe8kY
…ly one hop

Answers two questions that must not be conflated.

Sensor boundary keeps datagrams. QUIC is a category error there: RFC 9000
requires a >=1200-byte padded Initial, ~24x our 114-byte envelope and far
past LoRaWAN DR0's 51-byte MTU; a node waking every 30 min cannot amortize
a handshake it will have lost by the next wakeup; EU868's 1% duty cycle
often makes the round trips unaffordable. Decisively, we do not need what
it provides — the envelope is OBJECT-secured (ed25519 over the exact 48
payload bytes), which is what lets an untrusted store-and-forward relay,
i.e. a LoRaWAN network server, sit in the path harmlessly.

Federation moves poll -> push, and this is a SECURITY fix before a
performance one: 30s polling caps revocation propagation at 30s, so a
compromised device stays valid at peers for up to a full interval. Push
first, transport second, via a FederationTransport trait (announce /
subscribe / sync_since) — with the polling backstop MANDATORY so a peer
that missed a push still converges.

QUIC becomes an optional transport (feature-gated, default off) where it
genuinely earns it: connection migration across LTE/satellite/wifi
failover, 0-RTT after a partition, per-artifact-class streams so a
stalled summary cannot block a revocation, and traffic-analysis
resistance — which matters for sensitive-species deployments, where alert
TIMING leaks location even when payloads are signed and coordinates
coarsened.

Two normative constraints: QUIC is defence in depth and NEVER the trust
boundary (everything received is verified identically regardless of
transport), and TLS identity is the biome's existing ed25519 key rather
than a new PKI. The §14 acceptance path and restart-attack tests must
pass with the feature disabled.

Note: no @ruvector/quic exists — RuVector is a vector database/format
family (memory substrate, ADR-268 §2.1), not a transport.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WNAP3jsEEwgoQLPpMe8kY
A REAL production bug, found by the notary work and confirmed by
measurement: serde_json's default float parser is fast but not exact.
18,496 of 200,000 realistic sensor values (9.2%) come back one ULP off
after a JSON round-trip.

Every signature in this workspace is computed over canonical JSON, and a
peer verifies by RE-SERIALIZING what it parsed. So a genuine, correctly
signed RegionalSummary or EnvironmentalEvent could fail verification at
the peer — silently, intermittently, and only in the field. Our tests
missed it because they signed and verified in-process, never across a
wire round-trip.

Fix: pin serde_json's float_roundtrip feature at the workspace level,
with a comment stating it is load-bearing.
Guard: rucelium-federation now has signed_summary_survives_a_json_wire_
round_trip, which does the real peer path (sign -> to_string -> from_str
-> verify) and asserts bit-identical floats. Verified that the guard
FAILS without the feature and passes with it.

Also lands:
- rucelium-notary complete (34 tests): domain-separated Merkle tree
  (last-node promotion, rejecting the CVE-2012-2459 duplication
  ambiguity), stateless inclusion proofs, algorithm-agile signed roots,
  third-party evidence-bundle verification, re-notarization chaining.
  Measured amortization: 2420 B / 4096 leaves = 0.59 B per observation.
  It also found that leaf_count must be bound to the SIGNED root, since
  verify_inclusion alone cannot reject a same-shape count.
- all ten worked applications green (60 tests in examples/)

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

mamd69 commented Aug 2, 2026

Copy link
Copy Markdown

Very interesting. Would be super helpful if you can share the claude code session...the link above appears to be private.

Closes a schema gap the compliance example exposed while being built.

EnvironmentalEvent.evidence is a list of EvidenceRef — (node_id,
sequence) — which pins WHICH observations were cited but never their
CONTENT. Two different readings with the same identity are
indistinguishable, so editing an observation's value inside an exported
evidence bundle broke nothing: the event signature still verified.
The industrial-compliance example had to smuggle a sha256 through the
signed message STRING and parse it back out to get tamper-evidence — a
workaround standing in for a missing field.

Adds EnvironmentalEvent.evidence_digest plus rucelium_core::
evidence_digest(), which hashes each cited observation's canonical JSON
LENGTH-PREFIXED and in citation order — length prefixing so two
different citation lists cannot concatenate to the same byte stream,
order-sensitivity because a reordered evidence list is a different
claim. Because the field sits inside the signed structure, altering any
cited observation now invalidates the signature.

Producers updated to bind real content (gateway alerts, bench alerts,
compliance bundles); events that make no content claim (DeviceRevoked)
carry None. The compliance verifier prefers the structured field and
keeps the message-string path only as a documented legacy fallback.

412 tests green across the workspace (gateway excluded — its QUIC/push
federation work is still in flight), clippy clean, fmt clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WNAP3jsEEwgoQLPpMe8kY
@ruvnet ruvnet changed the title RuCelium: federated environmental intelligence fabric (ADR-264) + Darwin-flywheel metaharness RuCelium: federated environmental intelligence fabric (ADR-264…269) — runtime, hardening, applications Aug 2, 2026
claude added 5 commits August 2, 2026 03:16
Snapshot of in-flight work: the FederationTransport abstraction,
push-on-revocation, and the optional QUIC transport. The agent is
actively editing, so this commit captures a moving target and the crate
does not build at this SHA (a push-announce call site is mid-rename).

Every other crate is green: 412 tests, zero clippy warnings, fmt clean.
The verified gateway lands in the next commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WNAP3jsEEwgoQLPpMe8kY
Federation is no longer a poller. A FederationTransport abstraction
(announce / subscribe / sync_since) carries signed artifacts, with
push-on-revocation so a compromised device's revocation propagates at
link speed instead of waiting up to a full polling interval — that
interval was a security window, not just latency.

The polling backstop stays MANDATORY (ADR-269 §3): a peer that missed a
push must still converge, so sync_since runs on reconnect and on a slow
timer regardless of transport. Everything received is verified
identically no matter how it arrived — the transport is never the trust
boundary.

466 tests green across the workspace, zero clippy warnings, fmt clean.
The optional QUIC transport (§4) is still being written.

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

Completes ADR-269. The optional QUIC transport (feature-gated, default
off) binds TLS identity to the biome's EXISTING ed25519 key — genuine
RFC 7250 raw public keys, no X.509 anywhere, no new PKI, no certificate
authority, no name-based trust.

How the pin actually holds:
- server wraps the same biome seed Biome signs with as PKCS#8 (RFC 8410)
  and serves a bare SubjectPublicKeyInfo; a test asserts the advertised
  key equals Biome::public_key_hex() — one key, not two
- client builds the expected SPKI with rustls' own helper (byte-identical
  to what a peer serves, asserted), accepts only an exact match, rejects
  intermediates, re-checks the pin inside verify_tls13_signature, refuses
  TLS 1.2, and advertises only Ed25519
- requires_raw_public_keys() = true means a server that doesn't negotiate
  RawPublicKey gets a fatal handshake error — an X.509 answer cannot be
  accepted, so there is no downgrade path
- the custom verifier is STRICTER than webpki (one key, exact bytes, no
  CA, no name matching) and never returns Ok for an unpinned key

The decisive test: a_peer_presenting_the_wrong_key_is_refused_and_
delivers_nothing — the mismatch names both keys and delivers zero
artifacts, then the same endpoint dialled with the correct key succeeds,
proving the refusal was the pin and not a connection failure.

Per §4.3 each artifact class gets its own stream, so a large summary
transfer cannot stall a revocation.

Known limitations, documented rather than hidden: the server does not
authenticate clients (safe only because the session is never the trust
boundary — an unauthenticated peer can waste bandwidth, never revoke a
device); 0-RTT resumption is not wired; and main.rs has no --quic-listen
flag yet, so the daemon still federates over HTTP.

470 tests green (58 default / 67 with --features quic), zero clippy
warnings in both configurations, fmt clean. The §5 regression guard
holds: restart.rs 6/6 and e2e.rs 1/1 pass unchanged with the feature off.

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

SECURITY. Found by asking the 20-year question in the honest ledger —
'could a stranger verify this after the institution that owned it is
gone?' Chasing that amber row surfaced a live vulnerability.

FederationBus::register_biome accepted ANY strictly-higher epoch and
rebound the identity to the presented key, with no proof of continuity:
the incoming key was never signed by the outgoing one. So a peer —
configured, malicious, or merely compromised — could announce
biome/thames-estuary at epoch 999 with its own key, and from then on the
gateway accepts the ATTACKER's summaries and revocations as that biome's
while rejecting the real biome's as an IdentityMismatch. The
identity-binding hardening was doing real work; rotation walked around it.

Fix follows TUF root rotation: new keys become trusted only via a
statement signed by currently-trusted keys.

- register_biome is now GENESIS ONLY (trust-on-first-use, idempotent for
  an unchanged key); rebinding an established identity returns
  SuccessionRequired
- rotation moves to rotate_biome(&KeySuccession), signed over canonical
  bytes that include from_epoch — so a captured succession cannot be
  replayed onto a later state
- two authorisation paths, no third: CONTINUITY (outgoing key signs) or
  RECOVERY (m-of-n distinct pre-declared custodians)

The recovery path is the point. Over twenty years an institution being
restructured, defunded, merged, or simply losing its key is the expected
case, not an edge case. A 2-of-3 custodian quorum can hand the identity
to a successor without the original key ever existing again — which is
the only way a 2026 baseline is still citable in 2046. Successions can
also rotate the custodian set, so governance evolves without breaking
the chain. threshold=0 opts out explicitly: the identity dies with its
key, by choice rather than by accident.

10 new tests, led by an_unsigned_epoch_bump_cannot_steal_an_identity and
custodians_can_recover_an_identity_whose_holder_is_gone. The pre-existing
rotation test failed after the fix because it exercised the vulnerable
path — rewritten against the succession API.

479 tests green, zero clippy warnings, fmt clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WNAP3jsEEwgoQLPpMe8kY
Third bug of the same family, found by continuing to probe serialization
edges. JSON has no NaN and no Infinity: serde_json writes BOTH as null,
indistinguishably, and parsing null back into an f32/f64 fails outright.

Measured:
  SERIALIZED:           {"confidence":null,"sig":null}
  LOCAL VERIFY MATCHES: true      <- signs and verifies in-process
  WIRE:                 {"confidence":null,"sig":"deadbeef"}
  PARSE FAILED:         invalid type: null, expected f32

So a signature over a non-finite float is worse than no signature: it
looks valid locally and is unparseable at the peer it was minted for —
signable but undeliverable, and silent about it.

Fix, stated as the invariant rather than a special case: SIGN ONLY WHAT
ROUND-TRIPS. New crate::round_trips serializes, parses back, and requires
equality — which catches NaN and Infinity today and any future
serialization hazard for free. Both signing paths (Biome::sign_event,
Biome::sign_summary) now fail closed: they leave the artifact unsigned
rather than mint an unusable signature, so FederationBus rejects it as
Unsigned instead of shipping something that cannot be verified.

Scope, honestly: no current call site produces a non-finite value —
summarize() cannot (an accumulator exists only with >=1 sample, so no
0/0, and sample values are validated finite before acceptance, asserted
by a new test over four windows including empty ones). This closes a
latent hazard on a public API, not an active outage.

4 new tests. Verified load-bearing by disabling the guard and watching
a_non_finite_float_is_never_signed fail, then restoring it.

483 tests green, zero clippy warnings, fmt clean.

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

ruvnet commented Aug 24, 2026

Copy link
Copy Markdown
Owner Author

Reviewed as part of clearing the outstanding queue (#3, #4, #5, #7 are now merged).

Not merging this one, because its own description carries a standing hold:

Draft — not for merge. Kept draft per reviewer instruction. The security review's merge blockers are all closed and its acceptance test passes, but the honest bar for "product" is a physical pilot (ADR-266 §6), not a simulator.

That is a deliberate decision with a stated reason, and it is not mine to overturn — a simulator passing its acceptance test is not the same claim as a fabric that has run on real hardware, and this PR is explicit that it only has the former.

Two practical notes for whenever it is picked up:

  1. It is now ~3 weeks behind four merged PRs, two of which (feat: add authenticated BLE and Channel Sounding evidence #5, security: authorize composite FieldEvent privacy #7) changed rufield-core and rufield-privacy. At 103 files and +34,250 lines it will need a real rebase, and the FieldEvent privacy surface it targets has changed underneath it — authorize_event now exists, and FieldInference gained track_id.

  2. Worth checking for the same wire-code collision that blocks feat: add trust-gated Rydberg quantum RF vector sensing #1: Modality codes 16 (BleAdvertisementRssi) and the channel-sounding additions from feat: add authenticated BLE and Channel Sounding evidence #5 are now taken.

Say the word if the hold should be lifted and I will rebase and merge it.

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.

3 participants