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
@@ -18,6 +18,22 @@ module Hecks
18
18
  include RuleReference
19
19
  include WordGate
20
20
 
21
+ # @param name [String] the entity's name, as written after `entity`
22
+ # @param owner_value_objects [Array<Bluebook::ValueObject>] the owning aggregate's value
23
+ # objects, threaded through unchanged for this piece's `identified_by` to resolve
24
+ # against
25
+ # @param owner_named_givens [Hash{String => Bluebook::Given}] the aggregate-wide,
26
+ # cross-entity given pool, shared and written through by `given_impl`
27
+ # @param identity_name_prefix [String, nil] the prefix a synthesized identity value
28
+ # object's name takes; nil derives it from `name`
29
+ # @param identity_value_object_installer [#call, nil] called with a synthesized value
30
+ # object to install it onto the owning aggregate; nil when there is none
31
+ # @param aggregate_name [String, nil] the root aggregate's name, for chapter-wide given
32
+ # keys; nil derives it from `name`
33
+ # @param chapter_entity_named_givens [Hash{String => Hash{String => Bluebook::Given}}]
34
+ # the chapter-wide, entity-scoped given pool, shared and written through
35
+ # @param chapter_entity_pending_givens [Array<Hash>] unresolved chapter-wide bare given
36
+ # references, appended to when this piece's own reference cannot resolve yet
21
37
  def initialize(name, owner_value_objects: [], owner_named_givens: {},
22
38
  identity_name_prefix: nil, identity_value_object_installer: nil,
23
39
  aggregate_name: nil, chapter_entity_named_givens: {}, chapter_entity_pending_givens: [])
@@ -30,18 +46,18 @@ module Hecks
30
46
  @owner_value_objects = owner_value_objects
31
47
  @identity_name_prefix = identity_name_prefix || Naming.demodulise(name)
32
48
  @identity_value_object_installer = identity_value_object_installer
33
- # THE AGGREGATE-WIDE cross-entity given pool — ONE hash, the
34
- # SAME object, threaded unchanged through every piece nested
49
+ # The aggregate-wide cross-entity given pool — one hash, the
50
+ # same object, threaded unchanged through every piece nested
35
51
  # under one aggregate however deep (the identical shape
36
52
  # `@owner_value_objects` already threads — see this class'
37
53
  # own `entity` comment). `given`'s own block form writes
38
- # through to it; a SIBLING piece's bare command-level
54
+ # through to it; a sibling piece's bare command-level
39
55
  # reference reads from it via `CommandBuilder#
40
56
  # reference_named_given`.
41
57
  @owner_named_givens = owner_named_givens
42
- # ONE LEVEL WIDER STILL — the CHAPTER-WIDE, ENTITY-SCOPED pool
58
+ # **One level wider still** — the chapter-wide, entity-scoped pool
43
59
  # (the piece analogue of `AggregateBuilder#@chapter_named_givens`,
44
- # one level down). `@aggregate_name` names THIS piece's own
60
+ # one level down). `@aggregate_name` names this piece's own
45
61
  # root, so the write-through below can key itself
46
62
  # "AggregateName.EntityName" — the same dotted addressing
47
63
  # convention `declared_by:` already uses chapter-wide, one
@@ -51,26 +67,41 @@ module Hecks
51
67
  @aggregate_name = aggregate_name || Naming.demodulise(name)
52
68
  @chapter_entity_named_givens = chapter_entity_named_givens
53
69
  @chapter_entity_pending_givens = chapter_entity_pending_givens
54
- # DEFERRED CONSTRUCTION — see `AggregateBuilder#drain_pending!`'s
70
+ # **Deferred construction** — see `AggregateBuilder#drain_pending!`'s
55
71
  # own comment; the identical mechanism, one level down, so a
56
72
  # nested piece's own commands (Dispatch inside Handler) see
57
- # every SIBLING entity/command/query this piece goes on to
73
+ # every sibling entity/command/query this piece goes on to
58
74
  # declare, not just whatever came before it textually.
59
75
  @pending_entities = []
60
76
  @pending_commands = []
61
77
  @pending_queries = []
62
78
  end
63
79
 
80
+ # Sets the human-readable description shown for this entity.
81
+ #
82
+ # @param value [String] the description text
83
+ # @return [String] the description as stored
64
84
  def description(value) = @description = value
65
85
 
66
- # THE SAME FIELD AggregateBuilder's OWN reference_to BUILDS — a
86
+ # Declares a reference from this piece to another aggregate's identity.
87
+ #
88
+ # The same field AggregateBuilder's own reference_to builds — a
67
89
  # piece can hold a reference to another root exactly the way its
68
90
  # own head can (Card.assignee_id, a Team's own id), just never
69
- # to another PIECE, since there's no cross-piece addressing
91
+ # to another piece, since there's no cross-piece addressing
70
92
  # anywhere in this language to resolve one against.
71
- # RENAMED FROM `reference_to` — item #13's full metaprogrammed
93
+ #
94
+ # Answers the `reference_to` word through the table's `calls:`
95
+ # column — item #13's full metaprogrammed
72
96
  # dispatch (slice 4b). Bootstrap-reachable, in
73
- # GenericDispatch::BOOTSTRAP_CALLS_FALLBACK.
97
+ # `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`.
98
+ #
99
+ # @param type [Module, Symbol, String] the referenced aggregate, written as a bare
100
+ # constant
101
+ # @param as [Symbol, nil] the attribute's name; nil derives it from `type`
102
+ # @param optional [Boolean] whether the reference may be absent
103
+ # @return [void]
104
+ # @raise [Bluebook::DSL::Malformed] if `as` (or the derived name) is already declared
74
105
  def reference_to_impl(type, as: nil, optional: false)
75
106
  target = Naming.demodulise(type)
76
107
  relationship_attribute(target, :reference_to,
@@ -86,6 +117,14 @@ module Hecks
86
117
  # `*_impl` to match AggregateBuilder's own siblings, item #13's
87
118
  # full metaprogrammed dispatch convention.
88
119
  # rubocop:disable Naming/PredicatePrefix
120
+ # Declares a list-typed relationship to another entity, referenced by its plural name.
121
+ #
122
+ # @param type [Module, Symbol, String] the related entity's plural name, a bare constant
123
+ # @param as [Symbol, nil] the attribute's name; nil derives it from `type`
124
+ # @param options [Hash] must be empty; kept only to name which unsupported keyword was
125
+ # given in the refusal message
126
+ # @return [void]
127
+ # @raise [Bluebook::DSL::Malformed] if any keyword argument is given
89
128
  def has_many_impl(type, as: nil, **options)
90
129
  unless options.empty?
91
130
  raise Malformed,
@@ -97,19 +136,31 @@ module Hecks
97
136
  as || Naming.snake(plural).to_sym, list: true)
98
137
  end
99
138
 
139
+ # Declares a single-valued relationship to another entity.
140
+ #
141
+ # @param type [Module, Symbol, String] the related entity, a bare constant
142
+ # @param as [Symbol, nil] the attribute's name; nil derives it from `type`
143
+ # @param optional [Boolean] whether the relationship may be absent
144
+ # @return [void]
100
145
  def has_one_impl(type, as: nil, optional: false)
101
146
  target = Naming.demodulise(type)
102
147
  relationship_attribute(target, :has_one, as || Naming.snake(target).to_sym, optional: optional)
103
148
  end
104
149
  # rubocop:enable Naming/PredicatePrefix
105
150
 
151
+ # Declares a single-valued relationship to the entity that owns this one.
152
+ #
153
+ # @param type [Module, Symbol, String] the owning entity, a bare constant
154
+ # @param as [Symbol, nil] the attribute's name; nil derives it from `type`
155
+ # @param optional [Boolean] whether the relationship may be absent
156
+ # @return [void]
106
157
  def belongs_to_impl(type, as: nil, optional: false)
107
158
  target = Naming.demodulise(type)
108
159
  relationship_attribute(target, :belongs_to, as || Naming.snake(target).to_sym, optional: optional)
109
160
  end
110
161
 
111
- # A PIECE is known by a field, not by a whole value object.
112
- # `identified_by :sequence` names the SCALAR inside it, which is
162
+ # A piece is known by a field, not by a whole value object.
163
+ # `identified_by :sequence` names the scalar inside it, which is
113
164
  # what an id actually is — a LedgerEntry is entry 3, not entry
114
165
  # {"value":3}. `identified_by` itself is AttributeCollector's own
115
166
  # shared method (S9) — the two constructs cannot drift apart in
@@ -117,50 +168,77 @@ module Hecks
117
168
  # (`identified_by :branch_code, :box_number`), which a piece may
118
169
  # declare for the same reason a head may.
119
170
 
171
+ # Queues a command declared on this piece, built later once every sibling has been seen.
172
+ #
120
173
  # `from:` — see `AggregateBuilder#command`'s own comment; the
121
- # SAME guard, checked against this PIECE's own lifecycle field
174
+ # same guard, checked against this piece's own lifecycle field
122
175
  # (S10, ADR 0025 — a piece's own state machine is checkable the
123
176
  # same way a head's is).
124
- # RENAMED FROM `command`/`query`/`entity`/`lifecycle` (all below)
125
- # — item #13's full metaprogrammed dispatch (slice 4c), same
177
+ #
178
+ # Answers the `command` word through the table's `calls:` column,
179
+ # along with its siblings `query`/`entity`/`lifecycle` below —
180
+ # item #13's full metaprogrammed dispatch (slice 4c), same
126
181
  # reasoning as AggregateBuilder's own siblings: bootstrap-
127
- # reachable, in BOOTSTRAP_CALLS_FALLBACK.
182
+ # reachable, in `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`.
183
+ #
184
+ # @param name [String] the command's name
185
+ # @param from [String, Symbol, Array<String, Symbol>, nil] the lifecycle state(s) this
186
+ # command guards from; nil admits from any state
187
+ # @yield the command body, evaluated against a `CommandBuilder` once drained
188
+ # @return [Array<Array>] every pending command queued so far, this one last
128
189
  def command_impl(name, from: nil, &block)
129
190
  @pending_commands << [name, from, block]
130
191
  end
131
192
 
193
+ # Queues a query declared on this piece, built later once every sibling has been seen.
194
+ #
195
+ # @param name [String] the query's name
196
+ # @yield the query body, evaluated against a `QueryBuilder` once drained
197
+ # @return [Array<Array>] every pending query queued so far, this one last
132
198
  def query_impl(name, &block)
133
199
  @pending_queries << [name, block]
134
200
  end
135
201
 
136
- # S17, ADR 0026 — A PIECE NESTED INSIDE A PIECE. "A `Dispatch`
202
+ # Queues a piece nested inside this one, built later once every sibling has been seen.
203
+ #
204
+ # S17, ADR 0026 — a piece nested inside a piece. "A `Dispatch`
137
205
  # [has] no life outside its `Handler`" (the ADR's own words) —
138
206
  # the same reason `Member` nests inside `ValueObject`, one level
139
207
  # further in. `owner_value_objects` passes straight through
140
208
  # unchanged, not re-derived from this entity's own attributes —
141
209
  # a piece mints no value objects of its own at any depth, so a
142
- # NESTED piece's bare `identified_by :field` still resolves
143
- # against the SAME root aggregate's value objects an outer
210
+ # nested piece's bare `identified_by :field` still resolves
211
+ # against the same root aggregate's value objects an outer
144
212
  # piece's already does (`AggregateBuilder#entity`'s own comment
145
213
  # names this pool ; there is exactly one of them, however deep
146
214
  # the nesting goes).
215
+ # @param name [String] the nested piece's name
216
+ # @yield the piece body, evaluated against an `EntityBuilder` once drained
217
+ # @return [Array<Array>] every pending piece queued so far, this one last
147
218
  def entity_impl(name, &block)
148
219
  @pending_entities << [name, block]
149
220
  end
150
221
 
222
+ # Declares this piece's own state machine.
223
+ #
224
+ # @param field [Symbol, String] the attribute the state machine lives on
225
+ # @param default [String, Symbol] the state a new record starts in
226
+ # @yield the lifecycle body of `transition` rows, evaluated against a `LifecycleBuilder`
227
+ # @return [Bluebook::Lifecycle] the built state machine
228
+ # @raise [Bluebook::DSL::Malformed] if two transitions for one command overlap
151
229
  def lifecycle_impl(field, default:, &)
152
230
  @lifecycle = LifecycleBuilder.build(field, default: default, &)
153
231
  end
154
232
 
155
- # A PRECONDITION SHARED ACROSS THIS PIECE'S OWN COMMANDS, DECLARED
156
- # ONCE — the same move `AggregateBuilder#given` already makes,
233
+ # A precondition shared across this piece's own commands, declared
234
+ # once — the same move `AggregateBuilder#given` already makes,
157
235
  # one level down. Real, live redundancy this closes: banking's
158
236
  # own `LedgerEntry.Amend`/`LedgerEntry.Reverse` each repeated
159
237
  # `given("customer is active") { parent.customer.status ==
160
238
  # "active" }` and `given("account is open") { parent.status ==
161
239
  # "open" }`, byte for byte, because a piece had no way to declare
162
- # either once and reference it back — only the AGGREGATE could.
163
- # DECLARE BEFORE THE COMMANDS THAT REFERENCE IT, the same
240
+ # either once and reference it back — only the aggregate could.
241
+ # Declare before the commands that reference it, the same
164
242
  # ordering `AggregateBuilder#given`'s own comment names — though
165
243
  # since ADR 0028, `command` only queues a descriptor and actually
166
244
  # builds at `#drain_pending!` time, well after this whole block
@@ -168,87 +246,119 @@ module Hecks
168
246
  # order within the block no longer actually matters here; named
169
247
  # for the reader anyway, since `given`'s own resolution logic
170
248
  # (`CommandBuilder#reference_named_given`) still reads whatever
171
- # `@named_givens` holds AT THE COMMAND'S OWN BUILD TIME, not by
249
+ # `@named_givens` holds at the command's own build time, not by
172
250
  # magic.
173
- # RENAMED FROM `given` — item #13's full metaprogrammed dispatch
174
- # (slice 4b), same reasoning as reference_to_impl above.
251
+ # Declares a rule this piece's own commands must satisfy, or references one a sibling
252
+ # piece anywhere in the chapter already declared.
175
253
  #
176
- # BARE — NO BLOCK — REFERENCES ANOTHER PIECE'S OWN DECLARATION,
177
- # ANYWHERE IN THE CHAPTER, not just a sibling under this same
254
+ # Answers the `given` word through the table's `calls:` column —
255
+ # item #13's full metaprogrammed dispatch
256
+ # (slice 4b), same reasoning as `reference_to_impl` above.
257
+ #
258
+ # Bare — no block — references another piece's own declaration,
259
+ # anywhere in the chapter, not just a sibling under this same
178
260
  # aggregate — one level wider than round 4's own cross-entity
179
261
  # sharing, mirroring `AggregateBuilder#given_impl`'s own
180
262
  # chapter-wide shape exactly one level down. Real, live corpus
181
263
  # this closes: `Account::LedgerEntry` and `SafeDepositBox::Visit`
182
- # — two pieces under two DIFFERENT aggregates — independently
264
+ # — two pieces under two different aggregates — independently
183
265
  # typed `given("customer is active") { parent.customer.status ==
184
266
  # "active" }` byte for byte; neither the aggregate-level chapter
185
- # pool (a DIFFERENT canonical — bare `customer.status`, the
267
+ # pool (a different canonical — bare `customer.status`, the
186
268
  # wrong scope for a piece's own command) nor the existing
187
269
  # same-aggregate cross-entity pool (`@owner_named_givens`, scoped
188
- # to ONE aggregate's own entity tree) could reach across the
270
+ # to one aggregate's own entity tree) could reach across the
189
271
  # aggregate boundary. Resolved against `@chapter_entity_named_
190
272
  # givens`, keyed "AggregateName.EntityName" — see
191
273
  # `#reference_named_chapter_entity_given`'s own comment for the
192
274
  # algorithm and `docs/implemented/resolution-rules/
193
275
  # chapter-entity-given.md` for the full write-up.
194
276
  #
195
- # `declared_by:` is a PLAIN STRING ("Account.LedgerEntry"), not a
277
+ # `declared_by:` is a plain string ("Account.LedgerEntry"), not a
196
278
  # constant — unlike `AggregateBuilder#given_impl`'s own
197
279
  # `declared_by:`, which names a real aggregate constant. A piece
198
280
  # has no first-class, independently-addressable reference
199
281
  # anywhere in this language (only its owning aggregate does);
200
- # inventing one to make this ONE argument spelling symmetrical
282
+ # inventing one to make this one argument spelling symmetrical
201
283
  # with the aggregate-level word is a real, separate, unscoped
202
284
  # feature this fix does not need — ships textual now, the same
203
285
  # way `admits:` shipped textual before its own constant-bridge
204
286
  # existed, revisited only if a genuine, separate need for
205
287
  # constant-addressed pieces shows up later.
288
+ #
289
+ # @param description [String] the rule's description; also the name a sibling piece
290
+ # references it by when no block is given
291
+ # @param declared_by [String, nil] disambiguates which piece's own rule to reference,
292
+ # as `"Aggregate.Entity"`, when more than one shares `description`; only meaningful
293
+ # with no block
294
+ # @yield the predicate body; evaluated for its extracted source, never called directly
295
+ # @return [void]
296
+ # @raise [Bluebook::DSL::Malformed] if given a block whose source cannot be extracted;
297
+ # given no block, the description is immediately ambiguous between more than one
298
+ # already-loaded piece with no `declared_by` to disambiguate; an unresolved reference
299
+ # defers instead, and may still raise once the whole chapter has loaded, if it then
300
+ # resolves to none or more than one candidate
206
301
  def given_impl(description, declared_by: nil, &predicate)
207
302
  return reference_named_chapter_entity_given(description, declared_by: declared_by) unless predicate
208
303
 
209
304
  named = build_rule(Given, description, predicate, owner_name: @name, word: "given",
210
305
  extraction_failure: "its source could not be read, so no other runtime could ever evaluate it")
211
306
  @named_givens[description] = named
212
- # WRITE-THROUGH, first-declared-wins (`||=`) — a SECOND piece
307
+ # Write-through, first-declared-wins (`||=`) — a second piece
213
308
  # under the same aggregate independently declaring the exact
214
309
  # same description stays purely local to itself (no silent
215
310
  # overwrite of whatever the first piece already shared;
216
311
  # real, live case a fuzzer or a future codemod could easily
217
- # surface: two pieces phrasing an UNRELATED rule identically
312
+ # surface: two pieces phrasing an unrelated rule identically
218
313
  # by coincidence, same as an aggregate-level given already
219
314
  # tolerates today).
220
315
  @owner_named_givens[description] ||= named
221
- # WRITE-THROUGH, PER OWNER — the chapter-wide analogue of the
316
+ # **Write-through, per owner** — the chapter-wide analogue of the
222
317
  # line above, keyed by [description, this piece's own dotted
223
318
  # "Aggregate.Entity" name] rather than description alone, the
224
319
  # identical reasoning `AggregateBuilder#given_impl`'s own
225
- # chapter write-through gives: two DIFFERENT pieces (anywhere
226
- # in the chapter) independently declaring the SAME description
227
- # are two DISTINCT candidates a later bare reference chooses
320
+ # chapter write-through gives: two different pieces (anywhere
321
+ # in the chapter) independently declaring the same description
322
+ # are two distinct candidates a later bare reference chooses
228
323
  # between (via `declared_by:` once there is more than one),
229
324
  # never silently merged into one slot.
230
325
  @chapter_entity_named_givens[description] ||= {}
231
326
  @chapter_entity_named_givens[description]["#{@aggregate_name}.#{@name}"] ||= named
232
327
  end
233
328
 
234
- # A PIECE'S OWN SHAPE RULE (S10, ADR 0025's own "Rules" shape,
329
+ # A piece's own shape rule (S10, ADR 0025's own "Rules" shape,
235
330
  # one level down from `ValueObjectBuilder#invariant`, whose
236
331
  # extraction/error pattern this mirrors) — checked against
237
- # EVERY INSTANCE of this piece the aggregate holds, not once
332
+ # every instance of this piece the aggregate holds, not once
238
333
  # against the aggregate's own flat state
239
334
  # (`Admissibility#enforce_invariants`'s own recursive walk).
240
335
  # No reference-by-name form (unlike `given`) — no known corpus
241
- # need for a piece's own invariant to be shared with a SIBLING
336
+ # need for a piece's own invariant to be shared with a sibling
242
337
  # piece yet; if that need shows up, it is `given`'s own
243
338
  # cross-entity write-through pattern to extend, not a reason to
244
339
  # invent a second one here speculatively.
245
- # RENAMED FROM `invariant` — item #13's full metaprogrammed
246
- # dispatch (slice 4b), same reasoning as given_impl above.
340
+ # Declares a rule every instance of this piece must satisfy.
341
+ #
342
+ # Answers the `invariant` word through the table's `calls:`
343
+ # column — item #13's full metaprogrammed
344
+ # dispatch (slice 4b), same reasoning as `given_impl` above.
345
+ #
346
+ # @param description [String] the rule's description
347
+ # @yield the predicate body; evaluated for its extracted source, never called directly
348
+ # @return [void]
349
+ # @raise [Bluebook::DSL::Malformed] if the block's source could not be extracted
247
350
  def invariant_impl(description, &predicate)
248
351
  @invariants << build_rule(Invariant, description, predicate, owner_name: @name, word: "invariant",
249
352
  extraction_failure: "it would be a rule the IR cannot carry")
250
353
  end
251
354
 
355
+ # Assembles the declared attributes, relationships, nested constructs and rules into an
356
+ # `Entity`.
357
+ #
358
+ # @return [Bluebook::Entity] the built piece
359
+ # @raise [Bluebook::DSL::Malformed] if identity resolution, a lifecycle guard on a
360
+ # command with no lifecycle, a lifecycle-field mutation outside a transition, or a
361
+ # synthesized closed set colliding with one the aggregate already holds fails
252
362
  def build
253
363
  drain_pending!
254
364
  resolve_pending_identity!
@@ -268,6 +378,26 @@ module Hecks
268
378
  )
269
379
  end
270
380
 
381
+ # Evaluates an `entity` block against a fresh builder and returns what it built.
382
+ #
383
+ # @param name [String] the entity's name
384
+ # @param owner_value_objects [Array<Bluebook::ValueObject>] the owning aggregate's value
385
+ # objects, for `identified_by` to resolve against
386
+ # @param owner_named_givens [Hash{String => Bluebook::Given}] the aggregate-wide,
387
+ # cross-entity given pool
388
+ # @param identity_name_prefix [String, nil] the prefix a synthesized identity value
389
+ # object's name takes
390
+ # @param identity_value_object_installer [#call, nil] called to install a synthesized
391
+ # value object onto the owning aggregate
392
+ # @param aggregate_name [String, nil] the root aggregate's name, for chapter-wide given
393
+ # keys
394
+ # @param chapter_entity_named_givens [Hash{String => Hash{String => Bluebook::Given}}]
395
+ # the chapter-wide, entity-scoped given pool
396
+ # @param chapter_entity_pending_givens [Array<Hash>] unresolved chapter-wide bare given
397
+ # references
398
+ # @yield the entity body, evaluated with the builder as `self`; may be omitted
399
+ # @return [Bluebook::Entity] the built piece
400
+ # @raise [Bluebook::DSL::Malformed] if the body fails any check `#build` raises
271
401
  def self.build(name, owner_value_objects: [], owner_named_givens: {},
272
402
  identity_name_prefix: nil, identity_value_object_installer: nil,
273
403
  aggregate_name: nil, chapter_entity_named_givens: {}, chapter_entity_pending_givens: [], &block)
@@ -283,25 +413,25 @@ module Hecks
283
413
 
284
414
  private
285
415
 
286
- # PRIMITIVE 2 (RuleReference#resolve_owner_keyed) — the CHAPTER-
287
- # WIDE, ENTITY-SCOPED analogue of `AggregateBuilder#
416
+ # Primitive 2 (RuleReference#resolve_owner_keyed) — the chapter-
417
+ # wide, entity-scoped analogue of `AggregateBuilder#
288
418
  # reference_named_chapter_given`; the three branches below are
289
- # this construct's OWN refusal wording, not shared, matching that
419
+ # this construct's own refusal wording, not shared, matching that
290
420
  # method's own precedent (`declared_by:` only exists on `given`
291
- # so far, at either scope). UNRESOLVED is deferred, not raised
421
+ # so far, at either scope). Unresolved is deferred, not raised
292
422
  # here — see `#pending_chapter_entity_given`, below.
293
423
  #
294
- # WRITES THROUGH TO `@owner_named_givens` TOO — not just
424
+ # **Writes through to `@owner_named_givens` too** — not just
295
425
  # `@named_givens` — or this piece resolving a description via the
296
- # WIDER, chapter pool would leave the NARROWER, same-aggregate
426
+ # wider, chapter pool would leave the narrower, same-aggregate
297
427
  # pool (`EntityBuilder#given_impl`'s own block-form write-through)
298
- # never populated for this description, breaking any SIBLING
428
+ # never populated for this description, breaking any sibling
299
429
  # piece's existing command-level bare reference
300
430
  # (`CommandBuilder#reference_named_given`) that depends on it —
301
431
  # real, live corpus: `SafeDepositBox::KeyIssuance.Return`'s own
302
432
  # bare `given("customer is active")` resolves through
303
433
  # `@owner_named_givens`, populated by `Visit`'s declaration
304
- # whether `Visit` types the predicate itself OR (now) references
434
+ # whether `Visit` types the predicate itself or (now) references
305
435
  # `Account::LedgerEntry`'s instead — this write keeps that
306
436
  # working unchanged either way, `||=` so nothing here overrides
307
437
  # an actual local declaration if one is ever added later.
@@ -329,10 +459,10 @@ module Hecks
329
459
  @owner_named_givens[description] ||= named
330
460
  end
331
461
 
332
- # A CHAPTER MAY BE SPLIT ACROSS FILES — the identical reason
462
+ # A chapter may be split across files — the identical reason
333
463
  # `AggregateBuilder#pending_chapter_given` defers rather than
334
464
  # raising the moment a bare reference outruns what's loaded so
335
- # far. Hands back a PLACEHOLDER `Given`, embedded by Ruby object
465
+ # far. Hands back a placeholder `Given`, embedded by Ruby object
336
466
  # reference in this piece's own `preconditions`, and queues the
337
467
  # request in `@chapter_entity_pending_givens` —
338
468
  # `BluebookBuilder#resolve_pending_chapter_entity_givens!`
@@ -344,22 +474,22 @@ module Hecks
344
474
  placeholder
345
475
  end
346
476
 
347
- # A PIECE'S OWN `one_of` LANDS ON ITS AGGREGATE. A type-position
477
+ # A piece's own `one_of` lands on its aggregate. A type-position
348
478
  # `one_of("never_moved", "moved")` on an entity attribute
349
479
  # synthesizes a closed-set value object — and until this, that
350
- # object was built and then DROPPED: `Entity.declare` carries no
480
+ # object was built and then dropped: `Entity.declare` carries no
351
481
  # value objects, so the synthesized set existed nowhere in the
352
482
  # finished graph. The attribute stayed typed "Moved" with nothing
353
483
  # to resolve it: runtime admission had no closed set to enforce
354
484
  # (the one_of was decorative), and the fuzzer's ValueGenerator
355
485
  # crashed every run on the first domain to declare one — a chess
356
486
  # King/Rook's own castling flag — with `does not know primitive
357
- # type "Moved"`. Installed through the SAME hook an entity's own
487
+ # type "Moved"`. Installed through the same hook an entity's own
358
488
  # identity value object already rides to the aggregate
359
489
  # (`identity_value_object_installer`, threaded unchanged through
360
490
  # nested pieces). Two sibling pieces synthesizing the same set
361
- # (King's and Rook's own `moved`) install it once; the same NAME
362
- # with a DIFFERENT member list is refused as the collision it is,
491
+ # (King's and Rook's own `moved`) install it once; the same name
492
+ # with a different member list is refused as the collision it is,
363
493
  # never first-wins silently.
364
494
  def install_closed_sets!
365
495
  return unless @identity_value_object_installer
@@ -435,7 +565,7 @@ module Hecks
435
565
 
436
566
  # `identified_by`'s own resolution pool (AttributeCollector#resolve_
437
567
  # pending_identity!'s hook, S9) — a piece mints no value objects of
438
- # its own, so a bare field's own type resolves against its OWNER
568
+ # its own, so a bare field's own type resolves against its owner
439
569
  # aggregate's, passed in at declaration (`AggregateBuilder#entity`).
440
570
  def identity_pool = @owner_value_objects
441
571