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
@@ -15,28 +15,48 @@ module Hecks
15
15
  include QuerySpecification::Common::DSL
16
16
  include WordGate
17
17
 
18
+ # @param name [String] the query's name, as written after `query`
18
19
  def initialize(name)
19
20
  @name = name
20
21
  @wheres = []
21
22
  end
22
23
 
24
+ # Sets the human-readable description shown for this query.
25
+ #
26
+ # @param value [String] the description text
27
+ # @return [String] the description as stored
23
28
  def description(value) = @description = value
24
29
 
30
+ # Declares a query parameter that names another aggregate's identity.
31
+ #
25
32
  # A query parameter naming another aggregate's own identity
26
33
  # (Card.Active's own `Board`, filtering to one board's cards) —
27
34
  # just a plain attribute typed as a reference,
28
- # AttributeCollector#attribute already handling an Reference
35
+ # `AttributeCollector#attribute_impl` already handling a Reference
29
36
  # exactly like any other. No "acts on itself" case to
30
37
  # distinguish here the way a command's own reference_to has —
31
38
  # a query has no root of its own to act on, only parameters.
32
- # RENAMED FROM `reference_to` — item #13's full metaprogrammed
33
- # dispatch (slice 4b). Bootstrap-reachable, in
34
- # GenericDispatch::BOOTSTRAP_CALLS_FALLBACK.
39
+ #
40
+ # Answers the `reference_to` word through the table's `calls:`
41
+ # column — item #13's full metaprogrammed dispatch (slice 4b).
42
+ # Bootstrap-reachable, in `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`.
43
+ #
44
+ # @param type [Module, Symbol, String] the referenced aggregate, written as a bare
45
+ # constant
46
+ # @param as [Symbol, nil] the parameter's name; nil derives it from the target
47
+ # @param optional [Boolean] whether the parameter may be omitted when the query runs
48
+ # @return [void]
49
+ # @raise [Bluebook::DSL::Malformed] if `as` (or the derived name) is already declared
35
50
  def reference_to_impl(type, as: nil, optional: false)
36
51
  target = Naming.demodulise(type)
37
52
  attribute_impl(as || default_reference_name(target), Reference.new(target), optional: optional)
38
53
  end
39
54
 
55
+ # Assembles the declared parameters and filtering into a `Query`.
56
+ #
57
+ # @return [Bluebook::Query] the built query
58
+ # @raise [Bluebook::DSL::Malformed] if the body declares `cursor`, which no interpreter
59
+ # implements
40
60
  def build
41
61
  seal_cursor
42
62
  Query.new(
@@ -54,6 +74,15 @@ module Hecks
54
74
  )
55
75
  end
56
76
 
77
+ # Evaluates a `query` block against a fresh builder, then fills in owner-derived types.
78
+ #
79
+ # @param name [String] the query's name
80
+ # @param owner_attributes [Array<Bluebook::Attribute>] the enclosing aggregate or
81
+ # entity's own attributes, matched against the block's own parameter names
82
+ # @yield the query body, evaluated with the builder as `self`; may be omitted
83
+ # @return [Bluebook::Query] the built query
84
+ # @raise [Bluebook::DSL::Malformed] if the body declares `cursor`, a duplicate attribute,
85
+ # or a filtering clause the query language refuses
57
86
  def self.build(name, owner_attributes: [], &block)
58
87
  builder = new(name)
59
88
  builder.instance_eval(&block) if block
@@ -69,14 +98,14 @@ module Hecks
69
98
 
70
99
  private
71
100
 
72
- # A block parameter names one of the OWNER's (the aggregate or entity
101
+ # A block parameter names one of the owner's (the aggregate or entity
73
102
  # this query is declared on) own already-declared attributes —
74
103
  # `query "ForDecision" do |decision| where decision: :decision end`
75
104
  # on Submission, whose own `attribute :decision, DecisionRef` already
76
105
  # says what `decision` is. Restating `attribute :decision, DecisionRef`
77
106
  # a second time inside the query was pure duplication; this derives
78
107
  # the same type from the owner instead. Only fills in a name the block
79
- # body did NOT already declare explicitly (checked AFTER instance_eval
108
+ # body did not already declare explicitly (checked after instance_eval
80
109
  # runs, so an existing bluebook still spelling it out both ways keeps
81
110
  # working unchanged — this only removes the need to, never refuses
82
111
  # the choice to). A block parameter matching nothing on the owner is
@@ -90,8 +119,8 @@ module Hecks
90
119
  owner_attr = owner_attributes.find { |a| a.name == param_name }
91
120
  next unless owner_attr
92
121
 
93
- # `Attribute.new` DIRECTLY, not the public `attribute(...)` DSL
94
- # entry — `owner_attr.type` is already SPELLED (a demodulised
122
+ # `Attribute.new` directly, not the public `attribute(...)` DSL
123
+ # entry — `owner_attr.type` is already spelled (a demodulised
95
124
  # String, `Attribute#spell`'s own doing), not a bareword the
96
125
  # bluebook author typed, so it must not run through
97
126
  # `AttributeCollector#attribute`'s quoted-type refusal (ADR
@@ -14,18 +14,33 @@ module Hecks
14
14
  include QuerySpecification::Common::DSL
15
15
  include WordGate
16
16
 
17
+ # @param name [String] the read model's own name, as written after `read_model`
17
18
  def initialize(name)
18
19
  @name = name
19
20
  end
20
21
 
22
+ # Sets the human-readable description shown for this read model.
23
+ #
24
+ # @param value [String] the description text
25
+ # @return [String] the description as stored
21
26
  def description(value)
22
27
  # moved to the language: ProjectionText / purpose, on Projection.Declare
23
28
  @description = value
24
29
  end
25
30
 
26
- # RENAMED FROM `reference_to` — item #13's full metaprogrammed
27
- # dispatch (slice 4b). Bootstrap-reachable, in
28
- # GenericDispatch::BOOTSTRAP_CALLS_FALLBACK.
31
+ # Declares the read model's own "one" side — the aggregate head every row projects
32
+ # around, keyed by its identity.
33
+ #
34
+ # Reached through `calls: "reference_to_impl"`. Bootstrap-reachable — the self-hosted
35
+ # `Bluebook.WholeBluebook` read model declares one — so it's exercised while
36
+ # `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK` is still in play, not only afterwards.
37
+ #
38
+ # @param type [Module, Symbol, String] the referenced aggregate, written as a bare
39
+ # constant
40
+ # @param as [Symbol, nil] the field name to project the reference under; `nil` derives it
41
+ # from the target's own name
42
+ # @return [void]
43
+ # @raise [Bluebook::DSL::Malformed] if `reference_to` was already declared
29
44
  def reference_to_impl(type, as: nil)
30
45
  raise Malformed, "#{@name} already has a projection reference" if @reference_target
31
46
 
@@ -33,28 +48,34 @@ module Hecks
33
48
  @reference_name = (as || Naming.snake(@reference_target)).to_sym
34
49
  end
35
50
 
36
- # Order-independent. `many:` is decided by comparing the included type
37
- # against the reference target, so this used to REFUSE an include
38
- # declared before the reference — a rule guarding an implementation
39
- # limitation rather than a truth about read models. The includes are
40
- # collected raw and resolved at build, when the reference is known, so
41
- # there is no rule left to enforce.
42
- # RENAMED FROM `include`/`group_by` — item #13's full
43
- # metaprogrammed dispatch (slice 4c). `include` IS bootstrap-
44
- # reachable (every core chapter's own `read_model` names which
45
- # aggregates it includes with it — a first grep dismissed this
46
- # as `Module#include` noise and was wrong; the cold-boot test
47
- # after this rename caught it directly), so it's in
48
- # BOOTSTRAP_CALLS_FALLBACK; `group_by` is not (no core read_model
49
- # groups). The class-level `include WordGate` this file's own
50
- # class body uses is `Module#include`, a different receiver,
51
- # unaffected by renaming this INSTANCE method either way.
51
+ # Adds one aggregate head this read model projects.
52
+ #
53
+ # Order-independent: `many:` is decided by comparing the included type
54
+ # against the reference target, resolved at build time (when the
55
+ # reference is known), not at the moment `include` itself runs — so
56
+ # an include declared before `reference_to` resolves exactly like one
57
+ # declared after it.
58
+ #
59
+ # Reached through `calls: "include_impl"`. Bootstrap-reachable —
60
+ # every core chapter's own `read_model` names which aggregates it
61
+ # includes with it — so it's exercised while
62
+ # `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK` is still in play.
63
+ # `group_by_impl`, below, is table-routed the same way but is not
64
+ # bootstrap-reachable (no core read_model groups). The class-level
65
+ # `include WordGate` this file's own class body uses is
66
+ # `Module#include`, a different receiver, unrelated to this
67
+ # instance method sharing its name.
68
+ #
69
+ # @param type [Module, Symbol, String] the included aggregate, written as a bare constant
70
+ # @param as [Symbol, nil] the field name to project this head's rows under; `nil` derives
71
+ # it from the target's own name (pluralized for a many-side head)
72
+ # @return [void]
52
73
  def include_impl(type, as: nil)
53
74
  @includes ||= []
54
75
  @includes << [Naming.demodulise(type), as]
55
76
  end
56
77
 
57
- # `on:` (ADR 0055) — OVERRIDES of `QuerySpecification::Common::DSL`'s
78
+ # `on:` (ADR 0055) — overrides of `QuerySpecification::Common::DSL`'s
58
79
  # shared `where_impl`/`order_by_impl`/`limit_impl`/`offset_impl`,
59
80
  # scoped to `ReadModelBuilder` alone rather than added to the shared
60
81
  # module `Query` also mixes in: a plain `query` has no
@@ -63,10 +84,10 @@ module Hecks
63
84
  # means a `Query`'s own `where(..., on: X)` gets Ruby's own loud
64
85
  # `unknown keyword: :on` instead of quietly doing nothing.
65
86
  #
66
- # `on:` names the target by TYPE (`on: Character`), resolved the
87
+ # `on:` names the target by type (`on: Character`), resolved the
67
88
  # same way `reference_to`/`include` already resolve their own type
68
89
  # argument (`Naming.demodulise`) — not by the include's own `as:`
69
- # alias. A read model that `include`s the SAME type twice under two
90
+ # alias. A read model that `include`s the same type twice under two
70
91
  # different `as:` has no way to say which one `on:` means today; no
71
92
  # real corpus read model does this, so it's a real, deliberate scope
72
93
  # limit (see ADR 0055), not an oversight.
@@ -77,14 +98,14 @@ module Hecks
77
98
  # `status: "disputed"` captured as `**kwargs` (GenericDispatch's own
78
99
  # `builder.send(calls, *args, **kwargs, &block)`), and Ruby stops
79
100
  # auto-converting a bare `**hash` call into a plain positional Hash
80
- # THE MOMENT a method declares any real keyword parameter — so a
101
+ # the moment a method declares any real keyword parameter — so a
81
102
  # `(clauses, on: nil)` signature raised "wrong number of arguments
82
103
  # (given 0, expected 1)" on every ordinary `where(field: value)`
83
104
  # call, never reaching `on:` at all. `**rest` sidesteps this: Ruby
84
105
  # still auto-splits `on:` into the declared keyword and gathers
85
- # every OTHER key into `rest` regardless of how the caller wrote it.
106
+ # every other key into `rest` regardless of how the caller wrote it.
86
107
  #
87
- # `QuerySpecification::Common::WhereClause` etc — FULLY QUALIFIED,
108
+ # `QuerySpecification::Common::WhereClause` etc — fully qualified,
88
109
  # not the bare names `dsl.rb`'s own shared `where_impl` gets away
89
110
  # with. That file is lexically nested inside `Common` itself, so
90
111
  # `WhereClause` resolves directly; this class is nested inside
@@ -92,10 +113,25 @@ module Hecks
92
113
  # `QuerySpecification::Common` at all — a bare `WhereClause` here
93
114
  # falls through to `const_missing` and, mid-bluebook-load, that's
94
115
  # `ConstShim`, which resolves it against the self-hosted grammar
95
- # domain's OWN unrelated `WhereClause` construct instead (a `Module`,
116
+ # domain's own unrelated `WhereClause` construct instead (a `Module`,
96
117
  # not this `Struct`) — found directly by reproducing "undefined
97
118
  # method `new' for module WhereClause" against a real corpus load,
98
119
  # not guessed.
120
+ #
121
+ # Records one `WhereClause` per `field => value` pair, targeted at a named many-side
122
+ # include when this read model has more than one.
123
+ #
124
+ # @param positional [Array<Hash{Symbol => Object}>] at most one bare Hash of where
125
+ # clauses; in practice always empty, since Ruby routes an ordinary
126
+ # `where(field: value)` call entirely into `rest` (see the comment above)
127
+ # @param on [Module, Symbol, String, nil] the many-side included aggregate these clauses
128
+ # apply to; `nil` when this read model has at most one many-side head
129
+ # @param rest [Hash{Symbol => Object}] `field => value` where clauses; a value is either
130
+ # a literal, a Symbol naming a query argument, or a one-pair Hash `{ comparator =>
131
+ # operand }` such as `{ gte: :minimum }` — a bare value means `eq`
132
+ # @return [void]
133
+ # @raise [ArgumentError] if more than one positional argument is given, or a Hash value
134
+ # does not have exactly one pair, or names a comparator outside `COMPARATORS`
99
135
  def where_impl(*positional, on: nil, **rest)
100
136
  raise ArgumentError, "wrong number of arguments (given #{positional.size}, expected 1)" if positional.size > 1
101
137
 
@@ -108,30 +144,55 @@ module Hecks
108
144
  end
109
145
  end
110
146
 
147
+ # Records the read model's single ordering, replacing any declared earlier.
148
+ #
149
+ # @param field [Symbol, String] the field to order by; a dotted path such as
150
+ # `:"order.value"` reaches a value object's member
151
+ # @param direction [Symbol, String] `:asc` or `:desc`
152
+ # @param on [Module, Symbol, String, nil] the many-side included aggregate this ordering
153
+ # applies to; `nil` when this read model has at most one many-side head
154
+ # @return [QuerySpecification::Common::OrderBy] the ordering just recorded
111
155
  def order_by_impl(field, direction = :asc, on: nil)
112
156
  @order_by = QuerySpecification::Common::OrderBy.new(field: field, direction: direction, target: resolve_target(on))
113
157
  end
114
158
 
159
+ # Records the most rows the targeted collection returns.
160
+ #
161
+ # @param value [Integer, Symbol] a literal row count, or a Symbol naming the query
162
+ # argument that supplies it
163
+ # @param on [Module, Symbol, String, nil] the many-side included aggregate this limit
164
+ # applies to; `nil` when this read model has at most one many-side head
165
+ # @return [QuerySpecification::Common::LimitSpec] the limit just recorded
115
166
  def limit_impl(value, on: nil)
116
167
  @limit = QuerySpecification::Common::LimitSpec.new(value: value, target: resolve_target(on))
117
168
  end
118
169
 
170
+ # Records how many matched rows the targeted collection skips before the limit applies.
171
+ #
172
+ # @param value [Integer, Symbol] a literal row count, or a Symbol naming the query
173
+ # argument that supplies it
174
+ # @param on [Module, Symbol, String, nil] the many-side included aggregate this offset
175
+ # applies to; `nil` when this read model has at most one many-side head
176
+ # @return [QuerySpecification::Common::OffsetSpec] the offset just recorded
119
177
  def offset_impl(value, on: nil)
120
178
  @offset = QuerySpecification::Common::OffsetSpec.new(value: value, target: resolve_target(on))
121
179
  end
122
180
 
123
- # NAMES which of the eligible head's own fields to nest its rows
181
+ # Names which of the eligible head's own fields to nest its rows
124
182
  # under — one level per field, the leaf being that row with the
125
183
  # named fields removed (they're already spent, as the keys that
126
184
  # reached it). The same "exactly one many-side head" rule
127
185
  # `seal_query_options` already enforces for where/order_by/etc
128
186
  # applies here too (`seal_group_by`) — grouping is a question
129
- # about ONE collection's own rows, same as those are.
187
+ # about one collection's own rows, same as those are.
188
+ # @param fields [Array<Symbol>] the eligible many-side head's own fields to nest its rows
189
+ # under, one level per field
190
+ # @return [void]
130
191
  def group_by_impl(*fields)
131
192
  # Hash rows, `{field:}`, not bare symbols — same shape
132
193
  # `aggregate_heads` already uses for exactly the reason it
133
194
  # does: the language's own self-hosted grammar (`projection
134
- # .bluebook`'s `GroupByField`) has to have SOMETHING to read a
195
+ # .bluebook`'s `GroupByField`) has to have something to read a
135
196
  # `field:` off of when `Judge` walks this list generically: a
136
197
  # bare `Symbol` has no attribute of its own to read.
137
198
  @group_by = fields.map { |field| { field: field.to_sym } }
@@ -139,23 +200,23 @@ module Hecks
139
200
 
140
201
  # `count` -- a bare row count over the eligible many-side head's
141
202
  # own rows (after `where`/`order_by`/`limit`/`offset` apply, the
142
- # same rows `group_by` itself would nest) -- ANSWERS "how many
143
- # match", not "which ones". A sibling REDUCTION to `group_by`,
203
+ # same rows `group_by` itself would nest) -- answers "how many
204
+ # match", not "which ones". A sibling reduction to `group_by`,
144
205
  # not a filter: `seal_aggregation` refuses combining it with
145
206
  # `group_by` or with `median`, the same "exactly one many-side
146
207
  # head" rule `seal_group_by` already enforces for the same
147
208
  # reason -- a bare marker, so `@count` is left unset (nil, not
148
- # false) rather than defaulted, matching the "ABSENT is not
149
- # EMPTY" reading `Lifecycle`'s own optional fields already rely
209
+ # false) rather than defaulted, matching the "absent is not
210
+ # empty" reading `Lifecycle`'s own optional fields already rely
150
211
  # on for the Judge's setter dispatch (Behaviour::ReadModel#
151
212
  # count?, ReadModelInterpreter#aggregation_target).
152
213
  # `count` — item #13's full metaprogrammed dispatch, slice 1
153
- # (whole-project table-unification survey): the ONLY Keyword row
214
+ # (whole-project table-unification survey): the only Keyword row
154
215
  # filling `count` — a bare marker, now stored as literal `true`
155
216
  # by `GenericDispatch` off that same table fact.
156
217
 
157
- # `median(field)` -- the median VALUE of one numeric field
158
- # across the eligible many-side head's own rows. EVEN COUNT: the
218
+ # `median(field)` -- the median value of one numeric field
219
+ # across the eligible many-side head's own rows. Even count: the
159
220
  # average of its two middle values (the standard definition,
160
221
  # not "the lower of the two") -- see
161
222
  # Runtime::ReadModelInterpreter#median for where that lands and
@@ -168,16 +229,24 @@ module Hecks
168
229
  # same shape as `count`, above (a bare, kind-driven coerce-and-
169
230
  # assign).
170
231
 
171
- # `reference_to` is now OPTIONAL — a read model with no root is a
172
- # BULK one: every `include`d head reads its own aggregate whole
232
+ # Assembles the declared references, includes and clauses into a `ReadModel`, after
233
+ # validating them.
234
+ #
235
+ # `reference_to` is optional — a read model with no root is a
236
+ # bulk one: every `include`d head reads its own aggregate whole
173
237
  # (no FK match against a root that doesn't exist), and dispatch
174
- # takes no id argument at all. This used to be REQUIRED, on the
175
- # assumption a read model was always "one root record's own
176
- # cross-aggregate view" — true of every real corpus report so
177
- # far, but not a truth about read models themselves: `group_by`'s
178
- # own real use (nesting an aggregate's OWN whole table by its own
179
- # field values) has no root to speak of. Still needs to describe
180
- # SOMETHING — zero includes AND no reference is refused.
238
+ # takes no id argument at all. Requiring one would refuse a real,
239
+ # legitimate shape: `group_by`'s own real use (nesting an
240
+ # aggregate's own whole table by its own field values) has no root
241
+ # to speak of. Still needs to describe something — zero includes
242
+ # and no reference is refused.
243
+ #
244
+ # @return [Bluebook::ReadModel] the built read model
245
+ # @raise [Bluebook::DSL::Malformed] if neither `reference_to` nor any `include` is
246
+ # declared, if `where`/`order_by`/`limit`/`offset`/`group_by`/`count`/`median` name an
247
+ # `on:` that isn't a many-side include or are left untargeted with more than one
248
+ # many-side head, if `count` and `median` are both declared or combined with
249
+ # `group_by`, or if `cursor` is declared
181
250
  def build
182
251
  if !@reference_target && Array(@includes).empty?
183
252
  raise Malformed,
@@ -200,6 +269,12 @@ module Hecks
200
269
  count: @count, median_field: @median_field)
201
270
  end
202
271
 
272
+ # Evaluates a `read_model` block against a fresh builder and returns what it built.
273
+ #
274
+ # @param name [String] the read model's own name
275
+ # @yield the read model's body, `instance_eval`'d against a new builder
276
+ # @return [Bluebook::ReadModel] the built read model
277
+ # @raise [Bluebook::DSL::Malformed] see `#build`
203
278
  def self.build(name, &block)
204
279
  builder = new(name)
205
280
  builder.instance_eval(&block) if block
@@ -213,18 +288,18 @@ module Hecks
213
288
  # (the "one" side, the reference target itself, is a single row;
214
289
  # ordering, paging, or tenant-scoping one row means nothing). ADR
215
290
  # 0055 gave `where`/`order_by`/`limit`/`offset` an `on:` to name
216
- # WHICH many-side collection they mean, so this asks two questions
291
+ # which many-side collection they mean, so this asks two questions
217
292
  # now instead of one:
218
293
  #
219
294
  # 1. Does every declared `on:` actually name a many-side included
220
295
  # aggregate? Checked regardless of how many many-side heads
221
296
  # exist — a typo refuses immediately, not only once ambiguity
222
297
  # would otherwise bite.
223
- # 2. Is there still an UNTARGETED option declared (including
298
+ # 2. Is there still an untargeted option declared (including
224
299
  # `authorize`'s own `tenant:`, which has no `on:` of its own —
225
300
  # a real, deliberate scope limit, see ADR 0055)? An untargeted
226
301
  # option still needs exactly one many-side head to mean
227
- # anything unambiguous — the ORIGINAL rule, unchanged, and
302
+ # anything unambiguous — the original rule, unchanged, and
228
303
  # still worded the same way (`spec/runtime/
229
304
  # read_model_interpreter_spec.rb`'s existing refusal regex
230
305
  # still matches).
@@ -246,7 +321,7 @@ module Hecks
246
321
  end
247
322
 
248
323
  # Question 1 of `seal_query_options`'s own two, split out to keep
249
- # both under the same "one job per method" shape every OTHER seal in
324
+ # both under the same "one job per method" shape every other seal in
250
325
  # this file already holds to (each raises its own one Malformed, for
251
326
  # its own one reason).
252
327
  def validate_declared_targets!(many)
@@ -275,7 +350,7 @@ module Hecks
275
350
  end
276
351
 
277
352
  # Same shape as `seal_query_options`, same reason — `group_by`
278
- # answers a question about ONE collection's own rows, so zero or
353
+ # answers a question about one collection's own rows, so zero or
279
354
  # several many-side heads leaves it with no unambiguous target.
280
355
  def seal_group_by
281
356
  return unless @group_by&.any?
@@ -289,10 +364,10 @@ module Hecks
289
364
  "own rows; name which one by including only it"
290
365
  end
291
366
 
292
- # `count`/`median` are the OTHER two reductions a read model may
367
+ # `count`/`median` are the other two reductions a read model may
293
368
  # declare over its one eligible collection — same "exactly one
294
369
  # many-side head" rule as `seal_group_by`, plus a rule
295
- # `seal_group_by` doesn't need: a read model reports ONE shape,
370
+ # `seal_group_by` doesn't need: a read model reports one shape,
296
371
  # so `count` and `median` cannot both be declared, and neither
297
372
  # may combine with `group_by` (nesting rows and reducing them to
298
373
  # a scalar are answers to different questions ; a caller asking