Iter 16. End-to-end ADR-120 §2.3 wiring: BfldEmitter now produces
rf_signature_hash derived from (site_salt, day_epoch, features), with
the IdentityEmbedding bytes as the preferred feature source. Closes
the gap from iter 15 — the hasher is now reachable from the pipeline.
Added (in src/emitter.rs):
- BfldEmitter.signature_hasher: Option<SignatureHasher> field
- BfldEmitter::with_signature_hasher(SignatureHasher) -> Self builder
- emit_with_oracle computes derived_hash BEFORE pushing embedding to ring:
1. unix_secs = inputs.timestamp_ns / NS_PER_SEC
2. feature bytes: embedding.as_slice() flattened to LE f32 bytes,
OR fallback canonical_risk_bytes(&inputs) (4-tuple of LE f32)
3. hasher.compute_at(unix_secs, &bytes)
- Derived hash overrides inputs.rf_signature_hash; when hasher absent
caller-supplied value passes through unchanged (backward compat)
- canonical_risk_bytes(&inputs) -> [u8; 16] private helper for fallback
tests/emitter_hasher.rs (6 named tests, all green):
no_hasher_passes_caller_supplied_hash_through
installed_hasher_overrides_caller_supplied_hash
same_emitter_same_inputs_produce_same_hash (determinism through emitter)
different_site_salts_produce_different_hashes_end_to_end
*** cross-site isolation proven via the BfldEmitter API, not just
via the SignatureHasher direct API (iter 15) ***
no_embedding_falls_back_to_risk_factor_bytes
fallback_hash_differs_from_embedding_hash
(embedding-based and fallback-based hashes are distinct paths)
ACs progressed:
- ADR-120 §2.7 AC2 — cross-site isolation now provable at the public
emitter surface, not just inside the hasher module.
- ADR-118 §2.1 pipeline integration — derived rf_signature_hash flows
through to the BfldEvent without caller participation. Operators
install the hasher once at boot; per-frame code never sees site_salt.
Test config:
- cargo test --no-default-features → 72 passed (emitter_hasher cfg-out)
- cargo test → 123 passed (117 + 6)
Out of scope (next iter target):
- IdentityFeatures struct — typed canonical-bytes encoder so callers
don't need to know that embedding bytes feed the hasher directly.
- Cross-iter integration test: BfldEmitter → BfldEvent::to_json with
derived hash, parsed back, hash field present and base64-encoded
(or hex-encoded) per the JSON wire spec.
Co-Authored-By: claude-flow <ruv@ruv.net>