hecks 2.7.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (463) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +11 -0
  3. data/lib/hecks/adapters/driven/claude_code.rb +4 -104
  4. data/lib/hecks/adapters/driven/d1.rb +44 -237
  5. data/lib/hecks/adapters/driven/folder.rb +17 -76
  6. data/lib/hecks/adapters/driven/google_authentication.rb +15 -49
  7. data/lib/hecks/adapters/driven/governance_authorization.rb +23 -70
  8. data/lib/hecks/adapters/driven/heki/journal.rb +8 -41
  9. data/lib/hecks/adapters/driven/heki/saga_store.rb +12 -54
  10. data/lib/hecks/adapters/driven/heki/snapshot.rb +4 -12
  11. data/lib/hecks/adapters/driven/heki.rb +13 -42
  12. data/lib/hecks/adapters/driven/identity_registry.rb +3 -14
  13. data/lib/hecks/adapters/driven/in_memory_ordering.rb +9 -26
  14. data/lib/hecks/adapters/driven/in_process_key_vault.rb +2 -14
  15. data/lib/hecks/adapters/driven/lambda/client.rb +10 -51
  16. data/lib/hecks/adapters/driven/lambda.rb +10 -84
  17. data/lib/hecks/adapters/driven/local_storage.rb +5 -40
  18. data/lib/hecks/adapters/driven/memory.rb +18 -99
  19. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +5 -21
  20. data/lib/hecks/adapters/driven/postgres/codec.rb +6 -28
  21. data/lib/hecks/adapters/driven/postgres/outbox.rb +3 -12
  22. data/lib/hecks/adapters/driven/postgres/reconnect.rb +4 -30
  23. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +34 -108
  24. data/lib/hecks/adapters/driven/postgres.rb +145 -134
  25. data/lib/hecks/adapters/driven/postgres_era.adapter +4 -15
  26. data/lib/hecks/adapters/driven/prism.rb +7 -30
  27. data/lib/hecks/adapters/driven/sql_query_builder.rb +21 -121
  28. data/lib/hecks/adapters/driven/sqlite/codec.rb +8 -40
  29. data/lib/hecks/adapters/driven/sqlite/projection.rb +26 -82
  30. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +40 -84
  31. data/lib/hecks/adapters/driven/sqlite.rb +128 -65
  32. data/lib/hecks/adapters/driven/tenant_provisioner.rb +15 -37
  33. data/lib/hecks/adapters/driven.rb +5 -36
  34. data/lib/hecks/adapters/driving/github_webhook.rb +8 -76
  35. data/lib/hecks/behaviors/dsl.rb +7 -25
  36. data/lib/hecks/behaviors/expectations.rb +32 -142
  37. data/lib/hecks/behaviors/ir.rb +7 -20
  38. data/lib/hecks/behaviors/rspec.rb +6 -17
  39. data/lib/hecks/behaviors/runner.rb +10 -34
  40. data/lib/hecks/behaviors.rb +4 -8
  41. data/lib/hecks/bluebook/aggregate.rb +9 -75
  42. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +9 -38
  43. data/lib/hecks/bluebook/assembly/build.rb +4 -15
  44. data/lib/hecks/bluebook/assembly/contract.rb +15 -136
  45. data/lib/hecks/bluebook/assembly/contracts.rb +54 -174
  46. data/lib/hecks/bluebook/assembly/marks.rb +31 -223
  47. data/lib/hecks/bluebook/assembly/specializer.rb +6 -52
  48. data/lib/hecks/bluebook/assembly.rb +6 -45
  49. data/lib/hecks/bluebook/attribute.rb +12 -61
  50. data/lib/hecks/bluebook/behaviour/aggregate.rb +10 -39
  51. data/lib/hecks/bluebook/behaviour/attribute.rb +3 -14
  52. data/lib/hecks/bluebook/behaviour/chapter.rb +17 -42
  53. data/lib/hecks/bluebook/behaviour/command.rb +14 -66
  54. data/lib/hecks/bluebook/behaviour/domain_port.rb +4 -20
  55. data/lib/hecks/bluebook/behaviour/entity.rb +7 -27
  56. data/lib/hecks/bluebook/behaviour/hexagon.rb +23 -41
  57. data/lib/hecks/bluebook/behaviour/lifecycle.rb +15 -40
  58. data/lib/hecks/bluebook/behaviour/policy.rb +23 -52
  59. data/lib/hecks/bluebook/behaviour/process_manager.rb +30 -57
  60. data/lib/hecks/bluebook/behaviour/read_model.rb +38 -26
  61. data/lib/hecks/bluebook/behaviour/traits.rb +27 -64
  62. data/lib/hecks/bluebook/behaviour/value_object.rb +5 -24
  63. data/lib/hecks/bluebook/capabilities.rb +17 -49
  64. data/lib/hecks/bluebook/chapter.rb +10 -48
  65. data/lib/hecks/bluebook/command.rb +23 -81
  66. data/lib/hecks/bluebook/domain_port.rb +14 -63
  67. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +50 -229
  68. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +50 -377
  69. data/lib/hecks/bluebook/dsl/attribute_collector.rb +33 -204
  70. data/lib/hecks/bluebook/dsl/binding_proxy.rb +17 -65
  71. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +86 -708
  72. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +56 -170
  73. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
  74. data/lib/hecks/bluebook/dsl/command_builder.rb +74 -546
  75. data/lib/hecks/bluebook/dsl/const_shim.rb +8 -41
  76. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +12 -87
  77. data/lib/hecks/bluebook/dsl/entity_builder.rb +44 -330
  78. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +20 -269
  79. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +26 -189
  80. data/lib/hecks/bluebook/dsl/identity_declaration.rb +13 -88
  81. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +6 -20
  82. data/lib/hecks/bluebook/dsl/policy_builder.rb +16 -88
  83. data/lib/hecks/bluebook/dsl/port_builder.rb +2 -6
  84. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +22 -96
  85. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +31 -184
  86. data/lib/hecks/bluebook/dsl/query_builder.rb +11 -42
  87. data/lib/hecks/bluebook/dsl/read_model_builder.rb +75 -201
  88. data/lib/hecks/bluebook/dsl/rule_reference.rb +22 -163
  89. data/lib/hecks/bluebook/dsl/translation_builder.rb +28 -114
  90. data/lib/hecks/bluebook/dsl/value_object_builder.rb +10 -124
  91. data/lib/hecks/bluebook/dsl/word_gate.rb +16 -162
  92. data/lib/hecks/bluebook/dsl/world_builder.rb +19 -106
  93. data/lib/hecks/bluebook/entity.rb +13 -71
  94. data/lib/hecks/bluebook/expression/ast_json.rb +24 -190
  95. data/lib/hecks/bluebook/expression/ast_reader.rb +12 -54
  96. data/lib/hecks/bluebook/expression/canonical_form.rb +11 -88
  97. data/lib/hecks/bluebook/expression/evaluator.rb +47 -295
  98. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +30 -147
  99. data/lib/hecks/bluebook/expression/resolver.rb +37 -660
  100. data/lib/hecks/bluebook/hexagon.rb +5 -28
  101. data/lib/hecks/bluebook/lifecycle.rb +5 -16
  102. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -7
  103. data/lib/hecks/bluebook/meta_validator/judge.rb +111 -361
  104. data/lib/hecks/bluebook/meta_validator/plan.rb +33 -162
  105. data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -7
  106. data/lib/hecks/bluebook/meta_validator/readings.rb +56 -342
  107. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -176
  108. data/lib/hecks/bluebook/meta_validator/shapes.rb +54 -268
  109. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +47 -308
  110. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +7 -43
  111. data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -18
  112. data/lib/hecks/bluebook/meta_validator.rb +79 -349
  113. data/lib/hecks/bluebook/model_check/client_profile.rb +10 -91
  114. data/lib/hecks/bluebook/model_check.rb +102 -600
  115. data/lib/hecks/bluebook/pattern_subset.rb +11 -69
  116. data/lib/hecks/bluebook/process_manager.rb +13 -83
  117. data/lib/hecks/bluebook/project_loader.rb +3 -39
  118. data/lib/hecks/bluebook/project_register.rb +12 -60
  119. data/lib/hecks/bluebook/query.rb +9 -48
  120. data/lib/hecks/bluebook/read_model.rb +57 -81
  121. data/lib/hecks/bluebook/reference.rb +7 -41
  122. data/lib/hecks/bluebook/smoke_test.rb +8 -98
  123. data/lib/hecks/bluebook/synthesizer.rb +13 -62
  124. data/lib/hecks/bluebook/translation.rb +26 -62
  125. data/lib/hecks/bluebook/value_object.rb +11 -36
  126. data/lib/hecks/bluebook.rb +5 -22
  127. data/lib/hecks/cache_dir.rb +84 -0
  128. data/lib/hecks/cli/document.rb +86 -0
  129. data/lib/hecks/cli/ir.rb +45 -0
  130. data/lib/hecks/cli/mcp.rb +31 -0
  131. data/lib/hecks/cli/mcp_door.rb +366 -0
  132. data/lib/hecks/cli/model_check.rb +203 -0
  133. data/lib/hecks/cli/project_cli.rb +90 -0
  134. data/lib/hecks/cli/project_diagrams.rb +36 -0
  135. data/lib/hecks/cli/run.rb +173 -0
  136. data/lib/hecks/cli/smoke_test.rb +45 -0
  137. data/lib/hecks/cli/stores.rb +53 -0
  138. data/lib/hecks/cli.rb +160 -0
  139. data/lib/hecks/construct.rb +8 -34
  140. data/lib/hecks/deploy/bluebook/deploy.bluebook +33 -142
  141. data/lib/hecks/deploy/bluebook/deploy.hecksagon +2 -9
  142. data/lib/hecks/embryonaut_bluebook/lock.rb +3 -35
  143. data/lib/hecks/embryonaut_bluebook/shape.rb +1 -17
  144. data/lib/hecks/embryonaut_bluebook/vendor.rb +8 -53
  145. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +4 -19
  146. data/lib/hecks/embryonaut_bluebook.rb +4 -71
  147. data/lib/hecks/facade/cli_door.rb +24 -106
  148. data/lib/hecks/facade/cli_runner.rb +28 -172
  149. data/lib/hecks/facade/command_request.rb +13 -33
  150. data/lib/hecks/facade/handle.rb +34 -121
  151. data/lib/hecks/facade/json_door.rb +31 -146
  152. data/lib/hecks/facade/surface/aggregate_door.rb +39 -104
  153. data/lib/hecks/facade/surface/chapter.rb +10 -54
  154. data/lib/hecks/facade/surface.rb +8 -23
  155. data/lib/hecks/facade.rb +10 -25
  156. data/lib/hecks/forms/app.rb +15 -70
  157. data/lib/hecks/forms/command_form_renderer.rb +10 -29
  158. data/lib/hecks/forms/field_renderer.rb +17 -156
  159. data/lib/hecks/forms/field_shape.rb +30 -222
  160. data/lib/hecks/forms/html.rb +12 -47
  161. data/lib/hecks/forms/index_renderer.rb +1 -4
  162. data/lib/hecks/forms/page.rb +3 -9
  163. data/lib/hecks/forms/params.rb +14 -60
  164. data/lib/hecks/forms/port_argument.rb +2 -19
  165. data/lib/hecks/forms/query_form_renderer.rb +4 -79
  166. data/lib/hecks/forms/record_renderer.rb +8 -75
  167. data/lib/hecks/forms/record_table.rb +7 -50
  168. data/lib/hecks/forms/reference_options.rb +3 -30
  169. data/lib/hecks/forms/value_object_shape.rb +8 -35
  170. data/lib/hecks/forms.rb +5 -33
  171. data/lib/hecks/fqn.rb +5 -12
  172. data/lib/hecks/framework/bluebook/compliance.bluebook +16 -65
  173. data/lib/hecks/framework/bluebook/console_settings.bluebook +47 -134
  174. data/lib/hecks/framework/bluebook/framework.hecksagon +5 -14
  175. data/lib/hecks/framework/bluebook/governance.bluebook +13 -34
  176. data/lib/hecks/framework/bluebook/identity.bluebook +11 -26
  177. data/lib/hecks/framework/bluebook/privacy.bluebook +8 -33
  178. data/lib/hecks/framework.rb +11 -80
  179. data/lib/hecks/freezer.rb +5 -39
  180. data/lib/hecks/grammar/expression.bluebook +15 -40
  181. data/lib/hecks/grammar/translation.bluebook +3 -6
  182. data/lib/hecks/grammar.rb +16 -41
  183. data/lib/hecks/ir.rb +14 -83
  184. data/lib/hecks/language/adapter.bluebook +2 -7
  185. data/lib/hecks/language/bluebook/aggregate.bluebook +56 -221
  186. data/lib/hecks/language/bluebook/attaches/paging.bluebook +6 -15
  187. data/lib/hecks/language/bluebook/bluebook.bluebook +15 -83
  188. data/lib/hecks/language/bluebook/bluebook.hecksagon +3 -15
  189. data/lib/hecks/language/bluebook/command.bluebook +27 -144
  190. data/lib/hecks/language/bluebook/entity.bluebook +24 -97
  191. data/lib/hecks/language/bluebook/policy.bluebook +16 -67
  192. data/lib/hecks/language/bluebook/process_manager.bluebook +28 -151
  193. data/lib/hecks/language/bluebook/projection.bluebook +126 -60
  194. data/lib/hecks/language/bluebook/query.bluebook +12 -43
  195. data/lib/hecks/language/bluebook/shape.bluebook +23 -73
  196. data/lib/hecks/language/bluebook/syntax.bluebook +51 -262
  197. data/lib/hecks/language/bluebook/vocabulary.bluebook +98 -197
  198. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +3 -10
  199. data/lib/hecks/language/hecksagon/domain_port.bluebook +4 -19
  200. data/lib/hecks/language/hecksagon/hecksagon.bluebook +6 -25
  201. data/lib/hecks/language/hecksagon/port_operation.bluebook +3 -14
  202. data/lib/hecks/language/oidc.json +35 -0
  203. data/lib/hecks/language/port.bluebook +4 -17
  204. data/lib/hecks/language/translation/translation.bluebook +3 -9
  205. data/lib/hecks/language/translation/translation_aggregate.bluebook +5 -24
  206. data/lib/hecks/language/world/wiring.bluebook +1 -3
  207. data/lib/hecks/language/world/world.bluebook +5 -7
  208. data/lib/hecks/literal.rb +8 -50
  209. data/lib/hecks/mcp_door_scope.rb +95 -0
  210. data/lib/hecks/mcp_stdio_guard.rb +4 -41
  211. data/lib/hecks/naming.rb +29 -137
  212. data/lib/hecks/ports/access_control.rb +7 -23
  213. data/lib/hecks/ports/agent/answers.rb +5 -23
  214. data/lib/hecks/ports/agent.rb +47 -172
  215. data/lib/hecks/ports/authentication.rb +12 -39
  216. data/lib/hecks/ports/authorization.rb +16 -43
  217. data/lib/hecks/ports/clock.rb +5 -45
  218. data/lib/hecks/ports/extraction.rb +7 -12
  219. data/lib/hecks/ports/identity_assignment.rb +8 -29
  220. data/lib/hecks/ports/identity_generation.rb +6 -29
  221. data/lib/hecks/ports/identity_resolution.rb +2 -10
  222. data/lib/hecks/ports/key_vault.rb +9 -45
  223. data/lib/hecks/ports/persistence/append_only.rb +62 -65
  224. data/lib/hecks/ports/persistence/binding_policy.rb +4 -14
  225. data/lib/hecks/ports/persistence/codec_boundary.rb +20 -80
  226. data/lib/hecks/ports/persistence/null_saga_store.rb +4 -20
  227. data/lib/hecks/ports/persistence/plugin.rb +13 -42
  228. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +38 -141
  229. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +15 -115
  230. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +16 -84
  231. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +7 -26
  232. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -22
  233. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +55 -181
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/events.rb +77 -0
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +12 -122
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +28 -128
  237. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +73 -474
  238. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +31 -124
  239. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +45 -219
  240. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +13 -108
  241. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +18 -75
  242. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +9 -49
  243. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +6 -123
  244. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +23 -55
  245. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +14 -72
  246. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +7 -16
  247. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +14 -115
  248. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +13 -45
  249. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +127 -554
  250. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +13 -63
  251. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +2 -17
  252. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +3 -14
  253. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +13 -58
  254. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -17
  255. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -26
  256. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +9 -25
  257. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +22 -122
  258. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +34 -123
  259. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +5 -12
  260. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +3 -9
  261. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -14
  262. data/lib/hecks/ports/persistence/plugins/era.rb +4 -23
  263. data/lib/hecks/ports/persistence/postgres_dump/connection.rb +7 -23
  264. data/lib/hecks/ports/persistence/postgres_dump.rb +4 -35
  265. data/lib/hecks/ports/persistence/remote_runtime.rb +2 -24
  266. data/lib/hecks/ports/persistence/repository_factory.rb +8 -27
  267. data/lib/hecks/ports/persistence/state_codec.rb +23 -103
  268. data/lib/hecks/ports/persistence.rb +7 -14
  269. data/lib/hecks/ports/projection.rb +42 -84
  270. data/lib/hecks/ports/query/in_memory.rb +10 -58
  271. data/lib/hecks/ports/query/ordering.rb +11 -32
  272. data/lib/hecks/ports/query.rb +14 -37
  273. data/lib/hecks/ports.rb +2 -8
  274. data/lib/hecks/projections/bootstrap_table.rb +11 -43
  275. data/lib/hecks/projections/deploy/fargate/assembly.rb +2 -15
  276. data/lib/hecks/projections/deploy/fargate/cdn.rb +1 -48
  277. data/lib/hecks/projections/deploy/fargate/cdn_default.rb +2 -11
  278. data/lib/hecks/projections/deploy/fargate/check.rb +5 -14
  279. data/lib/hecks/projections/deploy/fargate/containers.rb +3 -78
  280. data/lib/hecks/projections/deploy/fargate/extras.rb +3 -44
  281. data/lib/hecks/projections/deploy/fargate/monitoring.rb +0 -44
  282. data/lib/hecks/projections/deploy/fargate/settings.rb +2 -54
  283. data/lib/hecks/projections/deploy/fargate/yaml.rb +13 -58
  284. data/lib/hecks/projections/deploy/fargate.rb +35 -202
  285. data/lib/hecks/projections/deploy/lambda.rb +151 -679
  286. data/lib/hecks/projections/deploy/preview/access_yaml.rb +1 -33
  287. data/lib/hecks/projections/deploy/preview/containers.rb +6 -111
  288. data/lib/hecks/projections/deploy/preview/routing_yaml.rb +4 -56
  289. data/lib/hecks/projections/deploy/preview/script.rb +2 -14
  290. data/lib/hecks/projections/deploy/preview/settings.rb +3 -33
  291. data/lib/hecks/projections/deploy/preview/task_yaml.rb +2 -38
  292. data/lib/hecks/projections/deploy/preview/template.rb +2 -58
  293. data/lib/hecks/projections/deploy/preview.rb +2 -67
  294. data/lib/hecks/projections/deploy/scripts/settings.rb +3 -25
  295. data/lib/hecks/projections/deploy/scripts.rb +4 -57
  296. data/lib/hecks/projections/deploy/shared.rb +47 -146
  297. data/lib/hecks/projections/deploy/smoke.rb +4 -61
  298. data/lib/hecks/projections/deploy/template_diff/comparer.rb +3 -25
  299. data/lib/hecks/projections/deploy/template_diff/loader.rb +2 -11
  300. data/lib/hecks/projections/deploy/template_diff.rb +5 -57
  301. data/lib/hecks/projections/diagrams.rb +20 -582
  302. data/lib/hecks/projections/glossary/html.rb +14 -130
  303. data/lib/hecks/projections/glossary/markdown.rb +8 -69
  304. data/lib/hecks/projections/glossary/mermaid.rb +9 -66
  305. data/lib/hecks/projections/glossary/sensitivity.rb +5 -34
  306. data/lib/hecks/projections/glossary/sentences.rb +14 -159
  307. data/lib/hecks/projections/glossary.rb +30 -222
  308. data/lib/hecks/projections/ir.rb +3 -10
  309. data/lib/hecks/projections/model/deviations.rb +15 -42
  310. data/lib/hecks/projections/model.rb +12 -51
  311. data/lib/hecks/projections/oidc.rb +12 -56
  312. data/lib/hecks/projections/parser_table.rb +9 -28
  313. data/lib/hecks/projections/reference.rb +9 -16
  314. data/lib/hecks/projections/rust_vocabulary.rb +12 -250
  315. data/lib/hecks/projections/shape.rb +3 -29
  316. data/lib/hecks/projections/statements.rb +7 -86
  317. data/lib/hecks/projections/vocabulary.rb +6 -22
  318. data/lib/hecks/projections.rb +2 -16
  319. data/lib/hecks/projector/cli_projector.rb +60 -375
  320. data/lib/hecks/projector/docs_projector.rb +10 -201
  321. data/lib/hecks/projector/exporter.rb +46 -138
  322. data/lib/hecks/projector/ir_projector.rb +2 -9
  323. data/lib/hecks/projector/narrate_projector.rb +15 -189
  324. data/lib/hecks/projector/target.rb +13 -76
  325. data/lib/hecks/projector.rb +30 -121
  326. data/lib/hecks/query_specification/common/comparators.rb +5 -26
  327. data/lib/hecks/query_specification/common/comparison.rb +35 -143
  328. data/lib/hecks/query_specification/common/dsl.rb +8 -24
  329. data/lib/hecks/query_specification/common/null_policy.rb +20 -70
  330. data/lib/hecks/query_specification/common/options.rb +2 -6
  331. data/lib/hecks/query_specification/common/where_clause.rb +2 -10
  332. data/lib/hecks/query_specification/field_path.rb +37 -77
  333. data/lib/hecks/query_specification/hop_path.rb +12 -76
  334. data/lib/hecks/release/gem_pin.rb +2 -26
  335. data/lib/hecks/release/runner/ci_publisher.rb +93 -0
  336. data/lib/hecks/release/runner/commands.rb +66 -0
  337. data/lib/hecks/release/runner/console.rb +52 -0
  338. data/lib/hecks/release/runner/gem_publisher.rb +43 -0
  339. data/lib/hecks/release/runner/git.rb +48 -0
  340. data/lib/hecks/release/runner/npm_publisher.rb +75 -0
  341. data/lib/hecks/release/runner/preflight.rb +104 -0
  342. data/lib/hecks/release/runner/published.rb +55 -0
  343. data/lib/hecks/release/runner/tagger.rb +80 -0
  344. data/lib/hecks/release/runner.rb +215 -0
  345. data/lib/hecks/rendering.rb +4 -26
  346. data/lib/hecks/router/namespace_installer.rb +12 -36
  347. data/lib/hecks/router.rb +4 -8
  348. data/lib/hecks/runtime/aggregate_lock.rb +8 -33
  349. data/lib/hecks/runtime/boot_gates.rb +3 -18
  350. data/lib/hecks/runtime/caller.rb +19 -59
  351. data/lib/hecks/runtime/capability_graph.rb +5 -21
  352. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
  353. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +23 -160
  354. data/lib/hecks/runtime/command_interpreter.rb +125 -307
  355. data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
  356. data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
  357. data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
  358. data/lib/hecks/runtime/command_rules/emission.rb +8 -21
  359. data/lib/hecks/runtime/command_rules/references.rb +23 -202
  360. data/lib/hecks/runtime/dependency_planning.rb +30 -102
  361. data/lib/hecks/runtime/dispatcher.rb +82 -241
  362. data/lib/hecks/runtime/entity_element.rb +40 -462
  363. data/lib/hecks/runtime/entity_interpreter.rb +65 -220
  364. data/lib/hecks/runtime/errors.rb +32 -115
  365. data/lib/hecks/runtime/event.rb +3 -25
  366. data/lib/hecks/runtime/identity.rb +18 -154
  367. data/lib/hecks/runtime/instance.rb +18 -150
  368. data/lib/hecks/runtime/interpreting.rb +11 -70
  369. data/lib/hecks/runtime/invocation.rb +28 -164
  370. data/lib/hecks/runtime/loader.rb +48 -186
  371. data/lib/hecks/runtime/outbox.rb +38 -261
  372. data/lib/hecks/runtime/policy_interpreter.rb +38 -200
  373. data/lib/hecks/runtime/port_operation_interpreter.rb +26 -87
  374. data/lib/hecks/runtime/query_interpreter.rb +45 -187
  375. data/lib/hecks/runtime/reaction_invocation.rb +36 -138
  376. data/lib/hecks/runtime/read_model_interpreter.rb +139 -208
  377. data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
  378. data/lib/hecks/runtime/reference_hop.rb +8 -81
  379. data/lib/hecks/runtime/refusal_wording.rb +8 -89
  380. data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
  381. data/lib/hecks/runtime/registry/verification.rb +52 -225
  382. data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
  383. data/lib/hecks/runtime/registry.rb +103 -244
  384. data/lib/hecks/runtime/remote_dispatcher.rb +37 -152
  385. data/lib/hecks/runtime/routing.rb +4 -15
  386. data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
  387. data/lib/hecks/runtime/saga_interpreter.rb +79 -263
  388. data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
  389. data/lib/hecks/runtime/tenant_check.rb +6 -68
  390. data/lib/hecks/runtime/tenant_scope.rb +3 -28
  391. data/lib/hecks/runtime/value/admission.rb +16 -67
  392. data/lib/hecks/runtime/value/coercion.rb +155 -641
  393. data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
  394. data/lib/hecks/runtime/value.rb +12 -84
  395. data/lib/hecks/runtime.rb +17 -65
  396. data/lib/hecks/storehouse.rb +146 -567
  397. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +8 -32
  398. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
  399. data/lib/hecks/vendoring/git_environment.rb +2 -8
  400. data/lib/hecks/vendoring/git_source.rb +4 -10
  401. data/lib/hecks/vendoring.rb +7 -46
  402. data/lib/hecks/version.rb +3 -14
  403. data/lib/hecks/vocabulary.rb +5 -0
  404. data/lib/hecks.rb +32 -82
  405. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
  406. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
  407. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
  408. metadata +30 -59
  409. data/lib/hecks/bench/cli.rb +0 -74
  410. data/lib/hecks/bench/environment.rb +0 -100
  411. data/lib/hecks/bench/postgres_probe.rb +0 -41
  412. data/lib/hecks/bench/report.rb +0 -116
  413. data/lib/hecks/bench/ruby_runner.rb +0 -151
  414. data/lib/hecks/bench/run.rb +0 -55
  415. data/lib/hecks/bench/rust_runner.rb +0 -165
  416. data/lib/hecks/bench/stats.rb +0 -75
  417. data/lib/hecks/bench/suite.rb +0 -172
  418. data/lib/hecks/bench/workload.rb +0 -137
  419. data/lib/hecks/bench.rb +0 -28
  420. data/lib/hecks/codemod.rb +0 -443
  421. data/lib/hecks/corpus.rb +0 -479
  422. data/lib/hecks/doc/reference.rb +0 -585
  423. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -684
  424. data/lib/hecks/fuzzing/combination_miner.rb +0 -178
  425. data/lib/hecks/fuzzing/concurrent_dispatch.rb +0 -469
  426. data/lib/hecks/fuzzing/coverage_campaign.rb +0 -161
  427. data/lib/hecks/fuzzing/differential.rb +0 -192
  428. data/lib/hecks/fuzzing/domain_generator.rb +0 -871
  429. data/lib/hecks/fuzzing/era_boundary.rb +0 -154
  430. data/lib/hecks/fuzzing/form_census.rb +0 -285
  431. data/lib/hecks/fuzzing/generated_domain_check.rb +0 -171
  432. data/lib/hecks/fuzzing/invalid_value_generator.rb +0 -125
  433. data/lib/hecks/fuzzing/isolated_boot.rb +0 -658
  434. data/lib/hecks/fuzzing/nondeterministic.rb +0 -79
  435. data/lib/hecks/fuzzing/persistence_parity.rb +0 -253
  436. data/lib/hecks/fuzzing/properties/corrections.rb +0 -125
  437. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +0 -688
  438. data/lib/hecks/fuzzing/properties/guards.rb +0 -398
  439. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +0 -399
  440. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +0 -137
  441. data/lib/hecks/fuzzing/properties/outbox.rb +0 -180
  442. data/lib/hecks/fuzzing/properties/querying.rb +0 -260
  443. data/lib/hecks/fuzzing/properties.rb +0 -267
  444. data/lib/hecks/fuzzing/qa_settings.rb +0 -164
  445. data/lib/hecks/fuzzing/replay.rb +0 -930
  446. data/lib/hecks/fuzzing/rotation_priority.rb +0 -114
  447. data/lib/hecks/fuzzing/rust_gap_manifest.rb +0 -139
  448. data/lib/hecks/fuzzing/self_consistency.rb +0 -825
  449. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +0 -532
  450. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +0 -171
  451. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +0 -127
  452. data/lib/hecks/fuzzing/sequence_generator/picker.rb +0 -116
  453. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +0 -138
  454. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +0 -344
  455. data/lib/hecks/fuzzing/sequence_generator.rb +0 -311
  456. data/lib/hecks/fuzzing/shrinker.rb +0 -309
  457. data/lib/hecks/fuzzing/structural_skips.rb +0 -53
  458. data/lib/hecks/fuzzing/sweep_depth.rb +0 -61
  459. data/lib/hecks/fuzzing/target_capabilities.rb +0 -255
  460. data/lib/hecks/fuzzing/value_generator.rb +0 -340
  461. data/lib/hecks/fuzzing.rb +0 -23
  462. data/lib/hecks/grammar/evolve.rb +0 -524
  463. data/lib/hecks/query_ir.rb +0 -482
@@ -2,12 +2,8 @@ require_relative "word_gate"
2
2
  module Hecks
3
3
  module Bluebook
4
4
  module DSL
5
- # Parses a `read_model "Name" do ... end` block into a `ReadModel` — a
6
- # cross-aggregate projection built from an optional `reference_to`
7
- # root (omitted, it's a rootless "bulk" read model) plus one or more
8
- # `include`d aggregate heads, with `where`/`order_by`/`limit`/etc, and
9
- # at most one of `group_by`, `count`, or `median` as its single
10
- # reduction over the one eligible many-side collection.
5
+ # Parses a `read_model "Name" do ... end` block into a `ReadModel`: a cross-aggregate
6
+ # projection over an optional `reference_to` root and its `include`d aggregate heads.
11
7
  class ReadModelBuilder
12
8
  GRAMMAR_CONTEXT = "ReadModel".freeze
13
9
 
@@ -24,16 +20,10 @@ module Hecks
24
20
  # @param value [String] the description text
25
21
  # @return [String] the description as stored
26
22
  def description(value)
27
- # moved to the language: ProjectionText / purpose, on Projection.Declare
28
23
  @description = value
29
24
  end
30
25
 
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.
26
+ # Declares the read model's own "one" side, the aggregate head every row projects around.
37
27
  #
38
28
  # @param type [Module, Symbol, String] the referenced aggregate, written as a bare
39
29
  # constant
@@ -50,21 +40,8 @@ module Hecks
50
40
 
51
41
  # Adds one aggregate head this read model projects.
52
42
  #
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.
43
+ # `many:` is decided at build time against the reference target, so an include declared
44
+ # before `reference_to` resolves like one declared after it.
68
45
  #
69
46
  # @param type [Module, Symbol, String] the included aggregate, written as a bare constant
70
47
  # @param as [Symbol, nil] the field name to project this head's rows under; `nil` derives
@@ -75,64 +52,21 @@ module Hecks
75
52
  @includes << [Naming.demodulise(type), as]
76
53
  end
77
54
 
78
- # `on:` (ADR 0055) — overrides of `QuerySpecification::Common::DSL`'s
79
- # shared `where_impl`/`order_by_impl`/`limit_impl`/`offset_impl`,
80
- # scoped to `ReadModelBuilder` alone rather than added to the shared
81
- # module `Query` also mixes in: a plain `query` has no
82
- # `aggregate_heads` at all, so `on:` there would be a silently-
83
- # ignored no-op rather than a real answer. Overriding only here
84
- # means a `Query`'s own `where(..., on: X)` gets Ruby's own loud
85
- # `unknown keyword: :on` instead of quietly doing nothing.
86
- #
87
- # `on:` names the target by type (`on: Character`), resolved the
88
- # same way `reference_to`/`include` already resolve their own type
89
- # argument (`Naming.demodulise`) — not by the include's own `as:`
90
- # alias. A read model that `include`s the same type twice under two
91
- # different `as:` has no way to say which one `on:` means today; no
92
- # real corpus read model does this, so it's a real, deliberate scope
93
- # limit (see ADR 0055), not an oversight.
94
- #
95
- # `*positional, on:, **rest` rather than a plain `(clauses, on: nil)`
96
- # — found necessary, not stylistic, by reproducing the failure
97
- # directly: `where(status: "disputed")` reaches here with its
98
- # `status: "disputed"` captured as `**kwargs` (GenericDispatch's own
99
- # `builder.send(calls, *args, **kwargs, &block)`), and Ruby stops
100
- # auto-converting a bare `**hash` call into a plain positional Hash
101
- # the moment a method declares any real keyword parameter — so a
102
- # `(clauses, on: nil)` signature raised "wrong number of arguments
103
- # (given 0, expected 1)" on every ordinary `where(field: value)`
104
- # call, never reaching `on:` at all. `**rest` sidesteps this: Ruby
105
- # still auto-splits `on:` into the declared keyword and gathers
106
- # every other key into `rest` regardless of how the caller wrote it.
107
- #
108
- # `QuerySpecification::Common::WhereClause` etc — fully qualified,
109
- # not the bare names `dsl.rb`'s own shared `where_impl` gets away
110
- # with. That file is lexically nested inside `Common` itself, so
111
- # `WhereClause` resolves directly; this class is nested inside
112
- # `Bluebook::DSL`, which has no lexical or ancestor path to
113
- # `QuerySpecification::Common` at all — a bare `WhereClause` here
114
- # falls through to `const_missing` and, mid-bluebook-load, that's
115
- # `ConstShim`, which resolves it against the self-hosted grammar
116
- # domain's own unrelated `WhereClause` construct instead (a `Module`,
117
- # not this `Struct`) — found directly by reproducing "undefined
118
- # method `new' for module WhereClause" against a real corpus load,
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.
55
+ # The `on:` overrides of the shared where/order_by/limit/offset are kept off the shared
56
+ # module so a plain `query` still raises `unknown keyword: :on`. `on:` names its target
57
+ # by type, not by an include's `as:` alias (ADR 0055).
58
+
59
+ # Records one `WhereClause` per `field => value` pair, optionally targeted with `on:`.
123
60
  #
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`
61
+ # @param positional [Array<Hash>] at most one bare Hash of clauses; normally empty
62
+ # @param on [Module, Symbol, String, nil] the many-side include the clauses apply to
63
+ # @param rest [Hash{Symbol => Object}] `field => value`; a value is a literal, a Symbol
64
+ # naming a query argument, or a one-pair `{ comparator => operand }` Hash
132
65
  # @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`
66
+ # @raise [ArgumentError] on more than one positional argument or a malformed comparator
135
67
  def where_impl(*positional, on: nil, **rest)
68
+ # `**rest` because a declared keyword stops Ruby turning a bare `where(status: "x")`
69
+ # into a positional Hash.
136
70
  raise ArgumentError, "wrong number of arguments (given #{positional.size}, expected 1)" if positional.size > 1
137
71
 
138
72
  @wheres ||= []
@@ -140,6 +74,8 @@ module Hecks
140
74
  clauses = (positional.first || {}).merge(rest)
141
75
  clauses.each do |field, value|
142
76
  op, operand = split_comparator(value)
77
+ # Fully qualified: a bare `WhereClause` reaches `ConstShim` and resolves to the
78
+ # grammar domain's unrelated module.
143
79
  @wheres << QuerySpecification::Common::WhereClause.new(field: field, op: op, value: operand, target: target)
144
80
  end
145
81
  end
@@ -178,74 +114,50 @@ module Hecks
178
114
  @offset = QuerySpecification::Common::OffsetSpec.new(value: value, target: resolve_target(on))
179
115
  end
180
116
 
181
- # Names which of the eligible head's own fields to nest its rows
182
- # under — one level per field, the leaf being that row with the
183
- # named fields removed (they're already spent, as the keys that
184
- # reached it). The same "exactly one many-side head" rule
185
- # `seal_query_options` already enforces for where/order_by/etc
186
- # applies here too (`seal_group_by`) — grouping is a question
187
- # about one collection's own rows, same as those are.
117
+ # Names which of the eligible head's own fields to nest its rows under, one level per
118
+ # field; the leaf is the row with those fields removed.
119
+ #
188
120
  # @param fields [Array<Symbol>] the eligible many-side head's own fields to nest its rows
189
121
  # under, one level per field
190
122
  # @return [void]
191
123
  def group_by_impl(*fields)
192
- # Hash rows, `{field:}`, not bare symbols — same shape
193
- # `aggregate_heads` already uses for exactly the reason it
194
- # does: the language's own self-hosted grammar (`projection
195
- # .bluebook`'s `GroupByField`) has to have something to read a
196
- # `field:` off of when `Judge` walks this list generically: a
197
- # bare `Symbol` has no attribute of its own to read.
124
+ # Hash rows rather than bare symbols: the self-hosted `GroupByField` grammar reads
125
+ # `field:` off each entry when `Judge` walks the list.
198
126
  @group_by = fields.map { |field| { field: field.to_sym } }
199
127
  end
200
128
 
201
- # `count` -- a bare row count over the eligible many-side head's
202
- # own rows (after `where`/`order_by`/`limit`/`offset` apply, the
203
- # same rows `group_by` itself would nest) -- answers "how many
204
- # match", not "which ones". A sibling reduction to `group_by`,
205
- # not a filter: `seal_aggregation` refuses combining it with
206
- # `group_by` or with `median`, the same "exactly one many-side
207
- # head" rule `seal_group_by` already enforces for the same
208
- # reason -- a bare marker, so `@count` is left unset (nil, not
209
- # false) rather than defaulted, matching the "absent is not
210
- # empty" reading `Lifecycle`'s own optional fields already rely
211
- # on for the Judge's setter dispatch (Behaviour::ReadModel#
212
- # count?, ReadModelInterpreter#aggregation_target).
213
- # `count` — item #13's full metaprogrammed dispatch, slice 1
214
- # (whole-project table-unification survey): the only Keyword row
215
- # filling `count` — a bare marker, now stored as literal `true`
216
- # by `GenericDispatch` off that same table fact.
217
-
218
- # `median(field)` -- the median value of one numeric field
219
- # across the eligible many-side head's own rows. Even count: the
220
- # average of its two middle values (the standard definition,
221
- # not "the lower of the two") -- see
222
- # Runtime::ReadModelInterpreter#median for where that lands and
223
- # is documented for a caller. `field` must name a numeric
224
- # attribute (a bare numeric primitive, or a value object
225
- # carrying one) -- checked once, at read time, by
226
- # ReadModelInterpreter#aggregation_target, the same place
227
- # `group_by`'s own field names are checked.
228
- # `median` — item #13's full metaprogrammed dispatch, slice 1:
229
- # same shape as `count`, above (a bare, kind-driven coerce-and-
230
- # assign).
129
+ # `sum`/`avg`/`min`/`max`/`any`/`all` need no method of their own: each is one
130
+ # required positional symbol, the same shape as `median`, which `GenericDispatch`
131
+ # already handles generically off the grammar table (ADR 0078).
132
+
133
+ # The field's value at one interpolated rank (`0.0`..`1.0`) across the eligible
134
+ # many-side head's own rows; `median` is the fixed `at: 0.5` case (ADR 0078). Takes
135
+ # a named argument alongside its field, which is outside `GenericDispatch`'s
136
+ # one-positional-argument shape, so it needs a method of its own.
137
+ #
138
+ # @param field [Symbol] the eligible many-side head's own numeric field
139
+ # @param at [Float] the rank to interpolate, `0.0` (the minimum) through `1.0` (the maximum)
140
+ # @return [void]
141
+ # @raise [ArgumentError] if `at` is not a `Float` in `0.0..1.0`
142
+ def percentile_impl(field, at:)
143
+ unless at.is_a?(Float) && (0.0..1.0).cover?(at)
144
+ raise ArgumentError, "percentile's at: must be a Float between 0.0 and 1.0 (given #{at.inspect})"
145
+ end
146
+
147
+ @percentile_field = field.to_sym
148
+ @percentile_at = at
149
+ end
231
150
 
232
151
  # Assembles the declared references, includes and clauses into a `ReadModel`, after
233
152
  # validating them.
234
153
  #
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
237
- # (no FK match against a root that doesn't exist), and dispatch
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
- #
154
+ # `reference_to` is optional: a rootless read model is a bulk one and takes no id.
155
+ # Zero includes and no reference is refused.
244
156
  # @return [Bluebook::ReadModel] the built read model
245
157
  # @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
158
+ # declared, if `where`/`order_by`/`limit`/`offset`/`group_by` or a reduction name an
247
159
  # `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
160
+ # many-side head, if more than one reduction is declared or one is combined with
249
161
  # `group_by`, or if `cursor` is declared
250
162
  def build
251
163
  if !@reference_target && Array(@includes).empty?
@@ -266,7 +178,10 @@ module Hecks
266
178
  cursor: @cursor,
267
179
  authorization: @authorization, null_semantics: @null_semantics,
268
180
  inspection: @inspection, group_by: @group_by || [],
269
- count: @count, median_field: @median_field)
181
+ count: @count, median_field: @median_field,
182
+ sum_field: @sum_field, avg_field: @avg_field, min_field: @min_field,
183
+ max_field: @max_field, percentile_field: @percentile_field,
184
+ percentile_at: @percentile_at, any_field: @any_field, all_field: @all_field)
270
185
  end
271
186
 
272
187
  # Evaluates a `read_model` block against a fresh builder and returns what it built.
@@ -283,30 +198,9 @@ module Hecks
283
198
 
284
199
  private
285
200
 
286
- # where/order_by/limit/offset/authorize's tenant all apply to
287
- # collections — the `include`d aggregates whose heads are "many"
288
- # (the "one" side, the reference target itself, is a single row;
289
- # ordering, paging, or tenant-scoping one row means nothing). ADR
290
- # 0055 gave `where`/`order_by`/`limit`/`offset` an `on:` to name
291
- # which many-side collection they mean, so this asks two questions
292
- # now instead of one:
293
- #
294
- # 1. Does every declared `on:` actually name a many-side included
295
- # aggregate? Checked regardless of how many many-side heads
296
- # exist — a typo refuses immediately, not only once ambiguity
297
- # would otherwise bite.
298
- # 2. Is there still an untargeted option declared (including
299
- # `authorize`'s own `tenant:`, which has no `on:` of its own —
300
- # a real, deliberate scope limit, see ADR 0055)? An untargeted
301
- # option still needs exactly one many-side head to mean
302
- # anything unambiguous — the original rule, unchanged, and
303
- # still worded the same way (`spec/runtime/
304
- # read_model_interpreter_spec.rb`'s existing refusal regex
305
- # still matches).
306
- #
307
- # A read model with several many-side heads is legal precisely when
308
- # every declared option names one; a read model with a single
309
- # many-side head is unaffected either way, `on:` or not.
201
+ # where/order_by/limit/offset/authorize's tenant apply to the many-side collections;
202
+ # the reference target is a single row. Every `on:` must name a many-side include, and
203
+ # any untargeted option needs exactly one many-side head (ADR 0055).
310
204
  def seal_query_options
311
205
  many = Array(@aggregate_heads).select { |head| head[:many] }
312
206
 
@@ -320,10 +214,7 @@ module Hecks
320
214
  "name which one with `on:` (e.g. `where(field: value, on: Character)`), or drop the options"
321
215
  end
322
216
 
323
- # Question 1 of `seal_query_options`'s own two, split out to keep
324
- # both under the same "one job per method" shape every other seal in
325
- # this file already holds to (each raises its own one Malformed, for
326
- # its own one reason).
217
+ # Refuses an `on:` that does not name one of the many-side includes.
327
218
  def validate_declared_targets!(many)
328
219
  many_by_aggregate = many.to_h { |head| [head[:aggregate], head] }
329
220
  declared_targets = Array(@wheres).map(&:target) + [@order_by&.target, @limit&.target, @offset&.target]
@@ -338,9 +229,7 @@ module Hecks
338
229
  end
339
230
  end
340
231
 
341
- # Question 2 of `seal_query_options`'s own two — see that method's
342
- # header. `authorize`'s own `tenant:` has no `on:` at all (ADR 0055's
343
- # own documented scope limit), so it always counts as untargeted.
232
+ # `authorize`'s `tenant:` has no `on:`, so it always counts as untargeted.
344
233
  def untargeted_option_declared?
345
234
  Array(@wheres).any? { |where| where.target.nil? } ||
346
235
  (@order_by && @order_by.target.nil?) ||
@@ -349,9 +238,7 @@ module Hecks
349
238
  @authorization&.tenant
350
239
  end
351
240
 
352
- # Same shape as `seal_query_options`, same reason — `group_by`
353
- # answers a question about one collection's own rows, so zero or
354
- # several many-side heads leaves it with no unambiguous target.
241
+ # Same rule as `seal_query_options`: `group_by` needs exactly one many-side head.
355
242
  def seal_group_by
356
243
  return unless @group_by&.any?
357
244
 
@@ -364,45 +251,35 @@ module Hecks
364
251
  "own rows; name which one by including only it"
365
252
  end
366
253
 
367
- # `count`/`median` are the other two reductions a read model may
368
- # declare over its one eligible collection — same "exactly one
369
- # many-side head" rule as `seal_group_by`, plus a rule
370
- # `seal_group_by` doesn't need: a read model reports one shape,
371
- # so `count` and `median` cannot both be declared, and neither
372
- # may combine with `group_by` (nesting rows and reducing them to
373
- # a scalar are answers to different questions ; a caller asking
374
- # "how many, nested by state" is not a shape this read model
375
- # produces today — a real, deliberate scope limit, not an
376
- # oversight, the same discipline the rootless model's own
377
- # "each head reads independently" limit already documents).
254
+ # Every reduction (`Behaviour::ReadModel::REDUCTION_FIELDS`) needs exactly one
255
+ # many-side head, cannot be declared alongside another, and cannot combine with
256
+ # `group_by`: a read model reports one shape.
378
257
  def seal_aggregation
379
- return unless @count || @median_field
258
+ declared = Behaviour::ReadModel::REDUCTION_FIELDS
259
+ .select { |ivar, _| instance_variable_get(:"@#{ivar}") }.values
260
+ return if declared.empty?
380
261
 
381
- if @count && @median_field
382
- raise Malformed,
383
- "#{@name} declares both count and median — a read model reports " \
384
- "one shape; choose one"
385
- end
386
262
  if @group_by&.any?
387
263
  raise Malformed,
388
- "#{@name} declares count/median together with group_by — a read " \
264
+ "#{@name} declares #{declared.join('/')} together with group_by — a read " \
389
265
  "model reports one shape; choose one"
390
266
  end
267
+ if declared.size > 1
268
+ joiner = declared.size == 2 ? "both #{declared.join(' and ')}" : declared.join(", ")
269
+ raise Malformed, "#{@name} declares #{joiner} — a read model reports one shape; choose one"
270
+ end
391
271
 
392
272
  many = Array(@aggregate_heads).count { |head| head[:many] }
393
273
  return if many == 1
394
274
 
395
275
  raise Malformed,
396
- "#{@name} declares count/median but includes #{many} many-side " \
397
- "aggregates, not exactly one — count/median reduce a single " \
276
+ "#{@name} declares #{declared.first} but includes #{many} many-side " \
277
+ "aggregates, not exactly one — #{declared.first} reduces a single " \
398
278
  "collection's own rows; name which one by including only it"
399
279
  end
400
280
 
401
- # `cursor` parses, round-trips through the IR, and is read by nothing —
402
- # no interpreter (Memory, Sqlite, Postgres) ever applies it. Refusing
403
- # it here, rather than deleting the word, keeps the declared syntax
404
- # honest (the language still knows the shape) while refusing to let a
405
- # bluebook author believe cursor-based pagination actually happens.
281
+ # `cursor` is parsed but no interpreter applies it, so it is refused rather than
282
+ # letting an author believe cursor pagination happens.
406
283
  def seal_cursor
407
284
  return unless @cursor
408
285
 
@@ -411,10 +288,7 @@ module Hecks
411
288
  "pagination — use limit/offset instead"
412
289
  end
413
290
 
414
- # `on:`'s own resolution (ADR 0055) — same demodulise `reference_to`/
415
- # `include` already use for their own type argument. `nil` when `on:`
416
- # is omitted, matching every other optional field's "absent, not
417
- # false" reading in this file.
291
+ # Resolves `on:` to a type name; `nil` when omitted.
418
292
  def resolve_target(on) = on && Naming.demodulise(on)
419
293
 
420
294
  def add_aggregate_head(type, name, many:)
@@ -3,76 +3,16 @@ require_relative "bootstrap_table"
3
3
  module Hecks
4
4
  module Bluebook
5
5
  module DSL
6
- # The three resolution primitives the S10 given/invariant family's
7
- # own "declared once, referenced by name" mechanism (ADR 0025)
8
- # reduces to, at every scope this language has grown one so far
9
- # (a command referencing its owner or a sibling piece's entity-wide
10
- # pool; an aggregate referencing another aggregate chapter-wide; a
11
- # value object referencing a sibling value object on the same
12
- # aggregate) — extracted here, once, so the next scope this family
13
- # widens to (there will be one — see docs/resolution-rules/
14
- # chapter-given.md's own "Known limitations" for two already named)
15
- # reuses one of these three shapes instead of a fourth hand-written
16
- # near-duplicate resolver.
17
- #
18
- # ## Not one unified algorithm
19
- #
20
- # A real design question this file answers directly: the three existing
21
- # resolvers are not superficially different, they are structurally different (a
22
- # multi-pool fallback chain; one pool keyed by declaring owner,
23
- # needing disambiguation; a live scan over already-built sibling
24
- # objects with no separate pool at all) — forcing them into one
25
- # shape would be a real behavior change (see `#lookup`, below, for
26
- # which construct uses which), not the pure internal refactor this
27
- # module is. `build_rule` is the one piece that was genuinely
28
- # identical across all 7 declaring methods (`given`×3,
29
- # `invariant`×3, `ensures`×1) before this file existed — extract
30
- # predicate source, refuse if extraction failed, build the struct.
31
- #
32
- # ## Resolution rules read off the grammar table, not a Ruby-only mirror
33
- #
34
- # `#lookup`/`#verify_resolves_via!` read which construct uses which
35
- # primitive off the self-hosted grammar table itself
36
- # (`Keyword#resolves_via`, `syntax.bluebook`), so a real domain's own boot, not just
37
- # `bundle exec rspec`, fails loudly the moment the table and this file's own
38
- # hand-written resolution methods disagree.
6
+ # The three "declared once, referenced by name" resolution primitives shared by
7
+ # every given/invariant/ensures scope in this DSL (ADR 0025).
39
8
  module RuleReference
40
9
  module_function
41
10
 
42
- # Extracts a predicate block's source and builds the rule struct that holds it,
11
+ # Extracts a predicate's source and builds the rule struct (Given or Invariant),
43
12
  # refusing when the source could not be read.
44
13
  #
45
- # `struct_class` is `Given` or `Invariant` (both `Struct.new(
46
- # :description, :canonical, :predicate, :ast, keyword_init: true)` —
47
- # `Given` lives in command.rb, `Invariant` in value_object.rb).
48
- # `owner_name`/`word` are only for the refusal message's own
49
- # wording. `extraction_failure` is the tail of that same
50
- # message, and stays a required parameter rather than one
51
- # hardcoded string on purpose — `given` ("its source could not
52
- # be read, so no other runtime could ever evaluate it"),
53
- # `invariant` ("it would be a rule the IR cannot carry"), and
54
- # `ensures` ("a postcondition is carried as text, and this one
55
- # has none") each already had their own exact wording before
56
- # this method existed; unifying them into one generic sentence
57
- # would be a real (if small) behavior change this refactor is
58
- # not making.
59
- #
60
- # @param struct_class [Class] the rule struct to build — `Bluebook::Given` or
61
- # `Bluebook::Invariant`
62
- # @param description [String] the rule's own description, as declared by the caller's
63
- # own `given`/`invariant`/`ensures` word
64
- # @param predicate [Proc] the rule's own body block, never called here — only its
65
- # extracted source is used
66
- # @param owner_name [String] the declaring construct's own name, for the refusal message
67
- # @param word [String] the declaring word (`"given"`, `"invariant"`, or `"ensures"`),
68
- # for the refusal message
69
- # @param extraction_failure [String] the refusal message's own tail, naming what an
70
- # unreadable predicate would mean for this particular word
71
- # @return [Bluebook::Given, Bluebook::Invariant] the built rule struct, an instance of
72
- # `struct_class`
73
- # @raise [Bluebook::DSL::Malformed] if `predicate`'s source could not be extracted, or
74
- # it matches against a pattern construct `Expression::AstJson::PatternSubset` refuses, or
75
- # it calls a method the expression language does not have
14
+ # extraction_failure is required, not hardcoded, since given/invariant/ensures
15
+ # each need their own exact refusal wording.
76
16
  def build_rule(struct_class, description, predicate, owner_name:, word:, extraction_failure:)
77
17
  canonical = Ports::Extraction.canonical(predicate)
78
18
 
@@ -89,103 +29,37 @@ module Hecks
89
29
  struct_class.new(description: description, canonical: canonical, predicate: predicate, ast: ast)
90
30
  end
91
31
 
92
- # Primitive 1 — an ordered chain of flat `Hash[description] =>
93
- # Rule` pools, first match wins. `CommandBuilder#given`'s own
94
- # two-pool shape (its own owner's `named_givens`, then a sibling
95
- # piece's entity-wide pool) is this with a 2-element chain — a
96
- # future single-pool bare reference is the same primitive with a
97
- # 1-element chain, not a separate "just look in one hash" method.
98
- # @param pools [Array<Hash{String => Bluebook::Given, Bluebook::Invariant}>] pools to
99
- # search in order; the first pool holding `description` wins
100
- # @param description [String] the rule's own description to find
101
- # @return [Bluebook::Given, Bluebook::Invariant, nil] the matching rule, or `nil` if no
102
- # pool has one
32
+ # Resolution primitive 1: first pool (in order) whose hash has this description wins.
103
33
  def resolve_hash_chain(pools, description)
104
34
  pools.each { |pool| return pool[description] if pool.key?(description) }
105
35
  nil
106
36
  end
107
37
 
108
- # Primitive 2 — one pool keyed by declaring owner,
109
- # `Hash[description][owner] => Rule` — `AggregateBuilder#given`'s
110
- # own chapter-wide shape, the only construct so far where the
111
- # same description can mean two genuinely different predicates
112
- # (docs/implemented/resolution-rules/chapter-given.md). Returns the full
113
- # candidates Hash (0, 1, or many entries) — deliberately not
114
- # raising here, so each caller keeps its own exact refusal
115
- # wording for "none," "ambiguous," and "declared_by: named the
116
- # wrong owner" rather than one generic message papering over all
117
- # three.
118
- # @param pool [Hash{String => Hash{String => Bluebook::Given, Bluebook::Invariant}}]
119
- # descriptions mapped to their own candidates, each keyed by declaring owner
120
- # @param description [String] the rule's own description to find
121
- # @return [Hash{String => Bluebook::Given, Bluebook::Invariant}] the candidates for
122
- # `description`, keyed by declaring owner; empty when none exist
38
+ # Resolution primitive 2: one pool keyed by declaring owner. Returns every candidate
39
+ # rather than raising, so each caller keeps its own "none"/"ambiguous"/"wrong owner"
40
+ # wording.
123
41
  def resolve_owner_keyed(pool, description)
124
42
  pool[description] || {}
125
43
  end
126
44
 
127
- # Primitive 3 — a live scan over already-built sibling objects'
128
- # own collections, not a separately-maintained pool at all —
129
- # `ValueObjectBuilder#invariant`'s own shape: every sibling value
130
- # object on the same aggregate has already been built by the time
131
- # a later one references back (declaration order, the same
132
- # constraint every scope in this family carries), so there is
133
- # nothing to write through — just read their own already-declared
134
- # rules directly. `reader` is the method name to call on each
135
- # sibling (`:invariants` today; kept a parameter, not hardcoded,
136
- # since a future sibling-scan scope might reference a different
137
- # collection).
138
- # @param siblings [Array<Object>] the already-built sibling objects to scan; each must
139
- # respond to `reader`
140
- # @param description [String] the rule's own description to find
141
- # @param reader [Symbol] the method to call on each sibling to read its own rule
142
- # collection, such as `:invariants`
143
- # @return [Bluebook::Given, Bluebook::Invariant, nil] the matching rule, or `nil` if no
144
- # sibling declares one
45
+ # Resolution primitive 3: scans already-built siblings' own rule collections directly
46
+ # instead of a separately-maintained pool, since declaration order guarantees every
47
+ # sibling exists by the time a later one references it.
145
48
  def resolve_sibling_scan(siblings, description, reader:)
146
49
  siblings.flat_map { |sibling| sibling.public_send(reader) }
147
50
  .find { |rule| rule.description == description }
148
51
  end
149
52
 
150
- # **Which construct uses which primitive** — no longer a Ruby-only
151
- # Hash (that was this constant's own shape, one round ago): the
152
- # user's own correction — "my goal is that if they read the same
153
- # table they behave identically" — means a table only Ruby ever
154
- # reads cannot deliver that, no matter how faithfully it is
155
- # cross-checked afterward. `Keyword#resolves_via`/`#disambiguator`
156
- # (self-hosted, `syntax.bluebook`) is the real table now — the
157
- # same generated data `rust/parser/src/keywords.rs` is generated
158
- # from (`bin/project_parser_table`). `#lookup` reads it live.
159
- #
160
- # **The one unavoidable exception**: the meta-domain's own bootstrap
161
- # (`MetaValidator.load_grammar_into`) dispatches `given`/
162
- # `invariant` on itself 61 times while building the very grammar
163
- # table that would answer "how does given/Aggregate resolve" —
164
- # `MetaValidator.grammar_registry`/`SyntaxBoot.call` are not
165
- # ready yet, and cannot be made ready without already having
166
- # resolved a `given` somewhere upstream. `MetaValidator.
167
- # bootstrapping?` is the same guard `MetaValidator.call` (the
168
- # judge) already uses to skip self-judging during this exact
169
- # window — `#lookup` uses it too, falling back to
170
- # `BOOTSTRAP_FALLBACK` (below) only while it's true. Every real
171
- # domain (banking, pizzas, compliance, any future one) boots
172
- # after `grammar_registry` is fully built and memoized, so reads
173
- # the real table, every time, no exception.
53
+ # Which (word, context) pair resolves via which primitive, read from the self-hosted
54
+ # grammar table (Keyword#resolves_via in syntax.bluebook) once it exists.
174
55
  #
175
- # No longer kept in sync by hand — the same `resolves_via`/
176
- # `disambiguator` columns, projected ahead of time into the
177
- # committed lib/hecks/bluebook/dsl/bootstrap_table.rb
178
- # (bin/project_bootstrap_table, pinned by spec/bootstrap_table_spec.rb).
56
+ # Projected ahead of time into bootstrap_table.rb for the bootstrap window before
57
+ # the grammar table itself is built (MetaValidator.bootstrapping?); kept in sync by
58
+ # bin/project_bootstrap_table, pinned by spec/bootstrap_table_spec.rb.
179
59
  BOOTSTRAP_FALLBACK = BootstrapTable::RESOLVES
180
60
 
181
- # Reads how a (word, context) pair resolves its rule references, off the self-hosted
182
- # grammar table itself (or, while that table is still booting, the projected fallback).
183
- #
184
- # @param word [String] the DSL word, such as `"given"` or `"invariant"`
185
- # @param context [String] the grammar context, such as `"Aggregate"`
186
- # @return [Hash{Symbol => String, nil}] `:resolves_via` and `:disambiguator` for this
187
- # (word, context) pair, `nil`-valued when the row leaves either blank, or `{}` if no row
188
- # matches at all
61
+ # Reads how a (word, context) pair resolves rule references, off the self-hosted
62
+ # grammar table (or, during bootstrap, the projected fallback).
189
63
  def lookup(word, context)
190
64
  if MetaValidator.bootstrapping?
191
65
  BOOTSTRAP_FALLBACK[[word, context]] || {}
@@ -199,24 +73,9 @@ module Hecks
199
73
  end
200
74
  end
201
75
 
202
- # A live cross-check, not a spec-only one — every real domain's
203
- # own boot (not just `bundle exec rspec`) now genuinely fails
204
- # loudly if a construct's own hand-written resolution method
205
- # ever disagrees with what the self-hosted grammar table claims
206
- # for it. Each of the three `reference_named_*` methods below
207
- # calls this first, naming the primitive it is about to use —
208
- # if `syntax.bluebook`'s own `resolves_via` for this exact
209
- # (word, context) pair ever names something else, this is a
210
- # real drift between the language's own self-description and
211
- # its own implementation, caught at the next boot of anything,
212
- # not just the next `rspec` run.
213
- # @param word [String] the DSL word, such as `"given"` or `"invariant"`
214
- # @param context [String] the grammar context, such as `"Aggregate"`
215
- # @param expected_primitive [String] the resolution primitive's own name the caller is
216
- # about to use, such as `"hash_chain"`, `"owner_keyed"`, or `"sibling_scan"`
217
- # @return [void]
218
- # @raise [RuntimeError] if `syntax.bluebook`'s own `resolves_via` for this (word, context)
219
- # pair names something other than `expected_primitive`
76
+ # Raises if syntax.bluebook's own resolves_via for (word, context) disagrees with
77
+ # the primitive the caller is about to use — catches drift between the grammar
78
+ # table and its Ruby implementation at boot, not just in spec.
220
79
  def verify_resolves_via!(word, context, expected_primitive)
221
80
  actual = lookup(word, context)[:resolves_via]
222
81
  return if actual == expected_primitive