hecks 2.7.0 → 2.9.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 (463) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +11 -0
  3. data/lib/hecks/adapters/driven/claude_code.rb +4 -104
  4. data/lib/hecks/adapters/driven/d1.rb +44 -237
  5. data/lib/hecks/adapters/driven/folder.rb +17 -76
  6. data/lib/hecks/adapters/driven/google_authentication.rb +15 -49
  7. data/lib/hecks/adapters/driven/governance_authorization.rb +23 -70
  8. data/lib/hecks/adapters/driven/heki/journal.rb +8 -41
  9. data/lib/hecks/adapters/driven/heki/saga_store.rb +12 -54
  10. data/lib/hecks/adapters/driven/heki/snapshot.rb +4 -12
  11. data/lib/hecks/adapters/driven/heki.rb +13 -42
  12. data/lib/hecks/adapters/driven/identity_registry.rb +3 -14
  13. data/lib/hecks/adapters/driven/in_memory_ordering.rb +9 -26
  14. data/lib/hecks/adapters/driven/in_process_key_vault.rb +2 -14
  15. data/lib/hecks/adapters/driven/lambda/client.rb +10 -51
  16. data/lib/hecks/adapters/driven/lambda.rb +10 -84
  17. data/lib/hecks/adapters/driven/local_storage.rb +5 -40
  18. data/lib/hecks/adapters/driven/memory.rb +18 -99
  19. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +5 -21
  20. data/lib/hecks/adapters/driven/postgres/codec.rb +6 -28
  21. data/lib/hecks/adapters/driven/postgres/outbox.rb +3 -12
  22. data/lib/hecks/adapters/driven/postgres/reconnect.rb +4 -30
  23. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +34 -108
  24. data/lib/hecks/adapters/driven/postgres.rb +145 -134
  25. data/lib/hecks/adapters/driven/postgres_era.adapter +4 -15
  26. data/lib/hecks/adapters/driven/prism.rb +7 -30
  27. data/lib/hecks/adapters/driven/sql_query_builder.rb +21 -121
  28. data/lib/hecks/adapters/driven/sqlite/codec.rb +8 -40
  29. data/lib/hecks/adapters/driven/sqlite/projection.rb +26 -82
  30. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +40 -84
  31. data/lib/hecks/adapters/driven/sqlite.rb +128 -65
  32. data/lib/hecks/adapters/driven/tenant_provisioner.rb +15 -37
  33. data/lib/hecks/adapters/driven.rb +5 -36
  34. data/lib/hecks/adapters/driving/github_webhook.rb +8 -76
  35. data/lib/hecks/behaviors/dsl.rb +7 -25
  36. data/lib/hecks/behaviors/expectations.rb +32 -142
  37. data/lib/hecks/behaviors/ir.rb +7 -20
  38. data/lib/hecks/behaviors/rspec.rb +6 -17
  39. data/lib/hecks/behaviors/runner.rb +10 -34
  40. data/lib/hecks/behaviors.rb +4 -8
  41. data/lib/hecks/bluebook/aggregate.rb +9 -75
  42. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +9 -38
  43. data/lib/hecks/bluebook/assembly/build.rb +4 -15
  44. data/lib/hecks/bluebook/assembly/contract.rb +15 -136
  45. data/lib/hecks/bluebook/assembly/contracts.rb +54 -174
  46. data/lib/hecks/bluebook/assembly/marks.rb +31 -223
  47. data/lib/hecks/bluebook/assembly/specializer.rb +6 -52
  48. data/lib/hecks/bluebook/assembly.rb +6 -45
  49. data/lib/hecks/bluebook/attribute.rb +12 -61
  50. data/lib/hecks/bluebook/behaviour/aggregate.rb +10 -39
  51. data/lib/hecks/bluebook/behaviour/attribute.rb +3 -14
  52. data/lib/hecks/bluebook/behaviour/chapter.rb +17 -42
  53. data/lib/hecks/bluebook/behaviour/command.rb +14 -66
  54. data/lib/hecks/bluebook/behaviour/domain_port.rb +4 -20
  55. data/lib/hecks/bluebook/behaviour/entity.rb +7 -27
  56. data/lib/hecks/bluebook/behaviour/hexagon.rb +23 -41
  57. data/lib/hecks/bluebook/behaviour/lifecycle.rb +15 -40
  58. data/lib/hecks/bluebook/behaviour/policy.rb +23 -52
  59. data/lib/hecks/bluebook/behaviour/process_manager.rb +30 -57
  60. data/lib/hecks/bluebook/behaviour/read_model.rb +38 -26
  61. data/lib/hecks/bluebook/behaviour/traits.rb +27 -64
  62. data/lib/hecks/bluebook/behaviour/value_object.rb +5 -24
  63. data/lib/hecks/bluebook/capabilities.rb +17 -49
  64. data/lib/hecks/bluebook/chapter.rb +10 -48
  65. data/lib/hecks/bluebook/command.rb +23 -81
  66. data/lib/hecks/bluebook/domain_port.rb +14 -63
  67. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +50 -229
  68. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +50 -377
  69. data/lib/hecks/bluebook/dsl/attribute_collector.rb +33 -204
  70. data/lib/hecks/bluebook/dsl/binding_proxy.rb +17 -65
  71. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +86 -708
  72. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +56 -170
  73. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
  74. data/lib/hecks/bluebook/dsl/command_builder.rb +74 -546
  75. data/lib/hecks/bluebook/dsl/const_shim.rb +8 -41
  76. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +12 -87
  77. data/lib/hecks/bluebook/dsl/entity_builder.rb +44 -330
  78. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +20 -269
  79. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +26 -189
  80. data/lib/hecks/bluebook/dsl/identity_declaration.rb +13 -88
  81. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +6 -20
  82. data/lib/hecks/bluebook/dsl/policy_builder.rb +16 -88
  83. data/lib/hecks/bluebook/dsl/port_builder.rb +2 -6
  84. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +22 -96
  85. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +31 -184
  86. data/lib/hecks/bluebook/dsl/query_builder.rb +11 -42
  87. data/lib/hecks/bluebook/dsl/read_model_builder.rb +75 -201
  88. data/lib/hecks/bluebook/dsl/rule_reference.rb +22 -163
  89. data/lib/hecks/bluebook/dsl/translation_builder.rb +28 -114
  90. data/lib/hecks/bluebook/dsl/value_object_builder.rb +10 -124
  91. data/lib/hecks/bluebook/dsl/word_gate.rb +16 -162
  92. data/lib/hecks/bluebook/dsl/world_builder.rb +19 -106
  93. data/lib/hecks/bluebook/entity.rb +13 -71
  94. data/lib/hecks/bluebook/expression/ast_json.rb +24 -190
  95. data/lib/hecks/bluebook/expression/ast_reader.rb +12 -54
  96. data/lib/hecks/bluebook/expression/canonical_form.rb +11 -88
  97. data/lib/hecks/bluebook/expression/evaluator.rb +47 -295
  98. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +30 -147
  99. data/lib/hecks/bluebook/expression/resolver.rb +37 -660
  100. data/lib/hecks/bluebook/hexagon.rb +5 -28
  101. data/lib/hecks/bluebook/lifecycle.rb +5 -16
  102. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -7
  103. data/lib/hecks/bluebook/meta_validator/judge.rb +111 -361
  104. data/lib/hecks/bluebook/meta_validator/plan.rb +33 -162
  105. data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -7
  106. data/lib/hecks/bluebook/meta_validator/readings.rb +56 -342
  107. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -176
  108. data/lib/hecks/bluebook/meta_validator/shapes.rb +54 -268
  109. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +47 -308
  110. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +7 -43
  111. data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -18
  112. data/lib/hecks/bluebook/meta_validator.rb +79 -349
  113. data/lib/hecks/bluebook/model_check/client_profile.rb +10 -91
  114. data/lib/hecks/bluebook/model_check.rb +102 -600
  115. data/lib/hecks/bluebook/pattern_subset.rb +11 -69
  116. data/lib/hecks/bluebook/process_manager.rb +13 -83
  117. data/lib/hecks/bluebook/project_loader.rb +3 -39
  118. data/lib/hecks/bluebook/project_register.rb +12 -60
  119. data/lib/hecks/bluebook/query.rb +9 -48
  120. data/lib/hecks/bluebook/read_model.rb +57 -81
  121. data/lib/hecks/bluebook/reference.rb +7 -41
  122. data/lib/hecks/bluebook/smoke_test.rb +8 -98
  123. data/lib/hecks/bluebook/synthesizer.rb +13 -62
  124. data/lib/hecks/bluebook/translation.rb +26 -62
  125. data/lib/hecks/bluebook/value_object.rb +11 -36
  126. data/lib/hecks/bluebook.rb +5 -22
  127. data/lib/hecks/cache_dir.rb +84 -0
  128. data/lib/hecks/cli/document.rb +86 -0
  129. data/lib/hecks/cli/ir.rb +45 -0
  130. data/lib/hecks/cli/mcp.rb +31 -0
  131. data/lib/hecks/cli/mcp_door.rb +366 -0
  132. data/lib/hecks/cli/model_check.rb +203 -0
  133. data/lib/hecks/cli/project_cli.rb +90 -0
  134. data/lib/hecks/cli/project_diagrams.rb +36 -0
  135. data/lib/hecks/cli/run.rb +173 -0
  136. data/lib/hecks/cli/smoke_test.rb +45 -0
  137. data/lib/hecks/cli/stores.rb +53 -0
  138. data/lib/hecks/cli.rb +160 -0
  139. data/lib/hecks/construct.rb +8 -34
  140. data/lib/hecks/deploy/bluebook/deploy.bluebook +33 -142
  141. data/lib/hecks/deploy/bluebook/deploy.hecksagon +2 -9
  142. data/lib/hecks/embryonaut_bluebook/lock.rb +3 -35
  143. data/lib/hecks/embryonaut_bluebook/shape.rb +1 -17
  144. data/lib/hecks/embryonaut_bluebook/vendor.rb +8 -53
  145. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +4 -19
  146. data/lib/hecks/embryonaut_bluebook.rb +4 -71
  147. data/lib/hecks/facade/cli_door.rb +24 -106
  148. data/lib/hecks/facade/cli_runner.rb +28 -172
  149. data/lib/hecks/facade/command_request.rb +13 -33
  150. data/lib/hecks/facade/handle.rb +34 -121
  151. data/lib/hecks/facade/json_door.rb +31 -146
  152. data/lib/hecks/facade/surface/aggregate_door.rb +39 -104
  153. data/lib/hecks/facade/surface/chapter.rb +10 -54
  154. data/lib/hecks/facade/surface.rb +8 -23
  155. data/lib/hecks/facade.rb +10 -25
  156. data/lib/hecks/forms/app.rb +15 -70
  157. data/lib/hecks/forms/command_form_renderer.rb +10 -29
  158. data/lib/hecks/forms/field_renderer.rb +17 -156
  159. data/lib/hecks/forms/field_shape.rb +30 -222
  160. data/lib/hecks/forms/html.rb +12 -47
  161. data/lib/hecks/forms/index_renderer.rb +1 -4
  162. data/lib/hecks/forms/page.rb +3 -9
  163. data/lib/hecks/forms/params.rb +14 -60
  164. data/lib/hecks/forms/port_argument.rb +2 -19
  165. data/lib/hecks/forms/query_form_renderer.rb +4 -79
  166. data/lib/hecks/forms/record_renderer.rb +8 -75
  167. data/lib/hecks/forms/record_table.rb +7 -50
  168. data/lib/hecks/forms/reference_options.rb +3 -30
  169. data/lib/hecks/forms/value_object_shape.rb +8 -35
  170. data/lib/hecks/forms.rb +5 -33
  171. data/lib/hecks/fqn.rb +5 -12
  172. data/lib/hecks/framework/bluebook/compliance.bluebook +16 -65
  173. data/lib/hecks/framework/bluebook/console_settings.bluebook +47 -134
  174. data/lib/hecks/framework/bluebook/framework.hecksagon +5 -14
  175. data/lib/hecks/framework/bluebook/governance.bluebook +13 -34
  176. data/lib/hecks/framework/bluebook/identity.bluebook +11 -26
  177. data/lib/hecks/framework/bluebook/privacy.bluebook +8 -33
  178. data/lib/hecks/framework.rb +11 -80
  179. data/lib/hecks/freezer.rb +5 -39
  180. data/lib/hecks/grammar/expression.bluebook +15 -40
  181. data/lib/hecks/grammar/translation.bluebook +3 -6
  182. data/lib/hecks/grammar.rb +16 -41
  183. data/lib/hecks/ir.rb +14 -83
  184. data/lib/hecks/language/adapter.bluebook +2 -7
  185. data/lib/hecks/language/bluebook/aggregate.bluebook +56 -221
  186. data/lib/hecks/language/bluebook/attaches/paging.bluebook +6 -15
  187. data/lib/hecks/language/bluebook/bluebook.bluebook +15 -83
  188. data/lib/hecks/language/bluebook/bluebook.hecksagon +3 -15
  189. data/lib/hecks/language/bluebook/command.bluebook +27 -144
  190. data/lib/hecks/language/bluebook/entity.bluebook +24 -97
  191. data/lib/hecks/language/bluebook/policy.bluebook +16 -67
  192. data/lib/hecks/language/bluebook/process_manager.bluebook +28 -151
  193. data/lib/hecks/language/bluebook/projection.bluebook +126 -60
  194. data/lib/hecks/language/bluebook/query.bluebook +12 -43
  195. data/lib/hecks/language/bluebook/shape.bluebook +23 -73
  196. data/lib/hecks/language/bluebook/syntax.bluebook +51 -262
  197. data/lib/hecks/language/bluebook/vocabulary.bluebook +98 -197
  198. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +3 -10
  199. data/lib/hecks/language/hecksagon/domain_port.bluebook +4 -19
  200. data/lib/hecks/language/hecksagon/hecksagon.bluebook +6 -25
  201. data/lib/hecks/language/hecksagon/port_operation.bluebook +3 -14
  202. data/lib/hecks/language/oidc.json +35 -0
  203. data/lib/hecks/language/port.bluebook +4 -17
  204. data/lib/hecks/language/translation/translation.bluebook +3 -9
  205. data/lib/hecks/language/translation/translation_aggregate.bluebook +5 -24
  206. data/lib/hecks/language/world/wiring.bluebook +1 -3
  207. data/lib/hecks/language/world/world.bluebook +5 -7
  208. data/lib/hecks/literal.rb +8 -50
  209. data/lib/hecks/mcp_door_scope.rb +95 -0
  210. data/lib/hecks/mcp_stdio_guard.rb +4 -41
  211. data/lib/hecks/naming.rb +29 -137
  212. data/lib/hecks/ports/access_control.rb +7 -23
  213. data/lib/hecks/ports/agent/answers.rb +5 -23
  214. data/lib/hecks/ports/agent.rb +47 -172
  215. data/lib/hecks/ports/authentication.rb +12 -39
  216. data/lib/hecks/ports/authorization.rb +16 -43
  217. data/lib/hecks/ports/clock.rb +5 -45
  218. data/lib/hecks/ports/extraction.rb +7 -12
  219. data/lib/hecks/ports/identity_assignment.rb +8 -29
  220. data/lib/hecks/ports/identity_generation.rb +6 -29
  221. data/lib/hecks/ports/identity_resolution.rb +2 -10
  222. data/lib/hecks/ports/key_vault.rb +9 -45
  223. data/lib/hecks/ports/persistence/append_only.rb +62 -65
  224. data/lib/hecks/ports/persistence/binding_policy.rb +4 -14
  225. data/lib/hecks/ports/persistence/codec_boundary.rb +20 -80
  226. data/lib/hecks/ports/persistence/null_saga_store.rb +4 -20
  227. data/lib/hecks/ports/persistence/plugin.rb +13 -42
  228. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +38 -141
  229. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +15 -115
  230. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +16 -84
  231. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +7 -26
  232. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -22
  233. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +55 -181
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/events.rb +77 -0
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +12 -122
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +28 -128
  237. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +73 -474
  238. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +31 -124
  239. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +45 -219
  240. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +13 -108
  241. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +18 -75
  242. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +9 -49
  243. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +6 -123
  244. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +23 -55
  245. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +14 -72
  246. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +7 -16
  247. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +14 -115
  248. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +13 -45
  249. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +127 -554
  250. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +13 -63
  251. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +2 -17
  252. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +3 -14
  253. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +13 -58
  254. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -17
  255. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -26
  256. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +9 -25
  257. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +22 -122
  258. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +34 -123
  259. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +5 -12
  260. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +3 -9
  261. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -14
  262. data/lib/hecks/ports/persistence/plugins/era.rb +4 -23
  263. data/lib/hecks/ports/persistence/postgres_dump/connection.rb +7 -23
  264. data/lib/hecks/ports/persistence/postgres_dump.rb +4 -35
  265. data/lib/hecks/ports/persistence/remote_runtime.rb +2 -24
  266. data/lib/hecks/ports/persistence/repository_factory.rb +8 -27
  267. data/lib/hecks/ports/persistence/state_codec.rb +23 -103
  268. data/lib/hecks/ports/persistence.rb +7 -14
  269. data/lib/hecks/ports/projection.rb +42 -84
  270. data/lib/hecks/ports/query/in_memory.rb +10 -58
  271. data/lib/hecks/ports/query/ordering.rb +11 -32
  272. data/lib/hecks/ports/query.rb +14 -37
  273. data/lib/hecks/ports.rb +2 -8
  274. data/lib/hecks/projections/bootstrap_table.rb +11 -43
  275. data/lib/hecks/projections/deploy/fargate/assembly.rb +2 -15
  276. data/lib/hecks/projections/deploy/fargate/cdn.rb +1 -48
  277. data/lib/hecks/projections/deploy/fargate/cdn_default.rb +2 -11
  278. data/lib/hecks/projections/deploy/fargate/check.rb +5 -14
  279. data/lib/hecks/projections/deploy/fargate/containers.rb +3 -78
  280. data/lib/hecks/projections/deploy/fargate/extras.rb +3 -44
  281. data/lib/hecks/projections/deploy/fargate/monitoring.rb +0 -44
  282. data/lib/hecks/projections/deploy/fargate/settings.rb +2 -54
  283. data/lib/hecks/projections/deploy/fargate/yaml.rb +13 -58
  284. data/lib/hecks/projections/deploy/fargate.rb +35 -202
  285. data/lib/hecks/projections/deploy/lambda.rb +151 -679
  286. data/lib/hecks/projections/deploy/preview/access_yaml.rb +1 -33
  287. data/lib/hecks/projections/deploy/preview/containers.rb +6 -111
  288. data/lib/hecks/projections/deploy/preview/routing_yaml.rb +4 -56
  289. data/lib/hecks/projections/deploy/preview/script.rb +2 -14
  290. data/lib/hecks/projections/deploy/preview/settings.rb +3 -33
  291. data/lib/hecks/projections/deploy/preview/task_yaml.rb +2 -38
  292. data/lib/hecks/projections/deploy/preview/template.rb +2 -58
  293. data/lib/hecks/projections/deploy/preview.rb +2 -67
  294. data/lib/hecks/projections/deploy/scripts/settings.rb +3 -25
  295. data/lib/hecks/projections/deploy/scripts.rb +4 -57
  296. data/lib/hecks/projections/deploy/shared.rb +47 -146
  297. data/lib/hecks/projections/deploy/smoke.rb +4 -61
  298. data/lib/hecks/projections/deploy/template_diff/comparer.rb +3 -25
  299. data/lib/hecks/projections/deploy/template_diff/loader.rb +2 -11
  300. data/lib/hecks/projections/deploy/template_diff.rb +5 -57
  301. data/lib/hecks/projections/diagrams.rb +20 -582
  302. data/lib/hecks/projections/glossary/html.rb +14 -130
  303. data/lib/hecks/projections/glossary/markdown.rb +8 -69
  304. data/lib/hecks/projections/glossary/mermaid.rb +9 -66
  305. data/lib/hecks/projections/glossary/sensitivity.rb +5 -34
  306. data/lib/hecks/projections/glossary/sentences.rb +14 -159
  307. data/lib/hecks/projections/glossary.rb +30 -222
  308. data/lib/hecks/projections/ir.rb +3 -10
  309. data/lib/hecks/projections/model/deviations.rb +15 -42
  310. data/lib/hecks/projections/model.rb +12 -51
  311. data/lib/hecks/projections/oidc.rb +12 -56
  312. data/lib/hecks/projections/parser_table.rb +9 -28
  313. data/lib/hecks/projections/reference.rb +9 -16
  314. data/lib/hecks/projections/rust_vocabulary.rb +12 -250
  315. data/lib/hecks/projections/shape.rb +3 -29
  316. data/lib/hecks/projections/statements.rb +7 -86
  317. data/lib/hecks/projections/vocabulary.rb +6 -22
  318. data/lib/hecks/projections.rb +2 -16
  319. data/lib/hecks/projector/cli_projector.rb +60 -375
  320. data/lib/hecks/projector/docs_projector.rb +10 -201
  321. data/lib/hecks/projector/exporter.rb +46 -138
  322. data/lib/hecks/projector/ir_projector.rb +2 -9
  323. data/lib/hecks/projector/narrate_projector.rb +15 -189
  324. data/lib/hecks/projector/target.rb +13 -76
  325. data/lib/hecks/projector.rb +30 -121
  326. data/lib/hecks/query_specification/common/comparators.rb +5 -26
  327. data/lib/hecks/query_specification/common/comparison.rb +35 -143
  328. data/lib/hecks/query_specification/common/dsl.rb +8 -24
  329. data/lib/hecks/query_specification/common/null_policy.rb +20 -70
  330. data/lib/hecks/query_specification/common/options.rb +2 -6
  331. data/lib/hecks/query_specification/common/where_clause.rb +2 -10
  332. data/lib/hecks/query_specification/field_path.rb +37 -77
  333. data/lib/hecks/query_specification/hop_path.rb +12 -76
  334. data/lib/hecks/release/gem_pin.rb +2 -26
  335. data/lib/hecks/release/runner/ci_publisher.rb +93 -0
  336. data/lib/hecks/release/runner/commands.rb +66 -0
  337. data/lib/hecks/release/runner/console.rb +52 -0
  338. data/lib/hecks/release/runner/gem_publisher.rb +43 -0
  339. data/lib/hecks/release/runner/git.rb +48 -0
  340. data/lib/hecks/release/runner/npm_publisher.rb +75 -0
  341. data/lib/hecks/release/runner/preflight.rb +104 -0
  342. data/lib/hecks/release/runner/published.rb +55 -0
  343. data/lib/hecks/release/runner/tagger.rb +80 -0
  344. data/lib/hecks/release/runner.rb +215 -0
  345. data/lib/hecks/rendering.rb +4 -26
  346. data/lib/hecks/router/namespace_installer.rb +12 -36
  347. data/lib/hecks/router.rb +4 -8
  348. data/lib/hecks/runtime/aggregate_lock.rb +8 -33
  349. data/lib/hecks/runtime/boot_gates.rb +3 -18
  350. data/lib/hecks/runtime/caller.rb +19 -59
  351. data/lib/hecks/runtime/capability_graph.rb +5 -21
  352. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
  353. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +23 -160
  354. data/lib/hecks/runtime/command_interpreter.rb +125 -307
  355. data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
  356. data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
  357. data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
  358. data/lib/hecks/runtime/command_rules/emission.rb +8 -21
  359. data/lib/hecks/runtime/command_rules/references.rb +23 -202
  360. data/lib/hecks/runtime/dependency_planning.rb +30 -102
  361. data/lib/hecks/runtime/dispatcher.rb +82 -241
  362. data/lib/hecks/runtime/entity_element.rb +40 -462
  363. data/lib/hecks/runtime/entity_interpreter.rb +65 -220
  364. data/lib/hecks/runtime/errors.rb +32 -115
  365. data/lib/hecks/runtime/event.rb +3 -25
  366. data/lib/hecks/runtime/identity.rb +18 -154
  367. data/lib/hecks/runtime/instance.rb +18 -150
  368. data/lib/hecks/runtime/interpreting.rb +11 -70
  369. data/lib/hecks/runtime/invocation.rb +28 -164
  370. data/lib/hecks/runtime/loader.rb +48 -186
  371. data/lib/hecks/runtime/outbox.rb +38 -261
  372. data/lib/hecks/runtime/policy_interpreter.rb +38 -200
  373. data/lib/hecks/runtime/port_operation_interpreter.rb +26 -87
  374. data/lib/hecks/runtime/query_interpreter.rb +45 -187
  375. data/lib/hecks/runtime/reaction_invocation.rb +36 -138
  376. data/lib/hecks/runtime/read_model_interpreter.rb +139 -208
  377. data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
  378. data/lib/hecks/runtime/reference_hop.rb +8 -81
  379. data/lib/hecks/runtime/refusal_wording.rb +8 -89
  380. data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
  381. data/lib/hecks/runtime/registry/verification.rb +52 -225
  382. data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
  383. data/lib/hecks/runtime/registry.rb +103 -244
  384. data/lib/hecks/runtime/remote_dispatcher.rb +37 -152
  385. data/lib/hecks/runtime/routing.rb +4 -15
  386. data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
  387. data/lib/hecks/runtime/saga_interpreter.rb +79 -263
  388. data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
  389. data/lib/hecks/runtime/tenant_check.rb +6 -68
  390. data/lib/hecks/runtime/tenant_scope.rb +3 -28
  391. data/lib/hecks/runtime/value/admission.rb +16 -67
  392. data/lib/hecks/runtime/value/coercion.rb +155 -641
  393. data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
  394. data/lib/hecks/runtime/value.rb +12 -84
  395. data/lib/hecks/runtime.rb +17 -65
  396. data/lib/hecks/storehouse.rb +146 -567
  397. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +8 -32
  398. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
  399. data/lib/hecks/vendoring/git_environment.rb +2 -8
  400. data/lib/hecks/vendoring/git_source.rb +4 -10
  401. data/lib/hecks/vendoring.rb +7 -46
  402. data/lib/hecks/version.rb +3 -14
  403. data/lib/hecks/vocabulary.rb +5 -0
  404. data/lib/hecks.rb +32 -82
  405. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
  406. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
  407. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
  408. metadata +30 -59
  409. data/lib/hecks/bench/cli.rb +0 -74
  410. data/lib/hecks/bench/environment.rb +0 -100
  411. data/lib/hecks/bench/postgres_probe.rb +0 -41
  412. data/lib/hecks/bench/report.rb +0 -116
  413. data/lib/hecks/bench/ruby_runner.rb +0 -151
  414. data/lib/hecks/bench/run.rb +0 -55
  415. data/lib/hecks/bench/rust_runner.rb +0 -165
  416. data/lib/hecks/bench/stats.rb +0 -75
  417. data/lib/hecks/bench/suite.rb +0 -172
  418. data/lib/hecks/bench/workload.rb +0 -137
  419. data/lib/hecks/bench.rb +0 -28
  420. data/lib/hecks/codemod.rb +0 -443
  421. data/lib/hecks/corpus.rb +0 -479
  422. data/lib/hecks/doc/reference.rb +0 -585
  423. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -684
  424. data/lib/hecks/fuzzing/combination_miner.rb +0 -178
  425. data/lib/hecks/fuzzing/concurrent_dispatch.rb +0 -469
  426. data/lib/hecks/fuzzing/coverage_campaign.rb +0 -161
  427. data/lib/hecks/fuzzing/differential.rb +0 -192
  428. data/lib/hecks/fuzzing/domain_generator.rb +0 -871
  429. data/lib/hecks/fuzzing/era_boundary.rb +0 -154
  430. data/lib/hecks/fuzzing/form_census.rb +0 -285
  431. data/lib/hecks/fuzzing/generated_domain_check.rb +0 -171
  432. data/lib/hecks/fuzzing/invalid_value_generator.rb +0 -125
  433. data/lib/hecks/fuzzing/isolated_boot.rb +0 -658
  434. data/lib/hecks/fuzzing/nondeterministic.rb +0 -79
  435. data/lib/hecks/fuzzing/persistence_parity.rb +0 -253
  436. data/lib/hecks/fuzzing/properties/corrections.rb +0 -125
  437. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +0 -688
  438. data/lib/hecks/fuzzing/properties/guards.rb +0 -398
  439. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +0 -399
  440. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +0 -137
  441. data/lib/hecks/fuzzing/properties/outbox.rb +0 -180
  442. data/lib/hecks/fuzzing/properties/querying.rb +0 -260
  443. data/lib/hecks/fuzzing/properties.rb +0 -267
  444. data/lib/hecks/fuzzing/qa_settings.rb +0 -164
  445. data/lib/hecks/fuzzing/replay.rb +0 -930
  446. data/lib/hecks/fuzzing/rotation_priority.rb +0 -114
  447. data/lib/hecks/fuzzing/rust_gap_manifest.rb +0 -139
  448. data/lib/hecks/fuzzing/self_consistency.rb +0 -825
  449. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +0 -532
  450. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +0 -171
  451. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +0 -127
  452. data/lib/hecks/fuzzing/sequence_generator/picker.rb +0 -116
  453. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +0 -138
  454. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +0 -344
  455. data/lib/hecks/fuzzing/sequence_generator.rb +0 -311
  456. data/lib/hecks/fuzzing/shrinker.rb +0 -309
  457. data/lib/hecks/fuzzing/structural_skips.rb +0 -53
  458. data/lib/hecks/fuzzing/sweep_depth.rb +0 -61
  459. data/lib/hecks/fuzzing/target_capabilities.rb +0 -255
  460. data/lib/hecks/fuzzing/value_generator.rb +0 -340
  461. data/lib/hecks/fuzzing.rb +0 -23
  462. data/lib/hecks/grammar/evolve.rb +0 -524
  463. data/lib/hecks/query_ir.rb +0 -482
@@ -1,53 +1,36 @@
1
1
  module Hecks
2
2
  module Bluebook
3
3
  module Behaviour
4
- # **What a policy does**. Its declared half is four plain fields; these
5
- # are the readings taken off them.
4
+ # Readings over a policy's declared fields.
6
5
  module Policy
7
- # The bluebook's name for this construct, asked the same way of a class
8
- # that has crossed over and of an IR object that has not. Collapses into
9
- # Construct when this one crosses.
6
+ # The bluebook's name for this construct.
10
7
  #
11
- # @return [String] this policy's declared name
8
+ # @return [String]
12
9
  def hecks_name = @name
13
10
 
14
11
  # The domain-and-aggregate prefix of the triggering event's name.
15
12
  #
16
- # @return [String, nil] the part of `on_event` before its first `.`
17
- # (the `"Domain::Aggregate"` prefix), or `nil` when `on_event`
18
- # carries no dot
13
+ # @return [String, nil] the part of `on_event` before its first `.`, or `nil` if none
19
14
  def event_qualifier = Naming.qualifier(@on_event)
20
15
 
21
- # The bare name of the triggering event, without its domain/aggregate
22
- # prefix.
16
+ # The triggering event's bare name, without its prefix.
23
17
  #
24
- # @return [String] the part of `on_event` after its first `.` (the
25
- # bare event name), or the whole string when it carries no dot
18
+ # @return [String]
26
19
  def event_name = Naming.unqualified(@on_event)
27
20
 
28
- # Whether this policy fans out — `for_each` names a query, and a
29
- # non-empty one turns a single reaction into one dispatch per row
30
- # the query answers. Read by the interpreter that runs the fan-out
31
- # and by the property that checks it dispatched once per row.
21
+ # Whether `for_each` names a query, turning one reaction into a dispatch per row.
32
22
  #
33
- # @return [Boolean] whether `for_each` names a query
23
+ # @return [Boolean]
34
24
  def fans_out? = !@for_each.to_s.empty?
35
25
 
36
- # Whether this policy is guarded — a non-empty `where` decides
37
- # whether the policy fires at all, read against the triggering
38
- # event's own payload.
26
+ # Whether a non-empty `where` decides if the policy fires at all.
39
27
  #
40
- # @return [Boolean] whether `where` is declared and non-empty
28
+ # @return [Boolean]
41
29
  def guarded? = !@where.to_s.empty?
42
30
 
43
- # The structured form of `where`, derived once — the same tree
44
- # `AstJson.emit_predicate` spells for every rule row, memoized
45
- # here because a policy is consulted once per event, not once per
46
- # boot. Nil when there is no `where`, exactly as the wire carries
47
- # it.
31
+ # The structured form of `where`, memoized because a policy is consulted once per event.
48
32
  #
49
- # @return [Hash, nil] the JSON-shaped boolean AST for `where`, or
50
- # `nil` when this policy is not guarded
33
+ # @return [Hash, nil] `nil` when the policy is not guarded
51
34
  def where_ast
52
35
  if defined?(@where_ast)
53
36
  @where_ast
@@ -56,39 +39,27 @@ module Hecks
56
39
  end
57
40
  end
58
41
 
59
- # The rule-shaped reading of the guard, for `Evaluator.call_rule`
60
- # — a policy's `where` has no description (nothing refuses with
61
- # it; an unmet where is a silent skip).
42
+ # The guard as a rule for `Evaluator.call_rule`; it has no description because an
43
+ # unmet `where` is a silent skip.
62
44
  #
63
- # @return [Bluebook::Given] the guard as a rule, with no description
64
- # and its `ast` already resolved via `where_ast`
45
+ # @return [Bluebook::Given]
65
46
  def where_rule = @where_rule ||= Given.new(description: nil, canonical: @where, ast: where_ast)
66
47
 
67
- # The fan-out query's route, split the way the runtime runs it:
68
- # `[query_domain, aggregate_name, query_name]`. The query runs
69
- # against the triggering event's own domain unless `for_each`
70
- # names one ("Domain::Aggregate.query"). Deliberately independent
71
- # of `across`/`target_domain`, which name where `trigger` fires,
72
- # not where the fan-out's own query runs.
48
+ # The fan-out query's route, split the way the runtime runs it.
73
49
  #
74
- # @param default_domain [String, Symbol] the triggering event's own
75
- # domain, used when `for_each` names no domain of its own
76
- # @return [Array(String, String, String)] `[domain, aggregate_name,
77
- # query_name]` the fan-out query resolves against
50
+ # The query runs in the triggering event's domain unless `for_each` names one
51
+ # ("Domain::Aggregate.query"); it is independent of `across`/`target_domain`.
52
+ #
53
+ # @param default_domain [String, Symbol] the triggering event's domain
54
+ # @return [Array(String, String, String)] `[domain, aggregate_name, query_name]`
78
55
  def for_each_route(default_domain)
79
56
  path, query_name = @for_each.to_s.split(".", 2)
80
57
  domain, aggregate = path.to_s.include?("::") ? path.split("::", 2) : [default_domain.to_s, path]
81
58
  [domain, aggregate, query_name]
82
59
  end
83
60
 
84
- # The argument name a fan-out dispatch mints each matched row's id
85
- # under is not minted here — it depends on the target command's own
86
- # declared shape (a target command declared on the very aggregate it
87
- # self-references, such as `Account.Freeze`, is addressed by its own
88
- # identity field's name, not a synthetic foreign key), not on the
89
- # aggregate name alone, so it lives on
90
- # `Behaviour::Command#addressing_key_for`, asked of the resolved
91
- # target command by `PolicyInterpreter#addressing_key_for`.
61
+ # The argument name for each fan-out row's id is not minted here: it depends on the
62
+ # target command's declared shape, so it lives on `Behaviour::Command#addressing_key_for`.
92
63
  end
93
64
  end
94
65
  end
@@ -1,96 +1,69 @@
1
1
  module Hecks
2
2
  module Bluebook
3
3
  module Behaviour
4
- # **What a process manager does**. Its declared half is the trigger,
5
- # the states and the handlers; the compensation half — `saga` — is
6
- # derived from the handler that answers a refusal, not declared.
4
+ # Readings over a process manager's trigger, states and handlers.
5
+ # Its `saga` is derived from the handler that answers a refusal, not declared.
7
6
  module ProcessManager
8
7
  # The bluebook's name for this construct.
9
8
  #
10
- # @return [String, nil] the process manager's declared name, or `nil` before it
11
- # is set
9
+ # @return [String, nil]
12
10
  def hecks_name = @name
13
11
 
14
- # The leg that answers — selected by (event, current state), C10.3
15
- # (docs/semantics/bluebook-semantics.md). Two legs may answer the
16
- # same event from different states; which one runs is decided by
17
- # the state the instance is in, never by declaration order. With
18
- # no `state` the reading is declarative — "does any leg answer this
19
- # event, and what is it" — for callers that ask about the
20
- # procedure rather than an instance (`saga`, the outbox's consumer
21
- # selection). Build refuses two legs on one (event, state) pair
22
- # (`ProcessManagerBuilder#validate!`), so a stated lookup is
23
- # unambiguous by construction.
12
+ # The leg that answers an event from a state (C10.3, docs/semantics/bluebook-semantics.md).
24
13
  #
25
- # @param event [String, Symbol] the triggering event's name
26
- # @param state [String, Symbol, nil] the instance's current state; `nil` asks
27
- # declaratively, ignoring state
28
- # @return [Bluebook::ProcessManagerHandler, nil] the matching handler row, or `nil`
29
- # if no declared leg answers this (event, state) pair
14
+ # State, never declaration order, picks between legs; build refuses two legs on one
15
+ # (event, state) pair. With no `state` the lookup is declarative: does any leg answer.
16
+ #
17
+ # @param event [String, Symbol]
18
+ # @param state [String, Symbol, nil] the instance's current state; `nil` ignores state
19
+ # @return [Bluebook::ProcessManagerHandler, nil]
30
20
  def handler_for(event, state = nil)
31
21
  @handlers.find { |h| h.event_type == event.to_s && (state.nil? || h.from_state == state.to_s) }
32
22
  end
33
23
 
34
- # Finds every declared leg for an event, regardless of source state.
24
+ # Every declared leg for an event, regardless of source state.
35
25
  #
36
- # @param event [String, Symbol] the triggering event's name
37
- # @return [Array<Bluebook::ProcessManagerHandler>] every handler row declared for
38
- # `event`; `[]` if none is declared
26
+ # @param event [String, Symbol]
27
+ # @return [Array<Bluebook::ProcessManagerHandler>]
39
28
  def handlers_for(event) = @handlers.select { |h| h.event_type == event.to_s }
40
29
 
41
- # Says whether any declared leg answers an event.
30
+ # Whether any declared leg answers an event.
42
31
  #
43
- # @param event [String, Symbol] the triggering event's name
44
- # @return [Boolean] whether any handler row is declared for `event`
32
+ # @param event [String, Symbol]
33
+ # @return [Boolean]
45
34
  def handles?(event) = @handlers.any? { |h| h.event_type == event.to_s }
46
35
 
47
- # Whether a state is one this procedure declares — asked of a value
48
- # a real run left a saga instance holding (its live or rehydrated
49
- # state), the way `Lifecycle#states` is asked of an aggregate's
50
- # resting field. A rehydrated instance in a state no handler could
51
- # have reached is corruption the durable round-trip introduced.
36
+ # Whether a state is one this procedure declares. A rehydrated instance in an
37
+ # undeclared state is corruption from the durable round-trip.
52
38
  #
53
- # @param state [String, Symbol] the state to check
54
- # @return [Boolean] whether `state` is one of this procedure's declared states
39
+ # @param state [String, Symbol]
40
+ # @return [Boolean]
55
41
  def declares_state?(state) = @states.map(&:to_s).include?(state.to_s)
56
42
 
57
- # Names the payload field a triggering event's instances are correlated by.
43
+ # The payload field a triggering event's instances are correlated by.
58
44
  #
59
- # @return [Symbol] the head of `correlates_by` — the part before the first `.`
45
+ # @return [Symbol] the part of `correlates_by` before the first `.`
60
46
  def correlation_head = @correlates_by.to_s.split(".").first.to_sym
61
47
 
62
- # The compensation half of a procedure, read off the handler that
63
- # answers `REFUSED`.
64
- #
65
- # nil for a procedure with no answer to a refusal, which is a legitimate
66
- # thing to be — a hiring pipeline cannot un-interview anybody.
48
+ # The compensation half, read off the handler that answers `REFUSED`.
67
49
  #
68
- # @return [Bluebook::Saga, nil] the derived saga, or `nil` if no handler answers
69
- # `REFUSED`
50
+ # @return [Bluebook::Saga, nil] `nil` when no handler answers a refusal, which is
51
+ # legitimate: a hiring pipeline cannot un-interview anybody
70
52
  def saga
71
53
  leg = handler_for(Bluebook::ProcessManager::REFUSED)
72
54
  return nil unless leg
73
55
 
74
- # `compensations` — a static preview, declaration order, not
75
- # one instance's own runtime history (which legs a given
76
- # instance actually completed is per-instance state,
77
- # `SagaInterpreter`'s own `completed_compensations`, not a
78
- # fact `Saga` — a pure declaration reading — could ever hold).
79
- # Every `compensates` any handler's own dispatch declares,
80
- # forward declaration order, then whatever this leg's own
81
- # hand-written body still lists — coexistence, not replacement
82
- # (`ProcessManagerBuilder::HandlerBuilder#dispatch_impl`'s own
83
- # comment): a saga can derive some of its compensation and
84
- # still hand-write the rest for what isn't expressible as
85
- # "undo command X".
56
+ # A static preview in declaration order, not one instance's runtime history.
57
+ # Derived `compensates` come first, then the leg's hand-written body, so a saga
58
+ # can derive some compensation and hand-write the rest.
86
59
  derived = handlers.flat_map { |handler| handler.dispatches.filter_map(&:compensates) }
87
60
  Bluebook::Saga.new(trigger: Bluebook::ProcessManager::REFUSED, from_state: leg.from_state,
88
61
  to_state: leg.to_state, compensations: derived + leg.dispatches)
89
62
  end
90
63
 
91
- # Says whether this procedure declares a handler for `REFUSED`.
64
+ # Whether this procedure declares a handler for `REFUSED`.
92
65
  #
93
- # @return [Boolean] whether this procedure has a derivable saga
66
+ # @return [Boolean]
94
67
  def saga? = !saga.nil?
95
68
  end
96
69
  end
@@ -1,18 +1,45 @@
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
+ # Every reduction a read model may declare over its one eligible collection, each
8
+ # naming the ivar that holds its declared value and the word it reads back as in a
9
+ # refusal message. A read model reports one shape, so at most one of these is ever set.
10
+ REDUCTION_FIELDS = {
11
+ count: "count", median_field: "median", sum_field: "sum", avg_field: "avg",
12
+ min_field: "min", max_field: "max", percentile_field: "percentile",
13
+ any_field: "any", all_field: "all"
14
+ }.freeze
15
+
16
+ # @return [Boolean] whether this read model declares any reduction (`REDUCTION_FIELDS`)
17
+ def reducing? = REDUCTION_FIELDS.each_key.any? { |ivar| instance_variable_get(:"@#{ivar}") }
18
+
7
19
  # Lists the fields this read model groups rows by.
8
20
  #
9
21
  # @return [Array<Symbol>] each declared `group_by` field's name, in declaration order
10
22
  def group_by_fields = @group_by.map { |row| row[:field].to_sym }
11
23
 
12
- # `!!` rather than a bare `@count` — the DSL/reconstruction
13
- # boundary (ReadModel#initialize) already normalises to
14
- # `true`/`nil`, so this is belt and braces against a future
15
- # caller constructing a ReadModel by hand with `count: false`.
24
+ # Reports whether this read model's `group_by` names every identity
25
+ # head of the aggregate it groups (ADR 0061).
26
+ #
27
+ # Such a key path can't be shared by two rows, so uniqueness is accepted
28
+ # from the declaration alone rather than checked per request.
29
+ #
30
+ # @param aggregate [Bluebook::Aggregate, nil] the aggregate of this read
31
+ # model's one many-side head
32
+ # @return [Boolean] false when `aggregate` is nil or declares no identity,
33
+ # since uniqueness cannot then be shown
34
+ def groups_by_identity?(aggregate)
35
+ return false unless aggregate
36
+
37
+ identity = Array(aggregate.identity_heads).map(&:to_sym)
38
+ !identity.empty? && (identity - group_by_fields).empty?
39
+ end
40
+
41
+ # `!!` guards against a future caller constructing a ReadModel by
42
+ # hand with `count: false` rather than the normalised `nil`.
16
43
  #
17
44
  # @return [Boolean] whether this read model reduces to a row count
18
45
  def count? = !!@count
@@ -22,14 +49,9 @@ module Hecks
22
49
  # @return [String] this read model's name in `snake_case`
23
50
  def query_name = Naming.snake(@name)
24
51
 
25
- # Which gathered heads the filtering applies to (ADR 0055) — plural,
26
- # since `where`/`order_by`/`limit`/`offset` can now each independently
27
- # name a many-side head via `on:` once there's more than one. A read
28
- # model with a single many-side head keeps the old reading: every
29
- # untargeted option (plus `group_by`/`count`/`median`, still
30
- # single-head-only — ADR 0055) applies to it, same as before `on:`
31
- # existed. With several many-side heads, only the ones actually named
32
- # by a targeted option are eligible.
52
+ # Which gathered heads the filtering applies to (ADR 0055): with one
53
+ # many-side head every untargeted option applies to it; with several,
54
+ # only the ones a targeted option names via `on:` are eligible.
33
55
  #
34
56
  # @return [Array<Symbol>] the `:as` name of each many-side head that filtering
35
57
  # applies to; `[]` if this read model has no many-side head
@@ -43,10 +65,7 @@ module Hecks
43
65
  targets.filter_map { |target| many.find { |head| head[:aggregate] == target.to_s } }.map { |head| head[:as] }
44
66
  end
45
67
 
46
- # The pre-`on:` reading (ADR 0055), unchanged: with exactly one
47
- # many-side head, every untargeted option (plus `group_by`/`count`/
48
- # `median`, still single-head-only) applies to it — split out only
49
- # to keep `filtered_head_names` itself under this file's own
68
+ # Split out only to keep `filtered_head_names` under this file's own
50
69
  # complexity budget, not because the two questions differ in kind.
51
70
  #
52
71
  # @param many [Array<Hash{Symbol => Object}>] the read model's many-side
@@ -55,18 +74,11 @@ module Hecks
55
74
  # declared, else `[]`
56
75
  def single_filtered_head_name(many)
57
76
  declared = wheres.any? || order_by || limit || offset || authorization&.tenant ||
58
- @group_by.any? || count? || @median_field
77
+ @group_by.any? || reducing?
59
78
  declared ? [many.first[:as]] : []
60
79
  end
61
80
 
62
- # The where/order_by/limit/offset that apply to one eligible head
63
- # (ADR 0055) — a small view `Ports::Query::InMemory.execute` reads
64
- # exactly the way it already reads a whole `Query`/`ReadModel`
65
- # (`.wheres`/`.order_by`/`.limit`/`.offset`/`.null_semantics`), scoped
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`
69
- # resolves to `head_as`'s own aggregate.
81
+ # Filtering options scoped to one included aggregate head (ADR 0055).
70
82
  FilteredOptions = Struct.new(:wheres, :order_by, :limit, :offset, :null_semantics)
71
83
 
72
84
  # Scopes this read model's filtering options down to one included head.
@@ -1,35 +1,17 @@
1
1
  module Hecks
2
2
  module Bluebook
3
3
  module Behaviour
4
- # The behaviour more than one construct shares.
5
- #
6
- # None of this is new duplication. `attribute(named)` was written
7
- # out four separate times — on Aggregate, Entity, Command and
8
- # PortOperation — and the identity derivation twice, byte for byte,
9
- # for as long as those constructs have existed. Splitting holding
10
- # from doing is what made them visible: the same method in four
11
- # files reads as four methods, and the same method in four modules
12
- # named `Behaviour::*` reads as one repeated.
13
- #
14
- # Written to work whether the construct is included into (an
15
- # ordinary object like Aggregate) or extended into (a class-shaped
16
- # one like Command), because every one of them reads instance
17
- # variables and nothing else.
4
+ # Behaviour shared by more than one construct. Each module works whether it is included
5
+ # (an ordinary object like Aggregate) or extended (a class-shaped one like Command),
6
+ # because it reads only instance variables.
18
7
 
19
8
  # A construct whose identity is a join of declared paths.
20
9
  #
21
- # The paths, in declaration order, because the identity is their join.
22
- # "number.value" says which field carries the identity ; several paths
23
- # say the identity is made of several facts, which is what anything
24
- # named beneath another thing needs. `identity_heads` are the attributes
25
- # those paths start at — what every reader that looks up or coerces an
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
28
- # no single head, and answering with the first would be a guess ; the
29
- # readers that need all of them ask for `identity_heads`.
10
+ # Several paths mean the identity is made of several facts. `identity_heads` are the
11
+ # attributes those paths start at; `identified_by` is the single head, set only when there
12
+ # is exactly one, since a composite has no single head and the first would be a guess.
30
13
  module Identified
31
- # Derives `identity_paths`, `identity_heads` and the single-head form of
32
- # `identified_by` from the declared `identified_by` path(s).
14
+ # Derives `identity_paths`, `identity_heads` and the single-head `identified_by`.
33
15
  #
34
16
  # @return [void]
35
17
  def derive_identity
@@ -41,72 +23,53 @@ module Hecks
41
23
 
42
24
  # A construct that answers for its own declarations by name.
43
25
  #
44
- # Indexed once, since the declarations are final by the time the
45
- # construct exists — every dispatch asks these finders by name, and a
46
- # linear scan repeated on every call was doing work the declared shape
47
- # had already settled at boot.
48
- #
49
- # `index_by_name` takes the collections to index rather than naming
50
- # them, because which collections a construct has is exactly the part
51
- # that differs: an aggregate has five, an entity three, a command one.
26
+ # Indexed once because declarations are final when the construct exists. `index_by_name`
27
+ # takes the collections to index since which ones a construct has differs per construct.
52
28
  module Indexed
53
- # Keyed by symbol — an attribute is asked for by its declared
54
- # symbol name everywhere in the runtime.
29
+ # Keyed by symbol, the way the runtime asks for an attribute.
55
30
  #
56
- # @param attributes [Array<Bluebook::Attribute>] the construct's declared
57
- # attributes
31
+ # @param attributes [Array<Bluebook::Attribute>]
58
32
  # @return [void]
59
33
  def index_attributes(attributes)
60
34
  @attributes_by_name = attributes.to_h { |held| [held.name, held] }
61
35
  end
62
36
 
63
- # Keyed by string and by `hecks_name` — a command, query or value
64
- # object is a construct whose Ruby `name` is something else
65
- # entirely (a constant path, or nothing at all for an anonymous
66
- # class), so the declared name is the only one worth indexing.
37
+ # Keyed by string and by `hecks_name`, since such a construct's Ruby `name` is a
38
+ # constant path or nothing at all for an anonymous class.
67
39
  #
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`
40
+ # @param collection [Array<#hecks_name>] value objects, commands or queries
41
+ # @return [Hash{String => Object}]
72
42
  def index_by_hecks_name(collection)
73
43
  collection.to_h { |held| [held.hecks_name, held] }
74
44
  end
75
45
 
76
- # Finds a declared attribute by its declared name.
46
+ # Finds a declared attribute by name.
77
47
  #
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
48
+ # @param named [String, Symbol]
49
+ # @return [Bluebook::Attribute, nil]
81
50
  def attribute(named) = @attributes_by_name[named.to_sym]
82
51
 
83
- # Finds a declared command by its declared name.
52
+ # Finds a declared command by name.
84
53
  #
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
54
+ # @param named [String, Symbol]
55
+ # @return [Class, nil] a `Bluebook::Command` subclass
88
56
  def command(named) = @commands_by_name[named.to_s]
89
57
 
90
- # Finds a declared query by its declared name.
58
+ # Finds a declared query by name.
91
59
  #
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
60
+ # @param named [String, Symbol]
61
+ # @return [Bluebook::Query, nil]
95
62
  def query(named) = @queries_by_name[named.to_s]
96
63
  end
97
64
 
98
65
  # A construct that owns what it declares.
99
66
  #
100
- # Owner links are only ever read lazily — hecks_fqn at ask time,
101
- # Reference#resolve at dispatch time — so the moment the construct
102
- # is complete is the right stamping point: its declarations are
103
- # final, and nothing outside needs to remember to stamp them.
67
+ # Owner links are read lazily (hecks_fqn, Reference#resolve), so stamping when the
68
+ # construct is complete is early enough and needs no help from outside.
104
69
  module Owns
105
70
  # Stamps each of `children` as owned by this construct.
106
71
  #
107
- # @param children [Array<Object>] the construct's own declared children —
108
- # arrays or bare objects, flattened before stamping — each answering
109
- # `hecks_owner=`
72
+ # @param children [Array<#hecks_owner=>] arrays or bare objects, flattened first
110
73
  # @return [void]
111
74
  def stamp(*children)
112
75
  children.flatten.each { |child| child.hecks_owner = self }
@@ -1,17 +1,10 @@
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
6
- # behaviour is singleton behaviour, and the holding half's `absorb`
7
- # is what a generated constructor would be.
4
+ # Behaviour of a value object; extended rather than included, since each shape is
5
+ # its own class.
8
6
  module ValueObject
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.
7
+ # Recorded as its own fact so an empty `one_of` differs from no `one_of` at all.
15
8
  #
16
9
  # @return [Boolean] whether this value object declares `one_of`, even
17
10
  # if left empty
@@ -24,21 +17,9 @@ module Hecks
24
17
  # `nil` if none is declared under that name
25
18
  def attribute(named) = attributes.find { |held| held.name == named.to_sym }
26
19
 
27
- # A single-attribute value object (EmailAddress{address},
28
- # CustomerNumber{value}) is a name for a scalar, not a genuine
29
- # group — [[feedback_name_the_scalar_field]]. `adapters/driven/
30
- # sql_query_builder.rb` and `fuzzing/invalid_value_generator.rb`
31
- # now read through this rather than inlining the check.
20
+ # A single-attribute value object (EmailAddress{address}) names a scalar, not a group.
32
21
  #
33
- # `forms/field_shape.rb`'s own two `attributes.first.name` sites
34
- # (`closed_set_options`/`closed_set_field`) look identical but are
35
- # not the same question — they pick a closed set's discriminant
36
- # column, and a closed set can be genuinely multi-attribute
37
- # (`Runtime::Value::Admission#member_matches?`'s own comment
38
- # names a real one: `StatementFrequency`'s `cadence`/
39
- # `retention_months`/`paper_fee_cents`). `sole_attribute` would
40
- # return `nil` for that shape and break the discriminant lookup —
41
- # left as `.first` on purpose, not a missed migration.
22
+ # A closed set's discriminant column is a different question and may span attributes.
42
23
  #
43
24
  # @return [Bluebook::Attribute, nil] this value object's only
44
25
  # attribute, or `nil` when it has zero or more than one
@@ -1,65 +1,33 @@
1
1
  module Hecks
2
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 | :port_operation the kind of verb that key must name
12
- #
13
- # A `:port_operation` verb is spelled `"Aggregate.Port.Operation"` and names
14
- # an operation the chapter's hecksagon declares on one of its aggregates.
15
- # The hecksagon attaches after the chapter is built, so the chapter itself
16
- # only checks the spelling; `Registry#verify!` checks the operation exists.
3
+ # What a chapter may declare it provides — trusted in place of a literal
4
+ # chapter-name check by role checks, adapters and the fuzzer.
17
5
  module Capabilities
18
6
  AUTHORIZATION = "authorization".freeze
19
- # Who may sign in, and with what role — rust/host's Google-OAuth
20
- # provision/member_rows resolve this instead of an env var naming
21
- # the aggregate (HECKS_MEMBERSHIP_AGGREGATE). Same declared-
22
- # not-named shape AUTHORIZATION already is for Governance.
7
+ # Who may sign in and with what role, resolved by declaration rather
8
+ # than an env var naming the aggregate.
23
9
  MEMBERSHIP = "membership".freeze
24
- # A stable organizational identity, independent of how it was
25
- # authenticated — recognised by declaration, not the literal name
26
- # "Identity". Same declared-not-named shape AUTHORIZATION already
27
- # is. rust/host does not build this chapter's payloads; any field
28
- # mapping lives on the consuming hecksagon's `translates` ACL.
10
+ # A stable organizational identity, recognised by declaration rather
11
+ # than the literal name "Identity".
29
12
  IDENTITY = "identity".freeze
30
- # Guest-facing newsletter signup: who subscribed, confirmed, or left —
31
- # recognised by declaration, not the literal chapter name
32
- # "Newsletter". rust/host's newsletter routes dispatch these verbs
33
- # and read the subscribing aggregate's instances off `subscribe`.
13
+ # Guest newsletter signups, recognised by declaration rather than the
14
+ # literal chapter name "Newsletter".
34
15
  NEWSLETTER = "newsletter".freeze
35
- # Sending an issue to the confirmed subscribers: the decision to send
36
- # and the record of each email handed to the mail provider. A separate
37
- # capability from `NEWSLETTER` so a chapter that only takes signups
38
- # declares nothing more; rust/host serves the send route only when a
39
- # chapter declares this one as well.
16
+ # Sending an issue to confirmed subscribers; kept separate from
17
+ # `NEWSLETTER` so a signup-only chapter declares nothing more.
40
18
  NEWSLETTER_ISSUES = "newsletter_issues".freeze
41
19
 
42
- # Taking a payment through an external processor: start one, then
43
- # hear the processor's verdict — recognised by declaration, not the
44
- # literal chapter name "Payments". rust/host's checkout and webhook
45
- # routes dispatch these verbs and read the paying aggregate's
46
- # instances off `initiate`. The two verdicts are hecksagon port
47
- # operations (the processor's webhook, translated into this chapter's
48
- # vocabulary), not commands.
20
+ # Taking a payment through an external processor, recognised by
21
+ # declaration rather than the literal name "Payments"; the two
22
+ # verdicts are port operations, not commands.
49
23
  PAYMENTS = "payments".freeze
50
24
 
51
- # Scheduling sessions and taking registrations for them — recognised
52
- # by declaration, not the literal aggregate names "Event" and
53
- # "Registration". rust/host's event and registration routes dispatch
54
- # these verbs; the aggregates they name are the event and the
55
- # registration.
25
+ # Scheduling sessions and taking registrations, recognised by
26
+ # declaration rather than the literal aggregate names.
56
27
  REGISTRATIONS = "registrations".freeze
57
28
 
58
- # A business's connection to its payment processor: connect it, drop
59
- # it, pause it, and switch taking payments on or off — recognised by
60
- # declaration, not the literal name "PaymentConnection". rust/host's
61
- # payments routes dispatch these verbs; the connection aggregate is
62
- # the one `connect` names.
29
+ # A business's connection to its payment processor, recognised by
30
+ # declaration rather than the literal name "PaymentConnection".
63
31
  PAYMENT_CONNECTION = "payment_connection".freeze
64
32
 
65
33
  CONTRACTS = {