hecks 0.2.0 → 0.3.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 (408) hide show
  1. checksums.yaml +5 -5
  2. data/lib/hecks/adapters/driven/claude_code.adapter +3 -0
  3. data/lib/hecks/adapters/driven/claude_code.rb +127 -0
  4. data/lib/hecks/adapters/driven/d1.adapter +12 -0
  5. data/lib/hecks/adapters/driven/d1.rb +403 -0
  6. data/lib/hecks/adapters/driven/folder.adapter +3 -0
  7. data/lib/hecks/adapters/driven/folder.rb +199 -0
  8. data/lib/hecks/adapters/driven/google_authentication.adapter +3 -0
  9. data/lib/hecks/adapters/driven/google_authentication.rb +100 -0
  10. data/lib/hecks/adapters/driven/governance_authorization.adapter +3 -0
  11. data/lib/hecks/adapters/driven/governance_authorization.rb +62 -0
  12. data/lib/hecks/adapters/driven/heki/journal.rb +51 -0
  13. data/lib/hecks/adapters/driven/heki/saga_store.rb +0 -0
  14. data/lib/hecks/adapters/driven/heki/snapshot.rb +38 -0
  15. data/lib/hecks/adapters/driven/heki.adapter +4 -0
  16. data/lib/hecks/adapters/driven/heki.rb +148 -0
  17. data/lib/hecks/adapters/driven/identity_registry.adapter +3 -0
  18. data/lib/hecks/adapters/driven/identity_registry.rb +26 -0
  19. data/lib/hecks/adapters/driven/in_memory_ordering.rb +51 -0
  20. data/lib/hecks/adapters/driven/lambda/client.rb +63 -0
  21. data/lib/hecks/adapters/driven/lambda.adapter +4 -0
  22. data/lib/hecks/adapters/driven/lambda.rb +131 -0
  23. data/lib/hecks/adapters/driven/memory.adapter +3 -0
  24. data/lib/hecks/adapters/driven/memory.rb +103 -0
  25. data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +3 -0
  26. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +28 -0
  27. data/lib/hecks/adapters/driven/postgres/codec.rb +80 -0
  28. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +168 -0
  29. data/lib/hecks/adapters/driven/postgres.adapter +5 -0
  30. data/lib/hecks/adapters/driven/postgres.rb +391 -0
  31. data/lib/hecks/adapters/driven/postgres_era/lineage/era_store.rb +171 -0
  32. data/lib/hecks/adapters/driven/postgres_era/lineage/field_cache.rb +190 -0
  33. data/lib/hecks/adapters/driven/postgres_era/lineage/head_compiler.rb +429 -0
  34. data/lib/hecks/adapters/driven/postgres_era/lineage/mint_transaction.rb +166 -0
  35. data/lib/hecks/adapters/driven/postgres_era/lineage/provisioning.rb +286 -0
  36. data/lib/hecks/adapters/driven/postgres_era/lineage/resumable_backfill.rb +168 -0
  37. data/lib/hecks/adapters/driven/postgres_era/lineage/tail_merge.rb +163 -0
  38. data/lib/hecks/adapters/driven/postgres_era/lineage/transform_installer.rb +114 -0
  39. data/lib/hecks/adapters/driven/postgres_era/lineage.rb +137 -0
  40. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/coverage_check.rb +89 -0
  41. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/era_resolver.rb +82 -0
  42. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/merge_coordinator.rb +43 -0
  43. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/minter.rb +132 -0
  44. data/lib/hecks/adapters/driven/postgres_era/lineage_manager.rb +74 -0
  45. data/lib/hecks/adapters/driven/postgres_era.adapter +6 -0
  46. data/lib/hecks/adapters/driven/postgres_era.rb +671 -0
  47. data/lib/hecks/adapters/driven/prism.adapter +3 -0
  48. data/lib/hecks/adapters/driven/prism.rb +80 -0
  49. data/lib/hecks/adapters/driven/secure_random_identity.adapter +3 -0
  50. data/lib/hecks/adapters/driven/secure_random_identity.rb +14 -0
  51. data/lib/hecks/adapters/driven/sql_query_builder.rb +221 -0
  52. data/lib/hecks/adapters/driven/sqlite/codec.rb +63 -0
  53. data/lib/hecks/adapters/driven/sqlite/projection.rb +105 -0
  54. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +180 -0
  55. data/lib/hecks/adapters/driven/sqlite.adapter +9 -0
  56. data/lib/hecks/adapters/driven/sqlite.rb +285 -0
  57. data/lib/hecks/adapters/driven/system_clock.adapter +3 -0
  58. data/lib/hecks/adapters/driven/system_clock.rb +14 -0
  59. data/lib/hecks/adapters/driven.rb +36 -0
  60. data/lib/hecks/adapters.rb +6 -0
  61. data/lib/hecks/behaviors/dsl.rb +96 -0
  62. data/lib/hecks/behaviors/expectations.rb +280 -0
  63. data/lib/hecks/behaviors/ir.rb +31 -0
  64. data/lib/hecks/behaviors/rspec.rb +42 -0
  65. data/lib/hecks/behaviors/runner.rb +96 -0
  66. data/lib/hecks/behaviors.rb +25 -0
  67. data/lib/hecks/bluebook/aggregate.rb +108 -0
  68. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +134 -0
  69. data/lib/hecks/bluebook/assembly/build.rb +48 -0
  70. data/lib/hecks/bluebook/assembly/contract.rb +112 -0
  71. data/lib/hecks/bluebook/assembly/contracts.rb +409 -0
  72. data/lib/hecks/bluebook/assembly/marks.rb +227 -0
  73. data/lib/hecks/bluebook/assembly/specializer.rb +70 -0
  74. data/lib/hecks/bluebook/assembly.rb +78 -0
  75. data/lib/hecks/bluebook/attribute.rb +96 -0
  76. data/lib/hecks/bluebook/behaviour/aggregate.rb +83 -0
  77. data/lib/hecks/bluebook/behaviour/attribute.rb +27 -0
  78. data/lib/hecks/bluebook/behaviour/chapter.rb +72 -0
  79. data/lib/hecks/bluebook/behaviour/command.rb +116 -0
  80. data/lib/hecks/bluebook/behaviour/domain_port.rb +25 -0
  81. data/lib/hecks/bluebook/behaviour/entity.rb +59 -0
  82. data/lib/hecks/bluebook/behaviour/hexagon.rb +50 -0
  83. data/lib/hecks/bluebook/behaviour/lifecycle.rb +51 -0
  84. data/lib/hecks/bluebook/behaviour/policy.rb +52 -0
  85. data/lib/hecks/bluebook/behaviour/process_manager.rb +38 -0
  86. data/lib/hecks/bluebook/behaviour/query.rb +10 -0
  87. data/lib/hecks/bluebook/behaviour/read_model.rb +29 -0
  88. data/lib/hecks/bluebook/behaviour/traits.rb +81 -0
  89. data/lib/hecks/bluebook/behaviour/value_object.rb +33 -0
  90. data/lib/hecks/bluebook/chapter.rb +68 -0
  91. data/lib/hecks/bluebook/command.rb +124 -0
  92. data/lib/hecks/bluebook/domain_port.rb +102 -0
  93. data/lib/hecks/bluebook/dsl/adapter_builder.rb +34 -0
  94. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +906 -0
  95. data/lib/hecks/bluebook/dsl/attribute_collector.rb +327 -0
  96. data/lib/hecks/bluebook/dsl/binding_proxy.rb +71 -0
  97. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +1018 -0
  98. data/lib/hecks/bluebook/dsl/command_builder.rb +626 -0
  99. data/lib/hecks/bluebook/dsl/const_shim.rb +81 -0
  100. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +79 -0
  101. data/lib/hecks/bluebook/dsl/entity_builder.rb +305 -0
  102. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +366 -0
  103. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +163 -0
  104. data/lib/hecks/bluebook/dsl/identity_declaration.rb +191 -0
  105. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +44 -0
  106. data/lib/hecks/bluebook/dsl/malformed.rb +7 -0
  107. data/lib/hecks/bluebook/dsl/policy_builder.rb +120 -0
  108. data/lib/hecks/bluebook/dsl/port_builder.rb +30 -0
  109. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +142 -0
  110. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +207 -0
  111. data/lib/hecks/bluebook/dsl/query_builder.rb +113 -0
  112. data/lib/hecks/bluebook/dsl/read_model_builder.rb +233 -0
  113. data/lib/hecks/bluebook/dsl/rule_reference.rb +172 -0
  114. data/lib/hecks/bluebook/dsl/translation_builder.rb +243 -0
  115. data/lib/hecks/bluebook/dsl/value_object_builder.rb +178 -0
  116. data/lib/hecks/bluebook/dsl/word_gate.rb +221 -0
  117. data/lib/hecks/bluebook/dsl/world_builder.rb +77 -0
  118. data/lib/hecks/bluebook/dsl.rb +45 -0
  119. data/lib/hecks/bluebook/entity.rb +103 -0
  120. data/lib/hecks/bluebook/expression/canonical_form.rb +55 -0
  121. data/lib/hecks/bluebook/expression/evaluator.rb +261 -0
  122. data/lib/hecks/bluebook/expression/projection.json +170 -0
  123. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +233 -0
  124. data/lib/hecks/bluebook/expression/resolver.rb +630 -0
  125. data/lib/hecks/bluebook/expression.rb +13 -0
  126. data/lib/hecks/bluebook/hexagon.rb +60 -0
  127. data/lib/hecks/bluebook/lifecycle.rb +42 -0
  128. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +54 -0
  129. data/lib/hecks/bluebook/meta_validator/judge.rb +559 -0
  130. data/lib/hecks/bluebook/meta_validator/plan.rb +332 -0
  131. data/lib/hecks/bluebook/meta_validator/port_judge.rb +47 -0
  132. data/lib/hecks/bluebook/meta_validator/readings.rb +350 -0
  133. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +312 -0
  134. data/lib/hecks/bluebook/meta_validator/shapes.rb +246 -0
  135. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +255 -0
  136. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +138 -0
  137. data/lib/hecks/bluebook/meta_validator/world_judge.rb +78 -0
  138. data/lib/hecks/bluebook/meta_validator.rb +452 -0
  139. data/lib/hecks/bluebook/model_check.rb +318 -0
  140. data/lib/hecks/bluebook/pattern_subset.rb +120 -0
  141. data/lib/hecks/bluebook/policy.rb +41 -0
  142. data/lib/hecks/bluebook/process_manager.rb +86 -0
  143. data/lib/hecks/bluebook/project_discovery.rb +30 -0
  144. data/lib/hecks/bluebook/project_loader.rb +40 -0
  145. data/lib/hecks/bluebook/project_register.rb +82 -0
  146. data/lib/hecks/bluebook/query.rb +61 -0
  147. data/lib/hecks/bluebook/read_model.rb +109 -0
  148. data/lib/hecks/bluebook/reference.rb +74 -0
  149. data/lib/hecks/bluebook/smoke_test.rb +166 -0
  150. data/lib/hecks/bluebook/synthesizer.rb +95 -0
  151. data/lib/hecks/bluebook/translation.rb +92 -0
  152. data/lib/hecks/bluebook/value_object.rb +58 -0
  153. data/lib/hecks/bluebook.rb +74 -0
  154. data/lib/hecks/codemod.rb +342 -0
  155. data/lib/hecks/construct.rb +71 -0
  156. data/lib/hecks/deploy/bluebook/deploy.bluebook +219 -0
  157. data/lib/hecks/deploy/bluebook/deploy.hecksagon +4 -0
  158. data/lib/hecks/deploy/oidc.json +18 -0
  159. data/lib/hecks/doc/reference.rb +389 -0
  160. data/lib/hecks/embryonaut_bluebook.rb +75 -0
  161. data/lib/hecks/facade/cli_door.rb +116 -0
  162. data/lib/hecks/facade/cli_runner.rb +190 -0
  163. data/lib/hecks/facade/command_request.rb +105 -0
  164. data/lib/hecks/facade/handle.rb +173 -0
  165. data/lib/hecks/facade/json_door.rb +154 -0
  166. data/lib/hecks/facade/surface/aggregate_door.rb +185 -0
  167. data/lib/hecks/facade/surface/chapter.rb +107 -0
  168. data/lib/hecks/facade/surface.rb +48 -0
  169. data/lib/hecks/facade.rb +44 -0
  170. data/lib/hecks/forms/app.rb +300 -0
  171. data/lib/hecks/forms/command_form_renderer.rb +113 -0
  172. data/lib/hecks/forms/examples/banking_console.bluebook +3 -0
  173. data/lib/hecks/forms/field_renderer.rb +170 -0
  174. data/lib/hecks/forms/field_shape.rb +232 -0
  175. data/lib/hecks/forms/html.rb +53 -0
  176. data/lib/hecks/forms/index_renderer.rb +35 -0
  177. data/lib/hecks/forms/page.rb +157 -0
  178. data/lib/hecks/forms/params.rb +132 -0
  179. data/lib/hecks/forms/query_form_renderer.rb +114 -0
  180. data/lib/hecks/forms/record_renderer.rb +115 -0
  181. data/lib/hecks/forms/record_table.rb +63 -0
  182. data/lib/hecks/forms/reference_options.rb +30 -0
  183. data/lib/hecks/forms/value_object_shape.rb +46 -0
  184. data/lib/hecks/forms.rb +54 -0
  185. data/lib/hecks/fqn.rb +94 -0
  186. data/lib/hecks/framework/bluebook/compliance.bluebook +1 -0
  187. data/lib/hecks/framework/bluebook/console_settings.bluebook +489 -0
  188. data/lib/hecks/framework/bluebook/framework.hecksagon +32 -0
  189. data/lib/hecks/framework/bluebook/governance.bluebook +130 -0
  190. data/lib/hecks/framework/bluebook/identity.bluebook +90 -0
  191. data/lib/hecks/framework/oidc.json +39 -0
  192. data/lib/hecks/framework.rb +90 -0
  193. data/lib/hecks/freezer.rb +67 -0
  194. data/lib/hecks/fuzzing/invalid_value_generator.rb +86 -0
  195. data/lib/hecks/fuzzing/isolated_boot.rb +92 -0
  196. data/lib/hecks/fuzzing/properties.rb +1146 -0
  197. data/lib/hecks/fuzzing/replay.rb +635 -0
  198. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +93 -0
  199. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +54 -0
  200. data/lib/hecks/fuzzing/sequence_generator/picker.rb +96 -0
  201. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +185 -0
  202. data/lib/hecks/fuzzing/sequence_generator.rb +130 -0
  203. data/lib/hecks/fuzzing/value_generator.rb +177 -0
  204. data/lib/hecks/fuzzing.rb +12 -0
  205. data/lib/hecks/grammar/evolve.rb +291 -0
  206. data/lib/hecks/grammar/expression.bluebook +417 -0
  207. data/lib/hecks/grammar/expression_operators.json +1152 -0
  208. data/lib/hecks/grammar/grammar.hecksagon +20 -0
  209. data/lib/hecks/grammar/oidc.json +69 -0
  210. data/lib/hecks/grammar/translation.bluebook +203 -0
  211. data/lib/hecks/grammar.rb +178 -0
  212. data/lib/hecks/ir.rb +126 -0
  213. data/lib/hecks/language/adapter.bluebook +116 -0
  214. data/lib/hecks/language/bluebook/aggregate.bluebook +581 -0
  215. data/lib/hecks/language/bluebook/attaches/paging.bluebook +76 -0
  216. data/lib/hecks/language/bluebook/bluebook.bluebook +256 -0
  217. data/lib/hecks/language/bluebook/bluebook.hecksagon +24 -0
  218. data/lib/hecks/language/bluebook/command.bluebook +437 -0
  219. data/lib/hecks/language/bluebook/entity.bluebook +316 -0
  220. data/lib/hecks/language/bluebook/policy.bluebook +172 -0
  221. data/lib/hecks/language/bluebook/process_manager.bluebook +264 -0
  222. data/lib/hecks/language/bluebook/projection.bluebook +267 -0
  223. data/lib/hecks/language/bluebook/query.bluebook +245 -0
  224. data/lib/hecks/language/bluebook/shape.bluebook +292 -0
  225. data/lib/hecks/language/bluebook/syntax.bluebook +447 -0
  226. data/lib/hecks/language/bluebook/vocabulary.bluebook +364 -0
  227. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +51 -0
  228. data/lib/hecks/language/hecksagon/domain_port.bluebook +76 -0
  229. data/lib/hecks/language/hecksagon/hecksagon.bluebook +131 -0
  230. data/lib/hecks/language/hecksagon/port_operation.bluebook +102 -0
  231. data/lib/hecks/language/oidc.json +313 -0
  232. data/lib/hecks/language/port.bluebook +92 -0
  233. data/lib/hecks/language/translation/translation.bluebook +110 -0
  234. data/lib/hecks/language/translation/translation_aggregate.bluebook +267 -0
  235. data/lib/hecks/language/world/wiring.bluebook +62 -0
  236. data/lib/hecks/language/world/world.bluebook +84 -0
  237. data/lib/hecks/literal.rb +125 -0
  238. data/lib/hecks/naming.rb +121 -0
  239. data/lib/hecks/ports/access_control.port +4 -0
  240. data/lib/hecks/ports/access_control.rb +62 -0
  241. data/lib/hecks/ports/agent/answers.rb +104 -0
  242. data/lib/hecks/ports/agent.port +4 -0
  243. data/lib/hecks/ports/agent.rb +167 -0
  244. data/lib/hecks/ports/authentication.port +4 -0
  245. data/lib/hecks/ports/authentication.rb +50 -0
  246. data/lib/hecks/ports/authorization.port +4 -0
  247. data/lib/hecks/ports/authorization.rb +59 -0
  248. data/lib/hecks/ports/clock.port +4 -0
  249. data/lib/hecks/ports/clock.rb +62 -0
  250. data/lib/hecks/ports/extraction.port +4 -0
  251. data/lib/hecks/ports/extraction.rb +37 -0
  252. data/lib/hecks/ports/identity_assignment.port +4 -0
  253. data/lib/hecks/ports/identity_assignment.rb +45 -0
  254. data/lib/hecks/ports/identity_generation.port +4 -0
  255. data/lib/hecks/ports/identity_generation.rb +49 -0
  256. data/lib/hecks/ports/identity_resolution.port +4 -0
  257. data/lib/hecks/ports/identity_resolution.rb +40 -0
  258. data/lib/hecks/ports/loading.port +4 -0
  259. data/lib/hecks/ports/loading.rb +13 -0
  260. data/lib/hecks/ports/persistence/append_only.rb +102 -0
  261. data/lib/hecks/ports/persistence/binding_policy.rb +56 -0
  262. data/lib/hecks/ports/persistence/execution.rb +18 -0
  263. data/lib/hecks/ports/persistence/lineage.rb +277 -0
  264. data/lib/hecks/ports/persistence/null_saga_store.rb +25 -0
  265. data/lib/hecks/ports/persistence/remote_runtime.rb +42 -0
  266. data/lib/hecks/ports/persistence/repository_factory.rb +30 -0
  267. data/lib/hecks/ports/persistence.port +4 -0
  268. data/lib/hecks/ports/persistence.rb +39 -0
  269. data/lib/hecks/ports/projection.port +4 -0
  270. data/lib/hecks/ports/projection.rb +76 -0
  271. data/lib/hecks/ports/query/in_memory.rb +60 -0
  272. data/lib/hecks/ports/query/ordering.rb +41 -0
  273. data/lib/hecks/ports/query.rb +36 -0
  274. data/lib/hecks/ports.rb +27 -0
  275. data/lib/hecks/projections/diagrams.rb +413 -0
  276. data/lib/hecks/projections/ir.rb +18 -0
  277. data/lib/hecks/projections/model/deviations.rb +98 -0
  278. data/lib/hecks/projections/model.rb +145 -0
  279. data/lib/hecks/projections/oidc.rb +110 -0
  280. data/lib/hecks/projections/parser_table.rb +159 -0
  281. data/lib/hecks/projections/reference.rb +38 -0
  282. data/lib/hecks/projections/shape.rb +31 -0
  283. data/lib/hecks/projections/statements.rb +110 -0
  284. data/lib/hecks/projections/vocabulary.rb +100 -0
  285. data/lib/hecks/projections.rb +32 -0
  286. data/lib/hecks/projector/cli_projector.rb +437 -0
  287. data/lib/hecks/projector/docs_projector.rb +321 -0
  288. data/lib/hecks/projector/exporter.rb +141 -0
  289. data/lib/hecks/projector/ir_projector.rb +18 -0
  290. data/lib/hecks/projector/narrate_projector.rb +243 -0
  291. data/lib/hecks/projector/target.rb +97 -0
  292. data/lib/hecks/projector.rb +186 -0
  293. data/lib/hecks/query_ir.rb +392 -0
  294. data/lib/hecks/query_specification/common/authorization_spec.rb +9 -0
  295. data/lib/hecks/query_specification/common/comparators.rb +25 -0
  296. data/lib/hecks/query_specification/common/comparison.rb +174 -0
  297. data/lib/hecks/query_specification/common/cursor_spec.rb +9 -0
  298. data/lib/hecks/query_specification/common/dsl.rb +58 -0
  299. data/lib/hecks/query_specification/common/inspection_spec.rb +9 -0
  300. data/lib/hecks/query_specification/common/limit_spec.rb +9 -0
  301. data/lib/hecks/query_specification/common/null_policy.rb +80 -0
  302. data/lib/hecks/query_specification/common/null_semantics.rb +11 -0
  303. data/lib/hecks/query_specification/common/offset_spec.rb +9 -0
  304. data/lib/hecks/query_specification/common/options.rb +32 -0
  305. data/lib/hecks/query_specification/common/order_by.rb +9 -0
  306. data/lib/hecks/query_specification/common/specification.rb +10 -0
  307. data/lib/hecks/query_specification/common/where_clause.rb +9 -0
  308. data/lib/hecks/query_specification/field_path.rb +89 -0
  309. data/lib/hecks/query_specification/hop_path.rb +130 -0
  310. data/lib/hecks/query_specification/read_model/specification.rb +18 -0
  311. data/lib/hecks/query_specification.rb +14 -0
  312. data/lib/hecks/rendering.rb +48 -0
  313. data/lib/hecks/router/namespace_installer.rb +157 -0
  314. data/lib/hecks/router.rb +70 -0
  315. data/lib/hecks/runtime/caller.rb +50 -0
  316. data/lib/hecks/runtime/capability_graph.rb +44 -0
  317. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +124 -0
  318. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +248 -0
  319. data/lib/hecks/runtime/command_interpreter.rb +413 -0
  320. data/lib/hecks/runtime/command_rules/admissibility.rb +307 -0
  321. data/lib/hecks/runtime/command_rules/arithmetic.rb +254 -0
  322. data/lib/hecks/runtime/command_rules/authorization.rb +64 -0
  323. data/lib/hecks/runtime/command_rules/emission.rb +34 -0
  324. data/lib/hecks/runtime/command_rules/references.rb +181 -0
  325. data/lib/hecks/runtime/command_rules.rb +28 -0
  326. data/lib/hecks/runtime/dependency_planning.rb +231 -0
  327. data/lib/hecks/runtime/dispatcher.rb +286 -0
  328. data/lib/hecks/runtime/entity_element.rb +253 -0
  329. data/lib/hecks/runtime/entity_interpreter.rb +228 -0
  330. data/lib/hecks/runtime/era_check.rb +136 -0
  331. data/lib/hecks/runtime/era_guard/shape_diff.rb +128 -0
  332. data/lib/hecks/runtime/era_guard.rb +193 -0
  333. data/lib/hecks/runtime/era_tamper.rb +61 -0
  334. data/lib/hecks/runtime/errors.rb +100 -0
  335. data/lib/hecks/runtime/event.rb +51 -0
  336. data/lib/hecks/runtime/identity.rb +128 -0
  337. data/lib/hecks/runtime/instance.rb +120 -0
  338. data/lib/hecks/runtime/interpreting.rb +69 -0
  339. data/lib/hecks/runtime/loader.rb +143 -0
  340. data/lib/hecks/runtime/policy_interpreter.rb +366 -0
  341. data/lib/hecks/runtime/port_operation_interpreter.rb +210 -0
  342. data/lib/hecks/runtime/query_interpreter.rb +254 -0
  343. data/lib/hecks/runtime/reaction_invocation.rb +245 -0
  344. data/lib/hecks/runtime/read_model_interpreter.rb +266 -0
  345. data/lib/hecks/runtime/rebuild_sweep.rb +74 -0
  346. data/lib/hecks/runtime/reference_hop.rb +99 -0
  347. data/lib/hecks/runtime/refusal_wording.rb +124 -0
  348. data/lib/hecks/runtime/registry/saga_persistence.rb +70 -0
  349. data/lib/hecks/runtime/registry/verification.rb +158 -0
  350. data/lib/hecks/runtime/registry.rb +218 -0
  351. data/lib/hecks/runtime/remote_dispatcher.rb +143 -0
  352. data/lib/hecks/runtime/routing.rb +96 -0
  353. data/lib/hecks/runtime/saga_interpreter/correlation.rb +97 -0
  354. data/lib/hecks/runtime/saga_interpreter.rb +301 -0
  355. data/lib/hecks/runtime/storage_shape.rb +120 -0
  356. data/lib/hecks/runtime/tenant_check.rb +84 -0
  357. data/lib/hecks/runtime/tenant_scope.rb +55 -0
  358. data/lib/hecks/runtime/value/admission.rb +112 -0
  359. data/lib/hecks/runtime/value/coercion.rb +500 -0
  360. data/lib/hecks/runtime/value/invariant_violation.rb +5 -0
  361. data/lib/hecks/runtime/value.rb +125 -0
  362. data/lib/hecks/runtime.rb +95 -0
  363. data/lib/hecks/translation/audit/approval_digest.rb +31 -0
  364. data/lib/hecks/translation/audit/layer_one.rb +37 -0
  365. data/lib/hecks/translation/audit/layer_two.rb +77 -0
  366. data/lib/hecks/translation/audit/unfed_report.rb +42 -0
  367. data/lib/hecks/translation/audit.rb +70 -0
  368. data/lib/hecks/translation/reattest.rb +72 -0
  369. data/lib/hecks/translation/rule_compiler.rb +120 -0
  370. data/lib/hecks/translation/scaffold/differ.rb +183 -0
  371. data/lib/hecks/translation/scaffold/renderer.rb +41 -0
  372. data/lib/hecks/translation/scaffold/writer.rb +28 -0
  373. data/lib/hecks/translation/scaffold.rb +30 -0
  374. data/lib/hecks/translation.rb +11 -0
  375. data/lib/hecks/version.rb +16 -0
  376. data/lib/hecks/vocabulary.rb +213 -0
  377. data/lib/hecks.rb +118 -6
  378. metadata +401 -222
  379. data/bin/hecks +0 -7
  380. data/bin/hecks-package +0 -65
  381. data/bin/hecks_console +0 -12
  382. data/bin/hecks_serverless +0 -6
  383. data/lib/cli/build.rb +0 -14
  384. data/lib/cli/command_runner.rb +0 -28
  385. data/lib/cli/console.rb +0 -10
  386. data/lib/cli/generate.rb +0 -37
  387. data/lib/cli/hecks-cli.rb +0 -27
  388. data/lib/cli/test.rb +0 -57
  389. data/lib/console/commands.rb +0 -8
  390. data/lib/console/hecks-console.rb +0 -1
  391. data/lib/packager/README.md +0 -0
  392. data/lib/packager/app_runner.rb +0 -21
  393. data/lib/packager/args.rb +0 -26
  394. data/lib/packager/compatibility/fixnum.rb +0 -6
  395. data/lib/packager/hecks.rb +0 -39
  396. data/lib/packager/query_runner.rb +0 -21
  397. data/lib/packager/resources/Dockerfile +0 -11
  398. data/lib/packager/resources/app_binary +0 -7
  399. data/lib/packager/resources/bundle_config +0 -3
  400. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz +0 -0
  401. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-osx.tar.gz +0 -0
  402. data/lib/serverless/Domain +0 -32
  403. data/lib/serverless/cli.rb +0 -75
  404. data/lib/serverless/resources/command_name.js +0 -5
  405. data/lib/serverless/resources/environment.js +0 -7
  406. data/lib/serverless/resources/handler.js.tt +0 -28
  407. data/lib/serverless/resources/run_binary.js +0 -22
  408. data/lib/serverless/resources/serverless.yml +0 -20
@@ -0,0 +1,261 @@
1
+ require "json"
2
+ require_relative "../../vocabulary"
3
+
4
+ module Hecks
5
+ module Bluebook
6
+ module Expression
7
+ module Evaluator
8
+ Operator = Struct.new(:symbol, :compares_less_than, :compares_equal, :negated, keyword_init: true)
9
+
10
+ # Six operators, reduced to two primitives (less_than, equal) combined
11
+ # with a small boolean algebra: compares_less_than/compares_equal choose
12
+ # which primitive(s) OR together, negated inverts the result.
13
+ #
14
+ # READ, NOT RESTATED. This table is the checked-in projection of the
15
+ # grammar chapter's admitted set (bin/expression_projection), joined with
16
+ # the algebra Vocabulary::Comparison declares. The evaluator cannot
17
+ # boot the chapter that configures it — the Prism adapter normalises
18
+ # every predicate through CanonicalForm while a bluebook loads — so
19
+ # the projection is how the domain reaches here: regenerated when the
20
+ # ledger changes, held fresh by spec/operators_export_spec.rb, and
21
+ # held to the live machinery by spec/operator_conformance_spec.rb.
22
+ PROJECTION = JSON.parse(
23
+ File.read(File.join(__dir__, "projection.json")), symbolize_names: true
24
+ ).freeze
25
+
26
+ OPERATORS = PROJECTION.fetch(:operators)
27
+ .select { |row| row[:category] == "comparison" }
28
+ .map { |row| Operator.new(**row.slice(:symbol, :compares_less_than, :compares_equal, :negated)) }
29
+ .freeze
30
+
31
+ COMPARISONS = OPERATORS.map(&:symbol).freeze
32
+
33
+ # The boolean/comparison grammar an expression parses into. Leaf nodes
34
+ # (Compare/Include/Resolve) hold already-parsed Resolver ASTs, not raw
35
+ # strings — which branch a leaf's grammar takes is, like the boolean/
36
+ # comparison grammar above it, a pure function of the string. Only the
37
+ # final state/attrs dictionary read, inside Resolver.interpret, varies
38
+ # per call. Parsing the whole tree once and caching it (ast_cache,
39
+ # below) is what makes that safe.
40
+ Or = Struct.new(:left, :right, keyword_init: true)
41
+ And = Struct.new(:left, :right, keyword_init: true)
42
+ Not = Struct.new(:node, keyword_init: true)
43
+ Compare = Struct.new(:operator, :left, :right, keyword_init: true)
44
+ Include = Struct.new(:haystack, :needle, keyword_init: true)
45
+ Resolve = Struct.new(:expr, keyword_init: true)
46
+
47
+ module_function
48
+
49
+ # Keyed by the exact string `call` receives. Canonical text is already
50
+ # normalised at DSL-build time, so the same given/invariant's text is
51
+ # byte-identical across every dispatch that evaluates it — parsed once
52
+ # here, interpreted fresh against each call's own state/attrs. Matches
53
+ # MetaValidator.verdicts' unsynchronized `||= {}` idiom : redundant
54
+ # parse work under real parallelism, never corruption.
55
+ def ast_cache = @ast_cache ||= {}
56
+
57
+ def call(expr, state, attrs = {})
58
+ interpret(ast_cache[expr] ||= parse(expr), state, attrs)
59
+ end
60
+
61
+ def parse(expr)
62
+ expr = strip_parens(expr.to_s.strip)
63
+
64
+ left, right = split_top_level(expr, "||")
65
+ return Or.new(left: parse(left), right: parse(right)) if left
66
+
67
+ left, right = split_top_level(expr, "&&")
68
+ return And.new(left: parse(left), right: parse(right)) if left
69
+
70
+ membership = match_include(expr)
71
+ return Include.new(haystack: Resolver.parse(membership[0]), needle: Resolver.parse(membership[1])) if membership
72
+
73
+ OPERATORS.each do |op|
74
+ left, right = split_comparison(expr, op.symbol)
75
+ return Compare.new(operator: op, left: Resolver.parse(left), right: Resolver.parse(right)) if left
76
+ end
77
+
78
+ return Not.new(node: parse(Regexp.last_match(1))) if expr =~ /\A!(.+)\z/
79
+
80
+ Resolve.new(expr: Resolver.parse(expr))
81
+ end
82
+
83
+ def interpret(node, state, attrs)
84
+ case node
85
+ when Or then interpret(node.left, state, attrs) || interpret(node.right, state, attrs)
86
+ when And then interpret(node.left, state, attrs) && interpret(node.right, state, attrs)
87
+ when Not then !interpret(node.node, state, attrs)
88
+ when Compare then compare(node.operator, node.left, node.right, state, attrs)
89
+ when Include then includes?([node.haystack, node.needle], state, attrs)
90
+ when Resolve then truthy?(Resolver.interpret(node.expr, state, attrs))
91
+ else
92
+ # Every node `parse` can produce has a `when` above — a
93
+ # backstop against the day this grammar grows a new node
94
+ # type and `interpret` doesn't grow to match it. A missing
95
+ # arm here used to return bare `nil`, and `Or`/`And` fold
96
+ # that straight into the boolean algebra as ordinary falsy
97
+ # — reading exactly like "the rule legitimately does not
98
+ # hold" rather than "the runtime cannot evaluate this rule
99
+ # at all", the one silent no-op this language otherwise
100
+ # refuses.
101
+ raise EvaluationError, "no interpreter handles #{node.class} — add a case before parse can produce it"
102
+ end
103
+ end
104
+
105
+ def compare(op, left, right, state, attrs)
106
+ lhs = Resolver.interpret(left, state, attrs)
107
+ rhs = Resolver.interpret(right, state, attrs)
108
+
109
+ apply(op, lhs, rhs)
110
+ end
111
+
112
+ # The algebra itself, on values already resolved — split out so a sign
113
+ # test (SignTest#compares_via names an Operator symbol) can apply the
114
+ # SAME primitives compare() uses against the literal 0, rather than
115
+ # re-deriving positive?/negative?/zero? by hand a second time.
116
+ def apply(op, lhs, rhs)
117
+ result = (op.compares_less_than && less_than(lhs, rhs)) ||
118
+ (op.compares_equal && equal?(lhs, rhs))
119
+ op.negated ? !result : result
120
+ end
121
+
122
+ def less_than(lhs, rhs)
123
+ left = Resolver.numeric(lhs)
124
+ right = Resolver.numeric(rhs)
125
+ return left < right if left && right
126
+ return lhs < rhs if lhs.is_a?(String) && rhs.is_a?(String)
127
+
128
+ raise EvaluationError,
129
+ "comparison of #{class_of(lhs)} with #{Resolver.describe(rhs)} failed"
130
+ end
131
+
132
+ def equal?(lhs, rhs)
133
+ left = Resolver.numeric(lhs)
134
+ right = Resolver.numeric(rhs)
135
+ return left == right if left && right
136
+
137
+ lhs == rhs
138
+ end
139
+
140
+ def truthy?(value)
141
+ !value.nil? && value != false
142
+ end
143
+
144
+ def class_of(value)
145
+ value.nil? ? "nil" : value.class.name
146
+ end
147
+
148
+ def match_include(expr)
149
+ index = expr.rindex(".include?(")
150
+ return nil unless index && expr.end_with?(")")
151
+
152
+ [expr[0...index], expr[(index + ".include?(".length)...-1]]
153
+ end
154
+
155
+ # Declared the same way in Vocabulary::IncludeHaystack
156
+ # (language/bluebook/vocabulary.bluebook) — spec/vocabulary_conformance_spec
157
+ # holds this equal to the language, so the set of haystack types
158
+ # `.include?` supports cannot drift from what the language says it
159
+ # does.
160
+ INCLUDE_HAYSTACKS = Hecks::Vocabulary.fetch("IncludeHaystack")
161
+
162
+ def includes?(parts, state, attrs)
163
+ haystack, needle = parts
164
+ wanted = Resolver.interpret(needle, state, attrs)
165
+
166
+ case (found = Resolver.interpret(haystack, state, attrs))
167
+ when Array then found.any? { |item| equal?(item, wanted) }
168
+ when String
169
+ unless wanted.is_a?(String)
170
+ raise EvaluationError, "no implicit conversion of #{class_of(wanted)} into String"
171
+ end
172
+
173
+ found.include?(wanted)
174
+ else false
175
+ end
176
+ end
177
+
178
+ def strip_parens(expr)
179
+ return expr unless expr.start_with?("(") && expr.end_with?(")")
180
+
181
+ depth = 0
182
+ expr.each_char.with_index do |char, index|
183
+ depth += 1 if char == "("
184
+ depth -= 1 if char == ")"
185
+ return expr if depth.zero? && index < expr.length - 1
186
+ end
187
+ strip_parens(expr[1..-2].strip)
188
+ end
189
+
190
+ def split_top_level(expr, operator)
191
+ index = top_level_index(expr, operator)
192
+ return nil unless index
193
+
194
+ [expr[0...index].strip, expr[(index + operator.length)..].strip]
195
+ end
196
+
197
+ def split_comparison(expr, operator)
198
+ index = top_level_index(expr, operator) { |at| !part_of_longer?(expr, at, operator) }
199
+ return nil unless index
200
+
201
+ [expr[0...index].strip, expr[(index + operator.length)..].strip]
202
+ end
203
+
204
+ def part_of_longer?(expr, index, operator)
205
+ after = expr[index + operator.length]
206
+ before = index.positive? ? expr[index - 1] : nil
207
+
208
+ return true if after == "=" && !operator.end_with?("=")
209
+ return true if ["<", ">", "!", "="].include?(before) && operator.start_with?("=")
210
+
211
+ false
212
+ end
213
+
214
+ def top_level_index(expr, operator)
215
+ depth = 0
216
+ quote = nil
217
+ index = 0
218
+
219
+ while index < expr.length
220
+ char = expr[index]
221
+
222
+ if quote
223
+ quote = nil if char == quote
224
+ elsif ['"', "'"].include?(char)
225
+ quote = char
226
+ # `{`/`}` depth -- vendored addition, not (yet) upstream
227
+ # hecks (migration plan task 9): this method already
228
+ # treats `(`/`)` as a grouping construct so an operator
229
+ # INSIDE a call's parens is never mistaken for a top-level
230
+ # split point ; `{`/`}` needed the identical treatment the
231
+ # moment `Bluebook::Expression::Resolver` grew block-taking
232
+ # `.all?`/`.any?`/`.none? { |s| PREDICATE }` support (see
233
+ # resolver.rb's own `BlockPredicate` addition) -- without
234
+ # this, an operator INSIDE the block's own predicate (e.g.
235
+ # `s.length > 0`) reads as a top-level split of the WHOLE
236
+ # `value.split("::").all? { |s| s.length > 0 }` expression,
237
+ # confirmed live via `Lexicon::Lexicon.Lookup`/`Query::Query.
238
+ # Run` (the exact `Phrase` invariant this gap was found
239
+ # against) : the stray `>` split the expression in half
240
+ # before `Resolver.parse` ever saw the block as one atomic
241
+ # leaf, and the two halves then failed independently with
242
+ # the same raw `TypeError` the block-predicate fix was
243
+ # built to close. `{`/`}` cannot legitimately appear inside
244
+ # a quoted literal either, so this sits beside the existing
245
+ # paren-depth branch, not instead of it.
246
+ elsif char == "(" || char == "{"
247
+ depth += 1
248
+ elsif char == ")" || char == "}"
249
+ depth -= 1
250
+ elsif depth.zero? && expr[index, operator.length] == operator
251
+ return index if !block_given? || yield(index)
252
+ end
253
+
254
+ index += 1
255
+ end
256
+ nil
257
+ end
258
+ end
259
+ end
260
+ end
261
+ end
@@ -0,0 +1,170 @@
1
+ {
2
+ "operators": [
3
+ {
4
+ "symbol": "||",
5
+ "category": "logical",
6
+ "precedence": 1,
7
+ "arity": 2
8
+ },
9
+ {
10
+ "symbol": "&&",
11
+ "category": "logical",
12
+ "precedence": 2,
13
+ "arity": 2
14
+ },
15
+ {
16
+ "symbol": ".include?",
17
+ "category": "membership",
18
+ "precedence": 3,
19
+ "arity": 2
20
+ },
21
+ {
22
+ "symbol": ">=",
23
+ "category": "comparison",
24
+ "precedence": 4,
25
+ "arity": 2,
26
+ "compares_less_than": true,
27
+ "compares_equal": false,
28
+ "negated": true
29
+ },
30
+ {
31
+ "symbol": "<=",
32
+ "category": "comparison",
33
+ "precedence": 4,
34
+ "arity": 2,
35
+ "compares_less_than": true,
36
+ "compares_equal": true,
37
+ "negated": false
38
+ },
39
+ {
40
+ "symbol": "<",
41
+ "category": "comparison",
42
+ "precedence": 4,
43
+ "arity": 2,
44
+ "compares_less_than": true,
45
+ "compares_equal": false,
46
+ "negated": false
47
+ },
48
+ {
49
+ "symbol": ">",
50
+ "category": "comparison",
51
+ "precedence": 4,
52
+ "arity": 2,
53
+ "compares_less_than": true,
54
+ "compares_equal": true,
55
+ "negated": true
56
+ },
57
+ {
58
+ "symbol": "==",
59
+ "category": "comparison",
60
+ "precedence": 4,
61
+ "arity": 2,
62
+ "compares_less_than": false,
63
+ "compares_equal": true,
64
+ "negated": false
65
+ },
66
+ {
67
+ "symbol": "!=",
68
+ "category": "comparison",
69
+ "precedence": 4,
70
+ "arity": 2,
71
+ "compares_less_than": false,
72
+ "compares_equal": true,
73
+ "negated": true
74
+ },
75
+ {
76
+ "symbol": "!",
77
+ "category": "logical",
78
+ "precedence": 5,
79
+ "arity": 1
80
+ },
81
+ {
82
+ "symbol": "+",
83
+ "category": "arithmetic",
84
+ "precedence": 6,
85
+ "arity": 2
86
+ },
87
+ {
88
+ "symbol": ".modulo",
89
+ "category": "arithmetic",
90
+ "precedence": 6,
91
+ "arity": 2
92
+ },
93
+ {
94
+ "symbol": ".positive?",
95
+ "category": "sign_test",
96
+ "precedence": 7,
97
+ "arity": 1
98
+ },
99
+ {
100
+ "symbol": ".negative?",
101
+ "category": "sign_test",
102
+ "precedence": 7,
103
+ "arity": 1
104
+ },
105
+ {
106
+ "symbol": ".zero?",
107
+ "category": "sign_test",
108
+ "precedence": 7,
109
+ "arity": 1
110
+ },
111
+ {
112
+ "symbol": ".empty?",
113
+ "category": "sized",
114
+ "precedence": 8,
115
+ "arity": 1
116
+ },
117
+ {
118
+ "symbol": ".to_s",
119
+ "category": "to_string",
120
+ "precedence": 9,
121
+ "arity": 1
122
+ },
123
+ {
124
+ "symbol": ".size",
125
+ "category": "sized",
126
+ "precedence": 8,
127
+ "arity": 1
128
+ },
129
+ {
130
+ "symbol": ".any?",
131
+ "category": "enumeration",
132
+ "precedence": 10,
133
+ "arity": 2
134
+ },
135
+ {
136
+ "symbol": ".none?",
137
+ "category": "enumeration",
138
+ "precedence": 10,
139
+ "arity": 2
140
+ },
141
+ {
142
+ "symbol": ".all?",
143
+ "category": "enumeration",
144
+ "precedence": 10,
145
+ "arity": 2
146
+ },
147
+ {
148
+ "symbol": ".find",
149
+ "category": "enumeration",
150
+ "precedence": 10,
151
+ "arity": 2
152
+ }
153
+ ],
154
+ "normalisations": [
155
+ {
156
+ "strategy": "collapse_whitespace",
157
+ "source_token": "",
158
+ "replacement": "",
159
+ "boundary": "none",
160
+ "position": 1
161
+ },
162
+ {
163
+ "strategy": "replace",
164
+ "source_token": ".length",
165
+ "replacement": ".size",
166
+ "boundary": "word",
167
+ "position": 2
168
+ }
169
+ ]
170
+ }
@@ -0,0 +1,233 @@
1
+ # HAND-WRITTEN — the block-predicate/find family of the leaf grammar
2
+ # (`Bluebook::Expression::Resolver`'s own `.all?`/`.any?`/`.none?`/
3
+ # `.find` suffixes), split into this sibling file to keep resolver.rb
4
+ # under Metrics/ModuleLength (350) : this reopens the SAME `Resolver`
5
+ # module resolver.rb defines, the long nested `module A; module B; ...`
6
+ # form (not the compact `A::B` form) so bare constant/method lookups
7
+ # here (`EvaluationError`, `Evaluator`, `describe`, `unwrap_scalar`,
8
+ # `walk_path`) resolve exactly the way they do inside resolver.rb
9
+ # itself — this is one module, split across two files, not two
10
+ # modules. See resolver.rb's own header for the grammar this belongs
11
+ # to; see each struct/method below for its own "why".
12
+ module Hecks
13
+ module Bluebook
14
+ module Expression
15
+ module Resolver
16
+ # `receiver.all? { |x| PREDICATE }` / `.any? { ... }` / `.none? {
17
+ # ... }` -- vendored addition, not (yet) upstream hecks
18
+ # (migration plan task 9), completing the same `Phrase`
19
+ # four-segment invariant the `Split` node above was built for
20
+ # (`value.split("::").all? { |s| s.length > 0 }`). Structurally
21
+ # different from every other addition in this file: every prior
22
+ # suffix is a flat receiver -> scalar transform, but a block
23
+ # predicate needs to evaluate its own sub-expression ONCE PER
24
+ # ELEMENT with the block parameter bound to that element. Kept
25
+ # minimal per the migration plan's own instruction -- no
26
+ # persistent iteration-variable concept added to Resolver's
27
+ # state model at all ; `predicate` below is a fully-parsed
28
+ # EVALUATOR ast (not a Resolver ast -- the predicate is a
29
+ # boolean/comparison expression like `s.length > 0`, exactly the
30
+ # grammar `Bluebook::Expression::Evaluator` owns, not this
31
+ # module's own leaf grammar), parsed once at `parse`-time same as
32
+ # every sibling node's sub-expressions are. `mode` distinguishes
33
+ # all?/any?/none? without three duplicated node types, since
34
+ # their only difference is which Array predicate aggregates the
35
+ # per-element results (interpret_with_element, below, is the
36
+ # "smallest correct thing" the plan asked for -- it threads the
37
+ # element binding through a temporarily-extended `attrs` hash for
38
+ # that one predicate's evaluation only, never touching
39
+ # `interpret`'s own signature or any other node's call sites).
40
+ # `Resolver` already calls into `Evaluator` elsewhere in this
41
+ # file (`sign_test_node`/`apply_sign_test` call `Evaluator.
42
+ # apply`/`Evaluator::OPERATORS` directly) -- this is the same
43
+ # precedented cross-reference, not a new coupling.
44
+ BlockPredicate = Struct.new(:mode, :receiver, :param, :predicate, keyword_init: true)
45
+
46
+ # Which Array method each block-predicate suffix maps to, and
47
+ # which Ruby Enumerable method decides the aggregate result --
48
+ # declared as data, not a three-way `case`, the same shape
49
+ # SIGN_TEST_OPERATORS above already uses for its own suffix
50
+ # family.
51
+ BLOCK_PREDICATE_MODES = {
52
+ "all?" => :all,
53
+ "any?" => :any,
54
+ "none?" => :none
55
+ }.freeze
56
+
57
+ # `receiver.find { |x| PREDICATE }` / `.find { ... }.a.b` -- the
58
+ # "find-then-project" shape the shipping domain's re-routing
59
+ # rules kept reaching for by hand (a caller-precomputed
60
+ # `next_load_location` field standing in for "the leg after this
61
+ # one", because `BlockPredicate` above can only answer yes/no,
62
+ # never hand back the element it found). `path` holds the
63
+ # dotted segments walked past the closing `}`, e.g. the `["
64
+ # next_load_location"]` in `legs.find { |l| l.load_location ==
65
+ # x }.next_load_location` -- empty when `.find { ... }` is used
66
+ # bare (its own found-element-or-nil is the value, same as any
67
+ # other leaf, e.g. composed with `.present?` the same way every
68
+ # other receiver already composes with it). Reuses `param`/
69
+ # `predicate`'s exact field names from `BlockPredicate` on
70
+ # purpose: `interpret_with_element` below binds by those two
71
+ # names and is shared unchanged between both node types.
72
+ Find = Struct.new(:receiver, :param, :predicate, :path, keyword_init: true)
73
+
74
+ module_function
75
+
76
+ # Every suffix that opens a `{ |x| ... }` block, `.find` included
77
+ # -- shared by `parse_block_opener` below, and by nothing else
78
+ # (this is NOT `BLOCK_PREDICATE_MODES` — `.find` isn't a mode
79
+ # `evaluate_block_predicate` aggregates through, it builds a
80
+ # `Find` node instead, see below).
81
+ BLOCK_OPENER_SUFFIXES = (BLOCK_PREDICATE_MODES.keys + ["find"]).freeze
82
+
83
+ # `.all?`/`.any?`/`.none?`/`.find` -- vendored addition, see the
84
+ # `BlockPredicate`/`Find` structs' own comments above. Matched
85
+ # last among the suffix rules (right before the `Lookup`
86
+ # catch-all) since a block's own predicate text can itself
87
+ # contain almost anything a leaf expression can, including
88
+ # ANOTHER block-opening suffix -- letting every more specific
89
+ # rule above try first avoids this one accidentally swallowing a
90
+ # receiver another rule was meant to parse.
91
+ #
92
+ # ONE combined header regex over ALL FOUR suffixes together,
93
+ # not `.find` and `.all?/any?/none?` scanned separately (that
94
+ # was this file's own first cut, and it broke the moment a
95
+ # block predicate's own predicate text contained a DIFFERENT
96
+ # kind of block-opener than the one being scanned for --
97
+ # `legs.any? { |leg| ... legs.find { |o| ... } ... }` : scanning
98
+ # for `.find` FIRST found the INNER `.find`, not the outer
99
+ # `.any?`, because a non-greedy receiver capture only guarantees
100
+ # the FIRST occurrence of ITS OWN suffix, not the first
101
+ # occurrence of ANY block-opening suffix -- confirmed live via
102
+ # the shipping domain's own re-routing rules, the same
103
+ # "no implicit conversion of Symbol into Integer" signature the
104
+ # original nested-`.any?` bug had, not inferred). Scanning for
105
+ # all four AT ONCE and letting the regex engine's own leftmost
106
+ # match win fixes both directions (`.find` nested in `.any?` OR
107
+ # `.any?` nested in `.find`) with the SAME one rule, since the
108
+ # true receiver never itself contains ANY of these four words
109
+ # followed by `{`.
110
+ #
111
+ # `matching_brace` walks forward counting `{`/`}` depth from
112
+ # there, the same quote-aware, depth-aware discipline
113
+ # `split_addition`/`array_elements` already apply, so a `}`
114
+ # inside a nested block (or a quoted `start_with?` substring)
115
+ # never miscounts. `.find`'s own trailing dotted path (`path`)
116
+ # is captured from whatever follows the closing brace ; every
117
+ # other suffix instead requires nothing follow it at all (the
118
+ # `BlockPredicate` shape, unchanged from before this rewrite).
119
+ def parse_block_opener(expr)
120
+ pattern = /\A(.+?)\.(#{BLOCK_OPENER_SUFFIXES.map { |suffix| Regexp.escape(suffix) }.join('|')})\s*\{\s*\|(\w+)\|\s*/m
121
+ header = expr.match(pattern)
122
+ return nil unless header
123
+
124
+ receiver_text = header[1]
125
+ suffix = header[2]
126
+ param = header[3]
127
+ body_start = header.end(0)
128
+ body_end = matching_brace(expr, body_start)
129
+ return nil unless body_end
130
+
131
+ predicate = Evaluator.parse(expr[body_start...body_end].strip)
132
+
133
+ if suffix == "find"
134
+ trailing = expr[(body_end + 1)..].strip
135
+ return nil unless trailing.empty? || trailing.start_with?(".")
136
+
137
+ Find.new(receiver: parse(receiver_text), param: param, predicate: predicate,
138
+ path: trailing.empty? ? [] : trailing[1..].split("."))
139
+ else
140
+ return nil unless expr[(body_end + 1)..].strip.empty?
141
+
142
+ BlockPredicate.new(mode: BLOCK_PREDICATE_MODES.fetch(suffix), receiver: parse(receiver_text),
143
+ param: param, predicate: predicate)
144
+ end
145
+ end
146
+
147
+ # The index of the `}` that closes the `{` implicitly opened
148
+ # just before `start` (the caller's own header match already
149
+ # consumed that opening brace, so depth begins at 1) -- nil if
150
+ # the string runs out before depth returns to 0 (a caller-error
151
+ # shape, not a valid expression). Quote-aware so a `}` inside a
152
+ # quoted substring (`.start_with?("}")`) never miscounts, the
153
+ # same discipline `split_addition`/`array_elements` already
154
+ # apply for their own depth tracking.
155
+ def matching_brace(expr, start)
156
+ depth = 1
157
+ quote = nil
158
+ index = start
159
+ while index < expr.length
160
+ char = expr[index]
161
+ if quote
162
+ quote = nil if char == quote
163
+ elsif ['"', "'"].include?(char)
164
+ quote = char
165
+ elsif char == "{"
166
+ depth += 1
167
+ elsif char == "}"
168
+ depth -= 1
169
+ return index if depth.zero?
170
+ end
171
+ index += 1
172
+ end
173
+ nil
174
+ end
175
+
176
+ # `.all?`/`.any?`/`.none?` -- vendored addition, see the
177
+ # `BlockPredicate` struct's own comment above. `collection` is
178
+ # already-interpreted (a real Array, produced by whatever
179
+ # receiver expression came before it -- typically `Split`'s
180
+ # output), so this only has to run the per-element predicate and
181
+ # aggregate. `interpret_with_element` is the "smallest correct
182
+ # thing" the migration plan asked for : no persistent iteration-
183
+ # variable concept added anywhere else in Resolver's state model,
184
+ # just `attrs` extended with the bound name for the span of that
185
+ # one predicate evaluation, discarded immediately after.
186
+ def evaluate_block_predicate(node, collection, state, attrs)
187
+ unless collection.is_a?(Array)
188
+ raise EvaluationError, "#{node.mode}? expects a list, got #{describe(collection)}"
189
+ end
190
+
191
+ outcomes = collection.map { |element| interpret_with_element(node, element, state, attrs) }
192
+
193
+ case node.mode
194
+ when :all then outcomes.all?
195
+ when :any then outcomes.any?
196
+ when :none then outcomes.none?
197
+ end
198
+ end
199
+
200
+ # Binds the block parameter for exactly one element's predicate
201
+ # evaluation -- `attrs` wins over `state` in `fetch` (see below),
202
+ # so the bound name shadows any same-named state/attrs field for
203
+ # the span of this one call only ; nothing persists past it.
204
+ def interpret_with_element(node, element, state, attrs)
205
+ Evaluator.interpret(node.predicate, state, attrs.merge(node.param.to_sym => element))
206
+ end
207
+
208
+ # `.find { |x| PREDICATE }` -- see the `Find` struct's own
209
+ # comment above. Reuses `interpret_with_element` unchanged
210
+ # (below, shared with `BlockPredicate` — both bind `node.param`
211
+ # to one element and interpret `node.predicate` against it) to
212
+ # find the FIRST element the predicate accepts, then projects
213
+ # `node.path` through it via `walk_path`, the same dotted-
214
+ # segment walk `lookup` uses for a plain attribute path. `nil`
215
+ # (no matching element, or a `path` segment that doesn't
216
+ # resolve) flows through rather than raising — the same "a
217
+ # dispatch-time given just refuses" shape every other missing-
218
+ # value case in this grammar already has, and the one a re-
219
+ # routing check like "is there a leg after this one" needs :
220
+ # not finding one is a normal outcome, not an error.
221
+ def found_of(node, collection, state, attrs)
222
+ raise EvaluationError, "find expects a list, got #{describe(collection)}" unless collection.is_a?(Array)
223
+
224
+ found = collection.find { |element| interpret_with_element(node, element, state, attrs) }
225
+ return unwrap_scalar(found) if node.path.empty?
226
+ return nil if found.nil?
227
+
228
+ unwrap_scalar(walk_path(found, node.path))
229
+ end
230
+ end
231
+ end
232
+ end
233
+ end