dkp_core/validate/
gate8.rs1use crate::{
2 okf::parser::parse_okf_dir,
3 pack::loader::Pack,
4 validate::gates::{CheckResult, GateResult, GateStatus},
5};
6
7pub fn run(pack: &Pack) -> GateResult {
9 if !pack.has_okf() {
10 return GateResult {
11 gate: 8,
12 status: GateStatus::NotApplicable,
13 checks: vec![CheckResult::skip("okf/ layer (not present)")],
14 message: Some("OKF layer absent; gate 8 not applicable".to_string()),
15 };
16 }
17
18 let mut checks = Vec::new();
19
20 match parse_okf_dir(&pack.okf_dir()) {
22 Ok(concepts) => {
23 let n = concepts.len();
24 checks.push(CheckResult::pass(format!("OKF concept files: {n} parsed")));
25
26 let missing_type: Vec<String> = concepts
28 .iter()
29 .filter(|c| c.frontmatter.get("type").is_none())
30 .map(|c| {
31 c.path
32 .file_name()
33 .unwrap_or_default()
34 .to_string_lossy()
35 .into_owned()
36 })
37 .collect();
38
39 if missing_type.is_empty() {
40 checks.push(CheckResult::pass(format!(
41 "OKF frontmatter: all {n} files have 'type' field"
42 )));
43 } else {
44 checks.push(CheckResult::fail(
45 "OKF frontmatter: 'type' field required",
46 format!("missing 'type': {}", missing_type.join(", ")),
47 ));
48 }
49 }
50 Err(e) => {
51 checks.push(CheckResult::fail("OKF concept files parse", e.to_string()));
52 }
53 }
54
55 let bundle_sig = pack.okf_dir().join("bundle.sig");
57 if bundle_sig.exists() {
58 checks.push(CheckResult::pass("bundle.sig present"));
59 } else {
60 checks.push(CheckResult::skip("bundle.sig (not present)"));
61 }
62
63 let failed = checks.iter().any(|c| c.status == GateStatus::Fail);
64 GateResult {
65 gate: 8,
66 status: if failed {
67 GateStatus::Fail
68 } else {
69 GateStatus::Pass
70 },
71 checks,
72 message: None,
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79 use tempfile::TempDir;
80
81 fn minimal_manifest_json() -> &'static str {
82 r#"{
83 "spec": "1.0.0",
84 "name": "test-pack",
85 "version": "1.0.0",
86 "domain": "testing",
87 "audience": "internal",
88 "intended_use": "unit tests",
89 "known_limitations": "none",
90 "update_date": "2026-01-01"
91 }"#
92 }
93
94 fn open_pack(tmp: &TempDir) -> Pack {
95 std::fs::write(tmp.path().join("manifest.json"), minimal_manifest_json()).unwrap();
96 Pack::open(tmp.path()).unwrap()
97 }
98
99 #[test]
100 fn okf_absent_is_not_applicable() {
101 let tmp = TempDir::new().unwrap();
102 let pack = open_pack(&tmp);
103 let result = run(&pack);
104 assert_eq!(result.status, GateStatus::NotApplicable);
105 assert_eq!(result.gate, 8);
106 }
107
108 #[test]
109 fn okf_valid_frontmatter_passes() {
110 let tmp = TempDir::new().unwrap();
111 let pack = open_pack(&tmp);
112 std::fs::create_dir_all(pack.okf_dir()).unwrap();
113 std::fs::write(
114 pack.okf_dir().join("concept.md"),
115 "---\ntype: concept\n---\nBody text.\n",
116 )
117 .unwrap();
118
119 let result = run(&pack);
120 assert_eq!(result.status, GateStatus::Pass);
121 }
122
123 #[test]
124 fn okf_missing_type_field_fails() {
125 let tmp = TempDir::new().unwrap();
126 let pack = open_pack(&tmp);
127 std::fs::create_dir_all(pack.okf_dir()).unwrap();
128 std::fs::write(
129 pack.okf_dir().join("concept.md"),
130 "---\nname: concept\n---\nBody text.\n",
131 )
132 .unwrap();
133
134 let result = run(&pack);
135 assert_eq!(result.status, GateStatus::Fail);
136 }
137
138 #[test]
139 fn okf_unparseable_file_fails() {
140 let tmp = TempDir::new().unwrap();
141 let pack = open_pack(&tmp);
142 std::fs::create_dir_all(pack.okf_dir()).unwrap();
143 std::fs::write(pack.okf_dir().join("concept.md"), "no frontmatter here").unwrap();
144
145 let result = run(&pack);
146 assert_eq!(result.status, GateStatus::Fail);
147 }
148
149 #[test]
150 fn bundle_sig_absent_is_skipped_not_failed() {
151 let tmp = TempDir::new().unwrap();
152 let pack = open_pack(&tmp);
153 std::fs::create_dir_all(pack.okf_dir()).unwrap();
154 std::fs::write(
155 pack.okf_dir().join("concept.md"),
156 "---\ntype: concept\n---\nBody.\n",
157 )
158 .unwrap();
159
160 let result = run(&pack);
161 assert_eq!(result.status, GateStatus::Pass);
162 assert!(result
163 .checks
164 .iter()
165 .any(|c| c.description.contains("bundle.sig") && c.status == GateStatus::Skipped));
166 }
167
168 #[test]
169 fn bundle_sig_present_passes_that_check() {
170 let tmp = TempDir::new().unwrap();
171 let pack = open_pack(&tmp);
172 std::fs::create_dir_all(pack.okf_dir()).unwrap();
173 std::fs::write(
174 pack.okf_dir().join("concept.md"),
175 "---\ntype: concept\n---\nBody.\n",
176 )
177 .unwrap();
178 std::fs::write(pack.okf_dir().join("bundle.sig"), "signature-bytes").unwrap();
179
180 let result = run(&pack);
181 assert_eq!(result.status, GateStatus::Pass);
182 assert!(result
183 .checks
184 .iter()
185 .any(|c| c.description == "bundle.sig present" && c.status == GateStatus::Pass));
186 }
187}