Skip to main content

faf_rust_sdk/binary/
chunk_registry.rs

1//! FAFb v2 Chunk Registry
2//!
3//! Classifies YAML keys into DNA, Context, or Pointer chunks.
4//! Ported from the v3 FAF specification.
5
6/// Chunk classification for v2 binary format
7///
8/// Stored in bits 0-1 of `SectionEntry.flags`:
9/// - `0b00` = DNA (core project identity)
10/// - `0b01` = Context (runtime/supplementary)
11/// - `0b10` = Pointer (documentation references)
12/// - `0b11` = Reserved
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum ChunkClassification {
15    /// Core project identity (project, tech_stack, commands, etc.)
16    Dna = 0b00,
17    /// Runtime/supplementary context
18    Context = 0b01,
19    /// Documentation pointer (e.g., "docs")
20    Pointer = 0b10,
21    /// Reserved for future use
22    Reserved = 0b11,
23}
24
25impl ChunkClassification {
26    /// Get the 2-bit value for encoding into flags
27    pub const fn bits(&self) -> u32 {
28        *self as u32
29    }
30
31    /// Decode from the low 2 bits of a flags value
32    pub fn from_bits(bits: u32) -> Self {
33        match bits & 0b11 {
34            0b00 => Self::Dna,
35            0b01 => Self::Context,
36            0b10 => Self::Pointer,
37            _ => Self::Reserved,
38        }
39    }
40
41    /// Human-readable name
42    pub const fn name(&self) -> &'static str {
43        match self {
44            Self::Dna => "DNA",
45            Self::Context => "Context",
46            Self::Pointer => "Pointer",
47            Self::Reserved => "Reserved",
48        }
49    }
50}
51
52/// Classification mask for the low 2 bits of section flags
53pub const CLASSIFICATION_MASK: u32 = 0b11;
54
55/// Known DNA keys — core project identity fields
56pub const DNA_KEYS: &[&str] = &[
57    "faf_version",
58    "project",
59    "instant_context",
60    "tech_stack",
61    "key_files",
62    "commands",
63    "architecture",
64    "context",
65    "bi_sync",
66    "meta",
67];
68
69/// The pointer key — documentation references
70pub const POINTER_KEY: &str = "docs";
71
72/// Classify a YAML key into its chunk type
73pub fn classify_key(key: &str) -> ChunkClassification {
74    if key == POINTER_KEY {
75        ChunkClassification::Pointer
76    } else if DNA_KEYS.contains(&key) {
77        ChunkClassification::Dna
78    } else {
79        ChunkClassification::Context
80    }
81}
82
83/// Get the default priority for a classified chunk
84pub fn default_priority_for_classification(classification: ChunkClassification) -> u8 {
85    match classification {
86        ChunkClassification::Dna => 200,     // High
87        ChunkClassification::Context => 64,  // Low
88        ChunkClassification::Pointer => 128, // Medium
89        ChunkClassification::Reserved => 0,  // Optional
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn test_all_dna_keys_classified() {
99        for key in DNA_KEYS {
100            assert_eq!(
101                classify_key(key),
102                ChunkClassification::Dna,
103                "Expected '{}' to be DNA",
104                key
105            );
106        }
107    }
108
109    #[test]
110    fn test_pointer_key() {
111        assert_eq!(classify_key("docs"), ChunkClassification::Pointer);
112    }
113
114    #[test]
115    fn test_unknown_keys_are_context() {
116        assert_eq!(classify_key("custom_field"), ChunkClassification::Context);
117        assert_eq!(classify_key("my_data"), ChunkClassification::Context);
118        assert_eq!(classify_key("anything"), ChunkClassification::Context);
119    }
120
121    #[test]
122    fn test_case_sensitivity() {
123        // Keys are case-sensitive (YAML convention)
124        assert_eq!(classify_key("Project"), ChunkClassification::Context);
125        assert_eq!(classify_key("DOCS"), ChunkClassification::Context);
126        assert_eq!(classify_key("project"), ChunkClassification::Dna);
127    }
128
129    #[test]
130    fn test_bits_roundtrip() {
131        for class in &[
132            ChunkClassification::Dna,
133            ChunkClassification::Context,
134            ChunkClassification::Pointer,
135            ChunkClassification::Reserved,
136        ] {
137            assert_eq!(ChunkClassification::from_bits(class.bits()), *class);
138        }
139    }
140
141    #[test]
142    fn test_bit_values() {
143        assert_eq!(ChunkClassification::Dna.bits(), 0b00);
144        assert_eq!(ChunkClassification::Context.bits(), 0b01);
145        assert_eq!(ChunkClassification::Pointer.bits(), 0b10);
146        assert_eq!(ChunkClassification::Reserved.bits(), 0b11);
147    }
148
149    #[test]
150    fn test_from_bits_masked() {
151        // Higher bits should be ignored
152        assert_eq!(
153            ChunkClassification::from_bits(0xFF00_0000),
154            ChunkClassification::Dna
155        );
156        assert_eq!(
157            ChunkClassification::from_bits(0xFF00_0001),
158            ChunkClassification::Context
159        );
160    }
161
162    #[test]
163    fn test_classification_names() {
164        assert_eq!(ChunkClassification::Dna.name(), "DNA");
165        assert_eq!(ChunkClassification::Context.name(), "Context");
166        assert_eq!(ChunkClassification::Pointer.name(), "Pointer");
167        assert_eq!(ChunkClassification::Reserved.name(), "Reserved");
168    }
169
170    #[test]
171    fn test_default_priorities() {
172        assert_eq!(
173            default_priority_for_classification(ChunkClassification::Dna),
174            200
175        );
176        assert_eq!(
177            default_priority_for_classification(ChunkClassification::Context),
178            64
179        );
180        assert_eq!(
181            default_priority_for_classification(ChunkClassification::Pointer),
182            128
183        );
184    }
185}