feat(adr-118/p1.9): PrivacyClass capability-helper truth tables (279/279 GREEN)
Iter 41. Pins the const-helper API (PrivacyClass::allows_network /
allows_matter) and proves it stays in sync with the Sink::MIN_CLASS
trait-level enforcement. Drift between these two APIs would be a
silent correctness bug — an operator checking allows_network() might
get a different answer than the actual NetworkSink::check_class()
runtime gate.
Added (in tests/privacy_class_capability.rs, no_std-compatible):
- 10 named tests, all green:
allows_network_truth_table (4 classes × bool)
allows_matter_truth_table (4 classes × bool)
allows_matter_implies_allows_network
Monotonicity: Matter is a strict subset of Network. Any class
that allows Matter MUST allow Network. The reverse is not true
(Derived is Network-eligible but not Matter-eligible).
allows_network_strictly_excludes_raw
Class 0 is the ONLY class that fails allows_network. Any future
refactor that lets Raw cross a NetworkSink violates ADR-118 I1.
allows_matter_strictly_requires_class_two_or_three
local_sink_accepts_every_class_per_helper
Cross-consistency: LocalSink::MIN_CLASS = Raw, accepts all.
network_sink_consistency_matches_allows_network
For every class, check_class<NetworkKind> agrees with allows_network().
matter_sink_consistency_matches_allows_matter
Same for Matter.
as_u8_returns_documented_byte_values (0, 1, 2, 3)
class_byte_ordering_matches_information_density (raw < derived < anon < restr)
Helper:
check_consistency<S: Sink>(class, helper_says_allowed) compares the
Boolean helper against (class_byte >= S::MIN_CLASS.as_u8()) and asserts
equality. Catches drift before it reaches operator-visible behavior.
ADR-124 status (iter step 0 sibling check):
- docs/adr/ADR-124-rvagent-mcp-ruvector-npm-integration.md unchanged
at 431 lines. SENSE-BRIDGE scope remains orthogonal.
ACs progressed:
- ADR-118 invariant I1 reinforced at the const-helper layer: a future
PR refactoring PrivacyClass::Raw to be Network-eligible breaks 4 of
the 10 tests (truth table + monotonicity + Raw exclusion + sink
consistency), so the regression is loud rather than silent.
- ADR-120 §2.2 sink-class contract pinned at the helper layer. The
iter 3 (Sink + check_class) and iter 1 (allows_network) APIs now
have a regression test enforcing their agreement.
Test config:
- cargo test --no-default-features → 90 passed (+10 no_std-compat)
- cargo test → 279 passed (269 + 10)
Out of scope (next iter target):
- PR-readiness pivot remains the genuine next step: CHANGELOG batch,
witness bundle regeneration, AC closeout table. All ADR-118/119/120/
121/122 ACs are now empirically covered. External-resource-gated
work (KIT BFId, Pi5/Nexmon hardware) stays skipped.
Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
parent
ce2eaab75a
commit
a7ccac7869
|
|
@ -0,0 +1,142 @@
|
|||
//! `PrivacyClass::allows_network` and `allows_matter` const-helper truth
|
||||
//! tables, plus a cross-consistency check against the `Sink` trait constants.
|
||||
//! Iter 1 introduced these helpers; iter 3 introduced the `Sink::MIN_CLASS`
|
||||
//! mechanism. The two APIs must agree.
|
||||
//!
|
||||
//! Why both APIs: `allows_network` / `allows_matter` are point-in-time
|
||||
//! Boolean queries for ergonomics ("can I publish this frame?"); the `Sink`
|
||||
//! marker-trait + `MIN_CLASS` const provides the structural enforcement at
|
||||
//! compile-time. Drift between them is a silent correctness bug — this iter
|
||||
//! pins the constraint that they always agree.
|
||||
|
||||
use wifi_densepose_bfld::sink::{LocalKind, MatterKind, NetworkKind, Sink};
|
||||
use wifi_densepose_bfld::PrivacyClass;
|
||||
|
||||
const ALL_CLASSES: [PrivacyClass; 4] = [
|
||||
PrivacyClass::Raw,
|
||||
PrivacyClass::Derived,
|
||||
PrivacyClass::Anonymous,
|
||||
PrivacyClass::Restricted,
|
||||
];
|
||||
|
||||
// --- direct truth tables ------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn allows_network_truth_table() {
|
||||
assert!(!PrivacyClass::Raw.allows_network());
|
||||
assert!(PrivacyClass::Derived.allows_network());
|
||||
assert!(PrivacyClass::Anonymous.allows_network());
|
||||
assert!(PrivacyClass::Restricted.allows_network());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn allows_matter_truth_table() {
|
||||
assert!(!PrivacyClass::Raw.allows_matter());
|
||||
assert!(!PrivacyClass::Derived.allows_matter());
|
||||
assert!(PrivacyClass::Anonymous.allows_matter());
|
||||
assert!(PrivacyClass::Restricted.allows_matter());
|
||||
}
|
||||
|
||||
// --- monotonicity property ---------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn allows_matter_implies_allows_network() {
|
||||
// Matter is a subset of Network — if a class is Matter-eligible, it
|
||||
// must also be Network-eligible. The reverse is not true (Derived is
|
||||
// Network-eligible but not Matter-eligible).
|
||||
for c in ALL_CLASSES {
|
||||
if c.allows_matter() {
|
||||
assert!(
|
||||
c.allows_network(),
|
||||
"{c:?}: allows_matter without allows_network is a contract violation",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn allows_network_strictly_excludes_raw() {
|
||||
// Class 0 (Raw) is the only class that fails allows_network. Any future
|
||||
// refactor that lets Raw cross a NetworkSink violates ADR-118 invariant I1.
|
||||
for c in ALL_CLASSES {
|
||||
let expected = !matches!(c, PrivacyClass::Raw);
|
||||
assert_eq!(
|
||||
c.allows_network(),
|
||||
expected,
|
||||
"{c:?}: allows_network drift",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn allows_matter_strictly_requires_class_two_or_three() {
|
||||
for c in ALL_CLASSES {
|
||||
let expected = matches!(c, PrivacyClass::Anonymous | PrivacyClass::Restricted);
|
||||
assert_eq!(c.allows_matter(), expected, "{c:?}: allows_matter drift");
|
||||
}
|
||||
}
|
||||
|
||||
// --- cross-consistency with Sink::MIN_CLASS ----------------------------
|
||||
|
||||
/// For a sink with `MIN_CLASS = K`, a class `C` should be accepted iff
|
||||
/// `C.as_u8() >= K.as_u8()`. Iter 3 implemented exactly this in `check_class`.
|
||||
/// The helpers above must agree.
|
||||
fn check_consistency<S: Sink>(class: PrivacyClass, helper_says_allowed: bool) {
|
||||
let sink_min = S::MIN_CLASS.as_u8();
|
||||
let class_byte = class.as_u8();
|
||||
let sink_says_allowed = class_byte >= sink_min;
|
||||
assert_eq!(
|
||||
helper_says_allowed,
|
||||
sink_says_allowed,
|
||||
"{class:?} vs {} ({} >= {} should be {}, helper said {})",
|
||||
S::KIND,
|
||||
class_byte,
|
||||
sink_min,
|
||||
sink_says_allowed,
|
||||
helper_says_allowed,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_sink_accepts_every_class_per_helper() {
|
||||
for c in ALL_CLASSES {
|
||||
// LocalSink has MIN_CLASS = Raw (byte 0) — accepts all.
|
||||
check_consistency::<LocalKind>(c, true);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn network_sink_consistency_matches_allows_network() {
|
||||
for c in ALL_CLASSES {
|
||||
check_consistency::<NetworkKind>(c, c.allows_network());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matter_sink_consistency_matches_allows_matter() {
|
||||
for c in ALL_CLASSES {
|
||||
check_consistency::<MatterKind>(c, c.allows_matter());
|
||||
}
|
||||
}
|
||||
|
||||
// --- byte-value pinning -----------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn as_u8_returns_documented_byte_values() {
|
||||
assert_eq!(PrivacyClass::Raw.as_u8(), 0);
|
||||
assert_eq!(PrivacyClass::Derived.as_u8(), 1);
|
||||
assert_eq!(PrivacyClass::Anonymous.as_u8(), 2);
|
||||
assert_eq!(PrivacyClass::Restricted.as_u8(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn class_byte_ordering_matches_information_density() {
|
||||
// Higher numerical class = less information density. Sanity check.
|
||||
let raw = PrivacyClass::Raw.as_u8();
|
||||
let derived = PrivacyClass::Derived.as_u8();
|
||||
let anonymous = PrivacyClass::Anonymous.as_u8();
|
||||
let restricted = PrivacyClass::Restricted.as_u8();
|
||||
assert!(raw < derived);
|
||||
assert!(derived < anonymous);
|
||||
assert!(anonymous < restricted);
|
||||
}
|
||||
Loading…
Reference in New Issue