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
@@ -10,6 +10,9 @@ module Hecks
10
10
  # naming them; the dispatcher itself builds an Invocation instead.
11
11
  module Routing
12
12
  Envelope = Struct.new(:aggregate, :entities, keyword_init: true) do
13
+ # @param aggregate [String, #to_s] the receiving aggregate's identity
14
+ # @param entities [Array<String, #to_s>, nil] the entity-hop identities after the
15
+ # aggregate, in call order; empty for an aggregate-level command
13
16
  def initialize(aggregate:, entities: [])
14
17
  super(aggregate: aggregate.to_s, entities: Array(entities).map(&:to_s).freeze)
15
18
  freeze
@@ -18,12 +21,34 @@ module Hecks
18
21
 
19
22
  module_function
20
23
 
24
+ # Resolves a `to:` argument into a routing envelope. A thin delegator to
25
+ # `Invocation.route`; the dispatcher itself builds an `Invocation` instead.
26
+ #
27
+ # @param to [String, Hash, nil] a bare aggregate identity, or a Hash with
28
+ # `aggregate:` and `entity:`/`entities:`; nil for no receiver
29
+ # @param entity_depth [Integer] the number of entity-hop identities the call expects
30
+ # @return [Routing::Envelope, nil] the resolved envelope, or nil when `to` is nil
31
+ # @raise [Runtime::TypeMismatch] if `to` is malformed or its entity count does not
32
+ # match `entity_depth`
21
33
  def envelope(to, entity_depth: 0) = Invocation.route(to, entity_depth: entity_depth)
22
34
 
35
+ # Resolves a command's offered facts into its flat args hash. A thin
36
+ # delegator to `Invocation.facts_for` for any caller still naming it.
37
+ #
23
38
  # Loaded by invocation.rb (which requires this file), so `Invocation`
24
39
  # is always defined by the time either method runs.
25
- def payload(command, with:, legacy:)
26
- facts = Invocation.facts_for(command, with: with, legacy: legacy)
40
+ #
41
+ # @param command [Class] a `Bluebook::Command` subclass (or port-operation class)
42
+ # responding to `hecks_name` and `attributes`
43
+ # @param with [Hash, nil] the command's facts, keyed by attribute name; nil when the
44
+ # caller offers `flat` instead
45
+ # @param flat [Hash] the command's facts as a flat args hash, used when `with` is nil
46
+ # @return [Hash{String, Symbol => Object}] the offered facts by attribute name; an
47
+ # attribute offered as nil is kept as nil
48
+ # @raise [Runtime::TypeMismatch] if both `with` and a non-empty `flat` are given, if
49
+ # `with` is not a Hash, or if `with` names an unknown or omits a required attribute
50
+ def payload(command, with:, flat:)
51
+ facts = Invocation.facts_for(command, with: with, flat: flat)
27
52
  Invocation.new(verb: nil, target: nil, facts: facts).to_args
28
53
  end
29
54
  end
@@ -50,18 +50,16 @@ module Hecks
50
50
 
51
51
  # A self-referencing leg carries the correlation forward under its
52
52
  # own emitting record's identity — `event.id`, not a field dug back
53
- # out of the payload. This used to read `event.payload[own_key]`
54
- # (`own_key` the aggregate's own reference-key convention, "wire",
55
- # "transfer"), which only ever held a value because legacy dispatch
56
- # left the self-addressing key riding along in the payload
57
- # unfiltered. Routing separated from payload (`to:`/`with:`, the
58
- # facade's own `Handle#run` always uses it) closed exactly that
59
- # leak — correctly, since an addressing key is not a fact the
60
- # payload should carry — which left this tier reading an empty
61
- # Hash for any self-referencing leg with no other declared
62
- # attributes (`OnboardingCase.Clear`, `.Decline` — no `attribute`
63
- # lines at all): the saga silently stopped advancing, forever, for
64
- # exactly the leg this tier exists to correlate.
53
+ # out of the payload, because an addressing key is not a fact the
54
+ # payload should carry: routing is kept separate from payload
55
+ # (`to:`/`with:`, the facade's own `Handle#run` always uses it), so
56
+ # `own_key` (the aggregate's own reference-key convention, "wire",
57
+ # "transfer") never rides along in the payload unfiltered. Digging
58
+ # for it there would read an empty Hash for any self-referencing
59
+ # leg with no other declared attributes (`OnboardingCase.Clear`,
60
+ # `.Decline` — no `attribute` lines at all), silently stopping the
61
+ # saga from advancing, forever, for exactly the leg this tier
62
+ # exists to correlate.
65
63
  #
66
64
  # `event.id` says the identical thing this tier always meant —
67
65
  # "the record that just emitted this event, by its own identity" —
@@ -28,14 +28,29 @@ module Hecks
28
28
 
29
29
  attr_reader :registry
30
30
 
31
+ # @param registry [Runtime::Registry] the booted registry whose declared
32
+ # process managers and saga persistence this interpreter runs against
33
+ # @param door [Runtime::Dispatcher] the dispatcher a saga leg's own dispatch
34
+ # re-enters through
31
35
  def initialize(registry, door:)
32
36
  @registry = registry
33
37
  @door = door
34
38
  end
35
39
 
40
+ # Runs `domain`'s declared process managers against `event`: begins,
41
+ # advances or ends each matching saga instance, checkpointing durably
42
+ # before its own dispatches run.
43
+ #
36
44
  # `only:` — one process manager, the outbox relay's way of running
37
45
  # exactly the consumer a row names (`Runtime::Outbox::Relay#
38
46
  # run_consumer`); nil advances every manager the domain declares.
47
+ #
48
+ # @param event [Runtime::Event] the just-emitted event to react to
49
+ # @param domain [String, Symbol] the domain whose declared process managers
50
+ # are checked
51
+ # @param only [Bluebook::ProcessManager, nil] one process manager to run
52
+ # exactly, instead of every manager `domain` declares
53
+ # @return [void]
39
54
  def advance(event, domain, only: nil)
40
55
  bluebook = @registry.bluebook(domain)
41
56
  return unless bluebook
@@ -56,12 +71,11 @@ module Hecks
56
71
  # could otherwise interleave their writes out of order, silently
57
72
  # reordering a saga's own transition history — worse for the
58
73
  # adapters with no locking of their own (Heki) than for Postgres.
59
- # `deep_copy` guards against the exact shape of bug PR #175 itself
60
- # already found once (over-freezing a live, still-mutated Hash) —
61
- # never hand a persistence adapter the same object `advance_saga`/
62
- # `unwind` go on to mutate in place; round-tripping through JSON
63
- # is also what guarantees the value is safe for every adapter that
64
- # itself calls `JSON.generate` on it.
74
+ # `deep_copy` guards against a real bug shape (over-freezing a live,
75
+ # still-mutated Hash) — never hand a persistence adapter the same
76
+ # object `advance_saga`/`unwind` go on to mutate in place;
77
+ # round-tripping through JSON is also what guarantees the value is
78
+ # safe for every adapter that itself calls `JSON.generate` on it.
65
79
  # `pending:` — see saga_pending_dispatch.rb. Injected into the
66
80
  # written copy of memory only, never into `instance[:memory]`
67
81
  # itself: every other reader of a live instance's memory
@@ -423,7 +437,7 @@ module Hecks
423
437
  # Same non-reentrancy reasoning as `advance_saga`'s own comment —
424
438
  # the mutex covers only the check-and-mutate-and-checkpoint step.
425
439
  advanced = @registry.saga_mutex.synchronize do
426
- # The compensating leg is selected by (REFUSED, current state)
440
+ # The compensating leg is selected by (`REFUSED`, current state)
427
441
  # too — C10.3, one rule for every leg.
428
442
  handler = process_manager.handler_for(REFUSED, instance[:state])
429
443
  unless handler
@@ -507,9 +521,9 @@ module Hecks
507
521
  end
508
522
 
509
523
  # BUG#6 — unconditionally the saga's own home domain, never inferred
510
- # from `command_name`'s own shape. This used to guess: a leftover
511
- # `::` after `Naming.command_ref`'s own rewrite was read as "already
512
- # domain-qualified" and left alone. That heuristic cannot actually
524
+ # from `command_name`'s own shape. Guessing from a leftover
525
+ # `::` after `Naming.command_ref`'s own rewrite (read as "already
526
+ # domain-qualified" and left alone) cannot actually
513
527
  # tell a genuinely cross-domain reference (`Banking::Account::
514
528
  # Debit` -> one `::` survives) apart from a same-domain entity
515
529
  # command reference (`Manifest::Slot::Fill` -> one `::` survives
@@ -519,11 +533,11 @@ module Hecks
519
533
  # (confirmed against `Naming.command_ref`'s own rewrite: it only
520
534
  # ever strips the last `::`, so the count of what remains is blind
521
535
  # to why it's there). Picking the cross-domain reading unconditionally
522
- # left `qa/stress_domains/waybill`'s own `Packing` saga dispatching
536
+ # would leave `qa/stress_domains/waybill`'s own `Packing` saga dispatching
523
537
  # `Manifest::Slot::Fill` — an entity command in its own domain —
524
- # unprefixed, so `Naming.split_verb` read "Manifest" as a domain
538
+ # unprefixed, so `Naming.split_verb` would read "Manifest" as a domain
525
539
  # name instead of this chapter's own aggregate, and the dispatch
526
- # failed with `UnknownVerb`, silently recorded as an ordinary
540
+ # would fail with `UnknownVerb`, silently recorded as an ordinary
527
541
  # domain refusal rather than surfacing as the real bug it is.
528
542
  #
529
543
  # The fix mirrors `PolicyInterpreter#deliver`'s own mechanism,
@@ -10,10 +10,11 @@ module Hecks
10
10
  # Dispatcher, its own adapter instances) rather than one shared
11
11
  # process switching connections mid-dispatch.
12
12
  #
13
- # That last part is the finding this module encodes. The project
14
- # register (Bluebook::ProjectRegister) already resolves an address's
15
- # realm to a dispatcher at registration time — Router#resolve looks
16
- # the FQN up in one flat table keyed by realm::domain::aggregate.verb,
13
+ # ## Why no ambient "current tenant"
14
+ #
15
+ # The project register (Bluebook::ProjectRegister) already resolves an
16
+ # address's realm to a dispatcher at registration time — Router#resolve
17
+ # looks the FQN up in one flat table keyed by realm::domain::aggregate.verb,
17
18
  # and each entry already carries its own dispatcher from its own
18
19
  # boot. So "which tenant" is decided once, at boot/registration time
19
20
  # (which of possibly many boots of the same directory a request's
@@ -23,8 +24,9 @@ module Hecks
23
24
  # already gives for free — each tenant's own PostgresEra instance
24
25
  # is its own connection, held for the life of that boot.
25
26
  #
26
- # So `tenant_capable?` asks a narrower question than it might sound:
27
- # not "can this adapter switch tenants," but "does booting this
27
+ # ## What `tenant_capable?` really asks
28
+ #
29
+ # Not "can this adapter switch tenants," but "does booting this
28
30
  # adapter twice, with different settings, for the same directory,
29
31
  # actually keep the two boots' data apart." Memory answers true
30
32
  # trivially — a `@records` Hash is a plain instance variable, and
@@ -41,6 +43,9 @@ module Hecks
41
43
  module TenantCheck
42
44
  module_function
43
45
 
46
+ # Refuses to let `domain` boot for more than one tenant unless every
47
+ # aggregate's resolved persistence adapter is `tenant_capable?`.
48
+ #
44
49
  # A domain is safe to boot for more than one tenant only if every
45
50
  # aggregate's resolved persistence adapter is tenant_capable? — one
46
51
  # ungoverned adapter sharing state across two tenant boots is a
@@ -48,6 +53,12 @@ module Hecks
48
53
  # a second tenant boot of the same directory is trusted, the same
49
54
  # severity EraCheck/refuse_ungoverned_roles! already hold their
50
55
  # own gates to.
56
+ #
57
+ # @param registry [Runtime::Registry] the booted registry to check
58
+ # @param domain [String, Symbol] the domain name to check every aggregate of
59
+ # @return [void]
60
+ # @raise [Runtime::WiringError] if any aggregate in `domain` is bound to an adapter
61
+ # that is not `tenant_capable?`
51
62
  def refuse_unless_tenant_capable!(registry, domain)
52
63
  bluebook = registry.bluebook(domain)
53
64
  return unless bluebook
@@ -67,12 +78,21 @@ module Hecks
67
78
  "or keep #{domain} single-tenant."
68
79
  end
69
80
 
81
+ # Answers whether `adapter_name`'s Ruby implementation keeps two
82
+ # tenants' boots of the same directory from sharing data.
83
+ #
70
84
  # The capability idiom itself — an adapter class that answers
71
85
  # tenant_capable? with true keeps two boots' data apart by
72
86
  # construction (Memory) or by an explicit per-boot isolation
73
87
  # setting (PostgresEra's schema:). Same defensive shape
74
88
  # EraCheck#lineage_capable? already uses: a class that doesn't
75
89
  # respond at all is false, not an error.
90
+ #
91
+ # @param registry [Runtime::Registry] the booted registry the adapter is wired into
92
+ # @param adapter_name [String] the adapter's declared name, such as `"PostgresEra"`
93
+ # @return [Boolean] true when the adapter is registered, has a Ruby implementation,
94
+ # and answers `tenant_capable?` true; false for any other case, including a
95
+ # missing adapter or one whose lookup raises
76
96
  def tenant_capable?(registry, adapter_name)
77
97
  adapter_class = registry.adapters[adapter_name] && registry.adapter_class(adapter_name)
78
98
  adapter_class.respond_to?(:tenant_capable?) && adapter_class.tenant_capable?
@@ -29,6 +29,16 @@ module Hecks
29
29
  module TenantScope
30
30
  module_function
31
31
 
32
+ # Wraps a declared query/read-model spec with its tenant boundary clause, if it has one.
33
+ #
34
+ # @param declared [Bluebook::Query, Bluebook::ReadModel] the declared specification to
35
+ # scope
36
+ # @param args [Hash] the query's arguments, checked for the declared tenant field
37
+ # @return [Bluebook::Query, Bluebook::ReadModel, Runtime::TenantScope::Scoped]
38
+ # `declared` unchanged when it declares no `authorize policy, tenant:`; otherwise a
39
+ # `Scoped` wrapper whose `#wheres` adds the tenant `eq` clause
40
+ # @raise [Runtime::Unauthorized] if `declared` declares a tenant boundary and `args`
41
+ # omits that field
32
42
  def apply(declared, args)
33
43
  tenant = declared.authorization&.tenant
34
44
  return declared unless tenant
@@ -50,11 +60,19 @@ module Hecks
50
60
  # own internal `.wheres` read would bypass this override (see this
51
61
  # module's own header for why).
52
62
  class Scoped < SimpleDelegator
63
+ # @param declared [Bluebook::Query, Bluebook::ReadModel] the specification to wrap,
64
+ # delegated to for everything but `#wheres`
65
+ # @param clause [QuerySpecification::Common::WhereClause] the synthetic tenant `eq`
66
+ # clause to append
53
67
  def initialize(declared, clause)
54
68
  super(declared)
55
69
  @clause = clause
56
70
  end
57
71
 
72
+ # Reads the wrapped specification's where-clauses, with the tenant clause appended.
73
+ #
74
+ # @return [Array<QuerySpecification::Common::WhereClause>] `declared.wheres` with the
75
+ # tenant clause appended
58
76
  def wheres = __getobj__.wheres + [@clause]
59
77
  end
60
78
  end