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
@@ -2,14 +2,16 @@ require_relative "../projector"
2
2
 
3
3
  module Hecks
4
4
  module Projections
5
- # A domain's own shape, projected as MERMAID diagrams — the same
5
+ # A domain's own shape, projected as Mermaid diagrams — the same
6
6
  # trick `Projections::Reference`/`DocsProjector` already play for
7
7
  # prose, one level further: a diagram generated from the
8
8
  # declaration can't drift from it the way a hand-drawn one
9
9
  # inevitably does, because there is no second copy to forget to
10
10
  # update.
11
11
  #
12
- # MERMAID, not Graphviz (the two considered) — every diagram type
12
+ # ## Why Mermaid, not Graphviz
13
+ #
14
+ # Mermaid, not Graphviz (the two considered) — every diagram type
13
15
  # below has a Mermaid form purpose-built for exactly what the
14
16
  # underlying construct already is (a `lifecycle` is a state
15
17
  # machine, `has_many`/`belongs_to` already speaks in cardinality,
@@ -23,6 +25,8 @@ module Hecks
23
25
  # dependency earns its way past std") would rather not take just to
24
26
  # draw a diagram.
25
27
  #
28
+ # ## Diagram kinds
29
+ #
26
30
  # Four diagram kinds, one file each per domain except lifecycles
27
31
  # (one per lifecycle-bearing construct, since that's how a reader
28
32
  # actually reaches for it — looking at one aggregate's states, not
@@ -62,6 +66,8 @@ module Hecks
62
66
  # here that looks outward past
63
67
  # this domain's own boundary
64
68
  #
69
+ # ## Node and construct naming
70
+ #
65
71
  # Construct names (aggregate/entity/command/event) are used bare,
66
72
  # unsanitized, as Mermaid node/entity ids — safe because this
67
73
  # language's own word grammar only ever admits simple CamelCase/
@@ -79,6 +85,14 @@ module Hecks
79
85
 
80
86
  module_function
81
87
 
88
+ # Projects one domain's whole shape into every diagram kind it has data
89
+ # for, one Mermaid file per entry.
90
+ #
91
+ # @param bluebook [Bluebook::Chapter] the assembled chapter to project
92
+ # @param options [Hash{Symbol => Object}] projection options; only `:hecksagon`
93
+ # (a `Bluebook::Hecksagon`, or `nil`) is read, and only by `frameworks_diagram`
94
+ # @return [Hash{String => String}] each diagram's filename (such as
95
+ # `"relationships.mmd"`) mapped to its rendered Mermaid source
82
96
  def call(bluebook:, options: {})
83
97
  files = {}
84
98
 
@@ -125,18 +139,35 @@ module Hecks
125
139
 
126
140
  # ── shared ────────────────────────────────────────────────────────
127
141
 
142
+ # Every aggregate and entity in this domain, flattened into one list —
143
+ # everything a lifecycle/relationship/command diagram walks alike.
144
+ #
128
145
  # An entity can carry its own lifecycle, relationship, or command
129
146
  # too — its own `lifecycle`/`reference_to`/`command` block,
130
147
  # addressed through its holding aggregate the same way
131
148
  # `DocsProjector` already treats an aggregate and its entities
132
149
  # alike. Walking both here means a domain's entity gaining any of
133
150
  # these needs no change to this file.
151
+ #
152
+ # @param bluebook [Bluebook::Chapter] the chapter to walk
153
+ # @return [Array<Bluebook::Aggregate, Class>] every aggregate, and every entity
154
+ # class (a `Bluebook::Entity` subclass) nested under it, in declaration order
134
155
  def holders(bluebook)
135
156
  bluebook.aggregates.flat_map { |aggregate| [aggregate, *aggregate.entities] }
136
157
  end
137
158
 
159
+ # The holders (see `holders`) that declare their own `lifecycle` block —
160
+ # one diagram file is projected per lifecycle-bearing holder, not one
161
+ # per domain.
162
+ #
163
+ # @param bluebook [Bluebook::Chapter] the chapter to walk
164
+ # @return [Array<Bluebook::Aggregate, Class>] every holder with a non-nil
165
+ # `lifecycle`
138
166
  def holders_with_lifecycle(bluebook) = holders(bluebook).select(&:lifecycle)
139
167
 
168
+ # Renders the two-line `%%` comment banner every generated diagram file
169
+ # opens with, warning against hand edits and naming how to regenerate it.
170
+ #
140
171
  # `chapter_name` drives the re-run hint always — that's the one
141
172
  # argument `bin/project_diagrams` actually takes, regardless of
142
173
  # which single aggregate/entity `subject` happens to name. Passing
@@ -144,6 +175,10 @@ module Hecks
144
175
  # `Order_lifecycle.mmd` telling a reader to run
145
176
  # `bin/project_diagrams <domain-path> Order` — a chapter name
146
177
  # Hecks.boot has never heard of.
178
+ #
179
+ # @param chapter_name [String] the domain's chapter name, used in the re-run hint
180
+ # @param subject [String] prose naming what this specific file was generated from
181
+ # @return [String] the two-line `%%`-commented Mermaid banner, newline-terminated
147
182
  def header(chapter_name, subject)
148
183
  <<~HEADER
149
184
  %% GENERATED by bin/project_diagrams from #{subject} — DO NOT EDIT BY HAND.
@@ -153,6 +188,15 @@ module Hecks
153
188
 
154
189
  # ── lifecycle -> stateDiagram-v2 ─────────────────────────────────
155
190
 
191
+ # Renders one holder's own declared `lifecycle` as a Mermaid
192
+ # `stateDiagram-v2` — its default starting state, and one edge per
193
+ # transition, labeled by the command that causes it.
194
+ #
195
+ # @param bluebook [Bluebook::Chapter] the chapter `holder` belongs to, for the
196
+ # file's re-run banner
197
+ # @param holder [Bluebook::Aggregate, Class] the aggregate or entity class (a
198
+ # `Bluebook::Entity` subclass) whose `lifecycle` to draw
199
+ # @return [String] the rendered Mermaid `stateDiagram-v2` source
156
200
  def lifecycle_diagram(bluebook, holder)
157
201
  lifecycle = holder.lifecycle
158
202
  edges = lifecycle.transitions.flat_map do |command_name, transition|
@@ -169,6 +213,10 @@ module Hecks
169
213
 
170
214
  # ── relationships -> erDiagram ───────────────────────────────────
171
215
 
216
+ # Renders the whole domain's declared `has_many`/`has_one`/`belongs_to`/
217
+ # `reference_to` attributes as a Mermaid `erDiagram`, one edge per
218
+ # reference attribute across every holder.
219
+ #
172
220
  # Standard crow's-foot reading, the same convention every ORM's own
173
221
  # ERD generator (Rails' erd gem included) already uses:
174
222
  # `has_many`/`has_one` are read from the owning side — one Holder
@@ -181,6 +229,10 @@ module Hecks
181
229
  # absent (a nilable reference, an empty has_one) — never the
182
230
  # crow's-foot "many" marker, which is a structural fact independent
183
231
  # of any one instance's optionality.
232
+ #
233
+ # @param bluebook [Bluebook::Chapter] the chapter to project
234
+ # @return [String, nil] the rendered Mermaid `erDiagram` source, or `nil` if the
235
+ # domain declares no reference attribute at all
184
236
  def relationship_diagram(bluebook)
185
237
  edges = holders(bluebook).flat_map do |holder|
186
238
  holder.attributes.select(&:reference?).map { |attribute| relationship_edge(holder, attribute) }
@@ -191,6 +243,14 @@ module Hecks
191
243
  "#{header(bluebook.name, subject)}erDiagram\n#{edges.join("\n")}\n"
192
244
  end
193
245
 
246
+ # One erDiagram edge for one holder's own declared reference attribute,
247
+ # in the DSL word's own crow's-foot reading (see `relationship_diagram`).
248
+ #
249
+ # @param holder [Bluebook::Aggregate, Class] the aggregate or entity class (a
250
+ # `Bluebook::Entity` subclass) that declares `attribute`
251
+ # @param attribute [Bluebook::Attribute] a reference attribute (`attribute.reference?`
252
+ # is true); its `relationship` names which DSL word minted it
253
+ # @return [String] the rendered `erDiagram` edge line
194
254
  def relationship_edge(holder, attribute)
195
255
  target = attribute.type.target_name
196
256
  case attribute.relationship
@@ -213,6 +273,10 @@ module Hecks
213
273
  # aggregates may share a command name; event ids are bare
214
274
  # (`evt_PizzaCreated`) since an event is this domain's own
215
275
  # addressing key, the same way `policy.on_event` reaches it.
276
+ #
277
+ # @param bluebook [Bluebook::Chapter] the chapter to project
278
+ # @return [String, nil] the rendered Mermaid flowchart source, or `nil` if the
279
+ # domain declares no command `emits` and no policy at all
216
280
  def dispatch_diagram(bluebook)
217
281
  lines = []
218
282
 
@@ -231,6 +295,14 @@ module Hecks
231
295
  "#{header(bluebook.name, subject)}flowchart LR\n#{lines.uniq.join("\n")}\n"
232
296
  end
233
297
 
298
+ # One flowchart edge from a command to one event it declares it `emits`.
299
+ #
300
+ # @param holder [Bluebook::Aggregate, Class] the aggregate or entity class (a
301
+ # `Bluebook::Entity` subclass) that declares `command`
302
+ # @param command [Class] the command class (a `Bluebook::Command` subclass) that
303
+ # emits `event`
304
+ # @param event [String] the emitted event's name
305
+ # @return [String] the rendered `-->|emits|` edge line
234
306
  def emits_edge(holder, command, event)
235
307
  %( #{command_node(holder.hecks_name, command.hecks_name)} -->|emits| #{event_node(event)})
236
308
  end
@@ -246,6 +318,10 @@ module Hecks
246
318
  # edge of its own here, which honestly shows "dispatch continues
247
319
  # elsewhere" rather than silently dropping the edge. The label
248
320
  # names which domain, so that's not a dead end on the page either.
321
+ #
322
+ # @param policy [Bluebook::Policy] the policy to draw as a trigger edge
323
+ # @return [String] the rendered `-->|triggers|` (or `-->|triggers in <domain>|`)
324
+ # edge line
249
325
  def trigger_edge(policy)
250
326
  bare_event = policy.on_event.to_s.split(".").last
251
327
  aggregate_name, command_name = policy.trigger_command.to_s.split(".", 2)
@@ -253,14 +329,28 @@ module Hecks
253
329
  %( #{event_node(bare_event)} -->|#{label}| #{command_node(aggregate_name, command_name)})
254
330
  end
255
331
 
332
+ # Mermaid stadium-shaped node id and label for one command, qualified by
333
+ # its owning aggregate since two aggregates may share a command name.
334
+ #
335
+ # @param aggregate_name [String] the command's owning aggregate/entity name
336
+ # @param command_name [String] the command's own name
337
+ # @return [String] the rendered `cmd_<aggregate>_<command>(["..."])` node
256
338
  def command_node(aggregate_name, command_name)
257
339
  %(cmd_#{aggregate_name}_#{command_name}(["#{aggregate_name}.#{command_name}"]))
258
340
  end
259
341
 
342
+ # Mermaid hexagonal node id and label for one event, addressed bare since
343
+ # an event is this domain's own addressing key.
344
+ #
345
+ # @param event_name [String] the event's own name
346
+ # @return [String] the rendered `evt_<event>{{"..."}}` node
260
347
  def event_node(event_name) = %(evt_#{event_name}{{"#{event_name}"}})
261
348
 
262
349
  # ── roles -> flowchart ────────────────────────────────────────────
263
350
 
351
+ # Renders every command's declared `role` as a Mermaid flowchart, one
352
+ # edge per role issuing a command.
353
+ #
264
354
  # Who issues what, across the whole domain — data no existing
265
355
  # projection draws at all today (the reference pages' own
266
356
  # `command_entry` only ever prints a command's role as a single
@@ -268,6 +358,10 @@ module Hecks
268
358
  # no declared `role` draws nothing — there is no fact to state.
269
359
  # Circle-shaped so a role reads as "who" beside `dispatch.mmd`'s
270
360
  # stadium ("what someone does") and hexagon ("what happened").
361
+ #
362
+ # @param bluebook [Bluebook::Chapter] the chapter to project
363
+ # @return [String, nil] the rendered Mermaid flowchart source, or `nil` if no
364
+ # command in the domain declares a `role`
271
365
  def roles_diagram(bluebook)
272
366
  lines = holders(bluebook).flat_map do |holder|
273
367
  holder.commands.select(&:role).map { |command| role_edge(holder, command) }
@@ -278,17 +372,36 @@ module Hecks
278
372
  "#{header(bluebook.name, subject)}flowchart LR\n#{lines.uniq.join("\n")}\n"
279
373
  end
280
374
 
375
+ # One flowchart edge from a command's declared role to the command
376
+ # itself.
377
+ #
378
+ # @param holder [Bluebook::Aggregate, Class] the aggregate or entity class (a
379
+ # `Bluebook::Entity` subclass) that declares `command`
380
+ # @param command [Class] the command class (a `Bluebook::Command` subclass); its
381
+ # `role` is drawn
382
+ # @return [String] the rendered `-->|issues|` edge line
281
383
  def role_edge(holder, command)
282
384
  %( #{role_node(command.role)} -->|issues| #{command_node(holder.hecks_name, command.hecks_name)})
283
385
  end
284
386
 
387
+ # Mermaid circular node id and label for one role, keeping the real
388
+ # free-text role string as the displayed label (see `role_id`).
389
+ #
390
+ # @param role_name [String] the command's declared `role` text
391
+ # @return [String] the rendered `role_<id>((<role>))` node
285
392
  def role_node(role_name) = %(#{role_id(role_name)}((#{role_name})))
286
393
 
394
+ # Sanitizes a free-text role name into a legal Mermaid node id.
395
+ #
287
396
  # A role name is free text ("Back office", "Vault officer") —
288
397
  # unlike every other name this file uses as a bare id, this one
289
398
  # has to be sanitized to become a legal Mermaid identifier. The
290
399
  # real string still appears as the node's own label
291
400
  # (`role_node`); only the id is mangled.
401
+ #
402
+ # @param role_name [String] the command's declared `role` text
403
+ # @return [String] `role_` followed by `role_name` with every run of
404
+ # non-alphanumeric characters collapsed to a single underscore
292
405
  def role_id(role_name) = "role_#{role_name.to_s.gsub(/[^A-Za-z0-9]+/, '_')}"
293
406
 
294
407
  # ── ports -> flowchart ───────────────────────────────────────────
@@ -306,8 +419,8 @@ module Hecks
306
419
  # aggregate the port hangs off (always present — a port always
307
420
  # belongs to exactly one aggregate), and a solid "to:" edge to
308
421
  # whichever aggregate the operation itself names as its receiver
309
- # (present only when `to:` is declared — PR #351's own real
310
- # addition; before it, this data didn't exist to draw at all).
422
+ # (present only when `to:` is declared — an operation with no
423
+ # declared target has no destination of its own to draw).
311
424
  # `emits` reuses `dispatch.mmd`'s own `event_node` unchanged — the
312
425
  # same fact, reached from a different direction.
313
426
  #
@@ -317,6 +430,10 @@ module Hecks
317
430
  # it raises, it isn't just always empty), so walking entities here
318
431
  # the way every other diagram in this file does would crash on
319
432
  # the first entity-bearing domain.
433
+ #
434
+ # @param bluebook [Bluebook::Chapter] the chapter to project
435
+ # @return [String, nil] the rendered Mermaid flowchart source, or `nil` if the
436
+ # domain declares no port operation at all
320
437
  def ports_diagram(bluebook)
321
438
  lines = bluebook.aggregates.flat_map do |holder|
322
439
  holder.ports.flat_map { |port| port.operations.map { |operation| port_edges(holder, port, operation) } }
@@ -328,6 +445,14 @@ module Hecks
328
445
  "#{header(bluebook.name, subject)}flowchart LR\n#{lines.uniq.join("\n")}\n"
329
446
  end
330
447
 
448
+ # All of one port operation's own edges: the aggregate that exposes it,
449
+ # its optional `to:` routing target, and one edge per event it `emits`.
450
+ #
451
+ # @param holder [Bluebook::Aggregate] the aggregate `port` is attached to
452
+ # @param port [Bluebook::DomainPort] the port `operation` belongs to
453
+ # @param operation [Bluebook::PortOperation] the operation to draw
454
+ # @return [Array<String>] the rendered edge lines: one "exposes" edge, plus a
455
+ # "to:" edge when `operation.to` is declared, plus one "emits" edge per event
331
456
  def port_edges(holder, port, operation)
332
457
  op = port_operation_node(holder.hecks_name, port.name, operation.hecks_name)
333
458
  edges = [" #{holder.hecks_name}[(#{holder.hecks_name})] -.->|exposes| #{op}"]
@@ -336,6 +461,13 @@ module Hecks
336
461
  edges
337
462
  end
338
463
 
464
+ # Mermaid trapezoidal node id and label for one port operation, qualified
465
+ # by both its owning aggregate and its owning port.
466
+ #
467
+ # @param aggregate_name [String] the operation's owning aggregate name
468
+ # @param port_name [String] the operation's owning port name
469
+ # @param operation_name [String] the operation's own name
470
+ # @return [String] the rendered `op_<aggregate>_<port>_<operation>[/"..."/]` node
339
471
  def port_operation_node(aggregate_name, port_name, operation_name)
340
472
  id = "op_#{aggregate_name}_#{port_name}_#{operation_name}"
341
473
  %(#{id}[/"#{port_name}.#{operation_name}"/])
@@ -343,6 +475,9 @@ module Hecks
343
475
 
344
476
  # ── read models -> flowchart ─────────────────────────────────────
345
477
 
478
+ # Renders the whole domain's declared `read_models` as a Mermaid
479
+ # flowchart, one edge per aggregate a read model is assembled from.
480
+ #
346
481
  # The read-side complement to `relationships.mmd` — that diagram
347
482
  # shows how aggregates reference each other for writes
348
483
  # (`has_many`/`belongs_to`/`reference_to`); this shows how a
@@ -369,6 +504,10 @@ module Hecks
369
504
  # read_model. Still MVP scope: `where`/`group_by`/`order_by`
370
505
  # aren't drawn at all yet — real facts, not invented, just not
371
506
  # this diagram's job yet.
507
+ #
508
+ # @param bluebook [Bluebook::Chapter] the chapter to project
509
+ # @return [String, nil] the rendered Mermaid flowchart source, or `nil` if the
510
+ # domain declares no `read_model` at all
372
511
  def read_model_diagram(bluebook)
373
512
  lines = bluebook.read_models.flat_map { |read_model| read_model_edges(read_model) }
374
513
  return nil if lines.empty?
@@ -377,6 +516,12 @@ module Hecks
377
516
  "#{header(bluebook.name, subject)}flowchart LR\n#{lines.uniq.join("\n")}\n"
378
517
  end
379
518
 
519
+ # One edge per aggregate one read model is assembled from, into that
520
+ # read model's own subroutine-shaped node.
521
+ #
522
+ # @param read_model [Bluebook::ReadModel] the read model to draw
523
+ # @return [Array<String>] the rendered edge lines, one per entry in
524
+ # `read_model.to_h[:aggregate_heads]`
380
525
  def read_model_edges(read_model)
381
526
  shape = read_model.to_h
382
527
  node = %(rm_#{shape[:name]}[["#{read_model_label(shape)}"]])
@@ -394,6 +539,13 @@ module Hecks
394
539
  end
395
540
  end
396
541
 
542
+ # The read model's own subroutine node label — its name, with
543
+ # `(count)`/`(median: field)` appended for the two aggregations this
544
+ # diagram states.
545
+ #
546
+ # @param shape [Hash{Symbol => Object}] a `Bluebook::ReadModel#to_h` result;
547
+ # reads `:name`, `:count` and `:median_field`
548
+ # @return [String] the node's display label
397
549
  def read_model_label(shape)
398
550
  return "#{shape[:name]} (count)" if shape[:count]
399
551
  return "#{shape[:name]} (median: #{shape[:median_field]})" if shape[:median_field]
@@ -403,6 +555,9 @@ module Hecks
403
555
 
404
556
  # ── surface -> flowchart ─────────────────────────────────────────
405
557
 
558
+ # Renders one holder's own commands (and what each writes) and queries
559
+ # as a Mermaid flowchart.
560
+ #
406
561
  # "What can I do to this, what can I ask about it" — one file per
407
562
  # holder, unlike every other diagram here: `dispatch.mmd` already
408
563
  # shows a command's own onward reaction chain, but never an
@@ -447,6 +602,12 @@ module Hecks
447
602
  # every other fact in this file. `append`'s own fields carry no
448
603
  # single source at all — its own field names are the fact worth
449
604
  # stating (real: `Order.AddTopping` appends `name, amount`).
605
+ #
606
+ # @param bluebook [Bluebook::Chapter] the chapter `holder` belongs to, for the
607
+ # file's re-run banner
608
+ # @param holder [Bluebook::Aggregate, Class] the aggregate or entity class (a
609
+ # `Bluebook::Entity` subclass) whose commands and queries to draw
610
+ # @return [String] the rendered Mermaid flowchart source
450
611
  def surface_diagram(bluebook, holder)
451
612
  lines = holder.commands.map do |command|
452
613
  " #{holder.hecks_name}[(#{holder.hecks_name})] -->|does| #{command_node(holder.hecks_name, command.hecks_name)}"
@@ -464,10 +625,25 @@ module Hecks
464
625
  "#{header(bluebook.name, subject)}flowchart LR\n#{lines.uniq.join("\n")}\n"
465
626
  end
466
627
 
628
+ # Mermaid diamond-shaped node id and label for one query, qualified by
629
+ # its owning aggregate.
630
+ #
631
+ # @param aggregate_name [String] the query's owning aggregate/entity name
632
+ # @param query_name [String] the query's own name
633
+ # @return [String] the rendered `qry_<aggregate>_<query>{"..."}` node
467
634
  def query_node(aggregate_name, query_name)
468
635
  %(qry_#{aggregate_name}_#{query_name}{"#{aggregate_name}.#{query_name}"})
469
636
  end
470
637
 
638
+ # One flowchart edge from a command to one attribute it mutates,
639
+ # labeled with what the mutation writes (see `mutation_label`).
640
+ #
641
+ # @param holder [Bluebook::Aggregate, Class] the aggregate or entity class (a
642
+ # `Bluebook::Entity` subclass) that declares `command` and the mutated attribute
643
+ # @param command [Class] the command class (a `Bluebook::Command` subclass) that
644
+ # declares `mutation`
645
+ # @param mutation [Bluebook::Mutation] the mutation to draw
646
+ # @return [String] the rendered `-->|"..."|` edge line
471
647
  def mutation_edge(holder, command, mutation)
472
648
  shape = mutation.to_h
473
649
  label = mutation_label(shape)
@@ -475,6 +651,13 @@ module Hecks
475
651
  %( #{command_node(holder.hecks_name, command.hecks_name)} -->|"#{label}"| #{target})
476
652
  end
477
653
 
654
+ # The edge label naming what one mutation writes — the op, and either
655
+ # its bound field names (an append/delegate/corrects) or its value
656
+ # source (see `mutation_source_detail`).
657
+ #
658
+ # @param shape [Hash{Symbol => Object}] a `Bluebook::Mutation#to_h` result;
659
+ # reads `:op`, and either `:fields` or `:source`
660
+ # @return [String] the rendered `"<op>s: <detail>"` label text
478
661
  def mutation_label(shape)
479
662
  verb = "#{shape[:op]}s"
480
663
  # `fields:` (not `source:`) is the multi-binding shape
@@ -501,14 +684,27 @@ module Hecks
501
684
  # was caught). Swapped for a single quote here rather than
502
685
  # escaped, the same "state it, don't invent it, just make it
503
686
  # legal Mermaid" trade `read_models.mmd`'s own quoting fix made.
687
+ #
688
+ # @param source [Hash{Symbol => Object}] a classified mutation source: `{kind:
689
+ # "literal", value:}`, `{kind: "argument", name:}`, or `{kind: "state", name:}`
690
+ # @return [String] `source[:value]` quoted for a literal, `source[:name]` for an
691
+ # argument, or `source[:kind]` itself for any other kind
504
692
  def mutation_source_detail(source)
505
693
  case source[:kind]
506
694
  when "literal" then "'#{source[:value].to_s.tr('"', "'")}'"
507
695
  when "argument" then source[:name]
508
- else source[:kind] # a source kind this file has no real corpus example of yet — named, not hidden
696
+ else
697
+ # A source kind this file has no real corpus example of yet — named, not hidden.
698
+ source[:kind]
509
699
  end
510
700
  end
511
701
 
702
+ # Mermaid plain-rectangle node id and label for one attribute a
703
+ # mutation writes, qualified by its owning holder.
704
+ #
705
+ # @param holder_name [String] the attribute's owning aggregate/entity name
706
+ # @param attribute_name [Symbol, String] the mutated attribute's own name
707
+ # @return [String] the rendered `attr_<holder>_<attribute>[<attribute>]` node
512
708
  def attribute_node(holder_name, attribute_name)
513
709
  %(attr_#{holder_name}_#{attribute_name}[#{attribute_name}])
514
710
  end
@@ -544,6 +740,12 @@ module Hecks
544
740
  # dispatch declined is exactly as real a cause of a state
545
741
  # transition as an event announced, and the diagram states it
546
742
  # exactly as verbatim as every other edge here does.
743
+ #
744
+ # @param bluebook [Bluebook::Chapter] the chapter `saga` belongs to, for the
745
+ # file's re-run banner
746
+ # @param saga [Bluebook::ProcessManager] the process manager whose states and
747
+ # dispatches to draw
748
+ # @return [String] the rendered Mermaid `stateDiagram-v2` source
547
749
  def saga_diagram(bluebook, saga)
548
750
  edges = saga.handlers.map { |handler| saga_edge(handler, saga) }
549
751
 
@@ -556,7 +758,10 @@ module Hecks
556
758
  MERMAID
557
759
  end
558
760
 
559
- # The REFUSED edge's own dispatch list is partly derived now —
761
+ # One stateDiagram-v2 edge for one saga handler, labeled by the event
762
+ # that causes it and what it dispatches.
763
+ #
764
+ # The `REFUSED` edge's own dispatch list is partly derived now —
560
765
  # per-dispatch saga compensation (`compensates`) moved a saga's own
561
766
  # compensating dispatches off the hand-written `on :refused` leg
562
767
  # and onto whichever forward dispatch each one undoes, so
@@ -564,9 +769,15 @@ module Hecks
564
769
  # edge for any saga using it — accurate to the declaration, wrong
565
770
  # about what the runtime actually does at refusal (it derives and
566
771
  # fires every declared `compensates`, newest first). `saga` is
567
- # passed through for exactly this — only the REFUSED handler needs
772
+ # passed through for exactly this — only the `REFUSED` handler needs
568
773
  # it, every other edge's own `handler.dispatches` already says
569
774
  # everything real about it.
775
+ #
776
+ # @param handler [Bluebook::ProcessManagerHandler] the handler row to draw as
777
+ # an edge
778
+ # @param saga [Bluebook::ProcessManager] the process manager `handler` belongs
779
+ # to, needed only to derive the `REFUSED` edge's compensating dispatches
780
+ # @return [String] the rendered `-->` stateDiagram-v2 edge line
570
781
  def saga_edge(handler, saga)
571
782
  label = handler.event_type
572
783
  # Derived first, then the hand-written body — the same order
@@ -585,12 +796,20 @@ module Hecks
585
796
  # derives and fires (newest-first, at actual refusal time; this
586
797
  # diagram states them in declaration order, since it draws the
587
798
  # saga's own shape, not one instance's own runtime history).
799
+ #
800
+ # @param saga [Bluebook::ProcessManager] the process manager whose declared
801
+ # compensations to list
802
+ # @return [Array<String>] the name of each compensating command declared anywhere
803
+ # in `saga`, in declaration order
588
804
  def derived_compensations(saga)
589
805
  saga.handlers.flat_map { |handler| handler.dispatches.filter_map { |dispatch| dispatch.compensates&.command_name } }
590
806
  end
591
807
 
592
808
  # ── frameworks -> flowchart ─────────────────────────────────────
593
809
 
810
+ # Renders the whole domain's declared `uses_framework` attachments and
811
+ # cross-domain policy targets as a Mermaid flowchart.
812
+ #
594
813
  # Every other diagram in this file stays inside one domain's own
595
814
  # boundary — this is the one that steps outside it. A real domain
596
815
  # depends on another domain's own aggregates in exactly two ways:
@@ -629,6 +848,13 @@ module Hecks
629
848
  # caller, or a spec that doesn't care) just means no frameworks.mmd
630
849
  # — same "nothing to state" skip every other diagram here already
631
850
  # takes when its own underlying data is empty.
851
+ #
852
+ # @param bluebook [Bluebook::Chapter] the chapter to project
853
+ # @param hecksagon [Bluebook::Hecksagon, nil] the chapter's `.hecksagon`, holding
854
+ # its `framework_members`, or `nil` if the caller has none to hand in
855
+ # @return [String, nil] the rendered Mermaid flowchart source, or `nil` if
856
+ # `hecksagon` is `nil`, or the domain declares no `uses_framework` and no
857
+ # cross-domain policy target
632
858
  def frameworks_diagram(bluebook, hecksagon)
633
859
  return nil unless hecksagon
634
860
 
@@ -641,6 +867,16 @@ module Hecks
641
867
  "#{header(bluebook.name, subject)}flowchart LR\n#{lines.uniq.join("\n")}\n"
642
868
  end
643
869
 
870
+ # One flowchart edge from this domain to a dependency, dotted for a
871
+ # standing `attaches` and solid for a `reaches across` that only fires
872
+ # when a policy does.
873
+ #
874
+ # @param from [String] this domain's own name
875
+ # @param label [String] the edge label, `"attaches"` or `"reaches across"`
876
+ # @param to [String, Symbol] the dependency domain's name
877
+ # @param dotted [Boolean] whether to render a dotted (`true`) or solid (`false`)
878
+ # arrow
879
+ # @return [String] the rendered edge line
644
880
  def domain_edge(from, label, to, dotted:)
645
881
  arrow = dotted ? "-.->" : "-->"
646
882
  %( #{from}[(#{from})] #{arrow}|#{label}| #{to}[(#{to})])