Skip to main content

lean_ctx/core/
conformance.rs

1//! Conformance & reproducibility scorecard (`conformance-v1`, EPIC 12.17).
2//!
3//! A self-check any user or CI can run to prove this instance honors its own
4//! contracts and that its extension surface behaves. It exercises three areas:
5//!
6//! * **contracts** — every machine-verified contract version is present.
7//! * **reproducibility** — the public discovery documents (`/v1/capabilities`,
8//!   `/v1/openapi.json`) are deterministic (same bytes across two builds).
9//! * **extensions** — every registered compressor / chunker / read-mode in the
10//!   [`extension_registry`](super::extension_registry) satisfies the stable
11//!   invariants the engine relies on (determinism, budget honoring, coverage).
12//!
13//! The output is a [`Scorecard`]: a flat list of [`Check`]s plus a pass count.
14//! It is data, not prose, so it can be rendered (CLI), shared (JSON), or gated
15//! on (`all_passed()` in `tests/conformance_suite.rs`).
16
17use std::sync::OnceLock;
18
19use serde::Serialize;
20use serde_json::{Value, json};
21
22/// A representative corpus the extension invariants run against. Mixes blank
23/// lines, multibyte UTF-8, and paragraph boundaries to stress edge cases.
24const CORPUS: &[&str] = &[
25    "",
26    "single line",
27    "a\n\n\n\nb  \n",
28    "para one\n\npara two\n\n\npara three",
29    "mültibyte ä ö ü 漢字 \n\n end",
30];
31
32/// One conformance check result.
33#[derive(Debug, Clone, Serialize)]
34pub struct Check {
35    pub name: String,
36    pub category: String,
37    pub passed: bool,
38    pub detail: String,
39}
40
41impl Check {
42    fn pass(category: &str, name: impl Into<String>) -> Self {
43        Self {
44            name: name.into(),
45            category: category.to_string(),
46            passed: true,
47            detail: String::new(),
48        }
49    }
50
51    fn fail(category: &str, name: impl Into<String>, detail: impl Into<String>) -> Self {
52        Self {
53            name: name.into(),
54            category: category.to_string(),
55            passed: false,
56            detail: detail.into(),
57        }
58    }
59
60    fn from_bool(category: &str, name: impl Into<String>, ok: bool, fail_detail: &str) -> Self {
61        if ok {
62            Self::pass(category, name)
63        } else {
64            Self::fail(category, name, fail_detail)
65        }
66    }
67}
68
69/// The full result of a conformance run.
70#[derive(Debug, Clone, Serialize)]
71pub struct Scorecard {
72    pub version: u32,
73    pub checks: Vec<Check>,
74}
75
76impl Scorecard {
77    #[must_use]
78    pub fn passed(&self) -> usize {
79        self.checks.iter().filter(|c| c.passed).count()
80    }
81
82    #[must_use]
83    pub fn total(&self) -> usize {
84        self.checks.len()
85    }
86
87    #[must_use]
88    pub fn all_passed(&self) -> bool {
89        self.checks.iter().all(|c| c.passed)
90    }
91
92    #[must_use]
93    pub fn failures(&self) -> Vec<&Check> {
94        self.checks.iter().filter(|c| !c.passed).collect()
95    }
96
97    #[must_use]
98    pub fn to_json(&self) -> Value {
99        json!({
100            "version": self.version,
101            "passed": self.passed(),
102            "total": self.total(),
103            "all_passed": self.all_passed(),
104            "checks": self.checks,
105        })
106    }
107}
108
109/// Run the full conformance suite against this instance.
110#[must_use]
111pub fn run() -> Scorecard {
112    let mut checks = Vec::new();
113    checks.extend(contract_checks());
114    checks.extend(reproducibility_checks());
115    checks.extend(extension_checks());
116    checks.extend(accuracy_checks());
117    checks.extend(a2a_checks());
118    Scorecard { version: 1, checks }
119}
120
121// ---------------------------------------------------------------------------
122// A2A: agent card and JSON-RPC contract conformance (GL#449).
123// ---------------------------------------------------------------------------
124
125/// Fields the A2A spec requires on a published agent card.
126const A2A_CARD_REQUIRED: &[&str] = &[
127    "name",
128    "description",
129    "version",
130    "protocolVersion",
131    "capabilities",
132    "skills",
133    "defaultInputModes",
134    "defaultOutputModes",
135    "authentication",
136];
137
138fn a2a_checks() -> Vec<Check> {
139    let mut checks = Vec::new();
140
141    let card = crate::core::a2a::agent_card::build_agent_card("conformance", true);
142    let missing: Vec<&&str> = A2A_CARD_REQUIRED
143        .iter()
144        .filter(|f| card.get(**f).is_none())
145        .collect();
146    checks.push(Check::from_bool(
147        "a2a",
148        "agent_card_required_fields",
149        missing.is_empty(),
150        &format!("agent card missing fields: {missing:?}"),
151    ));
152
153    checks.push(Check::from_bool(
154        "a2a",
155        "agent_card_deterministic",
156        card == crate::core::a2a::agent_card::build_agent_card("conformance", true),
157        "two agent card builds differ",
158    ));
159
160    let skills_ok = card
161        .get("skills")
162        .and_then(serde_json::Value::as_array)
163        .is_some_and(|skills| {
164            !skills.is_empty()
165                && skills.iter().all(|s| {
166                    s.get("id").is_some()
167                        && s.get("name").is_some()
168                        && s.get("description").is_some()
169                })
170        });
171    checks.push(Check::from_bool(
172        "a2a",
173        "agent_card_skills_complete",
174        skills_ok,
175        "skills missing id/name/description",
176    ));
177
178    // JSON-RPC error contract: wrong version → -32600, unknown method → -32601.
179    let bad_version = crate::core::a2a::a2a_compat::handle_a2a_jsonrpc(
180        &crate::core::a2a::a2a_compat::JsonRpcRequest {
181            jsonrpc: "1.0".to_string(),
182            id: serde_json::Value::Number(1.into()),
183            method: "tasks/get".to_string(),
184            params: serde_json::Value::Null,
185        },
186    );
187    checks.push(Check::from_bool(
188        "a2a",
189        "jsonrpc_rejects_bad_version",
190        bad_version.error.as_ref().is_some_and(|e| e.code == -32600),
191        "jsonrpc 1.0 not rejected with -32600",
192    ));
193
194    let unknown_method = crate::core::a2a::a2a_compat::handle_a2a_jsonrpc(
195        &crate::core::a2a::a2a_compat::JsonRpcRequest {
196            jsonrpc: "2.0".to_string(),
197            id: serde_json::Value::Number(2.into()),
198            method: "tasks/nonexistent".to_string(),
199            params: serde_json::Value::Null,
200        },
201    );
202    checks.push(Check::from_bool(
203        "a2a",
204        "jsonrpc_unknown_method_code",
205        unknown_method
206            .error
207            .as_ref()
208            .is_some_and(|e| e.code == -32601),
209        "unknown method not rejected with -32601",
210    ));
211
212    checks
213}
214
215// ---------------------------------------------------------------------------
216// Accuracy: structural invariants of the lossy read modes (GL#441).
217//
218// Byte-golden snapshots would break on every intentional format improvement;
219// these checks instead pin down what each mode must *preserve* (symbols,
220// deps) and must *drop* (bodies), plus determinism and size bounds — the
221// properties an agent's correctness actually depends on.
222// ---------------------------------------------------------------------------
223
224/// A stable Rust fixture exercising pub fns, a struct, imports, and a body
225/// secret that lossy modes must strip.
226const ACCURACY_FIXTURE: &str = r"use std::collections::HashMap;
227use std::path::PathBuf;
228
229pub struct Inventory {
230    items: HashMap<String, u32>,
231}
232
233pub fn add_item(inv: &mut Inventory, name: &str, qty: u32) {
234    let body_secret_alpha = qty + 1;
235    inv.items.insert(name.to_string(), body_secret_alpha);
236}
237
238pub fn total_count(inv: &Inventory) -> u32 {
239    let body_secret_beta: u32 = inv.items.values().sum();
240    body_secret_beta
241}
242
243fn internal_rebalance(inv: &mut Inventory) {
244    inv.items.retain(|_, qty| *qty > 0);
245}
246";
247
248/// Symbols every lossy structural mode must keep visible.
249const MUST_KEEP_SYMBOLS: &[&str] = &["add_item", "total_count", "Inventory"];
250
251/// Body-local identifiers `signatures`/`map` must strip.
252const MUST_DROP_BODIES: &[&str] = &["body_secret_alpha", "body_secret_beta"];
253
254/// The `ctx_read` mode renderer the accuracy checks measure: the signature of
255/// `tools::ctx_read::process_mode` (content, mode, file ref, short name,
256/// extension, original tokens, CRP mode, file path, task) returning the
257/// rendered view and its pre-footer body token count.
258pub type ReadModeRenderer = fn(
259    &str,
260    &str,
261    &str,
262    &str,
263    &str,
264    usize,
265    crate::core::protocol::CrpMode,
266    &str,
267    Option<&str>,
268) -> (String, usize);
269
270static READ_MODE_RENDERER: OnceLock<ReadModeRenderer> = OnceLock::new();
271
272/// Register the `ctx_read` renderer the accuracy checks run against. The read
273/// tools live above the engine; the CLI registers them at start-up
274/// (`tools::ctx_read::register_conformance_renderer`). The first registration
275/// wins.
276///
277/// Unregistered, the scorecard cannot verify the read modes: it then carries a
278/// failing `accuracy` check instead of skipping them, so a run without the
279/// renderer never reports `all_passed()`.
280pub fn register_read_mode_renderer(renderer: ReadModeRenderer) {
281    let _ = READ_MODE_RENDERER.set(renderer);
282}
283
284fn render_mode(render: ReadModeRenderer, mode: &str) -> String {
285    render_mode_full(render, mode).0
286}
287
288/// Rendered view plus its pre-footer body token count. The compression-size
289/// invariant measures `.1` (the body), since the reactive recovery footer and the
290/// savings line are orthogonal affordances appended after compression, not part of
291/// the compressed content.
292fn render_mode_full(render: ReadModeRenderer, mode: &str) -> (String, usize) {
293    render(
294        ACCURACY_FIXTURE,
295        mode,
296        "",
297        "fixture.rs",
298        "rs",
299        crate::core::tokens::count_tokens(ACCURACY_FIXTURE),
300        crate::core::protocol::CrpMode::Off,
301        "conformance/fixture.rs",
302        None,
303    )
304}
305
306fn accuracy_checks() -> Vec<Check> {
307    match READ_MODE_RENDERER.get() {
308        Some(render) => accuracy_checks_with(*render),
309        None => vec![unregistered_renderer_check()],
310    }
311}
312
313fn unregistered_renderer_check() -> Check {
314    Check::fail(
315        "accuracy",
316        "read_mode_renderer_registered",
317        "the ctx_read renderer is not registered; read-mode accuracy is unverified",
318    )
319}
320
321fn accuracy_checks_with(render: ReadModeRenderer) -> Vec<Check> {
322    let render_mode = |mode: &str| render_mode(render, mode);
323    let render_mode_full = |mode: &str| render_mode_full(render, mode);
324    let mut checks = Vec::new();
325
326    for mode in ["map", "signatures", "aggressive", "entropy"] {
327        checks.push(Check::from_bool(
328            "accuracy",
329            format!("read_mode_deterministic:{mode}"),
330            render_mode(mode) == render_mode(mode),
331            "two renders of the same fixture differ",
332        ));
333    }
334
335    for mode in ["map", "signatures"] {
336        let out = render_mode(mode);
337        let missing: Vec<&&str> = MUST_KEEP_SYMBOLS
338            .iter()
339            .filter(|s| !out.contains(**s))
340            .collect();
341        checks.push(Check::from_bool(
342            "accuracy",
343            format!("read_mode_keeps_symbols:{mode}"),
344            missing.is_empty(),
345            &format!("symbols lost: {missing:?}"),
346        ));
347        let leaked: Vec<&&str> = MUST_DROP_BODIES
348            .iter()
349            .filter(|s| out.contains(**s))
350            .collect();
351        checks.push(Check::from_bool(
352            "accuracy",
353            format!("read_mode_strips_bodies:{mode}"),
354            leaked.is_empty(),
355            &format!("body content leaked: {leaked:?}"),
356        ));
357    }
358
359    let fixture_tokens = crate::core::tokens::count_tokens(ACCURACY_FIXTURE);
360    for mode in ["map", "signatures", "aggressive"] {
361        let sent = render_mode_full(mode).1;
362        checks.push(Check::from_bool(
363            "accuracy",
364            format!("read_mode_compresses:{mode}"),
365            sent < fixture_tokens,
366            &format!("no compression: {sent} >= {fixture_tokens} tokens"),
367        ));
368    }
369
370    // Target-density mode (GL#444): the body (excluding header/savings lines)
371    // must stay within the token budget, and the render must be deterministic.
372    {
373        let target = 0.4_f64;
374        let result = crate::core::entropy::entropy_compress_to_density(ACCURACY_FIXTURE, target);
375        let actual = result.compressed_tokens as f64 / fixture_tokens.max(1) as f64;
376        checks.push(Check::from_bool(
377            "accuracy",
378            "density_respects_budget:0.4",
379            actual <= target + 0.10,
380            &format!("density {actual:.2} exceeds target {target:.2} (+0.10 tolerance)"),
381        ));
382        checks.push(Check::from_bool(
383            "accuracy",
384            "density_deterministic:0.4",
385            render_mode("density:0.4") == render_mode("density:0.4"),
386            "two density renders of the same fixture differ",
387        ));
388    }
389
390    checks
391}
392
393fn contract_checks() -> Vec<Check> {
394    let present = !crate::core::contracts::versions_kv().is_empty();
395    vec![Check::from_bool(
396        "contracts",
397        "contract_versions_present",
398        present,
399        "versions_kv() is empty",
400    )]
401}
402
403fn reproducibility_checks() -> Vec<Check> {
404    let caps_stable = crate::core::server_capabilities::capabilities_value()
405        == crate::core::server_capabilities::capabilities_value();
406    let openapi_stable =
407        crate::core::openapi::openapi_value() == crate::core::openapi::openapi_value();
408    vec![
409        Check::from_bool(
410            "reproducibility",
411            "capabilities_deterministic",
412            caps_stable,
413            "capabilities document differs across builds",
414        ),
415        Check::from_bool(
416            "reproducibility",
417            "openapi_deterministic",
418            openapi_stable,
419            "openapi document differs across builds",
420        ),
421    ]
422}
423
424fn extension_checks() -> Vec<Check> {
425    let mut checks = Vec::new();
426    let Ok(reg) = crate::core::extension_registry::global().read() else {
427        checks.push(Check::fail(
428            "extensions",
429            "registry_readable",
430            "extension registry lock poisoned",
431        ));
432        return checks;
433    };
434
435    for name in reg.compressor_names() {
436        if let Some(c) = reg.compressor(&name) {
437            checks.push(compressor_invariants(&name, c.as_ref()));
438        }
439    }
440    for name in reg.chunker_names() {
441        if let Some(c) = reg.chunker(&name) {
442            checks.push(chunker_invariants(&name, c.as_ref()));
443        }
444    }
445    for name in reg.read_mode_names() {
446        if let Some(m) = reg.read_mode(&name) {
447            checks.push(read_mode_invariants(&name, m.as_ref()));
448        }
449    }
450    checks
451}
452
453fn compressor_invariants(name: &str, c: &dyn crate::core::extension_registry::Compressor) -> Check {
454    for input in CORPUS {
455        // Determinism.
456        if c.compress(input, None) != c.compress(input, None) {
457            return Check::fail(
458                "extensions",
459                format!("compressor:{name}"),
460                "non-deterministic",
461            );
462        }
463        // Budget is a hard byte ceiling, never split mid-char (valid UTF-8).
464        let budget = 4;
465        let out = c.compress(input, Some(budget));
466        if out.len() > budget {
467            return Check::fail(
468                "extensions",
469                format!("compressor:{name}"),
470                format!("exceeded byte budget: {} > {budget}", out.len()),
471            );
472        }
473    }
474    Check::pass("extensions", format!("compressor:{name}"))
475}
476
477fn chunker_invariants(name: &str, c: &dyn crate::core::extension_registry::Chunker) -> Check {
478    // Empty input ⇒ no chunks.
479    if !c.chunk("").is_empty() {
480        return Check::fail(
481            "extensions",
482            format!("chunker:{name}"),
483            "empty input produced chunks",
484        );
485    }
486    for input in CORPUS.iter().filter(|s| !s.trim().is_empty()) {
487        // Determinism.
488        if c.chunk(input) != c.chunk(input) {
489            return Check::fail("extensions", format!("chunker:{name}"), "non-deterministic");
490        }
491        let chunks = c.chunk(input);
492        // Non-empty input ⇒ at least one chunk, none empty after trim.
493        if chunks.is_empty() {
494            return Check::fail(
495                "extensions",
496                format!("chunker:{name}"),
497                "non-empty input produced no chunks",
498            );
499        }
500        if chunks.iter().any(|c| c.trim().is_empty()) {
501            return Check::fail(
502                "extensions",
503                format!("chunker:{name}"),
504                "produced an empty chunk",
505            );
506        }
507    }
508    Check::pass("extensions", format!("chunker:{name}"))
509}
510
511fn read_mode_invariants(name: &str, m: &dyn crate::core::extension_registry::ReadMode) -> Check {
512    for input in CORPUS {
513        if m.render(input, "x.txt") != m.render(input, "x.txt") {
514            return Check::fail(
515                "extensions",
516                format!("read_mode:{name}"),
517                "non-deterministic",
518            );
519        }
520    }
521    // The byte-faithful `full` mode must round-trip source verbatim.
522    if name == "full" {
523        let sample = "verbatim\nsource\n漢字";
524        if m.render(sample, "x.txt") != sample {
525            return Check::fail("extensions", "read_mode:full", "full mode altered source");
526        }
527    }
528    Check::pass("extensions", format!("read_mode:{name}"))
529}
530
531#[cfg(test)]
532mod tests {
533    use super::*;
534
535    /// Everything but the read-mode accuracy checks; the full suite with the
536    /// registered `ctx_read` renderer runs in `tools::ctx_read`.
537    #[test]
538    fn builtin_engine_checks_pass() {
539        let mut checks = Vec::new();
540        checks.extend(contract_checks());
541        checks.extend(reproducibility_checks());
542        checks.extend(extension_checks());
543        checks.extend(a2a_checks());
544        let card = Scorecard { version: 1, checks };
545        assert!(
546            card.all_passed(),
547            "conformance failures: {:?}",
548            card.failures()
549        );
550        assert!(card.total() >= 6, "expected a meaningful number of checks");
551    }
552
553    /// Without the renderer the read modes are unverified: the scorecard
554    /// carries a failing check instead of skipping them.
555    #[test]
556    fn an_unregistered_renderer_fails_the_scorecard() {
557        let check = unregistered_renderer_check();
558        assert!(!check.passed);
559        assert_eq!(check.category, "accuracy");
560        let card = Scorecard {
561            version: 1,
562            checks: vec![check],
563        };
564        assert!(!card.all_passed());
565    }
566
567    #[test]
568    fn scorecard_json_shape() {
569        let v = run().to_json();
570        assert_eq!(v["version"], 1);
571        assert!(v["checks"].is_array());
572        // Shape only: `passed == total` is covered by builtin_suite_passes.
573        // Asserting it here races with tests that register an intentionally
574        // broken compressor in the global extension registry.
575        let passed = v["passed"].as_u64().expect("passed is a number");
576        let total = v["total"].as_u64().expect("total is a number");
577        assert!(passed <= total);
578        assert_eq!(v["checks"].as_array().map(|c| c.len() as u64), Some(total));
579    }
580
581    #[test]
582    fn detects_a_nondeterministic_compressor() {
583        use std::sync::atomic::{AtomicU64, Ordering};
584        struct Flaky(AtomicU64);
585        impl crate::core::extension_registry::Compressor for Flaky {
586            #[allow(clippy::unnecessary_literal_bound)]
587            fn name(&self) -> &str {
588                "flaky"
589            }
590            fn compress(&self, _input: &str, _budget: Option<usize>) -> String {
591                self.0.fetch_add(1, Ordering::SeqCst).to_string()
592            }
593        }
594        let check = compressor_invariants("flaky", &Flaky(AtomicU64::new(0)));
595        assert!(!check.passed);
596        assert!(check.detail.contains("non-deterministic"));
597    }
598}
599
600// --- OCLA Capability Conformance ---
601
602use crate::core::ocla::invocation::CapabilityResult;
603use lean_ctx_protocol::CapabilityManifestV1;
604
605/// Result of checking a capability manifest or invocation against contract rules.
606#[derive(Debug, Clone, PartialEq, Eq, Serialize, serde::Deserialize)]
607pub struct ConformanceResult {
608    pub capability_id: String,
609    pub version: String,
610    pub checks_passed: u32,
611    pub checks_failed: u32,
612    pub failures: Vec<ConformanceFailure>,
613}
614
615#[derive(Debug, Clone, PartialEq, Eq, Serialize, serde::Deserialize)]
616pub struct ConformanceFailure {
617    pub check: String,
618    pub expected: String,
619    pub actual: String,
620}
621
622/// Validate a CapabilityManifestV1 against OCLA contract rules.
623pub fn check_manifest_conformance(manifest: &CapabilityManifestV1) -> ConformanceResult {
624    let mut report = ConformanceResult {
625        capability_id: manifest.capability_id.as_str().to_owned(),
626        version: manifest.version.clone(),
627        checks_passed: 0,
628        checks_failed: 0,
629        failures: Vec::new(),
630    };
631    let validation = crate::core::ocla::capability_fabric::normalize_manifest(manifest.clone());
632    record_capability_check(
633        &mut report,
634        "manifest_contract",
635        validation.is_ok(),
636        "valid canonical capability contract",
637        validation
638            .err()
639            .map(|error| error.to_string())
640            .unwrap_or_default(),
641    );
642    report
643}
644
645fn record_capability_check(
646    report: &mut ConformanceResult,
647    check: &str,
648    passed: bool,
649    expected: &str,
650    actual: String,
651) {
652    if passed {
653        report.checks_passed += 1;
654    } else {
655        report.checks_failed += 1;
656        report.failures.push(ConformanceFailure {
657            check: check.to_owned(),
658            expected: expected.to_owned(),
659            actual,
660        });
661    }
662}
663
664/// Validate a CapabilityResult against the manifest's declared constraints.
665pub fn check_invocation_conformance(
666    manifest: &CapabilityManifestV1,
667    result: &CapabilityResult,
668) -> ConformanceResult {
669    let mut report = check_manifest_conformance(manifest);
670    let observation = &result.observation;
671    record_capability_check(
672        &mut report,
673        "observation_identity",
674        observation.schema_version
675            == crate::core::ocla::invocation::CAPABILITY_OBSERVATION_SCHEMA_VERSION
676            && !observation.task_id.trim().is_empty()
677            && observation.capability_id == manifest.capability_id.as_str()
678            && observation.capability_version == manifest.version,
679        "versioned observation bound to task and capability",
680        "observation identity mismatch".into(),
681    );
682    record_capability_check(
683        &mut report,
684        "observation_consistency",
685        observation.success == result.success
686            && observation.output_tokens == result.output_tokens
687            && observation.latency_ms == result.latency_ms,
688        "result and observation agree",
689        "contradictory result measurements".into(),
690    );
691    record_capability_check(
692        &mut report,
693        "failure_classification",
694        // Partial is not completed success. FallbackToNative names the route;
695        // the final native invocation can either succeed or fail.
696        matches!(
697            (result.success, observation.failure_mode),
698            (
699                true,
700                None | Some(crate::core::ocla::invocation::CapabilityFailureMode::FallbackToNative)
701            ) | (false, Some(_))
702        ),
703        "success and typed terminal state agree",
704        "contradictory success/failure classification".into(),
705    );
706
707    // A declared hard bound is a conformance check, not a hidden warning.
708    if let Some(bounds) = manifest.extra.get("latency_bounds_ms") {
709        let max_ms = bounds.get("max_ms").and_then(serde_json::Value::as_u64);
710        record_capability_check(
711            &mut report,
712            "latency_within_bounds",
713            max_ms.is_some_and(|max_ms| result.latency_ms <= max_ms),
714            "latency within declared unsigned max_ms",
715            format!("{} ms; bounds={bounds}", result.latency_ms),
716        );
717    }
718    report
719}
720
721#[cfg(test)]
722mod capability_conformance_tests {
723    use super::*;
724    use crate::core::ocla::adapters::PassthroughAdapter;
725    use crate::core::ocla::invocation::{
726        CapabilityAdapter, CapabilityInput, CapabilityInvocation, PolicyConstraints,
727    };
728
729    #[test]
730    fn terminal_failure_truth_table_is_enforced() {
731        use crate::core::ocla::invocation::CapabilityFailureMode::*;
732        let fixture: Value = serde_json::from_str(include_str!(concat!(
733            env!("CARGO_MANIFEST_DIR"),
734            "/../tests/ocla_contract_suite/v1/conformance/invocation_success.json"
735        )))
736        .unwrap();
737        let manifest: CapabilityManifestV1 =
738            serde_json::from_value(fixture["manifest"].clone()).unwrap();
739        let baseline: CapabilityResult = serde_json::from_value(fixture["result"].clone()).unwrap();
740        for (success, mode, valid) in [
741            (true, None, true),
742            (false, None, false),
743            (true, Some(Timeout), false),
744            (false, Some(Timeout), true),
745            (true, Some(Unavailable), false),
746            (false, Some(Unavailable), true),
747            (true, Some(RejectedByPolicy), false),
748            (false, Some(RejectedByPolicy), true),
749            (true, Some(InvalidOutput), false),
750            (false, Some(InvalidOutput), true),
751            (true, Some(Internal), false),
752            (false, Some(Internal), true),
753            (true, Some(Partial), false),
754            (false, Some(Partial), true),
755            (true, Some(FallbackToNative), true),
756            (false, Some(FallbackToNative), true),
757        ] {
758            let mut result = baseline.clone();
759            result.success = success;
760            result.observation.success = success;
761            result.observation.failure_mode = mode;
762            let report = check_invocation_conformance(&manifest, &result);
763            assert_eq!(
764                report.checks_failed == 0,
765                valid,
766                "success={success}, mode={mode:?}"
767            );
768        }
769    }
770
771    #[test]
772    fn invalid_contracts_never_receive_placeholder_passes() {
773        let mut manifest = PassthroughAdapter::new().manifest().clone();
774        manifest.schema_version = 0;
775        let report = check_manifest_conformance(&manifest);
776        assert_eq!(report.checks_passed, 0);
777        assert_eq!(report.checks_failed, 1);
778        assert_eq!(report.failures.len(), 1);
779    }
780
781    #[test]
782    fn real_adapter_result_passes_but_contradictory_measurements_fail() {
783        let adapter = PassthroughAdapter::new();
784        let invocation = CapabilityInvocation {
785            task_id: "conformance-task".into(),
786            capability_id: adapter.manifest().capability_id.as_str().into(),
787            capability_version: adapter.manifest().version.clone(),
788            input: CapabilityInput::ModelRequest {
789                prompt: "test input".into(),
790                model: None,
791            },
792            policy_constraints: PolicyConstraints::default(),
793            timeout_ms: 0,
794        };
795        let mut result = adapter.invoke(invocation).unwrap();
796        let report = check_invocation_conformance(adapter.manifest(), &result);
797        assert_eq!(report.checks_failed, 0);
798        assert!(report.checks_passed >= 4);
799        result.output_tokens += 1;
800        let report = check_invocation_conformance(adapter.manifest(), &result);
801        assert_eq!(report.checks_failed, 1);
802        assert_eq!(report.failures[0].check, "observation_consistency");
803    }
804}