Skip to main content

tokmd_types/
tokmd_packets.rs

1//! Cross-tool packet bundle input types for `tokmd render`.
2//!
3//! These types model the `tokmd-packets.json` contract consumed when rendering
4//! audience-specific packet presets from unsafe-review manual-candidate bundles.
5
6use std::collections::BTreeMap;
7
8use serde::{Deserialize, Serialize};
9
10/// Stable schema identifier for packet bundle manifests.
11pub const TOKMD_PACKETS_SCHEMA: &str = "tokmd.packets/v1";
12
13/// Bun UB packet presets documented in `docs/specs/tokmd-packets-render.md`.
14pub const BUN_UB_PACKET_PRESETS: &[&str] = &[
15    "bun-ub-handoff",
16    "bun-ub-pr-body",
17    "bun-ub-ledger-note",
18    "bun-ub-review-map",
19    "bun-ub-next-pick",
20];
21
22/// Top-level packet bundle manifest (`tokmd-packets.json`).
23#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
24pub struct TokmdPacketsManifest {
25    pub schema: String,
26    #[serde(default, skip_serializing_if = "Option::is_none")]
27    pub producer: Option<TokmdPacketsProducer>,
28    #[serde(default, skip_serializing_if = "Vec::is_empty")]
29    pub inputs_present: Vec<String>,
30    #[serde(default, skip_serializing_if = "Vec::is_empty")]
31    pub inputs_absent: Vec<String>,
32    #[serde(default, skip_serializing_if = "Vec::is_empty")]
33    pub non_claims: Vec<String>,
34    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
35    pub preset_inputs: BTreeMap<String, PacketPresetInput>,
36}
37
38/// Producer metadata recorded by the exporting tool.
39#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
40pub struct TokmdPacketsProducer {
41    pub name: String,
42    #[serde(default, skip_serializing_if = "Option::is_none")]
43    pub version: Option<String>,
44    #[serde(default, skip_serializing_if = "Option::is_none")]
45    pub tokmd_run: Option<bool>,
46}
47
48/// Ready-to-format sections for one audience preset.
49#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
50pub struct PacketPresetInput {
51    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
52    pub sections: BTreeMap<String, String>,
53    #[serde(default, skip_serializing_if = "Vec::is_empty")]
54    pub limitations: Vec<String>,
55    #[serde(default, skip_serializing_if = "Vec::is_empty")]
56    pub missing_sections: Vec<String>,
57}
58
59impl TokmdPacketsManifest {
60    /// Returns true when the manifest uses the supported schema id.
61    pub fn schema_matches(&self) -> bool {
62        self.schema == TOKMD_PACKETS_SCHEMA
63    }
64
65    /// Returns true when `preset` is a known Bun UB packet preset name.
66    pub fn preset_is_known(preset: &str) -> bool {
67        BUN_UB_PACKET_PRESETS.contains(&preset)
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn tokmd_packets_schema_constant() {
77        assert_eq!(TOKMD_PACKETS_SCHEMA, "tokmd.packets/v1");
78    }
79
80    #[test]
81    fn bun_ub_presets_are_stable() {
82        assert_eq!(BUN_UB_PACKET_PRESETS.len(), 5);
83        assert!(BUN_UB_PACKET_PRESETS.contains(&"bun-ub-handoff"));
84    }
85
86    #[test]
87    fn manifest_roundtrip_preserves_preset_inputs() {
88        let mut sections = BTreeMap::new();
89        sections.insert("candidate_identity".into(), "seed-42".into());
90        let manifest = TokmdPacketsManifest {
91            schema: TOKMD_PACKETS_SCHEMA.into(),
92            producer: Some(TokmdPacketsProducer {
93                name: "unsafe-review".into(),
94                version: Some("0.1.0".into()),
95                tokmd_run: Some(false),
96            }),
97            inputs_present: vec!["manual-candidates.json".into()],
98            inputs_absent: vec!["comment-plan.json".into()],
99            non_claims: vec!["Does not prove UB.".into()],
100            preset_inputs: BTreeMap::from([(
101                "bun-ub-handoff".into(),
102                PacketPresetInput {
103                    sections,
104                    limitations: vec!["witness-plan.md absent".into()],
105                    missing_sections: vec!["test or witness target".into()],
106                },
107            )]),
108        };
109        let json = serde_json::to_string(&manifest).unwrap();
110        let back: TokmdPacketsManifest = serde_json::from_str(&json).unwrap();
111        assert_eq!(back, manifest);
112        assert!(back.schema_matches());
113    }
114}