hecks 1.2.0 → 1.4.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 (358) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hecks/adapters/driven/claude_code.rb +7 -7
  3. data/lib/hecks/adapters/driven/d1.rb +187 -23
  4. data/lib/hecks/adapters/driven/folder.rb +10 -10
  5. data/lib/hecks/adapters/driven/google_authentication.rb +8 -8
  6. data/lib/hecks/adapters/driven/governance_authorization.rb +31 -13
  7. data/lib/hecks/adapters/driven/heki/journal.rb +60 -2
  8. data/lib/hecks/adapters/driven/heki/saga_store.rb +5 -5
  9. data/lib/hecks/adapters/driven/heki.rb +13 -7
  10. data/lib/hecks/adapters/driven/identity_registry.rb +2 -2
  11. data/lib/hecks/adapters/driven/in_memory_ordering.rb +3 -3
  12. data/lib/hecks/adapters/driven/lambda/client.rb +34 -9
  13. data/lib/hecks/adapters/driven/lambda.rb +39 -33
  14. data/lib/hecks/adapters/driven/local_storage.rb +17 -10
  15. data/lib/hecks/adapters/driven/memory.rb +205 -9
  16. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +1 -1
  17. data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
  18. data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
  19. data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
  20. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
  21. data/lib/hecks/adapters/driven/postgres.rb +175 -28
  22. data/lib/hecks/adapters/driven/postgres_era.adapter +5 -0
  23. data/lib/hecks/adapters/driven/prism.rb +4 -4
  24. data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
  25. data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
  26. data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
  27. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
  28. data/lib/hecks/adapters/driven/sqlite.rb +181 -21
  29. data/lib/hecks/adapters/driven.rb +4 -4
  30. data/lib/hecks/adapters/driving/github_webhook.rb +145 -0
  31. data/lib/hecks/behaviors/dsl.rb +2 -2
  32. data/lib/hecks/behaviors/expectations.rb +51 -23
  33. data/lib/hecks/behaviors/ir.rb +1 -1
  34. data/lib/hecks/behaviors/rspec.rb +1 -1
  35. data/lib/hecks/behaviors/runner.rb +2 -2
  36. data/lib/hecks/behaviors.rb +1 -1
  37. data/lib/hecks/bluebook/aggregate.rb +13 -13
  38. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +10 -10
  39. data/lib/hecks/bluebook/assembly/build.rb +1 -1
  40. data/lib/hecks/bluebook/assembly/contract.rb +39 -16
  41. data/lib/hecks/bluebook/assembly/contracts.rb +40 -38
  42. data/lib/hecks/bluebook/assembly/marks.rb +18 -18
  43. data/lib/hecks/bluebook/assembly/specializer.rb +19 -21
  44. data/lib/hecks/bluebook/assembly.rb +9 -9
  45. data/lib/hecks/bluebook/attribute.rb +9 -9
  46. data/lib/hecks/bluebook/behaviour/aggregate.rb +11 -11
  47. data/lib/hecks/bluebook/behaviour/attribute.rb +5 -5
  48. data/lib/hecks/bluebook/behaviour/chapter.rb +23 -5
  49. data/lib/hecks/bluebook/behaviour/command.rb +23 -23
  50. data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -3
  51. data/lib/hecks/bluebook/behaviour/entity.rb +8 -8
  52. data/lib/hecks/bluebook/behaviour/hexagon.rb +4 -4
  53. data/lib/hecks/bluebook/behaviour/lifecycle.rb +5 -5
  54. data/lib/hecks/bluebook/behaviour/policy.rb +12 -12
  55. data/lib/hecks/bluebook/behaviour/process_manager.rb +7 -7
  56. data/lib/hecks/bluebook/behaviour/query.rb +1 -1
  57. data/lib/hecks/bluebook/behaviour/read_model.rb +8 -8
  58. data/lib/hecks/bluebook/behaviour/traits.rb +12 -12
  59. data/lib/hecks/bluebook/behaviour/value_object.rb +6 -6
  60. data/lib/hecks/bluebook/capabilities.rb +27 -0
  61. data/lib/hecks/bluebook/chapter.rb +28 -9
  62. data/lib/hecks/bluebook/command.rb +12 -12
  63. data/lib/hecks/bluebook/domain_port.rb +9 -9
  64. data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
  65. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
  66. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +96 -96
  67. data/lib/hecks/bluebook/dsl/attribute_collector.rb +41 -41
  68. data/lib/hecks/bluebook/dsl/binding_proxy.rb +22 -2
  69. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +111 -74
  70. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +48 -30
  71. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
  72. data/lib/hecks/bluebook/dsl/command_builder.rb +103 -103
  73. data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
  74. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
  75. data/lib/hecks/bluebook/dsl/entity_builder.rb +56 -56
  76. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
  77. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +89 -30
  78. data/lib/hecks/bluebook/dsl/identity_declaration.rb +17 -17
  79. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
  80. data/lib/hecks/bluebook/dsl/policy_builder.rb +30 -21
  81. data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
  82. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
  83. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +35 -35
  84. data/lib/hecks/bluebook/dsl/query_builder.rb +5 -5
  85. data/lib/hecks/bluebook/dsl/read_model_builder.rb +34 -34
  86. data/lib/hecks/bluebook/dsl/rule_reference.rb +41 -39
  87. data/lib/hecks/bluebook/dsl/translation_builder.rb +9 -9
  88. data/lib/hecks/bluebook/dsl/value_object_builder.rb +16 -16
  89. data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
  90. data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
  91. data/lib/hecks/bluebook/entity.rb +11 -11
  92. data/lib/hecks/bluebook/expression/ast_json.rb +20 -20
  93. data/lib/hecks/bluebook/expression/ast_reader.rb +3 -3
  94. data/lib/hecks/bluebook/expression/canonical_form.rb +9 -9
  95. data/lib/hecks/bluebook/expression/evaluator.rb +18 -18
  96. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +18 -18
  97. data/lib/hecks/bluebook/expression/resolver.rb +60 -62
  98. data/lib/hecks/bluebook/hexagon.rb +1 -1
  99. data/lib/hecks/bluebook/lifecycle.rb +1 -1
  100. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -1
  101. data/lib/hecks/bluebook/meta_validator/judge.rb +122 -86
  102. data/lib/hecks/bluebook/meta_validator/plan.rb +39 -39
  103. data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -2
  104. data/lib/hecks/bluebook/meta_validator/readings.rb +43 -43
  105. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +44 -37
  106. data/lib/hecks/bluebook/meta_validator/shapes.rb +25 -21
  107. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +145 -31
  108. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +6 -6
  109. data/lib/hecks/bluebook/meta_validator/world_judge.rb +5 -5
  110. data/lib/hecks/bluebook/meta_validator.rb +70 -70
  111. data/lib/hecks/bluebook/model_check.rb +301 -84
  112. data/lib/hecks/bluebook/pattern_subset.rb +9 -9
  113. data/lib/hecks/bluebook/policy.rb +15 -13
  114. data/lib/hecks/bluebook/process_manager.rb +14 -14
  115. data/lib/hecks/bluebook/project_register.rb +6 -6
  116. data/lib/hecks/bluebook/query.rb +4 -4
  117. data/lib/hecks/bluebook/read_model.rb +14 -14
  118. data/lib/hecks/bluebook/reference.rb +8 -8
  119. data/lib/hecks/bluebook/smoke_test.rb +19 -19
  120. data/lib/hecks/bluebook/synthesizer.rb +12 -12
  121. data/lib/hecks/bluebook/translation.rb +4 -4
  122. data/lib/hecks/bluebook/value_object.rb +6 -6
  123. data/lib/hecks/bluebook.rb +2 -2
  124. data/lib/hecks/codemod/legacy_dispatch_args.rb +299 -0
  125. data/lib/hecks/codemod/legacy_dispatch_recorder.rb +186 -0
  126. data/lib/hecks/codemod.rb +36 -35
  127. data/lib/hecks/construct.rb +6 -6
  128. data/lib/hecks/corpus.rb +317 -0
  129. data/lib/hecks/deprecation.rb +95 -0
  130. data/lib/hecks/doc/reference.rb +19 -19
  131. data/lib/hecks/embryonaut_bluebook.rb +11 -11
  132. data/lib/hecks/facade/cli_door.rb +69 -10
  133. data/lib/hecks/facade/cli_runner.rb +105 -24
  134. data/lib/hecks/facade/command_request.rb +23 -0
  135. data/lib/hecks/facade/handle.rb +79 -32
  136. data/lib/hecks/facade/json_door.rb +106 -25
  137. data/lib/hecks/facade/surface/aggregate_door.rb +42 -27
  138. data/lib/hecks/facade/surface/chapter.rb +26 -17
  139. data/lib/hecks/facade/surface.rb +16 -3
  140. data/lib/hecks/facade.rb +15 -4
  141. data/lib/hecks/forms/app.rb +46 -30
  142. data/lib/hecks/forms/command_form_renderer.rb +70 -9
  143. data/lib/hecks/forms/field_renderer.rb +142 -6
  144. data/lib/hecks/forms/field_shape.rb +183 -20
  145. data/lib/hecks/forms/html.rb +51 -7
  146. data/lib/hecks/forms/index_renderer.rb +14 -2
  147. data/lib/hecks/forms/params.rb +120 -23
  148. data/lib/hecks/forms/port_argument.rb +2 -2
  149. data/lib/hecks/forms/query_form_renderer.rb +2 -2
  150. data/lib/hecks/forms/record_renderer.rb +2 -2
  151. data/lib/hecks/forms/record_table.rb +1 -1
  152. data/lib/hecks/forms/value_object_shape.rb +3 -3
  153. data/lib/hecks/forms.rb +24 -4
  154. data/lib/hecks/fqn.rb +1 -1
  155. data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
  156. data/lib/hecks/framework.rb +48 -17
  157. data/lib/hecks/freezer.rb +11 -11
  158. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +90 -90
  159. data/lib/hecks/fuzzing/combination_miner.rb +119 -0
  160. data/lib/hecks/fuzzing/concurrent_dispatch.rb +361 -0
  161. data/lib/hecks/fuzzing/coverage_campaign.rb +118 -0
  162. data/lib/hecks/fuzzing/differential.rb +158 -0
  163. data/lib/hecks/fuzzing/domain_generator.rb +694 -0
  164. data/lib/hecks/fuzzing/era_boundary.rb +124 -0
  165. data/lib/hecks/fuzzing/form_census.rb +199 -0
  166. data/lib/hecks/fuzzing/generated_domain_check.rb +95 -0
  167. data/lib/hecks/fuzzing/invalid_value_generator.rb +6 -6
  168. data/lib/hecks/fuzzing/isolated_boot.rb +226 -38
  169. data/lib/hecks/fuzzing/nondeterministic.rb +67 -0
  170. data/lib/hecks/fuzzing/persistence_parity.rb +161 -0
  171. data/lib/hecks/fuzzing/properties/corrections.rb +100 -0
  172. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +321 -41
  173. data/lib/hecks/fuzzing/properties/guards.rb +129 -26
  174. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +32 -32
  175. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +31 -11
  176. data/lib/hecks/fuzzing/properties/outbox.rb +142 -0
  177. data/lib/hecks/fuzzing/properties/querying.rb +17 -17
  178. data/lib/hecks/fuzzing/properties.rb +84 -39
  179. data/lib/hecks/fuzzing/qa_settings.rb +152 -0
  180. data/lib/hecks/fuzzing/replay.rb +224 -114
  181. data/lib/hecks/fuzzing/rotation_priority.rb +94 -0
  182. data/lib/hecks/fuzzing/rust_gap_manifest.rb +113 -0
  183. data/lib/hecks/fuzzing/self_consistency.rb +676 -0
  184. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +526 -0
  185. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +101 -30
  186. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +55 -8
  187. data/lib/hecks/fuzzing/sequence_generator/picker.rb +31 -11
  188. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +123 -32
  189. data/lib/hecks/fuzzing/sequence_generator.rb +152 -31
  190. data/lib/hecks/fuzzing/shrinker.rb +197 -0
  191. data/lib/hecks/fuzzing/structural_skips.rb +39 -0
  192. data/lib/hecks/fuzzing/sweep_depth.rb +53 -0
  193. data/lib/hecks/fuzzing/target_capabilities.rb +180 -0
  194. data/lib/hecks/fuzzing/value_generator.rb +66 -14
  195. data/lib/hecks/fuzzing.rb +11 -0
  196. data/lib/hecks/grammar/evolve.rb +10 -10
  197. data/lib/hecks/grammar.rb +7 -7
  198. data/lib/hecks/ir.rb +13 -13
  199. data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
  200. data/lib/hecks/language/bluebook/policy.bluebook +11 -1
  201. data/lib/hecks/language/bluebook/vocabulary.bluebook +365 -15
  202. data/lib/hecks/language/oidc.json +5 -0
  203. data/lib/hecks/literal.rb +9 -9
  204. data/lib/hecks/naming.rb +89 -21
  205. data/lib/hecks/ports/access_control.rb +58 -2
  206. data/lib/hecks/ports/agent/answers.rb +83 -6
  207. data/lib/hecks/ports/agent.rb +119 -35
  208. data/lib/hecks/ports/authentication.rb +44 -4
  209. data/lib/hecks/ports/authorization.rb +53 -11
  210. data/lib/hecks/ports/clock.rb +42 -23
  211. data/lib/hecks/ports/extraction.rb +16 -0
  212. data/lib/hecks/ports/identity_assignment.rb +24 -2
  213. data/lib/hecks/ports/identity_generation.rb +17 -3
  214. data/lib/hecks/ports/identity_resolution.rb +18 -1
  215. data/lib/hecks/ports/loading.rb +4 -0
  216. data/lib/hecks/ports/persistence/append_only.rb +172 -8
  217. data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
  218. data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
  219. data/lib/hecks/ports/persistence/execution.rb +4 -0
  220. data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
  221. data/lib/hecks/ports/persistence/plugin.rb +42 -4
  222. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
  223. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
  224. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
  225. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
  226. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
  227. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
  228. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
  229. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
  230. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
  231. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +160 -58
  232. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
  233. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +28 -5
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
  237. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +44 -6
  238. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
  239. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +57 -4
  240. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
  241. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +338 -83
  242. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
  243. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
  244. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
  245. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +42 -8
  246. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
  247. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
  248. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
  249. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -19
  250. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +5 -5
  251. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +1 -1
  252. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
  253. data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
  254. data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
  255. data/lib/hecks/ports/persistence/repository_factory.rb +29 -6
  256. data/lib/hecks/ports/persistence/state_codec.rb +319 -0
  257. data/lib/hecks/ports/persistence.rb +36 -1
  258. data/lib/hecks/ports/projection.rb +61 -7
  259. data/lib/hecks/ports/query/in_memory.rb +3 -3
  260. data/lib/hecks/ports/query/ordering.rb +6 -6
  261. data/lib/hecks/ports/query.rb +35 -0
  262. data/lib/hecks/projections/bootstrap_table.rb +112 -0
  263. data/lib/hecks/projections/diagrams.rb +75 -75
  264. data/lib/hecks/projections/glossary/html.rb +250 -0
  265. data/lib/hecks/projections/glossary/markdown.rb +105 -0
  266. data/lib/hecks/projections/glossary/mermaid.rb +110 -0
  267. data/lib/hecks/projections/glossary/page.css +271 -0
  268. data/lib/hecks/projections/glossary/page.js +72 -0
  269. data/lib/hecks/projections/glossary/sections.rb +17 -0
  270. data/lib/hecks/projections/glossary/sentences.rb +205 -0
  271. data/lib/hecks/projections/glossary.rb +214 -286
  272. data/lib/hecks/projections/ir.rb +1 -1
  273. data/lib/hecks/projections/model/deviations.rb +18 -17
  274. data/lib/hecks/projections/model.rb +25 -21
  275. data/lib/hecks/projections/oidc.rb +7 -7
  276. data/lib/hecks/projections/parser_table.rb +5 -5
  277. data/lib/hecks/projections/reference.rb +3 -3
  278. data/lib/hecks/projections/rust_vocabulary.rb +443 -0
  279. data/lib/hecks/projections/shape.rb +2 -2
  280. data/lib/hecks/projections/statements.rb +11 -11
  281. data/lib/hecks/projections/vocabulary.rb +9 -9
  282. data/lib/hecks/projections.rb +5 -3
  283. data/lib/hecks/projector/cli_projector.rb +29 -29
  284. data/lib/hecks/projector/docs_projector.rb +13 -13
  285. data/lib/hecks/projector/exporter.rb +42 -21
  286. data/lib/hecks/projector/ir_projector.rb +1 -1
  287. data/lib/hecks/projector/narrate_projector.rb +15 -22
  288. data/lib/hecks/projector/target.rb +13 -13
  289. data/lib/hecks/projector.rb +15 -15
  290. data/lib/hecks/query_ir.rb +47 -47
  291. data/lib/hecks/query_specification/common/comparators.rb +19 -3
  292. data/lib/hecks/query_specification/common/comparison.rb +132 -24
  293. data/lib/hecks/query_specification/common/dsl.rb +65 -9
  294. data/lib/hecks/query_specification/common/null_policy.rb +57 -13
  295. data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
  296. data/lib/hecks/query_specification/common/options.rb +25 -0
  297. data/lib/hecks/query_specification/field_path.rb +69 -15
  298. data/lib/hecks/query_specification/hop_path.rb +57 -20
  299. data/lib/hecks/query_specification/read_model/specification.rb +4 -0
  300. data/lib/hecks/rendering.rb +3 -3
  301. data/lib/hecks/router/namespace_installer.rb +3 -3
  302. data/lib/hecks/router.rb +1 -1
  303. data/lib/hecks/runtime/aggregate_lock.rb +11 -11
  304. data/lib/hecks/runtime/boot_gates.rb +3 -3
  305. data/lib/hecks/runtime/caller.rb +8 -8
  306. data/lib/hecks/runtime/capability_graph.rb +2 -2
  307. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +22 -25
  308. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +64 -67
  309. data/lib/hecks/runtime/command_interpreter.rb +159 -102
  310. data/lib/hecks/runtime/command_rules/admissibility.rb +69 -69
  311. data/lib/hecks/runtime/command_rules/arithmetic.rb +137 -91
  312. data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
  313. data/lib/hecks/runtime/command_rules/emission.rb +18 -1
  314. data/lib/hecks/runtime/command_rules/references.rb +94 -19
  315. data/lib/hecks/runtime/command_rules.rb +3 -0
  316. data/lib/hecks/runtime/dependency_planning.rb +11 -11
  317. data/lib/hecks/runtime/dispatcher.rb +283 -90
  318. data/lib/hecks/runtime/entity_element.rb +282 -46
  319. data/lib/hecks/runtime/entity_interpreter.rb +160 -69
  320. data/lib/hecks/runtime/errors.rb +19 -19
  321. data/lib/hecks/runtime/event.rb +6 -6
  322. data/lib/hecks/runtime/identity.rb +22 -22
  323. data/lib/hecks/runtime/instance.rb +39 -14
  324. data/lib/hecks/runtime/interpreting.rb +12 -12
  325. data/lib/hecks/runtime/invocation.rb +276 -0
  326. data/lib/hecks/runtime/loader.rb +14 -14
  327. data/lib/hecks/runtime/outbox.rb +23 -23
  328. data/lib/hecks/runtime/policy_interpreter.rb +54 -54
  329. data/lib/hecks/runtime/port_operation_interpreter.rb +22 -19
  330. data/lib/hecks/runtime/query_interpreter.rb +111 -56
  331. data/lib/hecks/runtime/reaction_invocation.rb +76 -9
  332. data/lib/hecks/runtime/read_model_interpreter.rb +40 -40
  333. data/lib/hecks/runtime/rebuild_sweep.rb +4 -4
  334. data/lib/hecks/runtime/reference_hop.rb +6 -6
  335. data/lib/hecks/runtime/refusal_wording.rb +92 -112
  336. data/lib/hecks/runtime/registry/saga_persistence.rb +21 -21
  337. data/lib/hecks/runtime/registry/verification.rb +36 -26
  338. data/lib/hecks/runtime/registry.rb +56 -27
  339. data/lib/hecks/runtime/remote_dispatcher.rb +38 -23
  340. data/lib/hecks/runtime/routing.rb +10 -88
  341. data/lib/hecks/runtime/saga_interpreter/correlation.rb +17 -17
  342. data/lib/hecks/runtime/saga_interpreter.rb +93 -56
  343. data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
  344. data/lib/hecks/runtime/tenant_check.rb +9 -9
  345. data/lib/hecks/runtime/tenant_scope.rb +5 -5
  346. data/lib/hecks/runtime/value/admission.rb +75 -30
  347. data/lib/hecks/runtime/value/coercion.rb +379 -226
  348. data/lib/hecks/runtime/value/entity_list_coercion.rb +248 -0
  349. data/lib/hecks/runtime/value.rb +28 -23
  350. data/lib/hecks/runtime.rb +7 -7
  351. data/lib/hecks/storehouse.rb +64 -64
  352. data/lib/hecks/version.rb +3 -3
  353. data/lib/hecks/vocabulary.rb +207 -5
  354. data/lib/hecks.rb +13 -11
  355. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +11 -11
  356. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +14 -12
  357. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +11 -11
  358. metadata +43 -2
@@ -0,0 +1,250 @@
1
+ require_relative "../../forms/html"
2
+
3
+ module Hecks
4
+ module Projections
5
+ module Glossary
6
+ # **The page, rendered from the markdown** — `html/index.html` is
7
+ # `glossary.md` read back and dressed: a navigation rail built from
8
+ # its `##` headings, each `###` a term entry, every ```mermaid fence
9
+ # a diagram, every `[x](#y)` an in-page link. Nothing here reads
10
+ # the bluebook; if the page shows it, the Markdown says it, so the
11
+ # two cannot drift.
12
+ #
13
+ # A subset renderer, not a markdown library — the projector emits
14
+ # a fixed handful of constructs (`Markdown`'s own header lists
15
+ # them), and this reads exactly those. The Gemfile keeps every
16
+ # dependency justified in its own comment; a full Markdown engine
17
+ # for six constructs would not earn one.
18
+ #
19
+ # The look lives in page.css / page.js beside this file — read at
20
+ # render time and inlined, so the page is one self-contained file.
21
+ # The only things it fetches are the two typefaces (Google Fonts)
22
+ # and Mermaid itself (cdnjs), which draws the diagrams in the
23
+ # browser; without a network the diagrams show as their source.
24
+ module Html
25
+ Block = Struct.new(:type, :text, :items, :level, keyword_init: true)
26
+
27
+ FONTS = "https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,500;" \
28
+ "0,6..72,600;1,6..72,400&family=Instrument+Sans:wght@400;500;600&display=swap".freeze
29
+ MERMAID = "https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.9.1/mermaid.min.js".freeze
30
+
31
+ module_function
32
+
33
+ def render(markdown)
34
+ blocks = parse(markdown)
35
+ slugs = heading_slugs(blocks)
36
+ title = blocks.find { |block| block.type == :heading && block.level == 1 }&.text.to_s
37
+ sections = split_sections(blocks)
38
+ page(title, sections, slugs)
39
+ end
40
+
41
+ # ── reading the Markdown ─────────────────────────────────────────
42
+
43
+ def parse(markdown)
44
+ lines = markdown.lines.map(&:chomp)
45
+ blocks = []
46
+ index = 0
47
+ while index < lines.size
48
+ line = lines[index]
49
+ if line.start_with?("```mermaid")
50
+ block, index = fence(lines, index)
51
+ blocks << block
52
+ elsif (heading = line.match(/\A(\#{1,3}) (.+)\z/))
53
+ blocks << Block.new(type: :heading, text: heading[2], level: heading[1].size)
54
+ index += 1
55
+ elsif line.start_with?("> ")
56
+ blocks << Block.new(type: :quote, text: line[2..])
57
+ index += 1
58
+ elsif line.start_with?("- ")
59
+ items, index = run(lines, index) { |text| text.start_with?("- ") }
60
+ blocks << Block.new(type: :list, items: items.map { |item| item[2..] })
61
+ elsif line.strip.empty?
62
+ index += 1
63
+ else
64
+ para, index = run(lines, index) { |text| !text.strip.empty? }
65
+ blocks << Block.new(type: :paragraph, text: para.join(" "))
66
+ end
67
+ end
68
+ blocks
69
+ end
70
+
71
+ # The mermaid source between the opening fence at `index` and its
72
+ # closing one, and the index just past that.
73
+ def fence(lines, index)
74
+ close = ((index + 1)...lines.size).find { |at| lines[at] == "```" } || lines.size
75
+ [Block.new(type: :mermaid, text: lines[(index + 1)...close].join("\n")), close + 1]
76
+ end
77
+
78
+ # The consecutive lines from `index` that satisfy the block, and
79
+ # the index just past them.
80
+ def run(lines, index)
81
+ taken = []
82
+ while index < lines.size && yield(lines[index])
83
+ taken << lines[index]
84
+ index += 1
85
+ end
86
+ [taken, index]
87
+ end
88
+
89
+ # The same slugs GitHub would give these headings, in the same
90
+ # order — so the links the Markdown carries land here too.
91
+ #
92
+ # Keyed by the block itself, not its value — a Struct compares by
93
+ # members, so two "### Open" headings in different sections would
94
+ # otherwise be one key, and the first would answer with the
95
+ # second's "-1" slug, leaving `#open` with nothing to land on.
96
+ def heading_slugs(blocks)
97
+ seen = Hash.new(0)
98
+ slugs = {}.compare_by_identity
99
+ blocks.select { |block| block.type == :heading }.each do |block|
100
+ base = Slugs.github(block.text)
101
+ seen[base] += 1
102
+ slugs[block] = seen[base] == 1 ? base : "#{base}-#{seen[base] - 1}"
103
+ end
104
+ slugs
105
+ end
106
+
107
+ # Everything before the first `##` is the front matter; each `##`
108
+ # opens a section that runs to the next.
109
+ def split_sections(blocks)
110
+ sections = [{ heading: nil, blocks: [] }]
111
+ blocks.each do |block|
112
+ sections << { heading: block, blocks: [] } if block.type == :heading && block.level == 2
113
+ sections.last[:blocks] << block
114
+ end
115
+ sections
116
+ end
117
+
118
+ # ── writing the page ─────────────────────────────────────────────
119
+
120
+ def page(title, sections, slugs)
121
+ front, *rest = sections
122
+ <<~HTML
123
+ <!doctype html>
124
+ <html lang="en">
125
+ <head>
126
+ <meta charset="utf-8">
127
+ <meta name="viewport" content="width=device-width, initial-scale=1">
128
+ <title>#{escape(title)}</title>
129
+ <link rel="stylesheet" href="#{FONTS}">
130
+ <script src="#{MERMAID}"></script>
131
+ <style>
132
+ #{asset('page.css')}
133
+ </style>
134
+ </head>
135
+ <body>
136
+ <div class="page">
137
+ #{rail(title, rest, slugs)}
138
+ <main>
139
+ #{front_matter(front[:blocks])}
140
+ #{rest.map { |section| section_html(section, slugs) }.join("\n")}
141
+ </main>
142
+ </div>
143
+ <script>
144
+ #{asset('page.js')}
145
+ </script>
146
+ </body>
147
+ </html>
148
+ HTML
149
+ end
150
+
151
+ def rail(title, sections, slugs)
152
+ domain = title.split(" — ").first
153
+ items = sections.map do |section|
154
+ heading = section[:heading]
155
+ %(<li><a href="##{slugs[heading]}">#{inline(heading.text)}</a></li>)
156
+ end
157
+ <<~HTML
158
+ <nav class="rail" aria-label="Sections">
159
+ <p class="domain">#{escape(domain)}</p>
160
+ <p class="sub">Glossary</p>
161
+ <input type="search" placeholder="Find a term" aria-label="Find a term" autocomplete="off">
162
+ <p class="count"></p>
163
+ <ol>
164
+ #{items.join("\n")}
165
+ </ol>
166
+ </nav>
167
+ HTML
168
+ end
169
+
170
+ def front_matter(blocks)
171
+ parts = ["<header>"]
172
+ blocks.each do |block|
173
+ case block.type
174
+ when :heading then parts << "<h1>#{inline(block.text.split(' — ').first)}</h1>"
175
+ when :quote then parts << "<p class=\"vision\">#{inline(block.text)}</p>"
176
+ when :paragraph then parts << "<p class=\"lede\">#{inline(block.text)}</p>"
177
+ when :mermaid then parts << figure(block, "How it all fits together")
178
+ end
179
+ end
180
+ parts << "</header>"
181
+ parts.join("\n")
182
+ end
183
+
184
+ # A section is its heading, then its opening (lede, diagrams, the
185
+ # rules list), then its terms — each `###` opens an article that
186
+ # runs to the next. A bold-only paragraph is the caption of
187
+ # whatever figure or list follows it.
188
+ def section_html(section, slugs)
189
+ heading = section[:heading]
190
+ state = { parts: [%(<section id="#{slugs[heading]}">), "<h2>#{inline(heading.text)}</h2>"],
191
+ caption: nil, in_terms: false }
192
+ section[:blocks].drop(1).each { |block| section_block(block, state, slugs) }
193
+ state[:parts] << "</article></div>" if state[:in_terms]
194
+ state[:parts] << "</section>"
195
+ state[:parts].join("\n")
196
+ end
197
+
198
+ def section_block(block, state, slugs)
199
+ parts = state[:parts]
200
+ case block.type
201
+ when :heading
202
+ parts << (state[:in_terms] ? "</article>" : "<div class=\"terms\">")
203
+ parts << %(<article class="term" id="#{slugs[block]}">) << "<h3>#{inline(block.text)}</h3>"
204
+ state[:in_terms] = true
205
+ when :mermaid
206
+ parts << figure(block, state.delete(:caption))
207
+ when :list
208
+ parts << "<p class=\"caption\">#{escape(state[:caption])}</p>" if state.delete(:caption)
209
+ parts << "<ul class=\"rules\">#{block.items.map { |item| "<li>#{inline(item)}</li>" }.join}</ul>"
210
+ when :quote
211
+ parts << "<p class=\"about\">#{inline(block.text)}</p>"
212
+ when :paragraph
213
+ paragraph(block.text, state)
214
+ end
215
+ end
216
+
217
+ def paragraph(text, state)
218
+ if text.match?(/\A\*\*[^*]+\*\*\z/)
219
+ state[:caption] = text.delete("*")
220
+ else
221
+ klass = text.start_with?("Always true:") ? " class=\"rule\"" : ""
222
+ state[:parts] << "<p#{klass}>#{inline(text)}</p>"
223
+ end
224
+ end
225
+
226
+ def figure(block, caption)
227
+ parts = ["<figure>"]
228
+ parts << "<figcaption>#{escape(caption)}</figcaption>" if caption
229
+ parts << "<pre class=\"mermaid\">#{escape(block.text)}</pre>" << "</figure>"
230
+ parts.join("\n")
231
+ end
232
+
233
+ # ── inline text ──────────────────────────────────────────────────
234
+
235
+ def escape(text) = Forms::Escape.html(text)
236
+
237
+ # Escape first, so nothing in the prose ever becomes a tag; then
238
+ # the three inline forms the Markdown uses, on the escaped text.
239
+ def inline(text)
240
+ escape(text)
241
+ .gsub(/\[([^\]]+)\]\(#([^)]+)\)/) { %(<a href="##{Regexp.last_match(2)}">#{Regexp.last_match(1)}</a>) }
242
+ .gsub(/\*\*(.+?)\*\*/) { "<strong>#{Regexp.last_match(1)}</strong>" }
243
+ .gsub(/\*(.+?)\*/) { "<em>#{Regexp.last_match(1)}</em>" }
244
+ end
245
+
246
+ def asset(name) = File.read(File.join(__dir__, name))
247
+ end
248
+ end
249
+ end
250
+ end
@@ -0,0 +1,105 @@
1
+ require_relative "sections"
2
+ require_relative "sentences"
3
+ require_relative "mermaid"
4
+ require_relative "../statements"
5
+
6
+ module Hecks
7
+ module Projections
8
+ module Glossary
9
+ # **The document itself** — `glossary.md`, the one source the page is
10
+ # rendered from. Written in the handful of Markdown constructs
11
+ # GitHub renders as-is (headings, blockquotes, ```mermaid fences,
12
+ # lists, in-page links), so the file is a complete, readable
13
+ # glossary on its own before any page is built from it:
14
+ #
15
+ # # Banking — Glossary
16
+ # > vision
17
+ # lede
18
+ # ```mermaid the map
19
+ # ## Account
20
+ # > description
21
+ # Starts out open. Can be open, frozen, or closed.
22
+ # **How it fits** ```mermaid
23
+ # **How it moves** ```mermaid
24
+ # **Always true** - one sentence per declared fact
25
+ # ### Account number one ### per term, A to Z
26
+ # Made up of a value (text).
27
+ # Always true: an account number is present.
28
+ module Markdown
29
+ LEDE = "Every term %<name>s uses, in the words of the people who work in it — grouped under the thing " \
30
+ "each belongs to, and listed A to Z within it. This page is generated from the working " \
31
+ "specification, so it says what the system does today, not what anyone hoped it would do. " \
32
+ "If a sentence here reads wrong to you, the specification is wrong: say so.".freeze
33
+
34
+ STANDING = {
35
+ ROLES => "Who does what. A role is named once here rather than under every term it touches.",
36
+ READ_MODELS => "Questions answered across more than one of the things above.",
37
+ REACTIONS => "What happens on its own, in response to something this specification does not itself raise."
38
+ }.freeze
39
+
40
+ module_function
41
+
42
+ def title(bluebook) = "#{bluebook.name} — Glossary"
43
+
44
+ def render(document)
45
+ parts = [header(document.bluebook)]
46
+ parts += document.sections.map { |section| section_text(section, document) }
47
+ "#{parts.join("\n\n")}\n"
48
+ end
49
+
50
+ def header(bluebook)
51
+ parts = ["# #{title(bluebook)}"]
52
+ parts << "> #{bluebook.vision}" if bluebook.vision
53
+ parts << format(LEDE, name: bluebook.name)
54
+ parts << fence(Mermaid.map(bluebook))
55
+ parts.join("\n\n")
56
+ end
57
+
58
+ def section_text(section, document)
59
+ parts = ["## #{section.title}"]
60
+ parts += section.aggregate ? opening(section.aggregate, document.bluebook) : ["> #{STANDING[section.name]}"]
61
+ parts += section.terms.map { |entry| term_text(entry, document.index) }
62
+ parts.join("\n\n")
63
+ end
64
+
65
+ # What an aggregate is, what it can be, how it fits and moves,
66
+ # and what is always true of it — before a single term.
67
+ def opening(aggregate, bluebook)
68
+ parts = []
69
+ parts << "> #{aggregate.description}" if aggregate.description
70
+ parts << Sentences.lifecycle_sentence(aggregate.lifecycle) if aggregate.lifecycle
71
+ parts << "**How it fits**" << fence(Mermaid.context(aggregate, bluebook))
72
+ parts << "**How it moves**" << fence(Mermaid.lifecycle(aggregate)) if aggregate.lifecycle
73
+ statements = always_true(aggregate, bluebook)
74
+ parts << "**Always true**" << statements.map { |statement| "- #{statement}" }.join("\n") unless statements.empty?
75
+ parts
76
+ end
77
+
78
+ # The statements projection's own sentences, spoken. An invariant
79
+ # is the author's words and stays exactly so. A relationship
80
+ # sentence is built mechanically from construct names ("An
81
+ # ATMCard references an Account."), and a construct name is the
82
+ # one thing this page never shows as an identifier — so each is
83
+ # replaced with how it is said, whole-word, and nothing else in
84
+ # the sentence is touched.
85
+ def always_true(aggregate, bluebook)
86
+ names = bluebook.aggregates.flat_map { |other| [other.hecks_name, *other.entities.map(&:hecks_name)] }
87
+ [aggregate, *aggregate.entities].flat_map do |holder|
88
+ Statements.attribute_statements(holder).map { |sentence| spoken(sentence, names) } +
89
+ Statements.invariant_statements(holder)
90
+ end.uniq
91
+ end
92
+
93
+ def spoken(sentence, names)
94
+ names.reduce(sentence) { |text, name| text.gsub(/\b#{Regexp.escape(name)}\b/, Naming.words(name)) }
95
+ end
96
+
97
+ def term_text(entry, index)
98
+ ["### #{entry.headword}", *Sentences.paragraphs(entry, index)].join("\n\n")
99
+ end
100
+
101
+ def fence(source) = "```mermaid\n#{source}\n```"
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,110 @@
1
+ require_relative "../../naming"
2
+
3
+ module Hecks
4
+ module Projections
5
+ module Glossary
6
+ # The three pictures the glossary draws, each kept to the handful of
7
+ # things that matter — Evans' "three to five objects central to the
8
+ # issue at hand", never the whole model at once.
9
+ #
10
+ # map the aggregates and which points at which — one small
11
+ # graph at the top, for the reader who wants the shape
12
+ # of the domain before its words.
13
+ # context one aggregate, its entities, and the neighbours it
14
+ # points at or is pointed at by. Value objects are
15
+ # words in the text ("Made up of …"), not boxes here —
16
+ # Account alone has eight, and eight boxes say less
17
+ # than one sentence.
18
+ # lifecycle the states a thing can be in and what moves it.
19
+ #
20
+ # Every label is a person's spelling (`Naming.words`), and the
21
+ # edges read from the same declared facts `Projections::Diagrams`
22
+ # draws its own from (`reference?`, `type.target_name`, `name`,
23
+ # `lifecycle.transitions`) — a different picture of the same
24
+ # declaration, not a second declaration.
25
+ module Mermaid
26
+ module_function
27
+
28
+ def map(bluebook)
29
+ names = bluebook.aggregates.map(&:hecks_name)
30
+ lines = ["flowchart LR"]
31
+ names.each { |name| lines << node(name) }
32
+ bluebook.aggregates.each do |aggregate|
33
+ references(aggregate).each do |attribute|
34
+ target = attribute.type.target_name
35
+ lines << node(target) unless names.include?(target) || lines.include?(node(target))
36
+ lines << edge(aggregate.hecks_name, attribute, target)
37
+ end
38
+ end
39
+ lines.join("\n")
40
+ end
41
+
42
+ def context(aggregate, bluebook)
43
+ focus = aggregate.hecks_name
44
+ lines = ["flowchart LR", node(focus, focus: true)]
45
+ entities(aggregate).each do |attribute|
46
+ lines << node(attribute.type.to_s)
47
+ lines << edge(focus, attribute, attribute.type.to_s)
48
+ end
49
+ references(aggregate).each do |attribute|
50
+ lines << node(attribute.type.target_name)
51
+ lines << edge(focus, attribute, attribute.type.target_name)
52
+ end
53
+ pointing_at(aggregate, bluebook).each do |holder, attribute|
54
+ lines << node(holder.hecks_name)
55
+ lines << edge(holder.hecks_name, attribute, focus)
56
+ end
57
+ lines << " classDef focus stroke-width:3px"
58
+ lines.uniq.join("\n")
59
+ end
60
+
61
+ def lifecycle(holder)
62
+ lifecycle = holder.lifecycle
63
+ lines = ["stateDiagram-v2"]
64
+ states = ([lifecycle.default] + lifecycle.transitions.map { |_name, transition| transition.target }).uniq
65
+ states.select { |state| state.to_s.include?("_") }.each do |state|
66
+ lines << " state \"#{state.to_s.tr('_', ' ')}\" as #{state}"
67
+ end
68
+ lines << " [*] --> #{lifecycle.default}"
69
+ lifecycle.transitions.each do |name, transition|
70
+ Array(transition.from).each { |from| lines << " #{from} --> #{transition.target}: #{Naming.words(name)}" }
71
+ end
72
+ lines.join("\n")
73
+ end
74
+
75
+ # ── the facts drawn ──────────────────────────────────────────────
76
+
77
+ def references(holder) = holder.attributes.select(&:reference?)
78
+
79
+ # An entity is held as `list_of(LedgerEntry)` — the attribute
80
+ # whose element type names one of the aggregate's own entities.
81
+ def entities(aggregate)
82
+ names = aggregate.entities.map(&:hecks_name)
83
+ aggregate.attributes.select { |attribute| !attribute.reference? && names.include?(attribute.type.to_s) }
84
+ end
85
+
86
+ def pointing_at(aggregate, bluebook)
87
+ bluebook.aggregates.reject { |other| other.equal?(aggregate) }.flat_map do |other|
88
+ references(other).select { |attribute| attribute.type.target_name == aggregate.hecks_name }
89
+ .map { |attribute| [other, attribute] }
90
+ end
91
+ end
92
+
93
+ # ── the drawing ──────────────────────────────────────────────────
94
+
95
+ # A node id the page source can carry without an identifier in
96
+ # it — `n_atm_card`, never `n_ATMCard`.
97
+ def id(name) = "n_#{Naming.snake(name).gsub(/[^a-z0-9_]/, '_')}"
98
+
99
+ def node(name, focus: false)
100
+ line = " #{id(name)}[\"#{Naming.words(name)}\"]"
101
+ focus ? "#{line}:::focus" : line
102
+ end
103
+
104
+ def edge(from, attribute, to)
105
+ " #{id(from)} -->|\"#{Naming.words(attribute.name).downcase}\"| #{id(to)}"
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end