hecks 1.4.0 → 1.5.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 (267) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hecks/adapters/driven/claude_code.rb +65 -0
  3. data/lib/hecks/adapters/driven/folder.rb +73 -0
  4. data/lib/hecks/adapters/driven/google_authentication.rb +25 -4
  5. data/lib/hecks/adapters/driven/governance_authorization.rb +57 -6
  6. data/lib/hecks/adapters/driven/heki/journal.rb +8 -0
  7. data/lib/hecks/adapters/driven/heki/saga_store.rb +53 -7
  8. data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
  9. data/lib/hecks/adapters/driven/heki.rb +93 -2
  10. data/lib/hecks/adapters/driven/identity_registry.rb +10 -0
  11. data/lib/hecks/adapters/driven/in_memory_ordering.rb +22 -0
  12. data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
  13. data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
  14. data/lib/hecks/adapters/driven/lambda/client.rb +35 -7
  15. data/lib/hecks/adapters/driven/lambda.rb +43 -2
  16. data/lib/hecks/adapters/driven/local_storage.rb +67 -1
  17. data/lib/hecks/adapters/driven/memory.rb +13 -13
  18. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +20 -0
  19. data/lib/hecks/adapters/driven/prism.rb +46 -7
  20. data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
  21. data/lib/hecks/adapters/driven/system_clock.rb +3 -0
  22. data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
  23. data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
  24. data/lib/hecks/adapters/driven.rb +2 -0
  25. data/lib/hecks/adapters/driving/github_webhook.rb +14 -1
  26. data/lib/hecks/behaviors/dsl.rb +58 -0
  27. data/lib/hecks/behaviors/expectations.rb +168 -7
  28. data/lib/hecks/behaviors/ir.rb +11 -0
  29. data/lib/hecks/behaviors/rspec.rb +8 -0
  30. data/lib/hecks/behaviors/runner.rb +19 -0
  31. data/lib/hecks/behaviors.rb +8 -0
  32. data/lib/hecks/bluebook/aggregate.rb +30 -0
  33. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +7 -0
  34. data/lib/hecks/bluebook/assembly/build.rb +25 -4
  35. data/lib/hecks/bluebook/assembly/contract.rb +66 -14
  36. data/lib/hecks/bluebook/assembly/contracts.rb +24 -19
  37. data/lib/hecks/bluebook/assembly/marks.rb +143 -14
  38. data/lib/hecks/bluebook/assembly/specializer.rb +19 -0
  39. data/lib/hecks/bluebook/assembly.rb +25 -7
  40. data/lib/hecks/bluebook/attribute.rb +17 -3
  41. data/lib/hecks/bluebook/behaviour/aggregate.rb +32 -0
  42. data/lib/hecks/bluebook/behaviour/attribute.rb +13 -0
  43. data/lib/hecks/bluebook/behaviour/chapter.rb +55 -2
  44. data/lib/hecks/bluebook/behaviour/command.rb +33 -3
  45. data/lib/hecks/bluebook/behaviour/domain_port.rb +20 -0
  46. data/lib/hecks/bluebook/behaviour/entity.rb +12 -0
  47. data/lib/hecks/bluebook/behaviour/hexagon.rb +26 -0
  48. data/lib/hecks/bluebook/behaviour/lifecycle.rb +23 -2
  49. data/lib/hecks/bluebook/behaviour/policy.rb +34 -9
  50. data/lib/hecks/bluebook/behaviour/process_manager.rb +32 -1
  51. data/lib/hecks/bluebook/behaviour/query.rb +5 -0
  52. data/lib/hecks/bluebook/behaviour/read_model.rb +21 -0
  53. data/lib/hecks/bluebook/behaviour/traits.rb +36 -0
  54. data/lib/hecks/bluebook/behaviour/value_object.rb +17 -5
  55. data/lib/hecks/bluebook/chapter.rb +23 -0
  56. data/lib/hecks/bluebook/command.rb +53 -8
  57. data/lib/hecks/bluebook/domain_port.rb +25 -0
  58. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +196 -37
  59. data/lib/hecks/bluebook/dsl/attribute_collector.rb +79 -42
  60. data/lib/hecks/bluebook/dsl/binding_proxy.rb +59 -0
  61. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +385 -53
  62. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +135 -21
  63. data/lib/hecks/bluebook/dsl/command_builder.rb +189 -27
  64. data/lib/hecks/bluebook/dsl/entity_builder.rb +139 -9
  65. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +41 -0
  66. data/lib/hecks/bluebook/dsl/identity_declaration.rb +23 -6
  67. data/lib/hecks/bluebook/dsl/policy_builder.rb +60 -19
  68. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +79 -15
  69. data/lib/hecks/bluebook/dsl/query_builder.rb +33 -4
  70. data/lib/hecks/bluebook/dsl/read_model_builder.rb +102 -27
  71. data/lib/hecks/bluebook/dsl/rule_reference.rb +60 -8
  72. data/lib/hecks/bluebook/dsl/translation_builder.rb +146 -40
  73. data/lib/hecks/bluebook/dsl/value_object_builder.rb +55 -7
  74. data/lib/hecks/bluebook/entity.rb +29 -0
  75. data/lib/hecks/bluebook/expression/ast_json.rb +115 -23
  76. data/lib/hecks/bluebook/expression/ast_reader.rb +29 -0
  77. data/lib/hecks/bluebook/expression/canonical_form.rb +48 -9
  78. data/lib/hecks/bluebook/expression/evaluator.rb +207 -29
  79. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +36 -0
  80. data/lib/hecks/bluebook/expression/resolver.rb +320 -77
  81. data/lib/hecks/bluebook/hexagon.rb +34 -0
  82. data/lib/hecks/bluebook/lifecycle.rb +11 -0
  83. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -0
  84. data/lib/hecks/bluebook/meta_validator/judge.rb +30 -26
  85. data/lib/hecks/bluebook/meta_validator/plan.rb +43 -8
  86. data/lib/hecks/bluebook/meta_validator/port_judge.rb +1 -0
  87. data/lib/hecks/bluebook/meta_validator/readings.rb +157 -7
  88. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -4
  89. data/lib/hecks/bluebook/meta_validator/shapes.rb +141 -0
  90. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +149 -23
  91. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +5 -4
  92. data/lib/hecks/bluebook/meta_validator/world_judge.rb +1 -0
  93. data/lib/hecks/bluebook/meta_validator.rb +180 -84
  94. data/lib/hecks/bluebook/model_check.rb +268 -24
  95. data/lib/hecks/bluebook/pattern_subset.rb +23 -1
  96. data/lib/hecks/bluebook/process_manager.rb +13 -0
  97. data/lib/hecks/bluebook/project_discovery.rb +5 -0
  98. data/lib/hecks/bluebook/project_loader.rb +40 -0
  99. data/lib/hecks/bluebook/project_register.rb +44 -0
  100. data/lib/hecks/bluebook/query.rb +27 -0
  101. data/lib/hecks/bluebook/read_model.rb +21 -1
  102. data/lib/hecks/bluebook/reference.rb +21 -8
  103. data/lib/hecks/bluebook/smoke_test.rb +29 -6
  104. data/lib/hecks/bluebook/synthesizer.rb +34 -0
  105. data/lib/hecks/bluebook/translation.rb +30 -1
  106. data/lib/hecks/bluebook/value_object.rb +23 -5
  107. data/lib/hecks/bluebook.rb +3 -4
  108. data/lib/hecks/codemod.rb +107 -20
  109. data/lib/hecks/construct.rb +15 -1
  110. data/lib/hecks/corpus.rb +146 -25
  111. data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
  112. data/lib/hecks/doc/reference.rb +185 -16
  113. data/lib/hecks/embryonaut_bluebook.rb +32 -9
  114. data/lib/hecks/facade/handle.rb +76 -3
  115. data/lib/hecks/facade/surface/aggregate_door.rb +8 -0
  116. data/lib/hecks/forms/field_shape.rb +3 -0
  117. data/lib/hecks/forms/page.rb +14 -0
  118. data/lib/hecks/forms/port_argument.rb +12 -0
  119. data/lib/hecks/forms/query_form_renderer.rb +63 -0
  120. data/lib/hecks/forms/record_renderer.rb +58 -0
  121. data/lib/hecks/forms/record_table.rb +27 -0
  122. data/lib/hecks/forms/reference_options.rb +24 -0
  123. data/lib/hecks/forms/value_object_shape.rb +10 -0
  124. data/lib/hecks/fqn.rb +58 -0
  125. data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
  126. data/lib/hecks/framework/oidc.json +15 -0
  127. data/lib/hecks/framework.rb +43 -20
  128. data/lib/hecks/freezer.rb +17 -1
  129. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +159 -24
  130. data/lib/hecks/fuzzing/combination_miner.rb +59 -0
  131. data/lib/hecks/fuzzing/concurrent_dispatch.rb +109 -8
  132. data/lib/hecks/fuzzing/coverage_campaign.rb +56 -13
  133. data/lib/hecks/fuzzing/differential.rb +34 -0
  134. data/lib/hecks/fuzzing/domain_generator.rb +188 -11
  135. data/lib/hecks/fuzzing/era_boundary.rb +45 -15
  136. data/lib/hecks/fuzzing/form_census.rb +86 -0
  137. data/lib/hecks/fuzzing/generated_domain_check.rb +76 -0
  138. data/lib/hecks/fuzzing/invalid_value_generator.rb +39 -0
  139. data/lib/hecks/fuzzing/isolated_boot.rb +79 -22
  140. data/lib/hecks/fuzzing/nondeterministic.rb +13 -1
  141. data/lib/hecks/fuzzing/persistence_parity.rb +95 -3
  142. data/lib/hecks/fuzzing/properties/corrections.rb +25 -0
  143. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +158 -14
  144. data/lib/hecks/fuzzing/properties/guards.rb +44 -0
  145. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +48 -0
  146. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +18 -0
  147. data/lib/hecks/fuzzing/properties/outbox.rb +49 -11
  148. data/lib/hecks/fuzzing/properties/querying.rb +68 -14
  149. data/lib/hecks/fuzzing/properties.rb +24 -15
  150. data/lib/hecks/fuzzing/qa_settings.rb +12 -0
  151. data/lib/hecks/fuzzing/replay.rb +137 -29
  152. data/lib/hecks/fuzzing/rotation_priority.rb +41 -21
  153. data/lib/hecks/fuzzing/rust_gap_manifest.rb +46 -20
  154. data/lib/hecks/fuzzing/self_consistency.rb +189 -40
  155. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +12 -6
  156. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +1 -1
  157. data/lib/hecks/fuzzing/sequence_generator.rb +47 -0
  158. data/lib/hecks/fuzzing/shrinker.rb +114 -2
  159. data/lib/hecks/fuzzing/structural_skips.rb +18 -4
  160. data/lib/hecks/fuzzing/sweep_depth.rb +8 -0
  161. data/lib/hecks/fuzzing/target_capabilities.rb +61 -14
  162. data/lib/hecks/fuzzing/value_generator.rb +98 -10
  163. data/lib/hecks/grammar/evolve.rb +178 -2
  164. data/lib/hecks/grammar.rb +46 -0
  165. data/lib/hecks/ir.rb +38 -7
  166. data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
  167. data/lib/hecks/literal.rb +32 -0
  168. data/lib/hecks/naming.rb +88 -7
  169. data/lib/hecks/ports/access_control.rb +5 -10
  170. data/lib/hecks/ports/authorization.rb +3 -6
  171. data/lib/hecks/ports/identity_assignment.rb +1 -2
  172. data/lib/hecks/ports/identity_resolution.rb +1 -2
  173. data/lib/hecks/ports/key_vault.port +6 -0
  174. data/lib/hecks/ports/key_vault.rb +58 -0
  175. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +6 -0
  176. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +38 -2
  177. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -0
  178. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +40 -0
  179. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +92 -1
  180. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
  181. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +8 -0
  182. data/lib/hecks/ports/query/in_memory.rb +39 -6
  183. data/lib/hecks/ports/query/ordering.rb +15 -0
  184. data/lib/hecks/ports.rb +1 -0
  185. data/lib/hecks/projections/bootstrap_table.rb +43 -8
  186. data/lib/hecks/projections/diagrams.rb +243 -7
  187. data/lib/hecks/projections/glossary/html.rb +88 -0
  188. data/lib/hecks/projections/glossary/markdown.rb +47 -0
  189. data/lib/hecks/projections/glossary/mermaid.rb +48 -0
  190. data/lib/hecks/projections/glossary/sentences.rb +105 -0
  191. data/lib/hecks/projections/glossary.rb +161 -19
  192. data/lib/hecks/projections/model/deviations.rb +44 -0
  193. data/lib/hecks/projections/model.rb +51 -1
  194. data/lib/hecks/projections/oidc.rb +40 -3
  195. data/lib/hecks/projections/parser_table.rb +49 -8
  196. data/lib/hecks/projections/reference.rb +12 -0
  197. data/lib/hecks/projections/rust_vocabulary.rb +219 -16
  198. data/lib/hecks/projections/shape.rb +8 -0
  199. data/lib/hecks/projections/statements.rb +63 -16
  200. data/lib/hecks/projections/vocabulary.rb +17 -0
  201. data/lib/hecks/projector/cli_projector.rb +218 -10
  202. data/lib/hecks/projector/docs_projector.rb +145 -19
  203. data/lib/hecks/projector/exporter.rb +65 -11
  204. data/lib/hecks/projector/ir_projector.rb +6 -0
  205. data/lib/hecks/projector/narrate_projector.rb +136 -15
  206. data/lib/hecks/projector/target.rb +29 -5
  207. data/lib/hecks/projector.rb +74 -6
  208. data/lib/hecks/query_ir.rb +47 -0
  209. data/lib/hecks/query_specification/common/null_policy.rb +5 -3
  210. data/lib/hecks/rendering.rb +6 -0
  211. data/lib/hecks/router/namespace_installer.rb +13 -0
  212. data/lib/hecks/router.rb +55 -0
  213. data/lib/hecks/runtime/aggregate_lock.rb +9 -0
  214. data/lib/hecks/runtime/boot_gates.rb +18 -0
  215. data/lib/hecks/runtime/caller.rb +32 -0
  216. data/lib/hecks/runtime/capability_graph.rb +11 -0
  217. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +23 -21
  218. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +14 -15
  219. data/lib/hecks/runtime/command_interpreter.rb +42 -17
  220. data/lib/hecks/runtime/command_rules/admissibility.rb +165 -14
  221. data/lib/hecks/runtime/command_rules/arithmetic.rb +17 -5
  222. data/lib/hecks/runtime/command_rules/references.rb +118 -28
  223. data/lib/hecks/runtime/dependency_planning.rb +45 -0
  224. data/lib/hecks/runtime/dispatcher.rb +28 -50
  225. data/lib/hecks/runtime/entity_element.rb +161 -8
  226. data/lib/hecks/runtime/entity_interpreter.rb +44 -9
  227. data/lib/hecks/runtime/errors.rb +18 -4
  228. data/lib/hecks/runtime/event.rb +10 -5
  229. data/lib/hecks/runtime/identity.rb +71 -3
  230. data/lib/hecks/runtime/instance.rb +67 -7
  231. data/lib/hecks/runtime/interpreting.rb +13 -5
  232. data/lib/hecks/runtime/invocation.rb +118 -36
  233. data/lib/hecks/runtime/loader.rb +94 -8
  234. data/lib/hecks/runtime/outbox.rb +145 -7
  235. data/lib/hecks/runtime/policy_interpreter.rb +22 -9
  236. data/lib/hecks/runtime/port_operation_interpreter.rb +20 -0
  237. data/lib/hecks/runtime/query_interpreter.rb +40 -12
  238. data/lib/hecks/runtime/reaction_invocation.rb +53 -8
  239. data/lib/hecks/runtime/read_model_interpreter.rb +23 -7
  240. data/lib/hecks/runtime/rebuild_sweep.rb +28 -0
  241. data/lib/hecks/runtime/reference_hop.rb +42 -0
  242. data/lib/hecks/runtime/refusal_wording.rb +50 -0
  243. data/lib/hecks/runtime/registry/saga_persistence.rb +11 -0
  244. data/lib/hecks/runtime/registry/verification.rb +119 -4
  245. data/lib/hecks/runtime/registry.rb +157 -4
  246. data/lib/hecks/runtime/remote_dispatcher.rb +92 -6
  247. data/lib/hecks/runtime/routing.rb +27 -2
  248. data/lib/hecks/runtime/saga_interpreter/correlation.rb +10 -12
  249. data/lib/hecks/runtime/saga_interpreter.rb +27 -13
  250. data/lib/hecks/runtime/tenant_check.rb +26 -6
  251. data/lib/hecks/runtime/tenant_scope.rb +18 -0
  252. data/lib/hecks/runtime/value/coercion.rb +255 -33
  253. data/lib/hecks/runtime/value/entity_list_coercion.rb +102 -30
  254. data/lib/hecks/runtime/value.rb +50 -0
  255. data/lib/hecks/runtime.rb +32 -0
  256. data/lib/hecks/storehouse.rb +305 -9
  257. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
  258. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
  259. data/lib/hecks/version.rb +1 -1
  260. data/lib/hecks.rb +79 -1
  261. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -0
  262. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +12 -2
  263. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +29 -5
  264. metadata +11 -5
  265. data/lib/hecks/codemod/legacy_dispatch_args.rb +0 -299
  266. data/lib/hecks/codemod/legacy_dispatch_recorder.rb +0 -186
  267. data/lib/hecks/deprecation.rb +0 -95
@@ -135,6 +135,47 @@ module Hecks
135
135
  bluebook_ir.add_port(built)
136
136
  end
137
137
 
138
+ # A TRANSLATION BOUNDARY, NOT A BUSINESS RULE — `translates "Name"
139
+ # do on Foreign::Domain::SomeEvent; trigger Local.Command; end`
140
+ # builds the EXACT SAME `Policy` a `policy` block inside this
141
+ # domain's own `.bluebook` would (same `on_impl`/`trigger_impl`,
142
+ # same `PolicyInterpreter` runtime — reusing `PolicyBuilder`
143
+ # directly, zero new runtime semantics). What's new is only WHERE
144
+ # it can be written: a cross-domain reaction is a wiring/context-
145
+ # mapping decision (this chapter conforming to a foreign chapter's
146
+ # published event), the same kind of decision `port`/
147
+ # `uses_framework` already are — not a fact this domain's own
148
+ # model states about itself, which `policy` (inside the bluebook)
149
+ # remains the right word for.
150
+ #
151
+ # MUST BE A BLOCK, not `translates Event, into: Command` — a flat
152
+ # call's arguments are evaluated eagerly, under THIS builder's own
153
+ # `ConstShim` resolver (`BindingProxy.namespace`, set up by
154
+ # `self.build` below), which mints a `BindingProxy` INSTANCE per
155
+ # segment and cannot answer a further `::` (it is not a Module) —
156
+ # a multi-segment reference like `Deploy::Tenant::TenantProvisioned`
157
+ # would raise before this method ever ran. `port_impl`'s own
158
+ # `ConstShim.with(->(const) { const })` swap only works because
159
+ # its own callers never pass a multi-segment bare constant as an
160
+ # eagerly-evaluated argument — everything of that shape lives
161
+ # inside ITS block, evaluated later, under the swapped resolver.
162
+ # A block defers evaluation the same way; a flat call cannot.
163
+ def translates(name, &block)
164
+ bluebook_ir = Hecks.current_registry.bluebook(@domain) or
165
+ raise Malformed, "#{@domain} declares no such bluebook — translates needs one to attach its reaction to"
166
+
167
+ # SAME RESOLVER BluebookBuilder ITSELF uses for a `policy`
168
+ # block's own `on`/`trigger` (bluebook_builder.rb) — a
169
+ # `ScopedConstant`, not the bare passthrough `port_impl` swaps
170
+ # to, because `on`/`trigger` here take genuinely multi-segment
171
+ # references (`Deploy::Tenant::TenantProvisioned`), not a
172
+ # single-segment type name.
173
+ resolver = ->(const) { ConstShim::ScopedConstant.for(const) }
174
+ built = ConstShim.with(resolver) { PolicyBuilder.build(name, &block) }
175
+
176
+ bluebook_ir.add_policy(built)
177
+ end
178
+
138
179
  # Assembles the collected binds, subscriptions and attachments into a `Hecksagon`.
139
180
  #
140
181
  # No ungoverned-role check here — see
@@ -23,6 +23,9 @@ module Hecks
23
23
  # against: AggregateBuilder's own `@value_objects + closed_sets`,
24
24
  # or EntityBuilder's owner's, since a piece mints none of its own.
25
25
  module IdentityDeclaration
26
+ # Records which of the three live `identified_by` forms a builder declared, deferring
27
+ # resolution until build time.
28
+ #
26
29
  # There are three live forms, deliberately distinguishable at the
27
30
  # declaration site:
28
31
  #
@@ -33,12 +36,12 @@ module Hecks
33
36
  # One symbol is retired: it cannot say whether the author means a
34
37
  # value concept or a field-shaped database key. Frozen source still
35
38
  # reaches the old interpretation through `legacy_identified_by`.
36
- # Renamed from `identified_by` — item #13's full metaprogrammed
37
- # dispatch (slice 4c), same shared-mixin shape `attribute_impl`
38
- # already proved in slice 3: one renamed method, both Aggregate
39
- # and Entity Keyword rows name it in `calls:`. Bootstrap-
40
- # reachable (every self-hosted aggregate/entity declares an
41
- # identity), so in BOOTSTRAP_CALLS_FALLBACK for both contexts.
39
+ # Both `AggregateBuilder` and `EntityBuilder` name this method in their Keyword rows'
40
+ # `calls:` column, so `GenericDispatch` forwards `identified_by` here unchanged, both
41
+ # after boot (reading the live grammar table) and during it
42
+ # (`GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`, which carries every `calls:`-routed row
43
+ # unconditionally, not a hand-picked bootstrap-reachable subset — though this one is
44
+ # exercised during boot too, since every self-hosted aggregate/entity declares an identity).
42
45
  # Dispatches across the three live forms documented above (value-
43
46
  # object + block, single type target, single/compound field
44
47
  # target), each an early return that sets exactly one pending
@@ -46,6 +49,20 @@ module Hecks
46
49
  # with-nil semantics and the shared `@name`/`identity_pool`
47
50
  # threaded back out as parameters, for no gain beyond what the
48
51
  # three-forms comment above already documents.
52
+ #
53
+ # @param targets [Array<Symbol, Module>] zero or more identity targets: a value-object
54
+ # type (a bareword, resolved by `ConstShim` to a `Module`), one or more attribute-name
55
+ # symbols for a field or compound key, or empty when `definition` is given
56
+ # @param as [Symbol, nil] the field name to mint for a value-object-type target; not
57
+ # accepted with a field or compound-key target
58
+ # @yield a bespoke value-object body, `instance_eval`'d by `ValueObjectBuilder.build` to
59
+ # mint the identity's own anonymous value object
60
+ # @return [Array<Symbol>, Array<String>, nil] `targets` when a compound key is declared,
61
+ # the resolved identity paths from `legacy_identified_by` in shadow-parsing mode, or
62
+ # `nil` otherwise; callers reach this through `GenericDispatch`, which discards it
63
+ # @raise [Bluebook::DSL::Malformed] if `identified_by` was already called, combines a
64
+ # block with targets, names no identity, mixes a value-object type with field names,
65
+ # passes `as:` with a field or compound key, or the block declares no attributes
49
66
  # rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
50
67
  def identified_by_impl(*targets, as: nil, &definition)
51
68
  return legacy_identified_by(*targets, as: as, &definition) if MetaValidator.shadow_parsing?
@@ -13,10 +13,13 @@ module Hecks
13
13
 
14
14
  include WordGate
15
15
 
16
+ # @param name [String] the policy's own name, as given to `policy "Name" do ... end`
16
17
  def initialize(name)
17
18
  @name = name
18
19
  end
19
20
 
21
+ # Records the event this policy reacts to.
22
+ #
20
23
  # `on Account::AccountFrozen` — a bare constant accepted (ADR
21
24
  # 0025, S6 — "events first-class"), resolved through `ConstShim`
22
25
  # the same way `trigger`/`dispatch` already resolve a command
@@ -28,17 +31,22 @@ module Hecks
28
31
  # AccountFrozen"` (quoted) and `on Account::AccountFrozen`
29
32
  # (bare) stay admitted until a full migration lands.
30
33
  #
31
- # Renamed from the generic single-fill coercion — item #13's
32
- # full metaprogrammed dispatch, slice 1 (whole-project
33
- # table-unification survey), now overridden here the same way
34
- # `trigger_impl` overrides its own generic default.
34
+ # Reached through `calls: "on_impl"` rather than `GenericDispatch`'s generic single-fill
35
+ # coercion, the same way `trigger_impl` below overrides its own generic default — `on`
36
+ # admits both a bare constant and quoted text, not one single argument kind.
37
+ #
38
+ # @param event_ref [Symbol, String, Module] the event, as a bare constant (a
39
+ # `ScopedConstant` module `ConstShim` resolves) or quoted text
40
+ # @return [void]
35
41
  def on_impl(event_ref)
36
42
  @on_event = Naming.event_ref(event_ref)
37
43
  end
38
44
 
45
+ # Records the command this policy dispatches and, optionally, how the event's payload
46
+ # projects onto it.
47
+ #
39
48
  # `with:` — what the trigger is given, when the event's own shape
40
- # is not it. Omitted, the whole event payload forwards verbatim,
41
- # which is what every policy did before this existed.
49
+ # is not it. Omitted, the whole event payload forwards verbatim.
42
50
  #
43
51
  # Same `key => value` shape a saga's own `dispatch ..., with:`
44
52
  # takes, and read the same way at runtime: a Symbol names a field
@@ -53,21 +61,28 @@ module Hecks
53
61
  # reactions" — command references become first-class): `trigger
54
62
  # Account::Debit`, a bare constant `ConstShim` resolves the same
55
63
  # way `reference_to Account` always has, not a quoted verb string.
56
- # Collapses the qualified/unqualified split this word and a
57
- # saga's own `dispatch` used to disagree about — see
58
- # `Naming.command_ref`'s own header for how the `::`/`.` rewrite
59
- # works, and `SagaInterpreter#qualified` for why an unqualified
60
- # form has always been enough (same-domain is the fallback, so
61
- # `Account::Debit` and `Banking::Account::Debit` mean the same
62
- # thing here).
64
+ # Matches the qualified-or-not shape a saga's own `dispatch`
65
+ # command name takes — see `Naming.command_ref`'s own header for
66
+ # how the `::`/`.` rewrite works, and `SagaInterpreter#qualified`
67
+ # for why an unqualified form is always enough (same-domain is
68
+ # the fallback, so `Account::Debit` and `Banking::Account::Debit`
69
+ # mean the same thing here).
63
70
  #
64
71
  # Legacy under shadow-parsing (S0a's own bridge) — frozen era
65
72
  # text still writes the quoted form.
66
73
  #
67
- # Renamed from `trigger` — item #13's full metaprogrammed
68
- # dispatch (slice 4), same reasoning as has_many_impl above: not
69
- # bootstrap-reachable, reached through calls: with no fallback
70
- # needed.
74
+ # `["Policy", "trigger"] => :trigger_impl` is in
75
+ # `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`, which carries every `calls:`-routed row
76
+ # unconditionally — this one is also exercised during boot itself, since every
77
+ # self-hosted policy declares a trigger.
78
+ #
79
+ # @param command_ref [Symbol, String, Module] the command, as a bare constant (a
80
+ # `ScopedConstant` module `ConstShim` resolves) or, under shadow-parsing, quoted text
81
+ # @param with [Hash{Symbol => Object}, nil] a projection from the triggering event's payload
82
+ # onto the command's own arguments; a Symbol value names an event field, anything else is
83
+ # a literal; `nil` forwards the whole event payload verbatim
84
+ # @return [void]
85
+ # @raise [Bluebook::DSL::Malformed] if `command_ref` is quoted text outside shadow-parsing
71
86
  def trigger_impl(command_ref, with: nil)
72
87
  if command_ref.is_a?(::String) && !MetaValidator.shadow_parsing?
73
88
  raise Malformed,
@@ -80,17 +95,28 @@ module Hecks
80
95
  @projection_declared = !with.nil?
81
96
  end
82
97
 
98
+ # Records the domain this policy's trigger reaches into.
99
+ #
83
100
  # `across "Notifications"` names the domain a trigger reaches into.
84
101
  # `expect_undelivered: true` declares that this domain expects that
85
102
  # target never to be reached (no such domain, on purpose), which
86
103
  # `ModelCheck` holds it to in both directions. Reached through
87
- # `calls:` since it gained the named flag — the generic single-fill
88
- # coercion takes no keyword arguments.
104
+ # `calls: "across_impl"` rather than the generic single-fill
105
+ # coercion, because `expect_undelivered:` is a keyword argument the
106
+ # generic coercion cannot take.
107
+ #
108
+ # @param domain [String, Symbol] the target domain's name
109
+ # @param expect_undelivered [Boolean] whether this domain expects `across`'s target to
110
+ # name no real domain; `ModelCheck` holds it to that in both directions
111
+ # @return [void]
89
112
  def across_impl(domain, expect_undelivered: false)
90
113
  @target_domain = domain.to_s
91
114
  @expect_undelivered = expect_undelivered == true
92
115
  end
93
116
 
117
+ # Records the guard predicate that decides whether this policy applies to a triggering
118
+ # event.
119
+ #
94
120
  # **The guard** — same extraction CommandBuilder#given/#ensures already
95
121
  # use (Ports::Extraction reads the block's source ; the block itself
96
122
  # is never called, here or at runtime — Runtime::PolicyInterpreter
@@ -105,6 +131,12 @@ module Hecks
105
131
  # Evaluated against the triggering event's own payload, not a
106
132
  # stored record — a policy reacts to what just happened, and has no
107
133
  # aggregate instance of its own to read state from.
134
+ #
135
+ # @yield the guard predicate, extracted as source text and evaluated later by
136
+ # `Runtime::PolicyInterpreter` against the triggering event's payload; never called here
137
+ # @return [void]
138
+ # @raise [Bluebook::DSL::Malformed] if the block's source could not be extracted, or if it
139
+ # references a pattern `Expression::AstJson.refuse_unshared_patterns!` does not share
108
140
  def where(&predicate)
109
141
  canonical = Ports::Extraction.canonical(predicate)
110
142
 
@@ -129,6 +161,10 @@ module Hecks
129
161
  # `for_each` — item #13's full metaprogrammed dispatch, slice 1:
130
162
  # same shape as `on`, above.
131
163
 
164
+ # Builds the `Policy` this builder has accumulated.
165
+ #
166
+ # @return [Bluebook::Policy] the built policy, carrying every field set by `on`,
167
+ # `trigger`, `across`, `where`, `for_each`, and `with:`
132
168
  def build
133
169
  Policy.new(
134
170
  name: @name,
@@ -142,6 +178,11 @@ module Hecks
142
178
  ).tap { |policy| policy.instance_variable_set(:@projection_declared, !!@projection_declared) }
143
179
  end
144
180
 
181
+ # Builds a `Policy` from a `policy "Name" do ... end` block.
182
+ #
183
+ # @param name [String] the policy's own name
184
+ # @yield the policy's body, `instance_eval`'d against a new builder
185
+ # @return [Bluebook::Policy] the built policy
145
186
  def self.build(name, &block)
146
187
  builder = new(name)
147
188
  builder.instance_eval(&block) if block
@@ -16,11 +16,14 @@ module Hecks
16
16
 
17
17
  include WordGate
18
18
 
19
+ # @param name [String] the process manager's own name, as written after `process_manager`
19
20
  def initialize(name)
20
21
  @name = name
21
22
  @handlers = []
22
23
  end
23
24
 
25
+ # Records the event that begins a fresh instance of this process manager.
26
+ #
24
27
  # `starts_on Transfer::TransferRequested` — bare constant
25
28
  # accepted (ADR 0025, S6 — "events first-class"), resolved
26
29
  # through `ConstShim` the same way `on_impl`/`transition_impl`
@@ -31,11 +34,21 @@ module Hecks
31
34
  # a "." qualified one). A plain String still passes through
32
35
  # unchanged, both for `shadow_parse` and for any corpus site a
33
36
  # future pass hasn't migrated yet.
37
+ #
38
+ # @param event_ref [Symbol, String, Module] the event, as a bare constant (a
39
+ # `ScopedConstant` module `ConstShim` resolves) or quoted text
40
+ # @return [void]
34
41
  def starts_on_impl(event_ref)
35
42
  @starts_on = Naming.event_name_ref(event_ref)
36
43
  end
37
44
 
45
+ # Records the event this process manager expects to close out its instance on.
46
+ #
38
47
  # `ends_on` — same reasoning as `starts_on_impl`, above.
48
+ #
49
+ # @param event_ref [Symbol, String, Module] the event, as a bare constant (a
50
+ # `ScopedConstant` module `ConstShim` resolves) or quoted text
51
+ # @return [void]
39
52
  def ends_on_impl(event_ref)
40
53
  @ends_on = Naming.event_name_ref(event_ref)
41
54
  end
@@ -45,15 +58,18 @@ module Hecks
45
58
  # kind-driven coerce-and-assign with nothing else, executed by
46
59
  # `GenericDispatch`.
47
60
  #
48
- # `starts_on`/`ends_on` used to be table-driven the same way,
49
- # until ADR 0025 S6 gave each a second, `kind: "constant"`
50
- # ArgumentSeed row (`starts_on Transfer::TransferRequested`) —
51
- # `GenericDispatch::COERCE_BY_KIND` only knows `"text"`/
52
- # `"symbol"`, and `shape_for` refuses to pick a `single_fill`
53
- # shape at all once two Argument rows share a `fills:` target
54
- # (`arguments.size == 1` below), so both words are hand-written
55
- # again, `calls:`-routed like `transition` already is.
56
-
61
+ # `starts_on`/`ends_on` are hand-written rather than table-driven
62
+ # the same way, because ADR 0025 S6 gives each a second,
63
+ # `kind: "constant"` ArgumentSeed row (`starts_on
64
+ # Transfer::TransferRequested`) — `GenericDispatch::COERCE_BY_KIND`
65
+ # only knows `"text"`/`"symbol"`, and `shape_for` refuses to pick a
66
+ # `single_fill` shape at all once two Argument rows share a
67
+ # `fills:` target (`arguments.size == 1` below), so both words are
68
+ # `calls:`-routed like `transition` already is.
69
+
70
+ # Records one leg of this process manager's state machine: the event that takes it, the
71
+ # state(s) it applies from, and the dispatches (and optional compensation) it fires.
72
+ #
57
73
  # One state-machine vocabulary (S7, ADR 0025 — "events and
58
74
  # reactions"): the same word `Lifecycle#transition` already
59
75
  # carries, one level over — `transition "AccountDebited" =>
@@ -89,10 +105,18 @@ module Hecks
89
105
  # persistence/rehydration) needs no change at all: what changed
90
106
  # is how the declaration reaches that same shape, not the shape
91
107
  # a real run ever sees or persists.
92
- # Renamed from `transition` — item #13's full metaprogrammed
93
- # dispatch (slice 4c). Not bootstrap-reachable (checked
94
- # directly — no core/attached chapter declares a ProcessManager
95
- # of its own).
108
+ # Reached through `calls: "transition_impl"`. Not exercised during boot itself — checked
109
+ # directly, no core/attached chapter declares a `ProcessManager` of its own — though the
110
+ # (context, word) pair is still carried in `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`
111
+ # unconditionally, along with every other `calls:`-routed row.
112
+ #
113
+ # @param mapping [Hash] one `event => target_state` pair plus a required `from:` key
114
+ # naming the source state(s) (`String`, `Symbol`, or `Array<String, Symbol>`) this
115
+ # transition applies from
116
+ # @yield the transition's dispatch body, `instance_eval`'d against a fresh `HandlerBuilder`
117
+ # @return [void]
118
+ # @raise [Bluebook::DSL::ProcessManagerBuilder::InvalidProcessManager] if `mapping` names
119
+ # no `from:`
96
120
  def transition_impl(mapping, &block)
97
121
  mapping = mapping.dup
98
122
  from = mapping.delete(:from)
@@ -142,6 +166,14 @@ module Hecks
142
166
  end
143
167
  end
144
168
 
169
+ # Assembles the declared transitions into a `ProcessManager`, after validating them.
170
+ #
171
+ # @return [Bluebook::ProcessManager] the built process manager, with its own `states`
172
+ # derived from the declared transitions
173
+ # @raise [Bluebook::DSL::ProcessManagerBuilder::InvalidProcessManager] if `correlates_by`
174
+ # is undeclared or names a whole field rather than one scalar, if `starts_on` is
175
+ # undeclared, if no transition was declared, or if two transitions answer the same event
176
+ # from the same source state
145
177
  def build
146
178
  validate!
147
179
 
@@ -155,6 +187,13 @@ module Hecks
155
187
  )
156
188
  end
157
189
 
190
+ # Evaluates a `process_manager` block against a fresh builder and returns what it built.
191
+ #
192
+ # @param name [String] the process manager's own name
193
+ # @yield the process manager's body, `instance_eval`'d against a new builder; may be
194
+ # omitted
195
+ # @return [Bluebook::ProcessManager] the built process manager
196
+ # @raise [Bluebook::DSL::ProcessManagerBuilder::InvalidProcessManager] see `#build`
158
197
  def self.build(name, &block)
159
198
  builder = new(name)
160
199
  builder.instance_eval(&block) if block
@@ -264,16 +303,19 @@ module Hecks
264
303
 
265
304
  include WordGate
266
305
 
306
+ # Starts this handler's own dispatch list empty.
267
307
  def initialize = @dispatches = []
268
308
 
309
+ # Records one command this transition dispatches, and, if given a block, the
310
+ # compensation that reverses it.
311
+ #
269
312
  # The command itself (ADR 0025, "events and reactions" — command
270
313
  # references become first-class), same shape and same reasons
271
314
  # as `PolicyBuilder#trigger`'s own header — bare constant live,
272
315
  # quoted text only under shadow-parsing (S0a's bridge; frozen
273
316
  # era text still writes `dispatch "Banking::Account.Debit"`).
274
317
  #
275
- # Renamed from `dispatch` — item #13's full metaprogrammed
276
- # dispatch (slice 4), same reasoning as trigger_impl above.
318
+ # Reached through `calls: "dispatch_impl"`.
277
319
  #
278
320
  # An optional block opens `compensates` on this dispatch
279
321
  # specifically — per-dispatch saga compensation, replacing a
@@ -287,6 +329,17 @@ module Hecks
287
329
  # (`SagaInterpreter`'s own `completed_compensations`) and
288
330
  # compensates only those, newest first, instead of trusting an
289
331
  # author's static list to be complete and correctly ordered.
332
+ #
333
+ # @param command_ref [Symbol, String, Module] the command, as a bare constant (a
334
+ # `ScopedConstant` module `ConstShim` resolves) or, under shadow-parsing, quoted text
335
+ # @param with [Hash{Symbol => Object}, nil] a projection onto the command's own
336
+ # arguments, the same `key => value` shape a policy's own `trigger ..., with:` takes;
337
+ # `nil` forwards the triggering context verbatim
338
+ # @yield the dispatch's own `compensates` body, `instance_eval`'d against a
339
+ # `DispatchBuilder`
340
+ # @return [Bluebook::DispatchSpec] the dispatch just recorded
341
+ # @raise [Bluebook::DSL::ProcessManagerBuilder::InvalidProcessManager] if `command_ref`
342
+ # is quoted text outside shadow-parsing
290
343
  def dispatch_impl(command_ref, with: nil, &block)
291
344
  if command_ref.is_a?(::String) && !MetaValidator.shadow_parsing?
292
345
  raise InvalidProcessManager,
@@ -330,6 +383,17 @@ module Hecks
330
383
 
331
384
  include WordGate
332
385
 
386
+ # Records the command that reverses the dispatch this `compensates` block sits inside.
387
+ #
388
+ # @param command_ref [Symbol, String, Module] the compensating command, as a bare
389
+ # constant (a `ScopedConstant` module `ConstShim` resolves) or, under shadow-parsing,
390
+ # quoted text
391
+ # @param with [Hash{Symbol => Object}, nil] a projection onto the command's own
392
+ # arguments, the same shape `HandlerBuilder#dispatch_impl`'s own `with:` takes; `nil`
393
+ # forwards the triggering context verbatim
394
+ # @return [Bluebook::DispatchSpec] the compensating dispatch just recorded
395
+ # @raise [Bluebook::DSL::ProcessManagerBuilder::InvalidProcessManager] if `command_ref`
396
+ # is quoted text outside shadow-parsing
333
397
  def compensates_impl(command_ref, with: nil)
334
398
  if command_ref.is_a?(::String) && !MetaValidator.shadow_parsing?
335
399
  raise InvalidProcessManager,
@@ -15,28 +15,48 @@ module Hecks
15
15
  include QuerySpecification::Common::DSL
16
16
  include WordGate
17
17
 
18
+ # @param name [String] the query's name, as written after `query`
18
19
  def initialize(name)
19
20
  @name = name
20
21
  @wheres = []
21
22
  end
22
23
 
24
+ # Sets the human-readable description shown for this query.
25
+ #
26
+ # @param value [String] the description text
27
+ # @return [String] the description as stored
23
28
  def description(value) = @description = value
24
29
 
30
+ # Declares a query parameter that names another aggregate's identity.
31
+ #
25
32
  # A query parameter naming another aggregate's own identity
26
33
  # (Card.Active's own `Board`, filtering to one board's cards) —
27
34
  # just a plain attribute typed as a reference,
28
- # AttributeCollector#attribute already handling an Reference
35
+ # `AttributeCollector#attribute_impl` already handling a Reference
29
36
  # exactly like any other. No "acts on itself" case to
30
37
  # distinguish here the way a command's own reference_to has —
31
38
  # a query has no root of its own to act on, only parameters.
32
- # Renamed from `reference_to` — item #13's full metaprogrammed
33
- # dispatch (slice 4b). Bootstrap-reachable, in
34
- # GenericDispatch::BOOTSTRAP_CALLS_FALLBACK.
39
+ #
40
+ # Answers the `reference_to` word through the table's `calls:`
41
+ # column — item #13's full metaprogrammed dispatch (slice 4b).
42
+ # Bootstrap-reachable, in `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`.
43
+ #
44
+ # @param type [Module, Symbol, String] the referenced aggregate, written as a bare
45
+ # constant
46
+ # @param as [Symbol, nil] the parameter's name; nil derives it from the target
47
+ # @param optional [Boolean] whether the parameter may be omitted when the query runs
48
+ # @return [void]
49
+ # @raise [Bluebook::DSL::Malformed] if `as` (or the derived name) is already declared
35
50
  def reference_to_impl(type, as: nil, optional: false)
36
51
  target = Naming.demodulise(type)
37
52
  attribute_impl(as || default_reference_name(target), Reference.new(target), optional: optional)
38
53
  end
39
54
 
55
+ # Assembles the declared parameters and filtering into a `Query`.
56
+ #
57
+ # @return [Bluebook::Query] the built query
58
+ # @raise [Bluebook::DSL::Malformed] if the body declares `cursor`, which no interpreter
59
+ # implements
40
60
  def build
41
61
  seal_cursor
42
62
  Query.new(
@@ -54,6 +74,15 @@ module Hecks
54
74
  )
55
75
  end
56
76
 
77
+ # Evaluates a `query` block against a fresh builder, then fills in owner-derived types.
78
+ #
79
+ # @param name [String] the query's name
80
+ # @param owner_attributes [Array<Bluebook::Attribute>] the enclosing aggregate or
81
+ # entity's own attributes, matched against the block's own parameter names
82
+ # @yield the query body, evaluated with the builder as `self`; may be omitted
83
+ # @return [Bluebook::Query] the built query
84
+ # @raise [Bluebook::DSL::Malformed] if the body declares `cursor`, a duplicate attribute,
85
+ # or a filtering clause the query language refuses
57
86
  def self.build(name, owner_attributes: [], &block)
58
87
  builder = new(name)
59
88
  builder.instance_eval(&block) if block