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
@@ -1,30 +1,34 @@
1
1
  module Hecks
2
2
  module Fuzzing
3
- # WHAT A SWEEP TARGET CAN ACTUALLY BE CHECKED FOR, read off the
3
+ # What a sweep target can actually be checked for, read off the
4
4
  # filesystem — never off a stored list.
5
5
  #
6
- # `bin/qa_sweep` used to decide its ONE comparison mode inline: "is
7
- # there a Cargo feature named after this directory? then
8
- # `:differential`, else `:ruby_only`", and a separate hand-typed abort
9
- # for `--persistence-parity` ("does any .hecksagon bind PostgresEra?").
10
- # Every further mode the practice adds (era boundary, concurrency, a
11
- # WASM front) would have grown one more inline `if`, each one a
6
+ # `bin/qa_sweep` does not decide its comparison modes with inline
7
+ # conditionals — "is there a Cargo feature named after this directory?
8
+ # then `:differential`, else `:ruby_only`", a separate hand-typed abort
9
+ # for `--persistence-parity` ("does any .hecksagon bind PostgresEra?"),
10
+ # and so on for every further mode the practice adds (era boundary,
11
+ # concurrency, a WASM front) — because each such inline `if` is a
12
12
  # policy decision hiding in a script. This module is those decisions
13
- # as DATA: `MODE_REQUIREMENTS` says which capabilities each mode
13
+ # as data: `MODE_REQUIREMENTS` says which capabilities each mode
14
14
  # needs, `infer` says which capabilities a target's own directory
15
15
  # actually has, and `resolve` is the one rule that joins them —
16
16
  # `modes_to_run = enabled ∩ eligible`.
17
17
  #
18
- # INFERENCE DECIDES; A STORED LIST ONLY RECORDS. `Target.capabilities`
19
- # (qa/bluebook/quality_control.bluebook, once PR-1's era lands) is
20
- # written by the runner from exactly this inference at release time
21
- # so `Target.EligibleFor(mode)` can AUDIT the rotation from the
22
- # ledger alone — but the runner re-infers every sweep, because a
23
- # stored list that lags yesterday's Cargo feature is precisely the
24
- # "quiet divergence" (the chapter's own opening comment) this whole
25
- # practice exists to hunt. Nothing here ever reads the ledger.
18
+ # ## Inference decides; a stored list only records
26
19
  #
27
- # EVERY REGEX IS ONE THE HARNESS ALREADY OWNED, moved here rather than
20
+ # `Target.capabilities` (qa/bluebook/quality_control.bluebook, once
21
+ # PR-1's era lands) is written by the runner from exactly this
22
+ # inference at release time so `Target.EligibleFor(mode)` can audit
23
+ # the rotation from the ledger alone — but the runner re-infers every
24
+ # sweep, because a stored list that lags yesterday's Cargo feature is
25
+ # precisely the "quiet divergence" (the chapter's own opening comment)
26
+ # this whole practice exists to hunt. Nothing here ever reads the
27
+ # ledger.
28
+ #
29
+ # ## Regex provenance
30
+ #
31
+ # Every regex is one the harness already owned, moved here rather than
28
32
  # re-derived, and each one's provenance is named beside it so a
29
33
  # future edit to the original site is a visible drift, not a silent
30
34
  # one: the Cargo feature line (`RustConformanceHelpers#build_rust_for`),
@@ -45,10 +49,13 @@ module Hecks
45
49
  # domain-level default (`persisted_by "PostgresEra"`).
46
50
  POSTGRES_ERA_BINDING = /persisted_by\s*\(?\s*"PostgresEra"/
47
51
 
48
- # `HecksagonBuilder#uses_framework` — the one line that attaches
49
- # Governance's own `RoleAssignment` lookup to a domain's `role` checks
50
- # (`CommandRules::Authorization#governance_attached?`).
51
- GOVERNANCE_ATTACHED = /uses_framework\s*\(?\s*"Governance"/
52
+ # `HecksagonBuilder#uses_framework "X"` — captures which member a
53
+ # hecksagon attaches, whatever its name. Whether that member answers
54
+ # a role check is then read off its own declaration (`provides
55
+ # "authorization"`, via `Framework.providers_of`), the same rule
56
+ # `Registry#authorization_provider_for` applies at runtime — never a
57
+ # match on the literal "Governance".
58
+ FRAMEWORK_ATTACHED = /uses_framework\s*\(?\s*"([^"]+)"/
52
59
 
53
60
  # A command-level `role "..."` — the only construct
54
61
  # `refuse_role_mismatch` ever has anything to check a caller against.
@@ -61,21 +68,21 @@ module Hecks
61
68
 
62
69
  PROCESS_MANAGER = /^\s*process_manager\s+"/
63
70
 
64
- # WHICH CAPABILITIES EACH MODE NEEDS BEFORE IT CAN SAY ANYTHING TRUE
65
- # ABOUT A TARGET. An empty list means "any target at all" — every
71
+ # Which capabilities each mode needs before it can say anything true
72
+ # about a target. An empty list means "any target at all" — every
66
73
  # domain boots under Memory, so Ruby-only properties and the
67
74
  # self-consistency pass are always answerable. `ruby_only` is listed
68
- # requirement-free on purpose and then EXCLUDED by `resolve` whenever
75
+ # requirement-free on purpose and then excluded by `resolve` whenever
69
76
  # `differential` resolved too: they are the same seat, and a compiled
70
77
  # Rust binary is strictly the better occupant (item 1 of the
71
- # detection plan folded the Ruby-only property battery INTO the
78
+ # detection plan folded the Ruby-only property battery into the
72
79
  # differential seat, so nothing is lost by the exclusion).
73
80
  #
74
81
  # The four `false`-by-default modes in `QualityControlDials::MODES`
75
82
  # (`adapter_parity_postgres`, `era_boundary`, `concurrency`,
76
83
  # `wasm_front`) are named here with their requirements even though
77
84
  # nothing runs them yet — so `resolved modes:` can already say, per
78
- # target, which of them WOULD be eligible the day a human flips the
85
+ # target, which of them would be eligible the day a human flips the
79
86
  # dial, and so flipping it is a one-line data change rather than a
80
87
  # code change plus a data change.
81
88
  MODE_REQUIREMENTS = {
@@ -92,7 +99,24 @@ module Hecks
92
99
  wasm_front: %w[rust]
93
100
  }.freeze
94
101
 
95
- # MODES THAT NAME A SEPARATE, EXPENSIVE PASS OF THEIR OWN rather than
102
+ # Which of those modes `bin/qa_sweep` can actually run today.
103
+ # `MODE_REQUIREMENTS` above says what a mode needs; this says what
104
+ # exists to do it, and the two are not the same. Conflating them is
105
+ # how `qa/settings.yml` came to enable `wasm_front` and
106
+ # `adapter_parity_postgres` with nothing behind either — no seat, no
107
+ # check folded into the seed loop, no `MODE_EXPECTATIONS` entry — while
108
+ # `resolved modes:` still printed them per target, so the sweep
109
+ # advertised coverage it never performed. A mode named in
110
+ # `MODE_REQUIREMENTS` but absent here is one this practice wants and has
111
+ # not built: `bin/qa_sweep` refuses to start when a dial or `--modes`
112
+ # enables it, rather than resolving it into a line nobody can act on.
113
+ # `spec/qa_sweep_runnable_modes_spec.rb` keeps this list honest from
114
+ # both sides by grepping the runner itself.
115
+ RUNNABLE_MODES = %i[differential ruby_only self_consistency properties_in_differential
116
+ structural_skip_report adapter_parity_sqlite persistence_parity
117
+ era_boundary concurrency].freeze
118
+
119
+ # Modes that name a separate, expensive pass of their own rather than
96
120
  # an extra check folded into the ordinary per-seed loop —
97
121
  # `bin/qa_sweep` runs these only when asked by name (`--modes
98
122
  # persistence_parity`, or its older alias `--persistence-parity`) or
@@ -101,37 +125,66 @@ module Hecks
101
125
  # exists because that pass pays for real Postgres I/O per dispatch).
102
126
  DEFERRED_MODES = %i[persistence_parity adapter_parity_postgres era_boundary concurrency].freeze
103
127
 
128
+ # Reads a target directory to find out which capabilities it actually has.
129
+ #
104
130
  # Sorted, plain strings — comma-joined by the runner into the
105
131
  # `Target.Release(capabilities:)` value object and printed verbatim
106
132
  # on the `resolved modes:` line, so the same spelling is what a
107
133
  # human reads, what `--all` parses back, and what the ledger stores.
134
+ #
135
+ # @param domain_path [String] filesystem path to the target domain's directory
136
+ # @param rust_dir [String] path to the Rust project root, checked for a matching
137
+ # Cargo feature; defaults to this repo's own `rust/` directory
138
+ # @return [Array<String>] the target's capabilities, sorted; a subset of
139
+ # `sqlite`, `rust`, `postgres_era`, `translations`, `governance`, `role_gated`,
140
+ # `tenant`, `sagas`
108
141
  def infer(domain_path, rust_dir: File.expand_path("../../../rust", __dir__))
109
142
  capabilities = %w[sqlite]
110
143
  capabilities << "rust" if rust_feature?(domain_path, rust_dir)
111
144
  capabilities << "postgres_era" if any_file?(domain_path, "*.hecksagon", POSTGRES_ERA_BINDING)
112
145
  capabilities << "translations" if Dir.glob(File.join(domain_path, "**", "translations", "*.bluebook")).any?
113
- capabilities << "governance" if any_file?(domain_path, "*.hecksagon", GOVERNANCE_ATTACHED)
146
+ capabilities << "governance" if authorization_attached?(domain_path)
114
147
  capabilities << "role_gated" if any_file?(domain_path, "*.bluebook", ROLE_GATED)
115
148
  capabilities << "tenant" if any_file?(domain_path, "*.bluebook", TENANT_SCOPED)
116
149
  capabilities << "sagas" if any_file?(domain_path, "*.bluebook", PROCESS_MANAGER)
117
150
  capabilities.sort
118
151
  end
119
152
 
153
+ # Answers whether `mode` can run against a target with `capabilities`.
154
+ #
155
+ # @param mode [Symbol, String] a key of `MODE_REQUIREMENTS`, such as `:differential`
156
+ # @param capabilities [Array<String>] the target's capabilities, as `infer` returns
157
+ # @return [Boolean] whether `capabilities` covers every capability `mode` requires
158
+ # @raise [ArgumentError] if `mode` names no entry in `MODE_REQUIREMENTS`
120
159
  def eligible?(mode, capabilities)
121
160
  required = MODE_REQUIREMENTS.fetch(mode.to_sym) { raise ArgumentError, "unknown sweep mode #{mode.inspect}" }
122
161
  (required - capabilities).empty?
123
162
  end
124
163
 
125
- # THE ONE RULE. `enabled` is whatever the dial (or `--modes`) turned
164
+ # The one rule. `enabled` is whatever the dial (or `--modes`) turned
126
165
  # on, in the dial's own declaration order — that order is preserved
127
166
  # so the printed line reads the same way the dial does. Then the
128
167
  # single exclusion named on `MODE_REQUIREMENTS`.
168
+ #
169
+ # @param enabled [Array<Symbol, String>] modes turned on, in the dial's own
170
+ # declaration order
171
+ # @param capabilities [Array<String>] the target's capabilities, as `infer` returns
172
+ # @return [Array<Symbol>] `enabled`'s eligible modes, in `enabled`'s order, with
173
+ # `:ruby_only` dropped whenever `:differential` is also eligible
129
174
  def resolve(enabled, capabilities)
130
175
  resolved = enabled.map(&:to_sym).select { |mode| eligible?(mode, capabilities) }
131
176
  resolved.delete(:ruby_only) if resolved.include?(:differential)
132
177
  resolved
133
178
  end
134
179
 
180
+ # Answers whether `rust_dir`'s Cargo.toml declares a feature named after
181
+ # `domain_path`'s own directory.
182
+ #
183
+ # @param domain_path [String] filesystem path to the target domain's directory;
184
+ # its basename, lowercased, is the feature name looked up
185
+ # @param rust_dir [String] path to the Rust project root (holds `Cargo.toml`)
186
+ # @return [Boolean] whether a matching Cargo feature is declared; false if
187
+ # `rust_dir` has no `Cargo.toml`
135
188
  def rust_feature?(domain_path, rust_dir)
136
189
  cargo_toml = File.join(rust_dir, "Cargo.toml")
137
190
  return false unless File.file?(cargo_toml)
@@ -141,6 +194,31 @@ module Hecks
141
194
  features.match?(/^#{Regexp.escape(feature)}\s*=\s*\[\]/)
142
195
  end
143
196
 
197
+ # Answers whether any `.hecksagon` under `domain_path` attaches a member
198
+ # that provides authorization.
199
+ #
200
+ # The capability label stays "governance" — it is the value
201
+ # `bin/qa_sweep` writes into the QualityControl ledger's
202
+ # `Target.capabilities`, and renaming it is a ledger change, not
203
+ # part of dropping the name check.
204
+ #
205
+ # @param domain_path [String] filesystem path to the target domain's directory
206
+ # @return [Boolean] whether the domain attaches a member that provides the
207
+ # `Bluebook::Capabilities::AUTHORIZATION` capability
208
+ def authorization_attached?(domain_path)
209
+ attached = Dir.glob(File.join(domain_path, "**", "*.hecksagon"))
210
+ .flat_map { |path| File.read(path).scan(FRAMEWORK_ATTACHED).flatten }.uniq
211
+ providers = Framework.providers_of(Bluebook::Capabilities::AUTHORIZATION)
212
+ attached.intersect?(providers)
213
+ end
214
+
215
+ # Answers whether any file under `domain_path` matching `glob` contains `pattern`.
216
+ #
217
+ # @param domain_path [String] filesystem path to the target domain's directory
218
+ # @param glob [String] a `Dir.glob` pattern relative to `domain_path`, such as
219
+ # `"*.bluebook"`
220
+ # @param pattern [Regexp] the pattern each matching file's contents is tested against
221
+ # @return [Boolean] whether any matching file's contents match `pattern`
144
222
  def any_file?(domain_path, glob, pattern)
145
223
  Dir.glob(File.join(domain_path, "**", glob)).any? { |path| File.read(path).match?(pattern) }
146
224
  end
@@ -4,7 +4,7 @@ module Hecks
4
4
  module Fuzzing
5
5
  # One attribute, one value — in the exact JSON shape the hand-written
6
6
  # corpus already uses (a value-object-typed attribute is a nested hash keyed
7
- # by its own field names ; a reference is the BARE ID of the head it points
7
+ # by its own field names ; a reference is the bare ID of the head it points
8
8
  # at, because that is what a reference is). Everything
9
9
  # returned is a plain, JSON-safe Ruby value : String keys throughout, never
10
10
  # symbols, so a generated step can be dumped straight to JSON.
@@ -15,7 +15,7 @@ module Hecks
15
15
  # codebase," and a fixed corpus of a few dozen examples rarely happens to
16
16
  # exercise the boundary that actually breaks. But mostly not : an attribute
17
17
  # named `email`/`currency` almost always needs a specific shape just to get
18
- # PAST its own invariant (`address.include?("@")`, `currency.size == 3`) —
18
+ # past its own invariant (`address.include?("@")`, `currency.size == 3`) —
19
19
  # banking's very first fuzz run never got past Customer.Register, because
20
20
  # no plain random word ever contains "@". Reaching deep state matters more
21
21
  # than edge-casing every single field every single time, so the "normal"
@@ -31,7 +31,7 @@ module Hecks
31
31
  # (lib/hecks/runtime/value/coercion.rb) has accepted a bare
32
32
  # `"large"` in place of `{"value" => "large"}` for any single-field
33
33
  # value object since 86727afd — but until this generator actually
34
- # PRODUCES that shape, nothing exercises it: neither the adapter-
34
+ # produces that shape, nothing exercises it: neither the adapter-
35
35
  # agreement gate nor the Rust/WASM `from_json` codegen (rust/project/
36
36
  # json_codec.rb) can ever be caught drifting on a shape they're
37
37
  # never handed.
@@ -44,22 +44,22 @@ module Hecks
44
44
  # PRD 05 (numeric-boundary-coverage) — Bignum (`2**100`, past i64's
45
45
  # own ceiling, which Ruby's own `Integer` has no such ceiling for —
46
46
  # `rust/src/kernel/json.rs`'s own `integral_i64` doc comment names
47
- # exactly this: a Rust kernel value CANNOT represent it, so this
47
+ # exactly this: a Rust kernel value cannot represent it, so this
48
48
  # exercises a real cross-runtime capability gap, not a Ruby-only
49
49
  # edge) and its negative twin. Both round-trip through Ruby's own
50
50
  # arithmetic/JSON cleanly (confirmed directly: `(2**100).clamp(...)`
51
51
  # and `JSON.generate(2**100)` both just work — Integer has no
52
- # ceiling here), so nothing in THIS runtime needed a fix for these;
52
+ # ceiling here), so nothing in this runtime needed a fix for these;
53
53
  # they're included so a real generated sequence occasionally
54
54
  # produces the value at all, since nothing had, repo-wide, before.
55
55
  INTEGER_EDGE_CASES = [0, -1, 2_147_483_647, -2_147_483_648, 2**100, -(2**100)].freeze
56
56
  # BUG#35 (QualityControl QA ledger, `lease-clock-json-precision`) —
57
57
  # a Bignum edge case (`2**100`, above) landing on an Integer-typed
58
- # CLOCK or COUNT reading fires the already-catalogued `Json::Num`/
58
+ # clock or count reading fires the already-catalogued `Json::Num`/
59
59
  # f64 precision-loss class (`rust/src/kernel/json.rs`'s
60
60
  # `parse_number` parses every number through `s.parse::<f64>()`
61
61
  # before any target-type conversion runs, and `Json::Num` is a
62
- # plain `f64` end to end — see that file's own header) on a NEW
62
+ # plain `f64` end to end — see that file's own header) on a new
63
63
  # site every time a new clock/count-shaped field is authored,
64
64
  # without adding any new coverage: both engines already refuse
65
65
  # (`TypeMismatch`, out of `i64` range either way) for this shape,
@@ -83,7 +83,7 @@ module Hecks
83
83
  # as a clock or a count is capped to a narrower, still-real
84
84
  # edge-case pool below (still exercises the ordinary i32
85
85
  # boundaries, just never a value past f64's own 2**53 exact-
86
- # integer ceiling) — every OTHER Integer-typed field (a money
86
+ # integer ceiling) — every other Integer-typed field (a money
87
87
  # amount, an identity sequence, anything not clock/count-shaped)
88
88
  # still draws from the full `INTEGER_EDGE_CASES` pool above,
89
89
  # unchanged.
@@ -100,12 +100,12 @@ module Hecks
100
100
  CLOCK_OR_COUNT_NAME_PATTERN = /clock|instant|expir|ttl|\bnow\b|timestamp|epoch|count/i
101
101
  # NaN and +/-Infinity — the real find (see `spec/runtime/
102
102
  # numeric_boundary_spec.rb`): `Value::Coercion#check_numeric_fields`
103
- # used to let all three sail through untyped-checked (each really
104
- # is a Float), reaching either `CommandRules::Arithmetic#clamp`
105
- # (raw `ArgumentError`, not a domain refusal) or `JSON.generate`
103
+ # type-checks all three (each really is a Float) before they can
104
+ # reach either `CommandRules::Arithmetic#clamp` (raw
105
+ # `ArgumentError`, not a domain refusal) or `JSON.generate`
106
106
  # (`JSON::GeneratorError`, also not a domain refusal) — both fixed
107
- # at the source now, so these are safe to generate. -0.0 is
108
- # deliberately included too even though it was ALREADY safe
107
+ # at the source, so these are safe to generate. -0.0 is
108
+ # deliberately included too even though it was already safe
109
109
  # (finite, round-trips through JSON as `-0.0` cleanly) — a signed
110
110
  # zero is exactly the kind of boundary a hand-written corpus never
111
111
  # happens to type, and the fuzzer existing to cover it is the
@@ -125,6 +125,18 @@ module Hecks
125
125
  # just named "address"; the VO's own name is where "email" lives. A
126
126
  # combined hint catches both spellings without needing to guess which
127
127
  # level a domain happened to name the thing on.
128
+ #
129
+ # @param attribute [Bluebook::Attribute] the attribute to generate a value for
130
+ # @param aggregate [Bluebook::Aggregate] the aggregate `attribute` belongs to;
131
+ # resolves a same-chapter identity value object
132
+ # @param random [Random] the RNG driving every draw this call makes
133
+ # @param known_ids [Hash{String => Array<String>}] known real ids, keyed by
134
+ # aggregate or entity name; a reference draws from this pool
135
+ # @param context [String, nil] the enclosing value object's own declared name,
136
+ # for name-aware primitive generation; nil at the top level
137
+ # @return [String, Integer, Float, Boolean, Hash] a JSON-safe value in the corpus's
138
+ # own shape: a nested Hash for a value-object-typed attribute, a bare id String
139
+ # for a reference, or a plain primitive otherwise
128
140
  def value_for(attribute, aggregate, random:, known_ids: {}, context: nil)
129
141
  return reference_value(attribute, random: random, known_ids: known_ids) if attribute.reference?
130
142
 
@@ -143,6 +155,20 @@ module Hecks
143
155
  primitive(attribute.type.to_s, random: random, name: "#{context} #{attribute.name}")
144
156
  end
145
157
 
158
+ # Generates a nested value for a whole value object: a random admitted
159
+ # row for a closed set, or a value per declared attribute otherwise —
160
+ # occasionally unwrapped to a bare scalar for a genuinely single-field
161
+ # value object.
162
+ #
163
+ # @param value_object [Bluebook::ValueObject] the value object to generate a value for
164
+ # @param aggregate [Bluebook::Aggregate] the aggregate `value_object` is reached
165
+ # from, forwarded to nested `value_for` calls
166
+ # @param random [Random] the RNG driving every draw this call makes
167
+ # @param known_ids [Hash{String => Array<String>}] known real ids, keyed by
168
+ # aggregate or entity name, forwarded to nested `value_for` calls
169
+ # @return [Hash, Object] `{field name => value, ...}` for each declared attribute
170
+ # or closed-set member; unwrapped to that lone value directly when the value
171
+ # object has exactly one field and the bare-scalar draw hits
146
172
  def object_for(value_object, aggregate, random:, known_ids:)
147
173
  fields =
148
174
  if value_object.closed_set? && !value_object.members.empty?
@@ -157,10 +183,10 @@ module Hecks
157
183
  end
158
184
  end
159
185
 
160
- # `fields.size == 1` — the SAME test `Behaviour::ValueObject#
186
+ # `fields.size == 1` — the same test `Behaviour::ValueObject#
161
187
  # sole_attribute` names: a genuinely single-field value object,
162
188
  # not merely "this particular closed-set member happened to pick
163
- # one field." Unwrapping ONLY here, not in `invalid_member`
189
+ # one field." Unwrapping only here, not in `invalid_member`
164
190
  # above — a deliberately-wrong combination stays a Hash so its
165
191
  # own wrongness is what gets exercised, not a second, unrelated
166
192
  # shape question.
@@ -172,17 +198,31 @@ module Hecks
172
198
  # A combination that (almost certainly) isn't one of the closed set's
173
199
  # admitted rows — deliberately, to exercise the refusal a `one_of`
174
200
  # exists to enforce, not just its happy path.
201
+ #
202
+ # @param value_object [Bluebook::ValueObject] the closed-set value object to
203
+ # generate a non-admitted combination for
204
+ # @param random [Random] the RNG driving every draw this call makes
205
+ # @return [Hash] `{field name => value, ...}` for every declared attribute, each
206
+ # drawn independently rather than sampled from an admitted row
175
207
  def invalid_member(value_object, random:)
176
208
  value_object.attributes.to_h do |field|
177
209
  [field.name.to_s, primitive(field.type.to_s, random: random, name: field.name.to_s)]
178
210
  end
179
211
  end
180
212
 
181
- # THE ID ITSELF. This minted `{"value" => id}` back when a reference was
182
- # stored wrapped ; the payload gate refuses that shape now, so a fuzzer
183
- # still emitting it would have every generated reference refused and
184
- # the SILENT guard would report the fuzzer broken rather than
185
- # the runtime.
213
+ # The ID itself, unwrapped: the payload gate refuses a reference stored
214
+ # wrapped as `{"value" => id}`, so a fuzzer emitting that shape would
215
+ # have every generated reference refused and the silent guard would
216
+ # report the fuzzer broken rather than the runtime.
217
+ #
218
+ # @param attribute [Bluebook::Attribute] the reference-typed attribute to
219
+ # generate a value for
220
+ # @param random [Random] the RNG driving every draw this call makes
221
+ # @param known_ids [Hash{String => Array<String>}] known real ids, keyed by
222
+ # aggregate or entity name; looked up under `attribute.type.target_name`
223
+ # @return [String] a real id drawn from the matching pool most of the time; a
224
+ # fabricated `"missing-..."` id when the pool is empty or the invalid-reference
225
+ # draw hits
186
226
  def reference_value(attribute, random:, known_ids:)
187
227
  pool = known_ids[attribute.type.target_name.to_s] || []
188
228
  return "missing-#{random.bytes(4).unpack1('H*')}" if pool.empty? || random.rand < INVALID_REFERENCE_PROBABILITY
@@ -190,6 +230,16 @@ module Hecks
190
230
  pool.sample(random: random)
191
231
  end
192
232
 
233
+ # Generates a value for one Ruby-primitive-typed attribute.
234
+ #
235
+ # @param type_name [String] the primitive type name: `"String"`, `"Integer"`,
236
+ # `"Float"`, `"TrueClass"`, or `"FalseClass"`
237
+ # @param random [Random] the RNG driving every draw this call makes
238
+ # @param name [String, nil] a name hint (attribute name, optionally
239
+ # context-prefixed) that draws an email-/currency-shaped string or a
240
+ # clock/count-shaped integer
241
+ # @return [String, Integer, Float, Boolean] a value of the type `type_name` names
242
+ # @raise [ArgumentError] if `type_name` names anything else
193
243
  def primitive(type_name, random:, name: nil)
194
244
  case type_name
195
245
  when "String" then string_value(random, name: name)
@@ -200,6 +250,14 @@ module Hecks
200
250
  end
201
251
  end
202
252
 
253
+ # Generates a String value, name-aware for the couple of shapes that
254
+ # need to look a specific way to get past their own invariant.
255
+ #
256
+ # @param random [Random] the RNG driving every draw this call makes
257
+ # @param name [String, nil] a name hint; an `"email"` or `"currency"` match
258
+ # draws a shaped value instead of random words
259
+ # @return [String] an edge-case string, an email address, a currency code, or
260
+ # 1-3 random words joined by spaces
203
261
  def string_value(random, name: nil)
204
262
  return STRING_EDGE_CASES.sample(random: random) if random.rand < EDGE_CASE_PROBABILITY
205
263
  return email_value(random) if name&.match?(/email/i)
@@ -208,15 +266,26 @@ module Hecks
208
266
  Array.new(random.rand(1..3)) { WORDS.sample(random: random) }.join(" ")
209
267
  end
210
268
 
269
+ # Generates a fabricated, syntactically valid email address.
270
+ #
271
+ # @param random [Random] the RNG driving every draw this call makes
272
+ # @return [String] a `"word@word.example"` address
211
273
  def email_value(random)
212
274
  "#{WORDS.sample(random: random)}@#{WORDS.sample(random: random)}.example"
213
275
  end
214
276
 
277
+ # Generates an Integer value, skewed positive.
278
+ #
215
279
  # Skewed positive : `cents.positive?`/`!cents.negative?`-style
216
280
  # invariants are common across this codebase's example domains, and a
217
281
  # sequence that can never get past one never reaches the state a deeper
218
282
  # bug would need. Zero and negative are still real, reachable outcomes —
219
283
  # via the edge-case pool, deliberately, not by starving them entirely.
284
+ #
285
+ # @param random [Random] the RNG driving every draw this call makes
286
+ # @param name [String, nil] a name hint; a clock/count-shaped match narrows
287
+ # the edge-case pool to `SAFE_INTEGER_EDGE_CASES`
288
+ # @return [Integer] an edge-case integer sometimes, else a random count in 1..1000
220
289
  def integer_value(random, name: nil)
221
290
  if random.rand < EDGE_CASE_PROBABILITY
222
291
  pool = clock_or_count_shaped?(name) ? SAFE_INTEGER_EDGE_CASES : INTEGER_EDGE_CASES
@@ -226,24 +295,43 @@ module Hecks
226
295
  random.rand(1..1000)
227
296
  end
228
297
 
298
+ # Reports whether a field name reads as a clock reading or a
299
+ # policy-capped count, per `CLOCK_OR_COUNT_NAME_PATTERN`'s own comment.
300
+ #
301
+ # @param name [String, nil] the name to test
302
+ # @return [Boolean] true if `name` matches the clock/count name pattern
229
303
  def clock_or_count_shaped?(name)
230
304
  name.to_s.match?(CLOCK_OR_COUNT_NAME_PATTERN)
231
305
  end
232
306
 
307
+ # Generates a Float value.
308
+ #
309
+ # @param random [Random] the RNG driving every draw this call makes
310
+ # @return [Float] an edge-case float sometimes, else a random value in
311
+ # 0.01..1000.0, rounded to 2 decimal places
233
312
  def float_value(random)
234
313
  return FLOAT_EDGE_CASES.sample(random: random) if random.rand < EDGE_CASE_PROBABILITY
235
314
 
236
315
  random.rand(0.01..1000.0).round(2)
237
316
  end
238
317
 
239
- # The bare scalar a generated IDENTITY value stands for, for recording into
318
+ # The bare scalar a generated identity value stands for, for recording into
240
319
  # `known_ids`. An identity is declared as a value object, so this opens one ;
241
320
  # a reference pointing at this record is already that scalar and needs no
242
- # opening at all. The two used to be the same reading and are not any more.
321
+ # opening at all — the two are different readings, not interchangeable.
322
+ #
323
+ # @param identity_value [Hash, Object] a generated identity value, as `value_for`
324
+ # produced it — a Hash for a multi-field value object, or the bare scalar already
325
+ # for a single-field one
326
+ # @return [String] the identity's bare scalar, stringified
243
327
  def scalar_of(identity_value)
244
328
  identity_value.is_a?(Hash) ? identity_value.values.first.to_s : identity_value.to_s
245
329
  end
246
330
 
331
+ # Generates a fabricated id unrelated to anything `known_ids` tracks.
332
+ #
333
+ # @param random [Random] the RNG driving every draw this call makes
334
+ # @return [String] a `"gen-"`-prefixed id with 8 random hex characters
247
335
  def random_id(random)
248
336
  "gen-#{random.bytes(4).unpack1('H*')}"
249
337
  end
data/lib/hecks/fuzzing.rb CHANGED
@@ -8,11 +8,16 @@ end
8
8
  require_relative "fuzzing/value_generator"
9
9
  require_relative "fuzzing/invalid_value_generator"
10
10
  require_relative "fuzzing/sequence_generator"
11
+ require_relative "fuzzing/nondeterministic"
11
12
  require_relative "fuzzing/replay"
13
+ require_relative "fuzzing/shrinker"
12
14
  require_relative "fuzzing/properties"
15
+ require_relative "fuzzing/qa_settings"
13
16
  require_relative "fuzzing/rotation_priority"
14
17
  require_relative "fuzzing/sweep_depth"
15
18
  require_relative "fuzzing/persistence_parity"
16
19
  require_relative "fuzzing/form_census"
17
20
  require_relative "fuzzing/target_capabilities"
21
+ require_relative "fuzzing/rust_gap_manifest"
18
22
  require_relative "fuzzing/structural_skips"
23
+ require_relative "fuzzing/coverage_campaign"