kumi 0.0.0 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +113 -3
  3. data/CHANGELOG.md +21 -1
  4. data/CLAUDE.md +387 -0
  5. data/README.md +270 -20
  6. data/docs/development/README.md +120 -0
  7. data/docs/development/error-reporting.md +361 -0
  8. data/documents/AST.md +126 -0
  9. data/documents/DSL.md +154 -0
  10. data/documents/FUNCTIONS.md +132 -0
  11. data/documents/SYNTAX.md +367 -0
  12. data/examples/deep_schema_compilation_and_evaluation_benchmark.rb +106 -0
  13. data/examples/federal_tax_calculator_2024.rb +112 -0
  14. data/examples/wide_schema_compilation_and_evaluation_benchmark.rb +80 -0
  15. data/lib/generators/trait_engine/templates/schema_spec.rb.erb +27 -0
  16. data/lib/kumi/analyzer/constant_evaluator.rb +51 -0
  17. data/lib/kumi/analyzer/passes/definition_validator.rb +42 -0
  18. data/lib/kumi/analyzer/passes/dependency_resolver.rb +71 -0
  19. data/lib/kumi/analyzer/passes/input_collector.rb +55 -0
  20. data/lib/kumi/analyzer/passes/name_indexer.rb +24 -0
  21. data/lib/kumi/analyzer/passes/pass_base.rb +67 -0
  22. data/lib/kumi/analyzer/passes/toposorter.rb +72 -0
  23. data/lib/kumi/analyzer/passes/type_checker.rb +139 -0
  24. data/lib/kumi/analyzer/passes/type_consistency_checker.rb +45 -0
  25. data/lib/kumi/analyzer/passes/type_inferencer.rb +125 -0
  26. data/lib/kumi/analyzer/passes/unsat_detector.rb +107 -0
  27. data/lib/kumi/analyzer/passes/visitor_pass.rb +41 -0
  28. data/lib/kumi/analyzer.rb +54 -0
  29. data/lib/kumi/atom_unsat_solver.rb +349 -0
  30. data/lib/kumi/compiled_schema.rb +41 -0
  31. data/lib/kumi/compiler.rb +127 -0
  32. data/lib/kumi/domain/enum_analyzer.rb +53 -0
  33. data/lib/kumi/domain/range_analyzer.rb +83 -0
  34. data/lib/kumi/domain/validator.rb +84 -0
  35. data/lib/kumi/domain/violation_formatter.rb +40 -0
  36. data/lib/kumi/domain.rb +8 -0
  37. data/lib/kumi/error_reporter.rb +164 -0
  38. data/lib/kumi/error_reporting.rb +95 -0
  39. data/lib/kumi/errors.rb +116 -0
  40. data/lib/kumi/evaluation_wrapper.rb +22 -0
  41. data/lib/kumi/explain.rb +281 -0
  42. data/lib/kumi/export/deserializer.rb +39 -0
  43. data/lib/kumi/export/errors.rb +12 -0
  44. data/lib/kumi/export/node_builders.rb +140 -0
  45. data/lib/kumi/export/node_registry.rb +38 -0
  46. data/lib/kumi/export/node_serializers.rb +156 -0
  47. data/lib/kumi/export/serializer.rb +23 -0
  48. data/lib/kumi/export.rb +33 -0
  49. data/lib/kumi/function_registry/collection_functions.rb +92 -0
  50. data/lib/kumi/function_registry/comparison_functions.rb +31 -0
  51. data/lib/kumi/function_registry/conditional_functions.rb +36 -0
  52. data/lib/kumi/function_registry/function_builder.rb +92 -0
  53. data/lib/kumi/function_registry/logical_functions.rb +42 -0
  54. data/lib/kumi/function_registry/math_functions.rb +72 -0
  55. data/lib/kumi/function_registry/string_functions.rb +54 -0
  56. data/lib/kumi/function_registry/type_functions.rb +51 -0
  57. data/lib/kumi/function_registry.rb +138 -0
  58. data/lib/kumi/input/type_matcher.rb +92 -0
  59. data/lib/kumi/input/validator.rb +52 -0
  60. data/lib/kumi/input/violation_creator.rb +50 -0
  61. data/lib/kumi/input.rb +8 -0
  62. data/lib/kumi/parser/build_context.rb +25 -0
  63. data/lib/kumi/parser/dsl.rb +12 -0
  64. data/lib/kumi/parser/dsl_cascade_builder.rb +125 -0
  65. data/lib/kumi/parser/expression_converter.rb +58 -0
  66. data/lib/kumi/parser/guard_rails.rb +43 -0
  67. data/lib/kumi/parser/input_builder.rb +94 -0
  68. data/lib/kumi/parser/input_proxy.rb +29 -0
  69. data/lib/kumi/parser/parser.rb +66 -0
  70. data/lib/kumi/parser/schema_builder.rb +172 -0
  71. data/lib/kumi/parser/sugar.rb +108 -0
  72. data/lib/kumi/schema.rb +49 -0
  73. data/lib/kumi/schema_instance.rb +43 -0
  74. data/lib/kumi/syntax/declarations.rb +23 -0
  75. data/lib/kumi/syntax/expressions.rb +30 -0
  76. data/lib/kumi/syntax/node.rb +46 -0
  77. data/lib/kumi/syntax/root.rb +12 -0
  78. data/lib/kumi/syntax/terminal_expressions.rb +27 -0
  79. data/lib/kumi/syntax.rb +9 -0
  80. data/lib/kumi/types/builder.rb +21 -0
  81. data/lib/kumi/types/compatibility.rb +86 -0
  82. data/lib/kumi/types/formatter.rb +24 -0
  83. data/lib/kumi/types/inference.rb +40 -0
  84. data/lib/kumi/types/normalizer.rb +70 -0
  85. data/lib/kumi/types/validator.rb +35 -0
  86. data/lib/kumi/types.rb +64 -0
  87. data/lib/kumi/version.rb +1 -1
  88. data/lib/kumi.rb +7 -3
  89. data/scripts/generate_function_docs.rb +59 -0
  90. data/test_impossible_cascade.rb +51 -0
  91. metadata +93 -10
  92. data/sig/kumi.rbs +0 -4
@@ -0,0 +1,361 @@
1
+ # Error Reporting Standards
2
+
3
+ This guide provides comprehensive standards for error reporting in Kumi, ensuring consistent, localized error messages throughout the system.
4
+
5
+ ## Overview
6
+
7
+ Kumi uses a unified error reporting interface that:
8
+ - Provides consistent location information (file:line:column)
9
+ - Categorizes errors by type (syntax, semantic, type, runtime)
10
+ - Supports both immediate raising and error accumulation patterns
11
+ - Maintains backward compatibility with existing tests
12
+ - Enables enhanced error messages with suggestions and context
13
+
14
+ ## Core Interface Components
15
+
16
+ ### ErrorReporter Module
17
+ Central error reporting functionality with standardized error entries:
18
+
19
+ ```ruby
20
+ # Create structured error entry
21
+ entry = ErrorReporter.create_error(
22
+ "Error message",
23
+ location: node.loc,
24
+ type: :semantic,
25
+ context: { additional: "info" }
26
+ )
27
+
28
+ # Add error to accumulator
29
+ ErrorReporter.add_error(errors, "message", location: node.loc)
30
+
31
+ # Immediately raise error
32
+ ErrorReporter.raise_error("message", location: node.loc, error_class: Errors::SyntaxError)
33
+ ```
34
+
35
+ ### ErrorReporting Mixin
36
+ Convenient methods for classes that need error reporting:
37
+
38
+ ```ruby
39
+ class MyClass
40
+ include ErrorReporting
41
+
42
+ def process
43
+ # Accumulated errors (analyzer pattern)
44
+ report_error(errors, "message", location: node.loc, type: :semantic)
45
+
46
+ # Immediate errors (parser pattern)
47
+ raise_localized_error("message", location: node.loc, error_class: Errors::SyntaxError)
48
+ end
49
+ end
50
+ ```
51
+
52
+ ## Implementation Patterns
53
+
54
+ ### Parser Classes (Immediate Errors)
55
+ Parser classes should raise errors immediately when encountered:
56
+
57
+ ```ruby
58
+ class DslBuilderContext
59
+ include ErrorReporting
60
+
61
+ def validate_name(name, type, location)
62
+ return if name.is_a?(Symbol)
63
+
64
+ raise_syntax_error(
65
+ "The name for '#{type}' must be a Symbol, got #{name.class}",
66
+ location: location
67
+ )
68
+ end
69
+
70
+ def raise_error(message, location)
71
+ # Legacy method - delegates to new interface
72
+ raise_syntax_error(message, location: location)
73
+ end
74
+ end
75
+ ```
76
+
77
+ ### Analyzer Passes (Accumulated Errors)
78
+ Analyzer passes should accumulate errors and report them at the end:
79
+
80
+ ```ruby
81
+ class MyAnalyzerPass < PassBase
82
+ def run(errors)
83
+ each_decl do |decl|
84
+ validate_declaration(decl, errors)
85
+ end
86
+ end
87
+
88
+ private
89
+
90
+ def validate_declaration(decl, errors)
91
+ # New error reporting method
92
+ report_error(
93
+ errors,
94
+ "Validation failed for #{decl.name}",
95
+ location: decl.loc,
96
+ type: :semantic
97
+ )
98
+
99
+ # Legacy method (backward compatible)
100
+ add_error(errors, decl.loc, "Legacy format message")
101
+ end
102
+ end
103
+ ```
104
+
105
+ ## Location Resolution Best Practices
106
+
107
+ ### Always Provide Location When Available
108
+ ```ruby
109
+ # Good: Specific node location
110
+ report_error(errors, "Type mismatch", location: node.loc)
111
+
112
+ # Acceptable: Fallback location
113
+ report_error(errors, "Cycle detected", location: first_node&.loc || :cycle)
114
+
115
+ # Avoid: No location information
116
+ report_error(errors, "Error occurred", location: nil)
117
+ ```
118
+
119
+ ### Complex Error Location Resolution
120
+ For errors that span multiple nodes or are contextual:
121
+
122
+ ```ruby
123
+ def report_cycle(cycle_path, errors)
124
+ # Find first declaration in cycle for location context
125
+ first_decl = find_declaration_by_name(cycle_path.first)
126
+ location = first_decl&.loc || :cycle
127
+
128
+ report_error(
129
+ errors,
130
+ "cycle detected: #{cycle_path.join(' → ')}",
131
+ location: location,
132
+ type: :semantic
133
+ )
134
+ end
135
+
136
+ def find_declaration_by_name(name)
137
+ return nil unless schema
138
+
139
+ schema.attributes.find { |attr| attr.name == name } ||
140
+ schema.traits.find { |trait| trait.name == name }
141
+ end
142
+ ```
143
+
144
+ ### Location Fallbacks
145
+ When AST location is not available, use meaningful symbolic locations:
146
+
147
+ ```ruby
148
+ # Cycle detection
149
+ location = node.loc || :cycle
150
+
151
+ # Type inference failures
152
+ location = decl.loc || :type_inference
153
+
154
+ # Cross-reference resolution
155
+ location = ref_node.loc || :reference_resolution
156
+ ```
157
+
158
+ ## Error Categorization
159
+
160
+ ### Error Types
161
+ - `:syntax` - Parse-time structural errors
162
+ - `:semantic` - Analysis-time logical errors
163
+ - `:type` - Type system violations
164
+ - `:runtime` - Execution-time failures
165
+
166
+ ### Type-specific Methods
167
+ ```ruby
168
+ # Syntax errors (parser)
169
+ report_syntax_error(errors, "Invalid syntax", location: loc)
170
+ raise_syntax_error("Invalid syntax", location: loc)
171
+
172
+ # Semantic errors (analyzer)
173
+ report_semantic_error(errors, "Logic error", location: loc)
174
+
175
+ # Type errors (type checker)
176
+ report_type_error(errors, "Type mismatch", location: loc)
177
+ ```
178
+
179
+ ## Enhanced Error Messages
180
+
181
+ ### Basic Enhanced Errors
182
+ ```ruby
183
+ report_enhanced_error(
184
+ errors,
185
+ "undefined reference to `missing_field`",
186
+ location: node.loc,
187
+ similar_names: ["missing_value", "missing_data"],
188
+ suggestions: [
189
+ "Check spelling of field name",
190
+ "Ensure field is declared in input block"
191
+ ]
192
+ )
193
+ ```
194
+
195
+ ### Context-rich Errors
196
+ ```ruby
197
+ report_error(
198
+ errors,
199
+ "Type mismatch in function call",
200
+ location: call_node.loc,
201
+ type: :type,
202
+ context: {
203
+ function: call_node.fn_name,
204
+ expected_type: expected,
205
+ actual_type: actual,
206
+ argument_position: position
207
+ }
208
+ )
209
+ ```
210
+
211
+ ## Backward Compatibility
212
+
213
+ ### Legacy Format Support
214
+ The system supports both legacy `[location, message]` arrays and new `ErrorEntry` objects:
215
+
216
+ ```ruby
217
+ # Analyzer.format_errors handles both formats
218
+ def format_errors(errors)
219
+ errors.map do |error|
220
+ case error
221
+ when ErrorReporter::ErrorEntry
222
+ error.to_s # New format: "at file.rb:10:5: message"
223
+ when Array
224
+ loc, msg = error
225
+ "at #{loc || '?'}: #{msg}" # Legacy format
226
+ end
227
+ end.join("\n")
228
+ end
229
+ ```
230
+
231
+ ### Migration Strategy
232
+ 1. **New code**: Use new error reporting methods (`report_error`, `raise_localized_error`)
233
+ 2. **Existing code**: No changes required - `add_error` method maintained for compatibility
234
+ 3. **Enhanced features**: Migrate to new methods to access suggestions, context, and categorization
235
+
236
+ ## Testing Error Reporting
237
+
238
+ ### Error Location Testing
239
+ ```ruby
240
+ RSpec.describe "Error Location Verification" do
241
+ it "reports errors at correct locations" do
242
+ schema_code = <<~RUBY
243
+ Kumi.schema do
244
+ input { integer :age }
245
+ trait :adult, (input.age >= 18)
246
+ trait :adult, (input.age >= 21) # Line 4: Duplicate
247
+ end
248
+ RUBY
249
+
250
+ begin
251
+ eval(schema_code, binding, "test.rb", 1)
252
+ rescue Kumi::Errors::SemanticError => e
253
+ expect(e.message).to include("test.rb:4")
254
+ expect(e.message).to include("duplicated definition")
255
+ end
256
+ end
257
+ end
258
+ ```
259
+
260
+ ### Error Quality Testing
261
+ ```ruby
262
+ it "provides comprehensive error information" do
263
+ error = expect_semantic_error do
264
+ schema do
265
+ input { string :name }
266
+ value :result, fn(:add, input.name, 5)
267
+ end
268
+ end
269
+
270
+ expect(error.message).to include("add") # Function name
271
+ expect(error.message).to include("string") # Actual type
272
+ expect(error.message).to include("expects") # Clear expectation
273
+ expect(error.message).to match(/:\d+:/) # Line number
274
+ end
275
+ ```
276
+
277
+ ### Edge Case Testing
278
+ Use `spec/integration/potential_breakage_spec.rb` patterns:
279
+
280
+ ```ruby
281
+ it "detects edge case that should break" do
282
+ expect do
283
+ schema do
284
+ input { integer :x }
285
+ # Edge case that might not be caught
286
+ value :result, some_edge_case_construct
287
+ end
288
+ end.to raise_error(Kumi::Errors::SemanticError)
289
+ end
290
+ ```
291
+
292
+ ## Performance Considerations
293
+
294
+ ### Error Object Creation
295
+ - ErrorEntry objects are lightweight structs
296
+ - Location formatting is lazy (only when `to_s` is called)
297
+ - Context information is stored efficiently in hashes
298
+
299
+ ### Batch Error Processing
300
+ For analyzer passes processing many nodes:
301
+
302
+ ```ruby
303
+ def run(errors)
304
+ # Batch process nodes to minimize error object creation
305
+ invalid_nodes = collect_invalid_nodes
306
+
307
+ invalid_nodes.each do |node|
308
+ report_error(errors, "Invalid: #{node.name}", location: node.loc)
309
+ end
310
+ end
311
+ ```
312
+
313
+ ## Common Patterns and Anti-patterns
314
+
315
+ ### ✅ Good Patterns
316
+ ```ruby
317
+ # Clear, specific error messages
318
+ report_error(errors, "argument 1 of `fn(:add)` expects float, got string", location: arg.loc)
319
+
320
+ # Proper location resolution
321
+ location = node.loc || fallback_location_for_context
322
+
323
+ # Type-appropriate error categorization
324
+ report_type_error(errors, "type mismatch", location: node.loc)
325
+ ```
326
+
327
+ ### ❌ Anti-patterns
328
+ ```ruby
329
+ # Vague error messages
330
+ report_error(errors, "error", location: node.loc)
331
+
332
+ # Missing location information
333
+ report_error(errors, "something failed", location: nil)
334
+
335
+ # Wrong error categorization
336
+ report_syntax_error(errors, "type mismatch", location: node.loc) # Should be type error
337
+ ```
338
+
339
+ ## Error Message Guidelines
340
+
341
+ ### Message Format
342
+ - Start with lowercase (automatic capitalization in display)
343
+ - Be specific about what failed and why
344
+ - Include relevant context (function names, types, values)
345
+ - Avoid technical jargon in user-facing messages
346
+
347
+ ### Examples
348
+ ```ruby
349
+ # Good messages
350
+ "argument 1 of `fn(:add)` expects float, got input field `name` of declared type string"
351
+ "duplicated definition `adult`"
352
+ "undefined reference to `missing_field`"
353
+ "cycle detected: a → b → a"
354
+
355
+ # Messages to improve
356
+ "validation failed"
357
+ "error in processing"
358
+ "something went wrong"
359
+ ```
360
+
361
+ This error reporting system ensures that users get clear, actionable feedback about issues in their Kumi schemas, with precise location information to help them fix problems quickly.
data/documents/AST.md ADDED
@@ -0,0 +1,126 @@
1
+ # Kumi AST Reference
2
+
3
+ ## Core Node Types
4
+
5
+ **Root**: Schema container
6
+ ```ruby
7
+ Root = Struct.new(:inputs, :attributes, :traits)
8
+ ```
9
+
10
+ **FieldDecl**: Input field metadata
11
+ ```ruby
12
+ FieldDecl = Struct.new(:name, :domain, :type)
13
+ # DSL: integer :age, domain: 18..65 → FieldDecl(:age, 18..65, :integer)
14
+ ```
15
+
16
+ **Trait**: Boolean predicate
17
+ ```ruby
18
+ Trait = Struct.new(:name, :expression)
19
+ # DSL: trait :adult, (input.age >= 18) → Trait(:adult, CallExpression(...))
20
+ ```
21
+
22
+ **Attribute**: Computed value
23
+ ```ruby
24
+ Attribute = Struct.new(:name, :expression)
25
+ # DSL: value :total, fn(:add, a, b) → Attribute(:total, CallExpression(:add, [...]))
26
+ ```
27
+
28
+ ## Expression Nodes
29
+
30
+ **CallExpression**: Function calls and operators
31
+ ```ruby
32
+ CallExpression = Struct.new(:fn_name, :args)
33
+ def &(other) = CallExpression.new(:and, [self, other]) # Enable chaining
34
+ ```
35
+
36
+ **FieldRef**: Field access (`input.field_name`)
37
+ ```ruby
38
+ FieldRef = Struct.new(:name)
39
+ # Has operator methods: >=, <=, >, <, ==, != that create CallExpression nodes
40
+ ```
41
+
42
+ **Binding**: References to other declarations
43
+ ```ruby
44
+ Binding = Struct.new(:name)
45
+ # Created by: ref(:name) OR bare identifier (trait_name) in composite traits
46
+ # DSL: ref(:adult) → Binding(:adult)
47
+ # DSL: adult & verified → CallExpression(:and, [Binding(:adult), Binding(:verified)])
48
+ ```
49
+
50
+ **Literal**: Constants (`18`, `"text"`, `true`)
51
+ ```ruby
52
+ Literal = Struct.new(:value)
53
+ ```
54
+
55
+ **ListExpression**: Arrays (`[1, 2, 3]`)
56
+ ```ruby
57
+ ListExpression = Struct.new(:elements)
58
+ ```
59
+
60
+ ## Cascade Expressions (Conditional Values)
61
+
62
+ **CascadeExpression**: Container for conditional logic
63
+ ```ruby
64
+ CascadeExpression = Struct.new(:cases)
65
+ ```
66
+
67
+ **WhenCaseExpression**: Individual conditions
68
+ ```ruby
69
+ WhenCaseExpression = Struct.new(:condition, :result)
70
+ ```
71
+
72
+ **Case type mappings**:
73
+ - `on :a, :b, result` → `condition: fn(:all?, ref(:a), ref(:b))`
74
+ - `on_any :a, :b, result` → `condition: fn(:any?, ref(:a), ref(:b))`
75
+ - `base result` → `condition: literal(true)`
76
+
77
+ ## Key Nuances
78
+
79
+ **Operator methods on FieldRef**: Enable `input.age >= 18` syntax by defining operators that create `CallExpression` nodes
80
+
81
+ **CallExpression `&` method**: Enables expression chaining like `(expr1) & (expr2)`
82
+
83
+ **Node immutability**: AST nodes are frozen after construction; analysis results stored separately
84
+
85
+ **Location tracking**: All nodes include file/line/column for error reporting
86
+
87
+ **Tree traversal**: Each node defines `children` method for recursive processing
88
+
89
+ **Expression wrapping**: During parsing, raw values auto-convert to `Literal` nodes via `ensure_syntax()`
90
+
91
+ ## Common Expression Trees
92
+
93
+ **Simple**: `(input.age >= 18)`
94
+ ```
95
+ CallExpression(:>=, [FieldRef(:age), Literal(18)])
96
+ ```
97
+
98
+ **Chained AND**: `(input.age >= 21) & (input.verified == true)`
99
+ ```
100
+ CallExpression(:and, [
101
+ CallExpression(:>=, [FieldRef(:age), Literal(21)]),
102
+ CallExpression(:==, [FieldRef(:verified), Literal(true)])
103
+ ])
104
+ ```
105
+
106
+ **Composite Trait**: `adult & verified & high_income`
107
+ ```
108
+ CallExpression(:and, [
109
+ CallExpression(:and, [
110
+ Binding(:adult),
111
+ Binding(:verified)
112
+ ]),
113
+ Binding(:high_income)
114
+ ])
115
+ ```
116
+
117
+ **Mixed Composition**: `adult & (input.score > 80) & verified`
118
+ ```
119
+ CallExpression(:and, [
120
+ CallExpression(:and, [
121
+ Binding(:adult),
122
+ CallExpression(:>, [FieldRef(:score), Literal(80)])
123
+ ]),
124
+ Binding(:verified)
125
+ ])
126
+ ```
data/documents/DSL.md ADDED
@@ -0,0 +1,154 @@
1
+ # Kumi DSL Reference
2
+
3
+ Kumi is a declarative language for defining, analyzing, and executing complex business logic. It compiles rules into a verifiable dependency graph, ensuring that logic is **sound, maintainable, and free of contradictions** before execution (as much as possible given the current library implementation).
4
+
5
+ -----
6
+
7
+ ## Guiding Principles
8
+
9
+ Kumi's design is opinionated and guides you toward creating robust and analyzable business logic.
10
+
11
+ * **Logic as Code, Not Just Configuration**: Rules are expressed in a clean, readable DSL that can be version-controlled and tested.
12
+ * **Provable Correctness**: A multi-pass analyzer statically verifies your schema, detecting duplicates, circular dependencies, type errors, and even **logically impossible conditions** (e.g., `age < 25 AND age > 65`) at compile time.
13
+ * **Explicit Data Contracts**: The mandatory `input` block serves as a formal, self-documenting contract for the data your schema expects, enabling runtime validation of types and domain constraints.
14
+ * **Composition Over Complexity**: Complex rules are built by composing simpler, named concepts (`trait`s), rather than creating large, monolithic blocks of logic.
15
+
16
+ -----
17
+
18
+ ## Core Syntax
19
+
20
+ A Kumi schema contains an `input` block to declare its data contract, followed by `trait` and `value` definitions.
21
+
22
+ ```ruby
23
+ schema do
24
+ # 1. Define the data contract for this schema.
25
+ input do
26
+ # ... field declarations
27
+ end
28
+
29
+ # 2. Define reusable boolean predicates (traits).
30
+ # ... trait definitions
31
+
32
+ # 3. Define computed fields (values).
33
+ # ... value definitions
34
+ end
35
+ ```
36
+
37
+ -----
38
+
39
+ ## Input Fields: The Data Contract
40
+
41
+ The `input` block declares the schema's data dependencies. All external data must be accessed via the `input` object (e.g., `input.age`).
42
+
43
+ ### **Declaration Methods**
44
+
45
+ The preferred way to declare fields is with **type-specific methods**, which provide compile-time type checking and runtime validation.
46
+
47
+ * **Primitives**:
48
+ ```ruby
49
+ string :name
50
+ integer :age, domain: 18..65
51
+ float :score, domain: 0.0..100.0
52
+ boolean :is_active
53
+ ```
54
+ * **Collections**:
55
+ ```ruby
56
+ array :tags, elem: { type: :string }
57
+ hash :metadata, key: { type: :string }, val: { type: :any }
58
+ ```
59
+
60
+ ### **Domain Constraints**
61
+
62
+ Attach validation rules directly to input fields using `domain:`. These are checked when data is loaded.
63
+
64
+ * **Range**: `domain: 1..100` or `0.0...1.0` (exclusive end)
65
+ * **Enumeration**: `domain: %w[pending active archived]`
66
+ * **Custom Logic**: `domain: ->(value) { value.even? }`
67
+
68
+ -----
69
+
70
+ ## Traits: Named Logical Predicates
71
+
72
+ A **`trait`** is a named expression that **must evaluate to a boolean**. Traits are the fundamental building blocks of logic, defining reusable, verifiable conditions.
73
+
74
+ ### **Defining & Composing Traits**
75
+
76
+ Traits are defined with a parenthesized expression and composed using the `&` operator. This composition is strictly **conjunctive (logical AND)**, a key constraint that enables Kumi's powerful static analysis.
77
+
78
+ ```ruby
79
+ # Base Traits
80
+ trait :is_adult, (input.age >= 18)
81
+ trait :is_verified, (input.status == "verified")
82
+
83
+ # Composite Trait (is_adult AND is_verified)
84
+ trait :can_proceed, is_adult & is_verified
85
+
86
+ # Mix bare trait names with inline expressions
87
+ trait :is_eligible, is_adult & is_verified & (input.score > 50)
88
+ ```
89
+
90
+ -----
91
+
92
+ ## Values: Computed Fields
93
+
94
+ A **`value`** is a named expression that computes a field of any type.
95
+
96
+ ### **Simple Values**
97
+
98
+ Values can be defined with expressions using `input` fields, functions (`fn`), and references to other values.
99
+
100
+ ```ruby
101
+ value :full_name, fn(:concat, input.first_name, " ", input.last_name)
102
+ value :discounted_price, fn(:multiply, input.base_price, 0.8)
103
+ ```
104
+
105
+ ### **Conditional Values (Cascades)**
106
+
107
+ For conditional logic, a `value` takes a block to create a **cascade**. Cascades select a result based on a series of conditions, which **must reference named `trait`s**. This enforces clarity by separating the *what* (the condition's name) from the *how* (its implementation).
108
+
109
+ ```ruby
110
+ value :access_level do
111
+ # `on` implies AND: user must be :premium AND :verified.
112
+ on :premium, :verified, "Full Access"
113
+
114
+ # `on_any` implies OR: user can be :staff OR :admin.
115
+ on_any :staff, :admin, "Elevated Access"
116
+
117
+ # `on_none` implies NOT (A OR B): user is neither :blocked NOR :suspended.
118
+ on_none :blocked, :suspended, "Limited Access"
119
+
120
+ # `base` is the default if no other conditions match.
121
+ base "No Access"
122
+ end
123
+ ```
124
+
125
+ -----
126
+
127
+ ## The Kumi Pattern: Separating AND vs. OR Logic
128
+
129
+ Kumi intentionally enforces a pattern for handling different types of logic to maximize clarity and analyzability.
130
+
131
+ * **`trait`s and `&` are for AND logic**: Use `trait` composition to build up a set of conditions that must *all* be true. This is your primary tool for defining constraints.
132
+
133
+ * **`value` cascades are for OR logic**: Use `on_any` within a `value` cascade to handle conditions where *any* one of several predicates is sufficient. This is the idiomatic way to express disjunctive logic.
134
+
135
+ This separation forces complex `OR` conditions to be handled within the clear, readable structure of a cascade, rather than being hidden inside a complex `trait` definition.
136
+
137
+ -----
138
+
139
+ ## Best Practices
140
+
141
+ * **Prefer Small, Composable Traits**: Avoid creating large, monolithic traits with many `&` conditions. Instead, define smaller, named traits and compose them.
142
+
143
+ ```ruby
144
+ # AVOID: Hard to read and reuse
145
+ trait :eligible, (input.age >= 18) & (input.status == "active") & (input.score > 50)
146
+
147
+ # PREFER: Clear, reusable, and self-documenting
148
+ trait :is_adult, (input.age >= 18)
149
+ trait :is_active, (input.status == "active")
150
+ trait :has_good_score, (input.score > 50)
151
+ trait :is_eligible, is_adult & is_active & has_good_score
152
+ ```
153
+
154
+ * **Name All Conditions**: If you need to use a condition in a `value` cascade, define it as a `trait` first. This gives the condition a clear business name and makes the cascade easier to read.