hecks 0.2.0 → 1.0.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 (419) 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 +427 -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 +101 -0
  12. data/lib/hecks/adapters/driven/heki/journal.rb +56 -0
  13. data/lib/hecks/adapters/driven/heki/saga_store.rb +99 -0
  14. data/lib/hecks/adapters/driven/heki/snapshot.rb +65 -0
  15. data/lib/hecks/adapters/driven/heki.adapter +4 -0
  16. data/lib/hecks/adapters/driven/heki.rb +179 -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 +145 -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 +88 -0
  28. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +207 -0
  29. data/lib/hecks/adapters/driven/postgres.adapter +5 -0
  30. data/lib/hecks/adapters/driven/postgres.rb +438 -0
  31. data/lib/hecks/adapters/driven/postgres_era.adapter +17 -0
  32. data/lib/hecks/adapters/driven/prism.adapter +3 -0
  33. data/lib/hecks/adapters/driven/prism.rb +80 -0
  34. data/lib/hecks/adapters/driven/secure_random_identity.adapter +3 -0
  35. data/lib/hecks/adapters/driven/secure_random_identity.rb +14 -0
  36. data/lib/hecks/adapters/driven/sql_query_builder.rb +221 -0
  37. data/lib/hecks/adapters/driven/sqlite/codec.rb +80 -0
  38. data/lib/hecks/adapters/driven/sqlite/projection.rb +172 -0
  39. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +192 -0
  40. data/lib/hecks/adapters/driven/sqlite.adapter +9 -0
  41. data/lib/hecks/adapters/driven/sqlite.rb +306 -0
  42. data/lib/hecks/adapters/driven/system_clock.adapter +3 -0
  43. data/lib/hecks/adapters/driven/system_clock.rb +14 -0
  44. data/lib/hecks/adapters/driven.rb +54 -0
  45. data/lib/hecks/adapters.rb +6 -0
  46. data/lib/hecks/behaviors/dsl.rb +125 -0
  47. data/lib/hecks/behaviors/expectations.rb +340 -0
  48. data/lib/hecks/behaviors/ir.rb +31 -0
  49. data/lib/hecks/behaviors/rspec.rb +42 -0
  50. data/lib/hecks/behaviors/runner.rb +96 -0
  51. data/lib/hecks/behaviors.rb +25 -0
  52. data/lib/hecks/bluebook/aggregate.rb +108 -0
  53. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +134 -0
  54. data/lib/hecks/bluebook/assembly/build.rb +48 -0
  55. data/lib/hecks/bluebook/assembly/contract.rb +112 -0
  56. data/lib/hecks/bluebook/assembly/contracts.rb +438 -0
  57. data/lib/hecks/bluebook/assembly/marks.rb +228 -0
  58. data/lib/hecks/bluebook/assembly/specializer.rb +70 -0
  59. data/lib/hecks/bluebook/assembly.rb +91 -0
  60. data/lib/hecks/bluebook/attribute.rb +96 -0
  61. data/lib/hecks/bluebook/behaviour/aggregate.rb +83 -0
  62. data/lib/hecks/bluebook/behaviour/attribute.rb +27 -0
  63. data/lib/hecks/bluebook/behaviour/chapter.rb +72 -0
  64. data/lib/hecks/bluebook/behaviour/command.rb +116 -0
  65. data/lib/hecks/bluebook/behaviour/domain_port.rb +25 -0
  66. data/lib/hecks/bluebook/behaviour/entity.rb +59 -0
  67. data/lib/hecks/bluebook/behaviour/hexagon.rb +50 -0
  68. data/lib/hecks/bluebook/behaviour/lifecycle.rb +68 -0
  69. data/lib/hecks/bluebook/behaviour/policy.rb +52 -0
  70. data/lib/hecks/bluebook/behaviour/process_manager.rb +51 -0
  71. data/lib/hecks/bluebook/behaviour/query.rb +10 -0
  72. data/lib/hecks/bluebook/behaviour/read_model.rb +29 -0
  73. data/lib/hecks/bluebook/behaviour/traits.rb +81 -0
  74. data/lib/hecks/bluebook/behaviour/value_object.rb +33 -0
  75. data/lib/hecks/bluebook/chapter.rb +78 -0
  76. data/lib/hecks/bluebook/command.rb +124 -0
  77. data/lib/hecks/bluebook/domain_port.rb +102 -0
  78. data/lib/hecks/bluebook/dsl/adapter_builder.rb +34 -0
  79. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +1018 -0
  80. data/lib/hecks/bluebook/dsl/attribute_collector.rb +348 -0
  81. data/lib/hecks/bluebook/dsl/binding_proxy.rb +71 -0
  82. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +1087 -0
  83. data/lib/hecks/bluebook/dsl/command_builder.rb +714 -0
  84. data/lib/hecks/bluebook/dsl/const_shim.rb +81 -0
  85. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +79 -0
  86. data/lib/hecks/bluebook/dsl/entity_builder.rb +430 -0
  87. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +366 -0
  88. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +163 -0
  89. data/lib/hecks/bluebook/dsl/identity_declaration.rb +191 -0
  90. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +44 -0
  91. data/lib/hecks/bluebook/dsl/malformed.rb +7 -0
  92. data/lib/hecks/bluebook/dsl/policy_builder.rb +135 -0
  93. data/lib/hecks/bluebook/dsl/port_builder.rb +39 -0
  94. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +142 -0
  95. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +306 -0
  96. data/lib/hecks/bluebook/dsl/query_builder.rb +113 -0
  97. data/lib/hecks/bluebook/dsl/read_model_builder.rb +233 -0
  98. data/lib/hecks/bluebook/dsl/rule_reference.rb +173 -0
  99. data/lib/hecks/bluebook/dsl/translation_builder.rb +243 -0
  100. data/lib/hecks/bluebook/dsl/value_object_builder.rb +178 -0
  101. data/lib/hecks/bluebook/dsl/word_gate.rb +228 -0
  102. data/lib/hecks/bluebook/dsl/world_builder.rb +117 -0
  103. data/lib/hecks/bluebook/dsl.rb +45 -0
  104. data/lib/hecks/bluebook/entity.rb +103 -0
  105. data/lib/hecks/bluebook/expression/canonical_form.rb +123 -0
  106. data/lib/hecks/bluebook/expression/evaluator.rb +304 -0
  107. data/lib/hecks/bluebook/expression/projection.json +218 -0
  108. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +233 -0
  109. data/lib/hecks/bluebook/expression/resolver.rb +781 -0
  110. data/lib/hecks/bluebook/expression.rb +13 -0
  111. data/lib/hecks/bluebook/hexagon.rb +60 -0
  112. data/lib/hecks/bluebook/lifecycle.rb +42 -0
  113. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +54 -0
  114. data/lib/hecks/bluebook/meta_validator/judge.rb +621 -0
  115. data/lib/hecks/bluebook/meta_validator/plan.rb +332 -0
  116. data/lib/hecks/bluebook/meta_validator/port_judge.rb +51 -0
  117. data/lib/hecks/bluebook/meta_validator/readings.rb +360 -0
  118. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +351 -0
  119. data/lib/hecks/bluebook/meta_validator/shapes.rb +266 -0
  120. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +255 -0
  121. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +138 -0
  122. data/lib/hecks/bluebook/meta_validator/world_judge.rb +78 -0
  123. data/lib/hecks/bluebook/meta_validator.rb +542 -0
  124. data/lib/hecks/bluebook/model_check.rb +445 -0
  125. data/lib/hecks/bluebook/pattern_subset.rb +184 -0
  126. data/lib/hecks/bluebook/policy.rb +41 -0
  127. data/lib/hecks/bluebook/process_manager.rb +128 -0
  128. data/lib/hecks/bluebook/project_discovery.rb +30 -0
  129. data/lib/hecks/bluebook/project_loader.rb +40 -0
  130. data/lib/hecks/bluebook/project_register.rb +82 -0
  131. data/lib/hecks/bluebook/query.rb +61 -0
  132. data/lib/hecks/bluebook/read_model.rb +109 -0
  133. data/lib/hecks/bluebook/reference.rb +74 -0
  134. data/lib/hecks/bluebook/smoke_test.rb +166 -0
  135. data/lib/hecks/bluebook/synthesizer.rb +95 -0
  136. data/lib/hecks/bluebook/translation.rb +92 -0
  137. data/lib/hecks/bluebook/value_object.rb +66 -0
  138. data/lib/hecks/bluebook.rb +74 -0
  139. data/lib/hecks/codemod.rb +342 -0
  140. data/lib/hecks/construct.rb +71 -0
  141. data/lib/hecks/deploy/bluebook/deploy.bluebook +219 -0
  142. data/lib/hecks/deploy/bluebook/deploy.hecksagon +4 -0
  143. data/lib/hecks/deploy/oidc.json +18 -0
  144. data/lib/hecks/doc/reference.rb +410 -0
  145. data/lib/hecks/embryonaut_bluebook.rb +75 -0
  146. data/lib/hecks/facade/cli_door.rb +119 -0
  147. data/lib/hecks/facade/cli_runner.rb +190 -0
  148. data/lib/hecks/facade/command_request.rb +105 -0
  149. data/lib/hecks/facade/handle.rb +173 -0
  150. data/lib/hecks/facade/json_door.rb +166 -0
  151. data/lib/hecks/facade/surface/aggregate_door.rb +185 -0
  152. data/lib/hecks/facade/surface/chapter.rb +107 -0
  153. data/lib/hecks/facade/surface.rb +48 -0
  154. data/lib/hecks/facade.rb +44 -0
  155. data/lib/hecks/forms/app.rb +341 -0
  156. data/lib/hecks/forms/command_form_renderer.rb +113 -0
  157. data/lib/hecks/forms/examples/banking_console.bluebook +3 -0
  158. data/lib/hecks/forms/field_renderer.rb +177 -0
  159. data/lib/hecks/forms/field_shape.rb +232 -0
  160. data/lib/hecks/forms/html.rb +84 -0
  161. data/lib/hecks/forms/index_renderer.rb +35 -0
  162. data/lib/hecks/forms/page.rb +157 -0
  163. data/lib/hecks/forms/params.rb +161 -0
  164. data/lib/hecks/forms/port_argument.rb +46 -0
  165. data/lib/hecks/forms/query_form_renderer.rb +114 -0
  166. data/lib/hecks/forms/record_renderer.rb +119 -0
  167. data/lib/hecks/forms/record_table.rb +68 -0
  168. data/lib/hecks/forms/reference_options.rb +30 -0
  169. data/lib/hecks/forms/value_object_shape.rb +46 -0
  170. data/lib/hecks/forms.rb +54 -0
  171. data/lib/hecks/fqn.rb +94 -0
  172. data/lib/hecks/framework/bluebook/compliance.bluebook +1 -0
  173. data/lib/hecks/framework/bluebook/console_settings.bluebook +489 -0
  174. data/lib/hecks/framework/bluebook/framework.hecksagon +32 -0
  175. data/lib/hecks/framework/bluebook/governance.bluebook +145 -0
  176. data/lib/hecks/framework/bluebook/identity.bluebook +90 -0
  177. data/lib/hecks/framework/oidc.json +39 -0
  178. data/lib/hecks/framework.rb +90 -0
  179. data/lib/hecks/freezer.rb +67 -0
  180. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +527 -0
  181. data/lib/hecks/fuzzing/invalid_value_generator.rb +86 -0
  182. data/lib/hecks/fuzzing/isolated_boot.rb +286 -0
  183. data/lib/hecks/fuzzing/properties.rb +1192 -0
  184. data/lib/hecks/fuzzing/replay.rb +668 -0
  185. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +93 -0
  186. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +80 -0
  187. data/lib/hecks/fuzzing/sequence_generator/picker.rb +96 -0
  188. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +250 -0
  189. data/lib/hecks/fuzzing/sequence_generator.rb +131 -0
  190. data/lib/hecks/fuzzing/value_generator.rb +200 -0
  191. data/lib/hecks/fuzzing.rb +12 -0
  192. data/lib/hecks/grammar/evolve.rb +324 -0
  193. data/lib/hecks/grammar/expression.bluebook +420 -0
  194. data/lib/hecks/grammar/expression_operators.json +1648 -0
  195. data/lib/hecks/grammar/grammar.hecksagon +20 -0
  196. data/lib/hecks/grammar/oidc.json +69 -0
  197. data/lib/hecks/grammar/translation.bluebook +203 -0
  198. data/lib/hecks/grammar.rb +178 -0
  199. data/lib/hecks/ir.rb +126 -0
  200. data/lib/hecks/language/adapter.bluebook +116 -0
  201. data/lib/hecks/language/bluebook/aggregate.bluebook +581 -0
  202. data/lib/hecks/language/bluebook/attaches/paging.bluebook +76 -0
  203. data/lib/hecks/language/bluebook/bluebook.bluebook +256 -0
  204. data/lib/hecks/language/bluebook/bluebook.hecksagon +24 -0
  205. data/lib/hecks/language/bluebook/command.bluebook +471 -0
  206. data/lib/hecks/language/bluebook/entity.bluebook +392 -0
  207. data/lib/hecks/language/bluebook/policy.bluebook +189 -0
  208. data/lib/hecks/language/bluebook/process_manager.bluebook +381 -0
  209. data/lib/hecks/language/bluebook/projection.bluebook +267 -0
  210. data/lib/hecks/language/bluebook/query.bluebook +245 -0
  211. data/lib/hecks/language/bluebook/shape.bluebook +292 -0
  212. data/lib/hecks/language/bluebook/syntax.bluebook +448 -0
  213. data/lib/hecks/language/bluebook/vocabulary.bluebook +379 -0
  214. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +51 -0
  215. data/lib/hecks/language/hecksagon/domain_port.bluebook +76 -0
  216. data/lib/hecks/language/hecksagon/hecksagon.bluebook +131 -0
  217. data/lib/hecks/language/hecksagon/port_operation.bluebook +102 -0
  218. data/lib/hecks/language/oidc.json +333 -0
  219. data/lib/hecks/language/port.bluebook +120 -0
  220. data/lib/hecks/language/translation/translation.bluebook +110 -0
  221. data/lib/hecks/language/translation/translation_aggregate.bluebook +267 -0
  222. data/lib/hecks/language/world/wiring.bluebook +62 -0
  223. data/lib/hecks/language/world/world.bluebook +84 -0
  224. data/lib/hecks/literal.rb +125 -0
  225. data/lib/hecks/naming.rb +174 -0
  226. data/lib/hecks/ports/access_control.port +9 -0
  227. data/lib/hecks/ports/access_control.rb +62 -0
  228. data/lib/hecks/ports/agent/answers.rb +104 -0
  229. data/lib/hecks/ports/agent.port +8 -0
  230. data/lib/hecks/ports/agent.rb +167 -0
  231. data/lib/hecks/ports/authentication.port +6 -0
  232. data/lib/hecks/ports/authentication.rb +50 -0
  233. data/lib/hecks/ports/authorization.port +7 -0
  234. data/lib/hecks/ports/authorization.rb +62 -0
  235. data/lib/hecks/ports/clock.port +5 -0
  236. data/lib/hecks/ports/clock.rb +62 -0
  237. data/lib/hecks/ports/extraction.port +5 -0
  238. data/lib/hecks/ports/extraction.rb +37 -0
  239. data/lib/hecks/ports/identity_assignment.port +5 -0
  240. data/lib/hecks/ports/identity_assignment.rb +45 -0
  241. data/lib/hecks/ports/identity_generation.port +5 -0
  242. data/lib/hecks/ports/identity_generation.rb +49 -0
  243. data/lib/hecks/ports/identity_resolution.port +5 -0
  244. data/lib/hecks/ports/identity_resolution.rb +40 -0
  245. data/lib/hecks/ports/loading.port +4 -0
  246. data/lib/hecks/ports/loading.rb +13 -0
  247. data/lib/hecks/ports/persistence/append_only.rb +138 -0
  248. data/lib/hecks/ports/persistence/binding_policy.rb +56 -0
  249. data/lib/hecks/ports/persistence/execution.rb +23 -0
  250. data/lib/hecks/ports/persistence/null_saga_store.rb +25 -0
  251. data/lib/hecks/ports/persistence/plugin.rb +54 -0
  252. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +169 -0
  253. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +128 -0
  254. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +161 -0
  255. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +61 -0
  256. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +304 -0
  257. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +171 -0
  258. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +190 -0
  259. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +478 -0
  260. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +166 -0
  261. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +314 -0
  262. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +168 -0
  263. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +170 -0
  264. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +134 -0
  265. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +137 -0
  266. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +89 -0
  267. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +85 -0
  268. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +43 -0
  269. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +132 -0
  270. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +74 -0
  271. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +758 -0
  272. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +120 -0
  273. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +31 -0
  274. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +46 -0
  275. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +102 -0
  276. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +46 -0
  277. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +70 -0
  278. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +72 -0
  279. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +120 -0
  280. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +183 -0
  281. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +41 -0
  282. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +28 -0
  283. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +30 -0
  284. data/lib/hecks/ports/persistence/plugins/era/translation.rb +11 -0
  285. data/lib/hecks/ports/persistence/plugins/era.rb +48 -0
  286. data/lib/hecks/ports/persistence/remote_runtime.rb +42 -0
  287. data/lib/hecks/ports/persistence/repository_factory.rb +30 -0
  288. data/lib/hecks/ports/persistence.port +4 -0
  289. data/lib/hecks/ports/persistence.rb +39 -0
  290. data/lib/hecks/ports/projection.port +4 -0
  291. data/lib/hecks/ports/projection.rb +96 -0
  292. data/lib/hecks/ports/query/in_memory.rb +60 -0
  293. data/lib/hecks/ports/query/ordering.rb +41 -0
  294. data/lib/hecks/ports/query.rb +36 -0
  295. data/lib/hecks/ports.rb +27 -0
  296. data/lib/hecks/projections/diagrams.rb +642 -0
  297. data/lib/hecks/projections/ir.rb +18 -0
  298. data/lib/hecks/projections/model/deviations.rb +98 -0
  299. data/lib/hecks/projections/model.rb +145 -0
  300. data/lib/hecks/projections/oidc.rb +110 -0
  301. data/lib/hecks/projections/parser_table.rb +159 -0
  302. data/lib/hecks/projections/reference.rb +38 -0
  303. data/lib/hecks/projections/shape.rb +44 -0
  304. data/lib/hecks/projections/statements.rb +110 -0
  305. data/lib/hecks/projections/vocabulary.rb +100 -0
  306. data/lib/hecks/projections.rb +32 -0
  307. data/lib/hecks/projector/cli_projector.rb +446 -0
  308. data/lib/hecks/projector/docs_projector.rb +321 -0
  309. data/lib/hecks/projector/exporter.rb +158 -0
  310. data/lib/hecks/projector/ir_projector.rb +18 -0
  311. data/lib/hecks/projector/narrate_projector.rb +243 -0
  312. data/lib/hecks/projector/target.rb +97 -0
  313. data/lib/hecks/projector.rb +186 -0
  314. data/lib/hecks/query_ir.rb +411 -0
  315. data/lib/hecks/query_specification/common/authorization_spec.rb +9 -0
  316. data/lib/hecks/query_specification/common/comparators.rb +25 -0
  317. data/lib/hecks/query_specification/common/comparison.rb +174 -0
  318. data/lib/hecks/query_specification/common/cursor_spec.rb +9 -0
  319. data/lib/hecks/query_specification/common/dsl.rb +58 -0
  320. data/lib/hecks/query_specification/common/inspection_spec.rb +9 -0
  321. data/lib/hecks/query_specification/common/limit_spec.rb +9 -0
  322. data/lib/hecks/query_specification/common/null_policy.rb +92 -0
  323. data/lib/hecks/query_specification/common/null_semantics.rb +11 -0
  324. data/lib/hecks/query_specification/common/offset_spec.rb +9 -0
  325. data/lib/hecks/query_specification/common/options.rb +32 -0
  326. data/lib/hecks/query_specification/common/order_by.rb +9 -0
  327. data/lib/hecks/query_specification/common/specification.rb +10 -0
  328. data/lib/hecks/query_specification/common/where_clause.rb +9 -0
  329. data/lib/hecks/query_specification/field_path.rb +107 -0
  330. data/lib/hecks/query_specification/hop_path.rb +132 -0
  331. data/lib/hecks/query_specification/read_model/specification.rb +18 -0
  332. data/lib/hecks/query_specification.rb +14 -0
  333. data/lib/hecks/rendering.rb +48 -0
  334. data/lib/hecks/router/namespace_installer.rb +157 -0
  335. data/lib/hecks/router.rb +70 -0
  336. data/lib/hecks/runtime/aggregate_lock.rb +45 -0
  337. data/lib/hecks/runtime/boot_gates.rb +41 -0
  338. data/lib/hecks/runtime/caller.rb +66 -0
  339. data/lib/hecks/runtime/capability_graph.rb +44 -0
  340. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +135 -0
  341. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +260 -0
  342. data/lib/hecks/runtime/command_interpreter.rb +497 -0
  343. data/lib/hecks/runtime/command_rules/admissibility.rb +357 -0
  344. data/lib/hecks/runtime/command_rules/arithmetic.rb +260 -0
  345. data/lib/hecks/runtime/command_rules/authorization.rb +65 -0
  346. data/lib/hecks/runtime/command_rules/emission.rb +34 -0
  347. data/lib/hecks/runtime/command_rules/references.rb +189 -0
  348. data/lib/hecks/runtime/command_rules.rb +28 -0
  349. data/lib/hecks/runtime/dependency_planning.rb +245 -0
  350. data/lib/hecks/runtime/dispatcher.rb +301 -0
  351. data/lib/hecks/runtime/entity_element.rb +253 -0
  352. data/lib/hecks/runtime/entity_interpreter.rb +299 -0
  353. data/lib/hecks/runtime/errors.rb +122 -0
  354. data/lib/hecks/runtime/event.rb +51 -0
  355. data/lib/hecks/runtime/identity.rb +156 -0
  356. data/lib/hecks/runtime/instance.rb +175 -0
  357. data/lib/hecks/runtime/interpreting.rb +90 -0
  358. data/lib/hecks/runtime/loader.rb +184 -0
  359. data/lib/hecks/runtime/policy_interpreter.rb +366 -0
  360. data/lib/hecks/runtime/port_operation_interpreter.rb +210 -0
  361. data/lib/hecks/runtime/query_interpreter.rb +286 -0
  362. data/lib/hecks/runtime/reaction_invocation.rb +253 -0
  363. data/lib/hecks/runtime/read_model_interpreter.rb +341 -0
  364. data/lib/hecks/runtime/rebuild_sweep.rb +74 -0
  365. data/lib/hecks/runtime/reference_hop.rb +99 -0
  366. data/lib/hecks/runtime/refusal_wording.rb +126 -0
  367. data/lib/hecks/runtime/registry/saga_persistence.rb +142 -0
  368. data/lib/hecks/runtime/registry/verification.rb +246 -0
  369. data/lib/hecks/runtime/registry.rb +279 -0
  370. data/lib/hecks/runtime/remote_dispatcher.rb +143 -0
  371. data/lib/hecks/runtime/routing.rb +96 -0
  372. data/lib/hecks/runtime/saga_interpreter/correlation.rb +97 -0
  373. data/lib/hecks/runtime/saga_interpreter.rb +503 -0
  374. data/lib/hecks/runtime/saga_pending_dispatch.rb +45 -0
  375. data/lib/hecks/runtime/tenant_check.rb +84 -0
  376. data/lib/hecks/runtime/tenant_scope.rb +55 -0
  377. data/lib/hecks/runtime/value/admission.rb +130 -0
  378. data/lib/hecks/runtime/value/coercion.rb +565 -0
  379. data/lib/hecks/runtime/value/invariant_violation.rb +5 -0
  380. data/lib/hecks/runtime/value.rb +125 -0
  381. data/lib/hecks/runtime.rb +107 -0
  382. data/lib/hecks/storehouse.rb +632 -0
  383. data/lib/hecks/version.rb +16 -0
  384. data/lib/hecks/vocabulary.rb +218 -0
  385. data/lib/hecks.rb +123 -6
  386. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +90 -0
  387. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +128 -0
  388. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +160 -0
  389. metadata +412 -222
  390. data/bin/hecks +0 -7
  391. data/bin/hecks-package +0 -65
  392. data/bin/hecks_console +0 -12
  393. data/bin/hecks_serverless +0 -6
  394. data/lib/cli/build.rb +0 -14
  395. data/lib/cli/command_runner.rb +0 -28
  396. data/lib/cli/console.rb +0 -10
  397. data/lib/cli/generate.rb +0 -37
  398. data/lib/cli/hecks-cli.rb +0 -27
  399. data/lib/cli/test.rb +0 -57
  400. data/lib/console/commands.rb +0 -8
  401. data/lib/console/hecks-console.rb +0 -1
  402. data/lib/packager/README.md +0 -0
  403. data/lib/packager/app_runner.rb +0 -21
  404. data/lib/packager/args.rb +0 -26
  405. data/lib/packager/compatibility/fixnum.rb +0 -6
  406. data/lib/packager/hecks.rb +0 -39
  407. data/lib/packager/query_runner.rb +0 -21
  408. data/lib/packager/resources/Dockerfile +0 -11
  409. data/lib/packager/resources/app_binary +0 -7
  410. data/lib/packager/resources/bundle_config +0 -3
  411. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz +0 -0
  412. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-osx.tar.gz +0 -0
  413. data/lib/serverless/Domain +0 -32
  414. data/lib/serverless/cli.rb +0 -75
  415. data/lib/serverless/resources/command_name.js +0 -5
  416. data/lib/serverless/resources/environment.js +0 -7
  417. data/lib/serverless/resources/handler.js.tt +0 -28
  418. data/lib/serverless/resources/run_binary.js +0 -22
  419. data/lib/serverless/resources/serverless.yml +0 -20
@@ -0,0 +1,527 @@
1
+ require_relative "../bluebook/expression/resolver"
2
+ require_relative "../bluebook/expression/evaluator"
3
+
4
+ module Hecks
5
+ module Fuzzing
6
+ # Phase 7 (equivalence-gap plan) — the one place in this codebase a
7
+ # genuine "true for all inputs," not merely "true for every input we
8
+ # happened to sample," claim is realistically achievable:
9
+ # `Hecks::Bluebook::Expression::{Evaluator,Resolver}`. It's finite-
10
+ # grammar and side-effect-free (Evaluator::PROJECTION/Resolver's own
11
+ # `interpret` never touch a database, a clock, or randomness), but NOT
12
+ # small enough for naive enumeration — ~20 node-producing constructs
13
+ # across two mutually-recursive grammar layers, unbounded recursion
14
+ # through dotted `Lookup` paths, `Addition`, and nested block
15
+ # predicates (`Resolver → Evaluator → Resolver → …`). A flat "every
16
+ # string up to length N" or "every node combination up to depth N with
17
+ # no type filter" explodes well past anything runnable long before
18
+ # depth 3 (two bugs this session lived in exactly this sublanguage —
19
+ # the `formerly_known_as`/`unmark_scalar` family, and the
20
+ # `canonical_form.rb` aliasing bug — neither would have been caught by
21
+ # sampling alone, which is the whole reason this phase exists).
22
+ #
23
+ # TYPE-DIRECTED, not exhaustive-over-strings: this generator only ever
24
+ # recurses into a sub-expression whose OWN type the surrounding
25
+ # construct actually accepts (never `"x".modulo(true)`) — mirroring,
26
+ # by hand, the same admitted-receiver-class tables `Resolver`'s own
27
+ # `interpret` enforces at runtime (`SizedType`/`ToStringType`/
28
+ # `IncludeHaystack` from vocabulary.bluebook, plus the ad hoc
29
+ # `is_a?`/`respond_to?` checks `numeric`/`size_of`/`string_of`/etc.
30
+ # apply that vocabulary.bluebook itself doesn't declare). Restricted
31
+ # to a small representative literal palette (TYPE_LEAVES, below) and a
32
+ # small synthetic attribute set per type (SYNTHETIC_ATTRS), bounded to
33
+ # MAX_DEPTH — that combination is what keeps this tractable (thousands
34
+ # to tens of thousands of cases, not millions) while still being a
35
+ # real, defensible "proven for every well-typed expression up to depth
36
+ # 3" claim for exactly the sublanguage that already bit this project
37
+ # twice.
38
+ #
39
+ # WHAT "PROVEN" MEANS HERE, PRECISELY: every expression this generator
40
+ # produces is well-typed BY CONSTRUCTION (every sub-expression's type
41
+ # matches what its parent construct actually accepts, per this file's
42
+ # own TYPE_RULES). Interpreting a well-typed expression must never
43
+ # raise anything OTHER than `Hecks::Bluebook::Expression::
44
+ # EvaluationError` — that class alone is the sublanguage's own real
45
+ # refusal vocabulary (a divisor that happens to be zero, a `Lookup`
46
+ # this run's synthetic state doesn't happen to carry); anything else
47
+ # escaping (a raw `TypeError`/`NoMethodError`/`ZeroDivisionError`) is
48
+ # exactly the crash-signature this whole file's own header comments
49
+ # (resolver.rb, block_predicates.rb) name as the historical bug class
50
+ # this sublanguage keeps almost-but-not-quite avoiding. This does NOT
51
+ # prove the interpreter computes the SEMANTICALLY right answer (that
52
+ # needs a second, independent oracle — out of scope here, exactly the
53
+ # way rust_conformance_fuzz_spec.rb is the cross-RUNTIME half of this
54
+ # same idea) — only that it never crashes on well-typed input.
55
+ module BoundedExhaustiveExpressions
56
+ module_function
57
+
58
+ # `:numeric` deliberately covers BOTH Integer and Float — the
59
+ # resolver's own `numeric`/`require_number` never distinguish them
60
+ # (resolver.rb) — so a generator that kept them as separate types
61
+ # would be modeling a distinction the grammar itself doesn't make.
62
+ TYPES = %i[numeric string boolean array nil_type].freeze
63
+
64
+ # THE PALETTE — deliberately small (this file's own header: "a small
65
+ # representative literal palette," matching the plan's own `{0, 1,
66
+ # -1}` example). Widening it doesn't test a different SHAPE of
67
+ # expression, only more values through the same shapes — real
68
+ # boundary-value coverage (Bignum, NaN, empty string, unicode) is
69
+ # PRD 05's job (spec/runtime/numeric_boundary_spec.rb,
70
+ # ValueGenerator's own edge-case tables), not this generator's.
71
+ TYPE_LEAVES = {
72
+ numeric: ["0", "1", "-1"].freeze,
73
+ string: ['""', '"a"'].freeze,
74
+ boolean: %w[true false].freeze,
75
+ nil_type: %w[nil].freeze
76
+ }.freeze
77
+
78
+ # TWO SYNTHETIC ATTRIBUTES PER TYPE (the plan's own "2-3 fake
79
+ # attributes per type") — real names are irrelevant to `Resolver
80
+ # .parse` (it never touches state/attrs at all, confirmed directly:
81
+ # only `Lookup#interpret` does) and irrelevant to `interpret` beyond
82
+ # needing to resolve against WHATEVER synthetic state this file
83
+ # supplies (`synthetic_state`, below) — so any non-colliding,
84
+ # non-suffix-shaped name works. Never named the same as a
85
+ # `BLOCK_PARAM` (below) — `Resolver#fetch`'s own `attrs`-wins-over-
86
+ # `state` precedence means a block parameter SHADOWS a same-named
87
+ # top-level attribute for the span of its own predicate, a real
88
+ # hazard this file sidesteps by construction rather than exercising
89
+ # it here (a real, cited resolver.rb hazard, not a gap in THIS
90
+ # generator's own coverage claim).
91
+ SYNTHETIC_ATTRS = {
92
+ numeric: %w[num_a num_b].freeze,
93
+ string: %w[str_a str_b].freeze,
94
+ boolean: %w[bool_a bool_b].freeze,
95
+ array: %w[arr_num arr_str].freeze
96
+ }.freeze
97
+
98
+ # `arr_num`/`arr_str` — ELEMENT type per array attribute, needed so
99
+ # a block predicate's own bound parameter (`BLOCK_PARAM`, below) is
100
+ # generated against the right leaf/production set for whatever it's
101
+ # actually bound to.
102
+ ARRAY_ELEMENT_TYPE = { "arr_num" => :numeric, "arr_str" => :string }.freeze
103
+
104
+ BLOCK_PARAM = "el"
105
+
106
+ MAX_DEPTH = 3
107
+
108
+ # `state`/`attrs` — a plain Hash satisfies BOTH (confirmed directly:
109
+ # `Resolver#known?` degrades to a bare index check when `state`
110
+ # doesn't `respond_to?(:key?)`, and `fetch` only ever needs
111
+ # `attrs.key?`/`attrs[]`.). Split ACROSS both, matching a real
112
+ # dispatch's own shape (some names come from the record's stored
113
+ # state, some from the command's own args) — not load-bearing for
114
+ # THIS proof (attrs wins regardless), but keeps the synthetic input
115
+ # closer to what `Admissibility` actually builds, in case anything
116
+ # here is reused for a future deeper check.
117
+ #
118
+ # THE VO-VS-SCALAR UNWRAP CASE, DELIBERATELY EXERCISED — half of
119
+ # `num_a`/`str_a`/`bool_a`/one array attribute's elements are bare
120
+ # scalars, the other half (`num_b`/`str_b`/`bool_b`/the other
121
+ # array's elements) are wrapped `{value: X}` — the single-field
122
+ # Value Object shape `Resolver#unwrap_scalar` auto-collapses.
123
+ # resolver.rb's own most serious historical bug in this file (the
124
+ # "UPDATE 2026-08-18" comment: a dotted walk that landed on an
125
+ # un-unwrapped VO compared `false` against every literal, SILENTLY,
126
+ # never raising) lived exactly at this boundary — a generator that
127
+ # only ever supplied bare scalars would never exercise the code
128
+ # path that bug lived in at all.
129
+ # `SingleFieldVO` — NOT a plain Hash, deliberately: `Resolver
130
+ # #unwrap_scalar`'s own guard is `value.respond_to?(:to_h) &&
131
+ # !value.is_a?(Hash) && !value.is_a?(Array)` — it exists
132
+ # specifically to collapse a real hydrated `Runtime::Value`
133
+ # instance (which responds to `#to_h` but is never itself a bare
134
+ # Hash) down to its lone scalar field, and just as deliberately
135
+ # leaves an ACTUAL Hash alone (a genuinely un-hydrated, multi-
136
+ # field record has no single scalar to collapse to). A first
137
+ # version of this generator's own synthetic state used plain
138
+ # `{value: X}` Hashes to stand in for a single-field VO — which
139
+ # `is_a?(Hash)` is true for, so `unwrap_scalar` correctly left them
140
+ # WRAPPED, and every VO-typed synthetic attribute then failed
141
+ # `Addition`/`Compare`/every scalar-typed operation with "expects a
142
+ # number, got {\"value\":5}" — a bug in THIS generator's own
143
+ # synthetic state, not in `Resolver`, caught only by noticing that
144
+ # `num_b == 5` (`num_b` a plain `{value: 5}` Hash) evaluated to
145
+ # `false` instead of `true` before this fix.
146
+ SingleFieldVO = Struct.new(:value) do
147
+ def to_h = { value: value }
148
+ end
149
+
150
+ def synthetic_state
151
+ {
152
+ num_a: 3,
153
+ num_b: SingleFieldVO.new(5),
154
+ str_a: "hello",
155
+ str_b: SingleFieldVO.new("world"),
156
+ bool_a: true,
157
+ bool_b: SingleFieldVO.new(false),
158
+ arr_num: [1, SingleFieldVO.new(2), 3],
159
+ arr_str: ["x", SingleFieldVO.new("y"), "z"]
160
+ }
161
+ end
162
+
163
+ def synthetic_attrs = {}
164
+
165
+ # `leaves(type, depth)` — every TERMINAL (non-recursive) expression
166
+ # of `type`: the fixed literal palette plus every synthetic
167
+ # attribute NAME declared for that type (never their VALUES — this
168
+ # generates TEXT, the same source a real `given`/`invariant` author
169
+ # would write; `synthetic_state`, above, is what gives those names
170
+ # meaning at `interpret` time).
171
+ def leaves(type)
172
+ (TYPE_LEAVES[type] || []) + (SYNTHETIC_ATTRS[type] || []) + Array(bound_leaves[type])
173
+ end
174
+
175
+ # A STACK, not a single slot — a NESTED block predicate (the real
176
+ # corpus already does this two levels deep, roster.bluebook's own
177
+ # `seats.any? { |s| assignments.none? { |a| … } }`) pushes a second
178
+ # bound leaf while the outer one is still active. Both share the
179
+ # SAME `BLOCK_PARAM` spelling ("el") — real Ruby block-parameter
180
+ # shadowing (the inner `el` simply shadows the outer one within its
181
+ # own predicate text), which this generator treats as legal on
182
+ # purpose: `resolver.rb`'s own `interpret_with_element` binds fresh
183
+ # `attrs.merge(param => element)` per level regardless of what the
184
+ # outer level already bound, so a shadowed name still interprets
185
+ # correctly — this generator is proving "does it crash," not "is
186
+ # every generated predicate semantically distinct."
187
+ def bound_leaves = @bound_leaves ||= Hash.new { |h, k| h[k] = [] }
188
+
189
+ def with_element_leaf(element_type)
190
+ bound_leaves[element_type] << BLOCK_PARAM
191
+ cache.delete_if { |(type, _depth), _| type == element_type }
192
+ yield
193
+ ensure
194
+ bound_leaves[element_type].pop
195
+ cache.delete_if { |(type, _depth), _| type == element_type }
196
+ end
197
+
198
+ # `productions(type, depth)` — every expression of `type` reachable
199
+ # in AT MOST `depth` recursive steps, MEMOIZED (the same sub-
200
+ # expression set is reused at every enclosing recursion, so without
201
+ # memoizing, cost would compound multiplicatively per level instead
202
+ # of additively). `depth` 0 is exactly `leaves(type)`; each
203
+ # increment adds every construct THIS FILE'S OWN TYPE_RULES (below)
204
+ # says can produce `type`, built from `depth - 1` sub-expressions.
205
+ def productions(type, depth)
206
+ cache[[type, depth]] ||= begin
207
+ base = leaves(type)
208
+ depth <= 0 ? base : (base + recursive_productions(type, depth)).uniq
209
+ end
210
+ end
211
+
212
+ def cache = @cache ||= {}
213
+
214
+ def recursive_productions(type, depth)
215
+ case type
216
+ when :numeric then numeric_productions(depth)
217
+ when :string then string_productions(depth)
218
+ when :boolean then boolean_productions(depth)
219
+ when :array then array_productions(depth)
220
+ when :nil_type then [] # no recursive producer of nil in this grammar — Find's "not found" is a runtime OUTCOME, not a distinct construct to render as source text
221
+ else raise ArgumentError, "no production rule for type #{type.inspect}"
222
+ end
223
+ end
224
+
225
+ # EVERY internal use of a sub-expression LIST (as opposed to the
226
+ # single final list `productions(type, depth)` returns to its own
227
+ # caller) goes through this, not `productions` directly — the
228
+ # actual thing that made an early version of this generator explode
229
+ # past a million cases by depth 3 wasn't `pairs`' own cross product
230
+ # (already sampled) but the dozen-plus LINEAR `flat_map`/`map`
231
+ # passes `boolean_productions` alone makes over `str`/`num`/`sub` —
232
+ # each individually harmless, but an unbounded few-thousand-item
233
+ # list run through a dozen of them, feeding the NEXT depth's own
234
+ # dozen passes, compounds fast. Bounding every INPUT list (not the
235
+ # final output) keeps the shape diversity `sample`'s even-spacing
236
+ # already preserves while keeping growth roughly linear in depth
237
+ # instead of combinatorial.
238
+ def bounded(type, depth) = sample(productions(type, depth))
239
+
240
+ # NUMERIC ← Addition(numeric, numeric) | Modulo(numeric, numeric) |
241
+ # Size(sized) | First/Last(numeric array). `Size` returns an
242
+ # Integer for a String OR an Array receiver alike (`SizedType` —
243
+ # `size_of`, resolver.rb) — both sides generated here.
244
+ def numeric_productions(depth)
245
+ sub = bounded(:numeric, depth - 1)
246
+ # `Modulo`'s own RECEIVER (not its argument — that side already
247
+ # goes through `Resolver#matching_paren`'s own fresh, self-
248
+ # contained re-parse, confirmed safe for any numeric shape
249
+ # including another `Addition`/`Modulo`) has the identical
250
+ # "`Addition` mis-parsed as a suffix receiver" hazard
251
+ # `resolver_numeric_leaves`'s own comment documents for `.to_s`/
252
+ # `.positive?` — `Resolver.parse` tries `split_addition` BEFORE
253
+ # `match_call`, so `"0 + 0.modulo(1)"` (meant as `(0 + 0)
254
+ # .modulo(1)`) actually parses as `0 + (0.modulo(1))`. Restricted
255
+ # to `resolver_numeric_leaves` on the RECEIVER side only — the
256
+ # argument stays the full, unrestricted numeric set.
257
+ pairs(sub).map { |a, b| "#{a} + #{b}" } +
258
+ cross(resolver_numeric_leaves(depth - 1), sub).map { |a, b| "#{a}.modulo(#{b})" } +
259
+ bounded(:string, depth - 1).map { |s| "#{s}.size" } +
260
+ bounded(:array, depth - 1).map { |a| "#{a}.size" } +
261
+ sample(numeric_array_productions(depth - 1)).flat_map { |a| ["#{a}.first", "#{a}.last"] }
262
+ end
263
+
264
+ # A "boolean" IN RESOLVER'S OWN SENSE — safe to embed as the
265
+ # RECEIVER of a trailing Resolver-level suffix (`.to_s`, and
266
+ # anywhere else a boolean-typed VALUE, as opposed to a boolean-
267
+ # typed EXPRESSION, is wanted). FOUND LIVE, the same way the
268
+ # nested-`.modulo` bug was: `Resolver.parse` has NO KNOWLEDGE of
269
+ # `==`/`<`/`&&`/`||`/leading `!`/`.include?` AT ALL — those are
270
+ # `Evaluator`'s OWN, entirely separate parsing layer, stripped off
271
+ # BEFORE anything reaches `Resolver.parse` at all (confirmed
272
+ # directly: `Resolver.parse("str_b < str_a")` — no `.` anywhere in
273
+ # that text for any suffix regex to anchor on — falls through
274
+ # every leaf regex to the `Lookup` catch-all, exactly like the
275
+ # nested-modulo bug did). `bounded(:boolean, depth)`'s FULL set
276
+ # includes `Compare`/`Include`/`Or`/`And`/`Not` — genuinely boolean-
277
+ # TYPED at `interpret` time, but `Evaluator`-level SYNTAX, not
278
+ # something `Resolver.parse` can ever recognize as a receiver no
279
+ # matter how it's parenthesized (confirmed directly too: `Resolver
280
+ # .parse` never strips parens at all — `"(3)"` alone already fails
281
+ # to resolve). This is a REAL, PERMANENT boundary of the actual
282
+ # grammar (this whole sublanguage's own two-layer split, not a
283
+ # limitation to work around) — a `given`/`invariant` author simply
284
+ # cannot write `(a < b).to_s` in this language, ever, no matter how
285
+ # they punctuate it. So this generator doesn't either: only
286
+ # RESOLVER-LEVEL boolean-producing constructs (bare literals/
287
+ # lookups, `SignTest`, `Empty`, `Presence`, `MatchesRegex`,
288
+ # `StartsWith`/`EndsWith`, `BlockPredicate` — every one of them
289
+ # parsed via a suffix regex INSIDE `Resolver.parse` itself, per
290
+ # this generator's own design report) are eligible here.
291
+ def resolver_boolean_leaves(depth)
292
+ leaves(:boolean) +
293
+ resolver_numeric_leaves(depth).flat_map { |n| ["#{n}.positive?", "#{n}.negative?", "#{n}.zero?"] } +
294
+ bounded(:string, depth).flat_map { |s| ["#{s}.present?", "#{s}.blank?", "#{s}.match?(/a/)", "#{s}.start_with?(\"a\")", "#{s}.end_with?(\"a\")"] } +
295
+ (sample(numeric_array_productions(depth)) + sample(string_array_productions(depth)) + bounded(:array, depth)).map { |x| "#{x}.empty?" } +
296
+ sample(block_predicate_productions(depth))
297
+ end
298
+
299
+ # A "numeric" IN RESOLVER'S OWN SENSE — `Addition`'s twin of
300
+ # `resolver_boolean_leaves`'s own restriction, found the identical
301
+ # way: `"0 + 0.to_s"` (meant as `(0 + 0).to_s`) actually parses as
302
+ # `0 + (0.to_s)`, because `Resolver.parse` tries `split_addition`
303
+ # BEFORE `.to_s`'s own suffix regex in its dispatch order — the `+`
304
+ # "wins" the split before the suffix ever gets a chance to anchor
305
+ # on its own receiver boundary. Confirmed to have zero real-corpus
306
+ # precedent either (`grep`, no `bluebook` file anywhere chains a
307
+ # method or sign-test onto a parenthesized addition) — the same
308
+ # verdict as `resolver_boolean_leaves`'s own comparisons/`&&`/`||`:
309
+ # a real permanent grammar boundary (`Resolver.parse` never strips
310
+ # parens, confirmed directly, so no amount of punctuation rescues
311
+ # `(a + b).to_s`), not a bug to fix in the resolver for a shape
312
+ # nothing has ever needed. `Modulo`/`Size`/`First`/`Last` stay IN
313
+ # (each is its own trailing `.method(...)`/`.method` call, so
314
+ # `Resolver.parse`'s greedy `(.+)\.suffix\z` regexes correctly
315
+ # isolate them as a receiver regardless of what precedes them —
316
+ # only bare top-level `+` has this problem).
317
+ def resolver_numeric_leaves(depth)
318
+ return leaves(:numeric) if depth <= 0
319
+
320
+ # SELF-referential on purpose, one depth down — `Modulo`'s own
321
+ # receiver needs the SAME restriction `resolver_numeric_leaves`
322
+ # exists to express in the first place (see its own header
323
+ # comment); the argument stays the full, unrestricted set, same
324
+ # as `numeric_productions`' identical split right above.
325
+ leaves(:numeric) +
326
+ cross(resolver_numeric_leaves(depth - 1), bounded(:numeric, depth - 1)).map { |a, b| "#{a}.modulo(#{b})" } +
327
+ bounded(:string, depth).map { |s| "#{s}.size" } +
328
+ bounded(:array, depth).map { |a| "#{a}.size" } +
329
+ sample(numeric_array_productions(depth)).flat_map { |a| ["#{a}.first", "#{a}.last"] }
330
+ end
331
+
332
+ # STRING ← ToS(numeric | boolean | nil | string) | First/Last(string
333
+ # array). `Split` produces an ARRAY, never a String (resolver.rb's
334
+ # own `apply_split`) — deliberately NOT listed as a string producer
335
+ # here; that would be exactly the "vocabulary-legal but grammar-
336
+ # can't-actually-produce-it" mistake this sublanguage's own
337
+ # `ArrayLiteral` bug (§1 of this generator's own design report) was
338
+ # found from, inverted.
339
+ def string_productions(depth)
340
+ sample(resolver_numeric_leaves(depth - 1)).map { |n| "#{n}.to_s" } +
341
+ sample(resolver_boolean_leaves(depth - 1)).map { |b| "#{b}.to_s" } +
342
+ bounded(:nil_type, depth - 1).map { |n| "#{n}.to_s" } +
343
+ bounded(:string, depth - 1).map { |s| "#{s}.to_s" } +
344
+ sample(string_array_productions(depth - 1)).flat_map { |a| ["#{a}.first", "#{a}.last"] }
345
+ end
346
+
347
+ # ARRAY ← Split(string, separator) | ArrayLiteral[same-type
348
+ # elements] | the synthetic array attributes (already in `leaves`).
349
+ # Kept deliberately small relative to numeric/string/boolean —
350
+ # `array` is overwhelmingly a RECEIVER type in this grammar (`.size`
351
+ # /`.any?`/`.include?`/…), rarely a produced VALUE; the two real
352
+ # producers are enough to exercise every array-typed consumer
353
+ # elsewhere in this file at least once via a non-leaf path.
354
+ def array_productions(depth)
355
+ return [] if depth <= 0
356
+
357
+ bounded(:string, depth - 1).map { |s| "#{s}.split(\",\")" } +
358
+ [numeric_array_literal(depth - 1), string_array_literal(depth - 1)]
359
+ end
360
+
361
+ def numeric_array_literal(depth) = "[#{bounded(:numeric, depth).first(2).join(', ')}]"
362
+ def string_array_literal(depth) = "[#{bounded(:string, depth).first(2).join(', ')}]"
363
+
364
+ # Arrays KNOWN (by construction, not merely by type) to hold numeric
365
+ # elements — the two synthetic array attributes (`ARRAY_ELEMENT_TYPE`
366
+ # tags `arr_num`) plus any numeric-array LITERAL this same depth
367
+ # budget can build. `.first`/`.last`/block predicates need to know
368
+ # the ELEMENT type, which plain `array_productions` doesn't carry —
369
+ # this (and `string_array_productions`, its twin) is how that extra
370
+ # bit of type information flows without inventing a second, richer
371
+ # AST just to carry it.
372
+ def numeric_array_productions(depth)
373
+ ["arr_num", numeric_array_literal(depth)]
374
+ # a Split of a string never yields numeric elements — no third
375
+ # entry here, deliberately, not omitted by oversight (see
376
+ # `string_array_productions`, its non-empty twin, right below).
377
+ end
378
+
379
+ def string_array_productions(depth)
380
+ ["arr_str", string_array_literal(depth)] + bounded(:string, depth).map { |s| "#{s}.split(\",\")" }
381
+ end
382
+
383
+ # BOOLEAN ← every comparison/predicate construct in the grammar.
384
+ # This is where almost all of the sublanguage's OWN real surface
385
+ # lives — a `given`/`invariant`/`ensures` body is ALWAYS boolean-
386
+ # typed at its own top level (`Evaluator.truthy?`), so this is also
387
+ # the set `all_predicates` (below) draws its top-level cases from
388
+ # directly.
389
+ def boolean_productions(depth)
390
+ sub = bounded(:boolean, depth - 1)
391
+ num = bounded(:numeric, depth - 1)
392
+ str = bounded(:string, depth - 1)
393
+
394
+ pairs(num).map { |a, b| "#{a} == #{b}" } +
395
+ pairs(num).map { |a, b| "#{a} > #{b}" } +
396
+ pairs(str).map { |a, b| "#{a} == #{b}" } +
397
+ pairs(str).map { |a, b| "#{a} < #{b}" } +
398
+ # `resolver_numeric_leaves`, not the raw `num` `==`/`>` above
399
+ # safely use — SignTest's own suffix match
400
+ # (`match_suffix`/`.positive?` et al.) is a plain trailing-
401
+ # string strip, not `Evaluator`'s comparison-scanning, so an
402
+ # `Addition` in `num` would hit the exact "`0 + 0.to_s`" mis-
403
+ # split this generator's own `resolver_numeric_leaves` comment
404
+ # documents (`"0 + 0.positive?"` would parse as `0 + (0
405
+ # .positive?)`, not `(0 + 0).positive?`).
406
+ resolver_numeric_leaves(depth - 1).flat_map { |n| ["#{n}.positive?", "#{n}.negative?", "#{n}.zero?"] } +
407
+ (str + sample(numeric_array_productions(depth - 1)) + sample(string_array_productions(depth - 1)) + bounded(:array, depth - 1))
408
+ .map { |x| "#{x}.empty?" } +
409
+ str.map { |s| "#{s}.match?(/a/)" } +
410
+ str.flat_map { |s| ["#{s}.present?", "#{s}.blank?"] } +
411
+ str.flat_map { |s| ["#{s}.start_with?(\"a\")", "#{s}.end_with?(\"a\")"] } +
412
+ pairs(sub).flat_map { |a, b| ["#{a} && #{b}", "#{a} || #{b}"] } +
413
+ sub.map { |b| "!#{b}" } +
414
+ block_predicate_productions(depth - 1) +
415
+ include_productions(depth - 1)
416
+ end
417
+
418
+ # `.all?`/`.any?`/`.none?` over EACH known-element-typed array
419
+ # source, with a predicate body drawn from `boolean_productions` at
420
+ # ONE LESS depth, evaluated against `BLOCK_PARAM` bound to the
421
+ # array's own element type — this is the ONLY place `Resolver` and
422
+ # `Evaluator` are truly mutually recursive (a `BlockPredicate`'s own
423
+ # `predicate` field is a full `Evaluator` AST, not a `Resolver`
424
+ # leaf — confirmed directly), and the only construct in this whole
425
+ # generator that can nest into ANOTHER block predicate (the real
426
+ # corpus already does this two levels deep —
427
+ # `examples/roster/bluebook/roster.bluebook`'s own `seats.any? { |s|
428
+ # assignments.none? { |a| … } }`).
429
+ def block_predicate_productions(depth)
430
+ return [] if depth.negative?
431
+
432
+ [
433
+ ["arr_num", :numeric], [numeric_array_literal(depth), :numeric],
434
+ ["arr_str", :string], [string_array_literal(depth), :string]
435
+ ].flat_map do |array_text, element_type|
436
+ predicate_bodies(element_type, depth).flat_map do |body|
437
+ %w[all? any? none?].map { |mode| "#{array_text}.#{mode} { |#{BLOCK_PARAM}| #{body} }" }
438
+ end
439
+ end
440
+ end
441
+
442
+ # The predicate body a block gets — `boolean_productions`, but with
443
+ # `BLOCK_PARAM` (bound to `element_type`) ALSO admitted as a leaf,
444
+ # since inside the block it is exactly as usable as any other
445
+ # `Lookup` name (resolver.rb's own `interpret_with_element`: the
446
+ # bound element joins `attrs` for the span of one evaluation, no
447
+ # different from a top-level attribute — confirmed directly).
448
+ # `sample`d for the same reason every other internal list is — this
449
+ # feeds THREE more constructs per body (`all?`/`any?`/`none?`) times
450
+ # FOUR array sources, so an unbounded body list here is exactly the
451
+ # kind of multiplier this file's own `bounded` comment warns about.
452
+ def predicate_bodies(element_type, depth)
453
+ sample(with_element_leaf(element_type) { boolean_productions(depth) })
454
+ end
455
+
456
+ # `haystack.include?(needle)` — String haystack needs a String
457
+ # needle (raises otherwise, evaluator.rb's own `includes?`); Array
458
+ # haystack admits ANY needle type (compared via `equal?`, itself
459
+ # numeric-coerced-first). Both sides generated here, matching
460
+ # `Vocabulary::IncludeHaystack` exactly.
461
+ def include_productions(depth)
462
+ str = bounded(:string, depth)
463
+ pairs(str).map { |haystack, needle| "#{haystack}.include?(#{needle})" } +
464
+ sample(numeric_array_productions(depth)).product(bounded(:numeric, depth)).map { |arr, needle| "#{arr}.include?(#{needle})" } +
465
+ sample(string_array_productions(depth)).product(bounded(:string, depth)).map { |arr, needle| "#{arr}.include?(#{needle})" }
466
+ end
467
+
468
+ # SAMPLED, NOT A FULL CROSS PRODUCT — a full `list.product(list)`
469
+ # is what actually explodes this generator (numeric productions
470
+ # alone hit 8000+ by depth 2; squaring THAT for `&&`/`==` pairs is
471
+ # where "tens of thousands" becomes tens of millions). The
472
+ # combinatorics genuinely don't buy proof coverage: proving `Or`/
473
+ # `And`/`Not` themselves never crash needs ONE representative pair
474
+ # per depth (`evaluator.rb`'s own `interpret` does zero type-
475
+ # dependent work for those three — `interpret(left) || interpret
476
+ # (right)`, plain Ruby, no coercion, no receiver-type check at
477
+ # all — so a crash there could only come from LEFT or RIGHT
478
+ # themselves, already covered by testing every operand on its own
479
+ # elsewhere in this same predicate set). `Compare`/`Include`/
480
+ # `Addition`/`Modulo` genuinely DO real per-pair type coercion
481
+ # (`Evaluator.apply`/`less_than`/`Resolver.add`/`apply_modulo`), so
482
+ # THOSE stay covered across every construct SHAPE at every depth —
483
+ # just sampled evenly across each side's own operand set (leaves
484
+ # AND deep productions alike, not just whichever the list happens
485
+ # to enumerate first) rather than every possible pairing of them.
486
+ SAMPLE_CAP = 14
487
+
488
+ def sample(list) = list.size <= SAMPLE_CAP ? list : list.each_slice(list.size.fdiv(SAMPLE_CAP).ceil).map(&:first)
489
+
490
+ def pairs(list)
491
+ sampled = sample(list)
492
+ sampled.product(sampled)
493
+ end
494
+
495
+ # `pairs`' own two-different-lists twin — used wherever the LEFT
496
+ # and RIGHT of a construct have genuinely different safety
497
+ # requirements (`Modulo`'s own receiver vs. argument, below) and
498
+ # squaring the SAME sampled list wouldn't be correct.
499
+ def cross(left, right) = sample(left).product(sample(right))
500
+
501
+ # THE FULL SET — every boolean-typed expression up to `MAX_DEPTH`,
502
+ # deduplicated (many shorter expressions are also produced, re-
503
+ # wrapped, at every deeper level — `.uniq` inside `productions`
504
+ # already collapses most of that; this is the final pass over the
505
+ # complete depth-`MAX_DEPTH` set specifically).
506
+ def all_predicates(max_depth = MAX_DEPTH)
507
+ productions(:boolean, max_depth).uniq
508
+ end
509
+
510
+ # Interprets one predicate against the shared synthetic
511
+ # state/attrs, returning `{ok: true, result: ...}` on any outcome
512
+ # `Evaluator.call` itself can express (a real true/false answer, OR
513
+ # a clean `EvaluationError` — both are the sublanguage WORKING
514
+ # correctly, never a finding) and `{ok: false, error: ...}` only
515
+ # for anything else escaping — the one shape this whole file exists
516
+ # to prove never happens for well-typed input.
517
+ def check(expr)
518
+ result = Hecks::Bluebook::Expression::Evaluator.call(expr, synthetic_state, synthetic_attrs)
519
+ { ok: true, result: result }
520
+ rescue Hecks::Bluebook::Expression::EvaluationError => e
521
+ { ok: true, result: :refused, message: e.message }
522
+ rescue StandardError => e
523
+ { ok: false, error: e }
524
+ end
525
+ end
526
+ end
527
+ end
@@ -0,0 +1,86 @@
1
+ module Hecks
2
+ module Fuzzing
3
+ # A value that is DELIBERATELY THE WRONG SHAPE for the attribute it is handed
4
+ # to — the sibling of ValueGenerator, which only ever builds values that satisfy
5
+ # a declared type.
6
+ #
7
+ # That limit is why the fuzzer could not reach the space a hand-written
8
+ # adversarial corpus covers. spec/corpus/banking.json got there one step at a
9
+ # time, by hand : a value object where an Integer is declared, a Float where an
10
+ # Integer is declared, a numeral wearing quotes, an attribute missing entirely,
11
+ # an attribute the command never declared. One of those — a hash reaching a
12
+ # refusal message — is the exact shape that exposed refusal wording drift
13
+ # (a composite rendered with `inspect` at one site and JSON at another —
14
+ # Rendering's whole story), and no generated value
15
+ # could ever have produced it.
16
+ #
17
+ # These are expected to be REFUSED, and that is the point : a refusal is an
18
+ # answer, and its wording is pinned byte-for-byte. The bugs live in
19
+ # the sentence, not in the happy path.
20
+ module InvalidValueGenerator
21
+ module_function
22
+
23
+ # Each kind names a real confusion, not random noise. Ordered roughly by how
24
+ # often this codebase has actually been bitten by it.
25
+ KINDS = %i[
26
+ object_for_scalar
27
+ scalar_for_object
28
+ float_for_integer
29
+ numeral_string
30
+ array_for_scalar
31
+ boolean_for_string
32
+ null
33
+ ].freeze
34
+
35
+ def corrupt(attribute, aggregate, random:)
36
+ kind = kinds_for(attribute, aggregate).sample(random: random)
37
+ build(kind, attribute, aggregate, random: random)
38
+ end
39
+
40
+ # Only the confusions that MEAN anything for this attribute. Offering
41
+ # `scalar_for_object` for a plain Integer would just be a second spelling of
42
+ # `numeral_string`, and a kind that cannot be wrong for the attribute it is
43
+ # handed teaches the corpus nothing.
44
+ def kinds_for(attribute, aggregate)
45
+ value_object = aggregate.value_object(attribute.type.to_s)
46
+ return %i[scalar_for_object array_for_scalar null] if value_object
47
+ return %i[object_for_scalar float_for_integer numeral_string array_for_scalar null] if attribute.type.to_s == "Integer"
48
+
49
+ %i[object_for_scalar array_for_scalar boolean_for_string null]
50
+ end
51
+
52
+ def build(kind, attribute, aggregate, random:)
53
+ case kind
54
+ when :object_for_scalar then { "cents" => random.rand(1..1000) }
55
+ when :scalar_for_object then scalar_for(attribute, aggregate, random: random)
56
+ when :float_for_integer then random.rand(1.0..100.0).round(2)
57
+ when :numeral_string then random.rand(1..1000).to_s
58
+ when :array_for_scalar then [random.rand(1..10), random.rand(1..10)]
59
+ when :boolean_for_string then random.rand(2).zero?
60
+ else nil
61
+ end
62
+ end
63
+
64
+ # A bare scalar where a value object is declared. A SINGLE-FIELD value object
65
+ # legitimately accepts one (that is the standing-in rule every domain relies
66
+ # on), so the interesting case is a value object with SEVERAL fields, where a
67
+ # scalar cannot stand for anything and the refusal has to say so.
68
+ def scalar_for(attribute, aggregate, random:)
69
+ value_object = aggregate.value_object(attribute.type.to_s)
70
+ return "a bare scalar" unless value_object && value_object.attributes.size == 1
71
+
72
+ # One field, so a scalar is legal — corrupt the FIELD's own type instead,
73
+ # which is still a shape the attribute cannot accept.
74
+ { value_object.attributes.first.name.to_s => ["nested", "array"] }
75
+ end
76
+
77
+ # An attribute the command never declared. `refuse_unknown_arguments` is a
78
+ # real dispatch step (Vocabulary::AggregateDispatchOrder), and nothing
79
+ # generated had ever exercised it.
80
+ def undeclared_argument(random:)
81
+ name = %w[colour flavour rank note].sample(random: random)
82
+ [name, %w[red loud third scribbled].sample(random: random)]
83
+ end
84
+ end
85
+ end
86
+ end