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