Repository navigation
Expand file tree
/
Copy pathreadiness.rs
More file actions
126 lines (125 loc) · 4.58 KB
/
Copy pathreadiness.rs
File metadata and controls
126 lines (125 loc) · 4.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
//! 前置 readiness 纯判定;不访问进程、文件系统或授予质量通过。
use crate::{PreparationEvidenceState, ReadinessInput, ReadinessOutcome, ReadinessState};
use std::collections::{BTreeMap, BTreeSet};
/// 按本轮可信要求、绑定与时钟判定准备状态,返回具体阻塞/未知标识。
/// 可选条件不改变汇总;缺来源、旧证据与零必需集合不能返回 ready。
#[must_use]
pub fn evaluate_readiness(input: &ReadinessInput) -> ReadinessOutcome {
let mut reasons = BTreeSet::new();
let mut blocked = BTreeSet::new();
let mut unresolved = BTreeSet::new();
if !input.requirements_verified {
reasons.insert("requirements_source_unverified".into());
}
if !input.observations_verified {
reasons.insert("observations_source_unverified".into());
}
if !reasons.is_empty() {
return outcome(ReadinessState::Unknown, blocked, unresolved, reasons);
}
if !input.requirements_complete {
reasons.insert("requirements_set_incomplete".into());
}
let mut counts = BTreeMap::new();
for requirement in &input.requirements {
*counts.entry(requirement.id.as_str()).or_insert(0_usize) += 1;
}
let mut applicable_count = 0;
for requirement in input
.requirements
.iter()
.filter(|requirement| requirement.required)
{
let id = &requirement.id;
if !valid_id(id) || counts[id.as_str()] != 1 || !valid_hash(&requirement.binding_sha256) {
unresolved.insert(id.clone());
reasons.insert("requirement_identity_invalid".into());
continue;
}
match requirement.applicable {
Some(false) => continue,
None => {
unresolved.insert(id.clone());
reasons.insert("applicability_unresolved".into());
continue;
}
Some(true) => applicable_count += 1,
}
let observations: Vec<_> = input
.observations
.iter()
.filter(|observation| observation.id == *id)
.collect();
if observations.len() != 1 {
unresolved.insert(id.clone());
reasons.insert("observation_missing_or_ambiguous".into());
continue;
}
let observation = observations[0];
if observation.binding_sha256 != requirement.binding_sha256 {
unresolved.insert(id.clone());
reasons.insert("observation_binding_mismatch".into());
continue;
}
if !input.now_unix.is_some_and(|now| {
now > 0
&& observation.observed_at > 0
&& observation.observed_at <= now
&& now < observation.expires_at
}) {
unresolved.insert(id.clone());
reasons.insert("observation_not_current".into());
continue;
}
match observation.state {
PreparationEvidenceState::Satisfied => {}
PreparationEvidenceState::Missing
| PreparationEvidenceState::Incompatible
| PreparationEvidenceState::Conflict => {
blocked.insert(id.clone());
}
PreparationEvidenceState::Unprobed | PreparationEvidenceState::Unresolved => {
unresolved.insert(id.clone());
reasons.insert("preparation_not_confirmed".into());
}
}
}
if applicable_count == 0 {
reasons.insert("no_confirmed_applicable_requirement".into());
}
let state = if !blocked.is_empty() {
ReadinessState::Incomplete
} else if !unresolved.is_empty() || !reasons.is_empty() {
ReadinessState::Unknown
} else {
ReadinessState::Ready
};
outcome(state, blocked, unresolved, reasons)
}
fn outcome(
state: ReadinessState,
blocked: BTreeSet<String>,
unresolved: BTreeSet<String>,
reasons: BTreeSet<String>,
) -> ReadinessOutcome {
ReadinessOutcome {
state,
blocked_ids: blocked.into_iter().collect(),
unresolved_ids: unresolved.into_iter().collect(),
reasons: reasons.into_iter().collect(),
}
}
pub(crate) fn valid_id(value: &str) -> bool {
!value.is_empty()
&& value.len() <= 256
&& value.bytes().all(|byte| {
byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'/' | b'.' | b':')
})
}
pub(crate) fn valid_hash(value: &str) -> bool {
value.len() == 64
&& value
.bytes()
.all(|byte| byte.is_ascii_digit() || matches!(byte, b'a'..=b'f'))
&& value.bytes().any(|byte| byte != b'0')
}