Expand description
CLEANLIB-657 (CX-8 P1-b) — the verdict() / enforce() dual consumption API.
CX-8 thesis: a blocked verdict is a SUCCESSFUL assessment, not an error. Modelling “blocked” as an exception conflates a policy/security STOP (we assessed it and the answer is “do not proceed”) with a SYSTEM FAILURE (we could not assess it at all). The two must stay distinguishable, because a caller that treats a couldn’t-assess as “no block seen → proceed” has built exactly the [Absence≠safe] false-clear the taxonomy exists to prevent.
So this module offers the same assessment through two ergonomics over one
raw acquisition outcome (Result<CustomerState, CleanLibraryError> —
Ok(state) = we got a verdict of some tier; Err(e) = we could not):
verdict— RETURNS style. A completed assessment of ANY tier (Block included) passes through as anAssessmentvalue;Erris reserved strictly for “couldn’t get a verdict”.enforce— RAISES style for gate use.Ok(())iff clean-to-proceed; a non-clean completed assessment becomesGateError::Blocked, and a couldn’t-assess propagates asGateError::NotAssessed. Fail-closed: aNotYetAssessed/RangeNotResolved(Warn tier) REFUSES — it never passes the gate, so “not assessed” can never read as “allowed”.
The three other SDKs (py StrEnum, js discriminated union, go typed const +
Valid()) mirror this contract against the shared conformance fixture
(CX-8 P2), so the return-vs-raise split is byte-for-byte identical across
surfaces.
§Quickstart (CX-8 P3)
Two ways to consume one assessment. A blocked verdict is a successful
assessment, not an error — only couldn’t get a verdict is an Err, so a
transport / coverage failure can never be mistaken for “no findings,
proceed” ([Absence≠safe]). This example runs as a doctest.
use cleanlib_client::{verdict, enforce, CustomerState, Tier, GateError, CleanLibraryError};
// `outcome` is what your acquisition produced: `Ok(state)` (a verdict of
// some tier) or `Err(e)` (you could not get one at all).
// 1. verdict() — RETURNS style. A Block is a VALUE, never an `Err`.
let a = verdict(Ok(CustomerState::Malicious)).unwrap();
assert_eq!(a.tier(), Tier::Block);
assert_eq!(a.exit_code(), 1);
assert!(!a.is_allowed());
// The "not assessed" path is still a returned VALUE — and it is NOT clean:
let na = verdict(Ok(CustomerState::NotYetAssessed)).unwrap();
assert!(!na.is_allowed());
assert_eq!(na.exit_code(), 2); // warn-tier, fail-closed
// A couldn't-get-a-verdict is the ONLY thing verdict() yields as `Err`:
let acq = Err(CleanLibraryError::CoverageIncomplete {
reason_code: "SCAN_ABORTED".into(),
message: "3 of 40 coordinates unreachable".into(),
});
assert!(verdict(acq).is_err()); // a real failure — never a clean result
// 2. enforce() — RAISES style for CI gates. `Ok(())` ONLY when clean.
assert!(enforce(Ok(CustomerState::Clean)).is_ok()); // proceed
match enforce(Ok(CustomerState::NotYetAssessed)) {
Err(GateError::Blocked { exit_code, .. }) => assert_eq!(exit_code, 2),
other => panic!("not-yet-assessed must block, got {other:?}"),
}
// A couldn't-assess fails CLOSED to the block exit code (1) — never 0:
let gate = enforce(Err(CleanLibraryError::CoverageIncomplete {
reason_code: "SCAN_ABORTED".into(),
message: "coverage failure".into(),
}));
assert!(matches!(gate, Err(GateError::NotAssessed(_))));
assert_eq!(gate.unwrap_err().exit_code(), 1);Structs§
- Assessment
- A COMPLETED assessment of a coordinate. A Block is a normal
Assessmentvalue — never an error. “Couldn’t get a verdict” is theErrarm of the acquisitionResult, kept strictly distinct from anyAssessment.
Enums§
- Gate
Error - Why a gate refused. The two arms preserve the CX-8 distinction the whole module exists to keep:
Functions§
- enforce
enforce()— RAISES style for gate use.Ok(())iff clean-to-proceed.- verdict
verdict()— RETURNS style. Passes a completed assessment through as anAssessmentvalue (a Block is a value, NOT anErr) and reservesErrstrictly for “couldn’t get a verdict” (coverage incomplete / attestation invalid / transport / server — whatever the acquisition surfaced).