hecks 1.4.0 → 1.5.1

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 (275) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hecks/adapters/driven/claude_code.rb +65 -0
  3. data/lib/hecks/adapters/driven/folder.rb +73 -0
  4. data/lib/hecks/adapters/driven/google_authentication.rb +25 -4
  5. data/lib/hecks/adapters/driven/governance_authorization.rb +57 -6
  6. data/lib/hecks/adapters/driven/heki/journal.rb +8 -0
  7. data/lib/hecks/adapters/driven/heki/saga_store.rb +53 -7
  8. data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
  9. data/lib/hecks/adapters/driven/heki.rb +93 -2
  10. data/lib/hecks/adapters/driven/identity_registry.rb +10 -0
  11. data/lib/hecks/adapters/driven/in_memory_ordering.rb +22 -0
  12. data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
  13. data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
  14. data/lib/hecks/adapters/driven/lambda/client.rb +35 -7
  15. data/lib/hecks/adapters/driven/lambda.rb +43 -2
  16. data/lib/hecks/adapters/driven/local_storage.rb +67 -1
  17. data/lib/hecks/adapters/driven/memory.rb +13 -13
  18. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +20 -0
  19. data/lib/hecks/adapters/driven/prism.rb +46 -7
  20. data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
  21. data/lib/hecks/adapters/driven/system_clock.rb +3 -0
  22. data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
  23. data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
  24. data/lib/hecks/adapters/driven.rb +2 -0
  25. data/lib/hecks/adapters/driving/github_webhook.rb +14 -1
  26. data/lib/hecks/behaviors/dsl.rb +58 -0
  27. data/lib/hecks/behaviors/expectations.rb +168 -7
  28. data/lib/hecks/behaviors/ir.rb +11 -0
  29. data/lib/hecks/behaviors/rspec.rb +8 -0
  30. data/lib/hecks/behaviors/runner.rb +19 -0
  31. data/lib/hecks/behaviors.rb +8 -0
  32. data/lib/hecks/bluebook/aggregate.rb +30 -0
  33. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +7 -0
  34. data/lib/hecks/bluebook/assembly/build.rb +25 -4
  35. data/lib/hecks/bluebook/assembly/contract.rb +66 -14
  36. data/lib/hecks/bluebook/assembly/contracts.rb +24 -19
  37. data/lib/hecks/bluebook/assembly/marks.rb +143 -14
  38. data/lib/hecks/bluebook/assembly/specializer.rb +19 -0
  39. data/lib/hecks/bluebook/assembly.rb +25 -7
  40. data/lib/hecks/bluebook/attribute.rb +17 -3
  41. data/lib/hecks/bluebook/behaviour/aggregate.rb +32 -0
  42. data/lib/hecks/bluebook/behaviour/attribute.rb +13 -0
  43. data/lib/hecks/bluebook/behaviour/chapter.rb +55 -2
  44. data/lib/hecks/bluebook/behaviour/command.rb +33 -3
  45. data/lib/hecks/bluebook/behaviour/domain_port.rb +20 -0
  46. data/lib/hecks/bluebook/behaviour/entity.rb +12 -0
  47. data/lib/hecks/bluebook/behaviour/hexagon.rb +26 -0
  48. data/lib/hecks/bluebook/behaviour/lifecycle.rb +23 -2
  49. data/lib/hecks/bluebook/behaviour/policy.rb +34 -9
  50. data/lib/hecks/bluebook/behaviour/process_manager.rb +32 -1
  51. data/lib/hecks/bluebook/behaviour/query.rb +5 -0
  52. data/lib/hecks/bluebook/behaviour/read_model.rb +21 -0
  53. data/lib/hecks/bluebook/behaviour/traits.rb +36 -0
  54. data/lib/hecks/bluebook/behaviour/value_object.rb +17 -5
  55. data/lib/hecks/bluebook/chapter.rb +23 -0
  56. data/lib/hecks/bluebook/command.rb +53 -8
  57. data/lib/hecks/bluebook/domain_port.rb +25 -0
  58. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +196 -37
  59. data/lib/hecks/bluebook/dsl/attribute_collector.rb +79 -42
  60. data/lib/hecks/bluebook/dsl/binding_proxy.rb +59 -0
  61. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +385 -53
  62. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +135 -21
  63. data/lib/hecks/bluebook/dsl/command_builder.rb +189 -27
  64. data/lib/hecks/bluebook/dsl/entity_builder.rb +139 -9
  65. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +41 -0
  66. data/lib/hecks/bluebook/dsl/identity_declaration.rb +23 -6
  67. data/lib/hecks/bluebook/dsl/policy_builder.rb +60 -19
  68. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +79 -15
  69. data/lib/hecks/bluebook/dsl/query_builder.rb +33 -4
  70. data/lib/hecks/bluebook/dsl/read_model_builder.rb +102 -27
  71. data/lib/hecks/bluebook/dsl/rule_reference.rb +60 -8
  72. data/lib/hecks/bluebook/dsl/translation_builder.rb +146 -40
  73. data/lib/hecks/bluebook/dsl/value_object_builder.rb +55 -7
  74. data/lib/hecks/bluebook/entity.rb +29 -0
  75. data/lib/hecks/bluebook/expression/ast_json.rb +115 -23
  76. data/lib/hecks/bluebook/expression/ast_reader.rb +29 -0
  77. data/lib/hecks/bluebook/expression/canonical_form.rb +48 -9
  78. data/lib/hecks/bluebook/expression/evaluator.rb +207 -29
  79. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +36 -0
  80. data/lib/hecks/bluebook/expression/resolver.rb +320 -77
  81. data/lib/hecks/bluebook/hexagon.rb +34 -0
  82. data/lib/hecks/bluebook/lifecycle.rb +11 -0
  83. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -0
  84. data/lib/hecks/bluebook/meta_validator/judge.rb +30 -26
  85. data/lib/hecks/bluebook/meta_validator/plan.rb +43 -8
  86. data/lib/hecks/bluebook/meta_validator/port_judge.rb +1 -0
  87. data/lib/hecks/bluebook/meta_validator/readings.rb +157 -7
  88. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -4
  89. data/lib/hecks/bluebook/meta_validator/shapes.rb +141 -0
  90. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +149 -23
  91. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +5 -4
  92. data/lib/hecks/bluebook/meta_validator/world_judge.rb +1 -0
  93. data/lib/hecks/bluebook/meta_validator.rb +180 -84
  94. data/lib/hecks/bluebook/model_check.rb +268 -24
  95. data/lib/hecks/bluebook/pattern_subset.rb +23 -1
  96. data/lib/hecks/bluebook/process_manager.rb +13 -0
  97. data/lib/hecks/bluebook/project_discovery.rb +5 -0
  98. data/lib/hecks/bluebook/project_loader.rb +40 -0
  99. data/lib/hecks/bluebook/project_register.rb +44 -0
  100. data/lib/hecks/bluebook/query.rb +27 -0
  101. data/lib/hecks/bluebook/read_model.rb +21 -1
  102. data/lib/hecks/bluebook/reference.rb +21 -8
  103. data/lib/hecks/bluebook/smoke_test.rb +29 -6
  104. data/lib/hecks/bluebook/synthesizer.rb +34 -0
  105. data/lib/hecks/bluebook/translation.rb +30 -1
  106. data/lib/hecks/bluebook/value_object.rb +23 -5
  107. data/lib/hecks/bluebook.rb +3 -4
  108. data/lib/hecks/codemod.rb +107 -20
  109. data/lib/hecks/construct.rb +15 -1
  110. data/lib/hecks/corpus.rb +146 -25
  111. data/lib/hecks/deploy/bluebook/deploy.bluebook +105 -0
  112. data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
  113. data/lib/hecks/deploy/oidc.json +5 -0
  114. data/lib/hecks/doc/reference.rb +185 -16
  115. data/lib/hecks/embryonaut_bluebook.rb +32 -9
  116. data/lib/hecks/facade/handle.rb +76 -3
  117. data/lib/hecks/facade/surface/aggregate_door.rb +8 -0
  118. data/lib/hecks/forms/field_shape.rb +3 -0
  119. data/lib/hecks/forms/page.rb +14 -0
  120. data/lib/hecks/forms/port_argument.rb +12 -0
  121. data/lib/hecks/forms/query_form_renderer.rb +63 -0
  122. data/lib/hecks/forms/record_renderer.rb +58 -0
  123. data/lib/hecks/forms/record_table.rb +27 -0
  124. data/lib/hecks/forms/reference_options.rb +24 -0
  125. data/lib/hecks/forms/value_object_shape.rb +10 -0
  126. data/lib/hecks/fqn.rb +58 -0
  127. data/lib/hecks/framework/bluebook/compliance.bluebook +221 -0
  128. data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
  129. data/lib/hecks/framework/oidc.json +15 -0
  130. data/lib/hecks/framework.rb +43 -20
  131. data/lib/hecks/freezer.rb +17 -1
  132. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +159 -24
  133. data/lib/hecks/fuzzing/combination_miner.rb +59 -0
  134. data/lib/hecks/fuzzing/concurrent_dispatch.rb +109 -8
  135. data/lib/hecks/fuzzing/coverage_campaign.rb +56 -13
  136. data/lib/hecks/fuzzing/differential.rb +34 -0
  137. data/lib/hecks/fuzzing/domain_generator.rb +188 -11
  138. data/lib/hecks/fuzzing/era_boundary.rb +45 -15
  139. data/lib/hecks/fuzzing/form_census.rb +86 -0
  140. data/lib/hecks/fuzzing/generated_domain_check.rb +76 -0
  141. data/lib/hecks/fuzzing/invalid_value_generator.rb +39 -0
  142. data/lib/hecks/fuzzing/isolated_boot.rb +79 -22
  143. data/lib/hecks/fuzzing/nondeterministic.rb +13 -1
  144. data/lib/hecks/fuzzing/persistence_parity.rb +95 -3
  145. data/lib/hecks/fuzzing/properties/corrections.rb +25 -0
  146. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +158 -14
  147. data/lib/hecks/fuzzing/properties/guards.rb +44 -0
  148. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +48 -0
  149. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +18 -0
  150. data/lib/hecks/fuzzing/properties/outbox.rb +49 -11
  151. data/lib/hecks/fuzzing/properties/querying.rb +68 -14
  152. data/lib/hecks/fuzzing/properties.rb +24 -15
  153. data/lib/hecks/fuzzing/qa_settings.rb +12 -0
  154. data/lib/hecks/fuzzing/replay.rb +137 -29
  155. data/lib/hecks/fuzzing/rotation_priority.rb +41 -21
  156. data/lib/hecks/fuzzing/rust_gap_manifest.rb +46 -20
  157. data/lib/hecks/fuzzing/self_consistency.rb +189 -40
  158. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +12 -6
  159. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +1 -1
  160. data/lib/hecks/fuzzing/sequence_generator.rb +47 -0
  161. data/lib/hecks/fuzzing/shrinker.rb +114 -2
  162. data/lib/hecks/fuzzing/structural_skips.rb +18 -4
  163. data/lib/hecks/fuzzing/sweep_depth.rb +8 -0
  164. data/lib/hecks/fuzzing/target_capabilities.rb +61 -14
  165. data/lib/hecks/fuzzing/value_generator.rb +98 -10
  166. data/lib/hecks/grammar/evolve.rb +178 -2
  167. data/lib/hecks/grammar.rb +46 -0
  168. data/lib/hecks/ir.rb +38 -7
  169. data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
  170. data/lib/hecks/literal.rb +32 -0
  171. data/lib/hecks/naming.rb +88 -7
  172. data/lib/hecks/ports/access_control.rb +5 -10
  173. data/lib/hecks/ports/authorization.rb +3 -6
  174. data/lib/hecks/ports/identity_assignment.rb +1 -2
  175. data/lib/hecks/ports/identity_resolution.rb +1 -2
  176. data/lib/hecks/ports/key_vault.port +6 -0
  177. data/lib/hecks/ports/key_vault.rb +85 -0
  178. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +6 -0
  179. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +38 -2
  180. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -0
  181. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +40 -0
  182. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +92 -1
  183. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
  184. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +8 -0
  185. data/lib/hecks/ports/query/in_memory.rb +39 -6
  186. data/lib/hecks/ports/query/ordering.rb +15 -0
  187. data/lib/hecks/ports.rb +1 -0
  188. data/lib/hecks/projections/bootstrap_table.rb +43 -8
  189. data/lib/hecks/projections/deploy/fargate.rb +666 -0
  190. data/lib/hecks/projections/deploy/lambda.rb +2423 -0
  191. data/lib/hecks/projections/deploy/shared.rb +624 -0
  192. data/lib/hecks/projections/diagrams.rb +243 -7
  193. data/lib/hecks/projections/glossary/html.rb +88 -0
  194. data/lib/hecks/projections/glossary/markdown.rb +47 -0
  195. data/lib/hecks/projections/glossary/mermaid.rb +48 -0
  196. data/lib/hecks/projections/glossary/sentences.rb +105 -0
  197. data/lib/hecks/projections/glossary.rb +161 -19
  198. data/lib/hecks/projections/model/deviations.rb +44 -0
  199. data/lib/hecks/projections/model.rb +51 -1
  200. data/lib/hecks/projections/oidc.rb +40 -3
  201. data/lib/hecks/projections/parser_table.rb +49 -8
  202. data/lib/hecks/projections/reference.rb +12 -0
  203. data/lib/hecks/projections/rust_vocabulary.rb +219 -16
  204. data/lib/hecks/projections/shape.rb +8 -0
  205. data/lib/hecks/projections/statements.rb +63 -16
  206. data/lib/hecks/projections/vocabulary.rb +17 -0
  207. data/lib/hecks/projections.rb +3 -0
  208. data/lib/hecks/projector/cli_projector.rb +218 -10
  209. data/lib/hecks/projector/docs_projector.rb +145 -19
  210. data/lib/hecks/projector/exporter.rb +65 -11
  211. data/lib/hecks/projector/ir_projector.rb +6 -0
  212. data/lib/hecks/projector/narrate_projector.rb +136 -15
  213. data/lib/hecks/projector/target.rb +47 -10
  214. data/lib/hecks/projector.rb +101 -19
  215. data/lib/hecks/query_ir.rb +47 -0
  216. data/lib/hecks/query_specification/common/null_policy.rb +5 -3
  217. data/lib/hecks/rendering.rb +6 -0
  218. data/lib/hecks/router/namespace_installer.rb +13 -0
  219. data/lib/hecks/router.rb +55 -0
  220. data/lib/hecks/runtime/aggregate_lock.rb +9 -0
  221. data/lib/hecks/runtime/boot_gates.rb +18 -0
  222. data/lib/hecks/runtime/caller.rb +32 -0
  223. data/lib/hecks/runtime/capability_graph.rb +11 -0
  224. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +23 -21
  225. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +14 -15
  226. data/lib/hecks/runtime/command_interpreter.rb +42 -17
  227. data/lib/hecks/runtime/command_rules/admissibility.rb +165 -14
  228. data/lib/hecks/runtime/command_rules/arithmetic.rb +17 -5
  229. data/lib/hecks/runtime/command_rules/references.rb +118 -28
  230. data/lib/hecks/runtime/dependency_planning.rb +45 -0
  231. data/lib/hecks/runtime/dispatcher.rb +28 -50
  232. data/lib/hecks/runtime/entity_element.rb +161 -8
  233. data/lib/hecks/runtime/entity_interpreter.rb +44 -9
  234. data/lib/hecks/runtime/errors.rb +18 -4
  235. data/lib/hecks/runtime/event.rb +10 -5
  236. data/lib/hecks/runtime/identity.rb +71 -3
  237. data/lib/hecks/runtime/instance.rb +67 -7
  238. data/lib/hecks/runtime/interpreting.rb +13 -5
  239. data/lib/hecks/runtime/invocation.rb +118 -36
  240. data/lib/hecks/runtime/loader.rb +94 -8
  241. data/lib/hecks/runtime/outbox.rb +145 -7
  242. data/lib/hecks/runtime/policy_interpreter.rb +22 -9
  243. data/lib/hecks/runtime/port_operation_interpreter.rb +20 -0
  244. data/lib/hecks/runtime/query_interpreter.rb +40 -12
  245. data/lib/hecks/runtime/reaction_invocation.rb +53 -8
  246. data/lib/hecks/runtime/read_model_interpreter.rb +23 -7
  247. data/lib/hecks/runtime/rebuild_sweep.rb +28 -0
  248. data/lib/hecks/runtime/reference_hop.rb +42 -0
  249. data/lib/hecks/runtime/refusal_wording.rb +50 -0
  250. data/lib/hecks/runtime/registry/saga_persistence.rb +11 -0
  251. data/lib/hecks/runtime/registry/verification.rb +119 -4
  252. data/lib/hecks/runtime/registry.rb +157 -4
  253. data/lib/hecks/runtime/remote_dispatcher.rb +92 -6
  254. data/lib/hecks/runtime/routing.rb +27 -2
  255. data/lib/hecks/runtime/saga_interpreter/correlation.rb +10 -12
  256. data/lib/hecks/runtime/saga_interpreter.rb +27 -13
  257. data/lib/hecks/runtime/tenant_check.rb +26 -6
  258. data/lib/hecks/runtime/tenant_scope.rb +18 -0
  259. data/lib/hecks/runtime/value/coercion.rb +255 -33
  260. data/lib/hecks/runtime/value/entity_list_coercion.rb +102 -30
  261. data/lib/hecks/runtime/value.rb +50 -0
  262. data/lib/hecks/runtime.rb +32 -0
  263. data/lib/hecks/storehouse.rb +305 -9
  264. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
  265. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
  266. data/lib/hecks/version.rb +1 -1
  267. data/lib/hecks.rb +79 -1
  268. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -0
  269. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +12 -2
  270. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +29 -5
  271. metadata +14 -5
  272. data/lib/hecks/codemod/legacy_dispatch_args.rb +0 -299
  273. data/lib/hecks/codemod/legacy_dispatch_recorder.rb +0 -186
  274. data/lib/hecks/deprecation.rb +0 -95
  275. data/lib/hecks/framework/bluebook/compliance.bluebook +0 -1
@@ -20,6 +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 generation
24
+ #
23
25
  # Type-directed, not exhaustive-over-strings: this generator only ever
24
26
  # recurses into a sub-expression whose own type the surrounding
25
27
  # construct actually accepts (never `"x".modulo(true)`) — mirroring,
@@ -36,7 +38,9 @@ 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
41
+ # ## What "PROVEN" means here, precisely
42
+ #
43
+ # Every expression this generator
40
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
@@ -133,20 +137,25 @@ module Hecks
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
139
  # 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.
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,18 +169,27 @@ 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
 
191
+ # The per-type stack of currently-bound block-parameter leaf names.
192
+ #
175
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
@@ -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)
@@ -237,12 +282,21 @@ module Hecks
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
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
302
  # `Modulo`'s own receiver (not its argument — that side already
@@ -290,6 +344,11 @@ module Hecks
290
344
  # `StartsWith`/`EndsWith`, `BlockPredicate` — every one of them
291
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?"] } +
@@ -320,6 +379,10 @@ module Hecks
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
 
@@ -342,6 +405,10 @@ module Hecks
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" } +
@@ -357,6 +424,10 @@ module Hecks
357
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,7 +435,16 @@ 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
450
  # Arrays known (by construction, not merely by type) to hold numeric
@@ -375,6 +455,10 @@ module Hecks
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,6 +466,12 @@ 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
@@ -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)
@@ -444,6 +540,11 @@ module Hecks
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
 
@@ -467,6 +568,11 @@ module Hecks
467
568
  # feeds three more constructs per body (`all?`/`any?`/`none?`) times
468
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
@@ -476,6 +582,11 @@ module Hecks
476
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})" } +
@@ -507,8 +618,18 @@ module Hecks
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)
@@ -518,6 +639,10 @@ module Hecks
518
639
  # and right of a construct have genuinely different safety
519
640
  # requirements (`Modulo`'s own receiver vs. argument, below) and
520
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
648
  # The full set — every boolean-typed expression up to `MAX_DEPTH`,
@@ -525,6 +650,10 @@ module Hecks
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
@@ -536,6 +665,12 @@ module Hecks
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 }
@@ -35,6 +35,10 @@ module Hecks
35
35
  # Every adversarial domain plus every example — the corpus the
36
36
  # census is measured over. Promoted generated domains are included:
37
37
  # their shapes are already swept too.
38
+ #
39
+ # @param root [String] repository root to search under
40
+ # @return [Array<String>] paths of every stress/example corpus member that
41
+ # declares at least one bluebook file
38
42
  def corpus_paths(root)
39
43
  Hecks::Corpus.members(:stress, :example, root: root).map(&:path)
40
44
  .select { |path| Hecks::Corpus.bluebook_files(path) }
@@ -44,6 +48,11 @@ module Hecks
44
48
  # The last `limit` bug commit subjects on this branch — cheap, needs
45
49
  # no ledger or Postgres, and names the mechanism in the title by
46
50
  # convention (`BUG#n: …`).
51
+ #
52
+ # @param root [String] repository root to run `git log` in
53
+ # @param limit [Integer] maximum number of commit subjects to return
54
+ # @return [Array<String>] matching commit subjects, newest first; empty if
55
+ # `git log` fails
47
56
  def recent_bug_titles(root, limit: 60)
48
57
  out, status = Open3.capture2("git", "log", "--format=%s", "--grep=BUG#", "-n", limit.to_s, chdir: root)
49
58
  status.success? ? out.lines.map(&:strip).reject(&:empty?) : []
@@ -51,6 +60,20 @@ module Hecks
51
60
 
52
61
  # The mechanical half: which form pairs the corpus meets (and by
53
62
  # whom), which it never meets, and what could not be measured.
63
+ #
64
+ # @param paths [Array<String>] corpus member paths to census, as returned by
65
+ # `#corpus_paths`
66
+ # @param root [String] repository root, to render `paths` relative to it
67
+ # @param bug_titles [Array<String>] recent bug commit subjects, as returned
68
+ # by `#recent_bug_titles`
69
+ # @return [Hash] `"forms"` (`Array<String>`, every declared form name),
70
+ # `"corpus"` (`Array<String>`, `paths` relative to `root`),
71
+ # `"unmet_pairs"` (`Array<String>`, form pairs no corpus member meets),
72
+ # `"single_carrier_pairs"` (`Array<String>`, pairs only one form name
73
+ # meets), `"skipped"` (`Array<String>`, paths that could not be
74
+ # censused, with the error), `"recent_bugs"` (`bug_titles`), and
75
+ # `"covered"` (`Hash{String => Array<String>}`, pair to every form name
76
+ # that meets it)
54
77
  def brief(paths, root:, bug_titles:)
55
78
  covered = Hash.new { |hash, key| hash[key] = [] }
56
79
  skipped = []
@@ -66,6 +89,16 @@ module Hecks
66
89
  "skipped" => skipped, "recent_bugs" => bug_titles, "covered" => covered }
67
90
  end
68
91
 
92
+ # Fills `PROMPT_TEMPLATE`'s `{{placeholders}}` with `brief`'s own data, for
93
+ # the agent to answer by writing candidate bluebooks.
94
+ #
95
+ # @param root [String] repository root the prompt template is read from
96
+ # @param brief [Hash] the mechanical brief, as returned by `#brief`
97
+ # @param count [Integer] how many candidates the prompt asks the agent for
98
+ # @param out_dir [String] where the prompt asks the agent to write candidates
99
+ # @return [String] the filled-in prompt text
100
+ # @raise [KeyError] if the template names a placeholder `substitutions` does
101
+ # not provide
69
102
  def prompt(root, brief, count:, out_dir:)
70
103
  substitutions = {
71
104
  "count" => count.to_s, "out_dir" => out_dir, "forms" => brief["forms"].join(", "),
@@ -78,6 +111,11 @@ module Hecks
78
111
 
79
112
  # The second, narrower ask: these candidates did not boot, here is
80
113
  # why, fix them in place. Nothing else in the directory changes.
114
+ #
115
+ # @param failures [Array<Array(Hash, String)>] `[candidate, error]` pairs,
116
+ # `candidate` as returned by `#candidates`, `error` the boot failure message
117
+ # @param out_dir [String] the directory the candidates were written under
118
+ # @return [String] the repair round's own prompt text
81
119
  def repair_prompt(failures, out_dir:)
82
120
  listed = failures.map { |candidate, error| "- #{candidate[:bluebook]}\n boot error: #{error}" }.join("\n")
83
121
  <<~PROMPT
@@ -93,6 +131,11 @@ module Hecks
93
131
  # `<out_dir>/<slug>/<anything>.bluebook` plus an optional
94
132
  # HYPOTHESIS.md beside it. One bluebook per candidate: the first,
95
133
  # alphabetically, if an agent wrote more.
134
+ #
135
+ # @param out_dir [String] the directory candidates were written under
136
+ # @return [Array<Hash>] one `{slug:, dir:, bluebook:, hypothesis:}` entry per
137
+ # candidate subdirectory that declares a `.bluebook` file; `hypothesis` is
138
+ # `nil` when no `HYPOTHESIS.md` sits beside it
96
139
  def candidates(out_dir)
97
140
  Dir[File.join(out_dir, "*")].select { |dir| File.directory?(dir) }.sort.filter_map do |dir|
98
141
  bluebook = Dir[File.join(dir, "*.bluebook")].min
@@ -107,12 +150,28 @@ module Hecks
107
150
  # Pairs the candidate meets on one aggregate that no corpus domain
108
151
  # does — informational, never a gate: an agent may be aiming at a
109
152
  # shape the census does not name yet.
153
+ #
154
+ # @param candidate_dir [String] path to the candidate's own directory
155
+ # @param covered [Hash{String => Array<String>}] the corpus's own covered
156
+ # pairs, as returned in `#brief`'s `"covered"` entry
157
+ # @return [Array<String>] form pairs the candidate meets that `covered` does
158
+ # not already name, sorted
110
159
  def new_pairs(candidate_dir, covered)
111
160
  FormCensus.covered_pairs(FormCensus.census(candidate_dir)).keys.reject { |pair| covered.key?(pair) }.sort
112
161
  end
113
162
 
163
+ # Renders a path relative to the repository root.
164
+ #
165
+ # @param path [String] an absolute (or `root`-prefixed) path
166
+ # @param root [String] the prefix to strip
167
+ # @return [String] `path` with `root`'s own prefix removed
114
168
  def relative(path, root) = path.delete_prefix("#{root}/")
115
169
 
170
+ # Renders a list of strings as a Markdown bullet list.
171
+ #
172
+ # @param items [Array<String>] lines to render as a Markdown bullet list
173
+ # @return [String] one `"- item"` line per entry, newline-joined; `"(none)"`
174
+ # when `items` is empty
116
175
  def bulleted(items) = items.empty? ? "(none)" : items.map { |item| "- #{item}" }.join("\n")
117
176
  end
118
177
  end