hecks 1.0.2 → 1.2.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 (247) 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 +100 -0
  15. data/lib/hecks/adapters/driven/postgres/reconnect.rb +57 -0
  16. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +22 -2
  17. data/lib/hecks/adapters/driven/postgres.rb +45 -31
  18. data/lib/hecks/adapters/driven/prism.rb +11 -0
  19. data/lib/hecks/adapters/driven/sql_query_builder.rb +45 -20
  20. data/lib/hecks/adapters/driven/sqlite/codec.rb +3 -1
  21. data/lib/hecks/adapters/driven/sqlite/projection.rb +22 -3
  22. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +23 -1
  23. data/lib/hecks/adapters/driven/sqlite.rb +87 -8
  24. data/lib/hecks/adapters/driven.rb +4 -0
  25. data/lib/hecks/adapters.rb +4 -0
  26. data/lib/hecks/behaviors/dsl.rb +7 -0
  27. data/lib/hecks/behaviors/expectations.rb +32 -4
  28. data/lib/hecks/behaviors/rspec.rb +2 -0
  29. data/lib/hecks/behaviors/runner.rb +4 -0
  30. data/lib/hecks/bluebook/aggregate.rb +6 -5
  31. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +4 -4
  32. data/lib/hecks/bluebook/assembly/build.rb +6 -3
  33. data/lib/hecks/bluebook/assembly/contract.rb +5 -1
  34. data/lib/hecks/bluebook/assembly/contracts.rb +4 -0
  35. data/lib/hecks/bluebook/assembly/marks.rb +15 -4
  36. data/lib/hecks/bluebook/assembly.rb +1 -3
  37. data/lib/hecks/bluebook/attribute.rb +6 -0
  38. data/lib/hecks/bluebook/behaviour/policy.rb +18 -0
  39. data/lib/hecks/bluebook/behaviour/process_manager.rb +17 -1
  40. data/lib/hecks/bluebook/behaviour/read_model.rb +53 -8
  41. data/lib/hecks/bluebook/behaviour/traits.rb +1 -1
  42. data/lib/hecks/bluebook/command.rb +6 -5
  43. data/lib/hecks/bluebook/dsl/adapter_builder.rb +4 -1
  44. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +524 -0
  45. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +29 -477
  46. data/lib/hecks/bluebook/dsl/attribute_collector.rb +14 -6
  47. data/lib/hecks/bluebook/dsl/binding_proxy.rb +5 -0
  48. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +848 -0
  49. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +25 -784
  50. data/lib/hecks/bluebook/dsl/command_builder.rb +53 -13
  51. data/lib/hecks/bluebook/dsl/const_shim.rb +6 -0
  52. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +20 -7
  53. data/lib/hecks/bluebook/dsl/entity_builder.rb +27 -6
  54. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +12 -2
  55. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +8 -1
  56. data/lib/hecks/bluebook/dsl/identity_declaration.rb +24 -12
  57. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +40 -1
  58. data/lib/hecks/bluebook/dsl/policy_builder.rb +10 -1
  59. data/lib/hecks/bluebook/dsl/port_builder.rb +6 -1
  60. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +33 -12
  61. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +56 -12
  62. data/lib/hecks/bluebook/dsl/query_builder.rb +7 -1
  63. data/lib/hecks/bluebook/dsl/read_model_builder.rb +142 -19
  64. data/lib/hecks/bluebook/dsl/rule_reference.rb +3 -1
  65. data/lib/hecks/bluebook/dsl/translation_builder.rb +13 -2
  66. data/lib/hecks/bluebook/dsl/value_object_builder.rb +8 -1
  67. data/lib/hecks/bluebook/dsl/word_gate.rb +5 -0
  68. data/lib/hecks/bluebook/dsl/world_builder.rb +13 -3
  69. data/lib/hecks/bluebook/entity.rb +5 -3
  70. data/lib/hecks/bluebook/expression/ast_json.rb +88 -9
  71. data/lib/hecks/bluebook/expression/ast_reader.rb +107 -0
  72. data/lib/hecks/bluebook/expression/canonical_form.rb +7 -0
  73. data/lib/hecks/bluebook/expression/evaluator.rb +64 -11
  74. data/lib/hecks/bluebook/expression/projection.json +12 -0
  75. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +4 -3
  76. data/lib/hecks/bluebook/expression/resolver.rb +83 -11
  77. data/lib/hecks/bluebook/expression.rb +1 -0
  78. data/lib/hecks/bluebook/hexagon.rb +10 -0
  79. data/lib/hecks/bluebook/lifecycle.rb +9 -0
  80. data/lib/hecks/bluebook/meta_validator/judge.rb +10 -4
  81. data/lib/hecks/bluebook/meta_validator/plan.rb +4 -0
  82. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +1 -1
  83. data/lib/hecks/bluebook/meta_validator/shapes.rb +3 -3
  84. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +7 -4
  85. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +42 -3
  86. data/lib/hecks/bluebook/meta_validator/world_judge.rb +1 -1
  87. data/lib/hecks/bluebook/meta_validator.rb +5 -5
  88. data/lib/hecks/bluebook/model_check.rb +119 -82
  89. data/lib/hecks/bluebook/pattern_subset.rb +9 -0
  90. data/lib/hecks/bluebook/policy.rb +2 -1
  91. data/lib/hecks/bluebook/process_manager.rb +6 -0
  92. data/lib/hecks/bluebook/project_register.rb +45 -25
  93. data/lib/hecks/bluebook/query.rb +4 -0
  94. data/lib/hecks/bluebook/read_model.rb +2 -2
  95. data/lib/hecks/bluebook/smoke_test.rb +5 -0
  96. data/lib/hecks/bluebook/synthesizer.rb +2 -1
  97. data/lib/hecks/bluebook/translation.rb +3 -0
  98. data/lib/hecks/bluebook/value_object.rb +2 -2
  99. data/lib/hecks/codemod.rb +13 -0
  100. data/lib/hecks/doc/reference.rb +17 -11
  101. data/lib/hecks/facade/cli_runner.rb +2 -2
  102. data/lib/hecks/facade/command_request.rb +11 -11
  103. data/lib/hecks/facade/handle.rb +3 -3
  104. data/lib/hecks/facade/surface/aggregate_door.rb +52 -38
  105. data/lib/hecks/facade.rb +4 -0
  106. data/lib/hecks/forms/app.rb +10 -6
  107. data/lib/hecks/forms/command_form_renderer.rb +3 -3
  108. data/lib/hecks/forms/field_renderer.rb +14 -9
  109. data/lib/hecks/forms/field_shape.rb +7 -2
  110. data/lib/hecks/forms/html.rb +5 -2
  111. data/lib/hecks/forms/index_renderer.rb +1 -1
  112. data/lib/hecks/forms/page.rb +7 -3
  113. data/lib/hecks/forms/query_form_renderer.rb +3 -2
  114. data/lib/hecks/forms/record_renderer.rb +22 -16
  115. data/lib/hecks/forms.rb +4 -0
  116. data/lib/hecks/fqn.rb +21 -9
  117. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +30 -8
  118. data/lib/hecks/fuzzing/invalid_value_generator.rb +0 -1
  119. data/lib/hecks/fuzzing/isolated_boot.rb +14 -6
  120. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +264 -0
  121. data/lib/hecks/fuzzing/properties/guards.rb +251 -0
  122. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +351 -0
  123. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +99 -0
  124. data/lib/hecks/fuzzing/properties/querying.rb +206 -0
  125. data/lib/hecks/fuzzing/properties.rb +41 -1065
  126. data/lib/hecks/fuzzing/replay.rb +60 -16
  127. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +6 -2
  128. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +2 -1
  129. data/lib/hecks/fuzzing/sequence_generator.rb +0 -1
  130. data/lib/hecks/fuzzing/value_generator.rb +2 -2
  131. data/lib/hecks/grammar/evolve.rb +34 -10
  132. data/lib/hecks/grammar/expression_operators.json +124 -0
  133. data/lib/hecks/grammar.rb +15 -5
  134. data/lib/hecks/ir.rb +9 -0
  135. data/lib/hecks/language/bluebook/aggregate.bluebook +4 -2
  136. data/lib/hecks/language/bluebook/command.bluebook +2 -1
  137. data/lib/hecks/language/bluebook/entity.bluebook +2 -1
  138. data/lib/hecks/language/bluebook/projection.bluebook +2 -1
  139. data/lib/hecks/language/bluebook/query.bluebook +4 -2
  140. data/lib/hecks/language/bluebook/shape.bluebook +2 -1
  141. data/lib/hecks/language/bluebook/vocabulary.bluebook +2 -0
  142. data/lib/hecks/literal.rb +18 -8
  143. data/lib/hecks/naming.rb +6 -3
  144. data/lib/hecks/ports/access_control.rb +1 -1
  145. data/lib/hecks/ports/agent.rb +5 -3
  146. data/lib/hecks/ports/authentication.rb +3 -2
  147. data/lib/hecks/ports/authorization.rb +1 -1
  148. data/lib/hecks/ports/clock.rb +1 -1
  149. data/lib/hecks/ports/extraction.rb +5 -1
  150. data/lib/hecks/ports/identity_assignment.rb +1 -1
  151. data/lib/hecks/ports/identity_generation.rb +1 -1
  152. data/lib/hecks/ports/identity_resolution.rb +1 -1
  153. data/lib/hecks/ports/loading.rb +4 -1
  154. data/lib/hecks/ports/persistence/append_only.rb +51 -4
  155. data/lib/hecks/ports/persistence/binding_policy.rb +1 -1
  156. data/lib/hecks/ports/persistence/plugin.rb +3 -0
  157. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +3 -1
  158. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +5 -5
  159. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +14 -5
  160. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +4 -0
  161. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +1 -1
  162. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +80 -16
  163. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +23 -6
  164. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +43 -5
  165. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +25 -4
  166. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +49 -9
  167. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +2 -2
  168. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +10 -0
  169. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +49 -38
  170. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +77 -18
  171. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +19 -21
  172. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +27 -9
  173. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +9 -0
  174. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +25 -10
  175. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +2 -6
  176. data/lib/hecks/ports/persistence/plugins/era.rb +3 -1
  177. data/lib/hecks/ports/persistence/remote_runtime.rb +1 -1
  178. data/lib/hecks/ports/persistence.rb +7 -3
  179. data/lib/hecks/ports/projection.rb +13 -5
  180. data/lib/hecks/ports/query/in_memory.rb +8 -2
  181. data/lib/hecks/ports/query.rb +1 -3
  182. data/lib/hecks/ports.rb +2 -0
  183. data/lib/hecks/projections/diagrams.rb +11 -3
  184. data/lib/hecks/projections/glossary.rb +374 -0
  185. data/lib/hecks/projections/ir.rb +2 -0
  186. data/lib/hecks/projections/model/deviations.rb +2 -1
  187. data/lib/hecks/projections/model.rb +15 -10
  188. data/lib/hecks/projections/vocabulary.rb +1 -3
  189. data/lib/hecks/projections.rb +1 -0
  190. data/lib/hecks/projector/cli_projector.rb +78 -42
  191. data/lib/hecks/projector/docs_projector.rb +21 -12
  192. data/lib/hecks/projector/exporter.rb +34 -1
  193. data/lib/hecks/projector/narrate_projector.rb +83 -25
  194. data/lib/hecks/projector.rb +2 -2
  195. data/lib/hecks/query_ir.rb +60 -36
  196. data/lib/hecks/query_specification/common/authorization_spec.rb +1 -1
  197. data/lib/hecks/query_specification/common/comparators.rb +4 -0
  198. data/lib/hecks/query_specification/common/comparison.rb +8 -1
  199. data/lib/hecks/query_specification/common/dsl.rb +5 -0
  200. data/lib/hecks/query_specification/common/limit_spec.rb +9 -2
  201. data/lib/hecks/query_specification/common/null_policy.rb +6 -2
  202. data/lib/hecks/query_specification/common/offset_spec.rb +9 -2
  203. data/lib/hecks/query_specification/common/options.rb +17 -5
  204. data/lib/hecks/query_specification/common/order_by.rb +9 -2
  205. data/lib/hecks/query_specification/common/where_clause.rb +15 -2
  206. data/lib/hecks/query_specification/field_path.rb +4 -4
  207. data/lib/hecks/query_specification/read_model/specification.rb +5 -2
  208. data/lib/hecks/router/namespace_installer.rb +22 -10
  209. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +57 -22
  210. data/lib/hecks/runtime/command_interpreter.rb +104 -65
  211. data/lib/hecks/runtime/command_rules/admissibility.rb +26 -8
  212. data/lib/hecks/runtime/command_rules/arithmetic.rb +35 -14
  213. data/lib/hecks/runtime/dependency_planning.rb +33 -21
  214. data/lib/hecks/runtime/dispatcher.rb +41 -7
  215. data/lib/hecks/runtime/entity_element.rb +25 -6
  216. data/lib/hecks/runtime/entity_interpreter.rb +28 -15
  217. data/lib/hecks/runtime/errors.rb +28 -2
  218. data/lib/hecks/runtime/instance.rb +9 -3
  219. data/lib/hecks/runtime/interpreting.rb +45 -9
  220. data/lib/hecks/runtime/loader.rb +21 -0
  221. data/lib/hecks/runtime/outbox.rb +365 -0
  222. data/lib/hecks/runtime/policy_interpreter.rb +32 -17
  223. data/lib/hecks/runtime/query_interpreter.rb +36 -16
  224. data/lib/hecks/runtime/reaction_invocation.rb +35 -22
  225. data/lib/hecks/runtime/read_model_interpreter.rb +31 -7
  226. data/lib/hecks/runtime/reference_hop.rb +1 -1
  227. data/lib/hecks/runtime/refusal_wording.rb +55 -49
  228. data/lib/hecks/runtime/registry/saga_persistence.rb +3 -1
  229. data/lib/hecks/runtime/registry/verification.rb +39 -2
  230. data/lib/hecks/runtime/registry.rb +21 -11
  231. data/lib/hecks/runtime/remote_dispatcher.rb +3 -1
  232. data/lib/hecks/runtime/routing.rb +52 -39
  233. data/lib/hecks/runtime/saga_interpreter/correlation.rb +7 -7
  234. data/lib/hecks/runtime/saga_interpreter.rb +119 -90
  235. data/lib/hecks/runtime/saga_pending_dispatch.rb +6 -3
  236. data/lib/hecks/runtime/tenant_scope.rb +7 -0
  237. data/lib/hecks/runtime/value/coercion.rb +271 -49
  238. data/lib/hecks/runtime/value.rb +1 -1
  239. data/lib/hecks/runtime.rb +7 -2
  240. data/lib/hecks/storehouse.rb +15 -8
  241. data/lib/hecks/version.rb +1 -1
  242. data/lib/hecks/vocabulary.rb +1 -0
  243. data/lib/hecks.rb +17 -13
  244. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +1 -1
  245. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +1 -1
  246. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +1 -1
  247. metadata +16 -2
@@ -24,6 +24,26 @@ module Hecks
24
24
  def layer_two!(violations, aggregate, declared, before, after)
25
25
  rekeyed = declared && !declared.rekeys.empty?
26
26
 
27
+ check_id_conservation!(violations, aggregate, before, after, rekeyed: rekeyed)
28
+
29
+ return unless declared
30
+ # No correspondence between an old id and its rekeyed row exists
31
+ # in Ruby (the rekey's SQL is its only implementation, same as
32
+ # compute's) — exempt from the per-record equivalence gate for
33
+ # the same reason compute paths already are, extended to the
34
+ # whole record since an old-id lookup into `after` can never
35
+ # succeed once the id itself has changed.
36
+ return if rekeyed
37
+
38
+ check_value_preservation!(violations, aggregate, declared, before, after)
39
+ end
40
+
41
+ # Record-count / id-set conservation — one of the two properties
42
+ # this module's own header names, and independent of the other
43
+ # (per-rule value preservation, below): it needs only `before`,
44
+ # `after`, and whether the edge rekeyed, never the declared rules
45
+ # themselves.
46
+ def check_id_conservation!(violations, aggregate, before, after, rekeyed:)
27
47
  # A rekey legitimately changes the id SET (that's the entire
28
48
  # point) — set-equality would flag every honest rekey as data
29
49
  # loss. What must still hold is RECORD COUNT: a botched rekey
@@ -43,15 +63,13 @@ module Hecks
43
63
  violations << "#{aggregate.name}: the id set changed across the edge " \
44
64
  "(lost #{lost.sort.inspect}, gained #{gained.sort.inspect})"
45
65
  end
46
- return unless declared
47
- # No correspondence between an old id and its rekeyed row exists
48
- # in Ruby (the rekey's SQL is its only implementation, same as
49
- # compute's) — exempt from the per-record equivalence gate for
50
- # the same reason compute paths already are, extended to the
51
- # whole record since an old-id lookup into `after` can never
52
- # succeed once the id itself has changed.
53
- return if rekeyed
66
+ end
54
67
 
68
+ # Per-rule value preservation — the other property this module's
69
+ # header names. Only reached once `layer_two!` has already ruled
70
+ # out "no declared edge" and "rekeyed" (see its own comment on
71
+ # that second guard); this method assumes both are false.
72
+ def check_value_preservation!(violations, aggregate, declared, before, after)
55
73
  rules = Ports::Persistence::Lineage.from_declared(declared, aggregate.name)
56
74
  compute_paths = declared.computes.flat_map { |compute| [compute.from, compute.to] }.map(&:to_s)
57
75
 
@@ -63,7 +81,7 @@ module Hecks
63
81
  actual = strip_compute_paths(normalize(after[id]), compute_paths)
64
82
  next if expected == actual
65
83
 
66
- diverged = (expected.keys | actual.keys).select { |key| expected[key] != actual[key] }
84
+ diverged = (expected.keys | actual.keys).reject { |key| expected[key] == actual[key] }
67
85
  violations << "#{aggregate.name}##{id}: the translated state diverges from the reference " \
68
86
  "transform at #{diverged.sort.join(', ')}"
69
87
  end
@@ -7,6 +7,15 @@ module Hecks
7
7
  # attribute, and the loud refusal for a REQUIRED one comes from
8
8
  # Layer 1 the moment an invariant reads it.
9
9
  module UnfedReport
10
+ # `fed` build-up plus one ordered guard chain per attribute
11
+ # (already fed, has a default, actually present in some record, no
12
+ # records to check at all) answering a single question — "is
13
+ # anything, ever, going to feed this field" — where each `next`
14
+ # rules out one way the answer is "not unfed after all" before the
15
+ # next is even worth asking. Splitting the guards out would just
16
+ # turn each into a same-shaped one-line predicate call.
17
+ # rubocop:disable-next Metrics/CyclomaticComplexity
18
+ # rubocop:disable-next Metrics/PerceivedComplexity
10
19
  def unfed(aggregate, declared, after)
11
20
  fed = if declared
12
21
  (declared.renames.values.map(&:to_s) +
@@ -74,27 +74,43 @@ module Hecks
74
74
  held_attrs = (held_shape["attributes"] || []).to_h { |attribute| [attribute["name"], attribute] }
75
75
  current_attrs = (current_shape["attributes"] || []).to_h { |attribute| [attribute["name"], attribute] }
76
76
 
77
- # retype pass: same attribute name, same member structure, the
78
- # TYPE's own name changed
79
- (held_attrs.keys & current_attrs.keys).each do |name|
77
+ rules.concat(retype_rules(held_attrs, current_attrs))
78
+ retyped = rules.filter_map { |rule| rule[:from] if rule[:kind] == :retype }
79
+
80
+ vanished = vanished_paths(held_attrs, current_attrs, retyped)
81
+ appeared = appeared_paths(held_attrs, current_attrs, retyped)
82
+
83
+ # Mutates `rules` in place (not build-and-concat, like the two
84
+ # passes above) because it must READ its own earlier writes:
85
+ # `taken`, below, is recomputed from `rules` at the top of every
86
+ # iteration, so a target this same loop already claimed for an
87
+ # earlier vanished path is excluded from a later one. Passing a
88
+ # snapshot instead of the live array would let two vanished
89
+ # paths both match the same appeared target.
90
+ resolve_vanished_rules!(rules, vanished, appeared)
91
+ rules
92
+ end
93
+
94
+ # retype pass: same attribute name, same member structure, the
95
+ # TYPE's own name changed
96
+ def retype_rules(held_attrs, current_attrs)
97
+ (held_attrs.keys & current_attrs.keys).filter_map do |name|
80
98
  held = held_attrs[name]
81
99
  current = current_attrs[name]
82
100
  next if held == current
83
101
  next unless container?(held) && container?(current)
84
102
  next unless held["type"]["members"] == current["type"]["members"] && held["list"] == current["list"]
85
103
 
86
- rules << { kind: :retype, from: held["type"]["type"], to: current["type"]["type"] }
104
+ { kind: :retype, from: held["type"]["type"], to: current["type"]["type"] }
87
105
  end
88
- retyped = rules.filter_map { |rule| rule[:from] if rule[:kind] == :retype }
89
-
90
- vanished = vanished_paths(held_attrs, current_attrs, retyped)
91
- appeared = appeared_paths(held_attrs, current_attrs, retyped)
106
+ end
92
107
 
108
+ def resolve_vanished_rules!(rules, vanished, appeared)
93
109
  vanished.each do |path, signature|
94
110
  matches = appeared.select { |_, candidate| candidate == signature }.keys
95
111
  taken = rules.filter_map { |rule| rule[:to] if %i[rename move].include?(rule[:kind]) }
96
112
  matches -= taken
97
- if matches.size == 1 && vanished.count { |_, other| other == signature } == 1
113
+ if matches.size == 1 && vanished.one? { |_, other| other == signature }
98
114
  target = matches.first
99
115
  kind = path.include?(".") || target.include?(".") ? :move : :rename
100
116
  rules << { kind: kind, from: path, to: target }
@@ -103,7 +119,6 @@ module Hecks
103
119
  rules << { kind: :unresolved, from: path, candidates: candidates }
104
120
  end
105
121
  end
106
- rules
107
122
  end
108
123
 
109
124
  # Held paths that need explaining: whole attributes that vanished,
@@ -10,12 +10,8 @@ module Hecks
10
10
  edge.aggregates.each do |aggregate|
11
11
  header = " aggregate #{aggregate.name.inspect}"
12
12
  header += ", was: #{aggregate.was.inspect}" if aggregate.was
13
- if aggregate.rules.empty?
14
- lines << "#{header} do"
15
- else
16
- lines << "#{header} do"
17
- aggregate.rules.each { |rule| lines << " #{render_rule(rule)}" }
18
- end
13
+ lines << "#{header} do"
14
+ aggregate.rules.each { |rule| lines << " #{render_rule(rule)}" } unless aggregate.rules.empty?
19
15
  lines << " end"
20
16
  end
21
17
  edge.retired.each { |name| lines << " retired #{name.inspect}" }
@@ -35,7 +35,9 @@ module Hecks
35
35
  # unchanged: registered only when this registry has an aggregate
36
36
  # actually bound to a lineage-capable adapter.
37
37
  def contribute_boot_gates(registry, gates)
38
- gates.register(:era_compute_rules, ->(reg, _dir) { Runtime::EraCheck.check_compute_rules_for_registry!(reg) }, phase: :pre_verify)
38
+ gates.register(:era_compute_rules, lambda { |reg, _dir|
39
+ Runtime::EraCheck.check_compute_rules_for_registry!(reg)
40
+ }, phase: :pre_verify)
39
41
  gates.register(:era_check, Runtime::EraCheck.method(:check_lineage!), phase: :pre_verify) if
40
42
  Runtime::EraCheck.lineage_capable_registry?(registry)
41
43
  end
@@ -33,7 +33,7 @@ module Hecks
33
33
  "#{self.class.name} is a remote-runtime delegate — dispatch through " \
34
34
  "Runtime::RemoteDispatcher instead of writing through the repository directly"
35
35
  end
36
- alias_method :project, :append
36
+ alias project append
37
37
 
38
38
  def entries = []
39
39
  end
@@ -1,9 +1,9 @@
1
1
  module Hecks
2
2
  module Ports
3
3
  module Persistence
4
- NAME = "persistence"
5
- VERB = "persisted_by"
6
- DEFAULT_ADAPTER = "Memory"
4
+ NAME = "persistence".freeze
5
+ VERB = "persisted_by".freeze
6
+ DEFAULT_ADAPTER = "Memory".freeze
7
7
  end
8
8
  end
9
9
  end
@@ -18,6 +18,10 @@ require_relative "persistence/null_saga_store"
18
18
 
19
19
  module Hecks
20
20
  module Ports
21
+ # Reopened once `BindingPolicy`/`RepositoryFactory` (required above) are
22
+ # loaded, to add the persistence port's actual call surface: resolving
23
+ # which adapter authoritatively binds an aggregate and building the
24
+ # repository that reads/writes it.
21
25
  module Persistence
22
26
  module_function
23
27
 
@@ -7,8 +7,8 @@ module Hecks
7
7
  # Projection is a read-side port. Its stores are rebuildable consumers of
8
8
  # authoritative entries and never participate in command decisions.
9
9
  module Projection
10
- NAME = "projection"
11
- VERB = "projected_by"
10
+ NAME = "projection".freeze
11
+ VERB = "projected_by".freeze
12
12
 
13
13
  module_function
14
14
 
@@ -26,6 +26,11 @@ module Hecks
26
26
  Worker.new(authoritative, target, policy: policy)
27
27
  end
28
28
 
29
+ # Catches one projection's store up to its authoritative journal:
30
+ # replays whatever entries the store hasn't yet appended/projected.
31
+ # Constructed via Projection.worker; #catch_up! runs from a
32
+ # separate process or scheduler, never from the command-side write
33
+ # path.
29
34
  class Worker
30
35
  attr_reader :projection
31
36
 
@@ -67,7 +72,10 @@ module Hecks
67
72
  unless consistent?(entries, present)
68
73
  raise Runtime::WiringError, "projection history does not match its authoritative history" if @policy == :strict
69
74
  end
70
- entries.drop(present.length).each { |entry| @projection.append(entry); @projection.project(entry) }
75
+ entries.drop(present.length).each do |entry|
76
+ @projection.append(entry)
77
+ @projection.project(entry)
78
+ end
71
79
  @projection
72
80
  end
73
81
 
@@ -78,9 +86,9 @@ module Hecks
78
86
  def same_entry?(left, right) = left.operation == right.operation && left.id == right.id && left.state == right.state
79
87
 
80
88
  def consistent?(entries, present)
81
- present.length <= entries.length && entries.first(present.length).zip(present).all? { |left, right|
89
+ present.length <= entries.length && entries.first(present.length).zip(present).all? do |left, right|
82
90
  same_entry?(left, right)
83
- }
91
+ end
84
92
  end
85
93
  end
86
94
 
@@ -6,6 +6,12 @@ require_relative "../../query_specification/common/comparison"
6
6
  module Hecks
7
7
  module Ports
8
8
  module Query
9
+ # In-memory implementation of a declared query specification:
10
+ # applies wheres/order_by/offset/limit directly to an Array of
11
+ # records instead of compiling SQL. Deliberately kept in exact
12
+ # LIMIT/OFFSET-order agreement with SqlQueryBuilder (see #execute's
13
+ # own comment) so a query answers the same page regardless of
14
+ # which adapter backs it.
9
15
  module InMemory
10
16
  FieldPath = QuerySpecification::FieldPath
11
17
  Comparison = QuerySpecification::Common::Comparison
@@ -14,9 +20,9 @@ module Hecks
14
20
 
15
21
  def execute(records, declared, args = {}, registry: nil)
16
22
  matched = records.select do |record|
17
- declared.wheres.all? { |clause|
23
+ declared.wheres.all? do |clause|
18
24
  holds?(clause, comparable(FieldPath.dig(record, clause.field)), args, registry: registry)
19
- }
25
+ end
20
26
  end
21
27
  field = declared.order_by&.field
22
28
  matched = Ordering.apply(matched, declared.order_by, declared.null_semantics,
@@ -19,9 +19,7 @@ module Hecks
19
19
  end
20
20
 
21
21
  def validate!(specification, adapter)
22
- if specification.cursor && specification.offset
23
- raise Unsupported, "a query cannot combine cursor and offset pagination"
24
- end
22
+ raise Unsupported, "a query cannot combine cursor and offset pagination" if specification.cursor && specification.offset
25
23
 
26
24
  return if specification.inspection.nil? || adapter.respond_to?(:inspect_query)
27
25
  return if specification.inspection.mode.to_s == "sql" && adapter.respond_to?(:query)
data/lib/hecks/ports.rb CHANGED
@@ -8,6 +8,8 @@
8
8
  # and the Folder adapter load as data.
9
9
 
10
10
  module Hecks
11
+ # Declared empty here — each `ports/*.rb` file required below reopens it
12
+ # to add its own port; see the header comment above for what a port is.
11
13
  module Ports
12
14
  end
13
15
  end
@@ -448,9 +448,17 @@ module Hecks
448
448
  # single source at all — its own field NAMES are the fact worth
449
449
  # stating (real: `Order.AddTopping` appends `name, amount`).
450
450
  def surface_diagram(bluebook, holder)
451
- lines = holder.commands.map { |command| " #{holder.hecks_name}[(#{holder.hecks_name})] -->|does| #{command_node(holder.hecks_name, command.hecks_name)}" }
452
- lines += holder.commands.flat_map { |command| command.mutations.map { |mutation| mutation_edge(holder, command, mutation) } }
453
- lines += holder.queries.map { |query| " #{holder.hecks_name}[(#{holder.hecks_name})] -.->|asks| #{query_node(holder.hecks_name, query.hecks_name)}" }
451
+ lines = holder.commands.map do |command|
452
+ " #{holder.hecks_name}[(#{holder.hecks_name})] -->|does| #{command_node(holder.hecks_name, command.hecks_name)}"
453
+ end
454
+ lines += holder.commands.flat_map do |command|
455
+ command.mutations.map do |mutation|
456
+ mutation_edge(holder, command, mutation)
457
+ end
458
+ end
459
+ lines += holder.queries.map do |query|
460
+ " #{holder.hecks_name}[(#{holder.hecks_name})] -.->|asks| #{query_node(holder.hecks_name, query.hecks_name)}"
461
+ end
454
462
 
455
463
  subject = "#{holder.hecks_name}'s own declared commands (and what each writes) and queries"
456
464
  "#{header(bluebook.name, subject)}flowchart LR\n#{lines.uniq.join("\n")}\n"