hecks 2.7.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (463) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +11 -0
  3. data/lib/hecks/adapters/driven/claude_code.rb +4 -104
  4. data/lib/hecks/adapters/driven/d1.rb +44 -237
  5. data/lib/hecks/adapters/driven/folder.rb +17 -76
  6. data/lib/hecks/adapters/driven/google_authentication.rb +15 -49
  7. data/lib/hecks/adapters/driven/governance_authorization.rb +23 -70
  8. data/lib/hecks/adapters/driven/heki/journal.rb +8 -41
  9. data/lib/hecks/adapters/driven/heki/saga_store.rb +12 -54
  10. data/lib/hecks/adapters/driven/heki/snapshot.rb +4 -12
  11. data/lib/hecks/adapters/driven/heki.rb +13 -42
  12. data/lib/hecks/adapters/driven/identity_registry.rb +3 -14
  13. data/lib/hecks/adapters/driven/in_memory_ordering.rb +9 -26
  14. data/lib/hecks/adapters/driven/in_process_key_vault.rb +2 -14
  15. data/lib/hecks/adapters/driven/lambda/client.rb +10 -51
  16. data/lib/hecks/adapters/driven/lambda.rb +10 -84
  17. data/lib/hecks/adapters/driven/local_storage.rb +5 -40
  18. data/lib/hecks/adapters/driven/memory.rb +18 -99
  19. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +5 -21
  20. data/lib/hecks/adapters/driven/postgres/codec.rb +6 -28
  21. data/lib/hecks/adapters/driven/postgres/outbox.rb +3 -12
  22. data/lib/hecks/adapters/driven/postgres/reconnect.rb +4 -30
  23. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +34 -108
  24. data/lib/hecks/adapters/driven/postgres.rb +145 -134
  25. data/lib/hecks/adapters/driven/postgres_era.adapter +4 -15
  26. data/lib/hecks/adapters/driven/prism.rb +7 -30
  27. data/lib/hecks/adapters/driven/sql_query_builder.rb +21 -121
  28. data/lib/hecks/adapters/driven/sqlite/codec.rb +8 -40
  29. data/lib/hecks/adapters/driven/sqlite/projection.rb +26 -82
  30. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +40 -84
  31. data/lib/hecks/adapters/driven/sqlite.rb +128 -65
  32. data/lib/hecks/adapters/driven/tenant_provisioner.rb +15 -37
  33. data/lib/hecks/adapters/driven.rb +5 -36
  34. data/lib/hecks/adapters/driving/github_webhook.rb +8 -76
  35. data/lib/hecks/behaviors/dsl.rb +7 -25
  36. data/lib/hecks/behaviors/expectations.rb +32 -142
  37. data/lib/hecks/behaviors/ir.rb +7 -20
  38. data/lib/hecks/behaviors/rspec.rb +6 -17
  39. data/lib/hecks/behaviors/runner.rb +10 -34
  40. data/lib/hecks/behaviors.rb +4 -8
  41. data/lib/hecks/bluebook/aggregate.rb +9 -75
  42. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +9 -38
  43. data/lib/hecks/bluebook/assembly/build.rb +4 -15
  44. data/lib/hecks/bluebook/assembly/contract.rb +15 -136
  45. data/lib/hecks/bluebook/assembly/contracts.rb +54 -174
  46. data/lib/hecks/bluebook/assembly/marks.rb +31 -223
  47. data/lib/hecks/bluebook/assembly/specializer.rb +6 -52
  48. data/lib/hecks/bluebook/assembly.rb +6 -45
  49. data/lib/hecks/bluebook/attribute.rb +12 -61
  50. data/lib/hecks/bluebook/behaviour/aggregate.rb +10 -39
  51. data/lib/hecks/bluebook/behaviour/attribute.rb +3 -14
  52. data/lib/hecks/bluebook/behaviour/chapter.rb +17 -42
  53. data/lib/hecks/bluebook/behaviour/command.rb +14 -66
  54. data/lib/hecks/bluebook/behaviour/domain_port.rb +4 -20
  55. data/lib/hecks/bluebook/behaviour/entity.rb +7 -27
  56. data/lib/hecks/bluebook/behaviour/hexagon.rb +23 -41
  57. data/lib/hecks/bluebook/behaviour/lifecycle.rb +15 -40
  58. data/lib/hecks/bluebook/behaviour/policy.rb +23 -52
  59. data/lib/hecks/bluebook/behaviour/process_manager.rb +30 -57
  60. data/lib/hecks/bluebook/behaviour/read_model.rb +38 -26
  61. data/lib/hecks/bluebook/behaviour/traits.rb +27 -64
  62. data/lib/hecks/bluebook/behaviour/value_object.rb +5 -24
  63. data/lib/hecks/bluebook/capabilities.rb +17 -49
  64. data/lib/hecks/bluebook/chapter.rb +10 -48
  65. data/lib/hecks/bluebook/command.rb +23 -81
  66. data/lib/hecks/bluebook/domain_port.rb +14 -63
  67. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +50 -229
  68. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +50 -377
  69. data/lib/hecks/bluebook/dsl/attribute_collector.rb +33 -204
  70. data/lib/hecks/bluebook/dsl/binding_proxy.rb +17 -65
  71. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +86 -708
  72. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +56 -170
  73. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
  74. data/lib/hecks/bluebook/dsl/command_builder.rb +74 -546
  75. data/lib/hecks/bluebook/dsl/const_shim.rb +8 -41
  76. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +12 -87
  77. data/lib/hecks/bluebook/dsl/entity_builder.rb +44 -330
  78. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +20 -269
  79. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +26 -189
  80. data/lib/hecks/bluebook/dsl/identity_declaration.rb +13 -88
  81. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +6 -20
  82. data/lib/hecks/bluebook/dsl/policy_builder.rb +16 -88
  83. data/lib/hecks/bluebook/dsl/port_builder.rb +2 -6
  84. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +22 -96
  85. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +31 -184
  86. data/lib/hecks/bluebook/dsl/query_builder.rb +11 -42
  87. data/lib/hecks/bluebook/dsl/read_model_builder.rb +75 -201
  88. data/lib/hecks/bluebook/dsl/rule_reference.rb +22 -163
  89. data/lib/hecks/bluebook/dsl/translation_builder.rb +28 -114
  90. data/lib/hecks/bluebook/dsl/value_object_builder.rb +10 -124
  91. data/lib/hecks/bluebook/dsl/word_gate.rb +16 -162
  92. data/lib/hecks/bluebook/dsl/world_builder.rb +19 -106
  93. data/lib/hecks/bluebook/entity.rb +13 -71
  94. data/lib/hecks/bluebook/expression/ast_json.rb +24 -190
  95. data/lib/hecks/bluebook/expression/ast_reader.rb +12 -54
  96. data/lib/hecks/bluebook/expression/canonical_form.rb +11 -88
  97. data/lib/hecks/bluebook/expression/evaluator.rb +47 -295
  98. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +30 -147
  99. data/lib/hecks/bluebook/expression/resolver.rb +37 -660
  100. data/lib/hecks/bluebook/hexagon.rb +5 -28
  101. data/lib/hecks/bluebook/lifecycle.rb +5 -16
  102. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -7
  103. data/lib/hecks/bluebook/meta_validator/judge.rb +111 -361
  104. data/lib/hecks/bluebook/meta_validator/plan.rb +33 -162
  105. data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -7
  106. data/lib/hecks/bluebook/meta_validator/readings.rb +56 -342
  107. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -176
  108. data/lib/hecks/bluebook/meta_validator/shapes.rb +54 -268
  109. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +47 -308
  110. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +7 -43
  111. data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -18
  112. data/lib/hecks/bluebook/meta_validator.rb +79 -349
  113. data/lib/hecks/bluebook/model_check/client_profile.rb +10 -91
  114. data/lib/hecks/bluebook/model_check.rb +102 -600
  115. data/lib/hecks/bluebook/pattern_subset.rb +11 -69
  116. data/lib/hecks/bluebook/process_manager.rb +13 -83
  117. data/lib/hecks/bluebook/project_loader.rb +3 -39
  118. data/lib/hecks/bluebook/project_register.rb +12 -60
  119. data/lib/hecks/bluebook/query.rb +9 -48
  120. data/lib/hecks/bluebook/read_model.rb +57 -81
  121. data/lib/hecks/bluebook/reference.rb +7 -41
  122. data/lib/hecks/bluebook/smoke_test.rb +8 -98
  123. data/lib/hecks/bluebook/synthesizer.rb +13 -62
  124. data/lib/hecks/bluebook/translation.rb +26 -62
  125. data/lib/hecks/bluebook/value_object.rb +11 -36
  126. data/lib/hecks/bluebook.rb +5 -22
  127. data/lib/hecks/cache_dir.rb +84 -0
  128. data/lib/hecks/cli/document.rb +86 -0
  129. data/lib/hecks/cli/ir.rb +45 -0
  130. data/lib/hecks/cli/mcp.rb +31 -0
  131. data/lib/hecks/cli/mcp_door.rb +366 -0
  132. data/lib/hecks/cli/model_check.rb +203 -0
  133. data/lib/hecks/cli/project_cli.rb +90 -0
  134. data/lib/hecks/cli/project_diagrams.rb +36 -0
  135. data/lib/hecks/cli/run.rb +173 -0
  136. data/lib/hecks/cli/smoke_test.rb +45 -0
  137. data/lib/hecks/cli/stores.rb +53 -0
  138. data/lib/hecks/cli.rb +160 -0
  139. data/lib/hecks/construct.rb +8 -34
  140. data/lib/hecks/deploy/bluebook/deploy.bluebook +33 -142
  141. data/lib/hecks/deploy/bluebook/deploy.hecksagon +2 -9
  142. data/lib/hecks/embryonaut_bluebook/lock.rb +3 -35
  143. data/lib/hecks/embryonaut_bluebook/shape.rb +1 -17
  144. data/lib/hecks/embryonaut_bluebook/vendor.rb +8 -53
  145. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +4 -19
  146. data/lib/hecks/embryonaut_bluebook.rb +4 -71
  147. data/lib/hecks/facade/cli_door.rb +24 -106
  148. data/lib/hecks/facade/cli_runner.rb +28 -172
  149. data/lib/hecks/facade/command_request.rb +13 -33
  150. data/lib/hecks/facade/handle.rb +34 -121
  151. data/lib/hecks/facade/json_door.rb +31 -146
  152. data/lib/hecks/facade/surface/aggregate_door.rb +39 -104
  153. data/lib/hecks/facade/surface/chapter.rb +10 -54
  154. data/lib/hecks/facade/surface.rb +8 -23
  155. data/lib/hecks/facade.rb +10 -25
  156. data/lib/hecks/forms/app.rb +15 -70
  157. data/lib/hecks/forms/command_form_renderer.rb +10 -29
  158. data/lib/hecks/forms/field_renderer.rb +17 -156
  159. data/lib/hecks/forms/field_shape.rb +30 -222
  160. data/lib/hecks/forms/html.rb +12 -47
  161. data/lib/hecks/forms/index_renderer.rb +1 -4
  162. data/lib/hecks/forms/page.rb +3 -9
  163. data/lib/hecks/forms/params.rb +14 -60
  164. data/lib/hecks/forms/port_argument.rb +2 -19
  165. data/lib/hecks/forms/query_form_renderer.rb +4 -79
  166. data/lib/hecks/forms/record_renderer.rb +8 -75
  167. data/lib/hecks/forms/record_table.rb +7 -50
  168. data/lib/hecks/forms/reference_options.rb +3 -30
  169. data/lib/hecks/forms/value_object_shape.rb +8 -35
  170. data/lib/hecks/forms.rb +5 -33
  171. data/lib/hecks/fqn.rb +5 -12
  172. data/lib/hecks/framework/bluebook/compliance.bluebook +16 -65
  173. data/lib/hecks/framework/bluebook/console_settings.bluebook +47 -134
  174. data/lib/hecks/framework/bluebook/framework.hecksagon +5 -14
  175. data/lib/hecks/framework/bluebook/governance.bluebook +13 -34
  176. data/lib/hecks/framework/bluebook/identity.bluebook +11 -26
  177. data/lib/hecks/framework/bluebook/privacy.bluebook +8 -33
  178. data/lib/hecks/framework.rb +11 -80
  179. data/lib/hecks/freezer.rb +5 -39
  180. data/lib/hecks/grammar/expression.bluebook +15 -40
  181. data/lib/hecks/grammar/translation.bluebook +3 -6
  182. data/lib/hecks/grammar.rb +16 -41
  183. data/lib/hecks/ir.rb +14 -83
  184. data/lib/hecks/language/adapter.bluebook +2 -7
  185. data/lib/hecks/language/bluebook/aggregate.bluebook +56 -221
  186. data/lib/hecks/language/bluebook/attaches/paging.bluebook +6 -15
  187. data/lib/hecks/language/bluebook/bluebook.bluebook +15 -83
  188. data/lib/hecks/language/bluebook/bluebook.hecksagon +3 -15
  189. data/lib/hecks/language/bluebook/command.bluebook +27 -144
  190. data/lib/hecks/language/bluebook/entity.bluebook +24 -97
  191. data/lib/hecks/language/bluebook/policy.bluebook +16 -67
  192. data/lib/hecks/language/bluebook/process_manager.bluebook +28 -151
  193. data/lib/hecks/language/bluebook/projection.bluebook +126 -60
  194. data/lib/hecks/language/bluebook/query.bluebook +12 -43
  195. data/lib/hecks/language/bluebook/shape.bluebook +23 -73
  196. data/lib/hecks/language/bluebook/syntax.bluebook +51 -262
  197. data/lib/hecks/language/bluebook/vocabulary.bluebook +98 -197
  198. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +3 -10
  199. data/lib/hecks/language/hecksagon/domain_port.bluebook +4 -19
  200. data/lib/hecks/language/hecksagon/hecksagon.bluebook +6 -25
  201. data/lib/hecks/language/hecksagon/port_operation.bluebook +3 -14
  202. data/lib/hecks/language/oidc.json +35 -0
  203. data/lib/hecks/language/port.bluebook +4 -17
  204. data/lib/hecks/language/translation/translation.bluebook +3 -9
  205. data/lib/hecks/language/translation/translation_aggregate.bluebook +5 -24
  206. data/lib/hecks/language/world/wiring.bluebook +1 -3
  207. data/lib/hecks/language/world/world.bluebook +5 -7
  208. data/lib/hecks/literal.rb +8 -50
  209. data/lib/hecks/mcp_door_scope.rb +95 -0
  210. data/lib/hecks/mcp_stdio_guard.rb +4 -41
  211. data/lib/hecks/naming.rb +29 -137
  212. data/lib/hecks/ports/access_control.rb +7 -23
  213. data/lib/hecks/ports/agent/answers.rb +5 -23
  214. data/lib/hecks/ports/agent.rb +47 -172
  215. data/lib/hecks/ports/authentication.rb +12 -39
  216. data/lib/hecks/ports/authorization.rb +16 -43
  217. data/lib/hecks/ports/clock.rb +5 -45
  218. data/lib/hecks/ports/extraction.rb +7 -12
  219. data/lib/hecks/ports/identity_assignment.rb +8 -29
  220. data/lib/hecks/ports/identity_generation.rb +6 -29
  221. data/lib/hecks/ports/identity_resolution.rb +2 -10
  222. data/lib/hecks/ports/key_vault.rb +9 -45
  223. data/lib/hecks/ports/persistence/append_only.rb +62 -65
  224. data/lib/hecks/ports/persistence/binding_policy.rb +4 -14
  225. data/lib/hecks/ports/persistence/codec_boundary.rb +20 -80
  226. data/lib/hecks/ports/persistence/null_saga_store.rb +4 -20
  227. data/lib/hecks/ports/persistence/plugin.rb +13 -42
  228. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +38 -141
  229. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +15 -115
  230. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +16 -84
  231. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +7 -26
  232. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -22
  233. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +55 -181
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/events.rb +77 -0
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +12 -122
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +28 -128
  237. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +73 -474
  238. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +31 -124
  239. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +45 -219
  240. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +13 -108
  241. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +18 -75
  242. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +9 -49
  243. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +6 -123
  244. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +23 -55
  245. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +14 -72
  246. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +7 -16
  247. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +14 -115
  248. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +13 -45
  249. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +127 -554
  250. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +13 -63
  251. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +2 -17
  252. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +3 -14
  253. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +13 -58
  254. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -17
  255. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -26
  256. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +9 -25
  257. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +22 -122
  258. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +34 -123
  259. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +5 -12
  260. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +3 -9
  261. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -14
  262. data/lib/hecks/ports/persistence/plugins/era.rb +4 -23
  263. data/lib/hecks/ports/persistence/postgres_dump/connection.rb +7 -23
  264. data/lib/hecks/ports/persistence/postgres_dump.rb +4 -35
  265. data/lib/hecks/ports/persistence/remote_runtime.rb +2 -24
  266. data/lib/hecks/ports/persistence/repository_factory.rb +8 -27
  267. data/lib/hecks/ports/persistence/state_codec.rb +23 -103
  268. data/lib/hecks/ports/persistence.rb +7 -14
  269. data/lib/hecks/ports/projection.rb +42 -84
  270. data/lib/hecks/ports/query/in_memory.rb +10 -58
  271. data/lib/hecks/ports/query/ordering.rb +11 -32
  272. data/lib/hecks/ports/query.rb +14 -37
  273. data/lib/hecks/ports.rb +2 -8
  274. data/lib/hecks/projections/bootstrap_table.rb +11 -43
  275. data/lib/hecks/projections/deploy/fargate/assembly.rb +2 -15
  276. data/lib/hecks/projections/deploy/fargate/cdn.rb +1 -48
  277. data/lib/hecks/projections/deploy/fargate/cdn_default.rb +2 -11
  278. data/lib/hecks/projections/deploy/fargate/check.rb +5 -14
  279. data/lib/hecks/projections/deploy/fargate/containers.rb +3 -78
  280. data/lib/hecks/projections/deploy/fargate/extras.rb +3 -44
  281. data/lib/hecks/projections/deploy/fargate/monitoring.rb +0 -44
  282. data/lib/hecks/projections/deploy/fargate/settings.rb +2 -54
  283. data/lib/hecks/projections/deploy/fargate/yaml.rb +13 -58
  284. data/lib/hecks/projections/deploy/fargate.rb +35 -202
  285. data/lib/hecks/projections/deploy/lambda.rb +151 -679
  286. data/lib/hecks/projections/deploy/preview/access_yaml.rb +1 -33
  287. data/lib/hecks/projections/deploy/preview/containers.rb +6 -111
  288. data/lib/hecks/projections/deploy/preview/routing_yaml.rb +4 -56
  289. data/lib/hecks/projections/deploy/preview/script.rb +2 -14
  290. data/lib/hecks/projections/deploy/preview/settings.rb +3 -33
  291. data/lib/hecks/projections/deploy/preview/task_yaml.rb +2 -38
  292. data/lib/hecks/projections/deploy/preview/template.rb +2 -58
  293. data/lib/hecks/projections/deploy/preview.rb +2 -67
  294. data/lib/hecks/projections/deploy/scripts/settings.rb +3 -25
  295. data/lib/hecks/projections/deploy/scripts.rb +4 -57
  296. data/lib/hecks/projections/deploy/shared.rb +47 -146
  297. data/lib/hecks/projections/deploy/smoke.rb +4 -61
  298. data/lib/hecks/projections/deploy/template_diff/comparer.rb +3 -25
  299. data/lib/hecks/projections/deploy/template_diff/loader.rb +2 -11
  300. data/lib/hecks/projections/deploy/template_diff.rb +5 -57
  301. data/lib/hecks/projections/diagrams.rb +20 -582
  302. data/lib/hecks/projections/glossary/html.rb +14 -130
  303. data/lib/hecks/projections/glossary/markdown.rb +8 -69
  304. data/lib/hecks/projections/glossary/mermaid.rb +9 -66
  305. data/lib/hecks/projections/glossary/sensitivity.rb +5 -34
  306. data/lib/hecks/projections/glossary/sentences.rb +14 -159
  307. data/lib/hecks/projections/glossary.rb +30 -222
  308. data/lib/hecks/projections/ir.rb +3 -10
  309. data/lib/hecks/projections/model/deviations.rb +15 -42
  310. data/lib/hecks/projections/model.rb +12 -51
  311. data/lib/hecks/projections/oidc.rb +12 -56
  312. data/lib/hecks/projections/parser_table.rb +9 -28
  313. data/lib/hecks/projections/reference.rb +9 -16
  314. data/lib/hecks/projections/rust_vocabulary.rb +12 -250
  315. data/lib/hecks/projections/shape.rb +3 -29
  316. data/lib/hecks/projections/statements.rb +7 -86
  317. data/lib/hecks/projections/vocabulary.rb +6 -22
  318. data/lib/hecks/projections.rb +2 -16
  319. data/lib/hecks/projector/cli_projector.rb +60 -375
  320. data/lib/hecks/projector/docs_projector.rb +10 -201
  321. data/lib/hecks/projector/exporter.rb +46 -138
  322. data/lib/hecks/projector/ir_projector.rb +2 -9
  323. data/lib/hecks/projector/narrate_projector.rb +15 -189
  324. data/lib/hecks/projector/target.rb +13 -76
  325. data/lib/hecks/projector.rb +30 -121
  326. data/lib/hecks/query_specification/common/comparators.rb +5 -26
  327. data/lib/hecks/query_specification/common/comparison.rb +35 -143
  328. data/lib/hecks/query_specification/common/dsl.rb +8 -24
  329. data/lib/hecks/query_specification/common/null_policy.rb +20 -70
  330. data/lib/hecks/query_specification/common/options.rb +2 -6
  331. data/lib/hecks/query_specification/common/where_clause.rb +2 -10
  332. data/lib/hecks/query_specification/field_path.rb +37 -77
  333. data/lib/hecks/query_specification/hop_path.rb +12 -76
  334. data/lib/hecks/release/gem_pin.rb +2 -26
  335. data/lib/hecks/release/runner/ci_publisher.rb +93 -0
  336. data/lib/hecks/release/runner/commands.rb +66 -0
  337. data/lib/hecks/release/runner/console.rb +52 -0
  338. data/lib/hecks/release/runner/gem_publisher.rb +43 -0
  339. data/lib/hecks/release/runner/git.rb +48 -0
  340. data/lib/hecks/release/runner/npm_publisher.rb +75 -0
  341. data/lib/hecks/release/runner/preflight.rb +104 -0
  342. data/lib/hecks/release/runner/published.rb +55 -0
  343. data/lib/hecks/release/runner/tagger.rb +80 -0
  344. data/lib/hecks/release/runner.rb +215 -0
  345. data/lib/hecks/rendering.rb +4 -26
  346. data/lib/hecks/router/namespace_installer.rb +12 -36
  347. data/lib/hecks/router.rb +4 -8
  348. data/lib/hecks/runtime/aggregate_lock.rb +8 -33
  349. data/lib/hecks/runtime/boot_gates.rb +3 -18
  350. data/lib/hecks/runtime/caller.rb +19 -59
  351. data/lib/hecks/runtime/capability_graph.rb +5 -21
  352. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
  353. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +23 -160
  354. data/lib/hecks/runtime/command_interpreter.rb +125 -307
  355. data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
  356. data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
  357. data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
  358. data/lib/hecks/runtime/command_rules/emission.rb +8 -21
  359. data/lib/hecks/runtime/command_rules/references.rb +23 -202
  360. data/lib/hecks/runtime/dependency_planning.rb +30 -102
  361. data/lib/hecks/runtime/dispatcher.rb +82 -241
  362. data/lib/hecks/runtime/entity_element.rb +40 -462
  363. data/lib/hecks/runtime/entity_interpreter.rb +65 -220
  364. data/lib/hecks/runtime/errors.rb +32 -115
  365. data/lib/hecks/runtime/event.rb +3 -25
  366. data/lib/hecks/runtime/identity.rb +18 -154
  367. data/lib/hecks/runtime/instance.rb +18 -150
  368. data/lib/hecks/runtime/interpreting.rb +11 -70
  369. data/lib/hecks/runtime/invocation.rb +28 -164
  370. data/lib/hecks/runtime/loader.rb +48 -186
  371. data/lib/hecks/runtime/outbox.rb +38 -261
  372. data/lib/hecks/runtime/policy_interpreter.rb +38 -200
  373. data/lib/hecks/runtime/port_operation_interpreter.rb +26 -87
  374. data/lib/hecks/runtime/query_interpreter.rb +45 -187
  375. data/lib/hecks/runtime/reaction_invocation.rb +36 -138
  376. data/lib/hecks/runtime/read_model_interpreter.rb +139 -208
  377. data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
  378. data/lib/hecks/runtime/reference_hop.rb +8 -81
  379. data/lib/hecks/runtime/refusal_wording.rb +8 -89
  380. data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
  381. data/lib/hecks/runtime/registry/verification.rb +52 -225
  382. data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
  383. data/lib/hecks/runtime/registry.rb +103 -244
  384. data/lib/hecks/runtime/remote_dispatcher.rb +37 -152
  385. data/lib/hecks/runtime/routing.rb +4 -15
  386. data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
  387. data/lib/hecks/runtime/saga_interpreter.rb +79 -263
  388. data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
  389. data/lib/hecks/runtime/tenant_check.rb +6 -68
  390. data/lib/hecks/runtime/tenant_scope.rb +3 -28
  391. data/lib/hecks/runtime/value/admission.rb +16 -67
  392. data/lib/hecks/runtime/value/coercion.rb +155 -641
  393. data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
  394. data/lib/hecks/runtime/value.rb +12 -84
  395. data/lib/hecks/runtime.rb +17 -65
  396. data/lib/hecks/storehouse.rb +146 -567
  397. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +8 -32
  398. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
  399. data/lib/hecks/vendoring/git_environment.rb +2 -8
  400. data/lib/hecks/vendoring/git_source.rb +4 -10
  401. data/lib/hecks/vendoring.rb +7 -46
  402. data/lib/hecks/version.rb +3 -14
  403. data/lib/hecks/vocabulary.rb +5 -0
  404. data/lib/hecks.rb +32 -82
  405. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
  406. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
  407. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
  408. metadata +30 -59
  409. data/lib/hecks/bench/cli.rb +0 -74
  410. data/lib/hecks/bench/environment.rb +0 -100
  411. data/lib/hecks/bench/postgres_probe.rb +0 -41
  412. data/lib/hecks/bench/report.rb +0 -116
  413. data/lib/hecks/bench/ruby_runner.rb +0 -151
  414. data/lib/hecks/bench/run.rb +0 -55
  415. data/lib/hecks/bench/rust_runner.rb +0 -165
  416. data/lib/hecks/bench/stats.rb +0 -75
  417. data/lib/hecks/bench/suite.rb +0 -172
  418. data/lib/hecks/bench/workload.rb +0 -137
  419. data/lib/hecks/bench.rb +0 -28
  420. data/lib/hecks/codemod.rb +0 -443
  421. data/lib/hecks/corpus.rb +0 -479
  422. data/lib/hecks/doc/reference.rb +0 -585
  423. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -684
  424. data/lib/hecks/fuzzing/combination_miner.rb +0 -178
  425. data/lib/hecks/fuzzing/concurrent_dispatch.rb +0 -469
  426. data/lib/hecks/fuzzing/coverage_campaign.rb +0 -161
  427. data/lib/hecks/fuzzing/differential.rb +0 -192
  428. data/lib/hecks/fuzzing/domain_generator.rb +0 -871
  429. data/lib/hecks/fuzzing/era_boundary.rb +0 -154
  430. data/lib/hecks/fuzzing/form_census.rb +0 -285
  431. data/lib/hecks/fuzzing/generated_domain_check.rb +0 -171
  432. data/lib/hecks/fuzzing/invalid_value_generator.rb +0 -125
  433. data/lib/hecks/fuzzing/isolated_boot.rb +0 -658
  434. data/lib/hecks/fuzzing/nondeterministic.rb +0 -79
  435. data/lib/hecks/fuzzing/persistence_parity.rb +0 -253
  436. data/lib/hecks/fuzzing/properties/corrections.rb +0 -125
  437. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +0 -688
  438. data/lib/hecks/fuzzing/properties/guards.rb +0 -398
  439. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +0 -399
  440. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +0 -137
  441. data/lib/hecks/fuzzing/properties/outbox.rb +0 -180
  442. data/lib/hecks/fuzzing/properties/querying.rb +0 -260
  443. data/lib/hecks/fuzzing/properties.rb +0 -267
  444. data/lib/hecks/fuzzing/qa_settings.rb +0 -164
  445. data/lib/hecks/fuzzing/replay.rb +0 -930
  446. data/lib/hecks/fuzzing/rotation_priority.rb +0 -114
  447. data/lib/hecks/fuzzing/rust_gap_manifest.rb +0 -139
  448. data/lib/hecks/fuzzing/self_consistency.rb +0 -825
  449. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +0 -532
  450. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +0 -171
  451. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +0 -127
  452. data/lib/hecks/fuzzing/sequence_generator/picker.rb +0 -116
  453. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +0 -138
  454. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +0 -344
  455. data/lib/hecks/fuzzing/sequence_generator.rb +0 -311
  456. data/lib/hecks/fuzzing/shrinker.rb +0 -309
  457. data/lib/hecks/fuzzing/structural_skips.rb +0 -53
  458. data/lib/hecks/fuzzing/sweep_depth.rb +0 -61
  459. data/lib/hecks/fuzzing/target_capabilities.rb +0 -255
  460. data/lib/hecks/fuzzing/value_generator.rb +0 -340
  461. data/lib/hecks/fuzzing.rb +0 -23
  462. data/lib/hecks/grammar/evolve.rb +0 -524
  463. data/lib/hecks/query_ir.rb +0 -482
@@ -1,684 +0,0 @@
1
- require_relative "../bluebook/expression/resolver"
2
- require_relative "../bluebook/expression/evaluator"
3
-
4
- module Hecks
5
- module Fuzzing
6
- # Phase 7 (equivalence-gap plan) — the one place in this codebase a
7
- # genuine "true for all inputs," not merely "true for every input we
8
- # happened to sample," claim is realistically achievable:
9
- # `Hecks::Bluebook::Expression::{Evaluator,Resolver}`. It's finite-
10
- # grammar and side-effect-free (Evaluator::PROJECTION/Resolver's own
11
- # `interpret` never touch a database, a clock, or randomness), but not
12
- # small enough for naive enumeration — ~20 node-producing constructs
13
- # across two mutually-recursive grammar layers, unbounded recursion
14
- # through dotted `Lookup` paths, `Addition`, and nested block
15
- # predicates (`Resolver → Evaluator → Resolver → …`). A flat "every
16
- # string up to length N" or "every node combination up to depth N with
17
- # no type filter" explodes well past anything runnable long before
18
- # depth 3 (two bugs this session lived in exactly this sublanguage —
19
- # the `formerly_known_as`/`unmark_scalar` family, and the
20
- # `canonical_form.rb` aliasing bug — neither would have been caught by
21
- # sampling alone, which is the whole reason this phase exists).
22
- #
23
- # ## Type-directed generation
24
- #
25
- # Type-directed, not exhaustive-over-strings: this generator only ever
26
- # recurses into a sub-expression whose own type the surrounding
27
- # construct actually accepts (never `"x".modulo(true)`) — mirroring,
28
- # by hand, the same admitted-receiver-class tables `Resolver`'s own
29
- # `interpret` enforces at runtime (`SizedType`/`ToStringType`/
30
- # `IncludeHaystack` from vocabulary.bluebook, plus the ad hoc
31
- # `is_a?`/`respond_to?` checks `numeric`/`size_of`/`string_of`/etc.
32
- # apply that vocabulary.bluebook itself doesn't declare). Restricted
33
- # to a small representative literal palette (TYPE_LEAVES, below) and a
34
- # small synthetic attribute set per type (SYNTHETIC_ATTRS), bounded to
35
- # MAX_DEPTH — that combination is what keeps this tractable (thousands
36
- # to tens of thousands of cases, not millions) while still being a
37
- # real, defensible "proven for every well-typed expression up to depth
38
- # 3" claim for exactly the sublanguage that already bit this project
39
- # twice.
40
- #
41
- # ## What "PROVEN" means here, precisely
42
- #
43
- # Every expression this generator
44
- # produces is well-typed by construction (every sub-expression's type
45
- # matches what its parent construct actually accepts, per this file's
46
- # own TYPE_RULES). Interpreting a well-typed expression must never
47
- # raise anything other than `Hecks::Bluebook::Expression::
48
- # EvaluationError` — that class alone is the sublanguage's own real
49
- # refusal vocabulary (a divisor that happens to be zero, a `Lookup`
50
- # this run's synthetic state doesn't happen to carry); anything else
51
- # escaping (a raw `TypeError`/`NoMethodError`/`ZeroDivisionError`) is
52
- # exactly the crash-signature this whole file's own header comments
53
- # (resolver.rb, block_predicates.rb) name as the historical bug class
54
- # this sublanguage keeps almost-but-not-quite avoiding. This does not
55
- # prove the interpreter computes the semantically right answer (that
56
- # needs a second, independent oracle — out of scope here, exactly the
57
- # way rust_conformance_fuzz_spec.rb is the cross-runtime half of this
58
- # same idea) — only that it never crashes on well-typed input.
59
- module BoundedExhaustiveExpressions
60
- module_function
61
-
62
- # `:numeric` deliberately covers both Integer and Float — the
63
- # resolver's own `numeric`/`require_number` never distinguish them
64
- # (resolver.rb) — so a generator that kept them as separate types
65
- # would be modeling a distinction the grammar itself doesn't make.
66
- TYPES = %i[numeric string boolean array nil_type].freeze
67
-
68
- # **The palette** — deliberately small (this file's own header: "a small
69
- # representative literal palette," matching the plan's own `{0, 1,
70
- # -1}` example). Widening it doesn't test a different shape of
71
- # expression, only more values through the same shapes — real
72
- # boundary-value coverage (Bignum, NaN, empty string, unicode) is
73
- # PRD 05's job (spec/runtime/numeric_boundary_spec.rb,
74
- # ValueGenerator's own edge-case tables), not this generator's.
75
- TYPE_LEAVES = {
76
- numeric: ["0", "1", "-1"].freeze,
77
- string: ['""', '"a"'].freeze,
78
- boolean: %w[true false].freeze,
79
- nil_type: %w[nil].freeze
80
- }.freeze
81
-
82
- # Two synthetic attributes per type (the plan's own "2-3 fake
83
- # attributes per type") — real names are irrelevant to `Resolver
84
- # .parse` (it never touches state/attrs at all, confirmed directly:
85
- # only `Lookup#interpret` does) and irrelevant to `interpret` beyond
86
- # needing to resolve against whatever synthetic state this file
87
- # supplies (`synthetic_state`, below) — so any non-colliding,
88
- # non-suffix-shaped name works. Never named the same as a
89
- # `BLOCK_PARAM` (below) — `Resolver#fetch`'s own `attrs`-wins-over-
90
- # `state` precedence means a block parameter shadows a same-named
91
- # top-level attribute for the span of its own predicate, a real
92
- # hazard this file sidesteps by construction rather than exercising
93
- # it here (a real, cited resolver.rb hazard, not a gap in this
94
- # generator's own coverage claim).
95
- SYNTHETIC_ATTRS = {
96
- numeric: %w[num_a num_b].freeze,
97
- string: %w[str_a str_b].freeze,
98
- boolean: %w[bool_a bool_b].freeze,
99
- array: %w[arr_num arr_str].freeze
100
- }.freeze
101
-
102
- # `arr_num`/`arr_str` — element type per array attribute, needed so
103
- # a block predicate's own bound parameter (`BLOCK_PARAM`, below) is
104
- # generated against the right leaf/production set for whatever it's
105
- # actually bound to.
106
- ARRAY_ELEMENT_TYPE = { "arr_num" => :numeric, "arr_str" => :string }.freeze
107
-
108
- BLOCK_PARAM = "el".freeze
109
-
110
- MAX_DEPTH = 3
111
-
112
- # `state`/`attrs` — a plain Hash satisfies both (confirmed directly:
113
- # `Resolver#known?` degrades to a bare index check when `state`
114
- # doesn't `respond_to?(:key?)`, and `fetch` only ever needs
115
- # `attrs.key?`/`attrs[]`.). Split across both, matching a real
116
- # dispatch's own shape (some names come from the record's stored
117
- # state, some from the command's own args) — not load-bearing for
118
- # this proof (attrs wins regardless), but keeps the synthetic input
119
- # closer to what `Admissibility` actually builds, in case anything
120
- # here is reused for a future deeper check.
121
- #
122
- # **The VO-vs-scalar unwrap case, deliberately exercised** — half of
123
- # `num_a`/`str_a`/`bool_a`/one array attribute's elements are bare
124
- # scalars, the other half (`num_b`/`str_b`/`bool_b`/the other
125
- # array's elements) are wrapped `{value: X}` — the single-field
126
- # Value Object shape `Resolver#unwrap_scalar` auto-collapses.
127
- # resolver.rb's own most serious historical bug in this file (the
128
- # "UPDATE 2026-08-18" comment: a dotted walk that landed on an
129
- # un-unwrapped VO compared `false` against every literal, silently,
130
- # never raising) lived exactly at this boundary — a generator that
131
- # only ever supplied bare scalars would never exercise the code
132
- # path that bug lived in at all.
133
- # `SingleFieldVO` — not a plain Hash, deliberately: `Resolver
134
- # #unwrap_scalar`'s own guard is `value.respond_to?(:to_h) &&
135
- # !value.is_a?(Hash) && !value.is_a?(Array)` — it exists
136
- # specifically to collapse a real hydrated `Runtime::Value`
137
- # instance (which responds to `#to_h` but is never itself a bare
138
- # Hash) down to its lone scalar field, and just as deliberately
139
- # leaves an actual Hash alone (a genuinely un-hydrated, multi-
140
- # field record has no single scalar to collapse to). A plain
141
- # `{value: X}` Hash cannot stand in here: `is_a?(Hash)` is true for
142
- # it, so `unwrap_scalar` leaves it wrapped, and every VO-typed
143
- # synthetic attribute then fails `Addition`/`Compare`/every
144
- # scalar-typed operation with "expects a number, got
145
- # {\"value\":5}" — a bug in this generator's own synthetic state,
146
- # not in `Resolver`, that would surface only as `num_b == 5`
147
- # (`num_b` a plain `{value: 5}` Hash) evaluating to `false` instead
148
- # of `true`.
149
- SingleFieldVO = Struct.new(:value) do
150
- def to_h = { value: value }
151
- end
152
-
153
- # Builds the fixed state every generated predicate is interpreted against.
154
- #
155
- # @return [Hash{Symbol => Object}] symbol-keyed synthetic state: half bare
156
- # scalars (`num_a`, `str_a`, `bool_a`, half of `arr_num`'s/`arr_str`'s
157
- # elements), half wrapped `SingleFieldVO` instances (`num_b`, `str_b`,
158
- # `bool_b`, the other half)
159
- def synthetic_state
160
- {
161
- num_a: 3,
162
- num_b: SingleFieldVO.new(5),
163
- str_a: "hello",
164
- str_b: SingleFieldVO.new("world"),
165
- bool_a: true,
166
- bool_b: SingleFieldVO.new(false),
167
- arr_num: [1, SingleFieldVO.new(2), 3],
168
- arr_str: ["x", SingleFieldVO.new("y"), "z"]
169
- }
170
- end
171
-
172
- # The `attrs` half of the interpret call — always empty, since every
173
- # synthetic name this generator uses lives in `synthetic_state` instead.
174
- #
175
- # @return [Hash] always `{}`
176
- def synthetic_attrs = {}
177
-
178
- # Lists every terminal (non-recursive) expression of `type`: the fixed
179
- # literal palette plus every synthetic attribute name declared for that
180
- # type (never their values — this generates text, the same source a
181
- # real `given`/`invariant` author would write; `synthetic_state`,
182
- # above, is what gives those names meaning at `interpret` time).
183
- #
184
- # @param type [Symbol] one of `TYPES`
185
- # @return [Array<String>] `type`'s literal palette, synthetic attribute
186
- # names, and any currently-bound block-parameter leaf for `type`
187
- def leaves(type)
188
- (TYPE_LEAVES[type] || []) + (SYNTHETIC_ATTRS[type] || []) + Array(bound_leaves[type])
189
- end
190
-
191
- # The per-type stack of currently-bound block-parameter leaf names.
192
- #
193
- # A stack, not a single slot — a nested block predicate (the real
194
- # corpus already does this two levels deep, roster.bluebook's own
195
- # `seats.any? { |s| assignments.none? { |a| … } }`) pushes a second
196
- # bound leaf while the outer one is still active. Both share the
197
- # same `BLOCK_PARAM` spelling ("el") — real Ruby block-parameter
198
- # shadowing (the inner `el` simply shadows the outer one within its
199
- # own predicate text), which this generator treats as legal on
200
- # purpose: `resolver.rb`'s own `interpret_with_element` binds fresh
201
- # `attrs.merge(param => element)` per level regardless of what the
202
- # outer level already bound, so a shadowed name still interprets
203
- # correctly — this generator is proving "does it crash," not "is
204
- # every generated predicate semantically distinct."
205
- #
206
- # @return [Hash{Symbol => Array<String>}] each type's own stack of bound
207
- # leaf names, usually `[]` or `["el"]`
208
- def bound_leaves = @bound_leaves ||= Hash.new { |h, k| h[k] = [] }
209
-
210
- # Runs the block with `BLOCK_PARAM` admitted as a leaf of `element_type`,
211
- # for building a block predicate's own body.
212
- #
213
- # @param element_type [Symbol] the type `BLOCK_PARAM` is bound to for the
214
- # duration of the block
215
- # @yield runs with the leaf pushed and any cached `element_type` productions
216
- # invalidated
217
- # @yieldreturn [Object] the block's own result
218
- # @return [Object] the block's own result, unchanged
219
- def with_element_leaf(element_type)
220
- bound_leaves[element_type] << BLOCK_PARAM
221
- cache.delete_if { |(type, _depth), _| type == element_type }
222
- yield
223
- ensure
224
- bound_leaves[element_type].pop
225
- cache.delete_if { |(type, _depth), _| type == element_type }
226
- end
227
-
228
- # Lists every expression of `type` reachable in at most `depth`
229
- # recursive steps, memoized (the same sub-expression set is reused at
230
- # every enclosing recursion, so without memoizing, cost would compound
231
- # multiplicatively per level instead of additively). `depth` 0 is
232
- # exactly `leaves(type)`; each increment adds every construct this
233
- # file's own production methods (below) say can produce `type`, built
234
- # from `depth - 1` sub-expressions.
235
- #
236
- # @param type [Symbol] one of `TYPES`
237
- # @param depth [Integer] maximum recursive steps remaining
238
- # @return [Array<String>] every expression of `type` reachable in at most
239
- # `depth` steps, deduplicated
240
- def productions(type, depth)
241
- cache[[type, depth]] ||= begin
242
- base = leaves(type)
243
- depth <= 0 ? base : (base + recursive_productions(type, depth)).uniq
244
- end
245
- end
246
-
247
- # The `productions` memo, keyed by `[type, depth]`.
248
- #
249
- # @return [Hash{Array(Symbol, Integer) => Array<String>}] the memoized productions cache
250
- def cache = @cache ||= {}
251
-
252
- # Dispatches to the recursive (non-leaf) production rule for `type`.
253
- #
254
- # @param type [Symbol] one of `TYPES`
255
- # @param depth [Integer] maximum recursive steps remaining
256
- # @return [Array<String>] every non-leaf expression of `type` reachable in at
257
- # most `depth` steps; `[]` for `:nil_type`, which has no recursive producer
258
- # @raise [ArgumentError] if `type` names anything outside `TYPES`
259
- def recursive_productions(type, depth)
260
- case type
261
- when :numeric then numeric_productions(depth)
262
- when :string then string_productions(depth)
263
- when :boolean then boolean_productions(depth)
264
- when :array then array_productions(depth)
265
- # no recursive producer of nil in this grammar — Find's "not found" is a
266
- # runtime outcome, not a distinct construct to render as source text
267
- when :nil_type then []
268
- else raise ArgumentError, "no production rule for type #{type.inspect}"
269
- end
270
- end
271
-
272
- # Every internal use of a sub-expression list (as opposed to the
273
- # single final list `productions(type, depth)` returns to its own
274
- # caller) goes through this, not `productions` directly — the
275
- # actual thing that made an early version of this generator explode
276
- # past a million cases by depth 3 wasn't `pairs`' own cross product
277
- # (already sampled) but the dozen-plus linear `flat_map`/`map`
278
- # passes `boolean_productions` alone makes over `str`/`num`/`sub` —
279
- # each individually harmless, but an unbounded few-thousand-item
280
- # list run through a dozen of them, feeding the next depth's own
281
- # dozen passes, compounds fast. Bounding every input list (not the
282
- # final output) keeps the shape diversity `sample`'s even-spacing
283
- # already preserves while keeping growth roughly linear in depth
284
- # instead of combinatorial.
285
- #
286
- # @param type [Symbol] one of `TYPES`
287
- # @param depth [Integer] maximum recursive steps remaining
288
- # @return [Array<String>] `productions(type, depth)`, sampled down to at most
289
- # `SAMPLE_CAP` entries
290
- def bounded(type, depth) = sample(productions(type, depth))
291
-
292
- # NUMERIC ← Addition(numeric, numeric) | Modulo(numeric, numeric) |
293
- # Size(sized) | First/Last(numeric array). `Size` returns an
294
- # Integer for a String or an Array receiver alike (`SizedType` —
295
- # `size_of`, resolver.rb) — both sides generated here.
296
- #
297
- # @param depth [Integer] maximum recursive steps remaining
298
- # @return [Array<String>] numeric expressions one construct deep: `Addition`,
299
- # `Modulo`, `Size` (of a string or array), `First`/`Last` (of a numeric array)
300
- def numeric_productions(depth)
301
- sub = bounded(:numeric, depth - 1)
302
- # `Modulo`'s own receiver (not its argument — that side already
303
- # goes through `Resolver#matching_paren`'s own fresh, self-
304
- # contained re-parse, confirmed safe for any numeric shape
305
- # including another `Addition`/`Modulo`) has the identical
306
- # "`Addition` mis-parsed as a suffix receiver" hazard
307
- # `resolver_numeric_leaves`'s own comment documents for `.to_s`/
308
- # `.positive?` — `Resolver.parse` tries `split_addition` before
309
- # `match_call`, so `"0 + 0.modulo(1)"` (meant as `(0 + 0)
310
- # .modulo(1)`) actually parses as `0 + (0.modulo(1))`. Restricted
311
- # to `resolver_numeric_leaves` on the receiver side only — the
312
- # argument stays the full, unrestricted numeric set.
313
- pairs(sub).map { |a, b| "#{a} + #{b}" } +
314
- cross(resolver_numeric_leaves(depth - 1), sub).map { |a, b| "#{a}.modulo(#{b})" } +
315
- bounded(:string, depth - 1).map { |s| "#{s}.size" } +
316
- bounded(:array, depth - 1).map { |a| "#{a}.size" } +
317
- sample(numeric_array_productions(depth - 1)).flat_map { |a| ["#{a}.first", "#{a}.last"] }
318
- end
319
-
320
- # A "boolean" in resolver's own sense — safe to embed as the
321
- # receiver of a trailing Resolver-level suffix (`.to_s`, and
322
- # anywhere else a boolean-typed value, as opposed to a boolean-
323
- # typed expression, is wanted). Found live, the same way the
324
- # nested-`.modulo` bug was: `Resolver.parse` has no knowledge of
325
- # `==`/`<`/`&&`/`||`/leading `!`/`.include?` at all — those are
326
- # `Evaluator`'s own, entirely separate parsing layer, stripped off
327
- # before anything reaches `Resolver.parse` at all (confirmed
328
- # directly: `Resolver.parse("str_b < str_a")` — no `.` anywhere in
329
- # that text for any suffix regex to anchor on — falls through
330
- # every leaf regex to the `Lookup` catch-all, exactly like the
331
- # nested-modulo bug did). `bounded(:boolean, depth)`'s full set
332
- # includes `Compare`/`Include`/`Or`/`And`/`Not` — genuinely boolean-
333
- # typed at `interpret` time, but `Evaluator`-level syntax, not
334
- # something `Resolver.parse` can ever recognize as a receiver no
335
- # matter how it's parenthesized (confirmed directly too: `Resolver
336
- # .parse` never strips parens at all — `"(3)"` alone already fails
337
- # to resolve). This is a real, permanent boundary of the actual
338
- # grammar (this whole sublanguage's own two-layer split, not a
339
- # limitation to work around) — a `given`/`invariant` author simply
340
- # cannot write `(a < b).to_s` in this language, ever, no matter how
341
- # they punctuate it. So this generator doesn't either: only
342
- # resolver-level boolean-producing constructs (bare literals/
343
- # lookups, `SignTest`, `Empty`, `Presence`, `MatchesRegex`,
344
- # `StartsWith`/`EndsWith`, `BlockPredicate` — every one of them
345
- # parsed via a suffix regex inside `Resolver.parse` itself, per
346
- # this generator's own design report) are eligible here.
347
- #
348
- # @param depth [Integer] maximum recursive steps remaining
349
- # @return [Array<String>] boolean-typed expressions safe to embed as a
350
- # Resolver-level suffix receiver: literals/lookups, sign tests, string
351
- # predicates, `.empty?`, and sampled block predicates
352
- def resolver_boolean_leaves(depth)
353
- leaves(:boolean) +
354
- resolver_numeric_leaves(depth).flat_map { |n| ["#{n}.positive?", "#{n}.negative?", "#{n}.zero?"] } +
355
- bounded(:string, depth).flat_map do |s|
356
- ["#{s}.present?", "#{s}.blank?", "#{s}.match?(/a/)", "#{s}.start_with?(\"a\")", "#{s}.end_with?(\"a\")"]
357
- end +
358
- (sample(numeric_array_productions(depth)) + sample(string_array_productions(depth)) + bounded(:array, depth)).map do |x|
359
- "#{x}.empty?"
360
- end +
361
- sample(block_predicate_productions(depth))
362
- end
363
-
364
- # A "numeric" in resolver's own sense — `Addition`'s twin of
365
- # `resolver_boolean_leaves`'s own restriction, found the identical
366
- # way: `"0 + 0.to_s"` (meant as `(0 + 0).to_s`) actually parses as
367
- # `0 + (0.to_s)`, because `Resolver.parse` tries `split_addition`
368
- # before `.to_s`'s own suffix regex in its dispatch order — the `+`
369
- # "wins" the split before the suffix ever gets a chance to anchor
370
- # on its own receiver boundary. Confirmed to have zero real-corpus
371
- # precedent either (`grep`, no `bluebook` file anywhere chains a
372
- # method or sign-test onto a parenthesized addition) — the same
373
- # verdict as `resolver_boolean_leaves`'s own comparisons/`&&`/`||`:
374
- # a real permanent grammar boundary (`Resolver.parse` never strips
375
- # parens, confirmed directly, so no amount of punctuation rescues
376
- # `(a + b).to_s`), not a bug to fix in the resolver for a shape
377
- # nothing has ever needed. `Modulo`/`Size`/`First`/`Last` stay in
378
- # (each is its own trailing `.method(...)`/`.method` call, so
379
- # `Resolver.parse`'s greedy `(.+)\.suffix\z` regexes correctly
380
- # isolate them as a receiver regardless of what precedes them —
381
- # only bare top-level `+` has this problem).
382
- #
383
- # @param depth [Integer] maximum recursive steps remaining
384
- # @return [Array<String>] numeric-typed expressions safe to embed as a
385
- # Resolver-level suffix receiver (never a bare top-level `Addition`)
386
- def resolver_numeric_leaves(depth)
387
- return leaves(:numeric) if depth <= 0
388
-
389
- # Self-referential on purpose, one depth down — `Modulo`'s own
390
- # receiver needs the same restriction `resolver_numeric_leaves`
391
- # exists to express in the first place (see its own header
392
- # comment); the argument stays the full, unrestricted set, same
393
- # as `numeric_productions`' identical split right above.
394
- leaves(:numeric) +
395
- cross(resolver_numeric_leaves(depth - 1), bounded(:numeric, depth - 1)).map { |a, b| "#{a}.modulo(#{b})" } +
396
- bounded(:string, depth).map { |s| "#{s}.size" } +
397
- bounded(:array, depth).map { |a| "#{a}.size" } +
398
- sample(numeric_array_productions(depth)).flat_map { |a| ["#{a}.first", "#{a}.last"] }
399
- end
400
-
401
- # STRING ← ToS(numeric | boolean | nil | string) | First/Last(string
402
- # array). `Split` produces an array, never a String (resolver.rb's
403
- # own `apply_split`) — deliberately not listed as a string producer
404
- # here; that would be exactly the "vocabulary-legal but grammar-
405
- # can't-actually-produce-it" mistake this sublanguage's own
406
- # `ArrayLiteral` bug (§1 of this generator's own design report) was
407
- # found from, inverted.
408
- #
409
- # @param depth [Integer] maximum recursive steps remaining
410
- # @return [Array<String>] string expressions one construct deep: `ToS` of a
411
- # numeric, boolean, nil, or string operand, or `First`/`Last` of a string array
412
- def string_productions(depth)
413
- sample(resolver_numeric_leaves(depth - 1)).map { |n| "#{n}.to_s" } +
414
- sample(resolver_boolean_leaves(depth - 1)).map { |b| "#{b}.to_s" } +
415
- bounded(:nil_type, depth - 1).map { |n| "#{n}.to_s" } +
416
- bounded(:string, depth - 1).map { |s| "#{s}.to_s" } +
417
- sample(string_array_productions(depth - 1)).flat_map { |a| ["#{a}.first", "#{a}.last"] }
418
- end
419
-
420
- # ARRAY ← Split(string, separator) | ArrayLiteral[same-type
421
- # elements] | the synthetic array attributes (already in `leaves`).
422
- # Kept deliberately small relative to numeric/string/boolean —
423
- # `array` is overwhelmingly a receiver type in this grammar (`.size`
424
- # /`.any?`/`.include?`/…), rarely a produced value; the two real
425
- # producers are enough to exercise every array-typed consumer
426
- # elsewhere in this file at least once via a non-leaf path.
427
- #
428
- # @param depth [Integer] maximum recursive steps remaining
429
- # @return [Array<String>] array expressions one construct deep: a string
430
- # `Split`, a numeric array literal, and a string array literal; `[]` at depth 0
431
- def array_productions(depth)
432
- return [] if depth <= 0
433
-
434
- bounded(:string, depth - 1).map { |s| "#{s}.split(\",\")" } +
435
- [numeric_array_literal(depth - 1), string_array_literal(depth - 1)]
436
- end
437
-
438
- # Builds a two-element numeric array literal at `depth`.
439
- #
440
- # @param depth [Integer] maximum recursive steps remaining for its own elements
441
- # @return [String] a `"[a, b]"` numeric array literal source text
442
- def numeric_array_literal(depth) = "[#{bounded(:numeric, depth).first(2).join(', ')}]"
443
-
444
- # Builds a two-element string array literal at `depth`.
445
- #
446
- # @param depth [Integer] maximum recursive steps remaining for its own elements
447
- # @return [String] a `"[a, b]"` string array literal source text
448
- def string_array_literal(depth) = "[#{bounded(:string, depth).first(2).join(', ')}]"
449
-
450
- # Arrays known (by construction, not merely by type) to hold numeric
451
- # elements — the two synthetic array attributes (`ARRAY_ELEMENT_TYPE`
452
- # tags `arr_num`) plus any numeric-array literal this same depth
453
- # budget can build. `.first`/`.last`/block predicates need to know
454
- # the element type, which plain `array_productions` doesn't carry —
455
- # this (and `string_array_productions`, its twin) is how that extra
456
- # bit of type information flows without inventing a second, richer
457
- # AST just to carry it.
458
- #
459
- # @param depth [Integer] maximum recursive steps remaining
460
- # @return [Array<String>] array expressions known to hold numeric elements:
461
- # `"arr_num"`, plus a numeric array literal at `depth`
462
- def numeric_array_productions(depth)
463
- ["arr_num", numeric_array_literal(depth)]
464
- # a Split of a string never yields numeric elements — no third
465
- # entry here, deliberately, not omitted by oversight (see
466
- # `string_array_productions`, its non-empty twin, right below).
467
- end
468
-
469
- # Arrays known (by construction) to hold string elements — `arr_str`'s
470
- # own twin of `numeric_array_productions`, above.
471
- #
472
- # @param depth [Integer] maximum recursive steps remaining
473
- # @return [Array<String>] `"arr_str"`, a string array literal at `depth`, and
474
- # every string `Split` expression at `depth`
475
- def string_array_productions(depth)
476
- ["arr_str", string_array_literal(depth)] + bounded(:string, depth).map { |s| "#{s}.split(\",\")" }
477
- end
478
-
479
- # BOOLEAN ← every comparison/predicate construct in the grammar.
480
- # This is where almost all of the sublanguage's own real surface
481
- # lives — a `given`/`invariant`/`ensures` body is always boolean-
482
- # typed at its own top level (`Evaluator.truthy?`), so this is also
483
- # the set `all_predicates` (below) draws its top-level cases from
484
- # directly.
485
- # One declarative table of boolean productions, not several
486
- # unrelated things bolted together — each `+`-joined term is a
487
- # distinct grammar rule, and several carry their own comment
488
- # explaining a specific inclusion/exclusion choice (the
489
- # `resolver_numeric_leaves`-not-`num` note above, `arr_num`'s non-
490
- # empty twin, etc). Extracting terms into separate methods would
491
- # need `sub`/`num`/`str` threaded into each as parameters and
492
- # would separate every rule from the comment justifying it.
493
- # rubocop:disable-next Metrics/AbcSize
494
- #
495
- # @param depth [Integer] maximum recursive steps remaining
496
- # @return [Array<String>] every boolean-typed construct in the grammar, one
497
- # level deep: comparisons, sign tests, `.empty?`/`.match?`/`.present?`/
498
- # `.blank?`/`.start_with?`/`.end_with?`, `&&`/`||`/`!`, block predicates,
499
- # and `include?`
500
- def boolean_productions(depth)
501
- sub = bounded(:boolean, depth - 1)
502
- num = bounded(:numeric, depth - 1)
503
- str = bounded(:string, depth - 1)
504
-
505
- pairs(num).map { |a, b| "#{a} == #{b}" } +
506
- pairs(num).map { |a, b| "#{a} > #{b}" } +
507
- pairs(str).map { |a, b| "#{a} == #{b}" } +
508
- pairs(str).map { |a, b| "#{a} < #{b}" } +
509
- # `resolver_numeric_leaves`, not the raw `num` `==`/`>` above
510
- # safely use — SignTest's own suffix match
511
- # (`match_suffix`/`.positive?` et al.) is a plain trailing-
512
- # string strip, not `Evaluator`'s comparison-scanning, so an
513
- # `Addition` in `num` would hit the exact "`0 + 0.to_s`" mis-
514
- # split this generator's own `resolver_numeric_leaves` comment
515
- # documents (`"0 + 0.positive?"` would parse as `0 + (0
516
- # .positive?)`, not `(0 + 0).positive?`).
517
- resolver_numeric_leaves(depth - 1).flat_map { |n| ["#{n}.positive?", "#{n}.negative?", "#{n}.zero?"] } +
518
- (str + sample(numeric_array_productions(depth - 1)) + sample(string_array_productions(depth - 1)) + bounded(:array,
519
- depth - 1))
520
- .map do |x|
521
- "#{x}.empty?"
522
- end +
523
- str.map { |s| "#{s}.match?(/a/)" } +
524
- str.flat_map { |s| ["#{s}.present?", "#{s}.blank?"] } +
525
- str.flat_map { |s| ["#{s}.start_with?(\"a\")", "#{s}.end_with?(\"a\")"] } +
526
- pairs(sub).flat_map { |a, b| ["#{a} && #{b}", "#{a} || #{b}"] } +
527
- sub.map { |b| "!#{b}" } +
528
- block_predicate_productions(depth - 1) +
529
- include_productions(depth - 1)
530
- end
531
-
532
- # `.all?`/`.any?`/`.none?` over each known-element-typed array
533
- # source, with a predicate body drawn from `boolean_productions` at
534
- # one less depth, evaluated against `BLOCK_PARAM` bound to the
535
- # array's own element type — this is the only place `Resolver` and
536
- # `Evaluator` are truly mutually recursive (a `BlockPredicate`'s own
537
- # `predicate` field is a full `Evaluator` AST, not a `Resolver`
538
- # leaf — confirmed directly), and the only construct in this whole
539
- # generator that can nest into another block predicate (the real
540
- # corpus already does this two levels deep —
541
- # `examples/roster/bluebook/roster.bluebook`'s own `seats.any? { |s|
542
- # assignments.none? { |a| … } }`).
543
- #
544
- # @param depth [Integer] maximum recursive steps remaining for the predicate body
545
- # @return [Array<String>] `.all?`/`.any?`/`.none?` calls over each
546
- # known-element-typed array source, one per predicate body `predicate_bodies`
547
- # produces; `[]` if `depth` is negative
548
- def block_predicate_productions(depth)
549
- return [] if depth.negative?
550
-
551
- [
552
- ["arr_num", :numeric], [numeric_array_literal(depth), :numeric],
553
- ["arr_str", :string], [string_array_literal(depth), :string]
554
- ].flat_map do |array_text, element_type|
555
- predicate_bodies(element_type, depth).flat_map do |body|
556
- %w[all? any? none?].map { |mode| "#{array_text}.#{mode} { |#{BLOCK_PARAM}| #{body} }" }
557
- end
558
- end
559
- end
560
-
561
- # The predicate body a block gets — `boolean_productions`, but with
562
- # `BLOCK_PARAM` (bound to `element_type`) also admitted as a leaf,
563
- # since inside the block it is exactly as usable as any other
564
- # `Lookup` name (resolver.rb's own `interpret_with_element`: the
565
- # bound element joins `attrs` for the span of one evaluation, no
566
- # different from a top-level attribute — confirmed directly).
567
- # `sample`d for the same reason every other internal list is — this
568
- # feeds three more constructs per body (`all?`/`any?`/`none?`) times
569
- # four array sources, so an unbounded body list here is exactly the
570
- # kind of multiplier this file's own `bounded` comment warns about.
571
- #
572
- # @param element_type [Symbol] the type `BLOCK_PARAM` is bound to inside the block
573
- # @param depth [Integer] maximum recursive steps remaining for the body
574
- # @return [Array<String>] a sampled set of boolean expressions, with
575
- # `BLOCK_PARAM` admitted as a leaf of `element_type`
576
- def predicate_bodies(element_type, depth)
577
- sample(with_element_leaf(element_type) { boolean_productions(depth) })
578
- end
579
-
580
- # `haystack.include?(needle)` — String haystack needs a String
581
- # needle (raises otherwise, evaluator.rb's own `includes?`); Array
582
- # haystack admits any needle type (compared via `equal?`, itself
583
- # numeric-coerced-first). Both sides generated here, matching
584
- # `Vocabulary::IncludeHaystack` exactly.
585
- #
586
- # @param depth [Integer] maximum recursive steps remaining
587
- # @return [Array<String>] `haystack.include?(needle)` expressions: a String
588
- # haystack with a String needle, or a numeric/string array haystack with a
589
- # matching-type needle
590
- def include_productions(depth)
591
- str = bounded(:string, depth)
592
- pairs(str).map { |haystack, needle| "#{haystack}.include?(#{needle})" } +
593
- sample(numeric_array_productions(depth)).product(bounded(:numeric, depth)).map do |arr, needle|
594
- "#{arr}.include?(#{needle})"
595
- end +
596
- sample(string_array_productions(depth)).product(bounded(:string, depth)).map do |arr, needle|
597
- "#{arr}.include?(#{needle})"
598
- end
599
- end
600
-
601
- # **Sampled, not a full cross product** — a full `list.product(list)`
602
- # is what actually explodes this generator (numeric productions
603
- # alone hit 8000+ by depth 2; squaring that for `&&`/`==` pairs is
604
- # where "tens of thousands" becomes tens of millions). The
605
- # combinatorics genuinely don't buy proof coverage: proving `Or`/
606
- # `And`/`Not` themselves never crash needs one representative pair
607
- # per depth (`evaluator.rb`'s own `interpret` does zero type-
608
- # dependent work for those three — `interpret(left) || interpret
609
- # (right)`, plain Ruby, no coercion, no receiver-type check at
610
- # all — so a crash there could only come from left or right
611
- # themselves, already covered by testing every operand on its own
612
- # elsewhere in this same predicate set). `Compare`/`Include`/
613
- # `Addition`/`Modulo` genuinely do real per-pair type coercion
614
- # (`Evaluator.apply`/`less_than`/`Resolver.add`/`apply_modulo`), so
615
- # those stay covered across every construct shape at every depth —
616
- # just sampled evenly across each side's own operand set (leaves
617
- # and deep productions alike, not just whichever the list happens
618
- # to enumerate first) rather than every possible pairing of them.
619
- SAMPLE_CAP = 14
620
-
621
- # Evenly samples `list` down to at most `SAMPLE_CAP` entries, so a list
622
- # size never compounds unbounded across recursive passes.
623
- #
624
- # @param list [Array<String>] the list to sample
625
- # @return [Array<String>] `list` unchanged if it is at most `SAMPLE_CAP` entries;
626
- # otherwise every `list.size.fdiv(SAMPLE_CAP).ceil`-th entry, evenly spaced
627
- def sample(list) = list.size <= SAMPLE_CAP ? list : list.each_slice(list.size.fdiv(SAMPLE_CAP).ceil).map(&:first)
628
-
629
- # Builds every pair of one sampled list with itself.
630
- #
631
- # @param list [Array<String>] the list to sample and pair with itself
632
- # @return [Array<Array(String, String)>] the sampled list's own self cross product
633
- def pairs(list)
634
- sampled = sample(list)
635
- sampled.product(sampled)
636
- end
637
-
638
- # `pairs`' own two-different-lists twin — used wherever the left
639
- # and right of a construct have genuinely different safety
640
- # requirements (`Modulo`'s own receiver vs. argument, below) and
641
- # squaring the same sampled list wouldn't be correct.
642
- #
643
- # @param left [Array<String>] the left-hand list to sample
644
- # @param right [Array<String>] the right-hand list to sample
645
- # @return [Array<Array(String, String)>] the two sampled lists' own cross product
646
- def cross(left, right) = sample(left).product(sample(right))
647
-
648
- # The full set — every boolean-typed expression up to `MAX_DEPTH`,
649
- # deduplicated (many shorter expressions are also produced, re-
650
- # wrapped, at every deeper level — `.uniq` inside `productions`
651
- # already collapses most of that; this is the final pass over the
652
- # complete depth-`MAX_DEPTH` set specifically).
653
- #
654
- # @param max_depth [Integer] maximum recursive depth to generate up to
655
- # @return [Array<String>] every boolean-typed expression up to `max_depth`,
656
- # deduplicated
657
- def all_predicates(max_depth = MAX_DEPTH)
658
- productions(:boolean, max_depth).uniq
659
- end
660
-
661
- # Interprets one predicate against the shared synthetic
662
- # state/attrs, returning `{ok: true, result: ...}` on any outcome
663
- # `Evaluator.call` itself can express (a real true/false answer, or
664
- # a clean `EvaluationError` — both are the sublanguage working
665
- # correctly, never a finding) and `{ok: false, error: ...}` only
666
- # for anything else escaping — the one shape this whole file exists
667
- # to prove never happens for well-typed input.
668
- #
669
- # @param expr [String] a generated (or hand-written) expression source string
670
- # @return [Hash{Symbol => Object}] `{ok: true, result: Object}` for a real
671
- # answer, `{ok: true, result: :refused, message: String}` for a clean
672
- # `EvaluationError`, or `{ok: false, error: StandardError}` for anything else
673
- # escaping
674
- def check(expr)
675
- result = Hecks::Bluebook::Expression::Evaluator.call(expr, synthetic_state, synthetic_attrs)
676
- { ok: true, result: result }
677
- rescue Hecks::Bluebook::Expression::EvaluationError => e
678
- { ok: true, result: :refused, message: e.message }
679
- rescue StandardError => e
680
- { ok: false, error: e }
681
- end
682
- end
683
- end
684
- end