mirror of
https://github.com/ruvnet/RuView
synced 2026-07-30 18:41:42 +00:00
ae6fd75095
Iter 12. Wires the ADR-121 §2.6 Recalibrate exemption: when an enrolled
person_id matches the current high-separability cluster, the gate
downgrades the would-be Recalibrate to PredictOnly. The high score is
the *intended* outcome of a Soul Signature match, not an attacker-grade
sniffer arrival — so site_salt rotation is suppressed.
Added (no_std-compatible):
- src/coherence_gate.rs additions:
* MatchOutcome enum: Match { person_id: u64 } | NotEnrolled | Suppressed
* SoulMatchOracle trait with matches_enrolled() -> MatchOutcome
* NullOracle (default-constructible, always reports NotEnrolled)
* CoherenceGate::evaluate_with_oracle(score, ts, &O: SoulMatchOracle)
— same hysteresis/debounce as evaluate(), but downgrades Recalibrate
to PredictOnly when oracle returns Match { .. }
* Refactored evaluate(): extracted advance_state(target, ts) shared with
evaluate_with_oracle. evaluate is now a 4-line wrapper.
- pub use MatchOutcome, NullOracle, SoulMatchOracle from lib.rs
tests/soul_match_oracle.rs (8 named tests, all green):
null_oracle_matches_default_evaluate_behavior
(parameterized over 5 score points; oracle-aware and oracle-free
gates produce identical trajectories)
match_outcome_downgrades_recalibrate_to_predict_only
(score=0.95 pends PredictOnly instead of Recalibrate)
match_exemption_promotes_predict_only_after_debounce_not_recalibrate
(after DEBOUNCE_NS, current is PredictOnly — never Recalibrate)
match_outcome_does_not_affect_lower_actions
(Reject pending stays Reject; oracle only intercepts Recalibrate)
suppressed_outcome_does_not_exempt_recalibrate
(Suppressed is functionally equivalent to NotEnrolled at the gate)
not_enrolled_outcome_does_not_exempt_recalibrate
match_outcome_carries_person_id
null_oracle_default_constructor_works
ACs progressed:
- ADR-121 §2.6 fully covered as a stateless integration point — the
hook is in place for the `--features soul-signature` Soul Signature
crate (TBD) to plug in a real RaBitQ-backed oracle.
- ADR-118 §1.4 Soul Signature companion contract is now structurally
enforced at the gate boundary: enrolled subjects do not trigger
site_salt rotation; everyone else does.
Test config:
- cargo test --no-default-features → 64 passed (56 + 8)
- cargo test → 93 passed (85 + 8)
Out of scope (next iter target):
- BfldEvent struct (ADR-121 §2.1 output event JSON) — the downstream
consumer of GateAction. Pairs the gate decision with presence/motion/
person_count sensing fields.
- Optional: connect SoulMatchOracle into the actual `--features
soul-signature` build (compile-time gate around a re-export).
Co-Authored-By: claude-flow <ruv@ruv.net>
99 lines
3.3 KiB
Rust
99 lines
3.3 KiB
Rust
//! Acceptance tests for ADR-121 §2.6 — `SoulMatchOracle` Recalibrate exemption.
|
|
|
|
use wifi_densepose_bfld::coherence_gate::DEBOUNCE_NS;
|
|
use wifi_densepose_bfld::{
|
|
CoherenceGate, GateAction, MatchOutcome, NullOracle, SoulMatchOracle,
|
|
};
|
|
|
|
/// Oracle that always claims an enrolled match.
|
|
struct AlwaysMatch;
|
|
impl SoulMatchOracle for AlwaysMatch {
|
|
fn matches_enrolled(&self) -> MatchOutcome {
|
|
MatchOutcome::Match { person_id: 0x4242_4242 }
|
|
}
|
|
}
|
|
|
|
/// Oracle that reports suppressed (class-3 deployment).
|
|
struct AlwaysSuppressed;
|
|
impl SoulMatchOracle for AlwaysSuppressed {
|
|
fn matches_enrolled(&self) -> MatchOutcome {
|
|
MatchOutcome::Suppressed
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn null_oracle_matches_default_evaluate_behavior() {
|
|
let mut a = CoherenceGate::new();
|
|
let mut b = CoherenceGate::new();
|
|
let oracle = NullOracle;
|
|
for (i, score) in [0.1, 0.4, 0.6, 0.8, 0.95].iter().enumerate() {
|
|
let ts = (i as u64) * 2 * DEBOUNCE_NS;
|
|
assert_eq!(a.evaluate(*score, ts), b.evaluate_with_oracle(*score, ts, &oracle));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn match_outcome_downgrades_recalibrate_to_predict_only() {
|
|
let mut g = CoherenceGate::new();
|
|
let oracle = AlwaysMatch;
|
|
// Score = 0.95 would normally pend Recalibrate. With AlwaysMatch oracle,
|
|
// it pends PredictOnly instead.
|
|
g.evaluate_with_oracle(0.95, 0, &oracle);
|
|
assert_eq!(g.pending(), Some(GateAction::PredictOnly));
|
|
}
|
|
|
|
#[test]
|
|
fn match_exemption_promotes_predict_only_after_debounce_not_recalibrate() {
|
|
let mut g = CoherenceGate::new();
|
|
let oracle = AlwaysMatch;
|
|
g.evaluate_with_oracle(0.95, 0, &oracle);
|
|
let out = g.evaluate_with_oracle(0.95, DEBOUNCE_NS, &oracle);
|
|
assert_eq!(out, GateAction::PredictOnly);
|
|
assert_ne!(out, GateAction::Recalibrate, "Match must prevent Recalibrate");
|
|
}
|
|
|
|
#[test]
|
|
fn match_outcome_does_not_affect_lower_actions() {
|
|
let mut g = CoherenceGate::new();
|
|
let oracle = AlwaysMatch;
|
|
// Score in the Reject band — oracle exemption does NOT apply (only to Recalibrate).
|
|
g.evaluate_with_oracle(0.8, 0, &oracle);
|
|
assert_eq!(g.pending(), Some(GateAction::Reject));
|
|
|
|
// Run to debounce — current must become Reject, not PredictOnly.
|
|
let out = g.evaluate_with_oracle(0.8, DEBOUNCE_NS, &oracle);
|
|
assert_eq!(out, GateAction::Reject);
|
|
}
|
|
|
|
#[test]
|
|
fn suppressed_outcome_does_not_exempt_recalibrate() {
|
|
let mut g = CoherenceGate::new();
|
|
let oracle = AlwaysSuppressed;
|
|
g.evaluate_with_oracle(0.95, 0, &oracle);
|
|
// Suppressed is functionally equivalent to NotEnrolled — Recalibrate stays pending.
|
|
assert_eq!(g.pending(), Some(GateAction::Recalibrate));
|
|
}
|
|
|
|
#[test]
|
|
fn not_enrolled_outcome_does_not_exempt_recalibrate() {
|
|
let mut g = CoherenceGate::new();
|
|
let oracle = NullOracle; // always NotEnrolled
|
|
g.evaluate_with_oracle(0.95, 0, &oracle);
|
|
assert_eq!(g.pending(), Some(GateAction::Recalibrate));
|
|
}
|
|
|
|
#[test]
|
|
fn match_outcome_carries_person_id() {
|
|
let outcome = AlwaysMatch.matches_enrolled();
|
|
match outcome {
|
|
MatchOutcome::Match { person_id } => assert_eq!(person_id, 0x4242_4242),
|
|
other => panic!("expected Match, got {other:?}"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn null_oracle_default_constructor_works() {
|
|
let oracle = NullOracle;
|
|
assert_eq!(oracle.matches_enrolled(), MatchOutcome::NotEnrolled);
|
|
}
|