Skip to main content

hara_native/kernel/
halc.rs

1use super::Form;
2use num_bigint::BigInt;
3use num_traits::ToPrimitive;
4use sha2::{Digest, Sha256};
5use std::collections::{HashMap, HashSet};
6
7const MAGIC: &[u8] = b"HALC";
8const FORMAT_VERSION: u16 = 1;
9const EXECUTABLE_FOUNDATION_FLAG: u16 = 1;
10const HASH_BYTES: usize = 32;
11const MAX_PAYLOAD_BYTES: usize = 64 * 1024 * 1024;
12const MAX_COLLECTION_ITEMS: i32 = 1_000_000;
13
14const NIL: u8 = 0;
15const FALSE: u8 = 1;
16const TRUE: u8 = 2;
17const LONG: u8 = 3;
18const DOUBLE: u8 = 4;
19const BIG_INTEGER: u8 = 5;
20const STRING: u8 = 6;
21const CHARACTER: u8 = 8;
22const SYMBOL: u8 = 9;
23const KEYWORD: u8 = 10;
24const LIST: u8 = 11;
25const VECTOR: u8 = 12;
26const MAP: u8 = 13;
27const SET: u8 = 14;
28const ORDERED_MAP: u8 = 15;
29const ORDERED_SET: u8 = 16;
30const REGEX: u8 = 17;
31
32#[derive(Debug, Clone)]
33pub struct HalcModule {
34    pub namespace: String,
35    pub resource: String,
36    pub source_hash: Vec<u8>,
37    pub forms: Vec<Form>,
38    pub schemas: HalcSchemaIndex,
39    pub origin: HalcOrigin,
40}
41
42/// The typed declarations recoverable from canonical HALC forms without
43/// evaluating the module. Keys are fully qualified Var names.
44#[derive(Debug, Clone, Default, PartialEq)]
45pub struct HalcSchemaIndex {
46    pub definitions: HashMap<String, Form>,
47    pub functions: HashMap<String, Form>,
48    pub definition_types: HashMap<String, super::SchemaType>,
49    pub function_types: HashMap<String, super::SchemaType>,
50}
51
52impl HalcSchemaIndex {
53    /// Resolves a function annotation through named-schema references while
54    /// preserving recursive graph edges as `Reference` nodes.
55    pub fn resolved_function_type(&self, qualified_var: &str) -> Option<&super::SchemaType> {
56        let schema = self.function_types.get(qualified_var)?;
57        match schema {
58            super::SchemaType::Reference(name) => self.definition_types.get(name).or(Some(schema)),
59            _ => Some(schema),
60        }
61    }
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum HalcOrigin {
66    Halc,
67}
68
69pub fn decode_halc(bytes: &[u8]) -> Result<HalcModule, String> {
70    let (payload, origin) = decode_envelope(bytes)?;
71    let mut reader = ByteReader::new(&payload);
72    let namespace = reader.read_string()?;
73    let resource = reader.read_string()?;
74    let source_hash = reader.read_bytes(HASH_BYTES)?;
75    let form_count = reader.read_count()?;
76    let mut forms = Vec::with_capacity(form_count as usize);
77    for _ in 0..form_count {
78        forms.push(reader.read_value()?);
79    }
80    if !reader.is_empty() {
81        return Err("trailing payload bytes".into());
82    }
83    let forms = canonicalize_schema_references(&namespace, forms)?;
84    let schemas = build_schema_index(&namespace, &forms)?;
85    Ok(HalcModule {
86        namespace,
87        resource,
88        source_hash,
89        forms,
90        schemas,
91        origin,
92    })
93}
94
95fn decode_envelope(bytes: &[u8]) -> Result<(Vec<u8>, HalcOrigin), String> {
96    let mut reader = ByteReader::new(bytes);
97    let magic = reader.read_bytes(MAGIC.len())?;
98    if magic != MAGIC {
99        return Err("bad magic".into());
100    }
101    let version = reader.read_u16()?;
102    if version != FORMAT_VERSION {
103        return Err(format!("unsupported format version {version}"));
104    }
105    let flags = reader.read_u16()?;
106    if flags != EXECUTABLE_FOUNDATION_FLAG {
107        return Err(format!("unsupported flags {flags}"));
108    }
109    let payload_length = reader.read_u32()? as usize;
110    if payload_length > MAX_PAYLOAD_BYTES {
111        return Err(format!("invalid payload length {payload_length}"));
112    }
113    let expected_hash = reader.read_bytes(HASH_BYTES)?;
114    let payload = reader.read_bytes(payload_length)?;
115    if !reader.is_empty() {
116        return Err("trailing bytes".into());
117    }
118    let actual_hash = Sha256::digest(&payload);
119    if actual_hash[..] != expected_hash[..] {
120        return Err("payload checksum mismatch".into());
121    }
122    Ok((payload, HalcOrigin::Halc))
123}
124
125struct ByteReader<'a> {
126    bytes: &'a [u8],
127    position: usize,
128}
129
130impl<'a> ByteReader<'a> {
131    fn new(bytes: &'a [u8]) -> Self {
132        Self { bytes, position: 0 }
133    }
134
135    fn is_empty(&self) -> bool {
136        self.position >= self.bytes.len()
137    }
138
139    fn remaining(&self) -> usize {
140        self.bytes.len().saturating_sub(self.position)
141    }
142
143    fn read_byte(&mut self) -> Result<u8, String> {
144        if self.position >= self.bytes.len() {
145            return Err("truncated artifact".into());
146        }
147        let byte = self.bytes[self.position];
148        self.position += 1;
149        Ok(byte)
150    }
151
152    fn read_bytes(&mut self, count: usize) -> Result<Vec<u8>, String> {
153        if self.remaining() < count {
154            return Err("truncated artifact".into());
155        }
156        let bytes = self.bytes[self.position..self.position + count].to_vec();
157        self.position += count;
158        Ok(bytes)
159    }
160
161    fn read_u16(&mut self) -> Result<u16, String> {
162        let bytes = self.read_bytes(2)?;
163        Ok(u16::from_be_bytes([bytes[0], bytes[1]]))
164    }
165
166    fn read_u32(&mut self) -> Result<u32, String> {
167        let bytes = self.read_bytes(4)?;
168        Ok(u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
169    }
170
171    fn read_i64(&mut self) -> Result<i64, String> {
172        let bytes = self.read_bytes(8)?;
173        Ok(i64::from_be_bytes([
174            bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
175        ]))
176    }
177
178    fn read_f64(&mut self) -> Result<f64, String> {
179        let bytes = self.read_bytes(8)?;
180        let value = f64::from_be_bytes([
181            bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
182        ]);
183        if !value.is_finite() {
184            return Err("non-finite number".into());
185        }
186        Ok(value)
187    }
188
189    fn read_string(&mut self) -> Result<String, String> {
190        let length = self.read_u32()? as usize;
191        if length > MAX_PAYLOAD_BYTES {
192            return Err(format!("invalid string length {length}"));
193        }
194        let bytes = self.read_bytes(length)?;
195        String::from_utf8(bytes).map_err(|_| "invalid UTF-8 in string".to_string())
196    }
197
198    fn read_nullable_string(&mut self) -> Result<Option<String>, String> {
199        let present = self.read_byte()? != 0;
200        if present {
201            Ok(Some(self.read_string()?))
202        } else {
203            Ok(None)
204        }
205    }
206
207    fn read_count(&mut self) -> Result<i32, String> {
208        let count = self.read_u32()? as i32;
209        if count < 0 || count > MAX_COLLECTION_ITEMS {
210            return Err(format!("invalid collection count {count}"));
211        }
212        Ok(count)
213    }
214
215    fn read_metadata(&mut self) -> Result<Option<Form>, String> {
216        let present = self.read_byte()? != 0;
217        if present {
218            Ok(Some(self.read_value()?))
219        } else {
220            Ok(None)
221        }
222    }
223
224    fn read_value(&mut self) -> Result<Form, String> {
225        let opcode = self.read_byte()?;
226        match opcode {
227            NIL => Ok(Form::Nil),
228            FALSE => Ok(Form::Bool(false)),
229            TRUE => Ok(Form::Bool(true)),
230            LONG => Ok(Form::Number(self.read_i64()?)),
231            DOUBLE => Ok(Form::Float(self.read_f64()?)),
232            BIG_INTEGER => {
233                let text = self.read_string()?;
234                let value = BigInt::parse_bytes(text.as_bytes(), 10)
235                    .ok_or_else(|| "invalid big integer".to_string())?;
236                Ok(match value.to_i64() {
237                    Some(value) => Form::Number(value),
238                    None => Form::BigInteger(value),
239                })
240            }
241            STRING => Ok(Form::String(self.read_string()?)),
242            CHARACTER => Ok(Form::Character(
243                char::from_u32(self.read_u32()?).ok_or("invalid character code point")?,
244            )),
245            SYMBOL => {
246                let namespace = self.read_nullable_string()?;
247                let name = self.read_string()?;
248                Ok(with_metadata(
249                    Form::Symbol(namespaced(namespace, name)),
250                    self.read_metadata()?,
251                ))
252            }
253            KEYWORD => {
254                let namespace = self.read_nullable_string()?;
255                let name = self.read_string()?;
256                Ok(with_metadata(
257                    Form::Keyword(namespaced(namespace, name)),
258                    self.read_metadata()?,
259                ))
260            }
261            LIST => {
262                let count = self.read_count()?;
263                let items = self.read_values(count)?;
264                Ok(with_metadata(Form::List(items), self.read_metadata()?))
265            }
266            VECTOR => {
267                let count = self.read_count()?;
268                let items = self.read_values(count)?;
269                Ok(with_metadata(Form::Vector(items), self.read_metadata()?))
270            }
271            MAP | ORDERED_MAP => {
272                let count = self.read_count()?;
273                let mut entries = Vec::with_capacity(count as usize);
274                for _ in 0..count {
275                    let key = self.read_value()?;
276                    let value = self.read_value()?;
277                    entries.push((key, value));
278                }
279                Ok(with_metadata(Form::Map(entries), self.read_metadata()?))
280            }
281            SET | ORDERED_SET => {
282                let count = self.read_count()?;
283                let items = self.read_values(count)?;
284                Ok(with_metadata(Form::Set(items), self.read_metadata()?))
285            }
286            REGEX => Ok(Form::Regex(self.read_string()?)),
287            _ => Err(format!("unknown value opcode {opcode}")),
288        }
289    }
290
291    fn read_values(&mut self, count: i32) -> Result<Vec<Form>, String> {
292        let mut values = Vec::with_capacity(count as usize);
293        for _ in 0..count {
294            values.push(self.read_value()?);
295        }
296        Ok(values)
297    }
298}
299
300fn with_metadata(value: Form, metadata: Option<Form>) -> Form {
301    match metadata {
302        Some(metadata) => Form::Metadata(Box::new(metadata), Box::new(value)),
303        None => value,
304    }
305}
306
307fn namespaced(namespace: Option<String>, name: String) -> String {
308    match namespace {
309        Some(ns) => format!("{ns}/{name}"),
310        None => name,
311    }
312}
313
314#[cfg(any(test, feature = "halc-encoder"))]
315fn write_string(output: &mut Vec<u8>, value: &str) {
316    output.extend_from_slice(&(value.len() as u32).to_be_bytes());
317    output.extend_from_slice(value.as_bytes());
318}
319
320#[cfg(any(test, feature = "halc-encoder"))]
321fn write_count(output: &mut Vec<u8>, count: i32) {
322    output.extend_from_slice(&count.to_be_bytes());
323}
324
325#[cfg(any(test, feature = "halc-encoder"))]
326fn write_namespaced(output: &mut Vec<u8>, symbol: &str) {
327    if let Some((ns, name)) = symbol.rsplit_once('/') {
328        output.push(1);
329        write_string(output, ns);
330        write_string(output, name);
331    } else {
332        output.push(0);
333        write_string(output, symbol);
334    }
335}
336
337#[cfg(any(test, feature = "halc-encoder"))]
338fn write_values(output: &mut Vec<u8>, values: &[Form]) {
339    write_count(output, values.len() as i32);
340    for value in values {
341        write_value(output, value);
342    }
343}
344
345#[cfg(any(test, feature = "halc-encoder"))]
346fn encoded_value(form: &Form) -> Vec<u8> {
347    let mut output = Vec::new();
348    write_value(&mut output, form);
349    output
350}
351
352#[cfg(any(test, feature = "halc-encoder"))]
353fn write_canonical_map(output: &mut Vec<u8>, entries: &[(Form, Form)]) {
354    write_count(output, entries.len() as i32);
355    let mut encoded_entries = entries
356        .iter()
357        .map(|(key, value)| (encoded_value(key), encoded_value(value)))
358        .collect::<Vec<_>>();
359    encoded_entries.sort_by(|(left, _), (right, _)| left.cmp(right));
360    for (key, value) in encoded_entries {
361        output.extend_from_slice(&key);
362        output.extend_from_slice(&value);
363    }
364}
365
366#[cfg(any(test, feature = "halc-encoder"))]
367fn write_value(output: &mut Vec<u8>, form: &Form) {
368    match form {
369        Form::Metadata(metadata, value) => write_value_with_metadata(output, value, Some(metadata)),
370        _ => write_value_with_metadata(output, form, None),
371    }
372}
373
374#[cfg(any(test, feature = "halc-encoder"))]
375fn write_metadata(output: &mut Vec<u8>, metadata: Option<&Form>) {
376    match metadata {
377        Some(metadata) => {
378            output.push(1);
379            write_value(output, metadata);
380        }
381        None => output.push(0),
382    }
383}
384
385#[cfg(any(test, feature = "halc-encoder"))]
386fn write_value_with_metadata(output: &mut Vec<u8>, form: &Form, metadata: Option<&Form>) {
387    match form {
388        Form::Nil => output.push(NIL),
389        Form::Bool(false) => output.push(FALSE),
390        Form::Bool(true) => output.push(TRUE),
391        Form::Number(n) => {
392            output.push(LONG);
393            output.extend_from_slice(&n.to_be_bytes());
394        }
395        Form::Float(f) => {
396            assert!(f.is_finite(), "non-finite number");
397            output.push(DOUBLE);
398            output.extend_from_slice(&f.to_be_bytes());
399        }
400        Form::BigInteger(s) => {
401            output.push(BIG_INTEGER);
402            write_string(output, &s.to_string());
403        }
404        Form::String(s) => {
405            output.push(STRING);
406            write_string(output, s);
407        }
408        Form::Character(c) => {
409            output.push(CHARACTER);
410            output.extend_from_slice(&(*c as u32).to_be_bytes());
411        }
412        Form::Symbol(s) => {
413            output.push(SYMBOL);
414            write_namespaced(output, s);
415            write_metadata(output, metadata);
416        }
417        Form::Keyword(s) => {
418            output.push(KEYWORD);
419            write_namespaced(output, s);
420            write_metadata(output, metadata);
421        }
422        Form::List(items) => {
423            output.push(LIST);
424            write_values(output, items);
425            write_metadata(output, metadata);
426        }
427        Form::Vector(items) => {
428            output.push(VECTOR);
429            write_values(output, items);
430            write_metadata(output, metadata);
431        }
432        Form::Map(entries) => {
433            output.push(MAP);
434            write_canonical_map(output, entries);
435            write_metadata(output, metadata);
436        }
437        Form::Set(items) => {
438            output.push(ORDERED_SET);
439            write_values(output, items);
440            write_metadata(output, metadata);
441        }
442        Form::Regex(s) => {
443            output.push(REGEX);
444            write_string(output, s);
445        }
446        Form::Tagged(_, _) | Form::Metadata(_, _) => {
447            panic!("test encoder does not support tagged/metadata forms")
448        }
449    }
450}
451
452/// Encodes a canonical HALC artifact from parsed forms.
453///
454/// This mirrors the v1 format used by `decode_halc` so that integration tests
455/// can construct artifacts without depending on an external encoder.
456#[cfg(any(test, feature = "halc-encoder"))]
457pub fn encode_halc_module(
458    namespace: &str,
459    resource: &str,
460    source: &str,
461    forms: Vec<Form>,
462) -> Result<Vec<u8>, String> {
463    let forms = canonicalize_schema_references(namespace, forms)?;
464    for form in &forms {
465        validate_finite_form(form)?;
466    }
467    build_schema_index(namespace, &forms)?;
468    let mut payload = Vec::new();
469    write_string(&mut payload, namespace);
470    write_string(&mut payload, resource);
471    payload.extend_from_slice(&Sha256::digest(source.as_bytes()));
472    write_count(&mut payload, forms.len() as i32);
473    for form in forms {
474        write_value(&mut payload, &form);
475    }
476    let mut artifact = Vec::new();
477    artifact.extend_from_slice(MAGIC);
478    artifact.extend_from_slice(&FORMAT_VERSION.to_be_bytes());
479    artifact.extend_from_slice(&EXECUTABLE_FOUNDATION_FLAG.to_be_bytes());
480    artifact.extend_from_slice(&(payload.len() as u32).to_be_bytes());
481    artifact.extend_from_slice(&Sha256::digest(&payload));
482    artifact.extend_from_slice(&payload);
483    Ok(artifact)
484}
485
486#[cfg(any(test, feature = "halc-encoder"))]
487fn validate_finite_form(form: &Form) -> Result<(), String> {
488    match form {
489        Form::Float(value) if !value.is_finite() => Err("non-finite number".into()),
490        Form::Tagged(_, value) => validate_finite_form(value),
491        Form::Metadata(metadata, value) => {
492            validate_finite_form(metadata)?;
493            validate_finite_form(value)
494        }
495        Form::Map(entries) => {
496            for (key, value) in entries {
497                validate_finite_form(key)?;
498                validate_finite_form(value)?;
499            }
500            Ok(())
501        }
502        Form::Set(values) | Form::Vector(values) | Form::List(values) => {
503            for value in values {
504                validate_finite_form(value)?;
505            }
506            Ok(())
507        }
508        _ => Ok(()),
509    }
510}
511
512fn canonicalize_schema_references(
513    namespace: &str,
514    mut forms: Vec<Form>,
515) -> Result<Vec<Form>, String> {
516    let definitions: HashSet<String> = forms
517        .iter()
518        .filter_map(|form| {
519            let Form::List(items) = form else { return None };
520            let Form::Symbol(operator) = items.first()? else {
521                return None;
522            };
523            if !matches!(
524                operator.as_str(),
525                "def" | "defn" | "defmacro" | "defstruct" | "declare"
526            ) {
527                return None;
528            }
529            binding_name(items.get(1)?).map(str::to_owned)
530        })
531        .collect();
532    let schema_values: HashMap<String, usize> = forms
533        .iter()
534        .enumerate()
535        .filter_map(|(index, form)| {
536            let Form::List(items) = form else { return None };
537            if !matches!(items.first(), Some(Form::Symbol(operator)) if operator == "def") {
538                return None;
539            }
540            binding_name(items.get(1)?).map(|name| (name.to_owned(), index))
541        })
542        .collect();
543    let aliases = module_aliases(&forms);
544    let mut schema_roots = Vec::new();
545
546    for form in &mut forms {
547        let Form::List(items) = form else { continue };
548        let Some(Form::Symbol(operator)) = items.first() else {
549            continue;
550        };
551        if operator != "defn" {
552            continue;
553        }
554        let Some(Form::Metadata(metadata, _)) = items.get_mut(1) else {
555            continue;
556        };
557        let Form::Map(entries) = metadata.as_mut() else {
558            continue;
559        };
560        let Some((_, schema)) = entries
561            .iter_mut()
562            .find(|(key, _)| matches!(key, Form::Keyword(name) if name == "schema"))
563        else {
564            continue;
565        };
566        let Form::List(reference) = schema else {
567            continue;
568        };
569        if reference.len() != 2
570            || !matches!(&reference[0], Form::Symbol(operator) if operator == "var")
571        {
572            continue;
573        }
574        let Form::Symbol(target) = &reference[1] else {
575            continue;
576        };
577        let (qualifier, local) = target
578            .rsplit_once('/')
579            .map_or((None, target.as_str()), |(qualifier, local)| {
580                (Some(qualifier), local)
581            });
582        let target_namespace = match qualifier {
583            None | Some("-") => namespace,
584            Some(qualifier) => aliases.get(qualifier).map_or(qualifier, String::as_str),
585        };
586        if target_namespace == namespace && !definitions.contains(local) {
587            return Err(format!("schema Var does not exist: {target}"));
588        }
589        if target_namespace == namespace {
590            schema_roots.push(local.to_owned());
591        }
592        reference[1] = Form::Symbol(format!("{target_namespace}/{local}"));
593    }
594
595    let mut visited = HashSet::new();
596    while let Some(schema_name) = schema_roots.pop() {
597        if !visited.insert(schema_name.clone()) {
598            continue;
599        }
600        let Some(index) = schema_values.get(&schema_name).copied() else {
601            continue;
602        };
603        let Form::List(definition) = &mut forms[index] else {
604            continue;
605        };
606        let Some(schema_value) = definition.get_mut(2) else {
607            continue;
608        };
609        canonicalize_nested_schema_references(
610            schema_value,
611            namespace,
612            &aliases,
613            &definitions,
614            &mut schema_roots,
615        )?;
616    }
617    Ok(forms)
618}
619
620fn canonicalize_nested_schema_references(
621    form: &mut Form,
622    namespace: &str,
623    aliases: &HashMap<String, String>,
624    definitions: &HashSet<String>,
625    local_references: &mut Vec<String>,
626) -> Result<(), String> {
627    if let Form::List(reference) = form {
628        if reference.len() == 2
629            && matches!(&reference[0], Form::Symbol(operator) if operator == "var")
630        {
631            let Form::Symbol(target) = &reference[1] else {
632                return Ok(());
633            };
634            let original = target.clone();
635            let (qualifier, local) = original
636                .rsplit_once('/')
637                .map_or((None, original.as_str()), |(qualifier, local)| {
638                    (Some(qualifier), local)
639                });
640            let target_namespace = match qualifier {
641                None | Some("-") => namespace,
642                Some(qualifier) => aliases.get(qualifier).map_or(qualifier, String::as_str),
643            };
644            if target_namespace == namespace {
645                if !definitions.contains(local) {
646                    return Err(format!("schema Var does not exist: {original}"));
647                }
648                local_references.push(local.to_owned());
649            }
650            reference[1] = Form::Symbol(format!("{target_namespace}/{local}"));
651            return Ok(());
652        }
653    }
654
655    match form {
656        Form::Tagged(_, value) => canonicalize_nested_schema_references(
657            value,
658            namespace,
659            aliases,
660            definitions,
661            local_references,
662        ),
663        Form::Metadata(metadata, value) => {
664            canonicalize_nested_schema_references(
665                metadata,
666                namespace,
667                aliases,
668                definitions,
669                local_references,
670            )?;
671            canonicalize_nested_schema_references(
672                value,
673                namespace,
674                aliases,
675                definitions,
676                local_references,
677            )
678        }
679        Form::Map(entries) => {
680            for (key, value) in entries {
681                canonicalize_nested_schema_references(
682                    key,
683                    namespace,
684                    aliases,
685                    definitions,
686                    local_references,
687                )?;
688                canonicalize_nested_schema_references(
689                    value,
690                    namespace,
691                    aliases,
692                    definitions,
693                    local_references,
694                )?;
695            }
696            Ok(())
697        }
698        Form::Set(values) | Form::Vector(values) | Form::List(values) => {
699            for value in values {
700                canonicalize_nested_schema_references(
701                    value,
702                    namespace,
703                    aliases,
704                    definitions,
705                    local_references,
706                )?;
707            }
708            Ok(())
709        }
710        _ => Ok(()),
711    }
712}
713
714fn binding_name(form: &Form) -> Option<&str> {
715    match form {
716        Form::Symbol(name) => Some(name),
717        Form::Metadata(_, value) => binding_name(value),
718        _ => None,
719    }
720}
721
722fn module_aliases(forms: &[Form]) -> HashMap<String, String> {
723    let mut aliases = HashMap::new();
724    for form in forms {
725        let Form::List(declaration) = form else {
726            continue;
727        };
728        if !matches!(declaration.first(), Some(Form::Symbol(operator)) if operator == "ns") {
729            continue;
730        }
731        for clause in declaration.iter().skip(2) {
732            let Form::List(clause) = clause else { continue };
733            if !matches!(clause.first(), Some(Form::Keyword(keyword)) if keyword == "require") {
734                continue;
735            }
736            for spec in clause.iter().skip(1) {
737                let Form::Vector(spec) = spec else { continue };
738                let Some(Form::Symbol(target)) = spec.first() else {
739                    continue;
740                };
741                for option in spec[1..].chunks(2) {
742                    if let [Form::Keyword(key), Form::Symbol(alias)] = option {
743                        if key == "as" {
744                            aliases.insert(alias.clone(), target.clone());
745                        }
746                    }
747                }
748            }
749        }
750    }
751    aliases
752}
753
754fn build_schema_index(namespace: &str, forms: &[Form]) -> Result<HalcSchemaIndex, String> {
755    let mut index = HalcSchemaIndex::default();
756    let mut values = HashMap::new();
757    let mut roots = Vec::new();
758
759    for form in forms {
760        let Form::List(items) = form else { continue };
761        let Some(Form::Symbol(operator)) = items.first() else {
762            continue;
763        };
764        let Some(name) = items.get(1).and_then(binding_name) else {
765            continue;
766        };
767        let qualified_name = format!("{namespace}/{name}");
768        if operator == "def" {
769            if let Some(value) = items.get(2) {
770                values.insert(name.to_owned(), value.clone());
771            }
772            continue;
773        }
774        if operator != "defn" {
775            continue;
776        }
777        let Some(Form::Metadata(metadata, _)) = items.get(1) else {
778            continue;
779        };
780        let Form::Map(entries) = metadata.as_ref() else {
781            continue;
782        };
783        let Some(schema) = entries.iter().find_map(|(key, value)| {
784            matches!(key, Form::Keyword(name) if name == "schema").then_some(value)
785        }) else {
786            continue;
787        };
788        index.functions.insert(qualified_name, schema.clone());
789        collect_local_schema_references(schema, namespace, &mut roots);
790    }
791
792    let mut visited = HashSet::new();
793    while let Some(name) = roots.pop() {
794        if !visited.insert(name.clone()) {
795            continue;
796        }
797        let Some(value) = values.get(&name) else {
798            continue;
799        };
800        index
801            .definitions
802            .insert(format!("{namespace}/{name}"), value.clone());
803        collect_local_schema_references(value, namespace, &mut roots);
804    }
805    for (name, schema) in &index.definitions {
806        index.definition_types.insert(
807            name.clone(),
808            super::normalize_schema(schema)
809                .map_err(|error| format!("invalid schema {name}: {error}"))?,
810        );
811    }
812    for (name, schema) in &index.functions {
813        index.function_types.insert(
814            name.clone(),
815            super::normalize_schema(schema)
816                .map_err(|error| format!("invalid function schema {name}: {error}"))?,
817        );
818    }
819    Ok(index)
820}
821
822fn collect_local_schema_references(form: &Form, namespace: &str, output: &mut Vec<String>) {
823    if let Form::List(reference) = form {
824        if reference.len() == 2
825            && matches!(&reference[0], Form::Symbol(operator) if operator == "var")
826        {
827            if let Form::Symbol(target) = &reference[1] {
828                if let Some((qualifier, local)) = target.rsplit_once('/') {
829                    if qualifier == namespace {
830                        output.push(local.to_owned());
831                    }
832                }
833            }
834            return;
835        }
836    }
837    match form {
838        Form::Tagged(_, value) => collect_local_schema_references(value, namespace, output),
839        Form::Metadata(metadata, value) => {
840            collect_local_schema_references(metadata, namespace, output);
841            collect_local_schema_references(value, namespace, output);
842        }
843        Form::Map(entries) => {
844            for (key, value) in entries {
845                collect_local_schema_references(key, namespace, output);
846                collect_local_schema_references(value, namespace, output);
847            }
848        }
849        Form::Set(values) | Form::Vector(values) | Form::List(values) => {
850            for value in values {
851                collect_local_schema_references(value, namespace, output);
852            }
853        }
854        _ => {}
855    }
856}
857
858#[cfg(test)]
859mod tests {
860    use super::*;
861    use crate::kernel::parse;
862
863    fn artifact_payload(forms: Vec<Form>) -> Vec<u8> {
864        encode_halc_module("demo.ns", "demo.hal", "", forms).unwrap()
865    }
866
867    #[test]
868    fn round_trips_primitive_values() {
869        let cases = [
870            "nil",
871            "true",
872            "false",
873            "42",
874            "-7",
875            "3.14",
876            "\"hello\"",
877            "\\x",
878            ":key",
879            ":ns/key",
880            "symbol",
881            "ns/symbol",
882        ];
883        for source in cases {
884            let original = parse(source).unwrap();
885            let bytes = artifact_payload(vec![original.clone()]);
886            let decoded = decode_halc(&bytes).unwrap();
887            assert_eq!(decoded.forms.len(), 1);
888            assert_eq!(decoded.forms[0], original, "{source}");
889        }
890    }
891
892    #[test]
893    fn round_trips_collections() {
894        let original = parse("(do {:a [1 2] :b #{3 4}})").unwrap();
895        let bytes = artifact_payload(vec![original.clone()]);
896        let decoded = decode_halc(&bytes).unwrap();
897        assert_eq!(decoded.forms.len(), 1);
898        assert_eq!(decoded.forms[0], original);
899    }
900
901    #[test]
902    fn maps_use_canonical_member_order() {
903        let map_a = artifact_payload(vec![parse("{2 \"b\" 1 \"a\"}").unwrap()]);
904        let map_b = artifact_payload(vec![parse("{1 \"a\" 2 \"b\"}").unwrap()]);
905        assert_eq!(map_a, map_b);
906    }
907
908    #[test]
909    fn schema_var_references_are_checked_and_namespace_canonicalized() {
910        let source = "(ns demo.schema) \
911                      (def Customer [:map [:id :int]]) \
912                      (defn ^{:schema #'-/Customer} customer-id [customer] customer)";
913        let bytes = encode_halc_module(
914            "demo.schema",
915            "demo/schema.hal",
916            source,
917            crate::kernel::parse_forms(source).unwrap(),
918        )
919        .unwrap();
920        let module = decode_halc(&bytes).unwrap();
921        let Form::List(definition) = &module.forms[2] else {
922            panic!("expected defn");
923        };
924        let Form::Metadata(metadata, _) = &definition[1] else {
925            panic!("expected definition metadata");
926        };
927        let Form::Map(metadata) = metadata.as_ref() else {
928            panic!("expected metadata map");
929        };
930        let schema = metadata
931            .iter()
932            .find_map(|(key, value)| {
933                matches!(key, Form::Keyword(name) if name == "schema").then_some(value)
934            })
935            .unwrap();
936        assert_eq!(
937            schema,
938            &Form::List(vec![
939                Form::Symbol("var".into()),
940                Form::Symbol("demo.schema/Customer".into()),
941            ])
942        );
943
944        let missing = "(ns demo.schema) \
945                       (defn ^{:schema #'MissingSchema} invalid [value] value)";
946        assert_eq!(
947            encode_halc_module(
948                "demo.schema",
949                "demo/schema.hal",
950                missing,
951                crate::kernel::parse_forms(missing).unwrap(),
952            )
953            .unwrap_err(),
954            "schema Var does not exist: MissingSchema"
955        );
956    }
957
958    #[test]
959    fn nested_schema_var_references_are_canonicalized_and_checked() {
960        let source = "(ns demo.schema) \
961                      (def Address [:map [:street :str]]) \
962                      (def Customer [:map [:address #'-/Address]]) \
963                      (defn ^{:schema #'Customer} save [customer] customer)";
964        let bytes = encode_halc_module(
965            "demo.schema",
966            "demo/schema.hal",
967            source,
968            crate::kernel::parse_forms(source).unwrap(),
969        )
970        .unwrap();
971        let module = decode_halc(&bytes).unwrap();
972        assert!(module.forms[2]
973            .to_string()
974            .contains("(var demo.schema/Address)"));
975        assert_eq!(module.schemas.functions.len(), 1);
976        assert!(module.schemas.functions.contains_key("demo.schema/save"));
977        assert_eq!(module.schemas.definitions.len(), 2);
978        assert!(module
979            .schemas
980            .definitions
981            .contains_key("demo.schema/Address"));
982        assert!(module
983            .schemas
984            .definitions
985            .contains_key("demo.schema/Customer"));
986        assert!(matches!(
987            module.schemas.resolved_function_type("demo.schema/save"),
988            Some(super::super::SchemaType::Map(fields)) if fields.len() == 1
989        ));
990
991        let missing = "(ns demo.schema) \
992                       (def Customer [:map [:address #'MissingAddress]]) \
993                       (defn ^{:schema #'Customer} save [customer] customer)";
994        assert_eq!(
995            encode_halc_module(
996                "demo.schema",
997                "demo/schema.hal",
998                missing,
999                crate::kernel::parse_forms(missing).unwrap(),
1000            )
1001            .unwrap_err(),
1002            "schema Var does not exist: MissingAddress"
1003        );
1004
1005        let recursive = "(ns demo.schema) \
1006                         (def Node [:map [:children [:vector #'Node]]]) \
1007                         (defn ^{:schema #'Node} walk [node] node)";
1008        assert!(encode_halc_module(
1009            "demo.schema",
1010            "demo/schema.hal",
1011            recursive,
1012            crate::kernel::parse_forms(recursive).unwrap(),
1013        )
1014        .is_ok());
1015
1016        let malformed = "(ns demo.schema) \
1017                         (def Customer [:map [:name]]) \
1018                         (defn ^{:schema #'Customer} save [customer] customer)";
1019        assert_eq!(
1020            encode_halc_module(
1021                "demo.schema",
1022                "demo/schema.hal",
1023                malformed,
1024                crate::kernel::parse_forms(malformed).unwrap(),
1025            )
1026            .unwrap_err(),
1027            "invalid schema demo.schema/Customer: :map schema fields must be [name type] or [name properties type]"
1028        );
1029    }
1030
1031    #[test]
1032    fn round_trips_metadata() {
1033        let original = parse("^:dynamic *value*").unwrap();
1034        let bytes = artifact_payload(vec![original.clone()]);
1035        let decoded = decode_halc(&bytes).unwrap();
1036        assert_eq!(decoded.forms, vec![original]);
1037    }
1038
1039    #[test]
1040    fn rejects_bad_magic() {
1041        let mut bytes = artifact_payload(vec![Form::Nil]);
1042        bytes[0] = 0;
1043        assert!(decode_halc(&bytes).unwrap_err().contains("bad magic"));
1044    }
1045
1046    #[test]
1047    fn rejects_checksum_mismatch() {
1048        let mut bytes = artifact_payload(vec![Form::Nil]);
1049        let last = bytes.len() - 1;
1050        bytes[last] = bytes[last].wrapping_add(1);
1051        assert!(decode_halc(&bytes).unwrap_err().contains("checksum"));
1052    }
1053
1054    #[test]
1055    fn decodes_the_truffle_portable_format_golden_artifact() {
1056        // This is the canonical v1 artifact emitted by Truffle's
1057        // HalcArtifactTest.goldenBytesLockThePortableFormat. Keep this test
1058        // independent of Rust's test-only encoder: it is the cross-runtime
1059        // compatibility boundary, rather than a Rust encoder/decoder
1060        // round-trip.
1061        let bytes = hex_bytes(concat!(
1062            "48414c43000100010000013f57211e103028689092d59627fbba64015c289acd1bc5b2e7be27ec53d8bf4c35",
1063            "00000001740000000174e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
1064            "0000001100010203000000000000002a044004000000000000050000001e313233343536373839303132",
1065            "333435363738393031323334353637383930060000000668c3a172c3a008000000780901000000056d792e6e73",
1066            "000000066d792d73796d000a00000000026b77000b00000002030000000000000001060000000161000c00000002",
1067            "030000000000000001060000000161000d00000002030000000000000001060000000161030000000000000002",
1068            "060000000162000e00000002030000000000000001030000000000000002000f00000002030000000000000002",
1069            "060000000162030000000000000001060000000161001000000002030000000000000002030000000000000001",
1070            "001100000003612b62",
1071        ));
1072
1073        let module = decode_halc(&bytes).unwrap();
1074        assert_eq!(module.origin, HalcOrigin::Halc);
1075        assert_eq!(module.namespace, "t");
1076        assert_eq!(module.resource, "t");
1077        assert_eq!(module.forms.len(), 17);
1078        assert_eq!(module.forms[0], Form::Nil);
1079        assert_eq!(module.forms[3], Form::Number(42));
1080        assert_eq!(module.forms[6], Form::String("hárà".into()));
1081        assert_eq!(module.forms[7], Form::Character('x'));
1082        assert_eq!(module.forms[8], Form::Symbol("my.ns/my-sym".into()));
1083        assert_eq!(module.forms[9], Form::Keyword("kw".into()));
1084        assert_eq!(module.forms[16], Form::Regex("a+b".into()));
1085    }
1086
1087    #[test]
1088    fn rejects_legacy_hir_magic() {
1089        let halc = artifact_payload(vec![Form::Number(42)]);
1090        let mut legacy = halc.clone();
1091        legacy[..4].copy_from_slice(b"HIR\0");
1092
1093        assert_eq!(decode_halc(&legacy).unwrap_err(), "bad magic");
1094        assert_eq!(&halc[..4], MAGIC);
1095    }
1096
1097    #[test]
1098    fn shared_cross_runtime_goldens_decode() {
1099        let complete = std::fs::read(crate::spec_registry::require(
1100            "01-lang/009-halc/draft/conformance/golden/complete.halc",
1101        ))
1102        .expect("complete HALC golden is readable");
1103        let current = decode_halc(&complete).unwrap();
1104        assert_eq!(current.origin, HalcOrigin::Halc);
1105        assert_eq!(current.namespace, "halc.conformance.complete");
1106        assert_eq!(current.resource, "conformance/complete.hal");
1107    }
1108
1109    #[test]
1110    fn registry_golden_matches_rust_encoding() {
1111        let source_path =
1112            crate::spec_registry::require("01-lang/009-halc/draft/conformance/complete.hal");
1113        let source = std::fs::read_to_string(source_path).expect("HALC source is readable");
1114        let forms = crate::kernel::parse_forms(&source).expect("HALC source parses");
1115        let encoded = encode_halc_module(
1116            "halc.conformance.complete",
1117            "conformance/complete.hal",
1118            &source,
1119            forms,
1120        )
1121        .expect("HALC source encodes");
1122        let expected = std::fs::read(crate::spec_registry::require(
1123            "01-lang/009-halc/draft/conformance/golden/complete.halc",
1124        ))
1125        .expect("HALC golden is readable");
1126        assert_eq!(expected, encoded);
1127    }
1128
1129    fn hex_bytes(hex: &str) -> Vec<u8> {
1130        (0..hex.len())
1131            .step_by(2)
1132            .map(|index| u8::from_str_radix(&hex[index..index + 2], 16).unwrap())
1133            .collect()
1134    }
1135}