Skip to main content

detcore_model/
backend_engagement.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9//! Machine-readable evidence that the selected backend performed its own work.
10
11use serde::Deserialize;
12use serde::Serialize;
13
14/// The backend-specific value used by compatibility-envelope scoring.
15///
16/// The variants keep each number attached to what it counts. A bare numeric
17/// field would allow a scheduler-turn count to be consumed as a mapped-site or
18/// branch count while remaining perfectly well formed.
19#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
20#[serde(tag = "backend", rename_all = "lowercase", deny_unknown_fields)]
21pub enum BackendEngagement {
22    Ptrace {
23        scheduler_turns: u64,
24    },
25    E9patch {
26        candidate_sites: u64,
27        mapped_sites: u64,
28        b0_sites: u64,
29    },
30    Dbt {
31        counted_branches: u64,
32    },
33}
34
35/// One complete `--backend-engagement-json` record.
36#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
37#[serde(deny_unknown_fields)]
38pub struct BackendEngagementReport {
39    pub schema: u8,
40    pub engagement: BackendEngagement,
41}
42
43impl BackendEngagementReport {
44    pub const SCHEMA: u8 = 2;
45
46    pub const fn new(engagement: BackendEngagement) -> Self {
47        Self {
48            schema: Self::SCHEMA,
49            engagement,
50        }
51    }
52
53    pub fn validate(&self) -> Result<(), String> {
54        if self.schema != Self::SCHEMA {
55            return Err(format!(
56                "backend-engagement schema must be {}, got {}",
57                Self::SCHEMA,
58                self.schema
59            ));
60        }
61        Ok(())
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn backend_and_counter_cannot_be_recombined() {
71        let report = BackendEngagementReport::new(BackendEngagement::E9patch {
72            candidate_sites: 7,
73            mapped_sites: 7,
74            b0_sites: 0,
75        });
76        let json = serde_json::to_string(&report).unwrap();
77        assert_eq!(
78            json,
79            r#"{"schema":2,"engagement":{"backend":"e9patch","candidate_sites":7,"mapped_sites":7,"b0_sites":0}}"#
80        );
81        let changed =
82            serde_json::to_string(&BackendEngagementReport::new(BackendEngagement::E9patch {
83                candidate_sites: 8,
84                mapped_sites: 8,
85                b0_sites: 0,
86            }))
87            .unwrap();
88        assert_ne!(json, changed, "the producer's value must reach the record");
89
90        let mismatched = r#"{"schema":2,"engagement":{"backend":"e9patch","counted_branches":7}}"#;
91        let error = serde_json::from_str::<BackendEngagementReport>(mismatched).unwrap_err();
92        assert!(
93            error
94                .to_string()
95                .contains("unknown field `counted_branches`")
96        );
97    }
98
99    #[test]
100    fn unsupported_schema_refuses_by_name() {
101        let report = BackendEngagementReport {
102            schema: 3,
103            engagement: BackendEngagement::Dbt {
104                counted_branches: 11,
105            },
106        };
107        assert_eq!(
108            report.validate().unwrap_err(),
109            "backend-engagement schema must be 2, got 3"
110        );
111    }
112    #[test]
113    fn e9patch_result_requires_all_three_counts() {
114        for incomplete in [
115            r#"{"schema":2,"engagement":{"backend":"e9patch","mapped_sites":7,"b0_sites":0}}"#,
116            r#"{"schema":2,"engagement":{"backend":"e9patch","candidate_sites":7,"b0_sites":0}}"#,
117            r#"{"schema":2,"engagement":{"backend":"e9patch","candidate_sites":7,"mapped_sites":7}}"#,
118        ] {
119            assert!(serde_json::from_str::<BackendEngagementReport>(incomplete).is_err());
120        }
121    }
122}