hecks 1.3.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 (373) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hecks/adapters/driven/claude_code.rb +72 -7
  3. data/lib/hecks/adapters/driven/d1.rb +187 -23
  4. data/lib/hecks/adapters/driven/folder.rb +83 -10
  5. data/lib/hecks/adapters/driven/google_authentication.rb +33 -12
  6. data/lib/hecks/adapters/driven/governance_authorization.rb +87 -18
  7. data/lib/hecks/adapters/driven/heki/journal.rb +13 -4
  8. data/lib/hecks/adapters/driven/heki/saga_store.rb +56 -10
  9. data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
  10. data/lib/hecks/adapters/driven/heki.rb +106 -9
  11. data/lib/hecks/adapters/driven/identity_registry.rb +12 -2
  12. data/lib/hecks/adapters/driven/in_memory_ordering.rb +25 -3
  13. data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
  14. data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
  15. data/lib/hecks/adapters/driven/lambda/client.rb +67 -14
  16. data/lib/hecks/adapters/driven/lambda.rb +82 -35
  17. data/lib/hecks/adapters/driven/local_storage.rb +83 -10
  18. data/lib/hecks/adapters/driven/memory.rb +205 -9
  19. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +21 -1
  20. data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
  21. data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
  22. data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
  23. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
  24. data/lib/hecks/adapters/driven/postgres.rb +175 -28
  25. data/lib/hecks/adapters/driven/prism.rb +50 -11
  26. data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
  27. data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
  28. data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
  29. data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
  30. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
  31. data/lib/hecks/adapters/driven/sqlite.rb +181 -21
  32. data/lib/hecks/adapters/driven/system_clock.rb +3 -0
  33. data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
  34. data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
  35. data/lib/hecks/adapters/driven.rb +6 -4
  36. data/lib/hecks/adapters/driving/github_webhook.rb +31 -18
  37. data/lib/hecks/behaviors/dsl.rb +60 -2
  38. data/lib/hecks/behaviors/expectations.rb +190 -29
  39. data/lib/hecks/behaviors/ir.rb +12 -1
  40. data/lib/hecks/behaviors/rspec.rb +9 -1
  41. data/lib/hecks/behaviors/runner.rb +21 -2
  42. data/lib/hecks/behaviors.rb +9 -1
  43. data/lib/hecks/bluebook/aggregate.rb +43 -13
  44. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +17 -10
  45. data/lib/hecks/bluebook/assembly/build.rb +26 -5
  46. data/lib/hecks/bluebook/assembly/contract.rb +98 -23
  47. data/lib/hecks/bluebook/assembly/contracts.rb +59 -52
  48. data/lib/hecks/bluebook/assembly/marks.rb +159 -30
  49. data/lib/hecks/bluebook/assembly/specializer.rb +38 -21
  50. data/lib/hecks/bluebook/assembly.rb +32 -14
  51. data/lib/hecks/bluebook/attribute.rb +26 -12
  52. data/lib/hecks/bluebook/behaviour/aggregate.rb +43 -11
  53. data/lib/hecks/bluebook/behaviour/attribute.rb +18 -5
  54. data/lib/hecks/bluebook/behaviour/chapter.rb +76 -5
  55. data/lib/hecks/bluebook/behaviour/command.rb +55 -25
  56. data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -7
  57. data/lib/hecks/bluebook/behaviour/entity.rb +20 -8
  58. data/lib/hecks/bluebook/behaviour/hexagon.rb +30 -4
  59. data/lib/hecks/bluebook/behaviour/lifecycle.rb +27 -6
  60. data/lib/hecks/bluebook/behaviour/policy.rb +42 -17
  61. data/lib/hecks/bluebook/behaviour/process_manager.rb +39 -8
  62. data/lib/hecks/bluebook/behaviour/query.rb +6 -1
  63. data/lib/hecks/bluebook/behaviour/read_model.rb +29 -8
  64. data/lib/hecks/bluebook/behaviour/traits.rb +48 -12
  65. data/lib/hecks/bluebook/behaviour/value_object.rb +21 -9
  66. data/lib/hecks/bluebook/capabilities.rb +27 -0
  67. data/lib/hecks/bluebook/chapter.rb +51 -9
  68. data/lib/hecks/bluebook/command.rb +62 -17
  69. data/lib/hecks/bluebook/domain_port.rb +34 -9
  70. data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
  71. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
  72. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +282 -123
  73. data/lib/hecks/bluebook/dsl/attribute_collector.rb +112 -75
  74. data/lib/hecks/bluebook/dsl/binding_proxy.rb +81 -2
  75. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +486 -117
  76. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +179 -47
  77. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
  78. data/lib/hecks/bluebook/dsl/command_builder.rb +284 -122
  79. data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
  80. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
  81. data/lib/hecks/bluebook/dsl/entity_builder.rb +191 -61
  82. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
  83. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +130 -30
  84. data/lib/hecks/bluebook/dsl/identity_declaration.rb +38 -21
  85. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
  86. data/lib/hecks/bluebook/dsl/policy_builder.rb +86 -36
  87. data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
  88. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
  89. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +111 -47
  90. data/lib/hecks/bluebook/dsl/query_builder.rb +37 -8
  91. data/lib/hecks/bluebook/dsl/read_model_builder.rb +127 -52
  92. data/lib/hecks/bluebook/dsl/rule_reference.rb +97 -43
  93. data/lib/hecks/bluebook/dsl/translation_builder.rb +150 -44
  94. data/lib/hecks/bluebook/dsl/value_object_builder.rb +68 -20
  95. data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
  96. data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
  97. data/lib/hecks/bluebook/entity.rb +40 -11
  98. data/lib/hecks/bluebook/expression/ast_json.rb +128 -36
  99. data/lib/hecks/bluebook/expression/ast_reader.rb +32 -3
  100. data/lib/hecks/bluebook/expression/canonical_form.rb +55 -16
  101. data/lib/hecks/bluebook/expression/evaluator.rb +221 -43
  102. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +54 -18
  103. data/lib/hecks/bluebook/expression/resolver.rb +369 -128
  104. data/lib/hecks/bluebook/hexagon.rb +35 -1
  105. data/lib/hecks/bluebook/lifecycle.rb +12 -1
  106. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +2 -1
  107. data/lib/hecks/bluebook/meta_validator/judge.rb +126 -108
  108. data/lib/hecks/bluebook/meta_validator/plan.rb +81 -46
  109. data/lib/hecks/bluebook/meta_validator/port_judge.rb +3 -2
  110. data/lib/hecks/bluebook/meta_validator/readings.rb +200 -50
  111. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +68 -41
  112. data/lib/hecks/bluebook/meta_validator/shapes.rb +166 -21
  113. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +286 -46
  114. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +11 -10
  115. data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -5
  116. data/lib/hecks/bluebook/meta_validator.rb +235 -139
  117. data/lib/hecks/bluebook/model_check.rb +434 -104
  118. data/lib/hecks/bluebook/pattern_subset.rb +32 -10
  119. data/lib/hecks/bluebook/policy.rb +15 -13
  120. data/lib/hecks/bluebook/process_manager.rb +27 -14
  121. data/lib/hecks/bluebook/project_discovery.rb +5 -0
  122. data/lib/hecks/bluebook/project_loader.rb +40 -0
  123. data/lib/hecks/bluebook/project_register.rb +50 -6
  124. data/lib/hecks/bluebook/query.rb +31 -4
  125. data/lib/hecks/bluebook/read_model.rb +35 -15
  126. data/lib/hecks/bluebook/reference.rb +26 -13
  127. data/lib/hecks/bluebook/smoke_test.rb +46 -23
  128. data/lib/hecks/bluebook/synthesizer.rb +46 -12
  129. data/lib/hecks/bluebook/translation.rb +34 -5
  130. data/lib/hecks/bluebook/value_object.rb +29 -11
  131. data/lib/hecks/bluebook.rb +5 -6
  132. data/lib/hecks/codemod.rb +138 -50
  133. data/lib/hecks/construct.rb +21 -7
  134. data/lib/hecks/corpus.rb +438 -0
  135. data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
  136. data/lib/hecks/doc/reference.rb +200 -31
  137. data/lib/hecks/embryonaut_bluebook.rb +38 -15
  138. data/lib/hecks/facade/cli_door.rb +69 -10
  139. data/lib/hecks/facade/cli_runner.rb +105 -24
  140. data/lib/hecks/facade/command_request.rb +23 -0
  141. data/lib/hecks/facade/handle.rb +155 -35
  142. data/lib/hecks/facade/json_door.rb +106 -25
  143. data/lib/hecks/facade/surface/aggregate_door.rb +50 -27
  144. data/lib/hecks/facade/surface/chapter.rb +26 -17
  145. data/lib/hecks/facade/surface.rb +16 -3
  146. data/lib/hecks/facade.rb +15 -4
  147. data/lib/hecks/forms/app.rb +46 -30
  148. data/lib/hecks/forms/command_form_renderer.rb +70 -9
  149. data/lib/hecks/forms/field_renderer.rb +142 -6
  150. data/lib/hecks/forms/field_shape.rb +183 -19
  151. data/lib/hecks/forms/html.rb +51 -7
  152. data/lib/hecks/forms/index_renderer.rb +14 -2
  153. data/lib/hecks/forms/page.rb +14 -0
  154. data/lib/hecks/forms/params.rb +120 -23
  155. data/lib/hecks/forms/port_argument.rb +14 -2
  156. data/lib/hecks/forms/query_form_renderer.rb +65 -2
  157. data/lib/hecks/forms/record_renderer.rb +60 -2
  158. data/lib/hecks/forms/record_table.rb +28 -1
  159. data/lib/hecks/forms/reference_options.rb +24 -0
  160. data/lib/hecks/forms/value_object_shape.rb +13 -3
  161. data/lib/hecks/forms.rb +24 -4
  162. data/lib/hecks/fqn.rb +59 -1
  163. data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
  164. data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
  165. data/lib/hecks/framework/oidc.json +15 -0
  166. data/lib/hecks/framework.rb +79 -25
  167. data/lib/hecks/freezer.rb +27 -11
  168. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +241 -106
  169. data/lib/hecks/fuzzing/combination_miner.rb +178 -0
  170. data/lib/hecks/fuzzing/concurrent_dispatch.rb +241 -45
  171. data/lib/hecks/fuzzing/coverage_campaign.rb +161 -0
  172. data/lib/hecks/fuzzing/differential.rb +192 -0
  173. data/lib/hecks/fuzzing/domain_generator.rb +871 -0
  174. data/lib/hecks/fuzzing/era_boundary.rb +68 -19
  175. data/lib/hecks/fuzzing/form_census.rb +121 -20
  176. data/lib/hecks/fuzzing/generated_domain_check.rb +171 -0
  177. data/lib/hecks/fuzzing/invalid_value_generator.rb +45 -6
  178. data/lib/hecks/fuzzing/isolated_boot.rb +137 -80
  179. data/lib/hecks/fuzzing/nondeterministic.rb +79 -0
  180. data/lib/hecks/fuzzing/persistence_parity.rb +111 -21
  181. data/lib/hecks/fuzzing/properties/corrections.rb +34 -9
  182. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +300 -51
  183. data/lib/hecks/fuzzing/properties/guards.rb +86 -42
  184. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +80 -32
  185. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +40 -35
  186. data/lib/hecks/fuzzing/properties/outbox.rb +70 -32
  187. data/lib/hecks/fuzzing/properties/querying.rb +82 -28
  188. data/lib/hecks/fuzzing/properties.rb +84 -51
  189. data/lib/hecks/fuzzing/qa_settings.rb +164 -0
  190. data/lib/hecks/fuzzing/replay.rb +241 -133
  191. data/lib/hecks/fuzzing/rotation_priority.rb +48 -28
  192. data/lib/hecks/fuzzing/rust_gap_manifest.rb +139 -0
  193. data/lib/hecks/fuzzing/self_consistency.rb +315 -137
  194. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +46 -40
  195. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +18 -11
  196. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +13 -12
  197. data/lib/hecks/fuzzing/sequence_generator/picker.rb +21 -12
  198. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +59 -32
  199. data/lib/hecks/fuzzing/sequence_generator.rb +158 -23
  200. data/lib/hecks/fuzzing/shrinker.rb +309 -0
  201. data/lib/hecks/fuzzing/structural_skips.rb +37 -130
  202. data/lib/hecks/fuzzing/sweep_depth.rb +12 -4
  203. data/lib/hecks/fuzzing/target_capabilities.rb +107 -29
  204. data/lib/hecks/fuzzing/value_generator.rb +110 -22
  205. data/lib/hecks/fuzzing.rb +5 -0
  206. data/lib/hecks/grammar/evolve.rb +188 -12
  207. data/lib/hecks/grammar.rb +53 -7
  208. data/lib/hecks/ir.rb +51 -20
  209. data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
  210. data/lib/hecks/language/bluebook/policy.bluebook +11 -1
  211. data/lib/hecks/language/bluebook/vocabulary.bluebook +348 -13
  212. data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
  213. data/lib/hecks/language/oidc.json +5 -0
  214. data/lib/hecks/literal.rb +41 -9
  215. data/lib/hecks/naming.rb +112 -31
  216. data/lib/hecks/ports/access_control.rb +53 -2
  217. data/lib/hecks/ports/agent/answers.rb +83 -6
  218. data/lib/hecks/ports/agent.rb +119 -35
  219. data/lib/hecks/ports/authentication.rb +44 -4
  220. data/lib/hecks/ports/authorization.rb +50 -11
  221. data/lib/hecks/ports/clock.rb +42 -23
  222. data/lib/hecks/ports/extraction.rb +16 -0
  223. data/lib/hecks/ports/identity_assignment.rb +23 -2
  224. data/lib/hecks/ports/identity_generation.rb +17 -3
  225. data/lib/hecks/ports/identity_resolution.rb +17 -1
  226. data/lib/hecks/ports/key_vault.port +6 -0
  227. data/lib/hecks/ports/key_vault.rb +58 -0
  228. data/lib/hecks/ports/loading.rb +4 -0
  229. data/lib/hecks/ports/persistence/append_only.rb +172 -8
  230. data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
  231. data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
  232. data/lib/hecks/ports/persistence/execution.rb +4 -0
  233. data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
  234. data/lib/hecks/ports/persistence/plugin.rb +42 -4
  235. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
  236. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
  237. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
  238. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
  239. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
  240. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
  241. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
  242. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
  243. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
  244. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +118 -66
  245. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
  246. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +34 -5
  247. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
  248. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
  249. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
  250. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +33 -9
  251. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
  252. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +95 -6
  253. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
  254. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +314 -90
  255. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
  256. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
  257. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
  258. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +48 -8
  259. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
  260. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
  261. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
  262. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +58 -19
  263. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +96 -5
  264. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
  265. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +9 -1
  266. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
  267. data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
  268. data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
  269. data/lib/hecks/ports/persistence/repository_factory.rb +23 -3
  270. data/lib/hecks/ports/persistence/state_codec.rb +319 -0
  271. data/lib/hecks/ports/persistence.rb +36 -1
  272. data/lib/hecks/ports/projection.rb +61 -7
  273. data/lib/hecks/ports/query/in_memory.rb +41 -8
  274. data/lib/hecks/ports/query/ordering.rb +21 -6
  275. data/lib/hecks/ports/query.rb +35 -0
  276. data/lib/hecks/ports.rb +1 -0
  277. data/lib/hecks/projections/bootstrap_table.rb +147 -0
  278. data/lib/hecks/projections/diagrams.rb +315 -79
  279. data/lib/hecks/projections/glossary/html.rb +91 -3
  280. data/lib/hecks/projections/glossary/markdown.rb +50 -3
  281. data/lib/hecks/projections/glossary/mermaid.rb +49 -1
  282. data/lib/hecks/projections/glossary/sections.rb +1 -1
  283. data/lib/hecks/projections/glossary/sentences.rb +110 -5
  284. data/lib/hecks/projections/glossary.rb +173 -31
  285. data/lib/hecks/projections/ir.rb +1 -1
  286. data/lib/hecks/projections/model/deviations.rb +62 -17
  287. data/lib/hecks/projections/model.rb +75 -21
  288. data/lib/hecks/projections/oidc.rb +45 -8
  289. data/lib/hecks/projections/parser_table.rb +53 -12
  290. data/lib/hecks/projections/reference.rb +15 -3
  291. data/lib/hecks/projections/rust_vocabulary.rb +646 -0
  292. data/lib/hecks/projections/shape.rb +10 -2
  293. data/lib/hecks/projections/statements.rb +69 -22
  294. data/lib/hecks/projections/vocabulary.rb +26 -9
  295. data/lib/hecks/projections.rb +5 -3
  296. data/lib/hecks/projector/cli_projector.rb +245 -37
  297. data/lib/hecks/projector/docs_projector.rb +154 -28
  298. data/lib/hecks/projector/exporter.rb +104 -29
  299. data/lib/hecks/projector/ir_projector.rb +7 -1
  300. data/lib/hecks/projector/narrate_projector.rb +144 -23
  301. data/lib/hecks/projector/target.rb +42 -18
  302. data/lib/hecks/projector.rb +86 -18
  303. data/lib/hecks/query_ir.rb +94 -47
  304. data/lib/hecks/query_specification/common/comparators.rb +19 -3
  305. data/lib/hecks/query_specification/common/comparison.rb +109 -27
  306. data/lib/hecks/query_specification/common/dsl.rb +65 -9
  307. data/lib/hecks/query_specification/common/null_policy.rb +61 -15
  308. data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
  309. data/lib/hecks/query_specification/common/options.rb +25 -0
  310. data/lib/hecks/query_specification/field_path.rb +69 -15
  311. data/lib/hecks/query_specification/hop_path.rb +57 -20
  312. data/lib/hecks/query_specification/read_model/specification.rb +4 -0
  313. data/lib/hecks/rendering.rb +9 -3
  314. data/lib/hecks/router/namespace_installer.rb +16 -3
  315. data/lib/hecks/router.rb +56 -1
  316. data/lib/hecks/runtime/aggregate_lock.rb +20 -11
  317. data/lib/hecks/runtime/boot_gates.rb +21 -3
  318. data/lib/hecks/runtime/caller.rb +40 -8
  319. data/lib/hecks/runtime/capability_graph.rb +13 -2
  320. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +40 -41
  321. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +45 -67
  322. data/lib/hecks/runtime/command_interpreter.rb +195 -113
  323. data/lib/hecks/runtime/command_rules/admissibility.rb +231 -80
  324. data/lib/hecks/runtime/command_rules/arithmetic.rb +154 -96
  325. data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
  326. data/lib/hecks/runtime/command_rules/emission.rb +18 -1
  327. data/lib/hecks/runtime/command_rules/references.rb +145 -55
  328. data/lib/hecks/runtime/command_rules.rb +3 -0
  329. data/lib/hecks/runtime/dependency_planning.rb +56 -11
  330. data/lib/hecks/runtime/dispatcher.rb +263 -92
  331. data/lib/hecks/runtime/entity_element.rb +304 -67
  332. data/lib/hecks/runtime/entity_interpreter.rb +149 -88
  333. data/lib/hecks/runtime/errors.rb +37 -23
  334. data/lib/hecks/runtime/event.rb +15 -10
  335. data/lib/hecks/runtime/identity.rb +91 -23
  336. data/lib/hecks/runtime/instance.rb +103 -18
  337. data/lib/hecks/runtime/interpreting.rb +24 -16
  338. data/lib/hecks/runtime/invocation.rb +358 -0
  339. data/lib/hecks/runtime/loader.rb +105 -19
  340. data/lib/hecks/runtime/outbox.rb +164 -26
  341. data/lib/hecks/runtime/policy_interpreter.rb +73 -60
  342. data/lib/hecks/runtime/port_operation_interpreter.rb +42 -19
  343. data/lib/hecks/runtime/query_interpreter.rb +93 -74
  344. data/lib/hecks/runtime/reaction_invocation.rb +73 -28
  345. data/lib/hecks/runtime/read_model_interpreter.rb +60 -44
  346. data/lib/hecks/runtime/rebuild_sweep.rb +32 -4
  347. data/lib/hecks/runtime/reference_hop.rb +48 -6
  348. data/lib/hecks/runtime/refusal_wording.rb +142 -115
  349. data/lib/hecks/runtime/registry/saga_persistence.rb +32 -21
  350. data/lib/hecks/runtime/registry/verification.rb +153 -28
  351. data/lib/hecks/runtime/registry.rb +202 -32
  352. data/lib/hecks/runtime/remote_dispatcher.rb +125 -24
  353. data/lib/hecks/runtime/routing.rb +36 -154
  354. data/lib/hecks/runtime/saga_interpreter/correlation.rb +25 -27
  355. data/lib/hecks/runtime/saga_interpreter.rb +90 -76
  356. data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
  357. data/lib/hecks/runtime/tenant_check.rb +33 -13
  358. data/lib/hecks/runtime/tenant_scope.rb +23 -5
  359. data/lib/hecks/runtime/value/admission.rb +75 -30
  360. data/lib/hecks/runtime/value/coercion.rb +555 -142
  361. data/lib/hecks/runtime/value/entity_list_coercion.rb +132 -60
  362. data/lib/hecks/runtime/value.rb +71 -21
  363. data/lib/hecks/runtime.rb +39 -7
  364. data/lib/hecks/storehouse.rb +368 -72
  365. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
  366. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
  367. data/lib/hecks/version.rb +3 -3
  368. data/lib/hecks/vocabulary.rb +205 -4
  369. data/lib/hecks.rb +91 -11
  370. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +19 -11
  371. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +24 -12
  372. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +38 -14
  373. metadata +28 -2
@@ -1,8 +1,13 @@
1
1
  module Hecks
2
2
  module Bluebook
3
3
  module Behaviour
4
- # WHAT A QUERY DOES beyond holding its declared shape.
4
+ # What a query does beyond holding its declared shape.
5
5
  module Query
6
+ # Finds a declared result attribute by name.
7
+ #
8
+ # @param named [String, Symbol] the attribute's declared name
9
+ # @return [Bluebook::Attribute, nil] the attribute, or `nil` if none is declared
10
+ # by that name
6
11
  def attribute(named) = @attributes.find { |a| a.name == named.to_sym }
7
12
  end
8
13
  end
@@ -1,27 +1,38 @@
1
1
  module Hecks
2
2
  module Bluebook
3
3
  module Behaviour
4
- # WHAT A READ MODEL DOES. Its declared half is the gathered heads
4
+ # **What a read model does**. Its declared half is the gathered heads
5
5
  # and the query shape; these are readings taken off them.
6
6
  module ReadModel
7
+ # Lists the fields this read model groups rows by.
8
+ #
9
+ # @return [Array<Symbol>] each declared `group_by` field's name, in declaration order
7
10
  def group_by_fields = @group_by.map { |row| row[:field].to_sym }
8
11
 
9
12
  # `!!` rather than a bare `@count` — the DSL/reconstruction
10
13
  # boundary (ReadModel#initialize) already normalises to
11
14
  # `true`/`nil`, so this is belt and braces against a future
12
15
  # caller constructing a ReadModel by hand with `count: false`.
16
+ #
17
+ # @return [Boolean] whether this read model reduces to a row count
13
18
  def count? = !!@count
14
19
 
20
+ # Names the verb `Dispatcher#query` looks this read model up by.
21
+ #
22
+ # @return [String] this read model's name in `snake_case`
15
23
  def query_name = Naming.snake(@name)
16
24
 
17
- # WHICH GATHERED HEADS THE FILTERING APPLIES TO (ADR 0055) — plural,
25
+ # Which gathered heads the filtering applies to (ADR 0055) — plural,
18
26
  # since `where`/`order_by`/`limit`/`offset` can now each independently
19
27
  # name a many-side head via `on:` once there's more than one. A read
20
28
  # model with a single many-side head keeps the old reading: every
21
- # UNTARGETED option (plus `group_by`/`count`/`median`, still
29
+ # untargeted option (plus `group_by`/`count`/`median`, still
22
30
  # single-head-only — ADR 0055) applies to it, same as before `on:`
23
31
  # existed. With several many-side heads, only the ones actually named
24
32
  # by a targeted option are eligible.
33
+ #
34
+ # @return [Array<Symbol>] the `:as` name of each many-side head that filtering
35
+ # applies to; `[]` if this read model has no many-side head
25
36
  def filtered_head_names
26
37
  many = @aggregate_heads.select { |head| head[:many] }
27
38
  return [] if many.empty?
@@ -33,26 +44,36 @@ module Hecks
33
44
  end
34
45
 
35
46
  # The pre-`on:` reading (ADR 0055), unchanged: with exactly one
36
- # many-side head, every UNTARGETED option (plus `group_by`/`count`/
47
+ # many-side head, every untargeted option (plus `group_by`/`count`/
37
48
  # `median`, still single-head-only) applies to it — split out only
38
49
  # to keep `filtered_head_names` itself under this file's own
39
50
  # complexity budget, not because the two questions differ in kind.
51
+ #
52
+ # @param many [Array<Hash{Symbol => Object}>] the read model's many-side
53
+ # `aggregate_heads` rows; must hold exactly one
54
+ # @return [Array<Symbol>] `[the one head's :as name]` if any filtering option is
55
+ # declared, else `[]`
40
56
  def single_filtered_head_name(many)
41
57
  declared = wheres.any? || order_by || limit || offset || authorization&.tenant ||
42
58
  @group_by.any? || count? || @median_field
43
59
  declared ? [many.first[:as]] : []
44
60
  end
45
61
 
46
- # THE where/order_by/limit/offset THAT APPLY TO ONE ELIGIBLE HEAD
62
+ # The where/order_by/limit/offset that apply to one eligible head
47
63
  # (ADR 0055) — a small view `Ports::Query::InMemory.execute` reads
48
64
  # exactly the way it already reads a whole `Query`/`ReadModel`
49
65
  # (`.wheres`/`.order_by`/`.limit`/`.offset`/`.null_semantics`), scoped
50
- # to `head_as`'s own aggregate: an UNTARGETED option applies when
51
- # `head_as` is the read model's ONE many-side head (the pre-`on:`
52
- # reading, unchanged) ; a TARGETED one applies when its `target`
66
+ # to `head_as`'s own aggregate: an untargeted option applies when
67
+ # `head_as` is the read model's one many-side head (the pre-`on:`
68
+ # reading, unchanged) ; a targeted one applies when its `target`
53
69
  # resolves to `head_as`'s own aggregate.
54
70
  FilteredOptions = Struct.new(:wheres, :order_by, :limit, :offset, :null_semantics)
55
71
 
72
+ # Scopes this read model's filtering options down to one included head.
73
+ #
74
+ # @param head_as [Symbol] the `:as` name of the head to scope filtering to
75
+ # @return [FilteredOptions] the `wheres`/`order_by`/`limit`/`offset` that apply to
76
+ # `head_as`, and this read model's own `null_semantics`
56
77
  def options_for(head_as)
57
78
  many = @aggregate_heads.select { |head| head[:many] }
58
79
  aggregate_name = @aggregate_heads.find { |head| head[:as] == head_as }&.fetch(:aggregate)
@@ -1,7 +1,7 @@
1
1
  module Hecks
2
2
  module Bluebook
3
3
  module Behaviour
4
- # THE BEHAVIOUR MORE THAN ONE CONSTRUCT SHARES.
4
+ # The behaviour more than one construct shares.
5
5
  #
6
6
  # None of this is new duplication. `attribute(named)` was written
7
7
  # out four separate times — on Aggregate, Entity, Command and
@@ -11,23 +11,27 @@ module Hecks
11
11
  # files reads as four methods, and the same method in four modules
12
12
  # named `Behaviour::*` reads as one repeated.
13
13
  #
14
- # Written to work whether the construct is INCLUDED into (an
15
- # ordinary object like Aggregate) or EXTENDED into (a class-shaped
14
+ # Written to work whether the construct is included into (an
15
+ # ordinary object like Aggregate) or extended into (a class-shaped
16
16
  # one like Command), because every one of them reads instance
17
17
  # variables and nothing else.
18
18
 
19
- # A CONSTRUCT WHOSE IDENTITY IS A JOIN OF DECLARED PATHS.
19
+ # A construct whose identity is a join of declared paths.
20
20
  #
21
- # THE PATHS, IN DECLARATION ORDER, because the identity IS their join.
21
+ # The paths, in declaration order, because the identity is their join.
22
22
  # "number.value" says which field carries the identity ; several paths
23
23
  # say the identity is made of several facts, which is what anything
24
24
  # named beneath another thing needs. `identity_heads` are the attributes
25
25
  # those paths start at — what every reader that looks up or coerces an
26
26
  # attribute actually wants — and `identified_by` is the single head,
27
- # offered ONLY when there is one path to have a head of. A composite has
27
+ # offered only when there is one path to have a head of. A composite has
28
28
  # no single head, and answering with the first would be a guess ; the
29
29
  # readers that need all of them ask for `identity_heads`.
30
30
  module Identified
31
+ # Derives `identity_paths`, `identity_heads` and the single-head form of
32
+ # `identified_by` from the declared `identified_by` path(s).
33
+ #
34
+ # @return [void]
31
35
  def derive_identity
32
36
  @identity_paths = Array(@identified_by).map(&:to_s).reject(&:empty?)
33
37
  @identity_heads = @identity_paths.map { |path| path.split(".").first.to_sym }.uniq
@@ -35,7 +39,7 @@ module Hecks
35
39
  end
36
40
  end
37
41
 
38
- # A CONSTRUCT THAT ANSWERS FOR ITS OWN DECLARATIONS BY NAME.
42
+ # A construct that answers for its own declarations by name.
39
43
  #
40
44
  # Indexed once, since the declarations are final by the time the
41
45
  # construct exists — every dispatch asks these finders by name, and a
@@ -43,35 +47,67 @@ module Hecks
43
47
  # had already settled at boot.
44
48
  #
45
49
  # `index_by_name` takes the collections to index rather than naming
46
- # them, because WHICH collections a construct has is exactly the part
50
+ # them, because which collections a construct has is exactly the part
47
51
  # that differs: an aggregate has five, an entity three, a command one.
48
52
  module Indexed
49
- # Keyed by SYMBOL — an attribute is asked for by its declared
53
+ # Keyed by symbol — an attribute is asked for by its declared
50
54
  # symbol name everywhere in the runtime.
55
+ #
56
+ # @param attributes [Array<Bluebook::Attribute>] the construct's declared
57
+ # attributes
58
+ # @return [void]
51
59
  def index_attributes(attributes)
52
60
  @attributes_by_name = attributes.to_h { |held| [held.name, held] }
53
61
  end
54
62
 
55
- # Keyed by STRING and by `hecks_name` — a command, query or value
63
+ # Keyed by string and by `hecks_name` — a command, query or value
56
64
  # object is a construct whose Ruby `name` is something else
57
65
  # entirely (a constant path, or nothing at all for an anonymous
58
- # class), so the DECLARED name is the only one worth indexing.
66
+ # class), so the declared name is the only one worth indexing.
67
+ #
68
+ # @param collection [Array<#hecks_name>] declared constructs to index —
69
+ # value objects, commands or queries, each answering `hecks_name`
70
+ # @return [Hash{String => Object}] the collection keyed by each member's
71
+ # `hecks_name`
59
72
  def index_by_hecks_name(collection)
60
73
  collection.to_h { |held| [held.hecks_name, held] }
61
74
  end
62
75
 
76
+ # Finds a declared attribute by its declared name.
77
+ #
78
+ # @param named [String, Symbol] the attribute's declared name
79
+ # @return [Bluebook::Attribute, nil] the attribute named `named`, or `nil`
80
+ # if none is declared under that name
63
81
  def attribute(named) = @attributes_by_name[named.to_sym]
82
+
83
+ # Finds a declared command by its declared name.
84
+ #
85
+ # @param named [String, Symbol] the command's declared name
86
+ # @return [Class, nil] the command class (a `Bluebook::Command` subclass)
87
+ # named `named`, or `nil` if none is declared under that name
64
88
  def command(named) = @commands_by_name[named.to_s]
89
+
90
+ # Finds a declared query by its declared name.
91
+ #
92
+ # @param named [String, Symbol] the query's declared name
93
+ # @return [Bluebook::Query, nil] the query named `named`, or `nil` if none
94
+ # is declared under that name
65
95
  def query(named) = @queries_by_name[named.to_s]
66
96
  end
67
97
 
68
- # A CONSTRUCT THAT OWNS WHAT IT DECLARES.
98
+ # A construct that owns what it declares.
69
99
  #
70
100
  # Owner links are only ever read lazily — hecks_fqn at ask time,
71
101
  # Reference#resolve at dispatch time — so the moment the construct
72
102
  # is complete is the right stamping point: its declarations are
73
103
  # final, and nothing outside needs to remember to stamp them.
74
104
  module Owns
105
+ # Stamps each of `children` as owned by this construct.
106
+ #
107
+ # @param children [Array<Object>] the construct's own declared children —
108
+ # arrays or bare objects, flattened before stamping — each answering
109
+ # `hecks_owner=`
110
+ # @return [void]
75
111
  def stamp(*children)
76
112
  children.flatten.each { |child| child.hecks_owner = self }
77
113
  end
@@ -1,35 +1,47 @@
1
1
  module Hecks
2
2
  module Bluebook
3
3
  module Behaviour
4
- # WHAT A VALUE OBJECT DOES. EXTENDED, not included — a value object
5
- # is a CLASS (`Class.new(self)`, one per declared shape), so its
4
+ # **What a value object does**. Extended, not included — a value object
5
+ # is a class (`Class.new(self)`, one per declared shape), so its
6
6
  # behaviour is singleton behaviour, and the holding half's `absorb`
7
7
  # is what a generated constructor would be.
8
8
  module ValueObject
9
- # A one_of DECLARED but left empty used to be indistinguishable from no
10
- # one_of at all — both are `members: []` — so the rule about it could
11
- # only live in the builder. Recording the declaration lets the language
12
- # judge it, the same way an empty attribute NAME survives into the IR
13
- # and is judged there.
9
+ # Recorded as its own fact (`closed_set`), not left to be inferred from
10
+ # `members: []` alone — an empty `one_of` and no `one_of` at all would
11
+ # otherwise be indistinguishable, which would leave the rule about it
12
+ # only livable in the builder. Recording the declaration lets the
13
+ # language judge it instead, the same way an empty attribute name
14
+ # survives into the IR and is judged there.
15
+ #
16
+ # @return [Boolean] whether this value object declares `one_of`, even
17
+ # if left empty
14
18
  def closed_set? = @closed_set
15
19
 
20
+ # Finds a declared attribute by its declared name.
21
+ #
22
+ # @param named [String, Symbol] the attribute's declared name
23
+ # @return [Bluebook::Attribute, nil] the attribute named `named`, or
24
+ # `nil` if none is declared under that name
16
25
  def attribute(named) = attributes.find { |held| held.name == named.to_sym }
17
26
 
18
27
  # A single-attribute value object (EmailAddress{address},
19
- # CustomerNumber{value}) is a NAME for a scalar, not a genuine
28
+ # CustomerNumber{value}) is a name for a scalar, not a genuine
20
29
  # group — [[feedback_name_the_scalar_field]]. `adapters/driven/
21
30
  # sql_query_builder.rb` and `fuzzing/invalid_value_generator.rb`
22
31
  # now read through this rather than inlining the check.
23
32
  #
24
33
  # `forms/field_shape.rb`'s own two `attributes.first.name` sites
25
34
  # (`closed_set_options`/`closed_set_field`) look identical but are
26
- # NOT the same question — they pick a closed set's DISCRIMINANT
35
+ # not the same question — they pick a closed set's discriminant
27
36
  # column, and a closed set can be genuinely multi-attribute
28
37
  # (`Runtime::Value::Admission#member_matches?`'s own comment
29
38
  # names a real one: `StatementFrequency`'s `cadence`/
30
39
  # `retention_months`/`paper_fee_cents`). `sole_attribute` would
31
40
  # return `nil` for that shape and break the discriminant lookup —
32
41
  # left as `.first` on purpose, not a missed migration.
42
+ #
43
+ # @return [Bluebook::Attribute, nil] this value object's only
44
+ # attribute, or `nil` when it has zero or more than one
33
45
  def sole_attribute
34
46
  attributes.first if attributes.size == 1
35
47
  end
@@ -0,0 +1,27 @@
1
+ module Hecks
2
+ module Bluebook
3
+ # What a chapter may declare it provides, and what each capability
4
+ # must name. A chapter that `provides "authorization"` is trusted in
5
+ # place of a check for the literal name "Governance" — by the role
6
+ # check at dispatch, the ungoverned-role refusal at boot, the
7
+ # authorization adapter and the fuzzer — so the declaration has to be
8
+ # complete and point at real verbs. `BluebookBuilder::Validation
9
+ # #validate_provisions!` holds every `provides` to this table.
10
+ #
11
+ # key => :command | :query the kind of verb that key must name
12
+ module Capabilities
13
+ AUTHORIZATION = "authorization".freeze
14
+
15
+ CONTRACTS = {
16
+ AUTHORIZATION => {
17
+ # every assignment an actor holds, current or historical
18
+ assignments: :query,
19
+ # the command that grants an actor a role
20
+ grant: :command,
21
+ # every grant of one role acting as another
22
+ transitions: :query
23
+ }.freeze
24
+ }.freeze
25
+ end
26
+ end
27
+ end
@@ -1,29 +1,30 @@
1
1
  require_relative "behaviour/chapter"
2
+ require_relative "capabilities"
2
3
  require_relative "../ir"
3
4
 
4
5
  module Hecks
5
6
  module Bluebook
6
- # THE CHAPTER, and no longer the namespace it lives in.
7
+ # The chapter, and no longer the namespace it lives in.
7
8
  #
8
- # `Hecks::Bluebook` was briefly a CLASS, because dropping the
9
+ # `Hecks::Bluebook` was briefly a class, because dropping the
9
10
  # old `IR::` segment would otherwise have produced
10
11
  # `Bluebook::Bluebook` — a class shadowing its own enclosing module.
11
12
  # Naming the construct what the code already called it everywhere
12
13
  # (a chapter; this file was already chapter.rb) removes the clash
13
14
  # at the source, so the namespace goes back to being a module.
14
15
  #
15
- # Measured before changing: `Bluebook` was used AS a class in five
16
+ # Measured before changing: `Bluebook` was used as a class in five
16
17
  # places and as a namespace in seventy-seven files.
17
18
  class Chapter
18
19
  include Construct
19
20
  include Behaviour::Chapter
20
21
  include Hecks::IR
21
22
 
22
- # THE SCHEMA'S OWN VERSION — not a domain's `version:` (Banking's
23
- # "v1", a business fact the AUTHOR chose), but the shape `to_h`
23
+ # **The schema's own version** — not a domain's `version:` (Banking's
24
+ # "v1", a business fact the author chose), but the shape `to_h`
24
25
  # itself emits. A consumer reading exported IR with no Ruby DSL to
25
26
  # cross-check against (a build-time generator, a stored snapshot)
26
- # needs to know WHICH shape it's holding before it can safely
27
+ # needs to know which shape it's holding before it can safely
27
28
  # interpret any of the rest of it. Bump this when `to_h`'s own
28
29
  # shape changes in a way a consumer would need to know about —
29
30
  # not when a domain's own declarations change.
@@ -40,7 +41,7 @@ module Hecks
40
41
  # `bluebook.formerly_known_as` off the live Ruby object) and the
41
42
  # meta-validator's cache key is `SHA256(JSON(bluebook.to_h))` — so
42
43
  # a fact this method didn't spell was a fact two chapters
43
- # differing ONLY by their old name could hash identically on,
44
+ # differing only by their old name could hash identically on,
44
45
  # same as read-model filters before them. Spelled here for the
45
46
  # same reason `version`/`vision` are: a plain field, present
46
47
  # (possibly nil) rather than silently absent.
@@ -50,15 +51,55 @@ module Hecks
50
51
  policies: many(:policies),
51
52
  process_managers: many(:process_managers),
52
53
  attaches_to: :attaches_to,
54
+ # A capability this chapter answers (`provides "authorization",
55
+ # grant: "RoleAssignment.Assign", ...`), one row per key, in the
56
+ # order written. Present (possibly empty) on every chapter, the
57
+ # same reading `attaches_to` beside it gives.
58
+ provides: -> { provides.map(&:to_h) },
53
59
  canonical_form: -> { Expression::CanonicalForm.table }
54
60
  )
55
61
 
62
+ # **One row of a declared capability** — `verb` is chapter-local
63
+ # ("RoleAssignment.Assign"); `Behaviour::Chapter#provided_verb`
64
+ # qualifies it with the chapter's own name.
65
+ Provision = Struct.new(:capability, :key, :verb, keyword_init: true) do
66
+ # Coerces one declared or reconstructed `provides` row into a `Provision`.
67
+ #
68
+ # @param row [Bluebook::Chapter::Provision, #to_h] a `Provision` already, or
69
+ # anything answering `to_h` with `capability`/`key`/`verb` entries
70
+ # @return [Bluebook::Chapter::Provision] `row` itself if it already is one, else
71
+ # a new `Provision` built from its fields
72
+ def self.from(row)
73
+ return row if row.is_a?(self)
74
+
75
+ fields = row.to_h.transform_keys(&:to_sym)
76
+ new(capability: fields[:capability].to_s, key: fields[:key].to_s, verb: fields[:verb].to_s)
77
+ end
78
+ end
79
+
56
80
  attr_reader :name, :version, :vision, :aggregates, :policies, :process_managers,
57
- :classification, :read_models, :ports, :formerly_known_as, :attaches_to
81
+ :classification, :read_models, :ports, :formerly_known_as, :attaches_to, :provides
58
82
 
83
+ # @param name [String, Symbol] the chapter's declared name
84
+ # @param version [String, Symbol, nil] the chapter's declared business version,
85
+ # such as `"v1"`, or `nil` if it declares none
86
+ # @param vision [String, nil] the chapter's declared vision statement
87
+ # @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates
88
+ # @param policies [Array<Bluebook::Policy>] every reaction declared across the
89
+ # chapter's own aggregates, hoisted here
90
+ # @param process_managers [Array<Bluebook::ProcessManager>] the chapter's declared
91
+ # process managers
92
+ # @param classification [String, Symbol, nil] whether this chapter is central to
93
+ # its project's own domain model, or `nil` if undeclared
94
+ # @param read_models [Array<Bluebook::ReadModel>] the chapter's declared read models
95
+ # @param formerly_known_as [String, Symbol, nil] an earlier name this domain was
96
+ # known under, or `nil` if it was never renamed
97
+ # @param attaches_to [Array<String, Symbol>] the contexts this chapter attaches to
98
+ # @param provides [Array<Bluebook::Chapter::Provision, Hash>] the capabilities this
99
+ # chapter declares it provides
59
100
  def initialize(name:, version: nil, vision: nil, aggregates: [], policies: [],
60
101
  process_managers: [], classification: nil, read_models: [], formerly_known_as: nil,
61
- attaches_to: [])
102
+ attaches_to: [], provides: [])
62
103
  @policies = policies
63
104
  @process_managers = process_managers
64
105
  @name = name.to_s
@@ -71,6 +112,7 @@ module Hecks
71
112
  @classification = classification&.to_s
72
113
  @formerly_known_as = formerly_known_as&.to_s
73
114
  @attaches_to = Array(attaches_to).map(&:to_s)
115
+ @provides = Array(provides).map { |row| Provision.from(row) }
74
116
  settle
75
117
  end
76
118
  end
@@ -10,14 +10,12 @@ module Hecks
10
10
  include Hecks::IR
11
11
  include Behaviour::Mutation
12
12
 
13
- # `sign:` — item #5 of the whole-project table-unification survey.
14
- # `increment`/`decrement`'s own +1/-1 used to be re-derived from the
15
- # op NAME by two independent Rust codegen scripts (rust/project/
16
- # mutations.rb, rust/codegen/src/mutations.rs — a ternary on
17
- # `op == "increment"` in each), even though the fact was already
18
- # table-driven on the Ruby runtime side
19
- # (Runtime::CommandRules::Arithmetic::MUTATION_OPS, held equal to
20
- # Vocabulary::MutationOp by spec/vocabulary_conformance_spec.rb).
13
+ # `sign:` — item #5 of the whole-project table-unification survey:
14
+ # `increment`/`decrement`'s own +1/-1 comes from `Vocabulary::MutationOp`
15
+ # here, the same table `Runtime::CommandRules::Arithmetic::MUTATION_OPS`
16
+ # already reads on the Ruby runtime side, rather than a ternary on the op
17
+ # name re-deriving it independently in each of two Rust codegen scripts
18
+ # (rust/project/mutations.rb, rust/codegen/src/mutations.rs).
21
19
  # Reads `Vocabulary::MutationOp` directly (plain data, no framework
22
20
  # dependency — safe during parsing, same reason `RuleReference`'s
23
21
  # own bootstrap concerns don't apply here) rather than requiring
@@ -25,20 +23,29 @@ module Hecks
25
23
  # real layering inversion (runtime depends on bluebook, not the
26
24
  # reverse). "" (not nil) for ops with no sign, matching every other
27
25
  # optional IR text field's own absent-is-empty-string convention.
26
+ #
27
+ # @param oper [String, Symbol] the mutation's operation name, such as
28
+ # `"increment"` or `"decrement"`
29
+ # @return [String] the operation's sign from `Vocabulary::MutationOp`
30
+ # (`"+"` or `"-"`), or `""` when the op has none or is not found
28
31
  def self.sign_for(oper)
29
32
  Hecks::Vocabulary.rows("MutationOp").find { |row| row["name"] == oper.to_s }&.fetch("sign", "") || ""
30
33
  end
31
34
 
32
35
  emits_ir(target: :target, op: :op, sign: -> { Mutation.sign_for(op) })
33
36
 
34
- # THE ONE GENUINELY BRANCHING EMISSION in the model: an append (or a
35
- # DELEGATE — `CommandBuilder#delegates_to`'s own comment gives the
37
+ # The one genuinely branching emission in the model: an append (or a
38
+ # delegate — `CommandBuilder#delegates_to`'s own comment gives the
36
39
  # full reasoning for reusing this exact wire shape rather than
37
40
  # inventing a parallel one) binds several fields at once and carries
38
41
  # `fields:`, everything else carries a single `source:`. Declared
39
42
  # emission covers the fixed head; `super` supplies it and this adds
40
43
  # the tail, which is why a construct with a variable shape needs no
41
44
  # new mixin API.
45
+ # @return [Hash{Symbol => Object}] the declared emission — `super`'s fixed
46
+ # head, plus `fields:` (a Hash of bound field values) for an
47
+ # append-shaped op, or `source:` (`classified_source`'s own result) for
48
+ # any other
42
49
  def to_h
43
50
  return super.merge(fields: appended_fields) if [:append, :delegate, :corrects].include?(op)
44
51
 
@@ -46,9 +53,9 @@ module Hecks
46
53
  end
47
54
  end
48
55
 
49
- # A command, as a RUBY CLASS.
56
+ # A command, as a Ruby class.
50
57
  #
51
- # NOT nested as a constant, and that is a finding rather than a shortcut. A
58
+ # Not nested as a constant, and that is a finding rather than a shortcut. A
52
59
  # command and a value object may legitimately share a name inside one
53
60
  # aggregate — the language does it six times, and means it: the command
54
61
  # `Argument` is the verb that appends to the `arguments` list whose element
@@ -56,10 +63,10 @@ module Hecks
56
63
  # So `Bluebook::Command::Argument` cannot be both, and a single constant
57
64
  # namespace cannot index a kind-ambiguous name. The same follows for
58
65
  # `hecks_fqn` : `Bluebook::Command.Argument` names both, which is why the judge's
59
- # ids only work per-category, each in its own repository. IDENTITY IS
60
- # (KIND, FQN), not fqn.
66
+ # ids only work per-category, each in its own repository. Identity is
67
+ # (kind, FQN), not fqn.
61
68
  #
62
- # It is a declaration holder anyway, because that is where the EDGES live.
69
+ # It is a declaration holder anyway, because that is where the edges live.
63
70
  # `acts_on` answers with the owning construct itself — the Aggregate,
64
71
  # or the entity holder for a piece's verb — rather than the name of one.
65
72
  # The invocation door (`pizza.add_topping`) is the facade's business, a
@@ -79,8 +86,8 @@ module Hecks
79
86
  ensures: -> { ensures.map { |rule| Expression::AstJson.rule_row(rule) } },
80
87
  mutations: many(:mutations),
81
88
  emits: :emits,
82
- # THE LIFECYCLE STATE THIS COMMAND IS ADMISSIBLE FROM (S10, ADR
83
- # 0025 — "lifecycle state becomes a command guard") — a GUARD,
89
+ # The lifecycle state this command is admissible from (S10, ADR
90
+ # 0025 — "lifecycle state becomes a command guard") — a guard,
84
91
  # not a transition: `command "Debit", from: "open"` replaces
85
92
  # `given("account is open") { status == "open" }`, checked
86
93
  # against the owning construct's own lifecycle field the same
@@ -95,6 +102,29 @@ module Hecks
95
102
  attr_reader :role, :goal, :attributes, :givens, :ensures, :mutations, :emits, :references,
96
103
  :from, :provenance
97
104
 
105
+ # Mints one command as its own anonymous class, a subclass of whichever
106
+ # `Command`-descended class `declare` is called on.
107
+ #
108
+ # @param name [String, Symbol] the command's declared name
109
+ # @param role [String, nil] the command's declared role text
110
+ # @param goal [String, nil] the command's declared goal text
111
+ # @param attributes [Array<Bluebook::Attribute>] the command's declared
112
+ # arguments
113
+ # @param givens [Array<Bluebook::Given>] the command's declared
114
+ # preconditions
115
+ # @param ensures [Array<Bluebook::Given>] the command's declared
116
+ # postconditions
117
+ # @param mutations [Array<Bluebook::Mutation>] the state changes this
118
+ # command applies
119
+ # @param emits [Array<String>] the event names this command may emit
120
+ # @param references [String, Symbol, nil] the aggregate name this verb's
121
+ # `reference_to` addresses, or `nil` for a verb declared on the
122
+ # aggregate it acts on
123
+ # @param from [String, Array<String>, nil] the lifecycle state(s) this
124
+ # command is admissible from, or `nil` for no such guard
125
+ # @param provenance [Object, nil] the command's declared canonical
126
+ # source, captured exactly as written, or `nil` if it declares none
127
+ # @return [Class] the new command class
98
128
  def declare(name:, role: nil, goal: nil, attributes: [], givens: [], ensures: [],
99
129
  mutations: [], emits: [], references: nil, from: nil, provenance: nil)
100
130
  verb = Class.new(self)
@@ -105,6 +135,21 @@ module Hecks
105
135
  verb
106
136
  end
107
137
 
138
+ # Assigns what the language declares, then hands off to the behaviour's
139
+ # own `settle` — indexing attributes, the one thing not derivable from
140
+ # the declaration.
141
+ #
142
+ # @param role [String, nil] see `declare`
143
+ # @param goal [String, nil] see `declare`
144
+ # @param attributes [Array<Bluebook::Attribute>] see `declare`
145
+ # @param givens [Array<Bluebook::Given>] see `declare`
146
+ # @param ensures [Array<Bluebook::Given>] see `declare`
147
+ # @param mutations [Array<Bluebook::Mutation>] see `declare`
148
+ # @param emits [Array<String>] see `declare`
149
+ # @param references [String, nil] see `declare`
150
+ # @param from [String, Array<String>, nil] see `declare`
151
+ # @param provenance [Object, nil] see `declare`
152
+ # @return [Class] self, once attributes are indexed
108
153
  def absorb(role:, goal:, attributes:, givens:, ensures:, mutations:, emits:, references:,
109
154
  from: nil, provenance: nil)
110
155
  @role = role