1#![allow(missing_docs)]
24
25use std::collections::HashMap;
26use std::fmt;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
30pub struct NodeId(pub u32);
31
32impl NodeId {
33 pub const NONE: NodeId = NodeId(u32::MAX);
35
36 pub fn index(self) -> usize {
37 self.0 as usize
38 }
39
40 pub fn is_none(self) -> bool {
41 self == NodeId::NONE
42 }
43}
44
45impl fmt::Display for NodeId {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 write!(f, "n{}", self.0)
48 }
49}
50
51#[derive(Clone)]
60pub struct SecretBytes(Vec<u8>);
61
62impl SecretBytes {
63 pub fn new(bytes: impl Into<Vec<u8>>) -> Self {
65 SecretBytes(bytes.into())
66 }
67
68 pub fn len(&self) -> usize {
71 self.0.len()
72 }
73
74 pub fn is_empty(&self) -> bool {
75 self.0.is_empty()
76 }
77
78 pub(crate) fn expose(&self) -> &[u8] {
81 &self.0
82 }
83}
84
85impl fmt::Debug for SecretBytes {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 f.write_str("Secret(***)")
88 }
89}
90
91impl fmt::Display for SecretBytes {
92 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93 f.write_str("***SECRET***")
94 }
95}
96
97impl PartialEq for SecretBytes {
98 fn eq(&self, _other: &Self) -> bool {
100 false
101 }
102}
103
104impl From<&str> for SecretBytes {
105 fn from(value: &str) -> Self {
106 SecretBytes(value.as_bytes().to_vec())
107 }
108}
109
110impl From<String> for SecretBytes {
111 fn from(value: String) -> Self {
112 SecretBytes(value.into_bytes())
113 }
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
118pub enum BinOp {
119 Add,
120 Sub,
121 Mul,
122 Div,
123 Eq,
124 Ne,
125 Lt,
126 Le,
127 Gt,
128 Ge,
129 And,
130 Or,
131}
132
133impl BinOp {
134 pub const fn code(self) -> u32 {
135 match self {
136 BinOp::Add => 0,
137 BinOp::Sub => 1,
138 BinOp::Mul => 2,
139 BinOp::Div => 3,
140 BinOp::Eq => 4,
141 BinOp::Ne => 5,
142 BinOp::Lt => 6,
143 BinOp::Le => 7,
144 BinOp::Gt => 8,
145 BinOp::Ge => 9,
146 BinOp::And => 10,
147 BinOp::Or => 11,
148 }
149 }
150
151 pub const fn from_code(code: u32) -> Option<Self> {
152 let op = match code {
153 0 => BinOp::Add,
154 1 => BinOp::Sub,
155 2 => BinOp::Mul,
156 3 => BinOp::Div,
157 4 => BinOp::Eq,
158 5 => BinOp::Ne,
159 6 => BinOp::Lt,
160 7 => BinOp::Le,
161 8 => BinOp::Gt,
162 9 => BinOp::Ge,
163 10 => BinOp::And,
164 11 => BinOp::Or,
165 _ => return None,
166 };
167 Some(op)
168 }
169
170 pub const fn as_str(self) -> &'static str {
171 match self {
172 BinOp::Add => "+",
173 BinOp::Sub => "-",
174 BinOp::Mul => "*",
175 BinOp::Div => "/",
176 BinOp::Eq => "==",
177 BinOp::Ne => "!=",
178 BinOp::Lt => "<",
179 BinOp::Le => "<=",
180 BinOp::Gt => ">",
181 BinOp::Ge => ">=",
182 BinOp::And => "and",
183 BinOp::Or => "or",
184 }
185 }
186}
187
188#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
190pub enum UnOp {
191 Neg,
192 Not,
193}
194
195impl UnOp {
196 pub const fn code(self) -> u32 {
197 match self {
198 UnOp::Neg => 0,
199 UnOp::Not => 1,
200 }
201 }
202
203 pub const fn from_code(code: u32) -> Option<Self> {
204 match code {
205 0 => Some(UnOp::Neg),
206 1 => Some(UnOp::Not),
207 _ => None,
208 }
209 }
210
211 pub const fn as_str(self) -> &'static str {
212 match self {
213 UnOp::Neg => "-",
214 UnOp::Not => "!",
215 }
216 }
217}
218
219#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
221pub enum SemanticTag {
222 Prompt,
223 Policy,
224 Template,
225}
226
227impl SemanticTag {
228 pub const fn code(self) -> u32 {
229 match self {
230 SemanticTag::Prompt => 0,
231 SemanticTag::Policy => 1,
232 SemanticTag::Template => 2,
233 }
234 }
235
236 pub const fn from_code(code: u32) -> Option<Self> {
237 match code {
238 0 => Some(SemanticTag::Prompt),
239 1 => Some(SemanticTag::Policy),
240 2 => Some(SemanticTag::Template),
241 _ => None,
242 }
243 }
244
245 pub const fn as_str(self) -> &'static str {
246 match self {
247 SemanticTag::Prompt => "prompt",
248 SemanticTag::Policy => "policy",
249 SemanticTag::Template => "template",
250 }
251 }
252}
253
254#[derive(Clone, PartialEq)]
256pub enum InterpPart {
257 Literal(String),
258 Expr(NodeId),
259}
260
261impl fmt::Debug for InterpPart {
262 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263 match self {
264 InterpPart::Literal(text) => write!(f, "Literal(<{} bytes>)", text.len()),
265 InterpPart::Expr(id) => write!(f, "Expr({id})"),
266 }
267 }
268}
269
270#[derive(Clone, PartialEq)]
273pub enum Constraint {
274 MinMax { min: Option<i64>, max: Option<i64> },
275 Enum(Vec<String>),
276 Pattern(String),
277 Odd,
278 Custom(String),
279}
280
281impl fmt::Debug for Constraint {
282 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283 match self {
284 Constraint::MinMax { min, max } => {
285 write!(f, "MinMax {{ min: {min:?}, max: {max:?} }}")
286 }
287 Constraint::Enum(values) => write!(f, "Enum(<{} values>)", values.len()),
288 Constraint::Pattern(_) => f.write_str("Pattern(<redacted>)"),
289 Constraint::Odd => f.write_str("Odd"),
290 Constraint::Custom(_) => f.write_str("Custom(<redacted>)"),
291 }
292 }
293}
294
295#[derive(Clone, PartialEq)]
297pub enum TypeSpec {
298 Null,
299 Bool,
300 Int,
301 Float,
302 String,
303 Duration,
304 Bytes,
305 Table { closed: bool },
306 Enum(Vec<String>),
307 Secret,
308 Any,
309 Blob,
310}
311
312impl fmt::Debug for TypeSpec {
313 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
314 match self {
315 TypeSpec::Table { closed } => write!(f, "Table {{ closed: {closed} }}"),
316 TypeSpec::Enum(values) => write!(f, "Enum(<{} values>)", values.len()),
317 other => f.write_str(other.name().as_str()),
318 }
319 }
320}
321
322impl TypeSpec {
323 pub const fn code(&self) -> u32 {
324 match self {
325 TypeSpec::Null => 0,
326 TypeSpec::Bool => 1,
327 TypeSpec::Int => 2,
328 TypeSpec::Float => 3,
329 TypeSpec::String => 4,
330 TypeSpec::Duration => 5,
331 TypeSpec::Bytes => 6,
332 TypeSpec::Table { .. } => 7,
333 TypeSpec::Enum(_) => 8,
334 TypeSpec::Secret => 9,
335 TypeSpec::Any => 10,
336 TypeSpec::Blob => 11,
337 }
338 }
339
340 pub fn name(&self) -> String {
342 match self {
343 TypeSpec::Null => "Null".into(),
344 TypeSpec::Bool => "Bool".into(),
345 TypeSpec::Int => "Int".into(),
346 TypeSpec::Float => "Float".into(),
347 TypeSpec::String => "String".into(),
348 TypeSpec::Duration => "Duration".into(),
349 TypeSpec::Bytes => "Bytes".into(),
350 TypeSpec::Table { closed } => {
351 if *closed {
352 "Table(closed)".into()
353 } else {
354 "Table".into()
355 }
356 }
357 TypeSpec::Enum(values) => format!("Enum({})", values.join(", ")),
358 TypeSpec::Secret => "Secret".into(),
359 TypeSpec::Any => "Any".into(),
360 TypeSpec::Blob => "Blob".into(),
361 }
362 }
363}
364
365#[derive(Clone, PartialEq)]
367pub enum PatchOp {
368 Set { path: String, value: NodeId },
369 Remove { path: String },
370 Rename { from: String, to: String },
371}
372
373impl fmt::Debug for PatchOp {
374 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
375 match self {
376 PatchOp::Set { value, .. } => write!(f, "Set {{ path: <redacted>, value: {value} }}"),
377 PatchOp::Remove { .. } => f.write_str("Remove { path: <redacted> }"),
378 PatchOp::Rename { .. } => f.write_str("Rename { from: <redacted>, to: <redacted> }"),
379 }
380 }
381}
382
383#[derive(Clone, PartialEq)]
385pub struct SchemaFieldSpec {
386 pub typ: TypeSpec,
387 pub min: Option<NodeId>,
388 pub max: Option<NodeId>,
389 pub min_len: Option<NodeId>,
390 pub max_len: Option<NodeId>,
391 pub pattern: Option<NodeId>,
392 pub default: Option<NodeId>,
393 pub deprecated: Option<String>,
394}
395
396impl fmt::Debug for SchemaFieldSpec {
397 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
398 f.debug_struct("SchemaFieldSpec")
399 .field("typ", &self.typ)
400 .field("min", &self.min)
401 .field("max", &self.max)
402 .field("min_len", &self.min_len)
403 .field("max_len", &self.max_len)
404 .field("pattern", &self.pattern)
405 .field("default", &self.default)
406 .field(
407 "deprecated",
408 &self.deprecated.as_ref().map(|text| text.len()),
409 )
410 .finish()
411 }
412}
413
414impl SchemaFieldSpec {
415 pub fn bare(typ: TypeSpec) -> Self {
416 Self {
417 typ,
418 min: None,
419 max: None,
420 min_len: None,
421 max_len: None,
422 pattern: None,
423 default: None,
424 deprecated: None,
425 }
426 }
427}
428
429#[derive(Clone, PartialEq)]
440pub enum Inode {
441 Null,
442 Bool(bool),
443 Int(i64),
444 Float(f64),
445 Str(String),
446 DurationMs(u64),
447 Quantity {
448 value: NodeId,
449 unit: super::UnitTag,
450 },
451 Array {
452 elements: Vec<NodeId>,
453 },
454 Table {
455 fields: Vec<(String, NodeId)>,
456 },
457 Secret(SecretBytes),
459 SecretRef {
462 locator: NodeId,
463 },
464 Ref(NodeId),
465 Env {
466 name: String,
467 default: Option<NodeId>,
468 },
469 Ident(String),
470 Interpolate {
471 parts: Vec<InterpPart>,
472 },
473 Binary {
474 op: BinOp,
475 lhs: NodeId,
476 rhs: NodeId,
477 },
478 Unary {
479 op: UnOp,
480 expr: NodeId,
481 },
482 Constrained {
483 target: NodeId,
484 constraint: Constraint,
485 },
486 Patch(PatchOp),
487 SchemaField(SchemaFieldSpec),
488 SchemaTable {
489 fields: Vec<(String, NodeId)>,
490 closed: bool,
491 },
492 SchemaArray {
493 element: NodeId,
494 },
495 Semantic {
496 tag: SemanticTag,
497 inner: NodeId,
498 },
499 ContentAddressed {
501 media_type: NodeId,
502 data: NodeId,
503 },
504 Blob {
510 hash: [u8; 32],
511 size: u64,
512 tainted: bool,
513 media_type: String,
514 payload: Option<Vec<u8>>,
515 },
516}
517
518impl fmt::Debug for Inode {
519 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
520 match self {
521 Inode::Null => f.write_str("Null"),
522 Inode::Bool(value) => write!(f, "Bool({value})"),
523 Inode::Int(value) => write!(f, "Int({value})"),
524 Inode::Float(value) => write!(f, "Float({value})"),
525 Inode::Str(value) => write!(f, "Str(<{} bytes>)", value.len()),
526 Inode::DurationMs(ms) => write!(f, "DurationMs({ms})"),
527 Inode::Quantity { value, .. } => write!(f, "Quantity({value}, <unit>)"),
528 Inode::Array { elements } => write!(f, "Array({elements:?})"),
529 Inode::Table { fields } => write!(f, "Table(<{} fields>)", fields.len()),
530 Inode::Secret(_) => f.write_str("Secret(***)"),
531 Inode::SecretRef { locator } => write!(f, "SecretRef({locator})"),
532 Inode::Ref(id) => write!(f, "Ref({id})"),
533 Inode::Env { name, default } => write!(f, "Env(<{} bytes>, {default:?})", name.len()),
534 Inode::Ident(name) => write!(f, "Ident(<{} bytes>)", name.len()),
535 Inode::Interpolate { parts } => write!(f, "Interpolate(<{} parts>)", parts.len()),
536 Inode::Binary { op, lhs, rhs } => write!(f, "Binary({}, {lhs}, {rhs})", op.as_str()),
537 Inode::Unary { op, expr } => write!(f, "Unary({}, {expr})", op.as_str()),
538 Inode::Constrained { target, .. } => {
539 write!(f, "Constrained({target}, <constraint>)")
540 }
541 Inode::Patch(_) => f.write_str("Patch(<op>)"),
542 Inode::SchemaField(_) => f.write_str("SchemaField(<spec>)"),
543 Inode::SchemaTable { fields, closed } => {
544 write!(f, "SchemaTable(<{} fields>, closed={closed})", fields.len())
545 }
546 Inode::SchemaArray { element } => write!(f, "SchemaArray({element})"),
547 Inode::Semantic { tag, inner } => write!(f, "Semantic({}, {inner})", tag.as_str()),
548 Inode::ContentAddressed { data, .. } => {
549 write!(f, "ContentAddressed(<media>, {data})")
550 }
551 Inode::Blob {
552 hash,
553 size,
554 tainted,
555 media_type,
556 payload,
557 } => write!(
558 f,
559 "Blob(blake3:{}, size={size}, media=<{} bytes>, tainted={tainted}, payload={})",
560 hash_hex(hash),
561 media_type.len(),
562 match payload {
563 Some(bytes) => format!("{} bytes", bytes.len()),
564 None => "absent".to_string(),
565 }
566 ),
567 }
568 }
569}
570
571pub fn hash_hex(hash: &[u8; 32]) -> String {
573 let mut out = String::with_capacity(64);
574 for byte in hash {
575 out.push_str(&format!("{byte:02x}"));
576 }
577 out
578}
579
580pub fn parse_hash_hex(text: &str) -> Option<[u8; 32]> {
582 if text.len() != 64 {
583 return None;
584 }
585 let mut out = [0u8; 32];
586 for (index, pair) in text.as_bytes().chunks_exact(2).enumerate() {
587 let hi = (pair[0] as char).to_digit(16)?;
588 let lo = (pair[1] as char).to_digit(16)?;
589 out[index] = (hi * 16 + lo) as u8;
590 }
591 Some(out)
592}
593
594#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
599#[allow(missing_docs)]
600pub enum SerializedKind {
601 Null,
602 Bool,
603 Int,
604 Float,
605 Str,
606 DurationMs,
607 Quantity,
608 Array,
609 Table,
610 Secret,
611 SecretRef,
612 Ref,
613 Env,
614 Ident,
615 Interpolate,
616 Binary,
617 Unary,
618 Constrained,
619 Patch,
620 SchemaField,
621 SchemaTable,
622 SchemaArray,
623 Semantic,
624 Blob,
625 ContentAddressed,
626}
627
628impl SerializedKind {
629 pub const fn code(self) -> u8 {
630 match self {
631 SerializedKind::Null => 0,
632 SerializedKind::Bool => 1,
633 SerializedKind::Int => 2,
634 SerializedKind::Float => 3,
635 SerializedKind::Str => 4,
636 SerializedKind::DurationMs => 5,
637 SerializedKind::Quantity => 6,
638 SerializedKind::Array => 7,
639 SerializedKind::Table => 8,
640 SerializedKind::Secret => 9,
641 SerializedKind::SecretRef => 10,
642 SerializedKind::Ref => 11,
643 SerializedKind::Env => 12,
644 SerializedKind::Ident => 13,
645 SerializedKind::Interpolate => 14,
646 SerializedKind::Binary => 15,
647 SerializedKind::Unary => 16,
648 SerializedKind::Constrained => 17,
649 SerializedKind::Patch => 18,
650 SerializedKind::SchemaField => 19,
651 SerializedKind::SchemaTable => 20,
652 SerializedKind::SchemaArray => 21,
653 SerializedKind::Semantic => 22,
654 SerializedKind::Blob => 23,
655 SerializedKind::ContentAddressed => 24,
656 }
657 }
658
659 pub const fn from_code(code: u8) -> Option<Self> {
660 let kind = match code {
661 0 => SerializedKind::Null,
662 1 => SerializedKind::Bool,
663 2 => SerializedKind::Int,
664 3 => SerializedKind::Float,
665 4 => SerializedKind::Str,
666 5 => SerializedKind::DurationMs,
667 6 => SerializedKind::Quantity,
668 7 => SerializedKind::Array,
669 8 => SerializedKind::Table,
670 9 => SerializedKind::Secret,
671 10 => SerializedKind::SecretRef,
672 11 => SerializedKind::Ref,
673 12 => SerializedKind::Env,
674 13 => SerializedKind::Ident,
675 14 => SerializedKind::Interpolate,
676 15 => SerializedKind::Binary,
677 16 => SerializedKind::Unary,
678 17 => SerializedKind::Constrained,
679 18 => SerializedKind::Patch,
680 19 => SerializedKind::SchemaField,
681 20 => SerializedKind::SchemaTable,
682 21 => SerializedKind::SchemaArray,
683 22 => SerializedKind::Semantic,
684 23 => SerializedKind::Blob,
685 24 => SerializedKind::ContentAddressed,
686 _ => return None,
687 };
688 Some(kind)
689 }
690
691 pub const fn name(self) -> &'static str {
692 crate::view::node_kind_name(self.code())
693 }
694}
695
696impl Inode {
697 pub const fn kind(&self) -> SerializedKind {
699 match self {
700 Inode::Null => SerializedKind::Null,
701 Inode::Bool(_) => SerializedKind::Bool,
702 Inode::Int(_) => SerializedKind::Int,
703 Inode::Float(_) => SerializedKind::Float,
704 Inode::Str(_) => SerializedKind::Str,
705 Inode::DurationMs(_) => SerializedKind::DurationMs,
706 Inode::Quantity { .. } => SerializedKind::Quantity,
707 Inode::Array { .. } => SerializedKind::Array,
708 Inode::Table { .. } => SerializedKind::Table,
709 Inode::Secret(_) => SerializedKind::Secret,
710 Inode::SecretRef { .. } => SerializedKind::SecretRef,
711 Inode::Ref(_) => SerializedKind::Ref,
712 Inode::Env { .. } => SerializedKind::Env,
713 Inode::Ident(_) => SerializedKind::Ident,
714 Inode::Interpolate { .. } => SerializedKind::Interpolate,
715 Inode::Binary { .. } => SerializedKind::Binary,
716 Inode::Unary { .. } => SerializedKind::Unary,
717 Inode::Constrained { .. } => SerializedKind::Constrained,
718 Inode::Patch(_) => SerializedKind::Patch,
719 Inode::SchemaField(_) => SerializedKind::SchemaField,
720 Inode::SchemaTable { .. } => SerializedKind::SchemaTable,
721 Inode::SchemaArray { .. } => SerializedKind::SchemaArray,
722 Inode::Semantic { .. } => SerializedKind::Semantic,
723 Inode::Blob { .. } => SerializedKind::Blob,
724 Inode::ContentAddressed { .. } => SerializedKind::ContentAddressed,
725 }
726 }
727}
728
729impl Inode {
730 pub const fn kind_code(&self) -> u8 {
732 match self {
733 Inode::Null => 0,
734 Inode::Bool(_) => 1,
735 Inode::Int(_) => 2,
736 Inode::Float(_) => 3,
737 Inode::Str(_) => 4,
738 Inode::DurationMs(_) => 5,
739 Inode::Quantity { .. } => 6,
740 Inode::Array { .. } => 7,
741 Inode::Table { .. } => 8,
742 Inode::Secret(_) => 9,
743 Inode::SecretRef { .. } => 10,
744 Inode::Ref(_) => 11,
745 Inode::Env { .. } => 12,
746 Inode::Ident(_) => 13,
747 Inode::Interpolate { .. } => 14,
748 Inode::Binary { .. } => 15,
749 Inode::Unary { .. } => 16,
750 Inode::Constrained { .. } => 17,
751 Inode::Patch(_) => 18,
752 Inode::SchemaField(_) => 19,
753 Inode::SchemaTable { .. } => 20,
754 Inode::SchemaArray { .. } => 21,
755 Inode::Semantic { .. } => 22,
756 Inode::Blob { .. } => 23,
757 Inode::ContentAddressed { .. } => 24,
758 }
759 }
760
761 pub const fn is_secret(&self) -> bool {
763 matches!(self, Inode::Secret(_) | Inode::SecretRef { .. })
764 }
765
766 pub fn children(&self) -> Vec<NodeId> {
772 match self {
773 Inode::Quantity { value, .. } => vec![*value],
774 Inode::Array { elements } => elements.clone(),
775 Inode::Table { fields } => fields.iter().map(|(_, id)| *id).collect(),
776 Inode::SecretRef { locator } => vec![*locator],
777 Inode::Ref(id) => vec![*id],
778 Inode::Env { default, .. } => default.iter().copied().collect(),
779 Inode::Interpolate { parts } => parts
780 .iter()
781 .filter_map(|part| match part {
782 InterpPart::Expr(id) => Some(*id),
783 InterpPart::Literal(_) => None,
784 })
785 .collect(),
786 Inode::Binary { lhs, rhs, .. } => vec![*lhs, *rhs],
787 Inode::Unary { expr, .. } => vec![*expr],
788 Inode::Constrained { target, .. } => vec![*target],
789 Inode::Patch(op) => match op {
790 PatchOp::Set { value, .. } => vec![*value],
791 PatchOp::Remove { .. } | PatchOp::Rename { .. } => Vec::new(),
792 },
793 Inode::SchemaField(spec) => {
794 let mut out = Vec::new();
795 for id in [
796 spec.min,
797 spec.max,
798 spec.min_len,
799 spec.max_len,
800 spec.pattern,
801 spec.default,
802 ]
803 .into_iter()
804 .flatten()
805 {
806 out.push(id);
807 }
808 out
809 }
810 Inode::SchemaTable { fields, .. } => fields.iter().map(|(_, id)| *id).collect(),
811 Inode::SchemaArray { element } => vec![*element],
812 Inode::Semantic { inner, .. } => vec![*inner],
813 Inode::ContentAddressed { media_type, data } => vec![*media_type, *data],
814 Inode::Null
815 | Inode::Bool(_)
816 | Inode::Int(_)
817 | Inode::Float(_)
818 | Inode::Str(_)
819 | Inode::DurationMs(_)
820 | Inode::Secret(_)
821 | Inode::Ident(_)
822 | Inode::Blob { .. } => Vec::new(),
823 }
824 }
825
826 pub fn strings(&self, out: &mut Vec<String>) {
828 match self {
829 Inode::Str(s) | Inode::Ident(s) => out.push(s.clone()),
830 Inode::Table { fields } => {
831 for (name, _) in fields {
832 out.push(name.clone());
833 }
834 }
835 Inode::Env { name, .. } => out.push(name.clone()),
836 Inode::Interpolate { parts } => {
837 for part in parts {
838 if let InterpPart::Literal(s) = part {
839 out.push(s.clone());
840 }
841 }
842 }
843 Inode::Constrained { constraint, .. } => match constraint {
844 Constraint::Enum(items) => out.extend(items.iter().cloned()),
845 Constraint::Pattern(s) | Constraint::Custom(s) => out.push(s.clone()),
846 Constraint::MinMax { .. } | Constraint::Odd => {}
847 },
848 Inode::Patch(op) => match op {
849 PatchOp::Set { path, .. } | PatchOp::Remove { path } => out.push(path.clone()),
850 PatchOp::Rename { from, to } => {
851 out.push(from.clone());
852 out.push(to.clone());
853 }
854 },
855 Inode::SchemaField(spec) => {
856 if let TypeSpec::Enum(items) = &spec.typ {
857 out.extend(items.iter().cloned());
858 }
859 if let Some(deprecated) = &spec.deprecated {
860 out.push(deprecated.clone());
861 }
862 }
863 Inode::SchemaTable { fields, .. } => {
864 for (name, _) in fields {
865 out.push(name.clone());
866 }
867 }
868 Inode::Blob { media_type, .. } => out.push(media_type.clone()),
869 Inode::Null
870 | Inode::Bool(_)
871 | Inode::Int(_)
872 | Inode::Float(_)
873 | Inode::DurationMs(_)
874 | Inode::Quantity { .. }
875 | Inode::Array { .. }
876 | Inode::Secret(_)
877 | Inode::SecretRef { .. }
878 | Inode::Ref(_)
879 | Inode::Unary { .. }
880 | Inode::Binary { .. }
881 | Inode::SchemaArray { .. }
882 | Inode::Semantic { .. }
883 | Inode::ContentAddressed { .. } => {}
884 }
885 }
886}
887
888#[derive(Clone, PartialEq)]
890pub enum AnnotationKind {
891 Intent(String),
893 Constraint { source: String, expr: NodeId },
895 AiImmutable(bool),
897}
898
899impl fmt::Debug for AnnotationKind {
900 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
901 match self {
902 AnnotationKind::Intent(text) => write!(f, "Intent(<{} bytes>)", text.len()),
903 AnnotationKind::Constraint { expr, .. } => {
904 write!(f, "Constraint {{ source: <redacted>, expr: {expr} }}")
905 }
906 AnnotationKind::AiImmutable(flag) => write!(f, "AiImmutable({flag})"),
907 }
908 }
909}
910
911#[derive(Debug, Clone, PartialEq)]
913pub struct Annotation {
914 pub node: NodeId,
915 pub kind: AnnotationKind,
916}
917
918#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
920pub enum ProvKind {
921 Text,
922 Int,
923 Flag,
924 Hash,
925 Input,
927}
928
929impl ProvKind {
930 pub const fn code(self) -> u32 {
931 match self {
932 ProvKind::Text => 0,
933 ProvKind::Int => 1,
934 ProvKind::Flag => 2,
935 ProvKind::Hash => 3,
936 ProvKind::Input => 4,
937 }
938 }
939
940 pub const fn from_code(code: u32) -> Option<Self> {
941 match code {
942 0 => Some(ProvKind::Text),
943 1 => Some(ProvKind::Int),
944 2 => Some(ProvKind::Flag),
945 3 => Some(ProvKind::Hash),
946 4 => Some(ProvKind::Input),
947 _ => None,
948 }
949 }
950}
951
952#[derive(Clone, PartialEq)]
954pub enum ProvValue {
955 Text(String),
956 Int(i64),
957 Flag(bool),
958 Hash([u8; 32]),
959}
960
961impl fmt::Debug for ProvValue {
962 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
963 match self {
964 ProvValue::Text(text) => write!(f, "Text(<{} bytes>)", text.len()),
965 ProvValue::Int(value) => write!(f, "Int({value})"),
966 ProvValue::Flag(flag) => write!(f, "Flag({flag})"),
967 ProvValue::Hash(hash) => write!(f, "Hash(blake3:{})", hash_hex(hash)),
968 }
969 }
970}
971
972#[derive(Clone, PartialEq)]
974pub struct ProvEntry {
975 pub key: String,
976 pub value: ProvValue,
977}
978
979impl fmt::Debug for ProvEntry {
980 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
981 write!(
982 f,
983 "ProvEntry {{ key: <{} bytes>, value: {:?} }}",
984 self.key.len(),
985 self.value
986 )
987 }
988}
989
990impl ProvEntry {
991 pub fn kind(&self) -> ProvKind {
992 match self.value {
993 ProvValue::Text(_) => ProvKind::Text,
994 ProvValue::Int(_) => ProvKind::Int,
995 ProvValue::Flag(_) => ProvKind::Flag,
996 ProvValue::Hash(_) => ProvKind::Hash,
997 }
998 }
999}
1000
1001#[derive(Clone, PartialEq, Default)]
1003pub struct Provenance {
1004 pub producer: Option<String>,
1006 pub timestamp_ms: u64,
1008 pub model: Option<String>,
1010 pub tool: Option<String>,
1012 pub parent: Option<[u8; 32]>,
1014 pub entries: Vec<ProvEntry>,
1016}
1017
1018impl fmt::Debug for Provenance {
1019 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1020 f.debug_struct("Provenance")
1021 .field("producer", &self.producer.as_ref().map(|text| text.len()))
1022 .field("timestamp_ms", &self.timestamp_ms)
1023 .field("model", &self.model.as_ref().map(|text| text.len()))
1024 .field("tool", &self.tool.as_ref().map(|text| text.len()))
1025 .field("parent", &self.parent)
1026 .field("entries", &self.entries)
1027 .finish()
1028 }
1029}
1030
1031#[derive(Clone, Default)]
1037pub struct InfinityModule {
1038 nodes: Vec<Inode>,
1039 root: Option<NodeId>,
1040 bindings: Vec<(String, NodeId)>,
1041 annotations: Vec<Annotation>,
1042 provenance: Option<Provenance>,
1043 extensions: Vec<(u64, Vec<u8>)>,
1044}
1045
1046impl fmt::Debug for InfinityModule {
1052 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1053 let secrets = crate::interop::secret_texts(self);
1054 let redact = |text: &str| crate::canonical::redact_text(text, &secrets);
1055 let mut redacted = self.clone();
1056 redact_for_debug(&mut redacted, &redact);
1057 let extensions: Vec<(u64, usize)> = redacted
1058 .extensions
1059 .iter()
1060 .map(|(tag, payload)| (*tag, payload.len()))
1061 .collect();
1062 f.debug_struct("InfinityModule")
1063 .field("nodes", &redacted.nodes)
1064 .field("root", &redacted.root)
1065 .field("bindings", &redacted.bindings)
1066 .field("annotations", &redacted.annotations)
1067 .field("provenance", &redacted.provenance)
1068 .field("extensions", &extensions)
1069 .finish()
1070 }
1071}
1072
1073fn redact_for_debug(module: &mut InfinityModule, redact: &impl Fn(&str) -> String) {
1074 for node in &mut module.nodes {
1075 redact_inode(node, redact);
1076 }
1077 for (name, _) in &mut module.bindings {
1078 *name = redact(name);
1079 }
1080 for annotation in &mut module.annotations {
1081 match &mut annotation.kind {
1082 AnnotationKind::Intent(text) => *text = redact(text),
1083 AnnotationKind::Constraint { source, .. } => *source = redact(source),
1084 AnnotationKind::AiImmutable(_) => {}
1085 }
1086 }
1087 if let Some(prov) = &mut module.provenance {
1088 if let Some(text) = &mut prov.producer {
1089 *text = redact(text);
1090 }
1091 if let Some(text) = &mut prov.model {
1092 *text = redact(text);
1093 }
1094 if let Some(text) = &mut prov.tool {
1095 *text = redact(text);
1096 }
1097 for entry in &mut prov.entries {
1098 entry.key = redact(&entry.key);
1099 if let ProvValue::Text(text) = &mut entry.value {
1100 *text = redact(text);
1101 }
1102 }
1103 }
1104}
1105
1106fn redact_inode(node: &mut Inode, redact: &impl Fn(&str) -> String) {
1107 match node {
1108 Inode::Str(text) => *text = redact(text),
1109 Inode::Quantity { unit, .. } => {
1110 if let super::UnitTag::Named(name) = unit {
1111 *name = redact(name);
1112 }
1113 }
1114 Inode::Table { fields } | Inode::SchemaTable { fields, .. } => {
1115 for (name, _) in fields {
1116 *name = redact(name);
1117 }
1118 }
1119 Inode::Env { name, .. } => *name = redact(name),
1120 Inode::Ident(text) => *text = redact(text),
1121 Inode::Interpolate { parts } => {
1122 for part in parts {
1123 if let InterpPart::Literal(text) = part {
1124 *text = redact(text);
1125 }
1126 }
1127 }
1128 Inode::Constrained { constraint, .. } => match constraint {
1129 Constraint::Enum(values) => {
1130 for value in values {
1131 *value = redact(value);
1132 }
1133 }
1134 Constraint::Pattern(text) | Constraint::Custom(text) => *text = redact(text),
1135 Constraint::MinMax { .. } | Constraint::Odd => {}
1136 },
1137 Inode::Patch(op) => match op {
1138 PatchOp::Set { path, .. } | PatchOp::Remove { path } => *path = redact(path),
1139 PatchOp::Rename { from, to } => {
1140 *from = redact(from);
1141 *to = redact(to);
1142 }
1143 },
1144 Inode::SchemaField(spec) => {
1145 if let TypeSpec::Enum(values) = &mut spec.typ {
1146 for value in values {
1147 *value = redact(value);
1148 }
1149 }
1150 if let Some(text) = &mut spec.deprecated {
1151 *text = redact(text);
1152 }
1153 }
1154 Inode::Blob { media_type, .. } => *media_type = redact(media_type),
1155 Inode::Null
1156 | Inode::Bool(_)
1157 | Inode::Int(_)
1158 | Inode::Float(_)
1159 | Inode::DurationMs(_)
1160 | Inode::Array { .. }
1161 | Inode::Secret(_)
1162 | Inode::SecretRef { .. }
1163 | Inode::Ref(_)
1164 | Inode::Binary { .. }
1165 | Inode::Unary { .. }
1166 | Inode::SchemaArray { .. }
1167 | Inode::Semantic { .. }
1168 | Inode::ContentAddressed { .. } => {}
1169 }
1170}
1171
1172impl PartialEq for InfinityModule {
1173 fn eq(&self, other: &Self) -> bool {
1183 self.structurally_eq(other)
1184 }
1185}
1186
1187fn node_eq(left: &Inode, right: &Inode) -> bool {
1189 match (left, right) {
1190 (Inode::Secret(_), Inode::Secret(_)) => true,
1191 (Inode::Secret(_), _) | (_, Inode::Secret(_)) => false,
1192 (Inode::Float(a), Inode::Float(b)) => a.to_bits() == b.to_bits(),
1195 _ => left == right,
1196 }
1197}
1198
1199impl InfinityModule {
1200 pub fn new() -> Self {
1201 Self::default()
1202 }
1203
1204 pub fn push(&mut self, node: Inode) -> NodeId {
1206 let id = NodeId(u32::try_from(self.nodes.len()).expect("arena exceeds u32 ids"));
1207 self.nodes.push(node);
1208 id
1209 }
1210
1211 pub fn add_node(&mut self, node: Inode) -> NodeId {
1213 self.push(node)
1214 }
1215
1216 pub fn get(&self, id: NodeId) -> Option<&Inode> {
1217 self.nodes.get(id.index())
1218 }
1219
1220 pub fn get_mut(&mut self, id: NodeId) -> Option<&mut Inode> {
1221 self.nodes.get_mut(id.index())
1222 }
1223
1224 pub fn node_mut(&mut self, id: NodeId) -> Result<&mut Inode, super::Error> {
1225 self.nodes
1226 .get_mut(id.index())
1227 .ok_or(super::Error::NodeRef(id.0))
1228 }
1229
1230 pub fn nodes(&self) -> &[Inode] {
1231 &self.nodes
1232 }
1233
1234 pub fn node_count(&self) -> usize {
1235 self.nodes.len()
1236 }
1237
1238 pub fn is_empty(&self) -> bool {
1239 self.nodes.is_empty()
1240 }
1241
1242 pub fn root(&self) -> Option<NodeId> {
1243 self.root
1244 }
1245
1246 pub fn set_root(&mut self, root: Option<NodeId>) {
1247 self.root = root;
1248 }
1249
1250 pub fn bindings(&self) -> &[(String, NodeId)] {
1251 &self.bindings
1252 }
1253
1254 pub fn bind(&mut self, name: impl Into<String>, id: NodeId) {
1256 let name = name.into();
1257 if let Some(slot) = self.bindings.iter_mut().find(|(n, _)| *n == name) {
1258 slot.1 = id;
1259 } else {
1260 self.bindings.push((name, id));
1261 }
1262 }
1263
1264 pub fn annotations(&self) -> &[Annotation] {
1265 &self.annotations
1266 }
1267
1268 pub fn annotate(&mut self, node: NodeId, kind: AnnotationKind) {
1269 self.annotations.push(Annotation { node, kind });
1270 }
1271
1272 pub fn clear_annotations(&mut self) {
1274 self.annotations.clear();
1275 }
1276
1277 pub fn remove_binding(&mut self, name: &str) -> Option<NodeId> {
1279 let position = self.bindings.iter().position(|(key, _)| key == name)?;
1280 Some(self.bindings.remove(position).1)
1281 }
1282
1283 pub fn provenance(&self) -> Option<&Provenance> {
1284 self.provenance.as_ref()
1285 }
1286
1287 pub fn has_secret(&self) -> bool {
1289 self.nodes
1290 .iter()
1291 .any(|node| matches!(node, Inode::Secret(_)))
1292 }
1293
1294 pub fn structurally_eq(&self, other: &Self) -> bool {
1299 let (Ok(left), Ok(right)) = (
1300 crate::canonical::prepare(self),
1301 crate::canonical::prepare(other),
1302 ) else {
1303 return false;
1304 };
1305 left.root == right.root
1306 && left.bindings == right.bindings
1307 && left.annotations == right.annotations
1308 && left.provenance == right.provenance
1309 && left.strings == right.strings
1310 && left.units == right.units
1311 && left.blobs == right.blobs
1312 && left.hashes == right.hashes
1313 && left.numbers == right.numbers
1314 && left.secrets.len() == right.secrets.len()
1315 && left.types.len() == right.types.len()
1316 && left
1317 .types
1318 .iter()
1319 .zip(right.types.iter())
1320 .all(|(a, b)| a.spec == b.spec && a.aux == b.aux)
1321 && left.nodes.len() == right.nodes.len()
1322 && left
1323 .nodes
1324 .iter()
1325 .zip(right.nodes.iter())
1326 .all(|(a, b)| node_eq(a, b))
1327 }
1328
1329 pub fn add_extension(&mut self, tag: u64, payload: impl Into<Vec<u8>>) {
1335 self.extensions.push((tag, payload.into()));
1336 }
1337
1338 pub fn extensions(&self) -> &[(u64, Vec<u8>)] {
1340 &self.extensions
1341 }
1342
1343 pub fn set_extensions(&mut self, extensions: Vec<(u64, Vec<u8>)>) {
1345 self.extensions = extensions;
1346 }
1347
1348 pub fn set_provenance(&mut self, provenance: Option<Provenance>) {
1349 self.provenance = provenance.map(|prov| {
1353 let mut by_key: std::collections::BTreeMap<String, ProvValue> =
1354 std::collections::BTreeMap::new();
1355 for entry in prov.entries {
1356 by_key.insert(entry.key, entry.value);
1357 }
1358 let entries: Vec<ProvEntry> = by_key
1359 .into_iter()
1360 .map(|(key, value)| ProvEntry { key, value })
1361 .collect();
1362 Provenance { entries, ..prov }
1363 });
1364 }
1365
1366 pub fn roots(&self) -> Vec<NodeId> {
1369 let mut roots = Vec::new();
1370 if let Some(root) = self.root {
1371 roots.push(root);
1372 }
1373 let mut bindings: Vec<&(String, NodeId)> = self.bindings.iter().collect();
1374 bindings.sort_by(|a, b| a.0.cmp(&b.0));
1375 roots.extend(bindings.into_iter().map(|(_, id)| *id));
1376 for annotation in &self.annotations {
1377 roots.push(annotation.node);
1378 if let AnnotationKind::Constraint { expr, .. } = &annotation.kind {
1379 roots.push(*expr);
1380 }
1381 }
1382 roots
1383 }
1384
1385 pub fn edges(&self) -> Vec<(NodeId, NodeId)> {
1389 let mut edges = Vec::new();
1390 for (index, node) in self.nodes.iter().enumerate() {
1391 let dependent = NodeId(index as u32);
1392 for child in node.children() {
1393 edges.push((child, dependent));
1394 }
1395 }
1396 edges.sort_unstable();
1397 edges.dedup();
1398 edges
1399 }
1400
1401 pub fn dependency_map(&self) -> HashMap<NodeId, Vec<NodeId>> {
1403 let mut map: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
1404 for (index, node) in self.nodes.iter().enumerate() {
1405 map.insert(NodeId(index as u32), node.children());
1406 }
1407 map
1408 }
1409
1410 pub fn validate_structure(&self, max_nodes: u64, max_depth: u32) -> Result<(), super::Error> {
1415 use super::Error;
1416 if self.nodes.len() as u64 > max_nodes {
1417 return Err(Error::Limit {
1418 kind: "nodes",
1419 limit: max_nodes,
1420 });
1421 }
1422 let count = self.nodes.len() as u32;
1423 let check = |id: NodeId| -> Result<(), Error> {
1424 if id.0 >= count {
1425 return Err(Error::NodeRef(id.0));
1426 }
1427 Ok(())
1428 };
1429 for (index, node) in self.nodes.iter().enumerate() {
1430 let id = NodeId(index as u32);
1431 for child in node.children() {
1432 check(child)?;
1433 }
1434 match node {
1435 Inode::Table { fields } | Inode::SchemaTable { fields, .. } => {
1436 let mut seen = std::collections::HashSet::with_capacity(fields.len());
1439 for (name, _) in fields {
1440 if !seen.insert(name.as_str()) {
1441 return Err(Error::DuplicateField { node: id.0 });
1442 }
1443 }
1444 }
1445 Inode::Float(value)
1446 if value.is_nan() && !crate::canonical::is_canonical_nan(*value) =>
1447 {
1448 return Err(Error::Node {
1449 id: id.0,
1450 reason: "non-canonical NaN",
1451 });
1452 }
1453 Inode::Constrained { constraint, .. } => match constraint {
1454 Constraint::MinMax {
1455 min: Some(min),
1456 max: Some(max),
1457 } if min > max => {
1458 return Err(Error::Node {
1459 id: id.0,
1460 reason: "constraint min exceeds max",
1461 });
1462 }
1463 Constraint::Enum(items) if items.is_empty() => {
1464 return Err(Error::Node {
1465 id: id.0,
1466 reason: "empty enum constraint",
1467 });
1468 }
1469 _ => {}
1470 },
1471 _ => {}
1472 }
1473 }
1474 if let Some(root) = self.root {
1475 check(root)?;
1476 }
1477 let mut seen = std::collections::HashSet::new();
1478 for (name, id) in &self.bindings {
1479 check(*id)?;
1480 if !seen.insert(name.as_str()) {
1481 return Err(Error::DuplicateBinding);
1482 }
1483 }
1484 for annotation in &self.annotations {
1485 check(annotation.node)?;
1486 if let AnnotationKind::Constraint { expr, .. } = &annotation.kind {
1487 check(*expr)?;
1488 }
1489 }
1490 self.check_acyclic(count)?;
1491 self.check_depth(max_depth)?;
1492 Ok(())
1493 }
1494
1495 pub fn check_acyclic(&self, count: u32) -> Result<(), super::Error> {
1502 use super::Error;
1503 let mut incoming = vec![0u32; count as usize];
1504 for node in &self.nodes {
1505 for child in node.children() {
1506 if child.0 >= count {
1507 return Err(Error::NodeRef(child.0));
1508 }
1509 let slot = &mut incoming[child.index()];
1510 *slot = slot.saturating_add(1);
1511 }
1512 }
1513 let mut queue: std::collections::VecDeque<u32> = incoming
1514 .iter()
1515 .enumerate()
1516 .filter(|(_, degree)| **degree == 0)
1517 .map(|(node, _)| node as u32)
1518 .collect();
1519 let mut processed = 0u32;
1520 while let Some(node) = queue.pop_front() {
1521 processed += 1;
1522 for child in self.nodes[node as usize].children() {
1523 let slot = &mut incoming[child.index()];
1524 *slot = slot.saturating_sub(1);
1525 if *slot == 0 {
1526 queue.push_back(child.0);
1527 }
1528 }
1529 }
1530 if processed != count {
1531 let node = incoming.iter().position(|degree| *degree > 0).unwrap_or(0) as u32;
1534 return Err(Error::Cycle { node });
1535 }
1536 Ok(())
1537 }
1538
1539 pub fn check_depth(&self, max_depth: u32) -> Result<(), super::Error> {
1544 use super::Error;
1545 let count = self.nodes.len();
1546 if count == 0 {
1547 return Ok(());
1548 }
1549 if max_depth == 0 {
1550 return self.check_acyclic(count as u32);
1553 }
1554 let mut incoming = vec![0u32; count];
1555 for node in &self.nodes {
1556 for child in node.children() {
1557 if child.index() >= count {
1558 return Err(Error::NodeRef(child.0));
1559 }
1560 incoming[child.index()] = incoming[child.index()].saturating_add(1);
1561 }
1562 }
1563 let mut depth = vec![1u32; count];
1564 let mut queue: std::collections::VecDeque<u32> = incoming
1565 .iter()
1566 .enumerate()
1567 .filter(|(_, degree)| **degree == 0)
1568 .map(|(node, _)| node as u32)
1569 .collect();
1570 if queue.is_empty() {
1571 return Err(Error::Cycle { node: 0 });
1573 }
1574 let mut processed = 0u32;
1575 while let Some(node) = queue.pop_front() {
1576 processed += 1;
1577 let next = depth[node as usize] + 1;
1578 for child in self.nodes[node as usize].children() {
1579 if next > max_depth {
1580 return Err(Error::Depth { limit: max_depth });
1581 }
1582 if next > depth[child.index()] {
1583 depth[child.index()] = next;
1584 }
1585 let slot = &mut incoming[child.index()];
1586 *slot = slot.saturating_sub(1);
1587 if *slot == 0 {
1588 queue.push_back(child.0);
1589 }
1590 }
1591 }
1592 if processed as usize != count {
1593 let node = incoming.iter().position(|degree| *degree > 0).unwrap_or(0) as u32;
1596 return Err(Error::Cycle { node });
1597 }
1598 Ok(())
1599 }
1600
1601 pub fn string_bytes(&self) -> u64 {
1603 let mut strings = Vec::new();
1604 for node in &self.nodes {
1605 node.strings(&mut strings);
1606 }
1607 strings.iter().map(|s| s.len() as u64).sum()
1608 }
1609
1610 pub fn from_parts(
1613 nodes: Vec<Inode>,
1614 root: Option<NodeId>,
1615 bindings: Vec<(String, NodeId)>,
1616 mut annotations: Vec<Annotation>,
1617 provenance: Option<Provenance>,
1618 ) -> Self {
1619 annotations.sort_by(|a, b| {
1620 (a.node.0, annotation_order(&a.kind)).cmp(&(b.node.0, annotation_order(&b.kind)))
1621 });
1622 InfinityModule {
1623 nodes,
1624 root,
1625 bindings,
1626 annotations,
1627 provenance,
1628 extensions: Vec::new(),
1629 }
1630 }
1631}
1632
1633fn annotation_order(kind: &AnnotationKind) -> u32 {
1634 match kind {
1635 AnnotationKind::Intent(_) => 0,
1636 AnnotationKind::Constraint { .. } => 1,
1637 AnnotationKind::AiImmutable(_) => 2,
1638 }
1639}
1640
1641#[derive(Debug, Default)]
1644pub struct ModuleBuilder {
1645 module: InfinityModule,
1646}
1647
1648impl ModuleBuilder {
1649 pub fn new() -> Self {
1650 Self::default()
1651 }
1652
1653 pub fn push(&mut self, node: Inode) -> NodeId {
1654 self.module.push(node)
1655 }
1656
1657 pub fn root(&mut self, id: NodeId) -> &mut Self {
1658 self.module.set_root(Some(id));
1659 self
1660 }
1661
1662 pub fn bind(&mut self, name: impl Into<String>, id: NodeId) -> &mut Self {
1663 self.module.bind(name, id);
1664 self
1665 }
1666
1667 pub fn annotate(&mut self, node: NodeId, kind: AnnotationKind) -> &mut Self {
1668 self.module.annotate(node, kind);
1669 self
1670 }
1671
1672 pub fn provenance(&mut self, provenance: Provenance) -> &mut Self {
1673 self.module.set_provenance(Some(provenance));
1674 self
1675 }
1676
1677 pub fn build(self) -> InfinityModule {
1678 self.module
1679 }
1680}