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
@@ -4,67 +4,14 @@ require_relative "resolver"
4
4
  module Hecks
5
5
  module Bluebook
6
6
  module Expression
7
- # Walks the real Evaluator/Resolver AST (the same objects a live
8
- # dispatch parses `given`/`ensures`/invariant text into — see
9
- # docs/implemented/guides/running-a-runtime.md's "The expression
10
- # grammar") and emits plain, JSON-serializable Ruby Hashes, tagged
11
- # by `"op"`.
12
- #
13
- # ## Why this exists beside `rust/project/expr_emitter.rb`
14
- #
15
- # The same two-method walk `rust/project/expr_emitter.rb`'s own
16
- # `emit_bool`/`emit_resolver` already do, over the same AST — that
17
- # file's own methods build Rust source-code strings for `rust/
18
- # project`'s codegen (`rust/src/kernel::expr::Expr` literals, baked
19
- # into a generated domain's own compiled binary); this builds data
20
- # instead, for a genuinely different consumer with a genuinely
21
- # different constraint: `rust/host` can never link the `rust`
22
- # (kernel) crate at all (a real, load-bearing build constraint —
23
- # `reference_validate.rs`'s own header has the full reasoning: one
24
- # path dependency would statically bake every domain's generated
25
- # dispatch code into every Lambda binary), so a value object's own
26
- # `invariant` predicate has to travel as something `rust/host` can
27
- # deserialize and interpret itself, at runtime, from `ir.json` — the
28
- # exact same relationship `rust/project`'s own `Expr` literals
29
- # already have to the compiled kernel, one layer further out.
30
- #
31
- # ## Why it lives in core `lib/hecks`, not `rust/project/`
32
- #
33
- # `rust/project.rb` is a separate, downstream toolchain
34
- # (`lib/hecks/projector.rb`'s own header: "a whole separate Ruby
35
- # program"), never `require`d by core `lib/hecks/bluebook/*.rb`
36
- # files (confirmed: no core file does). `value_object.rb`'s own
37
- # `invariants:` IR emission needs this for every domain's ordinary
38
- # `to_h`/`ir.json` export — golden fixtures, `hecks-parse`'s parity
39
- # comparisons, and any deploy artifact, not only a `bin/project_rust`
40
- # run — so it belongs beside `Evaluator`/`Resolver` themselves, not
41
- # bolted onto a tool that only sometimes runs.
42
- #
43
- # ## Complete, not corpus-scoped
44
- #
45
- # Every node this grammar admits gets a real arm, the identical
46
- # "raise, don't silently drop" discipline `expr_emitter.rb`'s own
47
- # `emit_bool`/`emit_resolver` already hold to — even though, as of
48
- # this writing, no real corpus value object invariant exercises
49
- # `Include`/`Modulo`/`BlockPredicate`/`Find`/`Array`/`MatchesRegex`/
50
- # `Presence`/`Assignment`/`Split`/`StartsWith`/`EndsWith`/`First`/
51
- # `Last` (only `given`/`ensures` clauses do, elsewhere in the corpus
52
- # — a different consumer of this same grammar). `rust/host/src/
53
- # expr_json.rs`'s own header names exactly which of these its
54
- # interpreter evaluates for real today versus refuses cleanly — a
55
- # narrower, deliberate, documented boundary on the interpreting
56
- # side, not on this emitting side: an author is free to write any
57
- # real expression in a value object's own `invariant`, and this
58
- # always emits it faithfully; whether `rust/host` can yet check it
59
- # at mint time is that file's own question to answer, not this
60
- # one's to pre-empt by refusing to even try.
7
+ # Walks the Evaluator/Resolver AST and emits JSON-serializable Hashes tagged by `"op"`.
8
+ # Lives in core because rust/host cannot link the kernel crate and interprets this from
9
+ # ir.json.
61
10
  module AstJson
62
11
  module_function
63
12
 
64
- # **The closed op roster** — every `"op"` tag the walkers below can
65
- # emit, pinned so a reader (or a spec) can refuse a tag it does
66
- # not know instead of guessing. A new node kind is a new entry
67
- # here, a new arm below, and a new arm in every reader.
13
+ # The closed roster of `"op"` tags. A new node kind needs an entry here and an arm in
14
+ # each walker and reader; unhandled nodes raise rather than being dropped.
68
15
  OPS = %w[
69
16
  or and not compare include
70
17
  int float str bool nil array lookup
@@ -73,48 +20,20 @@ module Hecks
73
20
  matches_regex presence assignment split starts_with ends_with
74
21
  ].freeze
75
22
 
76
- # One rule row, the way every rule site emits it — description and
77
- # canonical text (what every reader has always had) plus the
78
- # structured form, derived from the same text. `ast` is a pure
79
- # function of `canonical`: the IR carries both so a reader that
80
- # only displays keeps the text, and a reader that evaluates never
81
- # re-parses it.
82
- #
83
- # @param rule [Bluebook::Given, Bluebook::Invariant] the rule to
84
- # render; `rule.ast` is used when already computed, otherwise
85
- # `rule.canonical` is parsed and emitted fresh
86
- # @return [Hash{Symbol => Object}] `:description` (`String`),
87
- # `:canonical` (`String`), and `:ast` (`Hash`, the JSON AST)
23
+ # One rule row: description, canonical text, and the AST derived from that text.
88
24
  def rule_row(rule)
89
25
  { description: rule.description, canonical: rule.canonical, ast: rule.ast || emit_predicate(rule.canonical) }
90
26
  end
91
27
 
92
28
  # Parses `canonical` and emits its JSON AST in one step.
93
- #
94
- # @param canonical [String] a canonical predicate source string
95
- # @return [Hash] the JSON-shaped boolean AST, tagged by `"op"`
96
29
  def emit_predicate(canonical)
97
30
  emit_bool(Evaluator.parse(canonical))
98
31
  end
99
32
 
100
- # C3.6 (docs/semantics/bluebook-semantics.md) — every `.match?`
101
- # pattern a rule carries is held to `PatternSubset`, exactly as an
102
- # attribute's own `pattern:` already is (`AttributeCollector#
103
- # refuse_unshared_pattern`): a regex whose meaning depends on the
104
- # engine reading it is a defect in the bluebook, refused at build.
105
- # Walks the emitted AST, so every rule site (givens, ensures,
106
- # invariants, preconditions, a policy's where) gets the one check.
33
+ # Holds every `.match?` pattern a rule carries to `PatternSubset`, as an attribute's
34
+ # `pattern:` is; walking the AST gives every rule site the one check.
107
35
  #
108
- # @param ast [Hash] a JSON AST node, as emitted by `emit_predicate`
109
- # or `emit_bool`
110
- # @param owner [String] the declaring bluebook's name, for the
111
- # refusal message
112
- # @param word [String] the rule's own kind and description
113
- # (such as `"given \"total is positive\""`), for the refusal
114
- # message
115
- # @return [Hash] `ast`, unchanged
116
- # @raise [Bluebook::DSL::Malformed] if any `matches_regex` node's
117
- # pattern uses a construct `PatternSubset` does not admit
36
+ # @raise [Bluebook::DSL::Malformed] if a `matches_regex` pattern is outside `PatternSubset`
118
37
  def refuse_unshared_patterns!(ast, owner:, word:)
119
38
  return ast if Hecks::Bluebook::MetaValidator.shadow_parsing? # frozen era text is history
120
39
 
@@ -132,30 +51,18 @@ module Hecks
132
51
  end
133
52
 
134
53
  # What a `lookup` path can never contain: a call's parentheses, or an argument list's
135
- # commas and spaces. Attribute names carry none of these, so a path that does is a method
136
- # call with arguments that the grammar has no node for.
54
+ # commas and spaces.
137
55
  #
138
- # A bare `?` is deliberately not in the set. `x.nil?` also parses as a lookup, but it loads
139
- # and, on a String, Boolean or `nil` value, evaluates without raising; real bluebooks
140
- # declare it, so refusing it would stop them loading.
56
+ # A bare `?` is deliberately not in the set: `x.nil?` also parses as a lookup, loads, and
57
+ # evaluates without raising, and real bluebooks declare it.
141
58
  UNRESOLVABLE_PATH = /[(),\s]/
142
59
  private_constant :UNRESOLVABLE_PATH
143
60
 
144
- # Refuses a rule that calls a method the expression grammar does not have.
145
- #
146
- # An unrecognised call such as `value.between?(100, 599)` parses as a `lookup` whose path
147
- # names an attribute that can never exist, so it loads and then fails on the first
148
- # dispatch. Refusing it here moves the failure to build time, where the author is looking.
149
- # The supported spellings (comparisons joined with `&&`/`||`, `.positive?`, `.empty?`,
150
- # `.include?`, ...) are the rows of `projection.json`.
61
+ # Refuses a rule that calls a method the grammar lacks, such as `value.between?(100, 599)`.
62
+ # It would parse as a `lookup` of an attribute that cannot exist and fail on first dispatch.
151
63
  #
152
- # @param ast [Hash] a JSON AST node, as emitted by `emit_predicate` or `emit_bool`
153
- # @param owner [String] the declaring bluebook's name, for the refusal message
154
- # @param word [String] the rule's own kind and description (such as
155
- # `"invariant \"a status code\""`), for the refusal message
156
- # @return [Hash] `ast`, unchanged
157
- # @raise [Bluebook::DSL::Malformed] if any `lookup` node's path contains a parenthesis,
158
- # comma or whitespace
64
+ # @raise [Bluebook::DSL::Malformed] if a `lookup` path contains a parenthesis, comma or
65
+ # space
159
66
  def refuse_unresolvable_lookups!(ast, owner:, word:)
160
67
  return ast if Hecks::Bluebook::MetaValidator.shadow_parsing? # frozen era text is history
161
68
 
@@ -174,13 +81,7 @@ module Hecks
174
81
  ast
175
82
  end
176
83
 
177
- # Every name a rule resolves at its root — the first segment of
178
- # each `lookup` path, unique, in first-seen order.
179
- #
180
- # @param ast [Hash] a JSON AST node, as emitted by `emit_predicate`
181
- # or `emit_bool`
182
- # @return [Array<String>] each `lookup` node's root name, unique,
183
- # in first-seen order
84
+ # Every name a rule resolves at its root: the first segment of each `lookup` path, unique.
184
85
  def lookup_heads(ast)
185
86
  heads = []
186
87
  each_node(ast) do |node|
@@ -189,13 +90,7 @@ module Hecks
189
90
  heads.uniq
190
91
  end
191
92
 
192
- # Visits `node` and every Hash node nested inside it, depth-first.
193
- #
194
- # @param node [Hash, Array, Object] a JSON AST node, or an Array or
195
- # scalar value found while walking one; a scalar is a silent no-op
196
- # @yieldparam node [Hash] each Hash node reached, including `node`
197
- # itself
198
- # @return [void]
93
+ # Visits `node` and every Hash nested inside it, depth-first.
199
94
  def each_node(node, &block)
200
95
  case node
201
96
  when ::Hash
@@ -207,16 +102,6 @@ module Hecks
207
102
  end
208
103
 
209
104
  # Emits the JSON form of one boolean-position Evaluator node.
210
- #
211
- # @param node [Bluebook::Expression::Evaluator::Or,
212
- # Bluebook::Expression::Evaluator::And,
213
- # Bluebook::Expression::Evaluator::Not,
214
- # Bluebook::Expression::Evaluator::Compare,
215
- # Bluebook::Expression::Evaluator::Include,
216
- # Bluebook::Expression::Evaluator::Resolve] the boolean-position
217
- # AST node to emit
218
- # @return [Hash] the JSON AST for `node`, tagged by `"op"`
219
- # @raise [RuntimeError] if `node` is not one of the handled classes
220
105
  def emit_bool(node)
221
106
  case node
222
107
  when Evaluator::Or then { "op" => "or", "left" => emit_bool(node.left), "right" => emit_bool(node.right) }
@@ -236,36 +121,16 @@ module Hecks
236
121
  end
237
122
 
238
123
  # Emits the JSON form of one comparison operator.
239
- #
240
- # @param comparator [Bluebook::Expression::Evaluator::Operator] the
241
- # operator to emit
242
- # @return [Hash{String => Boolean}] `"less_than"`, `"equal"`, and
243
- # `"negated"`, exactly as `comparator` carries them
244
124
  def emit_comparison(comparator)
245
125
  { "less_than" => comparator.compares_less_than, "equal" => comparator.compares_equal, "negated" => comparator.negated }
246
126
  end
247
127
 
248
- # The JSON-target sibling of `expr_emitter.rb`'s own
249
- # `emit_include` — see that method's own comment for the full
250
- # reasoning (a literal array haystack has no `Expr::Include`-
251
- # representable shape on either target, kernel or host, so both
252
- # rewrite it identically into an or-of-equalities at emission
253
- # time rather than carrying a shape neither interpreter could
254
- # evaluate). A non-literal haystack still emits `include`
255
- # unchanged.
128
+ # Mirrors `expr_emitter.rb`'s `emit_include`: no target can represent a literal-array
129
+ # haystack in `include`, so it is rewritten to an or of equalities at emission.
256
130
  EQ = Evaluator::OPERATORS.find { |op| op.symbol == "==" }
257
131
  private_constant :EQ
258
132
 
259
- # Emits the JSON form of one `include` node.
260
- #
261
- # @param node [Bluebook::Expression::Evaluator::Include] the
262
- # `include` node to emit
263
- # @return [Hash] the JSON AST: `{"op" => "include", ...}` for a
264
- # non-literal haystack, `{"op" => "bool", "value" => false}` for
265
- # an empty literal array haystack, or an `"or"`-of-`"compare"`
266
- # tree of equalities for a non-empty literal array haystack
267
- # @raise [RuntimeError] if `node.haystack`, `node.needle`, or one of
268
- # the haystack's literal elements is an unhandled resolver node
133
+ # Emits one `include` node; an empty literal haystack is `false`.
269
134
  def emit_include(node)
270
135
  return { "op" => "include", "haystack" => emit_resolver(node.haystack), "needle" => emit_resolver(node.needle) } \
271
136
  unless node.haystack.is_a?(Resolver::ArrayLiteral)
@@ -279,38 +144,8 @@ module Hecks
279
144
  equalities.reduce { |left, right| { "op" => "or", "left" => left, "right" => right } }
280
145
  end
281
146
 
282
- # One case arm per Resolver node type — the class header above is
283
- # explicit that this dispatch must stay complete and in one place
284
- # ("every node this grammar admits gets a real arm"); splitting it
285
- # into several methods would hide whether the set is still
286
- # exhaustive instead of making that visible at a glance.
287
- #
288
- # @param node [Bluebook::Expression::Resolver::IntegerLiteral,
289
- # Bluebook::Expression::Resolver::FloatLiteral,
290
- # Bluebook::Expression::Resolver::StringLiteral,
291
- # Bluebook::Expression::Resolver::BoolLiteral,
292
- # Bluebook::Expression::Resolver::NilLiteral,
293
- # Bluebook::Expression::Resolver::Lookup,
294
- # Bluebook::Expression::Resolver::Addition,
295
- # Bluebook::Expression::Resolver::SignTest,
296
- # Bluebook::Expression::Resolver::Empty,
297
- # Bluebook::Expression::Resolver::ToS,
298
- # Bluebook::Expression::Resolver::Modulo,
299
- # Bluebook::Expression::Resolver::Size,
300
- # Bluebook::Expression::Resolver::BlockPredicate,
301
- # Bluebook::Expression::Resolver::Find,
302
- # Bluebook::Expression::Resolver::ArrayLiteral,
303
- # Bluebook::Expression::Resolver::MatchesRegex,
304
- # Bluebook::Expression::Resolver::Presence,
305
- # Bluebook::Expression::Resolver::Assignment,
306
- # Bluebook::Expression::Resolver::Split,
307
- # Bluebook::Expression::Resolver::StartsWith,
308
- # Bluebook::Expression::Resolver::EndsWith,
309
- # Bluebook::Expression::Resolver::First,
310
- # Bluebook::Expression::Resolver::Last] the value-position AST
311
- # node to emit
312
- # @return [Hash] the JSON AST for `node`, tagged by `"op"`
313
- # @raise [RuntimeError] if `node` is not one of the handled classes
147
+ # One case arm per Resolver node type, kept in one method so exhaustiveness shows at a
148
+ # glance.
314
149
  # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
315
150
  def emit_resolver(node)
316
151
  case node
@@ -319,8 +154,7 @@ module Hecks
319
154
  when Resolver::StringLiteral then { "op" => "str", "value" => node.value }
320
155
  when Resolver::BoolLiteral then { "op" => "bool", "value" => node.value }
321
156
  when Resolver::NilLiteral then { "op" => "nil" }
322
- # `path` is the same shape `find.path` already has — segments, not
323
- # a dotted string a reader would have to split by its own rule.
157
+ # Segments, as `find.path` has, so a reader need not split a dotted string.
324
158
  when Resolver::Lookup then { "op" => "lookup", "path" => node.path.split(".") }
325
159
  when Resolver::Addition then { "op" => "add", "left" => emit_resolver(node.left), "right" => emit_resolver(node.right) }
326
160
  when Resolver::SignTest
@@ -4,41 +4,20 @@ require_relative "resolver"
4
4
  module Hecks
5
5
  module Bluebook
6
6
  module Expression
7
- # The inverse of `AstJson` — reads the `"op"`-tagged JSON a rule row
8
- # carries as `ast` back into the same `Evaluator`/`Resolver` node
9
- # Structs `Evaluator.parse` builds from `canonical`. This is how the
10
- # runtime evaluates a rule without re-parsing its text: the one
11
- # parse happened at DSL-build time, behind `AstJson`; dispatch walks
12
- # the structured form (`Evaluator.call_rule`), and `canonical` stays
13
- # what it always displayed as — text for humans and refusal wording.
14
- #
15
- # Written as the obvious mirror of `ast_json.rb`'s two walkers, arm
16
- # for arm, so a new op there is a new arm here and
17
- # spec/expression_ast_spec.rb's roster contract fails until it
18
- # lands. Promoted from spec/support once the runtime switched —
19
- # the spec proved the round trip first, then the runtime adopted it.
20
- #
21
- # One deliberate asymmetry, inherited: `AstJson` rewrites a
22
- # literal-array `.include?` into an or of equalities (see
23
- # `emit_include`), so reading never produces an `Include` over an
24
- # `ArrayLiteral`. Evaluation is unchanged by that rewrite, which is
25
- # exactly what the equivalence spec pins.
7
+ # The inverse of `AstJson`: reads a rule row's `"op"`-tagged `ast` back into the
8
+ # `Evaluator`/`Resolver` nodes, so dispatch evaluates a rule without re-parsing its text.
26
9
  module AstReader
27
10
  module_function
28
11
 
29
- # Reads a whole predicate's own `"op"`-tagged JSON back into its AST.
30
- #
31
- # @param json [Hash{String => Object}] the `"op"`-tagged JSON a rule row's own
32
- # `ast` carries
33
- # @return [Evaluator::Or, Evaluator::And, Evaluator::Not, Evaluator::Compare,
34
- # Evaluator::Include, Evaluator::Resolve] the boolean/comparison AST node
12
+ # Arm for arm with `AstJson`: a new op there fails spec/expression_ast_spec.rb until added
13
+ # here.
14
+ # A literal-array `.include?` is emitted as an or of equalities, so it never reads back as
15
+ # an `Include` over an `ArrayLiteral`; evaluation is unchanged.
16
+
17
+ # Reads a whole predicate's `"op"`-tagged JSON back into its AST.
35
18
  def read_predicate(json) = read_bool(json)
36
19
 
37
20
  # Reads one boolean/comparison node, recursing into its own children.
38
- #
39
- # @param json [Hash{String => Object}] the `"op"`-tagged JSON for one node
40
- # @return [Evaluator::Or, Evaluator::And, Evaluator::Not, Evaluator::Compare,
41
- # Evaluator::Include, Evaluator::Resolve] the boolean/comparison AST node
42
21
  def read_bool(json)
43
22
  case json.fetch("op")
44
23
  when "or" then Evaluator::Or.new(left: read_bool(json["left"]), right: read_bool(json["right"]))
@@ -53,14 +32,8 @@ module Hecks
53
32
  end
54
33
  end
55
34
 
56
- # The comparator algebra travels as its three-flag triple; the
57
- # `Operator` carrying that exact triple is the one `parse` would
58
- # have chosen, because the roster (`expression/projection.json`)
59
- # holds one symbol per triple.
60
- #
61
- # @param cmp [Hash{String => Boolean}] `{"less_than"=>, "equal"=>, "negated"=>}`
62
- # @return [Evaluator::Operator] the operator carrying that exact triple
63
- # @raise [RuntimeError] if no operator in `Evaluator::OPERATORS` has that triple
35
+ # The roster (`expression/projection.json`) holds one symbol per flag triple,
36
+ # so the triple identifies the operator `parse` would have chosen.
64
37
  def operator(cmp)
65
38
  Evaluator::OPERATORS.find do |op|
66
39
  op.compares_less_than == cmp.fetch("less_than") &&
@@ -70,14 +43,6 @@ module Hecks
70
43
  end
71
44
 
72
45
  # Reads one dotted/arithmetic leaf node, recursing into its own children.
73
- #
74
- # @param json [Hash{String => Object}] the `"op"`-tagged JSON for one leaf node
75
- # @return [Object] a `Resolver` AST node — one of `IntegerLiteral`, `FloatLiteral`,
76
- # `StringLiteral`, `BoolLiteral`, `NilLiteral`, `ArrayLiteral`, `Lookup`,
77
- # `Addition`, `SignTest`, `Empty`, `ToS`, `Modulo`, `Size`, `First`, `Last`,
78
- # `BlockPredicate`, `Find`, `MatchesRegex`, `Presence`, `Assignment`, `Split`,
79
- # `StartsWith`, or `EndsWith`
80
- # @raise [RuntimeError] if `json["op"]` names no known resolver op
81
46
  # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity -- one arm
82
47
  # per AstJson op is the point; splitting the case would hide the roster.
83
48
  def read_resolver(json)
@@ -118,15 +83,8 @@ module Hecks
118
83
  end
119
84
  # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
120
85
 
121
- # `SignTest#test` is only wording (the refusal names it); the
122
- # triple is what evaluates. Recover the spelling from the
123
- # vocabulary so a rebuilt node refuses with the same message the
124
- # parsed one would.
125
- #
126
- # @param operator [Evaluator::Operator] the comparison operator a sign test's own
127
- # triple resolved to
128
- # @return [String] the sign test's own vocabulary name, or `operator.symbol` when
129
- # no sign test shares its triple
86
+ # `SignTest#test` is only refusal wording; recovering it keeps a rebuilt node's message
87
+ # identical to the parsed one's.
130
88
  def sign_test_name(operator)
131
89
  Resolver::SIGN_TEST_OPERATORS.key(operator.symbol) || operator.symbol
132
90
  end
@@ -4,35 +4,21 @@ require_relative "../../vocabulary"
4
4
  module Hecks
5
5
  module Bluebook
6
6
  module Expression
7
- # Rewrites a predicate's source text into one canonical spelling
8
- # before it's ever parsed or hashed — collapsing whitespace and
9
- # folding admitted synonyms (e.g. `.length` → `.size`) via the
10
- # `RULES` table projected from the grammar chapter, so two byte-
11
- # different but equivalent predicates compare and cache identically.
12
- # Never touches text inside quoted string literals
13
- # (`map_outside_strings`).
7
+ # Rewrites predicate source into one canonical spelling (whitespace, admitted synonyms)
8
+ # so equivalent predicates compare and hash alike. Quoted string literals are left alone.
14
9
  module CanonicalForm
15
10
  Rule = Struct.new(:strategy, :source_token, :replacement, :boundary, :position, keyword_init: true)
16
11
 
17
12
  STRATEGIES = Hecks::Vocabulary.fetch("NormalisationStrategy")
18
13
 
19
- # **Read, not restated** — the admitted normalisation rules, projected
20
- # from the grammar chapter by bin/expression_projection exactly as
21
- # the evaluator's operator table is. See Evaluator::PROJECTION for
22
- # why a projection rather than a boot.
14
+ # Normalisation rules projected from the grammar chapter by bin/expression_projection.
23
15
  RULES = JSON.parse(
24
16
  File.read(File.join(__dir__, "projection.json")), symbolize_names: true
25
17
  ).fetch(:normalisations).map { |row| Rule.new(**row) }.freeze
26
18
 
27
19
  module_function
28
20
 
29
- # Returns the admitted normalisation rules as plain Hashes, for
30
- # embedding in a bluebook's read-back contract.
31
- #
32
- # @return [Array<Hash{Symbol => String}>] one hash per rule, in
33
- # declared `position` order, with keys `:strategy`,
34
- # `:source_token`, `:replacement`, `:boundary`, and `:position`
35
- # (stringified)
21
+ # The normalisation rules as plain Hashes, in `position` order, for a read-back contract.
36
22
  def table
37
23
  RULES.sort_by(&:position).map do |rule|
38
24
  {
@@ -45,25 +31,11 @@ module Hecks
45
31
  end
46
32
  end
47
33
 
48
- # Rewrites `source` into its canonical spelling by applying every
49
- # admitted `RULES` entry, in declared `position` order.
50
- #
51
- # @param source [String] the raw predicate source to normalise
52
- # @return [String] the canonicalised text, stripped of leading and
53
- # trailing whitespace
34
+ # Applies every `RULES` entry to `source` in `position` order; returns the stripped result.
54
35
  def apply(source)
55
36
  RULES.sort_by(&:position).reduce(source.to_s) { |text, rule| step(text, rule) }.strip
56
37
  end
57
38
 
58
- # Applies one normalisation `rule` to `text`, dispatching on the
59
- # rule's strategy.
60
- #
61
- # @param text [String] the source text (or string-literal segment) to
62
- # transform
63
- # @param rule [Rule] the normalisation rule to apply
64
- # @return [String] `text` after the rule is applied
65
- # @raise [ArgumentError] if `rule.strategy` names a strategy this
66
- # method does not implement
67
39
  def step(text, rule)
68
40
  case rule.strategy
69
41
  when "collapse_whitespace" then map_outside_strings(text) { |segment| segment.gsub(/\s+/, " ") }
@@ -73,14 +45,6 @@ module Hecks
73
45
  end
74
46
  end
75
47
 
76
- # Substitutes every occurrence of `rule.source_token` in `text` with
77
- # `rule.replacement`, honoring the rule's word-boundary setting.
78
- #
79
- # @param text [String] the source text (or string-literal segment) to
80
- # transform
81
- # @param rule [Rule] the `"replace"`-strategy rule supplying the
82
- # token, replacement, and boundary
83
- # @return [String] `text` with the substitution applied
84
48
  def replace(text, rule)
85
49
  map_outside_strings(text) do |segment|
86
50
  if rule.boundary == "none"
@@ -91,30 +55,9 @@ module Hecks
91
55
  end
92
56
  end
93
57
 
94
- # Applies the given block to the text outside quoted string literals
95
- # only, copying every quoted run through byte-for-byte. Every rule
96
- # here (collapse_whitespace, the `.length`→`.size` fold) must stay
97
- # quote-aware: run it quote-blind and `"a b"` collapses to `"a b"`,
98
- # `"a.length"` folds to `"a.size"`, just as readily as the real
99
- # source outside the quotes — silently rewriting what a predicate
100
- # compares a string attribute against, not merely how the predicate
101
- # itself is spelled. A canonical string literal's contents are data,
102
- # never syntax to normalise.
103
- #
104
- # Handles both `"` and `'` delimiters (this grammar's own
105
- # `Resolver.quoted?` admits either), quote-aware exactly the way
106
- # `Evaluator.top_level_index`/`Resolver.array_elements` already are
107
- # elsewhere in this sublanguage. An unterminated quote (malformed
108
- # input) is passed through raw rather than risk mangling it further.
109
- #
110
- # @param text [String] the source text to scan for quoted string
111
- # literals
112
- # @yieldparam segment [String] a run of `text` outside any quoted
113
- # literal
114
- # @yieldreturn [String] the transformed replacement for `segment`
115
- # @return [String] `text` with the block applied to every unquoted
116
- # run, and every quoted run (`"`- or `'`-delimited) copied through
117
- # unchanged
58
+ # Yields each run of `text` outside quoted literals, copying quoted runs through verbatim.
59
+ # Quote-blind rewriting would change what a predicate compares a string against.
60
+ # Handles `"` and `'`; an unterminated quote is passed through raw.
118
61
  def map_outside_strings(text)
119
62
  result = +""
120
63
  buffer = +""
@@ -130,29 +73,9 @@ module Hecks
130
73
  end
131
74
  elsif ['"', "'"].include?(char)
132
75
  result << yield(buffer)
133
- # `char.dup`, not `char.to_s` (a no-op on a String — always
134
- # returns self, never a copy) and not `+char` either
135
- # (`String#+@` only dups a frozen receiver; `each_char`'s
136
- # yielded strings aren't frozen, so `+char` is just as
137
- # much a no-op here). Without a real copy, `buffer` and
138
- # `quote` alias the same mutable object: the very next
139
- # `buffer << char` grows `quote` right along with it, so
140
- # `char == quote` can only ever compare a single character
141
- # against an ever-lengthening string and never closes the
142
- # literal — everything after a predicate's first quoted
143
- # string silently skipped normalisation for the rest of
144
- # the text, undetected because passing text through
145
- # unnormalised is silent. Masked by every existing spec
146
- # here, which only checks that quoted contents survive
147
- # untouched (the M7 fix this method exists for) — that
148
- # still holds by accident once the bug makes the "outside"
149
- # branch unreachable. Found live: a multi-line `given`/
150
- # `ensures` block whose only quoted literal closes before a
151
- # later line — the newline and that later line's own
152
- # indentation went uncollapsed, diverging from
153
- # `hecks-parse`'s own (correct) single-space join.
154
- # `spec/parser_parity_spec.rb`, roster's "a front-row seat
155
- # takes a member of age".
76
+ # Must be a real copy: aliasing `buffer` and `quote` makes `buffer << char` grow
77
+ # `quote`, so the literal never closes and later text skips normalisation.
78
+ # Pinned by spec/parser_parity_spec.rb (multi-line `given`/`ensures`).
156
79
  buffer = char.dup
157
80
  quote = char.dup
158
81
  else