hecks 1.2.0 → 1.4.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 (358) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hecks/adapters/driven/claude_code.rb +7 -7
  3. data/lib/hecks/adapters/driven/d1.rb +187 -23
  4. data/lib/hecks/adapters/driven/folder.rb +10 -10
  5. data/lib/hecks/adapters/driven/google_authentication.rb +8 -8
  6. data/lib/hecks/adapters/driven/governance_authorization.rb +31 -13
  7. data/lib/hecks/adapters/driven/heki/journal.rb +60 -2
  8. data/lib/hecks/adapters/driven/heki/saga_store.rb +5 -5
  9. data/lib/hecks/adapters/driven/heki.rb +13 -7
  10. data/lib/hecks/adapters/driven/identity_registry.rb +2 -2
  11. data/lib/hecks/adapters/driven/in_memory_ordering.rb +3 -3
  12. data/lib/hecks/adapters/driven/lambda/client.rb +34 -9
  13. data/lib/hecks/adapters/driven/lambda.rb +39 -33
  14. data/lib/hecks/adapters/driven/local_storage.rb +17 -10
  15. data/lib/hecks/adapters/driven/memory.rb +205 -9
  16. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +1 -1
  17. data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
  18. data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
  19. data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
  20. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
  21. data/lib/hecks/adapters/driven/postgres.rb +175 -28
  22. data/lib/hecks/adapters/driven/postgres_era.adapter +5 -0
  23. data/lib/hecks/adapters/driven/prism.rb +4 -4
  24. data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
  25. data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
  26. data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
  27. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
  28. data/lib/hecks/adapters/driven/sqlite.rb +181 -21
  29. data/lib/hecks/adapters/driven.rb +4 -4
  30. data/lib/hecks/adapters/driving/github_webhook.rb +145 -0
  31. data/lib/hecks/behaviors/dsl.rb +2 -2
  32. data/lib/hecks/behaviors/expectations.rb +51 -23
  33. data/lib/hecks/behaviors/ir.rb +1 -1
  34. data/lib/hecks/behaviors/rspec.rb +1 -1
  35. data/lib/hecks/behaviors/runner.rb +2 -2
  36. data/lib/hecks/behaviors.rb +1 -1
  37. data/lib/hecks/bluebook/aggregate.rb +13 -13
  38. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +10 -10
  39. data/lib/hecks/bluebook/assembly/build.rb +1 -1
  40. data/lib/hecks/bluebook/assembly/contract.rb +39 -16
  41. data/lib/hecks/bluebook/assembly/contracts.rb +40 -38
  42. data/lib/hecks/bluebook/assembly/marks.rb +18 -18
  43. data/lib/hecks/bluebook/assembly/specializer.rb +19 -21
  44. data/lib/hecks/bluebook/assembly.rb +9 -9
  45. data/lib/hecks/bluebook/attribute.rb +9 -9
  46. data/lib/hecks/bluebook/behaviour/aggregate.rb +11 -11
  47. data/lib/hecks/bluebook/behaviour/attribute.rb +5 -5
  48. data/lib/hecks/bluebook/behaviour/chapter.rb +23 -5
  49. data/lib/hecks/bluebook/behaviour/command.rb +23 -23
  50. data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -3
  51. data/lib/hecks/bluebook/behaviour/entity.rb +8 -8
  52. data/lib/hecks/bluebook/behaviour/hexagon.rb +4 -4
  53. data/lib/hecks/bluebook/behaviour/lifecycle.rb +5 -5
  54. data/lib/hecks/bluebook/behaviour/policy.rb +12 -12
  55. data/lib/hecks/bluebook/behaviour/process_manager.rb +7 -7
  56. data/lib/hecks/bluebook/behaviour/query.rb +1 -1
  57. data/lib/hecks/bluebook/behaviour/read_model.rb +8 -8
  58. data/lib/hecks/bluebook/behaviour/traits.rb +12 -12
  59. data/lib/hecks/bluebook/behaviour/value_object.rb +6 -6
  60. data/lib/hecks/bluebook/capabilities.rb +27 -0
  61. data/lib/hecks/bluebook/chapter.rb +28 -9
  62. data/lib/hecks/bluebook/command.rb +12 -12
  63. data/lib/hecks/bluebook/domain_port.rb +9 -9
  64. data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
  65. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
  66. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +96 -96
  67. data/lib/hecks/bluebook/dsl/attribute_collector.rb +41 -41
  68. data/lib/hecks/bluebook/dsl/binding_proxy.rb +22 -2
  69. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +111 -74
  70. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +48 -30
  71. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
  72. data/lib/hecks/bluebook/dsl/command_builder.rb +103 -103
  73. data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
  74. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
  75. data/lib/hecks/bluebook/dsl/entity_builder.rb +56 -56
  76. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
  77. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +89 -30
  78. data/lib/hecks/bluebook/dsl/identity_declaration.rb +17 -17
  79. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
  80. data/lib/hecks/bluebook/dsl/policy_builder.rb +30 -21
  81. data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
  82. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
  83. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +35 -35
  84. data/lib/hecks/bluebook/dsl/query_builder.rb +5 -5
  85. data/lib/hecks/bluebook/dsl/read_model_builder.rb +34 -34
  86. data/lib/hecks/bluebook/dsl/rule_reference.rb +41 -39
  87. data/lib/hecks/bluebook/dsl/translation_builder.rb +9 -9
  88. data/lib/hecks/bluebook/dsl/value_object_builder.rb +16 -16
  89. data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
  90. data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
  91. data/lib/hecks/bluebook/entity.rb +11 -11
  92. data/lib/hecks/bluebook/expression/ast_json.rb +20 -20
  93. data/lib/hecks/bluebook/expression/ast_reader.rb +3 -3
  94. data/lib/hecks/bluebook/expression/canonical_form.rb +9 -9
  95. data/lib/hecks/bluebook/expression/evaluator.rb +18 -18
  96. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +18 -18
  97. data/lib/hecks/bluebook/expression/resolver.rb +60 -62
  98. data/lib/hecks/bluebook/hexagon.rb +1 -1
  99. data/lib/hecks/bluebook/lifecycle.rb +1 -1
  100. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -1
  101. data/lib/hecks/bluebook/meta_validator/judge.rb +122 -86
  102. data/lib/hecks/bluebook/meta_validator/plan.rb +39 -39
  103. data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -2
  104. data/lib/hecks/bluebook/meta_validator/readings.rb +43 -43
  105. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +44 -37
  106. data/lib/hecks/bluebook/meta_validator/shapes.rb +25 -21
  107. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +145 -31
  108. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +6 -6
  109. data/lib/hecks/bluebook/meta_validator/world_judge.rb +5 -5
  110. data/lib/hecks/bluebook/meta_validator.rb +70 -70
  111. data/lib/hecks/bluebook/model_check.rb +301 -84
  112. data/lib/hecks/bluebook/pattern_subset.rb +9 -9
  113. data/lib/hecks/bluebook/policy.rb +15 -13
  114. data/lib/hecks/bluebook/process_manager.rb +14 -14
  115. data/lib/hecks/bluebook/project_register.rb +6 -6
  116. data/lib/hecks/bluebook/query.rb +4 -4
  117. data/lib/hecks/bluebook/read_model.rb +14 -14
  118. data/lib/hecks/bluebook/reference.rb +8 -8
  119. data/lib/hecks/bluebook/smoke_test.rb +19 -19
  120. data/lib/hecks/bluebook/synthesizer.rb +12 -12
  121. data/lib/hecks/bluebook/translation.rb +4 -4
  122. data/lib/hecks/bluebook/value_object.rb +6 -6
  123. data/lib/hecks/bluebook.rb +2 -2
  124. data/lib/hecks/codemod/legacy_dispatch_args.rb +299 -0
  125. data/lib/hecks/codemod/legacy_dispatch_recorder.rb +186 -0
  126. data/lib/hecks/codemod.rb +36 -35
  127. data/lib/hecks/construct.rb +6 -6
  128. data/lib/hecks/corpus.rb +317 -0
  129. data/lib/hecks/deprecation.rb +95 -0
  130. data/lib/hecks/doc/reference.rb +19 -19
  131. data/lib/hecks/embryonaut_bluebook.rb +11 -11
  132. data/lib/hecks/facade/cli_door.rb +69 -10
  133. data/lib/hecks/facade/cli_runner.rb +105 -24
  134. data/lib/hecks/facade/command_request.rb +23 -0
  135. data/lib/hecks/facade/handle.rb +79 -32
  136. data/lib/hecks/facade/json_door.rb +106 -25
  137. data/lib/hecks/facade/surface/aggregate_door.rb +42 -27
  138. data/lib/hecks/facade/surface/chapter.rb +26 -17
  139. data/lib/hecks/facade/surface.rb +16 -3
  140. data/lib/hecks/facade.rb +15 -4
  141. data/lib/hecks/forms/app.rb +46 -30
  142. data/lib/hecks/forms/command_form_renderer.rb +70 -9
  143. data/lib/hecks/forms/field_renderer.rb +142 -6
  144. data/lib/hecks/forms/field_shape.rb +183 -20
  145. data/lib/hecks/forms/html.rb +51 -7
  146. data/lib/hecks/forms/index_renderer.rb +14 -2
  147. data/lib/hecks/forms/params.rb +120 -23
  148. data/lib/hecks/forms/port_argument.rb +2 -2
  149. data/lib/hecks/forms/query_form_renderer.rb +2 -2
  150. data/lib/hecks/forms/record_renderer.rb +2 -2
  151. data/lib/hecks/forms/record_table.rb +1 -1
  152. data/lib/hecks/forms/value_object_shape.rb +3 -3
  153. data/lib/hecks/forms.rb +24 -4
  154. data/lib/hecks/fqn.rb +1 -1
  155. data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
  156. data/lib/hecks/framework.rb +48 -17
  157. data/lib/hecks/freezer.rb +11 -11
  158. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +90 -90
  159. data/lib/hecks/fuzzing/combination_miner.rb +119 -0
  160. data/lib/hecks/fuzzing/concurrent_dispatch.rb +361 -0
  161. data/lib/hecks/fuzzing/coverage_campaign.rb +118 -0
  162. data/lib/hecks/fuzzing/differential.rb +158 -0
  163. data/lib/hecks/fuzzing/domain_generator.rb +694 -0
  164. data/lib/hecks/fuzzing/era_boundary.rb +124 -0
  165. data/lib/hecks/fuzzing/form_census.rb +199 -0
  166. data/lib/hecks/fuzzing/generated_domain_check.rb +95 -0
  167. data/lib/hecks/fuzzing/invalid_value_generator.rb +6 -6
  168. data/lib/hecks/fuzzing/isolated_boot.rb +226 -38
  169. data/lib/hecks/fuzzing/nondeterministic.rb +67 -0
  170. data/lib/hecks/fuzzing/persistence_parity.rb +161 -0
  171. data/lib/hecks/fuzzing/properties/corrections.rb +100 -0
  172. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +321 -41
  173. data/lib/hecks/fuzzing/properties/guards.rb +129 -26
  174. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +32 -32
  175. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +31 -11
  176. data/lib/hecks/fuzzing/properties/outbox.rb +142 -0
  177. data/lib/hecks/fuzzing/properties/querying.rb +17 -17
  178. data/lib/hecks/fuzzing/properties.rb +84 -39
  179. data/lib/hecks/fuzzing/qa_settings.rb +152 -0
  180. data/lib/hecks/fuzzing/replay.rb +224 -114
  181. data/lib/hecks/fuzzing/rotation_priority.rb +94 -0
  182. data/lib/hecks/fuzzing/rust_gap_manifest.rb +113 -0
  183. data/lib/hecks/fuzzing/self_consistency.rb +676 -0
  184. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +526 -0
  185. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +101 -30
  186. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +55 -8
  187. data/lib/hecks/fuzzing/sequence_generator/picker.rb +31 -11
  188. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +123 -32
  189. data/lib/hecks/fuzzing/sequence_generator.rb +152 -31
  190. data/lib/hecks/fuzzing/shrinker.rb +197 -0
  191. data/lib/hecks/fuzzing/structural_skips.rb +39 -0
  192. data/lib/hecks/fuzzing/sweep_depth.rb +53 -0
  193. data/lib/hecks/fuzzing/target_capabilities.rb +180 -0
  194. data/lib/hecks/fuzzing/value_generator.rb +66 -14
  195. data/lib/hecks/fuzzing.rb +11 -0
  196. data/lib/hecks/grammar/evolve.rb +10 -10
  197. data/lib/hecks/grammar.rb +7 -7
  198. data/lib/hecks/ir.rb +13 -13
  199. data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
  200. data/lib/hecks/language/bluebook/policy.bluebook +11 -1
  201. data/lib/hecks/language/bluebook/vocabulary.bluebook +365 -15
  202. data/lib/hecks/language/oidc.json +5 -0
  203. data/lib/hecks/literal.rb +9 -9
  204. data/lib/hecks/naming.rb +89 -21
  205. data/lib/hecks/ports/access_control.rb +58 -2
  206. data/lib/hecks/ports/agent/answers.rb +83 -6
  207. data/lib/hecks/ports/agent.rb +119 -35
  208. data/lib/hecks/ports/authentication.rb +44 -4
  209. data/lib/hecks/ports/authorization.rb +53 -11
  210. data/lib/hecks/ports/clock.rb +42 -23
  211. data/lib/hecks/ports/extraction.rb +16 -0
  212. data/lib/hecks/ports/identity_assignment.rb +24 -2
  213. data/lib/hecks/ports/identity_generation.rb +17 -3
  214. data/lib/hecks/ports/identity_resolution.rb +18 -1
  215. data/lib/hecks/ports/loading.rb +4 -0
  216. data/lib/hecks/ports/persistence/append_only.rb +172 -8
  217. data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
  218. data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
  219. data/lib/hecks/ports/persistence/execution.rb +4 -0
  220. data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
  221. data/lib/hecks/ports/persistence/plugin.rb +42 -4
  222. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
  223. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
  224. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
  225. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
  226. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
  227. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
  228. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
  229. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
  230. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
  231. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +160 -58
  232. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
  233. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +28 -5
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
  237. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +44 -6
  238. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
  239. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +57 -4
  240. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
  241. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +338 -83
  242. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
  243. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
  244. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
  245. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +42 -8
  246. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
  247. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
  248. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
  249. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -19
  250. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +5 -5
  251. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +1 -1
  252. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
  253. data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
  254. data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
  255. data/lib/hecks/ports/persistence/repository_factory.rb +29 -6
  256. data/lib/hecks/ports/persistence/state_codec.rb +319 -0
  257. data/lib/hecks/ports/persistence.rb +36 -1
  258. data/lib/hecks/ports/projection.rb +61 -7
  259. data/lib/hecks/ports/query/in_memory.rb +3 -3
  260. data/lib/hecks/ports/query/ordering.rb +6 -6
  261. data/lib/hecks/ports/query.rb +35 -0
  262. data/lib/hecks/projections/bootstrap_table.rb +112 -0
  263. data/lib/hecks/projections/diagrams.rb +75 -75
  264. data/lib/hecks/projections/glossary/html.rb +250 -0
  265. data/lib/hecks/projections/glossary/markdown.rb +105 -0
  266. data/lib/hecks/projections/glossary/mermaid.rb +110 -0
  267. data/lib/hecks/projections/glossary/page.css +271 -0
  268. data/lib/hecks/projections/glossary/page.js +72 -0
  269. data/lib/hecks/projections/glossary/sections.rb +17 -0
  270. data/lib/hecks/projections/glossary/sentences.rb +205 -0
  271. data/lib/hecks/projections/glossary.rb +214 -286
  272. data/lib/hecks/projections/ir.rb +1 -1
  273. data/lib/hecks/projections/model/deviations.rb +18 -17
  274. data/lib/hecks/projections/model.rb +25 -21
  275. data/lib/hecks/projections/oidc.rb +7 -7
  276. data/lib/hecks/projections/parser_table.rb +5 -5
  277. data/lib/hecks/projections/reference.rb +3 -3
  278. data/lib/hecks/projections/rust_vocabulary.rb +443 -0
  279. data/lib/hecks/projections/shape.rb +2 -2
  280. data/lib/hecks/projections/statements.rb +11 -11
  281. data/lib/hecks/projections/vocabulary.rb +9 -9
  282. data/lib/hecks/projections.rb +5 -3
  283. data/lib/hecks/projector/cli_projector.rb +29 -29
  284. data/lib/hecks/projector/docs_projector.rb +13 -13
  285. data/lib/hecks/projector/exporter.rb +42 -21
  286. data/lib/hecks/projector/ir_projector.rb +1 -1
  287. data/lib/hecks/projector/narrate_projector.rb +15 -22
  288. data/lib/hecks/projector/target.rb +13 -13
  289. data/lib/hecks/projector.rb +15 -15
  290. data/lib/hecks/query_ir.rb +47 -47
  291. data/lib/hecks/query_specification/common/comparators.rb +19 -3
  292. data/lib/hecks/query_specification/common/comparison.rb +132 -24
  293. data/lib/hecks/query_specification/common/dsl.rb +65 -9
  294. data/lib/hecks/query_specification/common/null_policy.rb +57 -13
  295. data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
  296. data/lib/hecks/query_specification/common/options.rb +25 -0
  297. data/lib/hecks/query_specification/field_path.rb +69 -15
  298. data/lib/hecks/query_specification/hop_path.rb +57 -20
  299. data/lib/hecks/query_specification/read_model/specification.rb +4 -0
  300. data/lib/hecks/rendering.rb +3 -3
  301. data/lib/hecks/router/namespace_installer.rb +3 -3
  302. data/lib/hecks/router.rb +1 -1
  303. data/lib/hecks/runtime/aggregate_lock.rb +11 -11
  304. data/lib/hecks/runtime/boot_gates.rb +3 -3
  305. data/lib/hecks/runtime/caller.rb +8 -8
  306. data/lib/hecks/runtime/capability_graph.rb +2 -2
  307. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +22 -25
  308. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +64 -67
  309. data/lib/hecks/runtime/command_interpreter.rb +159 -102
  310. data/lib/hecks/runtime/command_rules/admissibility.rb +69 -69
  311. data/lib/hecks/runtime/command_rules/arithmetic.rb +137 -91
  312. data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
  313. data/lib/hecks/runtime/command_rules/emission.rb +18 -1
  314. data/lib/hecks/runtime/command_rules/references.rb +94 -19
  315. data/lib/hecks/runtime/command_rules.rb +3 -0
  316. data/lib/hecks/runtime/dependency_planning.rb +11 -11
  317. data/lib/hecks/runtime/dispatcher.rb +283 -90
  318. data/lib/hecks/runtime/entity_element.rb +282 -46
  319. data/lib/hecks/runtime/entity_interpreter.rb +160 -69
  320. data/lib/hecks/runtime/errors.rb +19 -19
  321. data/lib/hecks/runtime/event.rb +6 -6
  322. data/lib/hecks/runtime/identity.rb +22 -22
  323. data/lib/hecks/runtime/instance.rb +39 -14
  324. data/lib/hecks/runtime/interpreting.rb +12 -12
  325. data/lib/hecks/runtime/invocation.rb +276 -0
  326. data/lib/hecks/runtime/loader.rb +14 -14
  327. data/lib/hecks/runtime/outbox.rb +23 -23
  328. data/lib/hecks/runtime/policy_interpreter.rb +54 -54
  329. data/lib/hecks/runtime/port_operation_interpreter.rb +22 -19
  330. data/lib/hecks/runtime/query_interpreter.rb +111 -56
  331. data/lib/hecks/runtime/reaction_invocation.rb +76 -9
  332. data/lib/hecks/runtime/read_model_interpreter.rb +40 -40
  333. data/lib/hecks/runtime/rebuild_sweep.rb +4 -4
  334. data/lib/hecks/runtime/reference_hop.rb +6 -6
  335. data/lib/hecks/runtime/refusal_wording.rb +92 -112
  336. data/lib/hecks/runtime/registry/saga_persistence.rb +21 -21
  337. data/lib/hecks/runtime/registry/verification.rb +36 -26
  338. data/lib/hecks/runtime/registry.rb +56 -27
  339. data/lib/hecks/runtime/remote_dispatcher.rb +38 -23
  340. data/lib/hecks/runtime/routing.rb +10 -88
  341. data/lib/hecks/runtime/saga_interpreter/correlation.rb +17 -17
  342. data/lib/hecks/runtime/saga_interpreter.rb +93 -56
  343. data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
  344. data/lib/hecks/runtime/tenant_check.rb +9 -9
  345. data/lib/hecks/runtime/tenant_scope.rb +5 -5
  346. data/lib/hecks/runtime/value/admission.rb +75 -30
  347. data/lib/hecks/runtime/value/coercion.rb +379 -226
  348. data/lib/hecks/runtime/value/entity_list_coercion.rb +248 -0
  349. data/lib/hecks/runtime/value.rb +28 -23
  350. data/lib/hecks/runtime.rb +7 -7
  351. data/lib/hecks/storehouse.rb +64 -64
  352. data/lib/hecks/version.rb +3 -3
  353. data/lib/hecks/vocabulary.rb +207 -5
  354. data/lib/hecks.rb +13 -11
  355. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +11 -11
  356. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +14 -12
  357. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +11 -11
  358. metadata +43 -2
@@ -8,7 +8,7 @@ module Hecks
8
8
  # happened to sample," claim is realistically achievable:
9
9
  # `Hecks::Bluebook::Expression::{Evaluator,Resolver}`. It's finite-
10
10
  # grammar and side-effect-free (Evaluator::PROJECTION/Resolver's own
11
- # `interpret` never touch a database, a clock, or randomness), but NOT
11
+ # `interpret` never touch a database, a clock, or randomness), but not
12
12
  # small enough for naive enumeration — ~20 node-producing constructs
13
13
  # across two mutually-recursive grammar layers, unbounded recursion
14
14
  # through dotted `Lookup` paths, `Addition`, and nested block
@@ -20,8 +20,8 @@ module Hecks
20
20
  # `canonical_form.rb` aliasing bug — neither would have been caught by
21
21
  # sampling alone, which is the whole reason this phase exists).
22
22
  #
23
- # TYPE-DIRECTED, not exhaustive-over-strings: this generator only ever
24
- # recurses into a sub-expression whose OWN type the surrounding
23
+ # Type-directed, not exhaustive-over-strings: this generator only ever
24
+ # recurses into a sub-expression whose own type the surrounding
25
25
  # construct actually accepts (never `"x".modulo(true)`) — mirroring,
26
26
  # by hand, the same admitted-receiver-class tables `Resolver`'s own
27
27
  # `interpret` enforces at runtime (`SizedType`/`ToStringType`/
@@ -36,34 +36,34 @@ module Hecks
36
36
  # 3" claim for exactly the sublanguage that already bit this project
37
37
  # twice.
38
38
  #
39
- # WHAT "PROVEN" MEANS HERE, PRECISELY: every expression this generator
40
- # produces is well-typed BY CONSTRUCTION (every sub-expression's type
39
+ # **What "PROVEN" means here, precisely**: every expression this generator
40
+ # produces is well-typed by construction (every sub-expression's type
41
41
  # matches what its parent construct actually accepts, per this file's
42
42
  # own TYPE_RULES). Interpreting a well-typed expression must never
43
- # raise anything OTHER than `Hecks::Bluebook::Expression::
43
+ # raise anything other than `Hecks::Bluebook::Expression::
44
44
  # EvaluationError` — that class alone is the sublanguage's own real
45
45
  # refusal vocabulary (a divisor that happens to be zero, a `Lookup`
46
46
  # this run's synthetic state doesn't happen to carry); anything else
47
47
  # escaping (a raw `TypeError`/`NoMethodError`/`ZeroDivisionError`) is
48
48
  # exactly the crash-signature this whole file's own header comments
49
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
50
+ # this sublanguage keeps almost-but-not-quite avoiding. This does not
51
+ # prove the interpreter computes the semantically right answer (that
52
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
53
+ # way rust_conformance_fuzz_spec.rb is the cross-runtime half of this
54
54
  # same idea) — only that it never crashes on well-typed input.
55
55
  module BoundedExhaustiveExpressions
56
56
  module_function
57
57
 
58
- # `:numeric` deliberately covers BOTH Integer and Float — the
58
+ # `:numeric` deliberately covers both Integer and Float — the
59
59
  # resolver's own `numeric`/`require_number` never distinguish them
60
60
  # (resolver.rb) — so a generator that kept them as separate types
61
61
  # would be modeling a distinction the grammar itself doesn't make.
62
62
  TYPES = %i[numeric string boolean array nil_type].freeze
63
63
 
64
- # THE PALETTE — deliberately small (this file's own header: "a small
64
+ # **The palette** — deliberately small (this file's own header: "a small
65
65
  # representative literal palette," matching the plan's own `{0, 1,
66
- # -1}` example). Widening it doesn't test a different SHAPE of
66
+ # -1}` example). Widening it doesn't test a different shape of
67
67
  # expression, only more values through the same shapes — real
68
68
  # boundary-value coverage (Bignum, NaN, empty string, unicode) is
69
69
  # PRD 05's job (spec/runtime/numeric_boundary_spec.rb,
@@ -75,18 +75,18 @@ module Hecks
75
75
  nil_type: %w[nil].freeze
76
76
  }.freeze
77
77
 
78
- # TWO SYNTHETIC ATTRIBUTES PER TYPE (the plan's own "2-3 fake
78
+ # Two synthetic attributes per type (the plan's own "2-3 fake
79
79
  # attributes per type") — real names are irrelevant to `Resolver
80
80
  # .parse` (it never touches state/attrs at all, confirmed directly:
81
81
  # only `Lookup#interpret` does) and irrelevant to `interpret` beyond
82
- # needing to resolve against WHATEVER synthetic state this file
82
+ # needing to resolve against whatever synthetic state this file
83
83
  # supplies (`synthetic_state`, below) — so any non-colliding,
84
84
  # non-suffix-shaped name works. Never named the same as a
85
85
  # `BLOCK_PARAM` (below) — `Resolver#fetch`'s own `attrs`-wins-over-
86
- # `state` precedence means a block parameter SHADOWS a same-named
86
+ # `state` precedence means a block parameter shadows a same-named
87
87
  # top-level attribute for the span of its own predicate, a real
88
88
  # hazard this file sidesteps by construction rather than exercising
89
- # it here (a real, cited resolver.rb hazard, not a gap in THIS
89
+ # it here (a real, cited resolver.rb hazard, not a gap in this
90
90
  # generator's own coverage claim).
91
91
  SYNTHETIC_ATTRS = {
92
92
  numeric: %w[num_a num_b].freeze,
@@ -95,7 +95,7 @@ module Hecks
95
95
  array: %w[arr_num arr_str].freeze
96
96
  }.freeze
97
97
 
98
- # `arr_num`/`arr_str` — ELEMENT type per array attribute, needed so
98
+ # `arr_num`/`arr_str` — element type per array attribute, needed so
99
99
  # a block predicate's own bound parameter (`BLOCK_PARAM`, below) is
100
100
  # generated against the right leaf/production set for whatever it's
101
101
  # actually bound to.
@@ -105,41 +105,41 @@ module Hecks
105
105
 
106
106
  MAX_DEPTH = 3
107
107
 
108
- # `state`/`attrs` — a plain Hash satisfies BOTH (confirmed directly:
108
+ # `state`/`attrs` — a plain Hash satisfies both (confirmed directly:
109
109
  # `Resolver#known?` degrades to a bare index check when `state`
110
110
  # doesn't `respond_to?(:key?)`, and `fetch` only ever needs
111
- # `attrs.key?`/`attrs[]`.). Split ACROSS both, matching a real
111
+ # `attrs.key?`/`attrs[]`.). Split across both, matching a real
112
112
  # dispatch's own shape (some names come from the record's stored
113
113
  # state, some from the command's own args) — not load-bearing for
114
- # THIS proof (attrs wins regardless), but keeps the synthetic input
114
+ # this proof (attrs wins regardless), but keeps the synthetic input
115
115
  # closer to what `Admissibility` actually builds, in case anything
116
116
  # here is reused for a future deeper check.
117
117
  #
118
- # THE VO-VS-SCALAR UNWRAP CASE, DELIBERATELY EXERCISED — half of
118
+ # **The VO-vs-scalar unwrap case, deliberately exercised** — half of
119
119
  # `num_a`/`str_a`/`bool_a`/one array attribute's elements are bare
120
120
  # scalars, the other half (`num_b`/`str_b`/`bool_b`/the other
121
121
  # array's elements) are wrapped `{value: X}` — the single-field
122
122
  # Value Object shape `Resolver#unwrap_scalar` auto-collapses.
123
123
  # resolver.rb's own most serious historical bug in this file (the
124
124
  # "UPDATE 2026-08-18" comment: a dotted walk that landed on an
125
- # un-unwrapped VO compared `false` against every literal, SILENTLY,
125
+ # un-unwrapped VO compared `false` against every literal, silently,
126
126
  # never raising) lived exactly at this boundary — a generator that
127
127
  # only ever supplied bare scalars would never exercise the code
128
128
  # path that bug lived in at all.
129
- # `SingleFieldVO` — NOT a plain Hash, deliberately: `Resolver
129
+ # `SingleFieldVO` — not a plain Hash, deliberately: `Resolver
130
130
  # #unwrap_scalar`'s own guard is `value.respond_to?(:to_h) &&
131
131
  # !value.is_a?(Hash) && !value.is_a?(Array)` — it exists
132
132
  # specifically to collapse a real hydrated `Runtime::Value`
133
133
  # instance (which responds to `#to_h` but is never itself a bare
134
134
  # Hash) down to its lone scalar field, and just as deliberately
135
- # leaves an ACTUAL Hash alone (a genuinely un-hydrated, multi-
135
+ # leaves an actual Hash alone (a genuinely un-hydrated, multi-
136
136
  # field record has no single scalar to collapse to). A first
137
137
  # version of this generator's own synthetic state used plain
138
138
  # `{value: X}` Hashes to stand in for a single-field VO — which
139
139
  # `is_a?(Hash)` is true for, so `unwrap_scalar` correctly left them
140
- # WRAPPED, and every VO-typed synthetic attribute then failed
140
+ # wrapped, and every VO-typed synthetic attribute then failed
141
141
  # `Addition`/`Compare`/every scalar-typed operation with "expects a
142
- # number, got {\"value\":5}" — a bug in THIS generator's own
142
+ # number, got {\"value\":5}" — a bug in this generator's own
143
143
  # synthetic state, not in `Resolver`, caught only by noticing that
144
144
  # `num_b == 5` (`num_b` a plain `{value: 5}` Hash) evaluated to
145
145
  # `false` instead of `true` before this fix.
@@ -162,21 +162,21 @@ module Hecks
162
162
 
163
163
  def synthetic_attrs = {}
164
164
 
165
- # `leaves(type, depth)` — every TERMINAL (non-recursive) expression
165
+ # `leaves(type, depth)` — every terminal (non-recursive) expression
166
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
167
+ # attribute name declared for that type (never their values — this
168
+ # generates text, the same source a real `given`/`invariant` author
169
169
  # would write; `synthetic_state`, above, is what gives those names
170
170
  # meaning at `interpret` time).
171
171
  def leaves(type)
172
172
  (TYPE_LEAVES[type] || []) + (SYNTHETIC_ATTRS[type] || []) + Array(bound_leaves[type])
173
173
  end
174
174
 
175
- # A STACK, not a single slot — a NESTED block predicate (the real
175
+ # A stack, not a single slot — a nested block predicate (the real
176
176
  # corpus already does this two levels deep, roster.bluebook's own
177
177
  # `seats.any? { |s| assignments.none? { |a| … } }`) pushes a second
178
178
  # bound leaf while the outer one is still active. Both share the
179
- # SAME `BLOCK_PARAM` spelling ("el") — real Ruby block-parameter
179
+ # same `BLOCK_PARAM` spelling ("el") — real Ruby block-parameter
180
180
  # shadowing (the inner `el` simply shadows the outer one within its
181
181
  # own predicate text), which this generator treats as legal on
182
182
  # purpose: `resolver.rb`'s own `interpret_with_element` binds fresh
@@ -196,11 +196,11 @@ module Hecks
196
196
  end
197
197
 
198
198
  # `productions(type, depth)` — every expression of `type` reachable
199
- # in AT MOST `depth` recursive steps, MEMOIZED (the same sub-
199
+ # in at most `depth` recursive steps, memoized (the same sub-
200
200
  # expression set is reused at every enclosing recursion, so without
201
201
  # memoizing, cost would compound multiplicatively per level instead
202
202
  # of additively). `depth` 0 is exactly `leaves(type)`; each
203
- # increment adds every construct THIS FILE'S OWN TYPE_RULES (below)
203
+ # increment adds every construct this file's own TYPE_RULES (below)
204
204
  # says can produce `type`, built from `depth - 1` sub-expressions.
205
205
  def productions(type, depth)
206
206
  cache[[type, depth]] ||= begin
@@ -218,22 +218,22 @@ module Hecks
218
218
  when :boolean then boolean_productions(depth)
219
219
  when :array then array_productions(depth)
220
220
  # no recursive producer of nil in this grammar — Find's "not found" is a
221
- # runtime OUTCOME, not a distinct construct to render as source text
221
+ # runtime outcome, not a distinct construct to render as source text
222
222
  when :nil_type then []
223
223
  else raise ArgumentError, "no production rule for type #{type.inspect}"
224
224
  end
225
225
  end
226
226
 
227
- # EVERY internal use of a sub-expression LIST (as opposed to the
227
+ # Every internal use of a sub-expression list (as opposed to the
228
228
  # single final list `productions(type, depth)` returns to its own
229
229
  # caller) goes through this, not `productions` directly — the
230
230
  # actual thing that made an early version of this generator explode
231
231
  # past a million cases by depth 3 wasn't `pairs`' own cross product
232
- # (already sampled) but the dozen-plus LINEAR `flat_map`/`map`
232
+ # (already sampled) but the dozen-plus linear `flat_map`/`map`
233
233
  # passes `boolean_productions` alone makes over `str`/`num`/`sub` —
234
234
  # each individually harmless, but an unbounded few-thousand-item
235
- # list run through a dozen of them, feeding the NEXT depth's own
236
- # dozen passes, compounds fast. Bounding every INPUT list (not the
235
+ # list run through a dozen of them, feeding the next depth's own
236
+ # dozen passes, compounds fast. Bounding every input list (not the
237
237
  # final output) keeps the shape diversity `sample`'s even-spacing
238
238
  # already preserves while keeping growth roughly linear in depth
239
239
  # instead of combinatorial.
@@ -241,20 +241,20 @@ module Hecks
241
241
 
242
242
  # NUMERIC ← Addition(numeric, numeric) | Modulo(numeric, numeric) |
243
243
  # Size(sized) | First/Last(numeric array). `Size` returns an
244
- # Integer for a String OR an Array receiver alike (`SizedType` —
244
+ # Integer for a String or an Array receiver alike (`SizedType` —
245
245
  # `size_of`, resolver.rb) — both sides generated here.
246
246
  def numeric_productions(depth)
247
247
  sub = bounded(:numeric, depth - 1)
248
- # `Modulo`'s own RECEIVER (not its argument — that side already
248
+ # `Modulo`'s own receiver (not its argument — that side already
249
249
  # goes through `Resolver#matching_paren`'s own fresh, self-
250
250
  # contained re-parse, confirmed safe for any numeric shape
251
251
  # including another `Addition`/`Modulo`) has the identical
252
252
  # "`Addition` mis-parsed as a suffix receiver" hazard
253
253
  # `resolver_numeric_leaves`'s own comment documents for `.to_s`/
254
- # `.positive?` — `Resolver.parse` tries `split_addition` BEFORE
254
+ # `.positive?` — `Resolver.parse` tries `split_addition` before
255
255
  # `match_call`, so `"0 + 0.modulo(1)"` (meant as `(0 + 0)
256
256
  # .modulo(1)`) actually parses as `0 + (0.modulo(1))`. Restricted
257
- # to `resolver_numeric_leaves` on the RECEIVER side only — the
257
+ # to `resolver_numeric_leaves` on the receiver side only — the
258
258
  # argument stays the full, unrestricted numeric set.
259
259
  pairs(sub).map { |a, b| "#{a} + #{b}" } +
260
260
  cross(resolver_numeric_leaves(depth - 1), sub).map { |a, b| "#{a}.modulo(#{b})" } +
@@ -263,32 +263,32 @@ module Hecks
263
263
  sample(numeric_array_productions(depth - 1)).flat_map { |a| ["#{a}.first", "#{a}.last"] }
264
264
  end
265
265
 
266
- # A "boolean" IN RESOLVER'S OWN SENSE — safe to embed as the
267
- # RECEIVER of a trailing Resolver-level suffix (`.to_s`, and
268
- # anywhere else a boolean-typed VALUE, as opposed to a boolean-
269
- # typed EXPRESSION, is wanted). FOUND LIVE, the same way the
270
- # nested-`.modulo` bug was: `Resolver.parse` has NO KNOWLEDGE of
271
- # `==`/`<`/`&&`/`||`/leading `!`/`.include?` AT ALL — those are
272
- # `Evaluator`'s OWN, entirely separate parsing layer, stripped off
273
- # BEFORE anything reaches `Resolver.parse` at all (confirmed
266
+ # A "boolean" in resolver's own sense — safe to embed as the
267
+ # receiver of a trailing Resolver-level suffix (`.to_s`, and
268
+ # anywhere else a boolean-typed value, as opposed to a boolean-
269
+ # typed expression, is wanted). Found live, the same way the
270
+ # nested-`.modulo` bug was: `Resolver.parse` has no knowledge of
271
+ # `==`/`<`/`&&`/`||`/leading `!`/`.include?` at all — those are
272
+ # `Evaluator`'s own, entirely separate parsing layer, stripped off
273
+ # before anything reaches `Resolver.parse` at all (confirmed
274
274
  # directly: `Resolver.parse("str_b < str_a")` — no `.` anywhere in
275
275
  # that text for any suffix regex to anchor on — falls through
276
276
  # every leaf regex to the `Lookup` catch-all, exactly like the
277
- # nested-modulo bug did). `bounded(:boolean, depth)`'s FULL set
277
+ # nested-modulo bug did). `bounded(:boolean, depth)`'s full set
278
278
  # includes `Compare`/`Include`/`Or`/`And`/`Not` — genuinely boolean-
279
- # TYPED at `interpret` time, but `Evaluator`-level SYNTAX, not
279
+ # typed at `interpret` time, but `Evaluator`-level syntax, not
280
280
  # something `Resolver.parse` can ever recognize as a receiver no
281
281
  # matter how it's parenthesized (confirmed directly too: `Resolver
282
282
  # .parse` never strips parens at all — `"(3)"` alone already fails
283
- # to resolve). This is a REAL, PERMANENT boundary of the actual
283
+ # to resolve). This is a real, permanent boundary of the actual
284
284
  # grammar (this whole sublanguage's own two-layer split, not a
285
285
  # limitation to work around) — a `given`/`invariant` author simply
286
286
  # cannot write `(a < b).to_s` in this language, ever, no matter how
287
287
  # they punctuate it. So this generator doesn't either: only
288
- # RESOLVER-LEVEL boolean-producing constructs (bare literals/
288
+ # resolver-level boolean-producing constructs (bare literals/
289
289
  # lookups, `SignTest`, `Empty`, `Presence`, `MatchesRegex`,
290
290
  # `StartsWith`/`EndsWith`, `BlockPredicate` — every one of them
291
- # parsed via a suffix regex INSIDE `Resolver.parse` itself, per
291
+ # parsed via a suffix regex inside `Resolver.parse` itself, per
292
292
  # this generator's own design report) are eligible here.
293
293
  def resolver_boolean_leaves(depth)
294
294
  leaves(:boolean) +
@@ -302,11 +302,11 @@ module Hecks
302
302
  sample(block_predicate_productions(depth))
303
303
  end
304
304
 
305
- # A "numeric" IN RESOLVER'S OWN SENSE — `Addition`'s twin of
305
+ # A "numeric" in resolver's own sense — `Addition`'s twin of
306
306
  # `resolver_boolean_leaves`'s own restriction, found the identical
307
307
  # way: `"0 + 0.to_s"` (meant as `(0 + 0).to_s`) actually parses as
308
308
  # `0 + (0.to_s)`, because `Resolver.parse` tries `split_addition`
309
- # BEFORE `.to_s`'s own suffix regex in its dispatch order — the `+`
309
+ # before `.to_s`'s own suffix regex in its dispatch order — the `+`
310
310
  # "wins" the split before the suffix ever gets a chance to anchor
311
311
  # on its own receiver boundary. Confirmed to have zero real-corpus
312
312
  # precedent either (`grep`, no `bluebook` file anywhere chains a
@@ -315,7 +315,7 @@ module Hecks
315
315
  # a real permanent grammar boundary (`Resolver.parse` never strips
316
316
  # parens, confirmed directly, so no amount of punctuation rescues
317
317
  # `(a + b).to_s`), not a bug to fix in the resolver for a shape
318
- # nothing has ever needed. `Modulo`/`Size`/`First`/`Last` stay IN
318
+ # nothing has ever needed. `Modulo`/`Size`/`First`/`Last` stay in
319
319
  # (each is its own trailing `.method(...)`/`.method` call, so
320
320
  # `Resolver.parse`'s greedy `(.+)\.suffix\z` regexes correctly
321
321
  # isolate them as a receiver regardless of what precedes them —
@@ -323,8 +323,8 @@ module Hecks
323
323
  def resolver_numeric_leaves(depth)
324
324
  return leaves(:numeric) if depth <= 0
325
325
 
326
- # SELF-referential on purpose, one depth down — `Modulo`'s own
327
- # receiver needs the SAME restriction `resolver_numeric_leaves`
326
+ # Self-referential on purpose, one depth down — `Modulo`'s own
327
+ # receiver needs the same restriction `resolver_numeric_leaves`
328
328
  # exists to express in the first place (see its own header
329
329
  # comment); the argument stays the full, unrestricted set, same
330
330
  # as `numeric_productions`' identical split right above.
@@ -336,8 +336,8 @@ module Hecks
336
336
  end
337
337
 
338
338
  # STRING ← ToS(numeric | boolean | nil | string) | First/Last(string
339
- # array). `Split` produces an ARRAY, never a String (resolver.rb's
340
- # own `apply_split`) — deliberately NOT listed as a string producer
339
+ # array). `Split` produces an array, never a String (resolver.rb's
340
+ # own `apply_split`) — deliberately not listed as a string producer
341
341
  # here; that would be exactly the "vocabulary-legal but grammar-
342
342
  # can't-actually-produce-it" mistake this sublanguage's own
343
343
  # `ArrayLiteral` bug (§1 of this generator's own design report) was
@@ -353,8 +353,8 @@ module Hecks
353
353
  # ARRAY ← Split(string, separator) | ArrayLiteral[same-type
354
354
  # elements] | the synthetic array attributes (already in `leaves`).
355
355
  # Kept deliberately small relative to numeric/string/boolean —
356
- # `array` is overwhelmingly a RECEIVER type in this grammar (`.size`
357
- # /`.any?`/`.include?`/…), rarely a produced VALUE; the two real
356
+ # `array` is overwhelmingly a receiver type in this grammar (`.size`
357
+ # /`.any?`/`.include?`/…), rarely a produced value; the two real
358
358
  # producers are enough to exercise every array-typed consumer
359
359
  # elsewhere in this file at least once via a non-leaf path.
360
360
  def array_productions(depth)
@@ -367,11 +367,11 @@ module Hecks
367
367
  def numeric_array_literal(depth) = "[#{bounded(:numeric, depth).first(2).join(', ')}]"
368
368
  def string_array_literal(depth) = "[#{bounded(:string, depth).first(2).join(', ')}]"
369
369
 
370
- # Arrays KNOWN (by construction, not merely by type) to hold numeric
370
+ # Arrays known (by construction, not merely by type) to hold numeric
371
371
  # elements — the two synthetic array attributes (`ARRAY_ELEMENT_TYPE`
372
- # tags `arr_num`) plus any numeric-array LITERAL this same depth
372
+ # tags `arr_num`) plus any numeric-array literal this same depth
373
373
  # budget can build. `.first`/`.last`/block predicates need to know
374
- # the ELEMENT type, which plain `array_productions` doesn't carry —
374
+ # the element type, which plain `array_productions` doesn't carry —
375
375
  # this (and `string_array_productions`, its twin) is how that extra
376
376
  # bit of type information flows without inventing a second, richer
377
377
  # AST just to carry it.
@@ -387,8 +387,8 @@ module Hecks
387
387
  end
388
388
 
389
389
  # BOOLEAN ← every comparison/predicate construct in the grammar.
390
- # This is where almost all of the sublanguage's OWN real surface
391
- # lives — a `given`/`invariant`/`ensures` body is ALWAYS boolean-
390
+ # This is where almost all of the sublanguage's own real surface
391
+ # lives — a `given`/`invariant`/`ensures` body is always boolean-
392
392
  # typed at its own top level (`Evaluator.truthy?`), so this is also
393
393
  # the set `all_predicates` (below) draws its top-level cases from
394
394
  # directly.
@@ -433,14 +433,14 @@ module Hecks
433
433
  include_productions(depth - 1)
434
434
  end
435
435
 
436
- # `.all?`/`.any?`/`.none?` over EACH known-element-typed array
436
+ # `.all?`/`.any?`/`.none?` over each known-element-typed array
437
437
  # source, with a predicate body drawn from `boolean_productions` at
438
- # ONE LESS depth, evaluated against `BLOCK_PARAM` bound to the
439
- # array's own element type — this is the ONLY place `Resolver` and
438
+ # one less depth, evaluated against `BLOCK_PARAM` bound to the
439
+ # array's own element type — this is the only place `Resolver` and
440
440
  # `Evaluator` are truly mutually recursive (a `BlockPredicate`'s own
441
441
  # `predicate` field is a full `Evaluator` AST, not a `Resolver`
442
442
  # leaf — confirmed directly), and the only construct in this whole
443
- # generator that can nest into ANOTHER block predicate (the real
443
+ # generator that can nest into another block predicate (the real
444
444
  # corpus already does this two levels deep —
445
445
  # `examples/roster/bluebook/roster.bluebook`'s own `seats.any? { |s|
446
446
  # assignments.none? { |a| … } }`).
@@ -458,14 +458,14 @@ module Hecks
458
458
  end
459
459
 
460
460
  # The predicate body a block gets — `boolean_productions`, but with
461
- # `BLOCK_PARAM` (bound to `element_type`) ALSO admitted as a leaf,
461
+ # `BLOCK_PARAM` (bound to `element_type`) also admitted as a leaf,
462
462
  # since inside the block it is exactly as usable as any other
463
463
  # `Lookup` name (resolver.rb's own `interpret_with_element`: the
464
464
  # bound element joins `attrs` for the span of one evaluation, no
465
465
  # different from a top-level attribute — confirmed directly).
466
466
  # `sample`d for the same reason every other internal list is — this
467
- # feeds THREE more constructs per body (`all?`/`any?`/`none?`) times
468
- # FOUR array sources, so an unbounded body list here is exactly the
467
+ # feeds three more constructs per body (`all?`/`any?`/`none?`) times
468
+ # four array sources, so an unbounded body list here is exactly the
469
469
  # kind of multiplier this file's own `bounded` comment warns about.
470
470
  def predicate_bodies(element_type, depth)
471
471
  sample(with_element_leaf(element_type) { boolean_productions(depth) })
@@ -473,7 +473,7 @@ module Hecks
473
473
 
474
474
  # `haystack.include?(needle)` — String haystack needs a String
475
475
  # needle (raises otherwise, evaluator.rb's own `includes?`); Array
476
- # haystack admits ANY needle type (compared via `equal?`, itself
476
+ # haystack admits any needle type (compared via `equal?`, itself
477
477
  # numeric-coerced-first). Both sides generated here, matching
478
478
  # `Vocabulary::IncludeHaystack` exactly.
479
479
  def include_productions(depth)
@@ -487,23 +487,23 @@ module Hecks
487
487
  end
488
488
  end
489
489
 
490
- # SAMPLED, NOT A FULL CROSS PRODUCT — a full `list.product(list)`
490
+ # **Sampled, not a full cross product** — a full `list.product(list)`
491
491
  # is what actually explodes this generator (numeric productions
492
- # alone hit 8000+ by depth 2; squaring THAT for `&&`/`==` pairs is
492
+ # alone hit 8000+ by depth 2; squaring that for `&&`/`==` pairs is
493
493
  # where "tens of thousands" becomes tens of millions). The
494
494
  # combinatorics genuinely don't buy proof coverage: proving `Or`/
495
- # `And`/`Not` themselves never crash needs ONE representative pair
495
+ # `And`/`Not` themselves never crash needs one representative pair
496
496
  # per depth (`evaluator.rb`'s own `interpret` does zero type-
497
497
  # dependent work for those three — `interpret(left) || interpret
498
498
  # (right)`, plain Ruby, no coercion, no receiver-type check at
499
- # all — so a crash there could only come from LEFT or RIGHT
499
+ # all — so a crash there could only come from left or right
500
500
  # themselves, already covered by testing every operand on its own
501
501
  # elsewhere in this same predicate set). `Compare`/`Include`/
502
- # `Addition`/`Modulo` genuinely DO real per-pair type coercion
502
+ # `Addition`/`Modulo` genuinely do real per-pair type coercion
503
503
  # (`Evaluator.apply`/`less_than`/`Resolver.add`/`apply_modulo`), so
504
- # THOSE stay covered across every construct SHAPE at every depth —
504
+ # those stay covered across every construct shape at every depth —
505
505
  # just sampled evenly across each side's own operand set (leaves
506
- # AND deep productions alike, not just whichever the list happens
506
+ # and deep productions alike, not just whichever the list happens
507
507
  # to enumerate first) rather than every possible pairing of them.
508
508
  SAMPLE_CAP = 14
509
509
 
@@ -514,13 +514,13 @@ module Hecks
514
514
  sampled.product(sampled)
515
515
  end
516
516
 
517
- # `pairs`' own two-different-lists twin — used wherever the LEFT
518
- # and RIGHT of a construct have genuinely different safety
517
+ # `pairs`' own two-different-lists twin — used wherever the left
518
+ # and right of a construct have genuinely different safety
519
519
  # requirements (`Modulo`'s own receiver vs. argument, below) and
520
- # squaring the SAME sampled list wouldn't be correct.
520
+ # squaring the same sampled list wouldn't be correct.
521
521
  def cross(left, right) = sample(left).product(sample(right))
522
522
 
523
- # THE FULL SET — every boolean-typed expression up to `MAX_DEPTH`,
523
+ # The full set — every boolean-typed expression up to `MAX_DEPTH`,
524
524
  # deduplicated (many shorter expressions are also produced, re-
525
525
  # wrapped, at every deeper level — `.uniq` inside `productions`
526
526
  # already collapses most of that; this is the final pass over the
@@ -531,8 +531,8 @@ module Hecks
531
531
 
532
532
  # Interprets one predicate against the shared synthetic
533
533
  # state/attrs, returning `{ok: true, result: ...}` on any outcome
534
- # `Evaluator.call` itself can express (a real true/false answer, OR
535
- # a clean `EvaluationError` — both are the sublanguage WORKING
534
+ # `Evaluator.call` itself can express (a real true/false answer, or
535
+ # a clean `EvaluationError` — both are the sublanguage working
536
536
  # correctly, never a finding) and `{ok: false, error: ...}` only
537
537
  # for anything else escaping — the one shape this whole file exists
538
538
  # to prove never happens for well-typed input.
@@ -0,0 +1,119 @@
1
+ require "json"
2
+ require "open3"
3
+ require_relative "form_census"
4
+
5
+ module Hecks
6
+ module Fuzzing
7
+ # An agent reads the adversarial corpus and writes the next meeting.
8
+ #
9
+ # `DomainGenerator` forces two `FormCensus::FORMS` onto one aggregate
10
+ # from a seed — cheap, mechanical, and blind to why a combination
11
+ # should break. The bugs the ledger actually logged came from someone
12
+ # reading a stress domain, a bug title, and a runtime file side by
13
+ # side and guessing where the next divergence lives (`corrections`
14
+ # from BUG#30-33, `case_escalation` from ADR 0037 Finding 5 meeting
15
+ # `corrects`). This is that guess, delegated: the mechanical half
16
+ # (what the corpus already puts together, which pairs nothing meets,
17
+ # what the recent bugs were) is computed here; the judgment half is a
18
+ # prompt (`qa/combination_miner/prompt.md`) an agent answers by
19
+ # writing candidate bluebooks, each with a hypothesis. Checking them
20
+ # is not this module's job — `bin/qa_mine_combinations` hands every
21
+ # valid candidate to `bin/qa_generated_domains --source`, the same
22
+ # differential, self-consistency, Rust build and shrinking path a
23
+ # generated domain takes.
24
+ #
25
+ # **Opt-in, never the rotation**. An agent call costs money and minutes
26
+ # and answers differently every time; `bin/qa_tick` never runs it and
27
+ # no `QualityControlDials` entry turns it on. A person runs
28
+ # `bin/qa_mine_combinations` when they want new shapes.
29
+ module CombinationMiner
30
+ PROMPT_TEMPLATE = "qa/combination_miner/prompt.md".freeze
31
+ HYPOTHESIS_FILE = "HYPOTHESIS.md".freeze
32
+
33
+ module_function
34
+
35
+ # Every adversarial domain plus every example — the corpus the
36
+ # census is measured over. Promoted generated domains are included:
37
+ # their shapes are already swept too.
38
+ def corpus_paths(root)
39
+ Hecks::Corpus.members(:stress, :example, root: root).map(&:path)
40
+ .select { |path| Hecks::Corpus.bluebook_files(path) }
41
+ .sort
42
+ end
43
+
44
+ # The last `limit` bug commit subjects on this branch — cheap, needs
45
+ # no ledger or Postgres, and names the mechanism in the title by
46
+ # convention (`BUG#n: …`).
47
+ def recent_bug_titles(root, limit: 60)
48
+ out, status = Open3.capture2("git", "log", "--format=%s", "--grep=BUG#", "-n", limit.to_s, chdir: root)
49
+ status.success? ? out.lines.map(&:strip).reject(&:empty?) : []
50
+ end
51
+
52
+ # The mechanical half: which form pairs the corpus meets (and by
53
+ # whom), which it never meets, and what could not be measured.
54
+ def brief(paths, root:, bug_titles:)
55
+ covered = Hash.new { |hash, key| hash[key] = [] }
56
+ skipped = []
57
+ paths.each do |path|
58
+ FormCensus.covered_pairs(FormCensus.census(path)).each { |pair, names| covered[pair].concat(names) }
59
+ rescue StandardError, ScriptError => e
60
+ skipped << "#{relative(path, root)} (#{e.class}: #{e.message.lines.first&.strip})"
61
+ end
62
+ { "forms" => FormCensus::FORMS.keys, "corpus" => paths.map { |path| relative(path, root) },
63
+ "unmet_pairs" => (FormCensus.pairs - covered.keys).sort,
64
+ "single_carrier_pairs" => covered.select { |_, names| names.uniq.size == 1 }
65
+ .map { |pair, names| "#{pair} (only #{names.first})" }.sort,
66
+ "skipped" => skipped, "recent_bugs" => bug_titles, "covered" => covered }
67
+ end
68
+
69
+ def prompt(root, brief, count:, out_dir:)
70
+ substitutions = {
71
+ "count" => count.to_s, "out_dir" => out_dir, "forms" => brief["forms"].join(", "),
72
+ "corpus" => bulleted(brief["corpus"]), "unmet_pairs" => bulleted(brief["unmet_pairs"]),
73
+ "single_carrier_pairs" => bulleted(brief["single_carrier_pairs"]),
74
+ "recent_bugs" => bulleted(brief["recent_bugs"]), "skipped" => bulleted(brief["skipped"])
75
+ }
76
+ File.read(File.join(root, PROMPT_TEMPLATE)).gsub(/\{\{(\w+)\}\}/) { substitutions.fetch(Regexp.last_match(1)) }
77
+ end
78
+
79
+ # The second, narrower ask: these candidates did not boot, here is
80
+ # why, fix them in place. Nothing else in the directory changes.
81
+ def repair_prompt(failures, out_dir:)
82
+ listed = failures.map { |candidate, error| "- #{candidate[:bluebook]}\n boot error: #{error}" }.join("\n")
83
+ <<~PROMPT
84
+ REPAIR ROUND. You wrote candidate Hecks bluebooks under #{out_dir} for the QA combination miner.
85
+ These did not boot. Edit each file IN PLACE so it boots, keeping the construct combination its
86
+ #{HYPOTHESIS_FILE} names (update the hypothesis if the fix changes the shape). Do not create new
87
+ candidates and do not touch anything outside #{out_dir}.
88
+
89
+ #{listed}
90
+ PROMPT
91
+ end
92
+
93
+ # `<out_dir>/<slug>/<anything>.bluebook` plus an optional
94
+ # HYPOTHESIS.md beside it. One bluebook per candidate: the first,
95
+ # alphabetically, if an agent wrote more.
96
+ def candidates(out_dir)
97
+ Dir[File.join(out_dir, "*")].select { |dir| File.directory?(dir) }.sort.filter_map do |dir|
98
+ bluebook = Dir[File.join(dir, "*.bluebook")].min
99
+ next unless bluebook
100
+
101
+ hypothesis = File.join(dir, HYPOTHESIS_FILE)
102
+ { slug: File.basename(dir), dir: dir, bluebook: bluebook,
103
+ hypothesis: File.exist?(hypothesis) ? File.read(hypothesis).strip : nil }
104
+ end
105
+ end
106
+
107
+ # Pairs the candidate meets on one aggregate that no corpus domain
108
+ # does — informational, never a gate: an agent may be aiming at a
109
+ # shape the census does not name yet.
110
+ def new_pairs(candidate_dir, covered)
111
+ FormCensus.covered_pairs(FormCensus.census(candidate_dir)).keys.reject { |pair| covered.key?(pair) }.sort
112
+ end
113
+
114
+ def relative(path, root) = path.delete_prefix("#{root}/")
115
+
116
+ def bulleted(items) = items.empty? ? "(none)" : items.map { |item| "- #{item}" }.join("\n")
117
+ end
118
+ end
119
+ end