Skip to content

Commit f99abbf

Browse files
senamakelmedullabot
andcommitted
fix(jev): reject zero recoverability thresholds
Co-authored-by: Medulla <medulla@tinyhumans.ai>
1 parent a621c8a commit f99abbf

3 files changed

Lines changed: 59 additions & 8 deletions

File tree

‎crates/tinytools-jev/src/recovery/README.md‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ using evidenced corrections or transient conditions without an external
1313
prerequisite. Score is a three-category ordered feasibility distribution; its
1414
expectation (0..=2) is **not** a probability of success. Confidence is distribution
1515
concentration and is **not** accuracy. Hosts must calibrate explicit versioned
16-
thresholds, including the counted repeated-blocker threshold.
16+
thresholds, including the counted repeated-blocker threshold. Recoverability must
17+
be finite and in `(0, 1]`; class and advice concentration thresholds accept finite
18+
values in `[0, 1]`. A zero recoverability threshold would admit corrective advice
19+
with no prospect of recovery and reject valid external-prerequisite classifications.
1720

1821
Candidate descriptions alone do not enable alternate evaluation. The host supplies
1922
`RecoveryAlternateReason::WrongToolAdvice` after prior advice or `RepeatedBlocker`

‎crates/tinytools-jev/src/recovery/validation.rs‎

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,11 @@ pub(super) fn validate_thresholds(thresholds: &RecoveryThresholds) -> Result<(),
1818
true,
1919
RecoveryObservation::MAX_KEY_BYTES,
2020
) || thresholds.repeated_blocker == 0
21-
|| ![
22-
thresholds.class_confidence,
23-
thresholds.recoverability,
24-
thresholds.advice_confidence,
25-
]
26-
.into_iter()
27-
.all(probability)
21+
|| !probability(thresholds.recoverability)
22+
|| thresholds.recoverability <= 0.0
23+
|| ![thresholds.class_confidence, thresholds.advice_confidence]
24+
.into_iter()
25+
.all(probability)
2826
{
2927
return Err(RankError::invalid_input("invalid recovery thresholds"));
3028
}

‎crates/tinytools-jev/src/recovery/validation_tests.rs‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,56 @@ async fn external_prerequisites_can_be_classified_without_corrective_advice() {
9595
}
9696
));
9797
}
98+
#[test]
99+
fn recoverability_threshold_requires_a_finite_positive_probability() {
100+
for value in [
101+
0.0,
102+
-0.0,
103+
-0.1,
104+
f64::NAN,
105+
f64::INFINITY,
106+
f64::NEG_INFINITY,
107+
1.1,
108+
] {
109+
let mut policy = thresholds();
110+
policy.recoverability = value;
111+
let fake = Arc::new(Fake {
112+
calls: AtomicUsize::new(0),
113+
result: Err(()),
114+
});
115+
assert!(
116+
RecoveryAdviser::new(fake, policy).is_err(),
117+
"invalid recoverability threshold {value}"
118+
);
119+
}
120+
for value in [f64::MIN_POSITIVE, 1.0] {
121+
let mut policy = thresholds();
122+
policy.recoverability = value;
123+
let fake = Arc::new(Fake {
124+
calls: AtomicUsize::new(0),
125+
result: Err(()),
126+
});
127+
assert!(
128+
RecoveryAdviser::new(fake, policy).is_ok(),
129+
"valid recoverability threshold {value}"
130+
);
131+
}
132+
}
133+
134+
#[test]
135+
fn concentration_thresholds_can_include_zero_and_one() {
136+
for value in [0.0, 1.0] {
137+
let mut policy = thresholds();
138+
policy.class_confidence = value;
139+
policy.advice_confidence = value;
140+
let fake = Arc::new(Fake {
141+
calls: AtomicUsize::new(0),
142+
result: Err(()),
143+
});
144+
assert!(RecoveryAdviser::new(fake, policy).is_ok());
145+
}
146+
}
147+
98148
#[test]
99149
fn thresholds_and_observation_boundaries_are_explicit() {
100150
for invalid in [f64::NAN, -0.1, 1.1] {

0 commit comments

Comments
 (0)