1use std::collections::BTreeSet;
20
21use crate::parser::ast::*;
22use crate::parser::tokenizer::Span;
23
24#[derive(Debug, Clone)]
28pub struct VerificationResult {
29 pub diagnostics: Vec<Diagnostic>,
30}
31
32impl VerificationResult {
33 pub fn new() -> Self {
34 Self {
35 diagnostics: Vec::new(),
36 }
37 }
38
39 pub fn is_valid(&self) -> bool {
41 !self
42 .diagnostics
43 .iter()
44 .any(|d| d.severity == Severity::Error)
45 }
46
47 pub fn errors(&self) -> Vec<&Diagnostic> {
49 self.diagnostics
50 .iter()
51 .filter(|d| d.severity == Severity::Error)
52 .collect()
53 }
54
55 pub fn warnings(&self) -> Vec<&Diagnostic> {
57 self.diagnostics
58 .iter()
59 .filter(|d| d.severity == Severity::Warning)
60 .collect()
61 }
62
63 fn add_error(&mut self, kind: DiagnosticKind, message: String, span: Option<Span>) {
64 self.diagnostics.push(Diagnostic {
65 severity: Severity::Error,
66 kind,
67 message,
68 span,
69 });
70 }
71
72 fn add_warning(&mut self, kind: DiagnosticKind, message: String, span: Option<Span>) {
73 self.diagnostics.push(Diagnostic {
74 severity: Severity::Warning,
75 kind,
76 message,
77 span,
78 });
79 }
80}
81
82impl Default for VerificationResult {
83 fn default() -> Self {
84 Self::new()
85 }
86}
87
88#[derive(Debug, Clone)]
90pub struct Diagnostic {
91 pub severity: Severity,
92 pub kind: DiagnosticKind,
93 pub message: String,
94 pub span: Option<Span>,
95}
96
97impl std::fmt::Display for Diagnostic {
98 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
99 let prefix = match self.severity {
100 Severity::Error => "error",
101 Severity::Warning => "warning",
102 };
103 if let Some(ref span) = self.span {
104 write!(
105 f,
106 "{} [{}] at {}: {}",
107 prefix, self.kind, span, self.message
108 )
109 } else {
110 write!(f, "{} [{}]: {}", prefix, self.kind, self.message)
111 }
112 }
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117pub enum Severity {
118 Error,
119 Warning,
120}
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
124pub enum DiagnosticKind {
125 TypeError,
126 InvariantError,
127 DeterminismViolation,
128 CoherenceError,
129}
130
131impl std::fmt::Display for DiagnosticKind {
132 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
133 match self {
134 DiagnosticKind::TypeError => write!(f, "type"),
135 DiagnosticKind::InvariantError => write!(f, "invariant"),
136 DiagnosticKind::DeterminismViolation => write!(f, "determinism"),
137 DiagnosticKind::CoherenceError => write!(f, "coherence"),
138 }
139 }
140}
141
142pub fn verify(ast: &ContractNode) -> VerificationResult {
149 let mut result = VerificationResult::new();
150
151 verify_types(ast, &mut result);
153
154 verify_invariants(ast, &mut result);
156
157 verify_determinism(ast, &mut result);
159
160 verify_coherence(ast, &mut result);
162
163 result
164}
165
166fn verify_types(ast: &ContractNode, result: &mut VerificationResult) {
170 verify_identity_types(&ast.identity, result);
172
173 verify_purpose_types(&ast.purpose_statement, result);
175
176 for field in &ast.data_semantics.state {
178 verify_type_expression(&field.type_expr, result);
179 if let Some(ref default) = field.default_value {
180 verify_default_matches_type(&field.name.value, &field.type_expr, default, result);
181 }
182 }
183
184 for op in &ast.behavioral_semantics.operations {
186 for param in &op.parameters {
187 verify_type_expression(¶m.type_expr, result);
188 if let Some(ref default) = param.default_value {
189 verify_default_matches_type(¶m.name.value, ¶m.type_expr, default, result);
190 }
191 }
192 }
193
194 verify_resource_limit_types(&ast.execution_constraints.resource_limits, result);
196}
197
198fn verify_identity_types(identity: &IdentityNode, result: &mut VerificationResult) {
200 if identity.version.value < 0 {
202 result.add_error(
203 DiagnosticKind::TypeError,
204 format!(
205 "version must be non-negative, found {}",
206 identity.version.value
207 ),
208 Some(identity.version.span.clone()),
209 );
210 }
211
212 let sid = &identity.stable_id.value;
214 if !is_valid_stable_id(sid) {
215 result.add_error(
216 DiagnosticKind::TypeError,
217 format!(
218 "stable_id '{}' does not match required pattern [a-z0-9][a-z0-9-]{{0,30}}[a-z0-9]",
219 sid
220 ),
221 Some(identity.stable_id.span.clone()),
222 );
223 }
224
225 let hash = &identity.semantic_hash.value;
227 if !hash.chars().all(|c| c.is_ascii_hexdigit()) {
228 result.add_error(
229 DiagnosticKind::TypeError,
230 format!("semantic_hash '{}' is not valid hexadecimal", hash),
231 Some(identity.semantic_hash.span.clone()),
232 );
233 }
234}
235
236fn is_valid_stable_id(id: &str) -> bool {
238 if id.len() < 2 || id.len() > 32 {
239 return false;
240 }
241 let bytes = id.as_bytes();
242 let valid_alnum = |b: u8| b.is_ascii_lowercase() || b.is_ascii_digit();
244 let valid_middle = |b: u8| valid_alnum(b) || b == b'-';
245 if !valid_alnum(bytes[0]) || !valid_alnum(bytes[bytes.len() - 1]) {
246 return false;
247 }
248 bytes[1..bytes.len() - 1].iter().all(|&b| valid_middle(b))
249}
250
251fn verify_purpose_types(purpose: &PurposeStatementNode, result: &mut VerificationResult) {
253 let cl = purpose.confidence_level.value;
255 if !(0.0..=1.0).contains(&cl) {
256 result.add_error(
257 DiagnosticKind::TypeError,
258 format!("confidence_level must be in range [0.0, 1.0], found {}", cl),
259 Some(purpose.confidence_level.span.clone()),
260 );
261 }
262
263 if purpose.narrative.value.len() > 500 {
265 result.add_warning(
266 DiagnosticKind::TypeError,
267 format!(
268 "narrative exceeds recommended 500 character limit ({} chars)",
269 purpose.narrative.value.len()
270 ),
271 Some(purpose.narrative.span.clone()),
272 );
273 }
274}
275
276fn verify_type_expression(type_expr: &TypeExpression, result: &mut VerificationResult) {
278 match type_expr {
279 TypeExpression::Primitive(_, _) => {
280 }
282 TypeExpression::Array(inner, _) => {
283 verify_type_expression(inner, result);
284 }
285 TypeExpression::Map(key, value, span) => {
286 verify_type_expression(key, result);
288 verify_type_expression(value, result);
289 verify_map_key_type(key, span, result);
290 }
291 TypeExpression::Object(fields, _) => {
292 let mut seen = BTreeSet::new();
294 for field in fields {
295 if !seen.insert(&field.name.value) {
296 result.add_error(
297 DiagnosticKind::TypeError,
298 format!("duplicate field name '{}' in Object type", field.name.value),
299 Some(field.name.span.clone()),
300 );
301 }
302 verify_type_expression(&field.type_expr, result);
303 if let Some(ref default) = field.default_value {
304 verify_default_matches_type(
305 &field.name.value,
306 &field.type_expr,
307 default,
308 result,
309 );
310 }
311 }
312 }
313 TypeExpression::Enum(variants, span) => {
314 if variants.is_empty() {
316 result.add_error(
317 DiagnosticKind::TypeError,
318 "Enum type must have at least one variant".to_string(),
319 Some(span.clone()),
320 );
321 }
322 let mut seen = BTreeSet::new();
324 for variant in variants {
325 if !seen.insert(&variant.value) {
326 result.add_error(
327 DiagnosticKind::TypeError,
328 format!("duplicate Enum variant '{}'", variant.value),
329 Some(variant.span.clone()),
330 );
331 }
332 }
333 }
334 }
335}
336
337fn verify_map_key_type(
339 key_type: &TypeExpression,
340 map_span: &Span,
341 result: &mut VerificationResult,
342) {
343 match key_type {
344 TypeExpression::Primitive(pt, _) => match pt {
345 PrimitiveType::String
346 | PrimitiveType::Integer
347 | PrimitiveType::Uuid
348 | PrimitiveType::Boolean
349 | PrimitiveType::Iso8601 => {
350 }
352 PrimitiveType::Float => {
353 result.add_error(
354 DiagnosticKind::TypeError,
355 "Float cannot be used as Map key type (non-deterministic equality)".to_string(),
356 Some(map_span.clone()),
357 );
358 }
359 },
360 TypeExpression::Enum(_, _) => {
361 }
363 _ => {
364 result.add_error(
365 DiagnosticKind::TypeError,
366 format!(
367 "Map key type must be a primitive or Enum, found {}",
368 type_expr_name(key_type)
369 ),
370 Some(map_span.clone()),
371 );
372 }
373 }
374}
375
376fn verify_default_matches_type(
378 field_name: &str,
379 type_expr: &TypeExpression,
380 default: &LiteralValue,
381 result: &mut VerificationResult,
382) {
383 let matches = default_matches_type(type_expr, default);
384 if !matches {
385 result.add_error(
386 DiagnosticKind::TypeError,
387 format!(
388 "default value for '{}' has type {}, expected {}",
389 field_name,
390 literal_type_name(default),
391 type_expr_name(type_expr),
392 ),
393 Some(literal_span(default)),
394 );
395 }
396}
397
398fn default_matches_type(type_expr: &TypeExpression, default: &LiteralValue) -> bool {
400 match (type_expr, default) {
401 (TypeExpression::Primitive(PrimitiveType::Integer, _), LiteralValue::Integer(_, _)) => true,
402 (TypeExpression::Primitive(PrimitiveType::Float, _), LiteralValue::Float(_, _)) => true,
403 (TypeExpression::Primitive(PrimitiveType::Float, _), LiteralValue::Integer(_, _)) => true,
405 (TypeExpression::Primitive(PrimitiveType::String, _), LiteralValue::String(_, _)) => true,
406 (TypeExpression::Primitive(PrimitiveType::Boolean, _), LiteralValue::Boolean(_, _)) => true,
407 (TypeExpression::Primitive(PrimitiveType::Iso8601, _), LiteralValue::String(_, _)) => true,
409 (TypeExpression::Primitive(PrimitiveType::Uuid, _), LiteralValue::String(_, _)) => true,
410 (TypeExpression::Enum(variants, _), LiteralValue::String(s, _)) => {
412 variants.iter().any(|v| v.value == *s)
413 }
414 (TypeExpression::Array(elem_type, _), LiteralValue::Array(elems, _)) => {
416 elems.iter().all(|e| default_matches_type(elem_type, e))
417 }
418 _ => false,
419 }
420}
421
422fn verify_resource_limit_types(limits: &ResourceLimitsNode, result: &mut VerificationResult) {
424 if limits.max_memory_bytes.value <= 0 {
425 result.add_error(
426 DiagnosticKind::TypeError,
427 format!(
428 "max_memory_bytes must be positive, found {}",
429 limits.max_memory_bytes.value
430 ),
431 Some(limits.max_memory_bytes.span.clone()),
432 );
433 }
434 if limits.computation_timeout_ms.value <= 0 {
435 result.add_error(
436 DiagnosticKind::TypeError,
437 format!(
438 "computation_timeout_ms must be positive, found {}",
439 limits.computation_timeout_ms.value
440 ),
441 Some(limits.computation_timeout_ms.span.clone()),
442 );
443 }
444 if limits.max_state_size_bytes.value <= 0 {
445 result.add_error(
446 DiagnosticKind::TypeError,
447 format!(
448 "max_state_size_bytes must be positive, found {}",
449 limits.max_state_size_bytes.value
450 ),
451 Some(limits.max_state_size_bytes.span.clone()),
452 );
453 }
454}
455
456fn verify_invariants(ast: &ContractNode, result: &mut VerificationResult) {
460 let state_field_names: BTreeSet<&str> = ast
461 .data_semantics
462 .state
463 .iter()
464 .map(|f| f.name.value.as_str())
465 .collect();
466
467 for invariant in &ast.data_semantics.invariants {
468 let inv_text = &invariant.value;
469
470 let referenced_fields = extract_identifiers(inv_text);
472
473 let mut found_field_ref = false;
475 for ident in &referenced_fields {
476 if state_field_names.contains(ident.as_str()) {
477 found_field_ref = true;
478 }
479 }
480
481 if !found_field_ref && !state_field_names.is_empty() && !inv_text.is_empty() {
483 result.add_warning(
484 DiagnosticKind::InvariantError,
485 format!(
486 "invariant '{}' does not reference any declared state fields",
487 inv_text,
488 ),
489 Some(invariant.span.clone()),
490 );
491 }
492 }
493
494 let mut seen = BTreeSet::new();
496 for invariant in &ast.data_semantics.invariants {
497 if !seen.insert(&invariant.value) {
498 result.add_warning(
499 DiagnosticKind::InvariantError,
500 format!("duplicate invariant: '{}'", invariant.value),
501 Some(invariant.span.clone()),
502 );
503 }
504 }
505}
506
507fn extract_identifiers(text: &str) -> Vec<String> {
509 let mut identifiers = Vec::new();
510 let mut chars = text.chars().peekable();
511
512 while let Some(&c) = chars.peek() {
513 if c.is_ascii_alphabetic() || c == '_' {
514 let mut ident = String::new();
515 while let Some(&c) = chars.peek() {
516 if c.is_ascii_alphanumeric() || c == '_' {
517 ident.push(c);
518 chars.next();
519 } else {
520 break;
521 }
522 }
523 if !is_keyword(&ident) {
525 identifiers.push(ident);
526 }
527 } else {
528 chars.next();
529 }
530 }
531
532 identifiers
533}
534
535fn is_keyword(s: &str) -> bool {
537 matches!(
538 s,
539 "is" | "not"
540 | "and"
541 | "or"
542 | "true"
543 | "false"
544 | "null"
545 | "empty"
546 | "if"
547 | "then"
548 | "else"
549 | "for"
550 | "while"
551 | "in"
552 | "gt"
553 | "lt"
554 | "eq"
555 | "ne"
556 | "ge"
557 | "le"
558 | "the"
559 | "a"
560 | "an"
561 | "of"
562 | "to"
563 | "at"
564 | "by"
565 | "must"
566 | "should"
567 | "can"
568 | "may"
569 | "will"
570 | "exists"
571 | "unique"
572 | "valid"
573 | "always"
574 | "never"
575 | "updated"
576 | "set"
577 | "contains"
578 | "matches"
579 )
580}
581
582fn verify_determinism(ast: &ContractNode, result: &mut VerificationResult) {
586 let nondeterministic_patterns = [
588 ("random", "randomness usage"),
590 ("rand(", "random function call"),
591 ("Math.random", "random function call"),
592 ("uuid_generate", "runtime UUID generation"),
593 ("generate_id", "runtime ID generation"),
594 ("now()", "system time access"),
596 ("current_time", "system time access"),
597 ("system_time", "system time access"),
598 ("Date.now", "system time access"),
599 ("time.time", "system time access"),
600 ("Instant::now", "system time access"),
601 ("fetch(", "external I/O"),
603 ("http_request", "external I/O"),
604 ("read_file", "external I/O"),
605 ("write_file", "external I/O"),
606 ("network_call", "external I/O"),
607 ("socket", "external I/O"),
608 ("HashMap", "non-deterministic hash iteration"),
610 ("HashSet", "non-deterministic hash iteration"),
611 ("dict_keys", "non-deterministic hash iteration"),
612 ];
613
614 for op in &ast.behavioral_semantics.operations {
616 check_string_for_nondeterminism(
617 &op.precondition.value,
618 &format!("operation '{}' precondition", op.name.value),
619 &op.precondition.span,
620 &nondeterministic_patterns,
621 result,
622 );
623 check_string_for_nondeterminism(
624 &op.postcondition.value,
625 &format!("operation '{}' postcondition", op.name.value),
626 &op.postcondition.span,
627 &nondeterministic_patterns,
628 result,
629 );
630 for se in &op.side_effects {
631 check_string_for_nondeterminism(
632 &se.value,
633 &format!("operation '{}' side_effect", op.name.value),
634 &se.span,
635 &nondeterministic_patterns,
636 result,
637 );
638 }
639 check_string_for_nondeterminism(
640 &op.idempotence.value,
641 &format!("operation '{}' idempotence", op.name.value),
642 &op.idempotence.span,
643 &nondeterministic_patterns,
644 result,
645 );
646 }
647
648 for inv in &ast.data_semantics.invariants {
650 check_string_for_nondeterminism(
651 &inv.value,
652 "invariant",
653 &inv.span,
654 &nondeterministic_patterns,
655 result,
656 );
657 }
658}
659
660fn check_string_for_nondeterminism(
662 text: &str,
663 context: &str,
664 span: &Span,
665 patterns: &[(&str, &str)],
666 result: &mut VerificationResult,
667) {
668 let lower = text.to_lowercase();
669 for &(pattern, description) in patterns {
670 if lower.contains(&pattern.to_lowercase()) {
671 result.add_error(
672 DiagnosticKind::DeterminismViolation,
673 format!(
674 "{} detected in {}: text contains '{}'",
675 description, context, pattern,
676 ),
677 Some(span.clone()),
678 );
679 }
680 }
681}
682
683fn verify_coherence(ast: &ContractNode, result: &mut VerificationResult) {
687 verify_unique_operation_names(ast, result);
689
690 verify_unique_state_fields(ast, result);
692
693 verify_sandbox_mode(ast, result);
695
696 verify_trigger_types(ast, result);
698
699 verify_operation_field_references(ast, result);
701
702 verify_extension_namespaces(ast, result);
704}
705
706fn verify_unique_operation_names(ast: &ContractNode, result: &mut VerificationResult) {
708 let mut seen = BTreeSet::new();
709 for op in &ast.behavioral_semantics.operations {
710 if !seen.insert(&op.name.value) {
711 result.add_error(
712 DiagnosticKind::CoherenceError,
713 format!("duplicate operation name '{}'", op.name.value),
714 Some(op.name.span.clone()),
715 );
716 }
717 }
718}
719
720fn verify_unique_state_fields(ast: &ContractNode, result: &mut VerificationResult) {
722 let mut seen = BTreeSet::new();
723 for field in &ast.data_semantics.state {
724 if !seen.insert(&field.name.value) {
725 result.add_error(
726 DiagnosticKind::CoherenceError,
727 format!("duplicate state field name '{}'", field.name.value),
728 Some(field.name.span.clone()),
729 );
730 }
731 }
732}
733
734fn verify_sandbox_mode(ast: &ContractNode, result: &mut VerificationResult) {
736 let valid_modes = ["full_isolation", "restricted", "none"];
737 let mode = &ast.execution_constraints.sandbox_mode.value;
738 if !valid_modes.contains(&mode.as_str()) {
739 result.add_warning(
740 DiagnosticKind::CoherenceError,
741 format!(
742 "unrecognized sandbox_mode '{}', expected one of: {}",
743 mode,
744 valid_modes.join(", ")
745 ),
746 Some(ast.execution_constraints.sandbox_mode.span.clone()),
747 );
748 }
749}
750
751fn verify_trigger_types(ast: &ContractNode, result: &mut VerificationResult) {
753 let valid_types = ["manual", "time_based", "event_based"];
754 for tt in &ast.execution_constraints.trigger_types {
755 if !valid_types.contains(&tt.value.as_str()) {
756 result.add_warning(
757 DiagnosticKind::CoherenceError,
758 format!(
759 "unrecognized trigger_type '{}', expected one of: {}",
760 tt.value,
761 valid_types.join(", ")
762 ),
763 Some(tt.span.clone()),
764 );
765 }
766 }
767}
768
769fn verify_operation_field_references(ast: &ContractNode, result: &mut VerificationResult) {
771 let state_field_names: BTreeSet<&str> = ast
772 .data_semantics
773 .state
774 .iter()
775 .map(|f| f.name.value.as_str())
776 .collect();
777
778 for op in &ast.behavioral_semantics.operations {
779 let pre_idents = extract_identifiers(&op.precondition.value);
781 for ident in &pre_idents {
782 if looks_like_field_ref(ident) && !state_field_names.contains(ident.as_str()) {
783 let param_names: BTreeSet<&str> = op
785 .parameters
786 .iter()
787 .map(|p| p.name.value.as_str())
788 .collect();
789 if !param_names.contains(ident.as_str()) {
790 result.add_warning(
791 DiagnosticKind::CoherenceError,
792 format!(
793 "precondition of '{}' references unknown field '{}'",
794 op.name.value, ident,
795 ),
796 Some(op.precondition.span.clone()),
797 );
798 }
799 }
800 }
801
802 let post_idents = extract_identifiers(&op.postcondition.value);
804 for ident in &post_idents {
805 if looks_like_field_ref(ident) && !state_field_names.contains(ident.as_str()) {
806 let param_names: BTreeSet<&str> = op
807 .parameters
808 .iter()
809 .map(|p| p.name.value.as_str())
810 .collect();
811 if !param_names.contains(ident.as_str()) {
812 result.add_warning(
813 DiagnosticKind::CoherenceError,
814 format!(
815 "postcondition of '{}' references unknown field '{}'",
816 op.name.value, ident,
817 ),
818 Some(op.postcondition.span.clone()),
819 );
820 }
821 }
822 }
823 }
824}
825
826fn looks_like_field_ref(ident: &str) -> bool {
828 ident.len() >= 2
830 && ident
831 .chars()
832 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')
833 && ident.contains('_')
834}
835
836fn verify_extension_namespaces(ast: &ContractNode, result: &mut VerificationResult) {
838 if let Some(ref ext) = ast.extensions {
839 let mut seen = BTreeSet::new();
840 for system in &ext.systems {
841 if !seen.insert(&system.name.value) {
842 result.add_error(
843 DiagnosticKind::CoherenceError,
844 format!("duplicate extension namespace '{}'", system.name.value),
845 Some(system.name.span.clone()),
846 );
847 }
848 }
849 }
850}
851
852fn type_expr_name(type_expr: &TypeExpression) -> String {
856 match type_expr {
857 TypeExpression::Primitive(pt, _) => pt.to_string(),
858 TypeExpression::Array(inner, _) => format!("Array<{}>", type_expr_name(inner)),
859 TypeExpression::Map(k, v, _) => {
860 format!("Map<{}, {}>", type_expr_name(k), type_expr_name(v))
861 }
862 TypeExpression::Object(_, _) => "Object".to_string(),
863 TypeExpression::Enum(_, _) => "Enum".to_string(),
864 }
865}
866
867fn literal_type_name(lit: &LiteralValue) -> String {
869 match lit {
870 LiteralValue::String(_, _) => "String".to_string(),
871 LiteralValue::Integer(_, _) => "Integer".to_string(),
872 LiteralValue::Float(_, _) => "Float".to_string(),
873 LiteralValue::Boolean(_, _) => "Boolean".to_string(),
874 LiteralValue::Array(_, _) => "Array".to_string(),
875 }
876}
877
878fn literal_span(lit: &LiteralValue) -> Span {
880 match lit {
881 LiteralValue::String(_, s) => s.clone(),
882 LiteralValue::Integer(_, s) => s.clone(),
883 LiteralValue::Float(_, s) => s.clone(),
884 LiteralValue::Boolean(_, s) => s.clone(),
885 LiteralValue::Array(_, s) => s.clone(),
886 }
887}
888
889#[cfg(test)]
892mod tests {
893 use super::*;
894 use crate::parser::parse;
895
896 fn parse_and_verify(input: &str) -> VerificationResult {
899 let ast = parse(input).expect("test input should parse");
900 verify(&ast)
901 }
902
903 #[test]
906 fn test_valid_minimal_contract() {
907 let result = parse_and_verify(include_str!(
908 "../../../tests/fixtures/conformance/valid/minimal-contract.icl"
909 ));
910 assert!(
911 result.is_valid(),
912 "minimal contract should verify: {:?}",
913 result.errors()
914 );
915 }
916
917 #[test]
918 fn test_valid_all_primitive_types() {
919 let result = parse_and_verify(include_str!(
920 "../../../tests/fixtures/conformance/valid/all-primitive-types.icl"
921 ));
922 assert!(
923 result.is_valid(),
924 "all-primitive-types should verify: {:?}",
925 result.errors()
926 );
927 }
928
929 #[test]
930 fn test_valid_composite_types() {
931 let result = parse_and_verify(include_str!(
932 "../../../tests/fixtures/conformance/valid/composite-types.icl"
933 ));
934 assert!(
935 result.is_valid(),
936 "composite-types should verify: {:?}",
937 result.errors()
938 );
939 }
940
941 #[test]
942 fn test_valid_multiple_operations() {
943 let result = parse_and_verify(include_str!(
944 "../../../tests/fixtures/conformance/valid/multiple-operations.icl"
945 ));
946 assert!(
947 result.is_valid(),
948 "multiple-operations should verify: {:?}",
949 result.errors()
950 );
951 }
952
953 #[test]
954 fn test_integer_type_valid_default() {
955 let input = make_contract_with_state("count: Integer = 42");
956 let result = parse_and_verify(&input);
957 assert!(
958 result.is_valid(),
959 "Integer default 42 should be valid: {:?}",
960 result.errors()
961 );
962 }
963
964 #[test]
965 fn test_float_type_valid_default() {
966 let input = make_contract_with_state("ratio: Float = 3.14");
967 let result = parse_and_verify(&input);
968 assert!(
969 result.is_valid(),
970 "Float default 3.14 should be valid: {:?}",
971 result.errors()
972 );
973 }
974
975 #[test]
976 fn test_float_type_integer_default_allowed() {
977 let input = make_contract_with_state("ratio: Float = 0");
978 let result = parse_and_verify(&input);
979 assert!(
980 result.is_valid(),
981 "Integer literal as Float default should be allowed: {:?}",
982 result.errors()
983 );
984 }
985
986 #[test]
987 fn test_string_type_valid_default() {
988 let input = make_contract_with_state("label: String = \"hello\"");
989 let result = parse_and_verify(&input);
990 assert!(
991 result.is_valid(),
992 "String default should be valid: {:?}",
993 result.errors()
994 );
995 }
996
997 #[test]
998 fn test_boolean_type_valid_default() {
999 let input = make_contract_with_state("active: Boolean = true");
1000 let result = parse_and_verify(&input);
1001 assert!(
1002 result.is_valid(),
1003 "Boolean default should be valid: {:?}",
1004 result.errors()
1005 );
1006 }
1007
1008 #[test]
1009 fn test_type_mismatch_string_for_integer() {
1010 let input = make_contract_with_state("count: Integer = \"hello\"");
1011 let result = parse_and_verify(&input);
1012 assert!(!result.is_valid(), "String default for Integer should fail");
1013 assert!(
1014 result
1015 .errors()
1016 .iter()
1017 .any(|d| d.kind == DiagnosticKind::TypeError),
1018 "Should produce TypeError"
1019 );
1020 }
1021
1022 #[test]
1023 fn test_type_mismatch_integer_for_string() {
1024 let input = make_contract_with_state("label: String = 42");
1025 let result = parse_and_verify(&input);
1026 assert!(!result.is_valid(), "Integer default for String should fail");
1027 }
1028
1029 #[test]
1030 fn test_type_mismatch_boolean_for_integer() {
1031 let input = make_contract_with_state("count: Integer = true");
1032 let result = parse_and_verify(&input);
1033 assert!(
1034 !result.is_valid(),
1035 "Boolean default for Integer should fail"
1036 );
1037 }
1038
1039 #[test]
1040 fn test_type_mismatch_string_for_boolean() {
1041 let input = make_contract_with_state("active: Boolean = \"yes\"");
1042 let result = parse_and_verify(&input);
1043 assert!(!result.is_valid(), "String default for Boolean should fail");
1044 }
1045
1046 #[test]
1047 fn test_enum_valid_default() {
1048 let input =
1049 make_contract_with_state("status: Enum [\"active\", \"inactive\"] = \"active\"");
1050 let result = parse_and_verify(&input);
1051 assert!(
1052 result.is_valid(),
1053 "Valid Enum default should pass: {:?}",
1054 result.errors()
1055 );
1056 }
1057
1058 #[test]
1059 fn test_enum_invalid_default() {
1060 let input =
1061 make_contract_with_state("status: Enum [\"active\", \"inactive\"] = \"unknown\"");
1062 let result = parse_and_verify(&input);
1063 assert!(!result.is_valid(), "Invalid Enum default should fail");
1064 }
1065
1066 #[test]
1067 fn test_enum_duplicate_variants() {
1068 let input = make_contract_with_state("status: Enum [\"active\", \"active\", \"inactive\"]");
1069 let result = parse_and_verify(&input);
1070 assert!(
1071 result
1072 .errors()
1073 .iter()
1074 .any(|d| d.message.contains("duplicate Enum variant")),
1075 "Should detect duplicate Enum variants"
1076 );
1077 }
1078
1079 #[test]
1080 fn test_object_duplicate_fields() {
1081 let input = make_contract_with_state("data: Object { name: String, name: Integer }");
1082 let result = parse_and_verify(&input);
1083 assert!(
1084 result
1085 .errors()
1086 .iter()
1087 .any(|d| d.message.contains("duplicate field name")),
1088 "Should detect duplicate Object fields"
1089 );
1090 }
1091
1092 #[test]
1093 fn test_map_float_key_rejected() {
1094 let input = make_contract_with_state("lookup: Map<Float, String>");
1095 let result = parse_and_verify(&input);
1096 assert!(
1097 result
1098 .errors()
1099 .iter()
1100 .any(|d| d.message.contains("Float cannot be used as Map key")),
1101 "Float Map keys should be rejected"
1102 );
1103 }
1104
1105 #[test]
1106 fn test_map_string_key_valid() {
1107 let input = make_contract_with_state("lookup: Map<String, Integer>");
1108 let result = parse_and_verify(&input);
1109 assert!(
1110 result.is_valid(),
1111 "String Map key should be valid: {:?}",
1112 result.errors()
1113 );
1114 }
1115
1116 #[test]
1117 fn test_array_type_valid() {
1118 let input = make_contract_with_state("items: Array<String>");
1119 let result = parse_and_verify(&input);
1120 assert!(
1121 result.is_valid(),
1122 "Array<String> should be valid: {:?}",
1123 result.errors()
1124 );
1125 }
1126
1127 #[test]
1128 fn test_nested_collection_types() {
1129 let input = make_contract_with_state("matrix: Array<Array<Integer>>");
1130 let result = parse_and_verify(&input);
1131 assert!(
1132 result.is_valid(),
1133 "Nested Array should be valid: {:?}",
1134 result.errors()
1135 );
1136 }
1137
1138 #[test]
1139 fn test_confidence_level_out_of_range_high() {
1140 let input = make_contract_with_confidence("1.5");
1142 assert!(
1143 parse(&input).is_err(),
1144 "confidence_level 1.5 should fail at parse"
1145 );
1146 }
1147
1148 #[test]
1149 fn test_confidence_level_out_of_range_low() {
1150 let mut ast = make_valid_ast();
1152 ast.purpose_statement.confidence_level = SpannedValue::new(-0.1, dummy_span());
1153 let result = verify(&ast);
1154 assert!(!result.is_valid(), "confidence_level -0.1 should fail");
1155 assert!(
1156 result
1157 .errors()
1158 .iter()
1159 .any(|d| d.message.contains("confidence_level")),
1160 "Should mention confidence_level: {:?}",
1161 result.errors()
1162 );
1163 }
1164
1165 #[test]
1166 fn test_confidence_level_boundary_zero() {
1167 let input = make_contract_with_confidence("0.0");
1168 let result = parse_and_verify(&input);
1169 assert!(
1170 result.is_valid(),
1171 "confidence_level 0.0 should be valid: {:?}",
1172 result.errors()
1173 );
1174 }
1175
1176 #[test]
1177 fn test_confidence_level_boundary_one() {
1178 let input = make_contract_with_confidence("1.0");
1179 let result = parse_and_verify(&input);
1180 assert!(
1181 result.is_valid(),
1182 "confidence_level 1.0 should be valid: {:?}",
1183 result.errors()
1184 );
1185 }
1186
1187 #[test]
1188 fn test_negative_version() {
1189 let mut ast = make_valid_ast();
1191 ast.identity.version = SpannedValue::new(-1, dummy_span());
1192 let result = verify(&ast);
1193 assert!(!result.is_valid(), "negative version should fail");
1194 assert!(
1195 result
1196 .errors()
1197 .iter()
1198 .any(|d| d.message.contains("version")),
1199 "Should mention version: {:?}",
1200 result.errors()
1201 );
1202 }
1203
1204 #[test]
1205 fn test_negative_resource_limits() {
1206 let mut ast = make_valid_ast();
1208 ast.execution_constraints.resource_limits.max_memory_bytes =
1209 SpannedValue::new(-1, dummy_span());
1210 let result = verify(&ast);
1211 assert!(!result.is_valid(), "negative max_memory_bytes should fail");
1212 assert!(
1213 result
1214 .errors()
1215 .iter()
1216 .any(|d| d.message.contains("max_memory_bytes")),
1217 "Should mention max_memory_bytes: {:?}",
1218 result.errors()
1219 );
1220 }
1221
1222 #[test]
1223 fn test_zero_timeout() {
1224 let input = make_contract_with_resource_limits(1048576, 0, 1048576);
1225 let result = parse_and_verify(&input);
1226 assert!(
1227 !result.is_valid(),
1228 "zero computation_timeout_ms should fail"
1229 );
1230 }
1231
1232 #[test]
1233 fn test_valid_stable_id() {
1234 let input = make_contract_with_stable_id("ic-test-001");
1235 let result = parse_and_verify(&input);
1236 assert!(result.is_valid(), "valid stable_id: {:?}", result.errors());
1237 }
1238
1239 #[test]
1240 fn test_invalid_stable_id_uppercase() {
1241 let input = make_contract_with_stable_id("IC-TEST-001");
1242 let result = parse_and_verify(&input);
1243 assert!(!result.is_valid(), "uppercase stable_id should fail");
1244 }
1245
1246 #[test]
1247 fn test_invalid_stable_id_starts_with_dash() {
1248 let input = make_contract_with_stable_id("-invalid");
1249 let result = parse_and_verify(&input);
1250 assert!(!result.is_valid(), "dash-start stable_id should fail");
1251 }
1252
1253 #[test]
1254 fn test_invalid_semantic_hash() {
1255 let input = make_contract_with_hash("not-hex-at-all!");
1256 let result = parse_and_verify(&input);
1257 assert!(!result.is_valid(), "non-hex hash should fail");
1258 }
1259
1260 #[test]
1263 fn test_invariant_references_valid_field() {
1264 let input = make_contract_with_state_and_invariants("count: Integer = 0", &["count >= 0"]);
1265 let result = parse_and_verify(&input);
1266 assert!(
1268 !result
1269 .warnings()
1270 .iter()
1271 .any(|d| d.kind == DiagnosticKind::InvariantError),
1272 "Valid field reference should not warn: {:?}",
1273 result.warnings()
1274 );
1275 }
1276
1277 #[test]
1278 fn test_duplicate_invariant_warning() {
1279 let input = make_contract_with_state_and_invariants(
1280 "count: Integer = 0",
1281 &["count >= 0", "count >= 0"],
1282 );
1283 let result = parse_and_verify(&input);
1284 assert!(
1285 result
1286 .warnings()
1287 .iter()
1288 .any(|d| d.message.contains("duplicate invariant")),
1289 "Should warn about duplicate invariants"
1290 );
1291 }
1292
1293 #[test]
1296 fn test_detect_randomness_in_precondition() {
1297 let input = make_contract_with_operation("random_op", "random() > 0.5", "result set");
1298 let result = parse_and_verify(&input);
1299 assert!(
1300 result
1301 .errors()
1302 .iter()
1303 .any(|d| d.kind == DiagnosticKind::DeterminismViolation),
1304 "Should detect randomness: {:?}",
1305 result.diagnostics
1306 );
1307 }
1308
1309 #[test]
1310 fn test_detect_system_time_in_postcondition() {
1311 let input = make_contract_with_operation("time_op", "true", "timestamp = now()");
1312 let result = parse_and_verify(&input);
1313 assert!(
1314 result
1315 .errors()
1316 .iter()
1317 .any(|d| d.kind == DiagnosticKind::DeterminismViolation),
1318 "Should detect system time: {:?}",
1319 result.diagnostics
1320 );
1321 }
1322
1323 #[test]
1324 fn test_detect_external_io() {
1325 let input = make_contract_with_operation("io_op", "true", "data = fetch(url)");
1326 let result = parse_and_verify(&input);
1327 assert!(
1328 result
1329 .errors()
1330 .iter()
1331 .any(|d| d.kind == DiagnosticKind::DeterminismViolation),
1332 "Should detect external I/O: {:?}",
1333 result.diagnostics
1334 );
1335 }
1336
1337 #[test]
1338 fn test_detect_hashmap_usage() {
1339 let input = make_contract_with_operation("hash_op", "true", "HashMap iteration order");
1340 let result = parse_and_verify(&input);
1341 assert!(
1342 result
1343 .errors()
1344 .iter()
1345 .any(|d| d.kind == DiagnosticKind::DeterminismViolation),
1346 "Should detect HashMap: {:?}",
1347 result.diagnostics
1348 );
1349 }
1350
1351 #[test]
1352 fn test_clean_operation_no_determinism_violation() {
1353 let input = make_contract_with_operation("clean_op", "count >= 0", "count updated");
1354 let result = parse_and_verify(&input);
1355 assert!(
1356 !result
1357 .errors()
1358 .iter()
1359 .any(|d| d.kind == DiagnosticKind::DeterminismViolation),
1360 "Clean operation should have no determinism violations: {:?}",
1361 result.errors()
1362 );
1363 }
1364
1365 #[test]
1368 fn test_duplicate_operation_names() {
1369 let input = make_contract_with_two_ops("update_count", "update_count");
1370 let result = parse_and_verify(&input);
1371 assert!(
1372 result
1373 .errors()
1374 .iter()
1375 .any(|d| d.message.contains("duplicate operation name")),
1376 "Should detect duplicate operation names: {:?}",
1377 result.diagnostics
1378 );
1379 }
1380
1381 #[test]
1382 fn test_unique_operation_names() {
1383 let input = make_contract_with_two_ops("create_item", "delete_item");
1384 let result = parse_and_verify(&input);
1385 assert!(
1386 !result
1387 .errors()
1388 .iter()
1389 .any(|d| d.message.contains("duplicate operation name")),
1390 "Unique operation names should pass: {:?}",
1391 result.errors()
1392 );
1393 }
1394
1395 #[test]
1396 fn test_duplicate_state_fields() {
1397 let input = make_contract_with_state("count: Integer, count: String");
1398 let result = parse_and_verify(&input);
1399 assert!(
1400 result
1401 .errors()
1402 .iter()
1403 .any(|d| d.message.contains("duplicate state field")),
1404 "Should detect duplicate state fields: {:?}",
1405 result.diagnostics
1406 );
1407 }
1408
1409 #[test]
1410 fn test_unknown_sandbox_mode_warning() {
1411 let input = make_contract_with_sandbox_mode("super_isolated");
1412 let result = parse_and_verify(&input);
1413 assert!(
1414 result
1415 .warnings()
1416 .iter()
1417 .any(|d| d.message.contains("sandbox_mode")),
1418 "Unknown sandbox_mode should warn: {:?}",
1419 result.diagnostics
1420 );
1421 }
1422
1423 #[test]
1424 fn test_valid_sandbox_modes() {
1425 for mode in &["full_isolation", "restricted", "none"] {
1426 let input = make_contract_with_sandbox_mode(mode);
1427 let result = parse_and_verify(&input);
1428 assert!(
1429 !result
1430 .warnings()
1431 .iter()
1432 .any(|d| d.message.contains("sandbox_mode")),
1433 "sandbox_mode '{}' should not warn: {:?}",
1434 mode,
1435 result.warnings()
1436 );
1437 }
1438 }
1439
1440 #[test]
1441 fn test_unknown_trigger_type_warning() {
1442 let input = make_contract_with_trigger_types(&["cron_job"]);
1443 let result = parse_and_verify(&input);
1444 assert!(
1445 result
1446 .warnings()
1447 .iter()
1448 .any(|d| d.message.contains("trigger_type")),
1449 "Unknown trigger_type should warn: {:?}",
1450 result.diagnostics
1451 );
1452 }
1453
1454 #[test]
1455 fn test_valid_trigger_types() {
1456 let input = make_contract_with_trigger_types(&["manual", "time_based", "event_based"]);
1457 let result = parse_and_verify(&input);
1458 assert!(
1459 !result
1460 .warnings()
1461 .iter()
1462 .any(|d| d.message.contains("trigger_type")),
1463 "Known trigger_types should not warn: {:?}",
1464 result.warnings()
1465 );
1466 }
1467
1468 #[test]
1471 fn test_conformance_valid_all_pass_verification() {
1472 let fixtures = [
1473 include_str!("../../../tests/fixtures/conformance/valid/minimal-contract.icl"),
1474 include_str!("../../../tests/fixtures/conformance/valid/all-primitive-types.icl"),
1475 include_str!("../../../tests/fixtures/conformance/valid/composite-types.icl"),
1476 include_str!("../../../tests/fixtures/conformance/valid/multiple-operations.icl"),
1477 ];
1478 for (i, fixture) in fixtures.iter().enumerate() {
1479 let result = parse_and_verify(fixture);
1480 assert!(
1481 result.is_valid(),
1482 "conformance fixture {} should verify: {:?}",
1483 i,
1484 result.errors()
1485 );
1486 }
1487 }
1488
1489 #[test]
1492 fn test_verification_determinism_100_iterations() {
1493 let input =
1494 include_str!("../../../tests/fixtures/conformance/valid/all-primitive-types.icl");
1495 let ast = parse(input).expect("should parse");
1496
1497 let first = verify(&ast);
1498 let first_count = first.diagnostics.len();
1499 let first_valid = first.is_valid();
1500
1501 for i in 0..100 {
1502 let result = verify(&ast);
1503 assert_eq!(
1504 result.diagnostics.len(),
1505 first_count,
1506 "Determinism failure at iteration {}: diagnostic count differs",
1507 i
1508 );
1509 assert_eq!(
1510 result.is_valid(),
1511 first_valid,
1512 "Determinism failure at iteration {}: validity differs",
1513 i
1514 );
1515 for (j, (a, b)) in first
1517 .diagnostics
1518 .iter()
1519 .zip(result.diagnostics.iter())
1520 .enumerate()
1521 {
1522 assert_eq!(
1523 a.message, b.message,
1524 "Determinism failure at iteration {}, diagnostic {}: messages differ",
1525 i, j
1526 );
1527 assert_eq!(
1528 a.severity, b.severity,
1529 "Determinism failure at iteration {}, diagnostic {}: severities differ",
1530 i, j
1531 );
1532 }
1533 }
1534 }
1535
1536 #[test]
1537 fn test_verification_determinism_complex_contract() {
1538 let input =
1539 include_str!("../../../tests/fixtures/conformance/valid/multiple-operations.icl");
1540 let ast = parse(input).expect("should parse");
1541
1542 let first = verify(&ast);
1543 for i in 0..100 {
1544 let result = verify(&ast);
1545 assert_eq!(
1546 result.diagnostics.len(),
1547 first.diagnostics.len(),
1548 "Determinism failure at iteration {} on complex contract",
1549 i
1550 );
1551 }
1552 }
1553
1554 fn make_contract_with_state(state_fields: &str) -> String {
1557 format!(
1558 r#"Contract {{
1559 Identity {{
1560 stable_id: "ic-test-001",
1561 version: 1,
1562 created_timestamp: 2026-02-01T00:00:00Z,
1563 owner: "test",
1564 semantic_hash: "0000000000000000"
1565 }}
1566 PurposeStatement {{
1567 narrative: "Test contract",
1568 intent_source: "test",
1569 confidence_level: 1.0
1570 }}
1571 DataSemantics {{
1572 state: {{
1573 {}
1574 }},
1575 invariants: []
1576 }}
1577 BehavioralSemantics {{
1578 operations: []
1579 }}
1580 ExecutionConstraints {{
1581 trigger_types: ["manual"],
1582 resource_limits: {{
1583 max_memory_bytes: 1048576,
1584 computation_timeout_ms: 100,
1585 max_state_size_bytes: 1048576
1586 }},
1587 external_permissions: [],
1588 sandbox_mode: "full_isolation"
1589 }}
1590 HumanMachineContract {{
1591 system_commitments: [],
1592 system_refusals: [],
1593 user_obligations: []
1594 }}
1595}}"#,
1596 state_fields
1597 )
1598 }
1599
1600 fn make_contract_with_state_and_invariants(state_fields: &str, invariants: &[&str]) -> String {
1601 let inv_str = invariants
1602 .iter()
1603 .map(|i| format!("\"{}\"", i))
1604 .collect::<Vec<_>>()
1605 .join(", ");
1606 format!(
1607 r#"Contract {{
1608 Identity {{
1609 stable_id: "ic-test-001",
1610 version: 1,
1611 created_timestamp: 2026-02-01T00:00:00Z,
1612 owner: "test",
1613 semantic_hash: "0000000000000000"
1614 }}
1615 PurposeStatement {{
1616 narrative: "Test contract",
1617 intent_source: "test",
1618 confidence_level: 1.0
1619 }}
1620 DataSemantics {{
1621 state: {{
1622 {}
1623 }},
1624 invariants: [{}]
1625 }}
1626 BehavioralSemantics {{
1627 operations: []
1628 }}
1629 ExecutionConstraints {{
1630 trigger_types: ["manual"],
1631 resource_limits: {{
1632 max_memory_bytes: 1048576,
1633 computation_timeout_ms: 100,
1634 max_state_size_bytes: 1048576
1635 }},
1636 external_permissions: [],
1637 sandbox_mode: "full_isolation"
1638 }}
1639 HumanMachineContract {{
1640 system_commitments: [],
1641 system_refusals: [],
1642 user_obligations: []
1643 }}
1644}}"#,
1645 state_fields, inv_str
1646 )
1647 }
1648
1649 fn make_contract_with_confidence(level: &str) -> String {
1650 format!(
1651 r#"Contract {{
1652 Identity {{
1653 stable_id: "ic-test-001",
1654 version: 1,
1655 created_timestamp: 2026-02-01T00:00:00Z,
1656 owner: "test",
1657 semantic_hash: "0000000000000000"
1658 }}
1659 PurposeStatement {{
1660 narrative: "Test contract",
1661 intent_source: "test",
1662 confidence_level: {}
1663 }}
1664 DataSemantics {{
1665 state: {{
1666 value: String
1667 }},
1668 invariants: []
1669 }}
1670 BehavioralSemantics {{
1671 operations: []
1672 }}
1673 ExecutionConstraints {{
1674 trigger_types: ["manual"],
1675 resource_limits: {{
1676 max_memory_bytes: 1048576,
1677 computation_timeout_ms: 100,
1678 max_state_size_bytes: 1048576
1679 }},
1680 external_permissions: [],
1681 sandbox_mode: "full_isolation"
1682 }}
1683 HumanMachineContract {{
1684 system_commitments: [],
1685 system_refusals: [],
1686 user_obligations: []
1687 }}
1688}}"#,
1689 level
1690 )
1691 }
1692
1693 fn make_contract_with_resource_limits(mem: i64, timeout: i64, state: i64) -> String {
1694 format!(
1695 r#"Contract {{
1696 Identity {{
1697 stable_id: "ic-test-001",
1698 version: 1,
1699 created_timestamp: 2026-02-01T00:00:00Z,
1700 owner: "test",
1701 semantic_hash: "0000000000000000"
1702 }}
1703 PurposeStatement {{
1704 narrative: "Test contract",
1705 intent_source: "test",
1706 confidence_level: 1.0
1707 }}
1708 DataSemantics {{
1709 state: {{
1710 value: String
1711 }},
1712 invariants: []
1713 }}
1714 BehavioralSemantics {{
1715 operations: []
1716 }}
1717 ExecutionConstraints {{
1718 trigger_types: ["manual"],
1719 resource_limits: {{
1720 max_memory_bytes: {},
1721 computation_timeout_ms: {},
1722 max_state_size_bytes: {}
1723 }},
1724 external_permissions: [],
1725 sandbox_mode: "full_isolation"
1726 }}
1727 HumanMachineContract {{
1728 system_commitments: [],
1729 system_refusals: [],
1730 user_obligations: []
1731 }}
1732}}"#,
1733 mem, timeout, state
1734 )
1735 }
1736
1737 fn make_contract_with_stable_id(id: &str) -> String {
1738 format!(
1739 r#"Contract {{
1740 Identity {{
1741 stable_id: "{}",
1742 version: 1,
1743 created_timestamp: 2026-02-01T00:00:00Z,
1744 owner: "test",
1745 semantic_hash: "0000000000000000"
1746 }}
1747 PurposeStatement {{
1748 narrative: "Test contract",
1749 intent_source: "test",
1750 confidence_level: 1.0
1751 }}
1752 DataSemantics {{
1753 state: {{
1754 value: String
1755 }},
1756 invariants: []
1757 }}
1758 BehavioralSemantics {{
1759 operations: []
1760 }}
1761 ExecutionConstraints {{
1762 trigger_types: ["manual"],
1763 resource_limits: {{
1764 max_memory_bytes: 1048576,
1765 computation_timeout_ms: 100,
1766 max_state_size_bytes: 1048576
1767 }},
1768 external_permissions: [],
1769 sandbox_mode: "full_isolation"
1770 }}
1771 HumanMachineContract {{
1772 system_commitments: [],
1773 system_refusals: [],
1774 user_obligations: []
1775 }}
1776}}"#,
1777 id
1778 )
1779 }
1780
1781 fn make_contract_with_hash(hash: &str) -> String {
1782 format!(
1783 r#"Contract {{
1784 Identity {{
1785 stable_id: "ic-test-001",
1786 version: 1,
1787 created_timestamp: 2026-02-01T00:00:00Z,
1788 owner: "test",
1789 semantic_hash: "{}"
1790 }}
1791 PurposeStatement {{
1792 narrative: "Test contract",
1793 intent_source: "test",
1794 confidence_level: 1.0
1795 }}
1796 DataSemantics {{
1797 state: {{
1798 value: String
1799 }},
1800 invariants: []
1801 }}
1802 BehavioralSemantics {{
1803 operations: []
1804 }}
1805 ExecutionConstraints {{
1806 trigger_types: ["manual"],
1807 resource_limits: {{
1808 max_memory_bytes: 1048576,
1809 computation_timeout_ms: 100,
1810 max_state_size_bytes: 1048576
1811 }},
1812 external_permissions: [],
1813 sandbox_mode: "full_isolation"
1814 }}
1815 HumanMachineContract {{
1816 system_commitments: [],
1817 system_refusals: [],
1818 user_obligations: []
1819 }}
1820}}"#,
1821 hash
1822 )
1823 }
1824
1825 fn make_contract_with_operation(name: &str, precondition: &str, postcondition: &str) -> String {
1826 format!(
1827 r#"Contract {{
1828 Identity {{
1829 stable_id: "ic-test-001",
1830 version: 1,
1831 created_timestamp: 2026-02-01T00:00:00Z,
1832 owner: "test",
1833 semantic_hash: "0000000000000000"
1834 }}
1835 PurposeStatement {{
1836 narrative: "Test contract",
1837 intent_source: "test",
1838 confidence_level: 1.0
1839 }}
1840 DataSemantics {{
1841 state: {{
1842 count: Integer = 0,
1843 result: String
1844 }},
1845 invariants: []
1846 }}
1847 BehavioralSemantics {{
1848 operations: [
1849 {{
1850 name: "{}",
1851 precondition: "{}",
1852 parameters: {{}},
1853 postcondition: "{}",
1854 side_effects: [],
1855 idempotence: "idempotent"
1856 }}
1857 ]
1858 }}
1859 ExecutionConstraints {{
1860 trigger_types: ["manual"],
1861 resource_limits: {{
1862 max_memory_bytes: 1048576,
1863 computation_timeout_ms: 100,
1864 max_state_size_bytes: 1048576
1865 }},
1866 external_permissions: [],
1867 sandbox_mode: "full_isolation"
1868 }}
1869 HumanMachineContract {{
1870 system_commitments: [],
1871 system_refusals: [],
1872 user_obligations: []
1873 }}
1874}}"#,
1875 name, precondition, postcondition
1876 )
1877 }
1878
1879 fn make_contract_with_two_ops(name1: &str, name2: &str) -> String {
1880 format!(
1881 r#"Contract {{
1882 Identity {{
1883 stable_id: "ic-test-001",
1884 version: 1,
1885 created_timestamp: 2026-02-01T00:00:00Z,
1886 owner: "test",
1887 semantic_hash: "0000000000000000"
1888 }}
1889 PurposeStatement {{
1890 narrative: "Test contract",
1891 intent_source: "test",
1892 confidence_level: 1.0
1893 }}
1894 DataSemantics {{
1895 state: {{
1896 count: Integer = 0
1897 }},
1898 invariants: []
1899 }}
1900 BehavioralSemantics {{
1901 operations: [
1902 {{
1903 name: "{}",
1904 precondition: "true",
1905 parameters: {{}},
1906 postcondition: "done",
1907 side_effects: [],
1908 idempotence: "idempotent"
1909 }},
1910 {{
1911 name: "{}",
1912 precondition: "true",
1913 parameters: {{}},
1914 postcondition: "done",
1915 side_effects: [],
1916 idempotence: "idempotent"
1917 }}
1918 ]
1919 }}
1920 ExecutionConstraints {{
1921 trigger_types: ["manual"],
1922 resource_limits: {{
1923 max_memory_bytes: 1048576,
1924 computation_timeout_ms: 100,
1925 max_state_size_bytes: 1048576
1926 }},
1927 external_permissions: [],
1928 sandbox_mode: "full_isolation"
1929 }}
1930 HumanMachineContract {{
1931 system_commitments: [],
1932 system_refusals: [],
1933 user_obligations: []
1934 }}
1935}}"#,
1936 name1, name2
1937 )
1938 }
1939
1940 fn make_contract_with_sandbox_mode(mode: &str) -> String {
1941 format!(
1942 r#"Contract {{
1943 Identity {{
1944 stable_id: "ic-test-001",
1945 version: 1,
1946 created_timestamp: 2026-02-01T00:00:00Z,
1947 owner: "test",
1948 semantic_hash: "0000000000000000"
1949 }}
1950 PurposeStatement {{
1951 narrative: "Test contract",
1952 intent_source: "test",
1953 confidence_level: 1.0
1954 }}
1955 DataSemantics {{
1956 state: {{
1957 value: String
1958 }},
1959 invariants: []
1960 }}
1961 BehavioralSemantics {{
1962 operations: []
1963 }}
1964 ExecutionConstraints {{
1965 trigger_types: ["manual"],
1966 resource_limits: {{
1967 max_memory_bytes: 1048576,
1968 computation_timeout_ms: 100,
1969 max_state_size_bytes: 1048576
1970 }},
1971 external_permissions: [],
1972 sandbox_mode: "{}"
1973 }}
1974 HumanMachineContract {{
1975 system_commitments: [],
1976 system_refusals: [],
1977 user_obligations: []
1978 }}
1979}}"#,
1980 mode
1981 )
1982 }
1983
1984 fn make_contract_with_trigger_types(types: &[&str]) -> String {
1985 let types_str = types
1986 .iter()
1987 .map(|t| format!("\"{}\"", t))
1988 .collect::<Vec<_>>()
1989 .join(", ");
1990 format!(
1991 r#"Contract {{
1992 Identity {{
1993 stable_id: "ic-test-001",
1994 version: 1,
1995 created_timestamp: 2026-02-01T00:00:00Z,
1996 owner: "test",
1997 semantic_hash: "0000000000000000"
1998 }}
1999 PurposeStatement {{
2000 narrative: "Test contract",
2001 intent_source: "test",
2002 confidence_level: 1.0
2003 }}
2004 DataSemantics {{
2005 state: {{
2006 value: String
2007 }},
2008 invariants: []
2009 }}
2010 BehavioralSemantics {{
2011 operations: []
2012 }}
2013 ExecutionConstraints {{
2014 trigger_types: [{}],
2015 resource_limits: {{
2016 max_memory_bytes: 1048576,
2017 computation_timeout_ms: 100,
2018 max_state_size_bytes: 1048576
2019 }},
2020 external_permissions: [],
2021 sandbox_mode: "full_isolation"
2022 }}
2023 HumanMachineContract {{
2024 system_commitments: [],
2025 system_refusals: [],
2026 user_obligations: []
2027 }}
2028}}"#,
2029 types_str
2030 )
2031 }
2032
2033 fn dummy_span() -> Span {
2035 Span {
2036 line: 0,
2037 column: 0,
2038 offset: 0,
2039 }
2040 }
2041
2042 fn make_valid_ast() -> ContractNode {
2044 ContractNode {
2045 identity: IdentityNode {
2046 stable_id: SpannedValue::new("ic-test-001".to_string(), dummy_span()),
2047 version: SpannedValue::new(1, dummy_span()),
2048 created_timestamp: SpannedValue::new(
2049 "2026-02-01T00:00:00Z".to_string(),
2050 dummy_span(),
2051 ),
2052 owner: SpannedValue::new("test".to_string(), dummy_span()),
2053 semantic_hash: SpannedValue::new("0000000000000000".to_string(), dummy_span()),
2054 span: dummy_span(),
2055 },
2056 purpose_statement: PurposeStatementNode {
2057 narrative: SpannedValue::new("Test contract".to_string(), dummy_span()),
2058 intent_source: SpannedValue::new("test".to_string(), dummy_span()),
2059 confidence_level: SpannedValue::new(1.0, dummy_span()),
2060 span: dummy_span(),
2061 },
2062 data_semantics: DataSemanticsNode {
2063 state: vec![StateFieldNode {
2064 name: SpannedValue::new("value".to_string(), dummy_span()),
2065 type_expr: TypeExpression::Primitive(PrimitiveType::String, dummy_span()),
2066 default_value: None,
2067 span: dummy_span(),
2068 }],
2069 invariants: vec![],
2070 span: dummy_span(),
2071 },
2072 behavioral_semantics: BehavioralSemanticsNode {
2073 operations: vec![],
2074 span: dummy_span(),
2075 },
2076 execution_constraints: ExecutionConstraintsNode {
2077 trigger_types: vec![SpannedValue::new("manual".to_string(), dummy_span())],
2078 resource_limits: ResourceLimitsNode {
2079 max_memory_bytes: SpannedValue::new(1048576, dummy_span()),
2080 computation_timeout_ms: SpannedValue::new(100, dummy_span()),
2081 max_state_size_bytes: SpannedValue::new(1048576, dummy_span()),
2082 span: dummy_span(),
2083 },
2084 external_permissions: vec![],
2085 sandbox_mode: SpannedValue::new("full_isolation".to_string(), dummy_span()),
2086 span: dummy_span(),
2087 },
2088 human_machine_contract: HumanMachineContractNode {
2089 system_commitments: vec![],
2090 system_refusals: vec![],
2091 user_obligations: vec![],
2092 span: dummy_span(),
2093 },
2094 extensions: None,
2095 span: dummy_span(),
2096 }
2097 }
2098}