hecks 1.0.2 → 1.1.0

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 (239) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hecks/adapters/driven/claude_code.rb +3 -3
  3. data/lib/hecks/adapters/driven/d1.rb +48 -28
  4. data/lib/hecks/adapters/driven/folder.rb +10 -3
  5. data/lib/hecks/adapters/driven/google_authentication.rb +2 -4
  6. data/lib/hecks/adapters/driven/heki/journal.rb +1 -1
  7. data/lib/hecks/adapters/driven/heki.rb +3 -5
  8. data/lib/hecks/adapters/driven/in_memory_ordering.rb +4 -1
  9. data/lib/hecks/adapters/driven/lambda.rb +2 -2
  10. data/lib/hecks/adapters/driven/local_storage.adapter +3 -0
  11. data/lib/hecks/adapters/driven/local_storage.rb +120 -0
  12. data/lib/hecks/adapters/driven/memory.rb +48 -3
  13. data/lib/hecks/adapters/driven/postgres/codec.rb +3 -1
  14. data/lib/hecks/adapters/driven/postgres/outbox.rb +97 -0
  15. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +3 -2
  16. data/lib/hecks/adapters/driven/postgres.rb +25 -14
  17. data/lib/hecks/adapters/driven/prism.rb +11 -0
  18. data/lib/hecks/adapters/driven/sql_query_builder.rb +45 -20
  19. data/lib/hecks/adapters/driven/sqlite/codec.rb +3 -1
  20. data/lib/hecks/adapters/driven/sqlite/projection.rb +17 -1
  21. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +23 -1
  22. data/lib/hecks/adapters/driven/sqlite.rb +87 -8
  23. data/lib/hecks/adapters/driven.rb +4 -0
  24. data/lib/hecks/adapters.rb +4 -0
  25. data/lib/hecks/behaviors/dsl.rb +7 -0
  26. data/lib/hecks/behaviors/expectations.rb +32 -4
  27. data/lib/hecks/behaviors/rspec.rb +2 -0
  28. data/lib/hecks/behaviors/runner.rb +4 -0
  29. data/lib/hecks/bluebook/aggregate.rb +6 -5
  30. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +4 -4
  31. data/lib/hecks/bluebook/assembly/build.rb +6 -3
  32. data/lib/hecks/bluebook/assembly/contract.rb +5 -1
  33. data/lib/hecks/bluebook/assembly/contracts.rb +4 -0
  34. data/lib/hecks/bluebook/assembly/marks.rb +2 -1
  35. data/lib/hecks/bluebook/assembly.rb +1 -3
  36. data/lib/hecks/bluebook/attribute.rb +6 -0
  37. data/lib/hecks/bluebook/behaviour/policy.rb +18 -0
  38. data/lib/hecks/bluebook/behaviour/process_manager.rb +17 -1
  39. data/lib/hecks/bluebook/behaviour/traits.rb +1 -1
  40. data/lib/hecks/bluebook/command.rb +6 -5
  41. data/lib/hecks/bluebook/dsl/adapter_builder.rb +4 -1
  42. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +524 -0
  43. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +19 -472
  44. data/lib/hecks/bluebook/dsl/attribute_collector.rb +14 -6
  45. data/lib/hecks/bluebook/dsl/binding_proxy.rb +5 -0
  46. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +848 -0
  47. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +25 -784
  48. data/lib/hecks/bluebook/dsl/command_builder.rb +53 -13
  49. data/lib/hecks/bluebook/dsl/const_shim.rb +6 -0
  50. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +20 -7
  51. data/lib/hecks/bluebook/dsl/entity_builder.rb +27 -6
  52. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +12 -2
  53. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +8 -1
  54. data/lib/hecks/bluebook/dsl/identity_declaration.rb +24 -12
  55. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +40 -1
  56. data/lib/hecks/bluebook/dsl/policy_builder.rb +10 -1
  57. data/lib/hecks/bluebook/dsl/port_builder.rb +6 -1
  58. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +33 -12
  59. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +56 -12
  60. data/lib/hecks/bluebook/dsl/query_builder.rb +7 -1
  61. data/lib/hecks/bluebook/dsl/read_model_builder.rb +11 -3
  62. data/lib/hecks/bluebook/dsl/rule_reference.rb +3 -1
  63. data/lib/hecks/bluebook/dsl/translation_builder.rb +13 -2
  64. data/lib/hecks/bluebook/dsl/value_object_builder.rb +8 -1
  65. data/lib/hecks/bluebook/dsl/word_gate.rb +5 -0
  66. data/lib/hecks/bluebook/dsl/world_builder.rb +13 -3
  67. data/lib/hecks/bluebook/entity.rb +5 -3
  68. data/lib/hecks/bluebook/expression/ast_json.rb +88 -9
  69. data/lib/hecks/bluebook/expression/ast_reader.rb +107 -0
  70. data/lib/hecks/bluebook/expression/canonical_form.rb +7 -0
  71. data/lib/hecks/bluebook/expression/evaluator.rb +64 -11
  72. data/lib/hecks/bluebook/expression/projection.json +12 -0
  73. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +4 -3
  74. data/lib/hecks/bluebook/expression/resolver.rb +83 -11
  75. data/lib/hecks/bluebook/expression.rb +1 -0
  76. data/lib/hecks/bluebook/hexagon.rb +10 -0
  77. data/lib/hecks/bluebook/lifecycle.rb +9 -0
  78. data/lib/hecks/bluebook/meta_validator/judge.rb +10 -4
  79. data/lib/hecks/bluebook/meta_validator/plan.rb +4 -0
  80. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +1 -1
  81. data/lib/hecks/bluebook/meta_validator/shapes.rb +3 -3
  82. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +7 -4
  83. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +42 -3
  84. data/lib/hecks/bluebook/meta_validator/world_judge.rb +1 -1
  85. data/lib/hecks/bluebook/meta_validator.rb +5 -5
  86. data/lib/hecks/bluebook/model_check.rb +119 -82
  87. data/lib/hecks/bluebook/pattern_subset.rb +9 -0
  88. data/lib/hecks/bluebook/policy.rb +2 -1
  89. data/lib/hecks/bluebook/process_manager.rb +6 -0
  90. data/lib/hecks/bluebook/project_register.rb +45 -25
  91. data/lib/hecks/bluebook/query.rb +4 -0
  92. data/lib/hecks/bluebook/read_model.rb +2 -2
  93. data/lib/hecks/bluebook/smoke_test.rb +5 -0
  94. data/lib/hecks/bluebook/synthesizer.rb +2 -1
  95. data/lib/hecks/bluebook/translation.rb +3 -0
  96. data/lib/hecks/bluebook/value_object.rb +2 -2
  97. data/lib/hecks/codemod.rb +13 -0
  98. data/lib/hecks/doc/reference.rb +17 -11
  99. data/lib/hecks/facade/cli_runner.rb +2 -2
  100. data/lib/hecks/facade/command_request.rb +11 -11
  101. data/lib/hecks/facade/handle.rb +3 -3
  102. data/lib/hecks/facade/surface/aggregate_door.rb +52 -38
  103. data/lib/hecks/facade.rb +4 -0
  104. data/lib/hecks/forms/app.rb +10 -6
  105. data/lib/hecks/forms/command_form_renderer.rb +3 -3
  106. data/lib/hecks/forms/field_renderer.rb +14 -9
  107. data/lib/hecks/forms/field_shape.rb +7 -2
  108. data/lib/hecks/forms/html.rb +5 -2
  109. data/lib/hecks/forms/index_renderer.rb +1 -1
  110. data/lib/hecks/forms/page.rb +7 -3
  111. data/lib/hecks/forms/query_form_renderer.rb +3 -2
  112. data/lib/hecks/forms/record_renderer.rb +22 -16
  113. data/lib/hecks/forms.rb +4 -0
  114. data/lib/hecks/fqn.rb +21 -9
  115. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +30 -8
  116. data/lib/hecks/fuzzing/invalid_value_generator.rb +0 -1
  117. data/lib/hecks/fuzzing/isolated_boot.rb +14 -6
  118. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +264 -0
  119. data/lib/hecks/fuzzing/properties/guards.rb +251 -0
  120. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +351 -0
  121. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +99 -0
  122. data/lib/hecks/fuzzing/properties/querying.rb +206 -0
  123. data/lib/hecks/fuzzing/properties.rb +41 -1065
  124. data/lib/hecks/fuzzing/replay.rb +60 -16
  125. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +6 -2
  126. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +2 -1
  127. data/lib/hecks/fuzzing/sequence_generator.rb +0 -1
  128. data/lib/hecks/fuzzing/value_generator.rb +2 -2
  129. data/lib/hecks/grammar/evolve.rb +34 -10
  130. data/lib/hecks/grammar/expression_operators.json +124 -0
  131. data/lib/hecks/grammar.rb +15 -5
  132. data/lib/hecks/ir.rb +9 -0
  133. data/lib/hecks/language/bluebook/aggregate.bluebook +4 -2
  134. data/lib/hecks/language/bluebook/command.bluebook +2 -1
  135. data/lib/hecks/language/bluebook/entity.bluebook +2 -1
  136. data/lib/hecks/language/bluebook/projection.bluebook +2 -1
  137. data/lib/hecks/language/bluebook/query.bluebook +4 -2
  138. data/lib/hecks/language/bluebook/shape.bluebook +2 -1
  139. data/lib/hecks/language/bluebook/vocabulary.bluebook +2 -0
  140. data/lib/hecks/literal.rb +18 -8
  141. data/lib/hecks/naming.rb +6 -3
  142. data/lib/hecks/ports/access_control.rb +1 -1
  143. data/lib/hecks/ports/agent.rb +5 -3
  144. data/lib/hecks/ports/authentication.rb +3 -2
  145. data/lib/hecks/ports/authorization.rb +1 -1
  146. data/lib/hecks/ports/clock.rb +1 -1
  147. data/lib/hecks/ports/extraction.rb +5 -1
  148. data/lib/hecks/ports/identity_assignment.rb +1 -1
  149. data/lib/hecks/ports/identity_generation.rb +1 -1
  150. data/lib/hecks/ports/identity_resolution.rb +1 -1
  151. data/lib/hecks/ports/loading.rb +4 -1
  152. data/lib/hecks/ports/persistence/append_only.rb +40 -4
  153. data/lib/hecks/ports/persistence/binding_policy.rb +1 -1
  154. data/lib/hecks/ports/persistence/plugin.rb +3 -0
  155. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +3 -1
  156. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +5 -5
  157. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +14 -5
  158. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +4 -0
  159. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +1 -1
  160. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +80 -16
  161. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +23 -6
  162. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +43 -5
  163. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +25 -4
  164. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +49 -9
  165. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +2 -2
  166. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +10 -0
  167. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +49 -38
  168. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +23 -13
  169. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +19 -21
  170. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +27 -9
  171. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +9 -0
  172. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +25 -10
  173. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +2 -6
  174. data/lib/hecks/ports/persistence/plugins/era.rb +3 -1
  175. data/lib/hecks/ports/persistence/remote_runtime.rb +1 -1
  176. data/lib/hecks/ports/persistence.rb +7 -3
  177. data/lib/hecks/ports/projection.rb +13 -5
  178. data/lib/hecks/ports/query/in_memory.rb +8 -2
  179. data/lib/hecks/ports/query.rb +1 -3
  180. data/lib/hecks/ports.rb +2 -0
  181. data/lib/hecks/projections/diagrams.rb +11 -3
  182. data/lib/hecks/projections/ir.rb +2 -0
  183. data/lib/hecks/projections/model/deviations.rb +2 -1
  184. data/lib/hecks/projections/model.rb +15 -10
  185. data/lib/hecks/projections/vocabulary.rb +1 -3
  186. data/lib/hecks/projector/cli_projector.rb +49 -29
  187. data/lib/hecks/projector/docs_projector.rb +21 -12
  188. data/lib/hecks/projector/exporter.rb +10 -1
  189. data/lib/hecks/projector/narrate_projector.rb +83 -25
  190. data/lib/hecks/projector.rb +2 -2
  191. data/lib/hecks/query_ir.rb +60 -36
  192. data/lib/hecks/query_specification/common/authorization_spec.rb +1 -1
  193. data/lib/hecks/query_specification/common/comparators.rb +4 -0
  194. data/lib/hecks/query_specification/common/comparison.rb +8 -1
  195. data/lib/hecks/query_specification/common/dsl.rb +5 -0
  196. data/lib/hecks/query_specification/common/null_policy.rb +6 -2
  197. data/lib/hecks/query_specification/common/options.rb +17 -5
  198. data/lib/hecks/query_specification/field_path.rb +4 -4
  199. data/lib/hecks/query_specification/read_model/specification.rb +5 -2
  200. data/lib/hecks/router/namespace_installer.rb +22 -10
  201. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +57 -22
  202. data/lib/hecks/runtime/command_interpreter.rb +104 -65
  203. data/lib/hecks/runtime/command_rules/admissibility.rb +26 -8
  204. data/lib/hecks/runtime/command_rules/arithmetic.rb +35 -14
  205. data/lib/hecks/runtime/dependency_planning.rb +33 -21
  206. data/lib/hecks/runtime/dispatcher.rb +41 -7
  207. data/lib/hecks/runtime/entity_element.rb +25 -6
  208. data/lib/hecks/runtime/entity_interpreter.rb +28 -15
  209. data/lib/hecks/runtime/errors.rb +28 -2
  210. data/lib/hecks/runtime/instance.rb +9 -3
  211. data/lib/hecks/runtime/interpreting.rb +29 -2
  212. data/lib/hecks/runtime/loader.rb +21 -0
  213. data/lib/hecks/runtime/outbox.rb +365 -0
  214. data/lib/hecks/runtime/policy_interpreter.rb +32 -17
  215. data/lib/hecks/runtime/query_interpreter.rb +36 -16
  216. data/lib/hecks/runtime/reaction_invocation.rb +35 -22
  217. data/lib/hecks/runtime/read_model_interpreter.rb +26 -3
  218. data/lib/hecks/runtime/reference_hop.rb +1 -1
  219. data/lib/hecks/runtime/refusal_wording.rb +55 -49
  220. data/lib/hecks/runtime/registry/saga_persistence.rb +3 -1
  221. data/lib/hecks/runtime/registry/verification.rb +39 -2
  222. data/lib/hecks/runtime/registry.rb +21 -11
  223. data/lib/hecks/runtime/remote_dispatcher.rb +3 -1
  224. data/lib/hecks/runtime/routing.rb +52 -39
  225. data/lib/hecks/runtime/saga_interpreter/correlation.rb +7 -7
  226. data/lib/hecks/runtime/saga_interpreter.rb +119 -90
  227. data/lib/hecks/runtime/saga_pending_dispatch.rb +6 -3
  228. data/lib/hecks/runtime/tenant_scope.rb +7 -0
  229. data/lib/hecks/runtime/value/coercion.rb +207 -48
  230. data/lib/hecks/runtime/value.rb +1 -1
  231. data/lib/hecks/runtime.rb +7 -2
  232. data/lib/hecks/storehouse.rb +15 -8
  233. data/lib/hecks/version.rb +1 -1
  234. data/lib/hecks/vocabulary.rb +1 -0
  235. data/lib/hecks.rb +17 -13
  236. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +1 -1
  237. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +1 -1
  238. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +1 -1
  239. metadata +14 -2
@@ -4,6 +4,13 @@ require_relative "../../vocabulary"
4
4
  module Hecks
5
5
  module Bluebook
6
6
  module Expression
7
+ # The boolean/comparison layer of a predicate's expression grammar —
8
+ # parses a canonical predicate string into an Or/And/Not/Compare/
9
+ # Include/Resolve AST (cached per distinct string, `ast_cache`) and
10
+ # interprets it against a call's own state/attrs. Leaf `Resolve`
11
+ # nodes delegate to `Resolver` for the dotted/arithmetic
12
+ # sub-grammar; `OPERATORS` is the comparison table projected from
13
+ # the grammar chapter (`PROJECTION`).
7
14
  module Evaluator
8
15
  Operator = Struct.new(:symbol, :compares_less_than, :compares_equal, :negated, keyword_init: true)
9
16
 
@@ -58,6 +65,54 @@ module Hecks
58
65
  interpret(ast_cache[expr] ||= parse(expr), state, attrs)
59
66
  end
60
67
 
68
+ # THE RULE-SHAPED ENTRY — evaluates a Given/Invariant (anything
69
+ # answering `canonical` and `ast`) by walking its STRUCTURED form,
70
+ # never re-parsing the text: the one parse happened at DSL-build
71
+ # time behind `AstJson`, and `AstReader` turns that tree back into
72
+ # the same nodes `parse` would have built (the equivalence is
73
+ # pinned by spec/expression_ast_spec.rb over the bounded-
74
+ # exhaustive generator). A rule with no `ast` (an Assembly-
75
+ # rebuilt one, or a placeholder resolved outside build_rule)
76
+ # falls back to parsing its canonical — same cache, same key.
77
+ #
78
+ # HECKS_EVAL=string reverts to the text path wholesale, kept for
79
+ # one release as the escape hatch while the ast path beds in.
80
+ def call_rule(rule, state, attrs = {})
81
+ return call(rule.canonical, state, attrs) if ENV["HECKS_EVAL"] == "string"
82
+
83
+ interpret(ast_cache[rule.canonical] ||= nodes_for(rule), state, attrs)
84
+ end
85
+
86
+ def nodes_for(rule)
87
+ rule.ast ? AstReader.read_predicate(rule.ast) : parse(rule.canonical)
88
+ end
89
+
90
+ # A refused `given`/`ensures`/`invariant` names its own DESCRIPTION
91
+ # ("not already superseded") but, on its own, not what the block
92
+ # actually evaluated to — the difference between "the rule is right
93
+ # and my data is wrong" and "the rule is subtly wrong" is often just
94
+ # seeing the two operands. Scoped to the single shape that has one
95
+ # honest answer: `expr`'s own TOP-LEVEL node is a bare `Compare` —
96
+ # not `Or`/`And`/`Not` (which of several sub-comparisons would even
97
+ # be "the" one at fault is genuinely ambiguous), `Include` (no
98
+ # left/right to show), or `Resolve` (a bare boolean read, nothing to
99
+ # compare against). Values are rendered with `Rendering.describe`,
100
+ # the same house style every other refusal already prints a value
101
+ # through. Returns `nil` — not raised — on anything else, INCLUDING
102
+ # an operand that itself fails to resolve (`EvaluationError`): a
103
+ # missing diagnostic is a worse debugging experience than none, a
104
+ # crash while building one is worse still.
105
+ def comparison_detail(expr, state, attrs = {})
106
+ node = ast_cache[expr] ||= parse(expr)
107
+ return nil unless node.is_a?(Compare)
108
+
109
+ lhs = Resolver.interpret(node.left, state, attrs)
110
+ rhs = Resolver.interpret(node.right, state, attrs)
111
+ "left: #{Resolver.describe(lhs)}, right: #{Resolver.describe(rhs)}"
112
+ rescue EvaluationError
113
+ nil
114
+ end
115
+
61
116
  def parse(expr)
62
117
  expr = strip_parens(expr.to_s.strip)
63
118
 
@@ -117,21 +172,21 @@ module Hecks
117
172
  end
118
173
  end
119
174
 
120
- def compare(op, left, right, state, attrs)
175
+ def compare(comparator, left, right, state, attrs)
121
176
  lhs = Resolver.interpret(left, state, attrs)
122
177
  rhs = Resolver.interpret(right, state, attrs)
123
178
 
124
- apply(op, lhs, rhs)
179
+ apply(comparator, lhs, rhs)
125
180
  end
126
181
 
127
182
  # The algebra itself, on values already resolved — split out so a sign
128
183
  # test (SignTest#compares_via names an Operator symbol) can apply the
129
184
  # SAME primitives compare() uses against the literal 0, rather than
130
185
  # re-deriving positive?/negative?/zero? by hand a second time.
131
- def apply(op, lhs, rhs)
132
- result = (op.compares_less_than && less_than(lhs, rhs)) ||
133
- (op.compares_equal && equal?(lhs, rhs))
134
- op.negated ? !result : result
186
+ def apply(comparator, lhs, rhs)
187
+ result = (comparator.compares_less_than && less_than(lhs, rhs)) ||
188
+ (comparator.compares_equal && equal?(lhs, rhs))
189
+ comparator.negated ? !result : result
135
190
  end
136
191
 
137
192
  def less_than(lhs, rhs)
@@ -198,9 +253,7 @@ module Hecks
198
253
  case (found = Resolver.interpret(haystack, state, attrs))
199
254
  when Array then found.any? { |item| equal?(item, wanted) }
200
255
  when String
201
- unless wanted.is_a?(String)
202
- raise EvaluationError, "no implicit conversion of #{class_of(wanted)} into String"
203
- end
256
+ raise EvaluationError, "no implicit conversion of #{class_of(wanted)} into String" unless wanted.is_a?(String)
204
257
 
205
258
  found.include?(wanted)
206
259
  else false
@@ -286,9 +339,9 @@ module Hecks
286
339
  # its own (`[0, 0 + 0]`) used to read as a split point for
287
340
  # THIS expression's own boolean/comparison grammar, exactly
288
341
  # the way an un-tracked `{`/`}` once did for block predicates.
289
- elsif char == "(" || char == "{" || char == "["
342
+ elsif ["(", "{", "["].include?(char)
290
343
  depth += 1
291
- elsif char == ")" || char == "}" || char == "]"
344
+ elsif [")", "}", "]"].include?(char)
292
345
  depth -= 1
293
346
  elsif depth.zero? && expr[index, operator.length] == operator
294
347
  return index if !block_given? || yield(index)
@@ -197,6 +197,18 @@
197
197
  "category": "positional",
198
198
  "precedence": 7,
199
199
  "arity": 1
200
+ },
201
+ {
202
+ "symbol": ".set?",
203
+ "category": "presence",
204
+ "precedence": 7,
205
+ "arity": 1
206
+ },
207
+ {
208
+ "symbol": ".unset?",
209
+ "category": "presence",
210
+ "precedence": 7,
211
+ "arity": 1
200
212
  }
201
213
  ],
202
214
  "normalisations": [
@@ -12,6 +12,9 @@
12
12
  module Hecks
13
13
  module Bluebook
14
14
  module Expression
15
+ # Reopens the SAME `Resolver` module resolver.rb defines — see this
16
+ # file's own header comment above for why the block-predicate/find
17
+ # family lives here rather than in resolver.rb itself.
15
18
  module Resolver
16
19
  # `receiver.all? { |x| PREDICATE }` / `.any? { ... }` / `.none? {
17
20
  # ... }` -- vendored addition, not (yet) upstream hecks
@@ -184,9 +187,7 @@ module Hecks
184
187
  # just `attrs` extended with the bound name for the span of that
185
188
  # one predicate evaluation, discarded immediately after.
186
189
  def evaluate_block_predicate(node, collection, state, attrs)
187
- unless collection.is_a?(Array)
188
- raise EvaluationError, "#{node.mode}? expects a list, got #{describe(collection)}"
189
- end
190
+ raise EvaluationError, "#{node.mode}? expects a list, got #{describe(collection)}" unless collection.is_a?(Array)
190
191
 
191
192
  outcomes = collection.map { |element| interpret_with_element(node, element, state, attrs) }
192
193
 
@@ -8,6 +8,14 @@ module Hecks
8
8
  module Expression
9
9
  class EvaluationError < StandardError; end
10
10
 
11
+ # The dotted/arithmetic leaf grammar a predicate's `Resolve` node
12
+ # bottoms out in — attribute lookups, literals, arithmetic, and the
13
+ # growing set of vendored method-call suffixes (`.match?`,
14
+ # `.present?`/`.blank?`, `.set?`/`.unset?`, `.split`, `.first`/
15
+ # `.last`, `.all?`/`.any?`/`.none?`/`.find`, ...) this file and its
16
+ # `resolver/block_predicates.rb` sibling parse and interpret. Each
17
+ # leaf's `parse` is a pure function of its own string, cached at the
18
+ # `Evaluator` layer; `interpret` reads state/attrs fresh each call.
11
19
  module Resolver
12
20
  SIGN_TESTS = Hecks::Vocabulary.fetch("SignTest")
13
21
 
@@ -109,6 +117,24 @@ module Hecks
109
117
  # field that could legitimately be "" rather than absent.
110
118
  Presence = Struct.new(:receiver, :negated, keyword_init: true)
111
119
 
120
+ # `.set?`/`.unset?` -- sibling of `.present?`/`.blank?` immediately
121
+ # above, added for a DELIBERATELY narrower question, spelled out in
122
+ # the name so choosing between the two pairs is a choice, not a
123
+ # trap: `.present?` means "not EMPTY" (Rails-standard -- nil/false,
124
+ # and an EMPTY String/Array/Hash, are blank; nothing else is),
125
+ # which quietly answers "was this ever assigned" and "does the
126
+ # assigned value happen to be empty" as the SAME question. For an
127
+ # optional field whose only legitimate unset state IS nil, that
128
+ # conflation is a real trap -- confirmed live: a corpus author
129
+ # reaching for `superseded_by.blank?` to guard an optional
130
+ # reference burned real time before landing on "just don't guard
131
+ # it," because `.blank?` cannot be asked to mean ONLY "was this
132
+ # never assigned." `.set?`/`.unset?` ask exactly that, and nothing
133
+ # else: `!receiver.nil?`, full stop -- an assigned-but-empty
134
+ # String/Array/Hash (or a VO wrapping one) is `.set?`, not
135
+ # `.unset?`, unlike `.blank?`'s own reading of the identical value.
136
+ Assignment = Struct.new(:receiver, :negated, keyword_init: true)
137
+
112
138
  # `receiver.split("SEP")` -- vendored addition, not (yet) upstream
113
139
  # hecks (migration plan task 9): confirmed the single
114
140
  # highest-value new finding of the real-dispatch smoke sweep --
@@ -186,6 +212,18 @@ module Hecks
186
212
  interpret(parse(expr), state, attrs)
187
213
  end
188
214
 
215
+ # A grammar's own dispatch table — one `return Node.new(...) if
216
+ # expr =~ /pattern/` per leaf production, in a FIXED precedence
217
+ # order (`.length` before the generic suffixes, sign tests before
218
+ # the rest, etc. — see this file's own history comments on why
219
+ # several of these were added in exactly this position). Each
220
+ # branch is already one line; splitting the table into smaller
221
+ # methods would not shrink any single check, only hide the
222
+ # precedence order this method's own line-by-line sequence IS.
223
+ # rubocop:disable-next Metrics/AbcSize
224
+ # rubocop:disable-next Metrics/CyclomaticComplexity
225
+ # rubocop:disable-next Metrics/MethodLength
226
+ # rubocop:disable-next Metrics/PerceivedComplexity
189
227
  def parse(expr)
190
228
  expr = expr.to_s.strip
191
229
 
@@ -215,7 +253,7 @@ module Hecks
215
253
 
216
254
  return Size.new(receiver: parse(Regexp.last_match(1))) if expr =~ /\A(.+)\.size\z/
217
255
 
218
- if expr =~ /\A(.+)\.match\?\(\/(.*)\/([a-z]*)\)\z/m
256
+ if expr =~ %r{\A(.+)\.match\?\(/(.*)/([a-z]*)\)\z}m
219
257
  return MatchesRegex.new(receiver: parse(Regexp.last_match(1)),
220
258
  pattern: Regexp.last_match(2),
221
259
  flags: Regexp.last_match(3))
@@ -224,19 +262,25 @@ module Hecks
224
262
  return Presence.new(receiver: parse(Regexp.last_match(1)), negated: false) if expr =~ /\A(.+)\.present\?\z/
225
263
  return Presence.new(receiver: parse(Regexp.last_match(1)), negated: true) if expr =~ /\A(.+)\.blank\?\z/
226
264
 
265
+ return Assignment.new(receiver: parse(Regexp.last_match(1)), negated: false) if expr =~ /\A(.+)\.set\?\z/
266
+ return Assignment.new(receiver: parse(Regexp.last_match(1)), negated: true) if expr =~ /\A(.+)\.unset\?\z/
267
+
227
268
  if expr =~ /\A(.+)\.split\("([^"]*)"\)\z/
228
- return Split.new(receiver: parse(Regexp.last_match(1)), separator: Regexp.last_match(2))
269
+ return Split.new(receiver: parse(Regexp.last_match(1)),
270
+ separator: Regexp.last_match(2))
229
271
  end
230
272
 
231
273
  return First.new(receiver: parse(Regexp.last_match(1))) if expr =~ /\A(.+)\.first\z/
232
274
  return Last.new(receiver: parse(Regexp.last_match(1))) if expr =~ /\A(.+)\.last\z/
233
275
 
234
276
  if expr =~ /\A(.+)\.start_with\?\("([^"]*)"\)\z/
235
- return StartsWith.new(receiver: parse(Regexp.last_match(1)), substring: Regexp.last_match(2))
277
+ return StartsWith.new(receiver: parse(Regexp.last_match(1)),
278
+ substring: Regexp.last_match(2))
236
279
  end
237
280
 
238
281
  if expr =~ /\A(.+)\.end_with\?\("([^"]*)"\)\z/
239
- return EndsWith.new(receiver: parse(Regexp.last_match(1)), substring: Regexp.last_match(2))
282
+ return EndsWith.new(receiver: parse(Regexp.last_match(1)),
283
+ substring: Regexp.last_match(2))
240
284
  end
241
285
 
242
286
  block_opener = parse_block_opener(expr)
@@ -252,6 +296,16 @@ module Hecks
252
296
  SignTest.new(operator: operator, test: test, receiver: parse(receiver))
253
297
  end
254
298
 
299
+ # `parse`'s own dispatch table, mirrored: one `when` per leaf node
300
+ # type it can produce, each already delegating to a small named
301
+ # helper (add, apply_sign_test, emptiness_of, ...) — the case
302
+ # itself IS the closed, declared set this method exists to
303
+ # exhaust; the `else` backstop's own comment explains why a
304
+ # missing arm is a bug this method is built to make loud, not
305
+ # quiet.
306
+ # rubocop:disable-next Metrics/AbcSize
307
+ # rubocop:disable-next Metrics/CyclomaticComplexity
308
+ # rubocop:disable-next Metrics/MethodLength
255
309
  def interpret(node, state, attrs)
256
310
  case node
257
311
  when IntegerLiteral, FloatLiteral, StringLiteral, BoolLiteral then node.value
@@ -274,6 +328,9 @@ module Hecks
274
328
  when Presence
275
329
  present = !blank?(interpret(node.receiver, state, attrs))
276
330
  node.negated ? !present : present
331
+ when Assignment
332
+ set = !interpret(node.receiver, state, attrs).nil?
333
+ node.negated ? !set : set
277
334
  when Split
278
335
  split_value(interpret(node.receiver, state, attrs), node.separator)
279
336
  when Last
@@ -339,8 +396,7 @@ module Hecks
339
396
  # found live while building this, not assumed).
340
397
  def matches_regex?(receiver_value, pattern, flags)
341
398
  text = case receiver_value
342
- when String, Symbol then receiver_value.to_s
343
- when Integer, Float then receiver_value.to_s
399
+ when String, Symbol, Integer, Float then receiver_value.to_s
344
400
  when NilClass then ""
345
401
  else
346
402
  raise EvaluationError, "match? expects a scalar, got #{receiver_value.class}"
@@ -442,8 +498,25 @@ module Hecks
442
498
  nil
443
499
  end
444
500
 
501
+ # Integer is signed 64-bit (C3.3, docs/semantics/bluebook-semantics.md)
502
+ # — Ruby's own Integer would promote past ±2^63-1 silently; the
503
+ # language does not, so a sum outside that range is an evaluation
504
+ # fault, worded exactly as the Rust kernel's `checked_add` words
505
+ # it. A Float sum that is not finite is the same fault (C3.4).
506
+ INT64_RANGE = (-(2**63))..((2**63) - 1)
507
+
445
508
  def add(left, right)
446
- require_number(left, "addition") + require_number(right, "addition")
509
+ lhs = require_number(left, "addition")
510
+ rhs = require_number(right, "addition")
511
+ sum = lhs + rhs
512
+ if sum.is_a?(Integer)
513
+ return sum if INT64_RANGE.cover?(sum)
514
+
515
+ raise EvaluationError, "addition overflowed: #{lhs} + #{rhs} does not fit in a 64-bit integer"
516
+ end
517
+ return sum if sum.finite?
518
+
519
+ raise EvaluationError, "addition overflowed: #{lhs} + #{rhs} is not a finite number"
447
520
  end
448
521
 
449
522
  def quoted?(expr)
@@ -533,10 +606,9 @@ module Hecks
533
606
 
534
607
  def string_of(value)
535
608
  case value
536
- when String then value
537
- when Integer, Float then value.to_s
538
- when TrueClass, FalseClass then value.to_s
539
- when NilClass then ""
609
+ when String then value
610
+ when Integer, Float, TrueClass, FalseClass then value.to_s
611
+ when NilClass then ""
540
612
  else
541
613
  raise EvaluationError, "to_s expects a scalar, got #{describe(value)}"
542
614
  end
@@ -11,4 +11,5 @@ end
11
11
  require_relative "expression/canonical_form"
12
12
  require_relative "expression/resolver"
13
13
  require_relative "expression/evaluator"
14
+ require_relative "expression/ast_reader"
14
15
  require_relative "expression/ast_json"
@@ -18,6 +18,11 @@ module Hecks
18
18
  def aggregate_name = Naming.demodulise(aggregate)
19
19
  end
20
20
 
21
+ # The built form of a `.hecksagon` file, produced by
22
+ # `DSL::HecksagonBuilder` — a domain's own binds (`Bind`, above),
23
+ # subscriptions, and attached framework/vendored members.
24
+ # `Behaviour::Hecksagon` supplies the bind lookups (`bind_for`/
25
+ # `binds_for`); this class holds only the declared data.
21
26
  class Hecksagon
22
27
  include Hecks::IR
23
28
  include Behaviour::Hecksagon
@@ -41,6 +46,11 @@ module Hecks
41
46
  end
42
47
  end
43
48
 
49
+ # The built form of a `.world` file, produced by `DSL::WorldBuilder` —
50
+ # a domain's own `realm`/`latest` version markers and its adapter bind
51
+ # SETTINGS (as opposed to `Hecksagon`'s own bind LIST, above).
52
+ # `Behaviour::World` supplies the settings lookups (`for_verb`/
53
+ # `for_binding`); this class holds only the declared data.
44
54
  class World
45
55
  include Hecks::IR
46
56
  include Behaviour::World
@@ -2,6 +2,10 @@ require_relative "behaviour/lifecycle"
2
2
 
3
3
  module Hecks
4
4
  module Bluebook
5
+ # One declared `transition` row — the state a command moves an
6
+ # aggregate or entity TO, and, when guarded, the state(s) it must
7
+ # currently be in (`from:`) for the transition to apply. An unguarded
8
+ # transition (`from: nil`) applies from any current state.
5
9
  class StateTransition
6
10
  attr_reader :target, :from
7
11
 
@@ -17,6 +21,11 @@ module Hecks
17
21
  def constrained? = !@from.nil?
18
22
  end
19
23
 
24
+ # The built form of a `lifecycle :field, default: ... do ... end`
25
+ # block, produced by `DSL::LifecycleBuilder` — the field a state
26
+ # machine lives on, its starting value, and the declared transition
27
+ # table. `Behaviour::Lifecycle` supplies the reads (`states`,
28
+ # `target_for`, ...) built purely from this data.
20
29
  class Lifecycle
21
30
  include Hecks::IR
22
31
  include Behaviour::Lifecycle
@@ -99,7 +99,7 @@ module Hecks
99
99
  value
100
100
  end
101
101
 
102
- def args(pairs) = pairs.reject { |_, value| value.nil? }
102
+ def args(pairs) = pairs.compact
103
103
 
104
104
  def offer(label)
105
105
  yield
@@ -352,7 +352,10 @@ module Hecks
352
352
  # meta store happened to iterate in insertion order — an accident that an
353
353
  # ask ordered any other way would have taken away, and Reconstruction is
354
354
  # the one reader that must have the SOURCE'S order rather than a stable one.
355
- POSITION = "position"
355
+ # `private` above has no effect on a constant; kept here anyway,
356
+ # beside the method that reads it, for the narrative.
357
+ # rubocop:disable-next Lint/UselessConstantScoping
358
+ POSITION = "position".freeze
356
359
 
357
360
  def declare(plan, category, node, id, parent_id, index, extra = {})
358
361
  return unless plan.declare
@@ -425,7 +428,7 @@ module Hecks
425
428
  category == "Entity" ? parent_id : id
426
429
  end
427
430
 
428
- def sealers(plan, category, receiver)
431
+ def sealers(plan, _category, receiver)
429
432
  id = receiver[:entities].last || receiver[:aggregate]
430
433
  plan.sealers.each { |verb| send_to("Bluebook::#{verb_for(plan, verb)}", id, to: receiver) }
431
434
  end
@@ -586,7 +589,10 @@ module Hecks
586
589
  # `field_value` ; it is read the same way `plan.parent_key` already is,
587
590
  # because it IS that fact, spelled for the walk's immediate parent
588
591
  # rather than for one specific kind of one.
589
- OWNER = "owner_id"
592
+ # `private` above has no effect on a constant; kept here anyway,
593
+ # beside the method that reads it, for the narrative.
594
+ # rubocop:disable-next Lint/UselessConstantScoping
595
+ OWNER = "owner_id".freeze
590
596
 
591
597
  def identity_part(plan, path, parent_id, node, index, category)
592
598
  head = path.to_s.split(".").first
@@ -35,6 +35,10 @@ module Hecks
35
35
  # nothing — that is the judge's job.
36
36
  class Plan
37
37
  # One append command: the verb, and the value-object-field -> argument map.
38
+ # `:map` shadows Enumerable#map on purpose — `spec/plan_spec.rb`
39
+ # itself asserts `appends["emits"].map` reads the field (a Hash),
40
+ # not an Enumerable#map result. Verified before disabling this cop.
41
+ # rubocop:disable-next Lint/StructNewOverride
38
42
  Append = Struct.new(:verb, :map, keyword_init: true)
39
43
 
40
44
  # One setting command. `targets` is target -> argument ; Lifecycle sets two
@@ -148,7 +148,7 @@ module Hecks
148
148
  # against, so the entity ones have to be told apart by `entity_id`. Rejecting
149
149
  # from an ORDERED read keeps the order.
150
150
  def own(category, aggregate_id)
151
- declared(category, aggregate_id).reject { |row| text(row[:entity_id]).to_s != "" }
151
+ declared(category, aggregate_id).select { |row| text(row[:entity_id]).to_s == "" }
152
152
  end
153
153
 
154
154
  # A piece's own verbs and asks. There is no `DeclaredIn` keyed by entity, so
@@ -238,12 +238,12 @@ module Hecks
238
238
  # Mutation's own `to_h` lambda does, rather than leaving the key
239
239
  # silently absent (spec/round_trip_spec's whole point: a field the
240
240
  # language does not hold is a named gap, not a byte-for-byte one).
241
- def mutation(target, op, bindings)
242
- base = { target: target.to_sym, op: op.to_sym, sign: Hecks::Bluebook::Mutation.sign_for(op) }
241
+ def mutation(target, oper, bindings)
242
+ base = { target: target.to_sym, op: oper.to_sym, sign: Hecks::Bluebook::Mutation.sign_for(oper) }
243
243
  # `:delegate`/`:corrects` (CommandBuilder#delegates_to's and
244
244
  # #corrects_impl's own comments) ride the SAME multi-binding
245
245
  # shape `:append` does.
246
- return base.merge(fields: appended(bindings)) if ["append", "delegate", "corrects"].include?(op)
246
+ return base.merge(fields: appended(bindings)) if ["append", "delegate", "corrects"].include?(oper)
247
247
 
248
248
  base.merge(source: classified(bindings.first))
249
249
  end
@@ -85,7 +85,9 @@ module Hecks
85
85
 
86
86
  def same_chapters?(cached, current)
87
87
  cached.size == current.size &&
88
- cached.zip(current).all? { |(cached_name, cached_chapter), (name, chapter)| cached_name == name && cached_chapter.equal?(chapter) }
88
+ cached.zip(current).all? do |(cached_name, cached_chapter), (name, chapter)|
89
+ cached_name == name && cached_chapter.equal?(chapter)
90
+ end
89
91
  end
90
92
 
91
93
  def boot
@@ -140,7 +142,8 @@ module Hecks
140
142
  def declare_syntax(runtime, bluebook)
141
143
  syntax = bluebook.aggregate("Syntax")
142
144
  runtime.dispatch("Bluebook::Bluebook.Declare", name: v(bluebook.hecks_name),
143
- vision: v("the language's own grammar table, dispatched into itself"),
145
+ vision: v("the language's own grammar table, " \
146
+ "dispatched into itself"),
144
147
  classification: v("core"))
145
148
  runtime.dispatch("Bluebook::Syntax.Declare", bluebook: bluebook.hecks_name, name: v(syntax.hecks_name))
146
149
  end
@@ -239,8 +242,8 @@ module Hecks
239
242
  end
240
243
 
241
244
  def stringify(row)
242
- row.to_h.each_with_object({}) do |(key, cell), out|
243
- out[key] = scalar(cell).to_s
245
+ row.to_h.transform_values do |cell|
246
+ scalar(cell).to_s
244
247
  end
245
248
  end
246
249
 
@@ -82,8 +82,26 @@ module Hecks
82
82
  Array(t.aggregates).each { |aggregate| judge_aggregate(t, aggregate) }
83
83
  end
84
84
 
85
+ # One Declare, then one AddX pass per rule collection the aggregate
86
+ # carries — each pass is independent of the others (they mutate
87
+ # different fields), so each is its own private method below; only
88
+ # Declare-before-any-Add is order-sensitive, and that order is
89
+ # preserved here.
85
90
  def judge_aggregate(translation, aggregate)
86
91
  name = aggregate.name
92
+ declare_aggregate(translation, aggregate, name)
93
+
94
+ judge_renames(aggregate, name)
95
+ judge_moves(aggregate, name)
96
+ judge_converts(aggregate, name)
97
+ judge_drops(aggregate, name)
98
+ judge_retypes(aggregate, name)
99
+ judge_computes(aggregate, name)
100
+ judge_rekeys(aggregate, name)
101
+ judge_backfills(aggregate, name)
102
+ end
103
+
104
+ def declare_aggregate(translation, aggregate, name)
87
105
  parent = {
88
106
  domain: v(translation.domain),
89
107
  from: v(translation.from),
@@ -91,45 +109,66 @@ module Hecks
91
109
  }
92
110
  send_to("Translation::TranslationAggregate.Declare", name,
93
111
  with: { translation_ref: parent, name: v(name), was: v(aggregate.was) })
112
+ end
94
113
 
114
+ def judge_renames(aggregate, name)
95
115
  Hash(aggregate.renames).each do |from, to|
96
116
  send_to("Translation::TranslationAggregate.AddRename", name, to: name,
97
117
  with: { from: v(from), to: v(to) })
98
118
  end
119
+ end
99
120
 
121
+ def judge_moves(aggregate, name)
100
122
  Array(aggregate.moves).each do |move|
101
123
  send_to("Translation::TranslationAggregate.AddMove", name, to: name,
102
124
  with: { from: v(move.from), to: v(move.to) })
103
125
  end
126
+ end
104
127
 
128
+ def judge_converts(aggregate, name)
105
129
  Array(aggregate.converts).each do |convert|
106
130
  send_to("Translation::TranslationAggregate.AddConvert", name, to: name,
107
- with: { from: v(convert.from), to: v(convert.to), values: v(convert.values.inspect) })
131
+ with: { from: v(convert.from),
132
+ to: v(convert.to),
133
+ values: v(convert.values.inspect) })
108
134
  end
135
+ end
109
136
 
137
+ def judge_drops(aggregate, name)
110
138
  Array(aggregate.drops).each do |dropped|
111
139
  send_to("Translation::TranslationAggregate.AddDrop", name, to: name,
112
140
  with: { value: v(dropped) })
113
141
  end
142
+ end
114
143
 
144
+ def judge_retypes(aggregate, name)
115
145
  Array(aggregate.retypes).each do |retype|
116
146
  send_to("Translation::TranslationAggregate.AddRetype", name, to: name,
117
147
  with: { from: v(retype.from), to: v(retype.to) })
118
148
  end
149
+ end
119
150
 
151
+ def judge_computes(aggregate, name)
120
152
  Array(aggregate.computes).each do |compute|
121
153
  send_to("Translation::TranslationAggregate.AddCompute", name, to: name,
122
- with: { from: v(compute.from), to: v(compute.to), sql: v(compute.sql) })
154
+ with: { from: v(compute.from),
155
+ to: v(compute.to),
156
+ sql: v(compute.sql) })
123
157
  end
158
+ end
124
159
 
160
+ def judge_rekeys(aggregate, name)
125
161
  Array(aggregate.rekeys).each do |rekey|
126
162
  send_to("Translation::TranslationAggregate.AddRekey", name, to: name,
127
163
  with: { sql: v(rekey.sql) })
128
164
  end
165
+ end
129
166
 
167
+ def judge_backfills(aggregate, name)
130
168
  Array(aggregate.backfills).each do |backfill|
131
169
  send_to("Translation::TranslationAggregate.AddBackfill", name, to: name,
132
- with: { name: v(backfill.name), default: v(backfill.default.inspect) })
170
+ with: { name: v(backfill.name),
171
+ default: v(backfill.default.inspect) })
133
172
  end
134
173
  end
135
174
  end
@@ -21,7 +21,7 @@ module Hecks
21
21
 
22
22
  def v(text) = text.nil? ? nil : { value: text.to_s }
23
23
 
24
- def args(pairs) = pairs.reject { |_, value| value.nil? }
24
+ def args(pairs) = pairs.compact
25
25
 
26
26
  def offer(label)
27
27
  yield
@@ -52,11 +52,11 @@ module Hecks
52
52
  # concept file requires no second catalog here; deterministic filename
53
53
  # order is the source order exported to IR.
54
54
  GRAMMAR_DIR = File.expand_path("../language/bluebook", __dir__).freeze
55
- GRAMMAR_FILES = Dir.glob(File.join(GRAMMAR_DIR, "*.bluebook")).sort.freeze
55
+ GRAMMAR_FILES = Dir.glob(File.join(GRAMMAR_DIR, "*.bluebook")).freeze
56
56
  # Sibling artifact languages use the same folder-is-the-chapter rule.
57
57
  # Their arrays are discovered, sorted source sets—not filename catalogs.
58
- WORLD_GRAMMAR = Dir.glob(File.expand_path("../language/world/*.bluebook", __dir__)).sort.freeze
59
- HECKSAGON_GRAMMAR = Dir.glob(File.expand_path("../language/hecksagon/*.bluebook", __dir__)).sort.freeze
58
+ WORLD_GRAMMAR = Dir.glob(File.expand_path("../language/world/*.bluebook", __dir__)).freeze
59
+ HECKSAGON_GRAMMAR = Dir.glob(File.expand_path("../language/hecksagon/*.bluebook", __dir__)).freeze
60
60
  # so is a port — whole-project table-unification survey, item #13's
61
61
  # remaining builders. Backs the new PortJudge door the same way
62
62
  # WORLD_GRAMMAR backs WorldJudge.
@@ -65,7 +65,7 @@ module Hecks
65
65
  ADAPTER_GRAMMAR = File.expand_path("../language/adapter.bluebook", __dir__).freeze
66
66
  # so is a translation — same reasoning, one file over. Backs
67
67
  # TranslationJudge.
68
- TRANSLATION_GRAMMAR = Dir.glob(File.expand_path("../language/translation/*.bluebook", __dir__)).sort.freeze
68
+ TRANSLATION_GRAMMAR = Dir.glob(File.expand_path("../language/translation/*.bluebook", __dir__)).freeze
69
69
 
70
70
  # ADR 0026's OWN SEAM: THE CORE DOES NOT NAME ITS EXTENSION POINTS.
71
71
  #
@@ -483,7 +483,7 @@ module Hecks
483
483
  # is added there.
484
484
  def self.load_attached_grammar_into(registry)
485
485
  Hecks.with_registry(registry) do
486
- Dir.glob(File.join(ATTACHED_GRAMMAR_DIR, "*.bluebook")).sort.each { |file| Kernel.load(file) }
486
+ Dir.glob(File.join(ATTACHED_GRAMMAR_DIR, "*.bluebook")).each { |file| Kernel.load(file) }
487
487
  end
488
488
  end
489
489