1use serde::Serialize;
18use serde_json::{json, Value};
19
20const CORPUS: &[&str] = &[
23 "",
24 "single line",
25 "a\n\n\n\nb \n",
26 "para one\n\npara two\n\n\npara three",
27 "mültibyte ä ö ü 漢字 \n\n end",
28];
29
30#[derive(Debug, Clone, Serialize)]
32pub struct Check {
33 pub name: String,
34 pub category: String,
35 pub passed: bool,
36 pub detail: String,
37}
38
39impl Check {
40 fn pass(category: &str, name: impl Into<String>) -> Self {
41 Self {
42 name: name.into(),
43 category: category.to_string(),
44 passed: true,
45 detail: String::new(),
46 }
47 }
48
49 fn fail(category: &str, name: impl Into<String>, detail: impl Into<String>) -> Self {
50 Self {
51 name: name.into(),
52 category: category.to_string(),
53 passed: false,
54 detail: detail.into(),
55 }
56 }
57
58 fn from_bool(category: &str, name: impl Into<String>, ok: bool, fail_detail: &str) -> Self {
59 if ok {
60 Self::pass(category, name)
61 } else {
62 Self::fail(category, name, fail_detail)
63 }
64 }
65}
66
67#[derive(Debug, Clone, Serialize)]
69pub struct Scorecard {
70 pub version: u32,
71 pub checks: Vec<Check>,
72}
73
74impl Scorecard {
75 #[must_use]
76 pub fn passed(&self) -> usize {
77 self.checks.iter().filter(|c| c.passed).count()
78 }
79
80 #[must_use]
81 pub fn total(&self) -> usize {
82 self.checks.len()
83 }
84
85 #[must_use]
86 pub fn all_passed(&self) -> bool {
87 self.checks.iter().all(|c| c.passed)
88 }
89
90 #[must_use]
91 pub fn failures(&self) -> Vec<&Check> {
92 self.checks.iter().filter(|c| !c.passed).collect()
93 }
94
95 #[must_use]
96 pub fn to_json(&self) -> Value {
97 json!({
98 "version": self.version,
99 "passed": self.passed(),
100 "total": self.total(),
101 "all_passed": self.all_passed(),
102 "checks": self.checks,
103 })
104 }
105}
106
107#[must_use]
109pub fn run() -> Scorecard {
110 let mut checks = Vec::new();
111 checks.extend(contract_checks());
112 checks.extend(reproducibility_checks());
113 checks.extend(extension_checks());
114 checks.extend(accuracy_checks());
115 checks.extend(a2a_checks());
116 Scorecard { version: 1, checks }
117}
118
119const A2A_CARD_REQUIRED: &[&str] = &[
125 "name",
126 "description",
127 "version",
128 "protocolVersion",
129 "capabilities",
130 "skills",
131 "defaultInputModes",
132 "defaultOutputModes",
133 "authentication",
134];
135
136fn a2a_checks() -> Vec<Check> {
137 let mut checks = Vec::new();
138
139 let card = crate::core::a2a::agent_card::build_agent_card("conformance");
140 let missing: Vec<&&str> = A2A_CARD_REQUIRED
141 .iter()
142 .filter(|f| card.get(**f).is_none())
143 .collect();
144 checks.push(Check::from_bool(
145 "a2a",
146 "agent_card_required_fields",
147 missing.is_empty(),
148 &format!("agent card missing fields: {missing:?}"),
149 ));
150
151 checks.push(Check::from_bool(
152 "a2a",
153 "agent_card_deterministic",
154 card == crate::core::a2a::agent_card::build_agent_card("conformance"),
155 "two agent card builds differ",
156 ));
157
158 let skills_ok = card
159 .get("skills")
160 .and_then(serde_json::Value::as_array)
161 .is_some_and(|skills| {
162 !skills.is_empty()
163 && skills.iter().all(|s| {
164 s.get("id").is_some()
165 && s.get("name").is_some()
166 && s.get("description").is_some()
167 })
168 });
169 checks.push(Check::from_bool(
170 "a2a",
171 "agent_card_skills_complete",
172 skills_ok,
173 "skills missing id/name/description",
174 ));
175
176 let bad_version = crate::core::a2a::a2a_compat::handle_a2a_jsonrpc(
178 &crate::core::a2a::a2a_compat::JsonRpcRequest {
179 jsonrpc: "1.0".to_string(),
180 id: serde_json::Value::Number(1.into()),
181 method: "tasks/get".to_string(),
182 params: serde_json::Value::Null,
183 },
184 );
185 checks.push(Check::from_bool(
186 "a2a",
187 "jsonrpc_rejects_bad_version",
188 bad_version.error.as_ref().is_some_and(|e| e.code == -32600),
189 "jsonrpc 1.0 not rejected with -32600",
190 ));
191
192 let unknown_method = crate::core::a2a::a2a_compat::handle_a2a_jsonrpc(
193 &crate::core::a2a::a2a_compat::JsonRpcRequest {
194 jsonrpc: "2.0".to_string(),
195 id: serde_json::Value::Number(2.into()),
196 method: "tasks/nonexistent".to_string(),
197 params: serde_json::Value::Null,
198 },
199 );
200 checks.push(Check::from_bool(
201 "a2a",
202 "jsonrpc_unknown_method_code",
203 unknown_method
204 .error
205 .as_ref()
206 .is_some_and(|e| e.code == -32601),
207 "unknown method not rejected with -32601",
208 ));
209
210 checks
211}
212
213const ACCURACY_FIXTURE: &str = r"use std::collections::HashMap;
225use std::path::PathBuf;
226
227pub struct Inventory {
228 items: HashMap<String, u32>,
229}
230
231pub fn add_item(inv: &mut Inventory, name: &str, qty: u32) {
232 let body_secret_alpha = qty + 1;
233 inv.items.insert(name.to_string(), body_secret_alpha);
234}
235
236pub fn total_count(inv: &Inventory) -> u32 {
237 let body_secret_beta: u32 = inv.items.values().sum();
238 body_secret_beta
239}
240
241fn internal_rebalance(inv: &mut Inventory) {
242 inv.items.retain(|_, qty| *qty > 0);
243}
244";
245
246const MUST_KEEP_SYMBOLS: &[&str] = &["add_item", "total_count", "Inventory"];
248
249const MUST_DROP_BODIES: &[&str] = &["body_secret_alpha", "body_secret_beta"];
251
252fn render_mode(mode: &str) -> String {
253 crate::tools::ctx_read::render::process_mode(
254 ACCURACY_FIXTURE,
255 mode,
256 "",
257 "fixture.rs",
258 "rs",
259 crate::core::tokens::count_tokens(ACCURACY_FIXTURE),
260 crate::tools::CrpMode::Off,
261 "conformance/fixture.rs",
262 None,
263 )
264 .0
265}
266
267fn accuracy_checks() -> Vec<Check> {
268 let mut checks = Vec::new();
269
270 for mode in ["map", "signatures", "aggressive", "entropy"] {
271 checks.push(Check::from_bool(
272 "accuracy",
273 format!("read_mode_deterministic:{mode}"),
274 render_mode(mode) == render_mode(mode),
275 "two renders of the same fixture differ",
276 ));
277 }
278
279 for mode in ["map", "signatures"] {
280 let out = render_mode(mode);
281 let missing: Vec<&&str> = MUST_KEEP_SYMBOLS
282 .iter()
283 .filter(|s| !out.contains(**s))
284 .collect();
285 checks.push(Check::from_bool(
286 "accuracy",
287 format!("read_mode_keeps_symbols:{mode}"),
288 missing.is_empty(),
289 &format!("symbols lost: {missing:?}"),
290 ));
291 let leaked: Vec<&&str> = MUST_DROP_BODIES
292 .iter()
293 .filter(|s| out.contains(**s))
294 .collect();
295 checks.push(Check::from_bool(
296 "accuracy",
297 format!("read_mode_strips_bodies:{mode}"),
298 leaked.is_empty(),
299 &format!("body content leaked: {leaked:?}"),
300 ));
301 }
302
303 let fixture_tokens = crate::core::tokens::count_tokens(ACCURACY_FIXTURE);
304 for mode in ["map", "signatures", "aggressive"] {
305 let sent = crate::core::tokens::count_tokens(&render_mode(mode));
306 checks.push(Check::from_bool(
307 "accuracy",
308 format!("read_mode_compresses:{mode}"),
309 sent < fixture_tokens,
310 &format!("no compression: {sent} >= {fixture_tokens} tokens"),
311 ));
312 }
313
314 {
317 let target = 0.4_f64;
318 let result = crate::core::entropy::entropy_compress_to_density(ACCURACY_FIXTURE, target);
319 let actual = result.compressed_tokens as f64 / fixture_tokens.max(1) as f64;
320 checks.push(Check::from_bool(
321 "accuracy",
322 "density_respects_budget:0.4",
323 actual <= target + 0.10,
324 &format!("density {actual:.2} exceeds target {target:.2} (+0.10 tolerance)"),
325 ));
326 checks.push(Check::from_bool(
327 "accuracy",
328 "density_deterministic:0.4",
329 render_mode("density:0.4") == render_mode("density:0.4"),
330 "two density renders of the same fixture differ",
331 ));
332 }
333
334 checks
335}
336
337fn contract_checks() -> Vec<Check> {
338 let present = !crate::core::contracts::versions_kv().is_empty();
339 vec![Check::from_bool(
340 "contracts",
341 "contract_versions_present",
342 present,
343 "versions_kv() is empty",
344 )]
345}
346
347fn reproducibility_checks() -> Vec<Check> {
348 let caps_stable = crate::core::server_capabilities::capabilities_value()
349 == crate::core::server_capabilities::capabilities_value();
350 let openapi_stable =
351 crate::core::openapi::openapi_value() == crate::core::openapi::openapi_value();
352 vec![
353 Check::from_bool(
354 "reproducibility",
355 "capabilities_deterministic",
356 caps_stable,
357 "capabilities document differs across builds",
358 ),
359 Check::from_bool(
360 "reproducibility",
361 "openapi_deterministic",
362 openapi_stable,
363 "openapi document differs across builds",
364 ),
365 ]
366}
367
368fn extension_checks() -> Vec<Check> {
369 let mut checks = Vec::new();
370 let Ok(reg) = crate::core::extension_registry::global().read() else {
371 checks.push(Check::fail(
372 "extensions",
373 "registry_readable",
374 "extension registry lock poisoned",
375 ));
376 return checks;
377 };
378
379 for name in reg.compressor_names() {
380 if let Some(c) = reg.compressor(&name) {
381 checks.push(compressor_invariants(&name, c.as_ref()));
382 }
383 }
384 for name in reg.chunker_names() {
385 if let Some(c) = reg.chunker(&name) {
386 checks.push(chunker_invariants(&name, c.as_ref()));
387 }
388 }
389 for name in reg.read_mode_names() {
390 if let Some(m) = reg.read_mode(&name) {
391 checks.push(read_mode_invariants(&name, m.as_ref()));
392 }
393 }
394 checks
395}
396
397fn compressor_invariants(name: &str, c: &dyn crate::core::extension_registry::Compressor) -> Check {
398 for input in CORPUS {
399 if c.compress(input, None) != c.compress(input, None) {
401 return Check::fail(
402 "extensions",
403 format!("compressor:{name}"),
404 "non-deterministic",
405 );
406 }
407 let budget = 4;
409 let out = c.compress(input, Some(budget));
410 if out.len() > budget {
411 return Check::fail(
412 "extensions",
413 format!("compressor:{name}"),
414 format!("exceeded byte budget: {} > {budget}", out.len()),
415 );
416 }
417 }
418 Check::pass("extensions", format!("compressor:{name}"))
419}
420
421fn chunker_invariants(name: &str, c: &dyn crate::core::extension_registry::Chunker) -> Check {
422 if !c.chunk("").is_empty() {
424 return Check::fail(
425 "extensions",
426 format!("chunker:{name}"),
427 "empty input produced chunks",
428 );
429 }
430 for input in CORPUS.iter().filter(|s| !s.trim().is_empty()) {
431 if c.chunk(input) != c.chunk(input) {
433 return Check::fail("extensions", format!("chunker:{name}"), "non-deterministic");
434 }
435 let chunks = c.chunk(input);
436 if chunks.is_empty() {
438 return Check::fail(
439 "extensions",
440 format!("chunker:{name}"),
441 "non-empty input produced no chunks",
442 );
443 }
444 if chunks.iter().any(|c| c.trim().is_empty()) {
445 return Check::fail(
446 "extensions",
447 format!("chunker:{name}"),
448 "produced an empty chunk",
449 );
450 }
451 }
452 Check::pass("extensions", format!("chunker:{name}"))
453}
454
455fn read_mode_invariants(name: &str, m: &dyn crate::core::extension_registry::ReadMode) -> Check {
456 for input in CORPUS {
457 if m.render(input, "x.txt") != m.render(input, "x.txt") {
458 return Check::fail(
459 "extensions",
460 format!("read_mode:{name}"),
461 "non-deterministic",
462 );
463 }
464 }
465 if name == "full" {
467 let sample = "verbatim\nsource\n漢字";
468 if m.render(sample, "x.txt") != sample {
469 return Check::fail("extensions", "read_mode:full", "full mode altered source");
470 }
471 }
472 Check::pass("extensions", format!("read_mode:{name}"))
473}
474
475#[cfg(test)]
476mod tests {
477 use super::*;
478
479 #[test]
480 fn builtin_suite_passes() {
481 let card = run();
482 assert!(
483 card.all_passed(),
484 "conformance failures: {:?}",
485 card.failures()
486 );
487 assert!(card.total() >= 6, "expected a meaningful number of checks");
488 }
489
490 #[test]
491 fn scorecard_json_shape() {
492 let v = run().to_json();
493 assert_eq!(v["version"], 1);
494 assert!(v["checks"].is_array());
495 let passed = v["passed"].as_u64().expect("passed is a number");
499 let total = v["total"].as_u64().expect("total is a number");
500 assert!(passed <= total);
501 assert_eq!(v["checks"].as_array().map(|c| c.len() as u64), Some(total));
502 }
503
504 #[test]
505 fn detects_a_nondeterministic_compressor() {
506 use std::sync::atomic::{AtomicU64, Ordering};
507 struct Flaky(AtomicU64);
508 impl crate::core::extension_registry::Compressor for Flaky {
509 #[allow(clippy::unnecessary_literal_bound)]
510 fn name(&self) -> &str {
511 "flaky"
512 }
513 fn compress(&self, _input: &str, _budget: Option<usize>) -> String {
514 self.0.fetch_add(1, Ordering::SeqCst).to_string()
515 }
516 }
517 let check = compressor_invariants("flaky", &Flaky(AtomicU64::new(0)));
518 assert!(!check.passed);
519 assert!(check.detail.contains("non-deterministic"));
520 }
521}