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
@@ -49,8 +49,21 @@ module Hecks
49
49
  # recursively via `build`) with `scalar` as what's left once
50
50
  # neither of those applies (the raw value, passed through
51
51
  # unchanged).
52
- def for_attribute(aggregate, attribute, value)
53
- return value if attribute.nil? || value.nil? # :optional
52
+ # `boundary: false` is the QUERY door (`QueryInterpreter#normalize_args`):
53
+ # a query attribute's declared type names the argument for callers and
54
+ # generators, never a runtime shape — comparison unwraps both sides
55
+ # itself, so a `reference: {value: ...}` offered against a `String`
56
+ # query field is the documented allowance (see banking's own
57
+ # `Account.OpenForCustomer`), not a C3.8 mismatch.
58
+ # `argument: true` is the CALLER'S door only (`Interpreting#normalize_
59
+ # args`, every command/entity/port dispatch): the one place a nil
60
+ # for a non-optional attribute is the caller leaving a required
61
+ # argument empty (C3.7). Every other caller — `sets` copying an
62
+ # optional argument into state, hydration, entity elements,
63
+ # identity, defaults — is state assembly, where nil is a legitimate
64
+ # "absent is not empty" value the aggregate's own attribute may hold.
65
+ def for_attribute(aggregate, attribute, value, boundary: true, argument: false)
66
+ return nil_or_missing(aggregate, attribute, value, argument) if attribute.nil? || value.nil?
54
67
  return reference_list(attribute, value) if attribute.list? && attribute.reference?
55
68
  return reference_identity(attribute, value) if attribute.reference?
56
69
  return hydrate_entity_list(aggregate, attribute, value) if attribute.list? # :list
@@ -64,21 +77,65 @@ module Hecks
64
77
  # AFTER coercion, not before: a scalar arrives wrapped in whatever holder
65
78
  # its type names (`{value: "append"}` for an OpName), and checking the
66
79
  # raw payload would be checking the envelope.
67
- coerced = value_object_for(aggregate, attribute.type)
68
- .then do |value_object|
69
- if value.is_a?(self) && value.type_name == value_object&.hecks_name
70
- value
71
- elsif value_object
72
- build(value_object, fields_for(value_object, attribute.name, value), aggregate)
73
- else
74
- value
75
- end
80
+ value_object = value_object_for(aggregate, attribute.type)
81
+ return bare_primitive(aggregate, attribute, value, boundary) if value_object.nil?
82
+
83
+ coerced = if value.is_a?(self) && value.type_name == value_object.hecks_name
84
+ value
85
+ else
86
+ build(value_object, fields_for(value_object, attribute.name, value), aggregate)
76
87
  end
77
88
 
78
89
  admit_declared_set(aggregate, attribute, coerced)
79
90
  coerced
80
91
  end
81
92
 
93
+ # NIL IS NOT A VALUE FOR A NON-OPTIONAL ARGUMENT (C3.7/C3.8). An
94
+ # `optional:` attribute and a load from the store pass nil through
95
+ # as they always did; a command argument offered as null for a
96
+ # required attribute is
97
+ # refused as the field the caller left empty — a value object is
98
+ # BUILT from no fields, so its first required field refuses with
99
+ # exactly the wording the Rust side's `from_json` gives it
100
+ # ("Money.cents expects Integer, got nil"), and a bare scalar
101
+ # refuses through `check_bare_primitive`'s own wording. Lists and
102
+ # references keep their nil passthrough (their absence is an empty
103
+ # relationship, `validate_relationship_cardinality`'s business).
104
+ # The `attribute.nil?`/`value.nil?` branch of `for_attribute`,
105
+ # pulled out on its own — an unknown attribute has no shape left
106
+ # to branch on, and a nil VALUE is either an ordinary absence
107
+ # (state assembly, hydration, a query ask) or, at the argument
108
+ # door only, `nil_argument`'s own C3.7 refusal.
109
+ private def nil_or_missing(aggregate, attribute, value, argument)
110
+ return value if attribute.nil? || !value.nil?
111
+
112
+ argument ? nil_argument(aggregate, attribute) : value
113
+ end
114
+
115
+ private def nil_argument(aggregate, attribute)
116
+ return nil if attribute.optional? || trusting_stored_state?
117
+ return nil if attribute.list? || attribute.reference?
118
+
119
+ value_object = aggregate.respond_to?(:value_object) ? value_object_for(aggregate, attribute.type) : nil
120
+ return build(value_object, {}, aggregate) if value_object
121
+
122
+ raise TypeMismatch,
123
+ RefusalWording.render("TypeMismatch", "numeric_field",
124
+ type: aggregate.hecks_name, field: attribute.name,
125
+ expected: attribute.type, offered: "nil")
126
+ end
127
+
128
+ # A BARE PRIMITIVE IS TYPE-CHECKED AT THE BOUNDARY TOO (C3.8,
129
+ # docs/semantics/bluebook-semantics.md) — the same two predicates a
130
+ # value object's own fields get, so wrong-typed caller input is a
131
+ # TypeMismatch refusal here, never an evaluation fault later. Its
132
+ # `admits:` set is still checked, exactly as a value object's is.
133
+ private def bare_primitive(aggregate, attribute, value, boundary)
134
+ check_bare_primitive(aggregate, attribute, value) if boundary
135
+ admit_declared_set(aggregate, attribute, value)
136
+ value
137
+ end
138
+
82
139
  # Aggregate-local value objects remain authoritative, which permits
83
140
  # intentional duplication. An ordinary fact may also name an identity
84
141
  # value object declared on another aggregate; that shape is borrowed
@@ -112,29 +169,43 @@ module Hecks
112
169
  paths = target.identity_paths
113
170
  return value if paths.empty?
114
171
 
115
- direct_head = if value.is_a?(self) && target.identity_heads.one?
116
- head = target.identity_heads.first
117
- target.attribute(head)&.type.to_s == value.type_name ? head.to_s : nil
118
- end
119
-
120
- parts = paths.map do |path|
121
- segments = path.to_s.split(".")
122
- segments.shift if direct_head && segments.first == direct_head
123
- segments.reduce(materialized) do |held, segment|
124
- next nil unless held.is_a?(Hash)
125
-
126
- # `key?` decides which spelling answers, never `||` — a
127
- # genuinely-held `false` must not fall through to the
128
- # other spelling (usually absent) and read as `nil`.
129
- sym = segment.to_sym
130
- held.key?(sym) ? held[sym] : held[segment]
131
- end
132
- end
172
+ direct_head = direct_identity_head(value, target)
173
+ parts = paths.map { |path| identity_part(materialized, path, direct_head) }
133
174
  return value if parts.any? { |part| part.nil? || (part.respond_to?(:empty?) && part.empty?) }
134
175
 
135
176
  Naming.identity(parts)
136
177
  end
137
178
 
179
+ # Whether `value` is itself the target's own (single) identity
180
+ # value object — pure, self-contained: reads only `value` and
181
+ # `target`, decides nothing about any particular path.
182
+ def direct_identity_head(value, target)
183
+ return nil unless value.is_a?(self) && target.identity_heads.one?
184
+
185
+ head = target.identity_heads.first
186
+ target.attribute(head)&.type.to_s == value.type_name ? head.to_s : nil
187
+ end
188
+
189
+ # One identity path's own value out of the materialized hash —
190
+ # stripping a leading segment already covered by
191
+ # `direct_identity_head`, then walking the rest. Pure given its
192
+ # three inputs; extracted from `reference_identity` alongside
193
+ # `direct_identity_head` above purely to keep that method to its
194
+ # own guard-clause shape.
195
+ def identity_part(materialized, path, direct_head)
196
+ segments = path.to_s.split(".")
197
+ segments.shift if direct_head && segments.first == direct_head
198
+ segments.reduce(materialized) do |held, segment|
199
+ next nil unless held.is_a?(Hash)
200
+
201
+ # `key?` decides which spelling answers, never `||` — a
202
+ # genuinely-held `false` must not fall through to the
203
+ # other spelling (usually absent) and read as `nil`.
204
+ sym = segment.to_sym
205
+ held.key?(sym) ? held[sym] : held[segment]
206
+ end
207
+ end
208
+
138
209
  def reference_list(attribute, value)
139
210
  unless value.is_a?(Array)
140
211
  raise TypeMismatch,
@@ -170,9 +241,7 @@ module Hecks
170
241
  # refuse below, unchanged -- only the genuinely unambiguous
171
242
  # single-field case auto-wraps, matching from_identifier's own
172
243
  # precedent exactly.
173
- if value_object.attributes.size == 1
174
- return { value_object.attributes.first.name => value }
175
- end
244
+ return { value_object.attributes.first.name => value } if value_object.attributes.size == 1
176
245
 
177
246
  raise TypeMismatch,
178
247
  RefusalWording.render("TypeMismatch", "value_object_shape",
@@ -218,7 +287,7 @@ module Hecks
218
287
  # `build`, so a nested field's own default is filled in before its
219
288
  # own invariants read it.
220
289
  def normalize_composite_fields(aggregate, value_object, fields)
221
- return fields unless aggregate&.respond_to?(:value_object)
290
+ return fields unless aggregate.respond_to?(:value_object)
222
291
 
223
292
  value_object.attributes.each do |attribute|
224
293
  next if attribute.list? || !fields.key?(attribute.name)
@@ -250,13 +319,22 @@ module Hecks
250
319
  # the same way, with exactly the same wording, as the identical type
251
320
  # declared directly on a command.
252
321
  def validate!(value_object, fields)
322
+ # C6.3 (docs/semantics/bluebook-semantics.md) — a value object is
323
+ # validated on CONSTRUCTION FROM INPUT only; state read back from
324
+ # the store is trusted as it was written, so tightening an
325
+ # invariant never makes an old record unreadable (migration is
326
+ # the era system's job). `hydrate` — the one load door — sets
327
+ # the flag; every input door leaves it unset.
328
+ return if trusting_stored_state?
329
+
330
+ check_required_fields(value_object, fields)
253
331
  admit_member(value_object, fields)
254
332
  check_admitted(value_object, fields)
255
333
  check_numeric_fields(value_object, fields)
256
334
  check_scalar_shapes(value_object, fields)
257
335
  check_patterns(value_object, fields)
258
336
  value_object.invariants.each do |invariant|
259
- next if Bluebook::Expression::Evaluator.call(invariant.canonical, fields)
337
+ next if Bluebook::Expression::Evaluator.call_rule(invariant, fields)
260
338
 
261
339
  raise InvariantViolation,
262
340
  RefusalWording.render("InvariantViolation", "value_object_invariant",
@@ -273,13 +351,27 @@ module Hecks
273
351
  end
274
352
 
275
353
  def hydrate(aggregate, state)
276
- state.each_with_object({}) do |(name, value), hydrated|
277
- key = name.to_sym
278
- attribute = aggregate.attribute(key)
279
- hydrated[key] = attribute ? for_attribute(aggregate, attribute, value) : value
354
+ trusting_stored_state do
355
+ state.each_with_object({}) do |(name, value), hydrated|
356
+ key = name.to_sym
357
+ attribute = aggregate.attribute(key)
358
+ hydrated[key] = attribute ? for_attribute(aggregate, attribute, value) : value
359
+ end
280
360
  end
281
361
  end
282
362
 
363
+ TRUSTED_LOAD_KEY = :hecks_trusting_stored_state
364
+
365
+ def trusting_stored_state
366
+ previous = Thread.current[TRUSTED_LOAD_KEY]
367
+ Thread.current[TRUSTED_LOAD_KEY] = true
368
+ yield
369
+ ensure
370
+ Thread.current[TRUSTED_LOAD_KEY] = previous
371
+ end
372
+
373
+ def trusting_stored_state? = Thread.current[TRUSTED_LOAD_KEY] == true
374
+
283
375
  # S17, ADR 0026 — SEARCHES THE WHOLE ENTITY TREE, not only the
284
376
  # root's own direct children. `aggregate` here is always the
285
377
  # ROOT aggregate — `for_attribute`'s own `aggregate` argument is
@@ -307,16 +399,16 @@ module Hecks
307
399
  entity = find_entity(aggregate, attribute.type.to_s)
308
400
  return value unless entity
309
401
 
310
- Array(value).map do |element|
402
+ hydrated = Array(value).map do |element|
311
403
  next element unless element.is_a?(Hash)
312
404
 
313
- element.each_with_object({}) do |(name, field_value), hydrated|
405
+ element.each_with_object({}) do |(name, field_value), acc|
314
406
  key = name.to_sym
315
407
  field = entity.attribute(key)
316
- hydrated[key] = field ? for_attribute(aggregate, field, field_value) : field_value
408
+ acc[key] = field ? for_attribute(aggregate, field, field_value) : field_value
317
409
  end
318
410
  end
319
- .then { |hydrated| Freezer.deep(hydrated) }
411
+ Freezer.deep(hydrated)
320
412
  end
321
413
 
322
414
  # `Value.identifier` used to live here: hand it a one-field value object
@@ -451,6 +543,78 @@ module Hecks
451
543
  # where the domain should have said no, and the run contract recorded the
452
544
  # crash beside genuine refusals as though the domain had judged it.
453
545
  #
546
+ # C3.8 — the boundary check for an attribute whose type is a bare
547
+ # primitive rather than a value object: `Integer`/`Float` by exact
548
+ # numeric class (`NUMERIC`), `String`/booleans by rejecting a
549
+ # composite shape (`COMPOSITE_SHAPES`) — identical to what
550
+ # `check_numeric_fields`/`check_scalar_shapes` hold a value
551
+ # object's own fields to, worded by the same template with the
552
+ # owning construct as `type`. A `String` field still admits any
553
+ # other scalar (the self-hosted grammar's own bootstrap relies on
554
+ # it — see `check_scalar_shapes`' comment); the Rust boundary is
555
+ # stricter there, recorded in the clause.
556
+ private def check_bare_primitive(owner, attribute, value)
557
+ type = attribute.type.to_s
558
+ expected = NUMERIC[type]
559
+ mistyped = if expected
560
+ !value.is_a?(expected)
561
+ elsif NON_NUMERIC_SCALARS.include?(type)
562
+ COMPOSITE_SHAPES.any? { |shape| value.is_a?(shape) }
563
+ else
564
+ false
565
+ end
566
+ if mistyped
567
+ raise TypeMismatch,
568
+ RefusalWording.render("TypeMismatch", "numeric_field",
569
+ type: owner.hecks_name, field: attribute.name,
570
+ expected: type, offered: Rendering.describe(value))
571
+ end
572
+
573
+ check_numeric_bounds(owner.hecks_name, attribute.name, value)
574
+ end
575
+
576
+ # C3.3/C3.4 — the value model's own bounds, held at every boundary:
577
+ # an Integer must fit in signed 64 bits, a Float must be finite.
578
+ # One check for value-object fields and bare-primitive arguments
579
+ # alike (`check_numeric_fields` and `check_bare_primitive`).
580
+ INT64_RANGE = (-(2**63))..((2**63) - 1)
581
+
582
+ private def check_numeric_bounds(type_name, field_name, given)
583
+ if given.is_a?(Integer) && !INT64_RANGE.cover?(given)
584
+ raise TypeMismatch,
585
+ RefusalWording.render("TypeMismatch", "integer_range",
586
+ type: type_name, field: field_name, offered: Rendering.describe(given))
587
+ end
588
+ return unless given.is_a?(Float) && !given.finite?
589
+
590
+ raise TypeMismatch,
591
+ RefusalWording.render("TypeMismatch", "non_finite_field",
592
+ type: type_name, field: field_name, offered: Rendering.describe(given))
593
+ end
594
+
595
+ # C3.7 — A VALUE OBJECT IS A TYPED FIELD PRODUCT: every non-optional
596
+ # field arrives, or construction refuses. A missing field and a null
597
+ # one are the same absence (`fields[name]` reads nil for both), worded
598
+ # as the type mismatch it is — "{type}.{field} expects {expected}, got
599
+ # nil" — the identical string the Rust side's generated `from_json`
600
+ # gives the same input, so the corpus can pin it on both. Checked
601
+ # FIRST: an invariant reading a field that never arrived is exactly
602
+ # the thing that used to answer "invariant violated" (or nothing at
603
+ # all — `ToppingName`'s `{value: null}` used to be accepted and
604
+ # stored). A `default:` has already been filled in by `apply_defaults`;
605
+ # a list field's absence is an empty list, never a refusal.
606
+ private def check_required_fields(value_object, fields)
607
+ value_object.attributes.each do |attribute|
608
+ next if attribute.optional? || attribute.list?
609
+ next unless fields[attribute.name].nil?
610
+
611
+ raise TypeMismatch,
612
+ RefusalWording.render("TypeMismatch", "numeric_field",
613
+ type: value_object.hecks_name, field: attribute.name,
614
+ expected: attribute.type, offered: "nil")
615
+ end
616
+ end
617
+
454
618
  # Checked BEFORE invariants, because an invariant reading a mistyped field
455
619
  # is exactly the thing that used to explode.
456
620
  NUMERIC = { "Integer" => Integer, "Float" => Numeric }.freeze
@@ -489,12 +653,7 @@ module Hecks
489
653
  # cleanly (confirmed empirically), and is a legitimate,
490
654
  # meaningful float value (a signed zero), not a corruption
491
655
  # risk — only NaN and +/-Infinity are.
492
- if given.is_a?(Float) && !given.finite?
493
- raise TypeMismatch,
494
- RefusalWording.render("TypeMismatch", "non_finite_field",
495
- type: value_object.hecks_name, field: attribute.name,
496
- offered: Rendering.describe(given))
497
- end
656
+ check_numeric_bounds(value_object.hecks_name, attribute.name, given)
498
657
  end
499
658
  end
500
659
 
@@ -108,7 +108,7 @@ module Hecks
108
108
  # forever, once per shape of "gone" any caller ever invents. The
109
109
  # caller filters and sorts the result; this only groups it.
110
110
  def self.latest_by(rows, key)
111
- rows.each_with_object({}) { |row, latest| latest[row.public_send(key)] = row }.values
111
+ rows.to_h { |row| [row.public_send(key), row] }.values
112
112
  end
113
113
 
114
114
  def method_missing(name, *args)
data/lib/hecks/runtime.rb CHANGED
@@ -34,6 +34,7 @@ require_relative "runtime/value"
34
34
  require_relative "runtime/identity"
35
35
  require_relative "runtime/instance"
36
36
  require_relative "runtime/registry"
37
+ require_relative "runtime/outbox"
37
38
  require_relative "runtime/capability_graph"
38
39
  require_relative "runtime/errors"
39
40
  require_relative "runtime/refusal_wording"
@@ -54,6 +55,10 @@ require_relative "runtime/tenant_check"
54
55
  require_relative "runtime/loader"
55
56
 
56
57
  module Hecks
58
+ # See the file header above for what this module is and owns; nested
59
+ # here (rather than documented in place) only because the
60
+ # `require_relative` calls above must run before Ruby reopens the
61
+ # module they populate.
57
62
  module Runtime
58
63
  class << self
59
64
  # The registry declarations are currently landing in, or nil outside a
@@ -99,8 +104,8 @@ module Hecks
99
104
  # `RoleAssignment`'s own `scope`, not just its `role_name`. See
100
105
  # `Runtime::Caller::Current`'s own header for why this lives here
101
106
  # rather than as a command-level DSL construct.
102
- def as_caller(role:, actor_id: nil, as_of: nil, scope: nil, &block)
103
- Caller.as(role: role, actor_id: actor_id, as_of: as_of, scope: scope, &block)
107
+ def as_caller(role:, actor_id: nil, as_of: nil, scope: nil, &)
108
+ Caller.as(role: role, actor_id: actor_id, as_of: as_of, scope: scope, &)
104
109
  end
105
110
  end
106
111
  end
@@ -592,14 +592,7 @@ module Hecks
592
592
  bluebook = bluebook_for(runtime)
593
593
  fqn = aggregate ? "#{bluebook.name}::#{aggregate_ir!(bluebook, aggregate).hecks_name}" : nil
594
594
 
595
- found = log_lines(bluebook.name).filter_map do |entry|
596
- next unless entry[:tool] == "dispatch" && entry[:ok] && entry[:events]
597
- next if fqn && !entry[:verb].to_s.start_with?("#{fqn}.")
598
- next if id && entry[:id] != id
599
-
600
- entry[:events].map { |event| event.merge(time: entry[:time], verb: entry[:verb], id: entry[:id]) }
601
- end.flatten(1)
602
-
595
+ found = log_lines(bluebook.name).filter_map { |entry| entry_events(entry, fqn, id) }.flatten(1)
603
596
  found = found.last(limit.to_i) if limit
604
597
 
605
598
  ok(domain: bluebook.name, events: found)
@@ -607,6 +600,20 @@ module Hecks
607
600
  refused(e)
608
601
  end
609
602
 
603
+ # One log line's own contribution to `events`, above — nil unless it
604
+ # was a successful dispatch carrying events, matching `fqn` (when an
605
+ # aggregate narrowed the search) and `id` (when a record did). A
606
+ # self-contained per-entry check with nothing to share with its
607
+ # neighbors, extracted only to keep `events` itself to the query's
608
+ # own shape: build the filter, apply the limit, wrap the result.
609
+ def entry_events(entry, fqn, id)
610
+ return unless entry[:tool] == "dispatch" && entry[:ok] && entry[:events]
611
+ return if fqn && !entry[:verb].to_s.start_with?("#{fqn}.")
612
+ return if id && entry[:id] != id
613
+
614
+ entry[:events].map { |event| event.merge(time: entry[:time], verb: entry[:verb], id: entry[:id]) }
615
+ end
616
+
610
617
  # ── shared shape ────────────────────────────────────────────────────
611
618
 
612
619
  # `Runtime::WiringError` BELONGS HERE TOO, alongside the true domain
data/lib/hecks/version.rb CHANGED
@@ -12,5 +12,5 @@ module Hecks
12
12
  # it there, or even in the consuming Gemfile, never closed the gap,
13
13
  # because gemspec evaluation happens before anything Bundler
14
14
  # resolves is actually loadable yet.
15
- VERSION = "1.0.2"
15
+ VERSION = "1.1.0".freeze
16
16
  end
@@ -153,6 +153,7 @@ module Hecks
153
153
  {"refusal"=>"TypeMismatch", "site"=>"composite_identity", "template"=>"{type} is a composite identity — an identity must have exactly one field"}.freeze,
154
154
  {"refusal"=>"TypeMismatch", "site"=>"numeric_field", "template"=>"{type}.{field} expects {expected}, got {offered}"}.freeze,
155
155
  {"refusal"=>"TypeMismatch", "site"=>"non_finite_field", "template"=>"{type}.{field} must be a finite number, got {offered}"}.freeze,
156
+ {"refusal"=>"TypeMismatch", "site"=>"integer_range", "template"=>"{type}.{field} must fit in a 64-bit integer, got {offered}"}.freeze,
156
157
  {"refusal"=>"TypeMismatch", "site"=>"pattern_mismatch", "template"=>"{type}.{field} must match {pattern}, got {offered}"}.freeze,
157
158
  {"refusal"=>"TypeMismatch", "site"=>"arithmetic_amount", "template"=>"{op} of {target} needs an Integer, got {offered}"}.freeze,
158
159
  {"refusal"=>"TypeMismatch", "site"=>"arithmetic_current", "template"=>"{op} of {target} needs an Integer {target}, got {offered}"}.freeze,
data/lib/hecks.rb CHANGED
@@ -34,12 +34,16 @@ require_relative "hecks/storehouse"
34
34
  require_relative "hecks/framework"
35
35
  require_relative "hecks/embryonaut_bluebook"
36
36
 
37
+ # The root namespace and public facade of the whole DSL/runtime: `Hecks.boot`/
38
+ # `.boot_files` assemble a running domain from `.bluebook`/`.hecksagon`/
39
+ # `.world` files, and `.bluebook`/`.hecksagon`/`.port`/`.adapter`/`.world`/
40
+ # `.data_translation` are the top-level declaration words every such file
41
+ # opens with, each collecting its built construct into the currently booting
42
+ # Registry (`#collect`, private below).
37
43
  module Hecks
38
44
  class LoadOutsideBoot < StandardError; end
39
45
 
40
46
  class << self
41
- attr_reader :current_registry
42
-
43
47
  # The loading words below collect into the registry the RUNTIME is
44
48
  # holding open ; booting and that ambient state belong to the runtime
45
49
  # layer, so this module is their facade and Hecks::Runtime is where
@@ -73,7 +77,7 @@ module Hecks
73
77
  Runtime.boot_files(paths, shared: shared, install_facade: install_facade, environment: environment)
74
78
  end
75
79
 
76
- def with_registry(registry, &block) = Runtime.with_registry(registry, &block)
80
+ def with_registry(registry, &) = Runtime.with_registry(registry, &)
77
81
 
78
82
  def current_registry = Runtime.current_registry
79
83
 
@@ -91,15 +95,15 @@ module Hecks
91
95
  #
92
96
  # `as_of` and `scope` are OPTIONAL too, same shape — see
93
97
  # `Runtime.as_caller`'s own header for what each does.
94
- def as_caller(role:, actor_id: nil, as_of: nil, scope: nil, &block)
95
- Runtime.as_caller(role: role, actor_id: actor_id, as_of: as_of, scope: scope, &block)
98
+ def as_caller(role:, actor_id: nil, as_of: nil, scope: nil, &)
99
+ Runtime.as_caller(role: role, actor_id: actor_id, as_of: as_of, scope: scope, &)
96
100
  end
97
101
 
98
- def bluebook(name, version: nil, &block)
99
- collect(:add_bluebook, Bluebook::DSL::BluebookBuilder.build(name, version: version, &block))
102
+ def bluebook(name, version: nil, &)
103
+ collect(:add_bluebook, Bluebook::DSL::BluebookBuilder.build(name, version: version, &))
100
104
  end
101
105
 
102
- def hecksagon(name, &block) = collect(:add_hecksagon, Bluebook::DSL::HecksagonBuilder.build(name, &block))
106
+ def hecksagon(name, &) = collect(:add_hecksagon, Bluebook::DSL::HecksagonBuilder.build(name, &))
103
107
  # REPOINTED TO DomainPortBuilder — the migration DomainPort's own
104
108
  # class comment names as its goal, now landed for the top-level
105
109
  # `.port` file callers too (the aggregate-scoped `Thing.port(...)`,
@@ -120,12 +124,12 @@ module Hecks
120
124
  # (`BindingProxy#port`, `HecksagonBuilder#port_impl`) — only this,
121
125
  # the literal top-level `.port` file entry point, keeps the older,
122
126
  # looser rule (see `DomainPortBuilder#initialize`'s own comment).
123
- def port(name, &block) = collect(:add_port, Bluebook::DSL::DomainPortBuilder.build(name, legacy_bare_port: true, &block))
124
- def adapter(name, &block) = collect(:add_adapter, Bluebook::DSL::AdapterBuilder.build(name, &block))
125
- def world(name, &block) = collect(:add_world, Bluebook::DSL::WorldBuilder.build(name, &block))
127
+ def port(name, &) = collect(:add_port, Bluebook::DSL::DomainPortBuilder.build(name, legacy_bare_port: true, &))
128
+ def adapter(name, &) = collect(:add_adapter, Bluebook::DSL::AdapterBuilder.build(name, &))
129
+ def world(name, &) = collect(:add_world, Bluebook::DSL::WorldBuilder.build(name, &))
126
130
 
127
- def data_translation(name, from:, to:, &block)
128
- collect(:add_translation, Bluebook::DSL::TranslationBuilder.build(name, from: from, to: to, &block))
131
+ def data_translation(name, from:, to:, &)
132
+ collect(:add_translation, Bluebook::DSL::TranslationBuilder.build(name, from: from, to: to, &))
129
133
  end
130
134
 
131
135
  private
@@ -53,7 +53,7 @@ module RuboCop
53
53
  "silently discarded in favor of `%<receiver>s[%<rhs_key>s]` instead of being " \
54
54
  "returned. Use `%<receiver>s.key?(%<lhs_key>s) ? %<receiver>s[%<lhs_key>s] : " \
55
55
  "%<receiver>s[%<rhs_key>s]`, or a shared digger (see `key?` in " \
56
- "`Hecks::QuerySpecification::FieldPath#read`), instead."
56
+ "`Hecks::QuerySpecification::FieldPath#read`), instead.".freeze
57
57
 
58
58
  # @!method bracket_lookup(node)
59
59
  def_node_matcher :bracket_lookup, "(send $_receiver :[] $_key)"
@@ -60,7 +60,7 @@ module RuboCop
60
60
  "because the first rule's write clobbers the second rule's read target before it runs (the exact " \
61
61
  "bug fixed for Lineage#apply_renames). Snapshot every old key's value FIRST, delete all old keys, " \
62
62
  "then write all new keys, so the pass applies as one simultaneous permutation instead of a " \
63
- "sequence of edits each stepping on the last."
63
+ "sequence of edits each stepping on the last.".freeze
64
64
 
65
65
  RESTRICT_ON_SEND = [:[]=].freeze
66
66
 
@@ -61,7 +61,7 @@ module RuboCop
61
61
  "other's view of it, the exact bug already fixed for `Dispatcher#reaction_depth` " \
62
62
  "(see dispatcher.rb's `#reenter`). Use `Thread.current[:...]` for per-thread " \
63
63
  "state, or a `Mutex`-guarded critical section (`Registry#saga_mutex`) if the " \
64
- "state genuinely must be shared."
64
+ "state genuinely must be shared.".freeze
65
65
 
66
66
  THREAD_SHARED_CLASSES = ["Dispatcher", "Registry"].freeze
67
67
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hecks
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Young
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-28 00:00:00.000000000 Z
11
+ date: 2026-09-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: prism
@@ -58,6 +58,8 @@ files:
58
58
  - lib/hecks/adapters/driven/lambda.adapter
59
59
  - lib/hecks/adapters/driven/lambda.rb
60
60
  - lib/hecks/adapters/driven/lambda/client.rb
61
+ - lib/hecks/adapters/driven/local_storage.adapter
62
+ - lib/hecks/adapters/driven/local_storage.rb
61
63
  - lib/hecks/adapters/driven/memory.adapter
62
64
  - lib/hecks/adapters/driven/memory.rb
63
65
  - lib/hecks/adapters/driven/mock_stripe_adapter.adapter
@@ -65,6 +67,7 @@ files:
65
67
  - lib/hecks/adapters/driven/postgres.adapter
66
68
  - lib/hecks/adapters/driven/postgres.rb
67
69
  - lib/hecks/adapters/driven/postgres/codec.rb
70
+ - lib/hecks/adapters/driven/postgres/outbox.rb
68
71
  - lib/hecks/adapters/driven/postgres/schema_builder.rb
69
72
  - lib/hecks/adapters/driven/postgres_era.adapter
70
73
  - lib/hecks/adapters/driven/prism.adapter
@@ -115,9 +118,11 @@ files:
115
118
  - lib/hecks/bluebook/dsl.rb
116
119
  - lib/hecks/bluebook/dsl/adapter_builder.rb
117
120
  - lib/hecks/bluebook/dsl/aggregate_builder.rb
121
+ - lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb
118
122
  - lib/hecks/bluebook/dsl/attribute_collector.rb
119
123
  - lib/hecks/bluebook/dsl/binding_proxy.rb
120
124
  - lib/hecks/bluebook/dsl/bluebook_builder.rb
125
+ - lib/hecks/bluebook/dsl/bluebook_builder/validation.rb
121
126
  - lib/hecks/bluebook/dsl/command_builder.rb
122
127
  - lib/hecks/bluebook/dsl/const_shim.rb
123
128
  - lib/hecks/bluebook/dsl/domain_port_builder.rb
@@ -141,6 +146,7 @@ files:
141
146
  - lib/hecks/bluebook/entity.rb
142
147
  - lib/hecks/bluebook/expression.rb
143
148
  - lib/hecks/bluebook/expression/ast_json.rb
149
+ - lib/hecks/bluebook/expression/ast_reader.rb
144
150
  - lib/hecks/bluebook/expression/canonical_form.rb
145
151
  - lib/hecks/bluebook/expression/evaluator.rb
146
152
  - lib/hecks/bluebook/expression/projection.json
@@ -219,6 +225,11 @@ files:
219
225
  - lib/hecks/fuzzing/invalid_value_generator.rb
220
226
  - lib/hecks/fuzzing/isolated_boot.rb
221
227
  - lib/hecks/fuzzing/properties.rb
228
+ - lib/hecks/fuzzing/properties/dispatch_and_mutations.rb
229
+ - lib/hecks/fuzzing/properties/guards.rb
230
+ - lib/hecks/fuzzing/properties/invariants_and_aggregation.rb
231
+ - lib/hecks/fuzzing/properties/lifecycle_and_replay.rb
232
+ - lib/hecks/fuzzing/properties/querying.rb
222
233
  - lib/hecks/fuzzing/replay.rb
223
234
  - lib/hecks/fuzzing/sequence_generator.rb
224
235
  - lib/hecks/fuzzing/sequence_generator/catalog.rb
@@ -394,6 +405,7 @@ files:
394
405
  - lib/hecks/runtime/instance.rb
395
406
  - lib/hecks/runtime/interpreting.rb
396
407
  - lib/hecks/runtime/loader.rb
408
+ - lib/hecks/runtime/outbox.rb
397
409
  - lib/hecks/runtime/policy_interpreter.rb
398
410
  - lib/hecks/runtime/port_operation_interpreter.rb
399
411
  - lib/hecks/runtime/query_interpreter.rb