hecks 0.2.0 → 0.3.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 (408) hide show
  1. checksums.yaml +5 -5
  2. data/lib/hecks/adapters/driven/claude_code.adapter +3 -0
  3. data/lib/hecks/adapters/driven/claude_code.rb +127 -0
  4. data/lib/hecks/adapters/driven/d1.adapter +12 -0
  5. data/lib/hecks/adapters/driven/d1.rb +403 -0
  6. data/lib/hecks/adapters/driven/folder.adapter +3 -0
  7. data/lib/hecks/adapters/driven/folder.rb +199 -0
  8. data/lib/hecks/adapters/driven/google_authentication.adapter +3 -0
  9. data/lib/hecks/adapters/driven/google_authentication.rb +100 -0
  10. data/lib/hecks/adapters/driven/governance_authorization.adapter +3 -0
  11. data/lib/hecks/adapters/driven/governance_authorization.rb +62 -0
  12. data/lib/hecks/adapters/driven/heki/journal.rb +51 -0
  13. data/lib/hecks/adapters/driven/heki/saga_store.rb +0 -0
  14. data/lib/hecks/adapters/driven/heki/snapshot.rb +38 -0
  15. data/lib/hecks/adapters/driven/heki.adapter +4 -0
  16. data/lib/hecks/adapters/driven/heki.rb +148 -0
  17. data/lib/hecks/adapters/driven/identity_registry.adapter +3 -0
  18. data/lib/hecks/adapters/driven/identity_registry.rb +26 -0
  19. data/lib/hecks/adapters/driven/in_memory_ordering.rb +51 -0
  20. data/lib/hecks/adapters/driven/lambda/client.rb +63 -0
  21. data/lib/hecks/adapters/driven/lambda.adapter +4 -0
  22. data/lib/hecks/adapters/driven/lambda.rb +131 -0
  23. data/lib/hecks/adapters/driven/memory.adapter +3 -0
  24. data/lib/hecks/adapters/driven/memory.rb +103 -0
  25. data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +3 -0
  26. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +28 -0
  27. data/lib/hecks/adapters/driven/postgres/codec.rb +80 -0
  28. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +168 -0
  29. data/lib/hecks/adapters/driven/postgres.adapter +5 -0
  30. data/lib/hecks/adapters/driven/postgres.rb +391 -0
  31. data/lib/hecks/adapters/driven/postgres_era/lineage/era_store.rb +171 -0
  32. data/lib/hecks/adapters/driven/postgres_era/lineage/field_cache.rb +190 -0
  33. data/lib/hecks/adapters/driven/postgres_era/lineage/head_compiler.rb +429 -0
  34. data/lib/hecks/adapters/driven/postgres_era/lineage/mint_transaction.rb +166 -0
  35. data/lib/hecks/adapters/driven/postgres_era/lineage/provisioning.rb +286 -0
  36. data/lib/hecks/adapters/driven/postgres_era/lineage/resumable_backfill.rb +168 -0
  37. data/lib/hecks/adapters/driven/postgres_era/lineage/tail_merge.rb +163 -0
  38. data/lib/hecks/adapters/driven/postgres_era/lineage/transform_installer.rb +114 -0
  39. data/lib/hecks/adapters/driven/postgres_era/lineage.rb +137 -0
  40. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/coverage_check.rb +89 -0
  41. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/era_resolver.rb +82 -0
  42. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/merge_coordinator.rb +43 -0
  43. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/minter.rb +132 -0
  44. data/lib/hecks/adapters/driven/postgres_era/lineage_manager.rb +74 -0
  45. data/lib/hecks/adapters/driven/postgres_era.adapter +6 -0
  46. data/lib/hecks/adapters/driven/postgres_era.rb +671 -0
  47. data/lib/hecks/adapters/driven/prism.adapter +3 -0
  48. data/lib/hecks/adapters/driven/prism.rb +80 -0
  49. data/lib/hecks/adapters/driven/secure_random_identity.adapter +3 -0
  50. data/lib/hecks/adapters/driven/secure_random_identity.rb +14 -0
  51. data/lib/hecks/adapters/driven/sql_query_builder.rb +221 -0
  52. data/lib/hecks/adapters/driven/sqlite/codec.rb +63 -0
  53. data/lib/hecks/adapters/driven/sqlite/projection.rb +105 -0
  54. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +180 -0
  55. data/lib/hecks/adapters/driven/sqlite.adapter +9 -0
  56. data/lib/hecks/adapters/driven/sqlite.rb +285 -0
  57. data/lib/hecks/adapters/driven/system_clock.adapter +3 -0
  58. data/lib/hecks/adapters/driven/system_clock.rb +14 -0
  59. data/lib/hecks/adapters/driven.rb +36 -0
  60. data/lib/hecks/adapters.rb +6 -0
  61. data/lib/hecks/behaviors/dsl.rb +96 -0
  62. data/lib/hecks/behaviors/expectations.rb +280 -0
  63. data/lib/hecks/behaviors/ir.rb +31 -0
  64. data/lib/hecks/behaviors/rspec.rb +42 -0
  65. data/lib/hecks/behaviors/runner.rb +96 -0
  66. data/lib/hecks/behaviors.rb +25 -0
  67. data/lib/hecks/bluebook/aggregate.rb +108 -0
  68. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +134 -0
  69. data/lib/hecks/bluebook/assembly/build.rb +48 -0
  70. data/lib/hecks/bluebook/assembly/contract.rb +112 -0
  71. data/lib/hecks/bluebook/assembly/contracts.rb +409 -0
  72. data/lib/hecks/bluebook/assembly/marks.rb +227 -0
  73. data/lib/hecks/bluebook/assembly/specializer.rb +70 -0
  74. data/lib/hecks/bluebook/assembly.rb +78 -0
  75. data/lib/hecks/bluebook/attribute.rb +96 -0
  76. data/lib/hecks/bluebook/behaviour/aggregate.rb +83 -0
  77. data/lib/hecks/bluebook/behaviour/attribute.rb +27 -0
  78. data/lib/hecks/bluebook/behaviour/chapter.rb +72 -0
  79. data/lib/hecks/bluebook/behaviour/command.rb +116 -0
  80. data/lib/hecks/bluebook/behaviour/domain_port.rb +25 -0
  81. data/lib/hecks/bluebook/behaviour/entity.rb +59 -0
  82. data/lib/hecks/bluebook/behaviour/hexagon.rb +50 -0
  83. data/lib/hecks/bluebook/behaviour/lifecycle.rb +51 -0
  84. data/lib/hecks/bluebook/behaviour/policy.rb +52 -0
  85. data/lib/hecks/bluebook/behaviour/process_manager.rb +38 -0
  86. data/lib/hecks/bluebook/behaviour/query.rb +10 -0
  87. data/lib/hecks/bluebook/behaviour/read_model.rb +29 -0
  88. data/lib/hecks/bluebook/behaviour/traits.rb +81 -0
  89. data/lib/hecks/bluebook/behaviour/value_object.rb +33 -0
  90. data/lib/hecks/bluebook/chapter.rb +68 -0
  91. data/lib/hecks/bluebook/command.rb +124 -0
  92. data/lib/hecks/bluebook/domain_port.rb +102 -0
  93. data/lib/hecks/bluebook/dsl/adapter_builder.rb +34 -0
  94. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +906 -0
  95. data/lib/hecks/bluebook/dsl/attribute_collector.rb +327 -0
  96. data/lib/hecks/bluebook/dsl/binding_proxy.rb +71 -0
  97. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +1018 -0
  98. data/lib/hecks/bluebook/dsl/command_builder.rb +626 -0
  99. data/lib/hecks/bluebook/dsl/const_shim.rb +81 -0
  100. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +79 -0
  101. data/lib/hecks/bluebook/dsl/entity_builder.rb +305 -0
  102. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +366 -0
  103. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +163 -0
  104. data/lib/hecks/bluebook/dsl/identity_declaration.rb +191 -0
  105. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +44 -0
  106. data/lib/hecks/bluebook/dsl/malformed.rb +7 -0
  107. data/lib/hecks/bluebook/dsl/policy_builder.rb +120 -0
  108. data/lib/hecks/bluebook/dsl/port_builder.rb +30 -0
  109. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +142 -0
  110. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +207 -0
  111. data/lib/hecks/bluebook/dsl/query_builder.rb +113 -0
  112. data/lib/hecks/bluebook/dsl/read_model_builder.rb +233 -0
  113. data/lib/hecks/bluebook/dsl/rule_reference.rb +172 -0
  114. data/lib/hecks/bluebook/dsl/translation_builder.rb +243 -0
  115. data/lib/hecks/bluebook/dsl/value_object_builder.rb +178 -0
  116. data/lib/hecks/bluebook/dsl/word_gate.rb +221 -0
  117. data/lib/hecks/bluebook/dsl/world_builder.rb +77 -0
  118. data/lib/hecks/bluebook/dsl.rb +45 -0
  119. data/lib/hecks/bluebook/entity.rb +103 -0
  120. data/lib/hecks/bluebook/expression/canonical_form.rb +55 -0
  121. data/lib/hecks/bluebook/expression/evaluator.rb +261 -0
  122. data/lib/hecks/bluebook/expression/projection.json +170 -0
  123. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +233 -0
  124. data/lib/hecks/bluebook/expression/resolver.rb +630 -0
  125. data/lib/hecks/bluebook/expression.rb +13 -0
  126. data/lib/hecks/bluebook/hexagon.rb +60 -0
  127. data/lib/hecks/bluebook/lifecycle.rb +42 -0
  128. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +54 -0
  129. data/lib/hecks/bluebook/meta_validator/judge.rb +559 -0
  130. data/lib/hecks/bluebook/meta_validator/plan.rb +332 -0
  131. data/lib/hecks/bluebook/meta_validator/port_judge.rb +47 -0
  132. data/lib/hecks/bluebook/meta_validator/readings.rb +350 -0
  133. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +312 -0
  134. data/lib/hecks/bluebook/meta_validator/shapes.rb +246 -0
  135. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +255 -0
  136. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +138 -0
  137. data/lib/hecks/bluebook/meta_validator/world_judge.rb +78 -0
  138. data/lib/hecks/bluebook/meta_validator.rb +452 -0
  139. data/lib/hecks/bluebook/model_check.rb +318 -0
  140. data/lib/hecks/bluebook/pattern_subset.rb +120 -0
  141. data/lib/hecks/bluebook/policy.rb +41 -0
  142. data/lib/hecks/bluebook/process_manager.rb +86 -0
  143. data/lib/hecks/bluebook/project_discovery.rb +30 -0
  144. data/lib/hecks/bluebook/project_loader.rb +40 -0
  145. data/lib/hecks/bluebook/project_register.rb +82 -0
  146. data/lib/hecks/bluebook/query.rb +61 -0
  147. data/lib/hecks/bluebook/read_model.rb +109 -0
  148. data/lib/hecks/bluebook/reference.rb +74 -0
  149. data/lib/hecks/bluebook/smoke_test.rb +166 -0
  150. data/lib/hecks/bluebook/synthesizer.rb +95 -0
  151. data/lib/hecks/bluebook/translation.rb +92 -0
  152. data/lib/hecks/bluebook/value_object.rb +58 -0
  153. data/lib/hecks/bluebook.rb +74 -0
  154. data/lib/hecks/codemod.rb +342 -0
  155. data/lib/hecks/construct.rb +71 -0
  156. data/lib/hecks/deploy/bluebook/deploy.bluebook +219 -0
  157. data/lib/hecks/deploy/bluebook/deploy.hecksagon +4 -0
  158. data/lib/hecks/deploy/oidc.json +18 -0
  159. data/lib/hecks/doc/reference.rb +389 -0
  160. data/lib/hecks/embryonaut_bluebook.rb +75 -0
  161. data/lib/hecks/facade/cli_door.rb +116 -0
  162. data/lib/hecks/facade/cli_runner.rb +190 -0
  163. data/lib/hecks/facade/command_request.rb +105 -0
  164. data/lib/hecks/facade/handle.rb +173 -0
  165. data/lib/hecks/facade/json_door.rb +154 -0
  166. data/lib/hecks/facade/surface/aggregate_door.rb +185 -0
  167. data/lib/hecks/facade/surface/chapter.rb +107 -0
  168. data/lib/hecks/facade/surface.rb +48 -0
  169. data/lib/hecks/facade.rb +44 -0
  170. data/lib/hecks/forms/app.rb +300 -0
  171. data/lib/hecks/forms/command_form_renderer.rb +113 -0
  172. data/lib/hecks/forms/examples/banking_console.bluebook +3 -0
  173. data/lib/hecks/forms/field_renderer.rb +170 -0
  174. data/lib/hecks/forms/field_shape.rb +232 -0
  175. data/lib/hecks/forms/html.rb +53 -0
  176. data/lib/hecks/forms/index_renderer.rb +35 -0
  177. data/lib/hecks/forms/page.rb +157 -0
  178. data/lib/hecks/forms/params.rb +132 -0
  179. data/lib/hecks/forms/query_form_renderer.rb +114 -0
  180. data/lib/hecks/forms/record_renderer.rb +115 -0
  181. data/lib/hecks/forms/record_table.rb +63 -0
  182. data/lib/hecks/forms/reference_options.rb +30 -0
  183. data/lib/hecks/forms/value_object_shape.rb +46 -0
  184. data/lib/hecks/forms.rb +54 -0
  185. data/lib/hecks/fqn.rb +94 -0
  186. data/lib/hecks/framework/bluebook/compliance.bluebook +1 -0
  187. data/lib/hecks/framework/bluebook/console_settings.bluebook +489 -0
  188. data/lib/hecks/framework/bluebook/framework.hecksagon +32 -0
  189. data/lib/hecks/framework/bluebook/governance.bluebook +130 -0
  190. data/lib/hecks/framework/bluebook/identity.bluebook +90 -0
  191. data/lib/hecks/framework/oidc.json +39 -0
  192. data/lib/hecks/framework.rb +90 -0
  193. data/lib/hecks/freezer.rb +67 -0
  194. data/lib/hecks/fuzzing/invalid_value_generator.rb +86 -0
  195. data/lib/hecks/fuzzing/isolated_boot.rb +92 -0
  196. data/lib/hecks/fuzzing/properties.rb +1146 -0
  197. data/lib/hecks/fuzzing/replay.rb +635 -0
  198. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +93 -0
  199. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +54 -0
  200. data/lib/hecks/fuzzing/sequence_generator/picker.rb +96 -0
  201. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +185 -0
  202. data/lib/hecks/fuzzing/sequence_generator.rb +130 -0
  203. data/lib/hecks/fuzzing/value_generator.rb +177 -0
  204. data/lib/hecks/fuzzing.rb +12 -0
  205. data/lib/hecks/grammar/evolve.rb +291 -0
  206. data/lib/hecks/grammar/expression.bluebook +417 -0
  207. data/lib/hecks/grammar/expression_operators.json +1152 -0
  208. data/lib/hecks/grammar/grammar.hecksagon +20 -0
  209. data/lib/hecks/grammar/oidc.json +69 -0
  210. data/lib/hecks/grammar/translation.bluebook +203 -0
  211. data/lib/hecks/grammar.rb +178 -0
  212. data/lib/hecks/ir.rb +126 -0
  213. data/lib/hecks/language/adapter.bluebook +116 -0
  214. data/lib/hecks/language/bluebook/aggregate.bluebook +581 -0
  215. data/lib/hecks/language/bluebook/attaches/paging.bluebook +76 -0
  216. data/lib/hecks/language/bluebook/bluebook.bluebook +256 -0
  217. data/lib/hecks/language/bluebook/bluebook.hecksagon +24 -0
  218. data/lib/hecks/language/bluebook/command.bluebook +437 -0
  219. data/lib/hecks/language/bluebook/entity.bluebook +316 -0
  220. data/lib/hecks/language/bluebook/policy.bluebook +172 -0
  221. data/lib/hecks/language/bluebook/process_manager.bluebook +264 -0
  222. data/lib/hecks/language/bluebook/projection.bluebook +267 -0
  223. data/lib/hecks/language/bluebook/query.bluebook +245 -0
  224. data/lib/hecks/language/bluebook/shape.bluebook +292 -0
  225. data/lib/hecks/language/bluebook/syntax.bluebook +447 -0
  226. data/lib/hecks/language/bluebook/vocabulary.bluebook +364 -0
  227. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +51 -0
  228. data/lib/hecks/language/hecksagon/domain_port.bluebook +76 -0
  229. data/lib/hecks/language/hecksagon/hecksagon.bluebook +131 -0
  230. data/lib/hecks/language/hecksagon/port_operation.bluebook +102 -0
  231. data/lib/hecks/language/oidc.json +313 -0
  232. data/lib/hecks/language/port.bluebook +92 -0
  233. data/lib/hecks/language/translation/translation.bluebook +110 -0
  234. data/lib/hecks/language/translation/translation_aggregate.bluebook +267 -0
  235. data/lib/hecks/language/world/wiring.bluebook +62 -0
  236. data/lib/hecks/language/world/world.bluebook +84 -0
  237. data/lib/hecks/literal.rb +125 -0
  238. data/lib/hecks/naming.rb +121 -0
  239. data/lib/hecks/ports/access_control.port +4 -0
  240. data/lib/hecks/ports/access_control.rb +62 -0
  241. data/lib/hecks/ports/agent/answers.rb +104 -0
  242. data/lib/hecks/ports/agent.port +4 -0
  243. data/lib/hecks/ports/agent.rb +167 -0
  244. data/lib/hecks/ports/authentication.port +4 -0
  245. data/lib/hecks/ports/authentication.rb +50 -0
  246. data/lib/hecks/ports/authorization.port +4 -0
  247. data/lib/hecks/ports/authorization.rb +59 -0
  248. data/lib/hecks/ports/clock.port +4 -0
  249. data/lib/hecks/ports/clock.rb +62 -0
  250. data/lib/hecks/ports/extraction.port +4 -0
  251. data/lib/hecks/ports/extraction.rb +37 -0
  252. data/lib/hecks/ports/identity_assignment.port +4 -0
  253. data/lib/hecks/ports/identity_assignment.rb +45 -0
  254. data/lib/hecks/ports/identity_generation.port +4 -0
  255. data/lib/hecks/ports/identity_generation.rb +49 -0
  256. data/lib/hecks/ports/identity_resolution.port +4 -0
  257. data/lib/hecks/ports/identity_resolution.rb +40 -0
  258. data/lib/hecks/ports/loading.port +4 -0
  259. data/lib/hecks/ports/loading.rb +13 -0
  260. data/lib/hecks/ports/persistence/append_only.rb +102 -0
  261. data/lib/hecks/ports/persistence/binding_policy.rb +56 -0
  262. data/lib/hecks/ports/persistence/execution.rb +18 -0
  263. data/lib/hecks/ports/persistence/lineage.rb +277 -0
  264. data/lib/hecks/ports/persistence/null_saga_store.rb +25 -0
  265. data/lib/hecks/ports/persistence/remote_runtime.rb +42 -0
  266. data/lib/hecks/ports/persistence/repository_factory.rb +30 -0
  267. data/lib/hecks/ports/persistence.port +4 -0
  268. data/lib/hecks/ports/persistence.rb +39 -0
  269. data/lib/hecks/ports/projection.port +4 -0
  270. data/lib/hecks/ports/projection.rb +76 -0
  271. data/lib/hecks/ports/query/in_memory.rb +60 -0
  272. data/lib/hecks/ports/query/ordering.rb +41 -0
  273. data/lib/hecks/ports/query.rb +36 -0
  274. data/lib/hecks/ports.rb +27 -0
  275. data/lib/hecks/projections/diagrams.rb +413 -0
  276. data/lib/hecks/projections/ir.rb +18 -0
  277. data/lib/hecks/projections/model/deviations.rb +98 -0
  278. data/lib/hecks/projections/model.rb +145 -0
  279. data/lib/hecks/projections/oidc.rb +110 -0
  280. data/lib/hecks/projections/parser_table.rb +159 -0
  281. data/lib/hecks/projections/reference.rb +38 -0
  282. data/lib/hecks/projections/shape.rb +31 -0
  283. data/lib/hecks/projections/statements.rb +110 -0
  284. data/lib/hecks/projections/vocabulary.rb +100 -0
  285. data/lib/hecks/projections.rb +32 -0
  286. data/lib/hecks/projector/cli_projector.rb +437 -0
  287. data/lib/hecks/projector/docs_projector.rb +321 -0
  288. data/lib/hecks/projector/exporter.rb +141 -0
  289. data/lib/hecks/projector/ir_projector.rb +18 -0
  290. data/lib/hecks/projector/narrate_projector.rb +243 -0
  291. data/lib/hecks/projector/target.rb +97 -0
  292. data/lib/hecks/projector.rb +186 -0
  293. data/lib/hecks/query_ir.rb +392 -0
  294. data/lib/hecks/query_specification/common/authorization_spec.rb +9 -0
  295. data/lib/hecks/query_specification/common/comparators.rb +25 -0
  296. data/lib/hecks/query_specification/common/comparison.rb +174 -0
  297. data/lib/hecks/query_specification/common/cursor_spec.rb +9 -0
  298. data/lib/hecks/query_specification/common/dsl.rb +58 -0
  299. data/lib/hecks/query_specification/common/inspection_spec.rb +9 -0
  300. data/lib/hecks/query_specification/common/limit_spec.rb +9 -0
  301. data/lib/hecks/query_specification/common/null_policy.rb +80 -0
  302. data/lib/hecks/query_specification/common/null_semantics.rb +11 -0
  303. data/lib/hecks/query_specification/common/offset_spec.rb +9 -0
  304. data/lib/hecks/query_specification/common/options.rb +32 -0
  305. data/lib/hecks/query_specification/common/order_by.rb +9 -0
  306. data/lib/hecks/query_specification/common/specification.rb +10 -0
  307. data/lib/hecks/query_specification/common/where_clause.rb +9 -0
  308. data/lib/hecks/query_specification/field_path.rb +89 -0
  309. data/lib/hecks/query_specification/hop_path.rb +130 -0
  310. data/lib/hecks/query_specification/read_model/specification.rb +18 -0
  311. data/lib/hecks/query_specification.rb +14 -0
  312. data/lib/hecks/rendering.rb +48 -0
  313. data/lib/hecks/router/namespace_installer.rb +157 -0
  314. data/lib/hecks/router.rb +70 -0
  315. data/lib/hecks/runtime/caller.rb +50 -0
  316. data/lib/hecks/runtime/capability_graph.rb +44 -0
  317. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +124 -0
  318. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +248 -0
  319. data/lib/hecks/runtime/command_interpreter.rb +413 -0
  320. data/lib/hecks/runtime/command_rules/admissibility.rb +307 -0
  321. data/lib/hecks/runtime/command_rules/arithmetic.rb +254 -0
  322. data/lib/hecks/runtime/command_rules/authorization.rb +64 -0
  323. data/lib/hecks/runtime/command_rules/emission.rb +34 -0
  324. data/lib/hecks/runtime/command_rules/references.rb +181 -0
  325. data/lib/hecks/runtime/command_rules.rb +28 -0
  326. data/lib/hecks/runtime/dependency_planning.rb +231 -0
  327. data/lib/hecks/runtime/dispatcher.rb +286 -0
  328. data/lib/hecks/runtime/entity_element.rb +253 -0
  329. data/lib/hecks/runtime/entity_interpreter.rb +228 -0
  330. data/lib/hecks/runtime/era_check.rb +136 -0
  331. data/lib/hecks/runtime/era_guard/shape_diff.rb +128 -0
  332. data/lib/hecks/runtime/era_guard.rb +193 -0
  333. data/lib/hecks/runtime/era_tamper.rb +61 -0
  334. data/lib/hecks/runtime/errors.rb +100 -0
  335. data/lib/hecks/runtime/event.rb +51 -0
  336. data/lib/hecks/runtime/identity.rb +128 -0
  337. data/lib/hecks/runtime/instance.rb +120 -0
  338. data/lib/hecks/runtime/interpreting.rb +69 -0
  339. data/lib/hecks/runtime/loader.rb +143 -0
  340. data/lib/hecks/runtime/policy_interpreter.rb +366 -0
  341. data/lib/hecks/runtime/port_operation_interpreter.rb +210 -0
  342. data/lib/hecks/runtime/query_interpreter.rb +254 -0
  343. data/lib/hecks/runtime/reaction_invocation.rb +245 -0
  344. data/lib/hecks/runtime/read_model_interpreter.rb +266 -0
  345. data/lib/hecks/runtime/rebuild_sweep.rb +74 -0
  346. data/lib/hecks/runtime/reference_hop.rb +99 -0
  347. data/lib/hecks/runtime/refusal_wording.rb +124 -0
  348. data/lib/hecks/runtime/registry/saga_persistence.rb +70 -0
  349. data/lib/hecks/runtime/registry/verification.rb +158 -0
  350. data/lib/hecks/runtime/registry.rb +218 -0
  351. data/lib/hecks/runtime/remote_dispatcher.rb +143 -0
  352. data/lib/hecks/runtime/routing.rb +96 -0
  353. data/lib/hecks/runtime/saga_interpreter/correlation.rb +97 -0
  354. data/lib/hecks/runtime/saga_interpreter.rb +301 -0
  355. data/lib/hecks/runtime/storage_shape.rb +120 -0
  356. data/lib/hecks/runtime/tenant_check.rb +84 -0
  357. data/lib/hecks/runtime/tenant_scope.rb +55 -0
  358. data/lib/hecks/runtime/value/admission.rb +112 -0
  359. data/lib/hecks/runtime/value/coercion.rb +500 -0
  360. data/lib/hecks/runtime/value/invariant_violation.rb +5 -0
  361. data/lib/hecks/runtime/value.rb +125 -0
  362. data/lib/hecks/runtime.rb +95 -0
  363. data/lib/hecks/translation/audit/approval_digest.rb +31 -0
  364. data/lib/hecks/translation/audit/layer_one.rb +37 -0
  365. data/lib/hecks/translation/audit/layer_two.rb +77 -0
  366. data/lib/hecks/translation/audit/unfed_report.rb +42 -0
  367. data/lib/hecks/translation/audit.rb +70 -0
  368. data/lib/hecks/translation/reattest.rb +72 -0
  369. data/lib/hecks/translation/rule_compiler.rb +120 -0
  370. data/lib/hecks/translation/scaffold/differ.rb +183 -0
  371. data/lib/hecks/translation/scaffold/renderer.rb +41 -0
  372. data/lib/hecks/translation/scaffold/writer.rb +28 -0
  373. data/lib/hecks/translation/scaffold.rb +30 -0
  374. data/lib/hecks/translation.rb +11 -0
  375. data/lib/hecks/version.rb +16 -0
  376. data/lib/hecks/vocabulary.rb +213 -0
  377. data/lib/hecks.rb +118 -6
  378. metadata +401 -222
  379. data/bin/hecks +0 -7
  380. data/bin/hecks-package +0 -65
  381. data/bin/hecks_console +0 -12
  382. data/bin/hecks_serverless +0 -6
  383. data/lib/cli/build.rb +0 -14
  384. data/lib/cli/command_runner.rb +0 -28
  385. data/lib/cli/console.rb +0 -10
  386. data/lib/cli/generate.rb +0 -37
  387. data/lib/cli/hecks-cli.rb +0 -27
  388. data/lib/cli/test.rb +0 -57
  389. data/lib/console/commands.rb +0 -8
  390. data/lib/console/hecks-console.rb +0 -1
  391. data/lib/packager/README.md +0 -0
  392. data/lib/packager/app_runner.rb +0 -21
  393. data/lib/packager/args.rb +0 -26
  394. data/lib/packager/compatibility/fixnum.rb +0 -6
  395. data/lib/packager/hecks.rb +0 -39
  396. data/lib/packager/query_runner.rb +0 -21
  397. data/lib/packager/resources/Dockerfile +0 -11
  398. data/lib/packager/resources/app_binary +0 -7
  399. data/lib/packager/resources/bundle_config +0 -3
  400. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz +0 -0
  401. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-osx.tar.gz +0 -0
  402. data/lib/serverless/Domain +0 -32
  403. data/lib/serverless/cli.rb +0 -75
  404. data/lib/serverless/resources/command_name.js +0 -5
  405. data/lib/serverless/resources/environment.js +0 -7
  406. data/lib/serverless/resources/handler.js.tt +0 -28
  407. data/lib/serverless/resources/run_binary.js +0 -22
  408. data/lib/serverless/resources/serverless.yml +0 -20
@@ -0,0 +1,93 @@
1
+ module Hecks
2
+ module Fuzzing
3
+ class SequenceGenerator
4
+ # Everything the booted domain offers, indexed for the picker:
5
+ # creating vs instance commands, entity commands, queries, and the
6
+ # populators that predict where an appended element lands.
7
+ module Catalog
8
+ private
9
+
10
+ def build_catalog(runtime)
11
+ creating, instance, entity_commands, queries, entity_queries = [], [], [], [], []
12
+ read_models = []
13
+
14
+ runtime.registry.bluebooks.each do |domain_name, bluebook|
15
+ bluebook.aggregates.each do |aggregate|
16
+ aggregate.commands.each do |command|
17
+ entry = { verb: "#{domain_name}::#{aggregate.hecks_name}.#{command.hecks_name}",
18
+ command: command, aggregate: aggregate }
19
+ (command.creates? ? creating : instance) << entry
20
+ end
21
+ aggregate.queries.each do |query|
22
+ queries << { verb: "#{domain_name}::#{aggregate.hecks_name}.#{query.name}",
23
+ query: query, aggregate: aggregate }
24
+ end
25
+ aggregate.entities.each do |entity|
26
+ entity.commands.each do |command|
27
+ entity_commands << { verb: "#{domain_name}::#{aggregate.hecks_name}.#{entity.hecks_name}.#{command.hecks_name}",
28
+ command: command, aggregate: aggregate, entity: entity }
29
+ end
30
+ entity.queries.each do |query|
31
+ entity_queries << { verb: "#{domain_name}::#{aggregate.hecks_name}.#{entity.hecks_name}.#{query.name}",
32
+ query: query, aggregate: aggregate, entity: entity }
33
+ end
34
+ end
35
+ end
36
+
37
+ # THE BARE DOMAIN FORM — "Domain.report_name", no "::" — the
38
+ # SAME shape `Dispatcher#query` itself branches on to route a
39
+ # read-model ask apart from an aggregate query. A report was
40
+ # never in this catalog at all before: `aggregation_matches_
41
+ # recompute` (count/median) has only ever been exercised by
42
+ # hand-built specs, never a single real generated sequence,
43
+ # because nothing here ever asked one. `model:`, not
44
+ # `aggregate:` — a rootless report has no aggregate of its
45
+ # own to be eligible against (see picker.rb's own use of
46
+ # `model.reference_target`).
47
+ bluebook.read_models.each do |model|
48
+ read_models << { verb: "#{domain_name}.#{model.query_name}", model: model }
49
+ end
50
+ end
51
+
52
+ { creating: creating, instance: instance, entity_commands: entity_commands,
53
+ queries: queries, entity_queries: entity_queries, read_models: read_models,
54
+ populators: populators(runtime),
55
+ # Which aggregates this corpus can actually make one of — the ones
56
+ # `satisfiable?` is entitled to wait for.
57
+ creatable: creating.map { |entry| entry[:aggregate].hecks_name }.to_set }
58
+ end
59
+
60
+ # Which command, on which aggregate, appends to which entity list — so a
61
+ # successful dispatch can predict the identity the element it just added
62
+ # landed on. Entity#identified_by is filled by `Array(current).size + 1`
63
+ # (CommandInterpreter#entity_element) when the append's own field
64
+ # mapping doesn't already assign it — the common case, predicted here.
65
+ # A domain whose append explicitly assigns identity through a mapped
66
+ # argument is covered too, without guessing: whatever value THIS
67
+ # generator supplied for that argument at dispatch time IS the
68
+ # identity, and gets recorded directly (see `record_outcome`).
69
+ def populators(runtime)
70
+ runtime.registry.bluebooks.each_value.flat_map do |bluebook|
71
+ bluebook.aggregates.flat_map do |aggregate|
72
+ aggregate.commands.filter_map do |command|
73
+ append = command.mutations.find { |mutation| mutation.op == :append }
74
+ next unless append
75
+
76
+ list_attribute = aggregate.attribute(append.target)
77
+ next unless list_attribute&.list?
78
+
79
+ entity = aggregate.entities.find { |candidate| candidate.hecks_name == list_attribute.type.to_s }
80
+ next unless entity
81
+
82
+ identity_field = entity.identified_by
83
+ mapped = identity_field && append.source[identity_field]
84
+ { command: command, aggregate: aggregate, entity: entity,
85
+ identity_field: identity_field, identity_argument: mapped.is_a?(Symbol) ? mapped : nil }
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,54 @@
1
+ require_relative "../value_generator"
2
+
3
+ module Hecks
4
+ module Fuzzing
5
+ class SequenceGenerator
6
+ # What a successful step created, and how a later step draws on it —
7
+ # the state tracking that makes multi-step sequences reach further
8
+ # than single calls ever did.
9
+ module OutcomeTracker
10
+ private
11
+
12
+ def record_outcome(catalog, entry, args)
13
+ aggregate = entry[:aggregate]
14
+ parent_key = (aggregate.identified_by || :id).to_s
15
+ parent_scalar = ValueGenerator.scalar_of(args[parent_key])
16
+
17
+ @known_ids[aggregate.hecks_name] << parent_scalar if entry[:entity].nil? && entry[:command].creates?
18
+
19
+ populator = catalog[:populators].find { |p| p[:command].equal?(entry[:command]) && p[:aggregate].equal?(aggregate) }
20
+ return unless populator
21
+
22
+ key = "#{aggregate.hecks_name}.#{populator[:entity].hecks_name}##{parent_scalar}"
23
+
24
+ # The auto-minted case (CommandInterpreter#entity_element): the
25
+ # element just landed at count-so-far + 1. The explicit case: this
26
+ # generator itself supplied whatever value sits at
27
+ # args[identity_argument] — no guessing needed, it's read straight
28
+ # back.
29
+ new_id =
30
+ if populator[:identity_argument]
31
+ ValueGenerator.scalar_of(args[populator[:identity_argument].to_s])
32
+ else
33
+ (@entity_known_ids[key].size + 1).to_s
34
+ end
35
+ @entity_known_ids[key] << new_id
36
+ end
37
+
38
+ def pick_known(name)
39
+ pool = @known_ids[name]
40
+ return ValueGenerator.random_id(@random) if pool.empty? || @random.rand < ValueGenerator::INVALID_REFERENCE_PROBABILITY
41
+
42
+ pool.sample(random: @random)
43
+ end
44
+
45
+ def pick_entity_known(aggregate_name, entity_name, parent_id)
46
+ pool = @entity_known_ids["#{aggregate_name}.#{entity_name}##{parent_id}"]
47
+ return ValueGenerator.random_id(@random) if pool.empty? || @random.rand < ValueGenerator::INVALID_REFERENCE_PROBABILITY
48
+
49
+ pool.sample(random: @random)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,96 @@
1
+ module Hecks
2
+ module Fuzzing
3
+ class SequenceGenerator
4
+ # Which step to try next: eligibility (what is POSSIBLE from the
5
+ # state so far) and weighting (what is LIKELY to reach somewhere
6
+ # new).
7
+ module Picker
8
+ private
9
+
10
+ def pick(catalog)
11
+ rest = catalog[:queries].dup
12
+ catalog[:instance].each { |entry| rest << entry if actionable?(catalog, entry) }
13
+ catalog[:entity_commands].each { |entry| rest << entry if actionable?(catalog, entry) }
14
+ catalog[:entity_queries].each { |entry| rest << entry if @known_ids[entry[:aggregate].hecks_name].any? }
15
+ catalog[:read_models].each { |entry| rest << entry if read_model_actionable?(entry) }
16
+
17
+ makers = catalog[:creating].select { |entry| satisfiable?(catalog, entry) }
18
+ pool = rest + makers.flat_map { |entry| [entry] * creating_weight(rest.size) }
19
+
20
+ steer(pool).sample(random: @random)
21
+ end
22
+
23
+ # WHILE THERE IS NOTHING TO FIND, MAKING SOMETHING IS THE ONLY USEFUL MOVE.
24
+ #
25
+ # A flat weight is right once the domain has records in it, and badly
26
+ # wrong before: banking declares ten queries, and from an empty store
27
+ # exactly ONE creating command is satisfiable — Customer.Register, the
28
+ # verb the whole cascade waits on. Two entries against ten left runs
29
+ # spending their entire budget querying a store nothing had been written
30
+ # to ; one seed answered ten empty queries in a row and emitted nothing
31
+ # at all.
32
+ #
33
+ # So while nothing exists, creation matches everything else put together.
34
+ # After that the ordinary weight applies and the run gets on with
35
+ # exercising what it made.
36
+ def creating_weight(rest_size)
37
+ return CREATING_WEIGHT if @known_ids.each_value.any?(&:any?)
38
+
39
+ [rest_size, CREATING_WEIGHT].max
40
+ end
41
+
42
+ def actionable?(catalog, entry)
43
+ @known_ids[entry[:aggregate].hecks_name].any? && satisfiable?(catalog, entry)
44
+ end
45
+
46
+ # A ROOTLESS report (no `reference_to` at all) has nothing to wait
47
+ # for — it reads whole tables, the same "always eligible" position
48
+ # `catalog[:queries]` itself takes. A ROOTED one needs a real
49
+ # instance of its own `reference_target` to ask about first, same
50
+ # rule `actionable?` already gives an instance command — asking
51
+ # `Banking.disputed_payment_count` before any Account exists would
52
+ # just be refused (`refuse_object_reference`/`NotFound`) the same
53
+ # way `Account.Credit` would be, for the identical reason.
54
+ def read_model_actionable?(entry)
55
+ entry[:model].reference_target.nil? || @known_ids[entry[:model].reference_target].any?
56
+ end
57
+
58
+ # A COMMAND THAT REFERENCES NOTHING THAT EXISTS CANNOT SUCCEED, so it is
59
+ # not offered until something does.
60
+ #
61
+ # `ValueGenerator.reference_value` has no real id to hand over when its
62
+ # pool is empty, so it mints `missing-…` and the dispatch is refused
63
+ # before it starts — a guaranteed waste of a step, and worse, one that
64
+ # never fills the pool it was waiting on. Banking cascades from it:
65
+ # Account.Open needs a Customer, ATMCard.Issue needs an Account,
66
+ # Transfer.Request needs two, so a run that opened with the wrong verb
67
+ # spent its whole budget being refused.
68
+ #
69
+ # This is the rule instance commands already follow — `known_ids.any?` —
70
+ # applied to what a command REFERENCES rather than to what it acts on.
71
+ # A target no creating command in this corpus can make (a cross-domain
72
+ # reference, which `CommandRules#resolve_references` skips anyway) is
73
+ # exempt, or the whole domain would starve waiting for it.
74
+ def satisfiable?(catalog, entry)
75
+ entry[:command].attributes.select(&:reference?).all? do |attribute|
76
+ target = attribute.type.target_name.to_s
77
+ !catalog[:creatable].include?(target) || @known_ids[target].any?
78
+ end
79
+ end
80
+
81
+ # Coverage-guided rather than uniformly random : a verb this sequence has
82
+ # not dispatched yet is weighted up, so a run spends its budget on the
83
+ # commands it has not reached instead of re-rolling the ones it has. The
84
+ # eligibility rules above still decide what is POSSIBLE — this only decides
85
+ # what is likely, so a verb gated behind state it does not have yet stays
86
+ # out of the pool entirely rather than being preferred forever.
87
+ def steer(pool)
88
+ fresh = pool.reject { |entry| @exercised.include?(entry[:verb]) }
89
+ return pool if fresh.empty?
90
+
91
+ pool + (fresh * UNEXERCISED_WEIGHT)
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,185 @@
1
+ require_relative "../../bluebook/expression/resolver"
2
+ require_relative "../invalid_value_generator"
3
+ require_relative "../value_generator"
4
+ require_relative "../../runtime/errors"
5
+
6
+ module Hecks
7
+ module Fuzzing
8
+ class SequenceGenerator
9
+ # Turn a picked entry into a corpus step: generate its arguments,
10
+ # occasionally malform exactly one of them, shape its identity, and
11
+ # dispatch it for real.
12
+ module StepBuilder
13
+ private
14
+
15
+ def build_query_step(runtime, entry)
16
+ args = args_for(entry[:query].attributes, entry[:aggregate])
17
+ safe_call { runtime.query(entry[:verb], **symbolize(args)) }
18
+ { "query" => entry[:verb], "args" => args }
19
+ end
20
+
21
+ # A REPORT ASK — the bare domain form `entry[:verb]` already carries
22
+ # ("Domain.report_name", no "::"), so `Dispatcher#query` routes it
23
+ # to the read model rather than an aggregate query. A `ReadModel`
24
+ # has no declared `.attributes` the way a `Query` does — its own
25
+ # argument surface is exactly ONE key, `reference_name`, and ONLY
26
+ # for a rooted model (`read_model_actionable?` already gated a
27
+ # rootless one straight into eligibility with nothing to supply).
28
+ # A BARE scalar, not `identity_shaped` — `ReadModelInterpreter#
29
+ # refuse_object_reference` explicitly REJECTS a Hash/Value offered
30
+ # here (this is the one place in the whole generator where the
31
+ # subject's own identity must NOT be wrapped the way a command
32
+ # argument's would be).
33
+ def build_read_model_step(runtime, entry)
34
+ model = entry[:model]
35
+ args = model.reference_target.nil? ? {} : { model.reference_name.to_s => pick_known(model.reference_target) }
36
+
37
+ safe_call { runtime.query(entry[:verb], **symbolize(args)) }
38
+ { "query" => entry[:verb], "args" => args }
39
+ end
40
+
41
+ def build_command_step(runtime, catalog, entry)
42
+ args = args_for(entry[:command].attributes, entry[:aggregate])
43
+ add_identity!(args, entry)
44
+
45
+ outcome = safe_call { runtime.dispatch(entry[:verb], **symbolize(args)) }
46
+ if outcome
47
+ record_outcome(catalog, entry, args)
48
+ @event_count += outcome.events.length
49
+ end
50
+ { "verb" => entry[:verb], "args" => args }
51
+ end
52
+
53
+ def args_for(attributes, aggregate)
54
+ args = attributes.each_with_object({}) do |attribute, built|
55
+ # List-typed direct command arguments have no example in this repo's
56
+ # domains today — every list is populated via a per-element append
57
+ # command instead. Skipped rather than guessed at.
58
+ next if attribute.list?
59
+ # AN OPTIONAL ARGUMENT IS SOMETIMES NOT GIVEN, and that is an
60
+ # ordinary payload rather than a damaged one — see
61
+ # OPTIONAL_OMITTED_PROBABILITY for why this cannot live in
62
+ # `malform` below and what it was costing while it did not
63
+ # exist at all.
64
+ next if attribute.optional? && @random.rand < SequenceGenerator::OPTIONAL_OMITTED_PROBABILITY
65
+
66
+ built[attribute.name.to_s] = ValueGenerator.value_for(attribute, aggregate, random: @random, known_ids: @known_ids)
67
+ end
68
+
69
+ malform(args, attributes, aggregate)
70
+ end
71
+
72
+ # ONE MALFORMATION AT A TIME, and usually none. A step whose payload is
73
+ # wrong in three ways only ever proves which check runs first ; wrong in
74
+ # exactly one way names the check that fired. And the rate stays low on
75
+ # purpose — a corrupted step is almost always refused, a sequence of
76
+ # refusals reaches no state at all, and bin/fuzz already counts those as
77
+ # SILENT rather than scoring them.
78
+ def malform(args, attributes, aggregate)
79
+ return args if args.empty? || @random.rand >= MALFORMED_ARGUMENT_PROBABILITY
80
+
81
+ case @random.rand(3)
82
+ when 0 then corrupt_one(args, attributes, aggregate)
83
+ when 1 then drop_one(args, aggregate)
84
+ else args.merge([InvalidValueGenerator.undeclared_argument(random: @random)].to_h)
85
+ end
86
+ end
87
+
88
+ # NEVER THE IDENTITY. A creating command with no id auto-mints one, and
89
+ # a minted id is deliberately unreproducible — a random hex, never a
90
+ # guessable counter — so dropping it manufactures a step whose outcome
91
+ # cannot be replayed and says nothing about the runtime's behaviour.
92
+ # Every step in the hand-written corpus
93
+ # supplies an id for the same reason. Whether an auto-minted id OUGHT to
94
+ # be reproducible is a real question, but it is not one a payload fuzzer
95
+ # can ask.
96
+ def drop_one(args, aggregate)
97
+ identity = (aggregate.identified_by || :id).to_s
98
+ droppable = args.keys - [identity, "id"]
99
+ return args if droppable.empty?
100
+
101
+ args.reject { |name, _| name == droppable.sample(random: @random) }
102
+ end
103
+
104
+ def corrupt_one(args, attributes, aggregate)
105
+ named = attributes.reject(&:list?).select { |attribute| args.key?(attribute.name.to_s) }
106
+ return args if named.empty?
107
+
108
+ attribute = named.sample(random: @random)
109
+ args.merge(attribute.name.to_s => InvalidValueGenerator.corrupt(attribute, aggregate, random: @random))
110
+ end
111
+
112
+ def add_identity!(args, entry)
113
+ aggregate = entry[:aggregate]
114
+ parent_key = (aggregate.identified_by || :id).to_s
115
+
116
+ if entry[:entity]
117
+ parent_scalar = pick_known(aggregate.hecks_name)
118
+ args[parent_key] = identity_shaped(aggregate, aggregate.identified_by, parent_scalar, aggregate)
119
+ entity_key = (entry[:entity].identified_by || :id).to_s
120
+ entity_scalar = pick_entity_known(aggregate.hecks_name, entry[:entity].hecks_name, parent_scalar)
121
+ args[entity_key] = identity_shaped(entry[:entity], entry[:entity].identified_by, entity_scalar, aggregate)
122
+ elsif entry[:command].creates?
123
+ args[parent_key] ||= identity_shaped(aggregate, aggregate.identified_by, ValueGenerator.random_id(@random), aggregate)
124
+ else
125
+ scalar = pick_known(aggregate.hecks_name)
126
+ args[parent_key] = identity_shaped(aggregate, aggregate.identified_by, scalar, aggregate)
127
+ end
128
+ end
129
+
130
+ # A bare scalar id, shaped to match whatever `construct` itself
131
+ # declares that identity field as. `Account::LedgerEntry` is addressed
132
+ # by `sequence`, and `sequence` is declared as a value-object-typed
133
+ # attribute (`LedgerSequence`), not a plain identifier — a fuzz run
134
+ # that skipped this wrapping and dispatched a bare `"1"` had every
135
+ # such step refused at the type gate (a bare scalar against a
136
+ # value-object-typed identity is a TypeMismatch, not a NotFound), so
137
+ # the wrapping is what lets a fuzz step address the record at
138
+ # all. Left as a bare scalar when the construct declares no such
139
+ # attribute at all — the default `:id` case, which really is untyped.
140
+ def identity_shaped(construct, key, scalar, aggregate)
141
+ return scalar unless key
142
+
143
+ attribute = construct.attribute(key)
144
+ return scalar unless attribute
145
+
146
+ value_object = aggregate.value_object(attribute.type.to_s)
147
+ return scalar unless value_object
148
+
149
+ field = value_object.attributes.first
150
+ return scalar unless field
151
+
152
+ { field.name.to_s => coerce_scalar(field.type.to_s, scalar) }
153
+ end
154
+
155
+ def coerce_scalar(type_name, scalar)
156
+ case type_name
157
+ when "Integer" then scalar.to_i
158
+ when "Float" then scalar.to_f
159
+ else scalar.to_s
160
+ end
161
+ end
162
+
163
+ def symbolize(args) = args.transform_keys(&:to_sym)
164
+
165
+ # A step the runtime declines is not a generator failure — it simply did
166
+ # not take effect, so nothing is recorded and the sequence carries on. The
167
+ # step still goes into the corpus, because a REFUSAL IS AN ANSWER, and
168
+ # its wording is pinned by the corpus.
169
+ #
170
+ # EvaluationError sits alongside the declared refusals deliberately : a
171
+ # payload the interpreter cannot read is the domain declining it, and
172
+ # bin/run already records exactly those in a corpus's refusals —
173
+ # `positive? expects a number, got "lots"` is one of banking's. Anything
174
+ # else still propagates and fails spec/fuzzing, which is what says the
175
+ # generator built a step that breaks the interpreter for reasons that have
176
+ # nothing to do with the domain declining a payload.
177
+ def safe_call
178
+ yield
179
+ rescue *Hecks::Runtime::DOMAIN_REFUSALS, Hecks::Bluebook::Expression::EvaluationError
180
+ nil
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,130 @@
1
+ require "fileutils"
2
+ require "set"
3
+ require "tmpdir"
4
+ require_relative "isolated_boot"
5
+ require_relative "invalid_value_generator"
6
+ require_relative "value_generator"
7
+ require_relative "sequence_generator/catalog"
8
+ require_relative "sequence_generator/picker"
9
+ require_relative "sequence_generator/step_builder"
10
+ require_relative "sequence_generator/outcome_tracker"
11
+
12
+ module Hecks
13
+ module Fuzzing
14
+ # A random-but-valid sequence of dispatches and queries, in the exact
15
+ # `{name, note, steps}` shape `spec/corpus/*.json` already uses — so a
16
+ # generated sequence replays as a corpus member, completely unchanged.
17
+ # Boots a throwaway copy of
18
+ # the domain and DISPATCHES each candidate step for real as it builds the
19
+ # sequence (not just synthesizing plausible-looking JSON) : the only way to
20
+ # know whether a step actually reached a new state, or which id an
21
+ # auto-minted entity landed on, is to run it and watch what happened.
22
+ #
23
+ # Single-call fuzzing mostly misses the bugs this project has actually
24
+ # found — they needed STATE first (a saga leg acting on a transfer that
25
+ # already exists, a reference pointing at a customer already registered).
26
+ # So this tracks what it has created as it goes, the same way
27
+ # spec/banking_state_machine_spec.rb's hand-written generator does, and
28
+ # weights later steps toward acting on what already exists.
29
+ #
30
+ # One concern per file beside this one: what the domain offers
31
+ # (sequence_generator/catalog.rb), which step to try next (picker.rb),
32
+ # how a step is built and dispatched (step_builder.rb), and what a
33
+ # success taught us (outcome_tracker.rb).
34
+ class SequenceGenerator
35
+ include Catalog
36
+ include Picker
37
+ include StepBuilder
38
+ include OutcomeTracker
39
+
40
+ # Creating commands are always eligible ; weighting them heavier (not
41
+ # exclusively — a domain with only one or two aggregates would starve
42
+ # everything else) makes a sequence reach an actionable state sooner
43
+ # instead of spending its early budget refusing "acts on nothing yet."
44
+ CREATING_WEIGHT = 2
45
+
46
+ # How often a payload is deliberately the wrong shape. Low, because a
47
+ # refused step reaches no new state and a sequence of them is SILENT.
48
+ MALFORMED_ARGUMENT_PROBABILITY = 0.12
49
+
50
+ # HOW OFTEN AN OPTIONAL ARGUMENT IS SIMPLY NOT GIVEN — a fair coin,
51
+ # because that is exactly what `optional:` means: present or absent,
52
+ # both legal, neither the interesting case.
53
+ #
54
+ # This is NOT a malformation and must not be filed as one. `malform`
55
+ # drops an argument too, but a dropped REQUIRED argument is refused,
56
+ # a refusal writes no record, and bin/fuzz counts it SILENT — so the
57
+ # one outcome worth reaching (a stored record carrying a null, then
58
+ # QUERIED) was unreachable from that path by construction. Until
59
+ # this existed no generated history contained a null at all, which
60
+ # meant `query_answers_match_reference` — the differential that
61
+ # diffs every native adapter against the reference interpreter — had
62
+ # never once compared a nullable field. Four query bugs shipped in
63
+ # exactly that blind spot (`ne:` with an empty string, array `in:`,
64
+ # `ne:` against a null, `in` on a numeric field); the adapter
65
+ # agreement gate had the identical hole and found a fourth bug the
66
+ # hour it was closed.
67
+ OPTIONAL_OMITTED_PROBABILITY = 0.5
68
+
69
+ # How strongly an unexercised verb is preferred over one this sequence has
70
+ # already dispatched. Random picking revisits the same handful of verbs and
71
+ # leaves whole commands untouched for a whole run — which is the same
72
+ # "reached no interesting state" problem the SILENT count reports, seen from
73
+ # the generating end rather than the scoring end.
74
+ UNEXERCISED_WEIGHT = 4
75
+
76
+ def self.generate(domain_path, seed:, steps:)
77
+ new(domain_path, seed: seed, steps: steps).call
78
+ end
79
+
80
+ # How many EVENTS the generated sequence actually produced — not
81
+ # steps, not successful dispatches, but the sum of every Result#events
82
+ # length across the run. This is the count bin/fuzz declares as the
83
+ # script's own `expectations.events` claim: whatever was achieved
84
+ # DURING generation becomes the claim a fresh replay of the same
85
+ # script is held to. Zero means the sequence never
86
+ # reached an interesting state — a fuzzer-effectiveness fact, not a
87
+ # replay one.
88
+ attr_reader :event_count
89
+
90
+ def initialize(domain_path, seed:, steps:)
91
+ @domain_path = domain_path
92
+ @seed = seed
93
+ @step_count = steps
94
+ @random = Random.new(seed)
95
+ @known_ids = Hash.new { |h, k| h[k] = [] }
96
+ @entity_known_ids = Hash.new { |h, k| h[k] = [] }
97
+ @exercised = Set.new
98
+ @event_count = 0
99
+ end
100
+
101
+ def call
102
+ # Real leftover data from ordinary use (bin/console, whatever) lives
103
+ # under the example's data/ — a generator that boots against it
104
+ # starts from state its own known_ids tracking doesn't know about.
105
+ # IsolatedBoot resets that AND rebinds persistence to Memory, since
106
+ # a Postgres-bound domain's real store lives outside the copied
107
+ # directory entirely and `rm_rf`ing data/ alone cannot reach it —
108
+ # see isolated_boot.rb's own header.
109
+ IsolatedBoot.call(@domain_path) do |copy|
110
+ runtime = Hecks.boot(copy)
111
+ catalog = build_catalog(runtime)
112
+ Array.new(@step_count) { attempt_step(runtime, catalog) }.compact
113
+ end
114
+ end
115
+
116
+ private
117
+
118
+ def attempt_step(runtime, catalog)
119
+ entry = pick(catalog)
120
+ return nil unless entry
121
+
122
+ @exercised << entry[:verb]
123
+ if entry[:query] then build_query_step(runtime, entry)
124
+ elsif entry[:model] then build_read_model_step(runtime, entry)
125
+ else build_command_step(runtime, catalog, entry)
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end