mirror of
https://github.com/ruvnet/RuView
synced 2026-08-07 20:01:43 +00:00
0ca8a38cbcba67c96fb10c08237ecda5778558da
4 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0ca8a38cbc |
feat(adr-118/p3.5): SignatureHasher (BLAKE3-keyed) — 117/117 GREEN
Iter 15. Lands ADR-120 §2.3 — the cryptographic foundation of invariant
I3 ("cross-site identity correlation is impossible"). rf_signature_hash
is now derived from a per-site secret and a daily epoch, so two nodes
observing the same physical person produce uncorrelated 256-bit digests.
Added (no_std-compatible):
- blake3 = "1.5", default-features = false (no_std, no SIMD by default)
- src/signature_hasher.rs:
* Constants SECONDS_PER_DAY (86_400), SITE_SALT_LEN (32), RF_SIGNATURE_LEN (32)
* SignatureHasher { site_salt: [u8; 32] } with new(salt) const ctor
* compute(day_epoch, &features) -> [u8; 32] (BLAKE3 keyed mode)
* compute_at(unix_secs, &features) -> [u8; 32] convenience
* day_epoch_from_unix_secs(unix_secs) -> u32 helper (floor(t / 86400))
- pub use SignatureHasher, RF_SIGNATURE_LEN, SITE_SALT_LEN from lib.rs
tests/signature_hasher.rs (8 named tests, all green):
deterministic_under_identical_inputs
different_site_salts_produce_different_hashes
different_day_epochs_rotate_the_hash
different_features_produce_different_hashes
output_length_is_32_bytes
day_epoch_from_unix_secs_matches_floor_division
(covers 0, 86_399, 86_400, and the 1.7e9 modern timestamp)
compute_at_matches_compute_with_derived_day
cross_site_hamming_distance_is_statistically_high
*** ADR-120 §2.7 AC2 acceptance test ***
Runs 100 trials with distinct (salt_a, salt_b) pairs observing
identical features, computes per-trial Hamming distance, asserts
mean >= 120 bits and min >= 80 bits. Empirically lands at ~128 bits
mean (the expected value for two independent 256-bit hashes), with
no trial below 80 bits — i.e., zero suspicious near-collisions.
ACs progressed:
- ADR-120 §2.7 AC2 — structurally enforced cross-site isolation, now
proven empirically by the Hamming-distance test. This is the
cryptographic half of invariant I3 in code, not just docs.
- ADR-118 invariant I3 — first runtime witness that two sites with
independent site_salts cannot correlate the same person's signature.
Test config:
- cargo test --no-default-features → 72 passed (64 + 8; signature_hasher is no_std)
- cargo test → 117 passed (109 + 8)
Out of scope (next iter target):
- Wire SignatureHasher into BfldEmitter: replace caller-supplied
rf_signature_hash with hasher.compute_at(ts, &features) so the
pipeline produces correct hashes end-to-end.
- IdentityFeatures canonical-bytes encoder so callers don't need to
hand-serialize per-feature representations.
Co-Authored-By: claude-flow <ruv@ruv.net>
|
||
|
|
926c66f677 |
feat(adr-118/p4.1): BfldEvent privacy-gated output + JSON (102/102 GREEN)
Iter 13. Lands ADR-121 §2.1 (output event) + ADR-122 §2.1 (field-gating
policy). BfldEvent collapses the GateAction-driven sensing pipeline
into the canonical wire-format publishable on MQTT.
Added:
- serde (workspace, derive feature, optional) + serde_json (workspace, optional) deps
- New crate feature `serde-json` (default-on; requires `std`)
- src/event.rs (gated on `feature = "std"`):
* BfldEvent struct with all sensing + identity-derived fields
* with_privacy_gating(...) constructor that applies field-gating policy:
class < Restricted (3): identity_risk_score + rf_signature_hash kept
class >= Restricted (3): both nulled to None
* apply_privacy_gating() — idempotent in-place masking
* to_json() -> Result<String, serde_json::Error> (gated on serde-json)
* Custom ser_privacy_class serializer emits lowercase names
("anonymous", "restricted", etc.) per the BFLD JSON spec
* skip_serializing_if = "Option::is_none" on identity-derived fields so
privacy-gated events are observationally indistinguishable from
events that never had the field set
- pub use BfldEvent from lib.rs
tests/event_privacy_gating.rs (9 named tests, all green):
anonymous_event_retains_identity_risk_and_hash
restricted_event_strips_identity_fields (class 3 → None)
apply_privacy_gating_is_idempotent
event_type_is_always_bfld_update (parameterized over 3 classes)
json::json_round_trip_emits_type_field_first_or_last_but_present
json::anonymous_json_includes_identity_fields
json::restricted_json_omits_identity_fields_entirely
(asserts the JSON string does NOT contain identity_risk_score or
rf_signature_hash, verifying skip_serializing_if works as intended)
json::privacy_class_serializes_to_lowercase_name
json::zone_id_none_is_omitted_from_json
ACs progressed:
- ADR-121 AC6 (identity_risk score absent at class 3) — structurally
enforced by with_privacy_gating + skip_serializing_if combination.
- ADR-122 AC1 — JSON shape matches the HA-DISCO publishable event
contract; identity fields can be reliably stripped by privacy_class.
- ADR-118 AC5 — privacy_mode = engaged maps to PrivacyClass::Restricted
with no identity fields in the published event.
Test config:
- cargo test --no-default-features → 64 passed (unchanged; event cfg-out)
- cargo test → 102 passed (93 + 9)
Out of scope (next iter target):
- Emitter struct that wires GateAction + privacy class + sensing inputs
into BfldEvent construction (ADR-118 §2.1 pipeline diagram).
- MQTT topic publisher (ADR-122 §2.2) — depends on a runtime (tokio).
Co-Authored-By: claude-flow <ruv@ruv.net>
|
||
|
|
775661b2e8 |
feat(adr-118/p1.4): BfldFrame (header + payload + CRC32) — 24/24 GREEN
Iter 4. Lands the central wire-format primitive: complete frames with
header + arbitrary-length payload, protected by CRC-32/ISO-HDLC.
Added:
- crc = "3" dependency (CRC-32/ISO-HDLC, same poly as Ethernet / zlib)
- src/frame.rs: CRC32_ALG const and crc32_of_payload(&[u8]) -> u32
- src/frame.rs: BfldFrame { header, payload: Vec<u8> } (gated on `std`)
* BfldFrame::new(header, payload) — auto-syncs payload_len + payload_crc32
* BfldFrame::to_bytes() -> Vec<u8> — header LE bytes ‖ payload
* BfldFrame::from_bytes(&[u8]) -> Result<Self, BfldError>
- BfldError::TruncatedFrame { got, need } variant
- Doc strings on BfldError::Crc and BfldError::PrivacyViolation field names
- tests/frame_roundtrip.rs (7 named tests, gated on feature = "std"):
frame_roundtrip_preserves_header_and_payload
frame_new_syncs_payload_len_and_crc
frame_serialization_is_deterministic
frame_rejects_payload_crc_mismatch
frame_rejects_truncated_buffer_smaller_than_header
frame_rejects_truncated_buffer_smaller_than_payload
empty_payload_is_valid (CRC of empty payload is 0x00000000)
Test config:
- cargo test --no-default-features → 17 passed (frame_roundtrip cfg-out)
- cargo test (default features = std) → 24 passed (3+6+7+8)
ADR-119 ACs progressed:
- AC4 partial: bad-magic + bad-version + CRC-mismatch + truncation rejected
with typed errors; field-level masking lives in the privacy_gate iter.
- AC5: BfldFrame round-trip preserves header + payload + CRC.
- AC6: Identical inputs produce bit-identical bytes (asserted explicitly).
Out of scope (next iter):
- Payload section parser (compressed_angle_matrix, amplitude_proxy, ...)
— only the byte buffer is opaque so far; sections need length prefixes.
- BfldFrameRef<'_> for ESP32-S3 self-only mode (no-alloc, ADR-123 §2.5).
- PrivacyGate::demote(frame, target_class) transformer (ADR-120 §2.4).
Co-Authored-By: claude-flow <ruv@ruv.net>
|
||
|
|
c965e3e6c0 |
feat(adr-118/p1): scaffold wifi-densepose-bfld crate + frame header (3/3 tests GREEN)
Land P1 of the BFLD rollout — the wire-format primitives: - New workspace member: v2/crates/wifi-densepose-bfld - PrivacyClass enum (Raw/Derived/Anonymous/Restricted) with allows_network() and allows_matter() const helpers reflecting ADR-120 §2.2 and ADR-122 §2.4 - BfldFrameHeader (#[repr(C, packed)]) per ADR-119 §2.1 - BFLD_MAGIC = 0xBF1D_0001, BFLD_VERSION = 1 - BfldError variants for InvalidMagic / UnsupportedVersion / Crc / PrivacyViolation - soul-signature cargo feature (gated, default OFF) per ADR-118 §1.4 - Compile-time size assertion via static_assertions::const_assert_eq! - 3 acceptance tests in tests/frame_header_size.rs (all pass) Bug fix: - ADR-119 AC1 claimed BfldFrameHeader is 40 bytes. Actual packed layout sums to 86 bytes. Updated AC1 and §2.1 prose to match. const_assert in frame.rs pins the value structurally — a future field addition that breaks the size fails to compile. Out of scope for this iter (deferred to later P1 commits): - Field-level missing-docs warnings (21) — addressed alongside accessor helpers - Payload section parsing — needs the section-length prefix tests - Round-trip serialize/parse — covered by a fixture-based test in the next iter cargo test -p wifi-densepose-bfld --no-default-features → 3 passed, 0 failed Co-Authored-By: claude-flow <ruv@ruv.net> |