hecks 2.7.0 → 2.9.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 (463) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +11 -0
  3. data/lib/hecks/adapters/driven/claude_code.rb +4 -104
  4. data/lib/hecks/adapters/driven/d1.rb +44 -237
  5. data/lib/hecks/adapters/driven/folder.rb +17 -76
  6. data/lib/hecks/adapters/driven/google_authentication.rb +15 -49
  7. data/lib/hecks/adapters/driven/governance_authorization.rb +23 -70
  8. data/lib/hecks/adapters/driven/heki/journal.rb +8 -41
  9. data/lib/hecks/adapters/driven/heki/saga_store.rb +12 -54
  10. data/lib/hecks/adapters/driven/heki/snapshot.rb +4 -12
  11. data/lib/hecks/adapters/driven/heki.rb +13 -42
  12. data/lib/hecks/adapters/driven/identity_registry.rb +3 -14
  13. data/lib/hecks/adapters/driven/in_memory_ordering.rb +9 -26
  14. data/lib/hecks/adapters/driven/in_process_key_vault.rb +2 -14
  15. data/lib/hecks/adapters/driven/lambda/client.rb +10 -51
  16. data/lib/hecks/adapters/driven/lambda.rb +10 -84
  17. data/lib/hecks/adapters/driven/local_storage.rb +5 -40
  18. data/lib/hecks/adapters/driven/memory.rb +18 -99
  19. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +5 -21
  20. data/lib/hecks/adapters/driven/postgres/codec.rb +6 -28
  21. data/lib/hecks/adapters/driven/postgres/outbox.rb +3 -12
  22. data/lib/hecks/adapters/driven/postgres/reconnect.rb +4 -30
  23. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +34 -108
  24. data/lib/hecks/adapters/driven/postgres.rb +145 -134
  25. data/lib/hecks/adapters/driven/postgres_era.adapter +4 -15
  26. data/lib/hecks/adapters/driven/prism.rb +7 -30
  27. data/lib/hecks/adapters/driven/sql_query_builder.rb +21 -121
  28. data/lib/hecks/adapters/driven/sqlite/codec.rb +8 -40
  29. data/lib/hecks/adapters/driven/sqlite/projection.rb +26 -82
  30. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +40 -84
  31. data/lib/hecks/adapters/driven/sqlite.rb +128 -65
  32. data/lib/hecks/adapters/driven/tenant_provisioner.rb +15 -37
  33. data/lib/hecks/adapters/driven.rb +5 -36
  34. data/lib/hecks/adapters/driving/github_webhook.rb +8 -76
  35. data/lib/hecks/behaviors/dsl.rb +7 -25
  36. data/lib/hecks/behaviors/expectations.rb +32 -142
  37. data/lib/hecks/behaviors/ir.rb +7 -20
  38. data/lib/hecks/behaviors/rspec.rb +6 -17
  39. data/lib/hecks/behaviors/runner.rb +10 -34
  40. data/lib/hecks/behaviors.rb +4 -8
  41. data/lib/hecks/bluebook/aggregate.rb +9 -75
  42. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +9 -38
  43. data/lib/hecks/bluebook/assembly/build.rb +4 -15
  44. data/lib/hecks/bluebook/assembly/contract.rb +15 -136
  45. data/lib/hecks/bluebook/assembly/contracts.rb +54 -174
  46. data/lib/hecks/bluebook/assembly/marks.rb +31 -223
  47. data/lib/hecks/bluebook/assembly/specializer.rb +6 -52
  48. data/lib/hecks/bluebook/assembly.rb +6 -45
  49. data/lib/hecks/bluebook/attribute.rb +12 -61
  50. data/lib/hecks/bluebook/behaviour/aggregate.rb +10 -39
  51. data/lib/hecks/bluebook/behaviour/attribute.rb +3 -14
  52. data/lib/hecks/bluebook/behaviour/chapter.rb +17 -42
  53. data/lib/hecks/bluebook/behaviour/command.rb +14 -66
  54. data/lib/hecks/bluebook/behaviour/domain_port.rb +4 -20
  55. data/lib/hecks/bluebook/behaviour/entity.rb +7 -27
  56. data/lib/hecks/bluebook/behaviour/hexagon.rb +23 -41
  57. data/lib/hecks/bluebook/behaviour/lifecycle.rb +15 -40
  58. data/lib/hecks/bluebook/behaviour/policy.rb +23 -52
  59. data/lib/hecks/bluebook/behaviour/process_manager.rb +30 -57
  60. data/lib/hecks/bluebook/behaviour/read_model.rb +38 -26
  61. data/lib/hecks/bluebook/behaviour/traits.rb +27 -64
  62. data/lib/hecks/bluebook/behaviour/value_object.rb +5 -24
  63. data/lib/hecks/bluebook/capabilities.rb +17 -49
  64. data/lib/hecks/bluebook/chapter.rb +10 -48
  65. data/lib/hecks/bluebook/command.rb +23 -81
  66. data/lib/hecks/bluebook/domain_port.rb +14 -63
  67. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +50 -229
  68. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +50 -377
  69. data/lib/hecks/bluebook/dsl/attribute_collector.rb +33 -204
  70. data/lib/hecks/bluebook/dsl/binding_proxy.rb +17 -65
  71. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +86 -708
  72. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +56 -170
  73. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
  74. data/lib/hecks/bluebook/dsl/command_builder.rb +74 -546
  75. data/lib/hecks/bluebook/dsl/const_shim.rb +8 -41
  76. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +12 -87
  77. data/lib/hecks/bluebook/dsl/entity_builder.rb +44 -330
  78. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +20 -269
  79. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +26 -189
  80. data/lib/hecks/bluebook/dsl/identity_declaration.rb +13 -88
  81. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +6 -20
  82. data/lib/hecks/bluebook/dsl/policy_builder.rb +16 -88
  83. data/lib/hecks/bluebook/dsl/port_builder.rb +2 -6
  84. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +22 -96
  85. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +31 -184
  86. data/lib/hecks/bluebook/dsl/query_builder.rb +11 -42
  87. data/lib/hecks/bluebook/dsl/read_model_builder.rb +75 -201
  88. data/lib/hecks/bluebook/dsl/rule_reference.rb +22 -163
  89. data/lib/hecks/bluebook/dsl/translation_builder.rb +28 -114
  90. data/lib/hecks/bluebook/dsl/value_object_builder.rb +10 -124
  91. data/lib/hecks/bluebook/dsl/word_gate.rb +16 -162
  92. data/lib/hecks/bluebook/dsl/world_builder.rb +19 -106
  93. data/lib/hecks/bluebook/entity.rb +13 -71
  94. data/lib/hecks/bluebook/expression/ast_json.rb +24 -190
  95. data/lib/hecks/bluebook/expression/ast_reader.rb +12 -54
  96. data/lib/hecks/bluebook/expression/canonical_form.rb +11 -88
  97. data/lib/hecks/bluebook/expression/evaluator.rb +47 -295
  98. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +30 -147
  99. data/lib/hecks/bluebook/expression/resolver.rb +37 -660
  100. data/lib/hecks/bluebook/hexagon.rb +5 -28
  101. data/lib/hecks/bluebook/lifecycle.rb +5 -16
  102. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -7
  103. data/lib/hecks/bluebook/meta_validator/judge.rb +111 -361
  104. data/lib/hecks/bluebook/meta_validator/plan.rb +33 -162
  105. data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -7
  106. data/lib/hecks/bluebook/meta_validator/readings.rb +56 -342
  107. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -176
  108. data/lib/hecks/bluebook/meta_validator/shapes.rb +54 -268
  109. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +47 -308
  110. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +7 -43
  111. data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -18
  112. data/lib/hecks/bluebook/meta_validator.rb +79 -349
  113. data/lib/hecks/bluebook/model_check/client_profile.rb +10 -91
  114. data/lib/hecks/bluebook/model_check.rb +102 -600
  115. data/lib/hecks/bluebook/pattern_subset.rb +11 -69
  116. data/lib/hecks/bluebook/process_manager.rb +13 -83
  117. data/lib/hecks/bluebook/project_loader.rb +3 -39
  118. data/lib/hecks/bluebook/project_register.rb +12 -60
  119. data/lib/hecks/bluebook/query.rb +9 -48
  120. data/lib/hecks/bluebook/read_model.rb +57 -81
  121. data/lib/hecks/bluebook/reference.rb +7 -41
  122. data/lib/hecks/bluebook/smoke_test.rb +8 -98
  123. data/lib/hecks/bluebook/synthesizer.rb +13 -62
  124. data/lib/hecks/bluebook/translation.rb +26 -62
  125. data/lib/hecks/bluebook/value_object.rb +11 -36
  126. data/lib/hecks/bluebook.rb +5 -22
  127. data/lib/hecks/cache_dir.rb +84 -0
  128. data/lib/hecks/cli/document.rb +86 -0
  129. data/lib/hecks/cli/ir.rb +45 -0
  130. data/lib/hecks/cli/mcp.rb +31 -0
  131. data/lib/hecks/cli/mcp_door.rb +366 -0
  132. data/lib/hecks/cli/model_check.rb +203 -0
  133. data/lib/hecks/cli/project_cli.rb +90 -0
  134. data/lib/hecks/cli/project_diagrams.rb +36 -0
  135. data/lib/hecks/cli/run.rb +173 -0
  136. data/lib/hecks/cli/smoke_test.rb +45 -0
  137. data/lib/hecks/cli/stores.rb +53 -0
  138. data/lib/hecks/cli.rb +160 -0
  139. data/lib/hecks/construct.rb +8 -34
  140. data/lib/hecks/deploy/bluebook/deploy.bluebook +33 -142
  141. data/lib/hecks/deploy/bluebook/deploy.hecksagon +2 -9
  142. data/lib/hecks/embryonaut_bluebook/lock.rb +3 -35
  143. data/lib/hecks/embryonaut_bluebook/shape.rb +1 -17
  144. data/lib/hecks/embryonaut_bluebook/vendor.rb +8 -53
  145. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +4 -19
  146. data/lib/hecks/embryonaut_bluebook.rb +4 -71
  147. data/lib/hecks/facade/cli_door.rb +24 -106
  148. data/lib/hecks/facade/cli_runner.rb +28 -172
  149. data/lib/hecks/facade/command_request.rb +13 -33
  150. data/lib/hecks/facade/handle.rb +34 -121
  151. data/lib/hecks/facade/json_door.rb +31 -146
  152. data/lib/hecks/facade/surface/aggregate_door.rb +39 -104
  153. data/lib/hecks/facade/surface/chapter.rb +10 -54
  154. data/lib/hecks/facade/surface.rb +8 -23
  155. data/lib/hecks/facade.rb +10 -25
  156. data/lib/hecks/forms/app.rb +15 -70
  157. data/lib/hecks/forms/command_form_renderer.rb +10 -29
  158. data/lib/hecks/forms/field_renderer.rb +17 -156
  159. data/lib/hecks/forms/field_shape.rb +30 -222
  160. data/lib/hecks/forms/html.rb +12 -47
  161. data/lib/hecks/forms/index_renderer.rb +1 -4
  162. data/lib/hecks/forms/page.rb +3 -9
  163. data/lib/hecks/forms/params.rb +14 -60
  164. data/lib/hecks/forms/port_argument.rb +2 -19
  165. data/lib/hecks/forms/query_form_renderer.rb +4 -79
  166. data/lib/hecks/forms/record_renderer.rb +8 -75
  167. data/lib/hecks/forms/record_table.rb +7 -50
  168. data/lib/hecks/forms/reference_options.rb +3 -30
  169. data/lib/hecks/forms/value_object_shape.rb +8 -35
  170. data/lib/hecks/forms.rb +5 -33
  171. data/lib/hecks/fqn.rb +5 -12
  172. data/lib/hecks/framework/bluebook/compliance.bluebook +16 -65
  173. data/lib/hecks/framework/bluebook/console_settings.bluebook +47 -134
  174. data/lib/hecks/framework/bluebook/framework.hecksagon +5 -14
  175. data/lib/hecks/framework/bluebook/governance.bluebook +13 -34
  176. data/lib/hecks/framework/bluebook/identity.bluebook +11 -26
  177. data/lib/hecks/framework/bluebook/privacy.bluebook +8 -33
  178. data/lib/hecks/framework.rb +11 -80
  179. data/lib/hecks/freezer.rb +5 -39
  180. data/lib/hecks/grammar/expression.bluebook +15 -40
  181. data/lib/hecks/grammar/translation.bluebook +3 -6
  182. data/lib/hecks/grammar.rb +16 -41
  183. data/lib/hecks/ir.rb +14 -83
  184. data/lib/hecks/language/adapter.bluebook +2 -7
  185. data/lib/hecks/language/bluebook/aggregate.bluebook +56 -221
  186. data/lib/hecks/language/bluebook/attaches/paging.bluebook +6 -15
  187. data/lib/hecks/language/bluebook/bluebook.bluebook +15 -83
  188. data/lib/hecks/language/bluebook/bluebook.hecksagon +3 -15
  189. data/lib/hecks/language/bluebook/command.bluebook +27 -144
  190. data/lib/hecks/language/bluebook/entity.bluebook +24 -97
  191. data/lib/hecks/language/bluebook/policy.bluebook +16 -67
  192. data/lib/hecks/language/bluebook/process_manager.bluebook +28 -151
  193. data/lib/hecks/language/bluebook/projection.bluebook +126 -60
  194. data/lib/hecks/language/bluebook/query.bluebook +12 -43
  195. data/lib/hecks/language/bluebook/shape.bluebook +23 -73
  196. data/lib/hecks/language/bluebook/syntax.bluebook +51 -262
  197. data/lib/hecks/language/bluebook/vocabulary.bluebook +98 -197
  198. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +3 -10
  199. data/lib/hecks/language/hecksagon/domain_port.bluebook +4 -19
  200. data/lib/hecks/language/hecksagon/hecksagon.bluebook +6 -25
  201. data/lib/hecks/language/hecksagon/port_operation.bluebook +3 -14
  202. data/lib/hecks/language/oidc.json +35 -0
  203. data/lib/hecks/language/port.bluebook +4 -17
  204. data/lib/hecks/language/translation/translation.bluebook +3 -9
  205. data/lib/hecks/language/translation/translation_aggregate.bluebook +5 -24
  206. data/lib/hecks/language/world/wiring.bluebook +1 -3
  207. data/lib/hecks/language/world/world.bluebook +5 -7
  208. data/lib/hecks/literal.rb +8 -50
  209. data/lib/hecks/mcp_door_scope.rb +95 -0
  210. data/lib/hecks/mcp_stdio_guard.rb +4 -41
  211. data/lib/hecks/naming.rb +29 -137
  212. data/lib/hecks/ports/access_control.rb +7 -23
  213. data/lib/hecks/ports/agent/answers.rb +5 -23
  214. data/lib/hecks/ports/agent.rb +47 -172
  215. data/lib/hecks/ports/authentication.rb +12 -39
  216. data/lib/hecks/ports/authorization.rb +16 -43
  217. data/lib/hecks/ports/clock.rb +5 -45
  218. data/lib/hecks/ports/extraction.rb +7 -12
  219. data/lib/hecks/ports/identity_assignment.rb +8 -29
  220. data/lib/hecks/ports/identity_generation.rb +6 -29
  221. data/lib/hecks/ports/identity_resolution.rb +2 -10
  222. data/lib/hecks/ports/key_vault.rb +9 -45
  223. data/lib/hecks/ports/persistence/append_only.rb +62 -65
  224. data/lib/hecks/ports/persistence/binding_policy.rb +4 -14
  225. data/lib/hecks/ports/persistence/codec_boundary.rb +20 -80
  226. data/lib/hecks/ports/persistence/null_saga_store.rb +4 -20
  227. data/lib/hecks/ports/persistence/plugin.rb +13 -42
  228. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +38 -141
  229. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +15 -115
  230. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +16 -84
  231. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +7 -26
  232. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -22
  233. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +55 -181
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/events.rb +77 -0
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +12 -122
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +28 -128
  237. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +73 -474
  238. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +31 -124
  239. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +45 -219
  240. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +13 -108
  241. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +18 -75
  242. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +9 -49
  243. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +6 -123
  244. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +23 -55
  245. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +14 -72
  246. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +7 -16
  247. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +14 -115
  248. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +13 -45
  249. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +127 -554
  250. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +13 -63
  251. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +2 -17
  252. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +3 -14
  253. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +13 -58
  254. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -17
  255. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -26
  256. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +9 -25
  257. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +22 -122
  258. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +34 -123
  259. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +5 -12
  260. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +3 -9
  261. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -14
  262. data/lib/hecks/ports/persistence/plugins/era.rb +4 -23
  263. data/lib/hecks/ports/persistence/postgres_dump/connection.rb +7 -23
  264. data/lib/hecks/ports/persistence/postgres_dump.rb +4 -35
  265. data/lib/hecks/ports/persistence/remote_runtime.rb +2 -24
  266. data/lib/hecks/ports/persistence/repository_factory.rb +8 -27
  267. data/lib/hecks/ports/persistence/state_codec.rb +23 -103
  268. data/lib/hecks/ports/persistence.rb +7 -14
  269. data/lib/hecks/ports/projection.rb +42 -84
  270. data/lib/hecks/ports/query/in_memory.rb +10 -58
  271. data/lib/hecks/ports/query/ordering.rb +11 -32
  272. data/lib/hecks/ports/query.rb +14 -37
  273. data/lib/hecks/ports.rb +2 -8
  274. data/lib/hecks/projections/bootstrap_table.rb +11 -43
  275. data/lib/hecks/projections/deploy/fargate/assembly.rb +2 -15
  276. data/lib/hecks/projections/deploy/fargate/cdn.rb +1 -48
  277. data/lib/hecks/projections/deploy/fargate/cdn_default.rb +2 -11
  278. data/lib/hecks/projections/deploy/fargate/check.rb +5 -14
  279. data/lib/hecks/projections/deploy/fargate/containers.rb +3 -78
  280. data/lib/hecks/projections/deploy/fargate/extras.rb +3 -44
  281. data/lib/hecks/projections/deploy/fargate/monitoring.rb +0 -44
  282. data/lib/hecks/projections/deploy/fargate/settings.rb +2 -54
  283. data/lib/hecks/projections/deploy/fargate/yaml.rb +13 -58
  284. data/lib/hecks/projections/deploy/fargate.rb +35 -202
  285. data/lib/hecks/projections/deploy/lambda.rb +151 -679
  286. data/lib/hecks/projections/deploy/preview/access_yaml.rb +1 -33
  287. data/lib/hecks/projections/deploy/preview/containers.rb +6 -111
  288. data/lib/hecks/projections/deploy/preview/routing_yaml.rb +4 -56
  289. data/lib/hecks/projections/deploy/preview/script.rb +2 -14
  290. data/lib/hecks/projections/deploy/preview/settings.rb +3 -33
  291. data/lib/hecks/projections/deploy/preview/task_yaml.rb +2 -38
  292. data/lib/hecks/projections/deploy/preview/template.rb +2 -58
  293. data/lib/hecks/projections/deploy/preview.rb +2 -67
  294. data/lib/hecks/projections/deploy/scripts/settings.rb +3 -25
  295. data/lib/hecks/projections/deploy/scripts.rb +4 -57
  296. data/lib/hecks/projections/deploy/shared.rb +47 -146
  297. data/lib/hecks/projections/deploy/smoke.rb +4 -61
  298. data/lib/hecks/projections/deploy/template_diff/comparer.rb +3 -25
  299. data/lib/hecks/projections/deploy/template_diff/loader.rb +2 -11
  300. data/lib/hecks/projections/deploy/template_diff.rb +5 -57
  301. data/lib/hecks/projections/diagrams.rb +20 -582
  302. data/lib/hecks/projections/glossary/html.rb +14 -130
  303. data/lib/hecks/projections/glossary/markdown.rb +8 -69
  304. data/lib/hecks/projections/glossary/mermaid.rb +9 -66
  305. data/lib/hecks/projections/glossary/sensitivity.rb +5 -34
  306. data/lib/hecks/projections/glossary/sentences.rb +14 -159
  307. data/lib/hecks/projections/glossary.rb +30 -222
  308. data/lib/hecks/projections/ir.rb +3 -10
  309. data/lib/hecks/projections/model/deviations.rb +15 -42
  310. data/lib/hecks/projections/model.rb +12 -51
  311. data/lib/hecks/projections/oidc.rb +12 -56
  312. data/lib/hecks/projections/parser_table.rb +9 -28
  313. data/lib/hecks/projections/reference.rb +9 -16
  314. data/lib/hecks/projections/rust_vocabulary.rb +12 -250
  315. data/lib/hecks/projections/shape.rb +3 -29
  316. data/lib/hecks/projections/statements.rb +7 -86
  317. data/lib/hecks/projections/vocabulary.rb +6 -22
  318. data/lib/hecks/projections.rb +2 -16
  319. data/lib/hecks/projector/cli_projector.rb +60 -375
  320. data/lib/hecks/projector/docs_projector.rb +10 -201
  321. data/lib/hecks/projector/exporter.rb +46 -138
  322. data/lib/hecks/projector/ir_projector.rb +2 -9
  323. data/lib/hecks/projector/narrate_projector.rb +15 -189
  324. data/lib/hecks/projector/target.rb +13 -76
  325. data/lib/hecks/projector.rb +30 -121
  326. data/lib/hecks/query_specification/common/comparators.rb +5 -26
  327. data/lib/hecks/query_specification/common/comparison.rb +35 -143
  328. data/lib/hecks/query_specification/common/dsl.rb +8 -24
  329. data/lib/hecks/query_specification/common/null_policy.rb +20 -70
  330. data/lib/hecks/query_specification/common/options.rb +2 -6
  331. data/lib/hecks/query_specification/common/where_clause.rb +2 -10
  332. data/lib/hecks/query_specification/field_path.rb +37 -77
  333. data/lib/hecks/query_specification/hop_path.rb +12 -76
  334. data/lib/hecks/release/gem_pin.rb +2 -26
  335. data/lib/hecks/release/runner/ci_publisher.rb +93 -0
  336. data/lib/hecks/release/runner/commands.rb +66 -0
  337. data/lib/hecks/release/runner/console.rb +52 -0
  338. data/lib/hecks/release/runner/gem_publisher.rb +43 -0
  339. data/lib/hecks/release/runner/git.rb +48 -0
  340. data/lib/hecks/release/runner/npm_publisher.rb +75 -0
  341. data/lib/hecks/release/runner/preflight.rb +104 -0
  342. data/lib/hecks/release/runner/published.rb +55 -0
  343. data/lib/hecks/release/runner/tagger.rb +80 -0
  344. data/lib/hecks/release/runner.rb +215 -0
  345. data/lib/hecks/rendering.rb +4 -26
  346. data/lib/hecks/router/namespace_installer.rb +12 -36
  347. data/lib/hecks/router.rb +4 -8
  348. data/lib/hecks/runtime/aggregate_lock.rb +8 -33
  349. data/lib/hecks/runtime/boot_gates.rb +3 -18
  350. data/lib/hecks/runtime/caller.rb +19 -59
  351. data/lib/hecks/runtime/capability_graph.rb +5 -21
  352. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
  353. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +23 -160
  354. data/lib/hecks/runtime/command_interpreter.rb +125 -307
  355. data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
  356. data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
  357. data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
  358. data/lib/hecks/runtime/command_rules/emission.rb +8 -21
  359. data/lib/hecks/runtime/command_rules/references.rb +23 -202
  360. data/lib/hecks/runtime/dependency_planning.rb +30 -102
  361. data/lib/hecks/runtime/dispatcher.rb +82 -241
  362. data/lib/hecks/runtime/entity_element.rb +40 -462
  363. data/lib/hecks/runtime/entity_interpreter.rb +65 -220
  364. data/lib/hecks/runtime/errors.rb +32 -115
  365. data/lib/hecks/runtime/event.rb +3 -25
  366. data/lib/hecks/runtime/identity.rb +18 -154
  367. data/lib/hecks/runtime/instance.rb +18 -150
  368. data/lib/hecks/runtime/interpreting.rb +11 -70
  369. data/lib/hecks/runtime/invocation.rb +28 -164
  370. data/lib/hecks/runtime/loader.rb +48 -186
  371. data/lib/hecks/runtime/outbox.rb +38 -261
  372. data/lib/hecks/runtime/policy_interpreter.rb +38 -200
  373. data/lib/hecks/runtime/port_operation_interpreter.rb +26 -87
  374. data/lib/hecks/runtime/query_interpreter.rb +45 -187
  375. data/lib/hecks/runtime/reaction_invocation.rb +36 -138
  376. data/lib/hecks/runtime/read_model_interpreter.rb +139 -208
  377. data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
  378. data/lib/hecks/runtime/reference_hop.rb +8 -81
  379. data/lib/hecks/runtime/refusal_wording.rb +8 -89
  380. data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
  381. data/lib/hecks/runtime/registry/verification.rb +52 -225
  382. data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
  383. data/lib/hecks/runtime/registry.rb +103 -244
  384. data/lib/hecks/runtime/remote_dispatcher.rb +37 -152
  385. data/lib/hecks/runtime/routing.rb +4 -15
  386. data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
  387. data/lib/hecks/runtime/saga_interpreter.rb +79 -263
  388. data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
  389. data/lib/hecks/runtime/tenant_check.rb +6 -68
  390. data/lib/hecks/runtime/tenant_scope.rb +3 -28
  391. data/lib/hecks/runtime/value/admission.rb +16 -67
  392. data/lib/hecks/runtime/value/coercion.rb +155 -641
  393. data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
  394. data/lib/hecks/runtime/value.rb +12 -84
  395. data/lib/hecks/runtime.rb +17 -65
  396. data/lib/hecks/storehouse.rb +146 -567
  397. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +8 -32
  398. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
  399. data/lib/hecks/vendoring/git_environment.rb +2 -8
  400. data/lib/hecks/vendoring/git_source.rb +4 -10
  401. data/lib/hecks/vendoring.rb +7 -46
  402. data/lib/hecks/version.rb +3 -14
  403. data/lib/hecks/vocabulary.rb +5 -0
  404. data/lib/hecks.rb +32 -82
  405. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
  406. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
  407. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
  408. metadata +30 -59
  409. data/lib/hecks/bench/cli.rb +0 -74
  410. data/lib/hecks/bench/environment.rb +0 -100
  411. data/lib/hecks/bench/postgres_probe.rb +0 -41
  412. data/lib/hecks/bench/report.rb +0 -116
  413. data/lib/hecks/bench/ruby_runner.rb +0 -151
  414. data/lib/hecks/bench/run.rb +0 -55
  415. data/lib/hecks/bench/rust_runner.rb +0 -165
  416. data/lib/hecks/bench/stats.rb +0 -75
  417. data/lib/hecks/bench/suite.rb +0 -172
  418. data/lib/hecks/bench/workload.rb +0 -137
  419. data/lib/hecks/bench.rb +0 -28
  420. data/lib/hecks/codemod.rb +0 -443
  421. data/lib/hecks/corpus.rb +0 -479
  422. data/lib/hecks/doc/reference.rb +0 -585
  423. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -684
  424. data/lib/hecks/fuzzing/combination_miner.rb +0 -178
  425. data/lib/hecks/fuzzing/concurrent_dispatch.rb +0 -469
  426. data/lib/hecks/fuzzing/coverage_campaign.rb +0 -161
  427. data/lib/hecks/fuzzing/differential.rb +0 -192
  428. data/lib/hecks/fuzzing/domain_generator.rb +0 -871
  429. data/lib/hecks/fuzzing/era_boundary.rb +0 -154
  430. data/lib/hecks/fuzzing/form_census.rb +0 -285
  431. data/lib/hecks/fuzzing/generated_domain_check.rb +0 -171
  432. data/lib/hecks/fuzzing/invalid_value_generator.rb +0 -125
  433. data/lib/hecks/fuzzing/isolated_boot.rb +0 -658
  434. data/lib/hecks/fuzzing/nondeterministic.rb +0 -79
  435. data/lib/hecks/fuzzing/persistence_parity.rb +0 -253
  436. data/lib/hecks/fuzzing/properties/corrections.rb +0 -125
  437. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +0 -688
  438. data/lib/hecks/fuzzing/properties/guards.rb +0 -398
  439. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +0 -399
  440. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +0 -137
  441. data/lib/hecks/fuzzing/properties/outbox.rb +0 -180
  442. data/lib/hecks/fuzzing/properties/querying.rb +0 -260
  443. data/lib/hecks/fuzzing/properties.rb +0 -267
  444. data/lib/hecks/fuzzing/qa_settings.rb +0 -164
  445. data/lib/hecks/fuzzing/replay.rb +0 -930
  446. data/lib/hecks/fuzzing/rotation_priority.rb +0 -114
  447. data/lib/hecks/fuzzing/rust_gap_manifest.rb +0 -139
  448. data/lib/hecks/fuzzing/self_consistency.rb +0 -825
  449. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +0 -532
  450. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +0 -171
  451. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +0 -127
  452. data/lib/hecks/fuzzing/sequence_generator/picker.rb +0 -116
  453. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +0 -138
  454. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +0 -344
  455. data/lib/hecks/fuzzing/sequence_generator.rb +0 -311
  456. data/lib/hecks/fuzzing/shrinker.rb +0 -309
  457. data/lib/hecks/fuzzing/structural_skips.rb +0 -53
  458. data/lib/hecks/fuzzing/sweep_depth.rb +0 -61
  459. data/lib/hecks/fuzzing/target_capabilities.rb +0 -255
  460. data/lib/hecks/fuzzing/value_generator.rb +0 -340
  461. data/lib/hecks/fuzzing.rb +0 -23
  462. data/lib/hecks/grammar/evolve.rb +0 -524
  463. data/lib/hecks/query_ir.rb +0 -482
@@ -1,171 +0,0 @@
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 = []
12
- instance = []
13
- entity_commands = []
14
- queries = []
15
- entity_queries = []
16
- read_models = []
17
-
18
- runtime.registry.bluebooks.each do |domain_name, bluebook|
19
- bluebook.aggregates.each do |aggregate|
20
- aggregate.commands.each do |command|
21
- entry = { verb: "#{domain_name}::#{aggregate.hecks_name}.#{command.hecks_name}",
22
- command: command, aggregate: aggregate }
23
- (command.creates? ? creating : instance) << entry
24
- end
25
- aggregate.queries.each do |query|
26
- queries << { verb: "#{domain_name}::#{aggregate.hecks_name}.#{query.name}",
27
- query: query, aggregate: aggregate }
28
- end
29
- catalog_entities(domain_name, aggregate, entity_commands, entity_queries)
30
- end
31
-
32
- # **The bare domain form** — "Domain.report_name", no "::" — the
33
- # same shape `Dispatcher#query` itself branches on to route a
34
- # read-model ask apart from an aggregate query. A report was
35
- # never in this catalog at all before: `aggregation_matches_
36
- # recompute` (count/median) has only ever been exercised by
37
- # hand-built specs, never a single real generated sequence,
38
- # because nothing here ever asked one. `model:`, not
39
- # `aggregate:` — a rootless report has no aggregate of its
40
- # own to be eligible against (see picker.rb's own use of
41
- # `model.reference_target`).
42
- bluebook.read_models.each do |model|
43
- read_models << { verb: "#{domain_name}.#{model.query_name}", model: model }
44
- end
45
- end
46
-
47
- { creating: creating, instance: instance, entity_commands: entity_commands,
48
- queries: queries, entity_queries: entity_queries, read_models: read_models,
49
- populators: populators(runtime),
50
- # The aggregates a query filters on, and the fields it compares
51
- # (query_binding.rb).
52
- query_bindings: build_query_bindings(runtime),
53
- # Every `role "..."` any command in the boot declares — the
54
- # "wrong hat" pool the caller draw's `mismatched` shape picks
55
- # from (adversary.rb `other_role`).
56
- roles: (creating + instance + entity_commands).filter_map { |e| e[:command].role }
57
- .map(&:to_s).reject(&:empty?).uniq.sort,
58
- # The grant verb every loaded authorization provider declares
59
- # (`provides "authorization", grant: ...`) — what the caller
60
- # draw steers at a declared role and records as a real grant.
61
- # Read off the declaration, never the literal Governance name.
62
- grant_verbs: runtime.registry.authorization_providers
63
- .filter_map { |chapter| chapter.provided_verb(Bluebook::Capabilities::AUTHORIZATION, :grant) }
64
- .sort,
65
- # Which aggregates this corpus can actually make one of — the ones
66
- # `satisfiable?` is entitled to wait for.
67
- creatable: creating.to_set { |entry| entry[:aggregate].hecks_name } }
68
- end
69
-
70
- # Every entity, at every depth — `Card` nested inside `Board`
71
- # inside `Workspace` (qa/stress_domains/nested_pieces) walks in
72
- # as `chain: [Board, Card]`, the exact hop list
73
- # `EntityInterpreter::Resolution.of` resolves the dotted verb
74
- # back into. Before this walk existed the catalog only ever read
75
- # `aggregate.entities` one level down, so a two-hop entity
76
- # command (BUG#11's whole class) could never be generated at all
77
- # — the one shape the differential harness most needed to reach
78
- # was structurally absent from every sequence it ever produced.
79
- # `entity:` stays the last hop (what every existing reader means
80
- # by "the entity"); `chain:` is the whole path.
81
- #
82
- # Entity queries stay one hop deep, exactly as before — a nested
83
- # entity's query has no established wire spelling this generator
84
- # can vouch for, and nothing in the corpus declares one.
85
- def catalog_entities(domain_name, aggregate, entity_commands, entity_queries)
86
- each_entity_chain(aggregate) do |chain|
87
- entity = chain.last
88
- path = chain.map(&:hecks_name).join(".")
89
- entity.commands.each do |command|
90
- entity_commands << { verb: "#{domain_name}::#{aggregate.hecks_name}.#{path}.#{command.hecks_name}",
91
- command: command, aggregate: aggregate, entity: entity, chain: chain }
92
- end
93
- next unless chain.size == 1
94
-
95
- entity.queries.each do |query|
96
- entity_queries << { verb: "#{domain_name}::#{aggregate.hecks_name}.#{path}.#{query.name}",
97
- query: query, aggregate: aggregate, entity: entity }
98
- end
99
- end
100
- end
101
-
102
- # Depth-first, parents before children, in declaration order — so
103
- # the depth-1 entries land in `entity_commands` in exactly the
104
- # order they always did (a pinned seed's picker pool is the same
105
- # pool it was), and a nested entity's own entries follow its
106
- # parent's.
107
- def each_entity_chain(owner, chain = [], &block)
108
- owner.entities.each do |entity|
109
- path = chain + [entity]
110
- yield path
111
- each_entity_chain(entity, path, &block)
112
- end
113
- end
114
-
115
- # Which command, on which owner, appends to which entity list — so a
116
- # successful dispatch can predict the identity the element it just
117
- # added landed on. Entity#identified_by is filled by `Array(current).size + 1`
118
- # (CommandInterpreter#entity_element) when the append's own field
119
- # mapping doesn't already assign it — the common case, predicted here.
120
- # A domain whose append explicitly assigns identity through a mapped
121
- # argument is covered too, without guessing: whatever value this
122
- # generator supplied for that argument at dispatch time is the
123
- # identity, and gets recorded directly (see `record_outcome`).
124
- #
125
- # `owner_chain:` — `[]` for an aggregate-level append (`Board.AddList`,
126
- # `Folder.AddSlip`), the entity path for an entity-level one
127
- # (`Board.AddCard` appending into `Board.cards`, owner_chain
128
- # `[Board]`) — so `record_outcome` can key the appended element
129
- # under the exact parent-plus-hops it landed beneath.
130
- # `identity_arguments:` — every identity head the mapping sources
131
- # from a command argument (a composite entity identity has several),
132
- # what the adversarial duplicate-identity mutation replays;
133
- # `identity_argument:` stays the single-head reading the existing
134
- # auto-mint prediction already keys on.
135
- def populators(runtime)
136
- runtime.registry.bluebooks.each_value.flat_map do |bluebook|
137
- bluebook.aggregates.flat_map do |aggregate|
138
- owners = [[aggregate, []]]
139
- each_entity_chain(aggregate) { |chain| owners << [chain.last, chain] }
140
-
141
- owners.flat_map do |owner, chain|
142
- owner.commands.filter_map { |command| populator_for(aggregate, owner, chain, command) }
143
- end
144
- end
145
- end
146
- end
147
-
148
- def populator_for(aggregate, owner, chain, command)
149
- append = command.mutations.find { |mutation| mutation.op == :append }
150
- return unless append
151
-
152
- list_attribute = owner.attribute(append.target)
153
- return unless list_attribute&.list?
154
-
155
- entity = owner.entities.find { |candidate| candidate.hecks_name == list_attribute.type.to_s }
156
- return unless entity
157
-
158
- identity_field = entity.identified_by
159
- mapped = identity_field && append.source[identity_field]
160
- identity_arguments = entity.identity_heads.filter_map do |head|
161
- source = append.source[head]
162
- source if source.is_a?(Symbol)
163
- end
164
- { command: command, aggregate: aggregate, owner: owner, owner_chain: chain, entity: entity,
165
- identity_field: identity_field, identity_argument: mapped.is_a?(Symbol) ? mapped : nil,
166
- identity_arguments: identity_arguments }
167
- end
168
- end
169
- end
170
- end
171
- end
@@ -1,127 +0,0 @@
1
- require_relative "../value_generator"
2
- require_relative "../../naming"
3
-
4
- module Hecks
5
- module Fuzzing
6
- class SequenceGenerator
7
- # What a successful step created, and how a later step draws on it —
8
- # the state tracking that makes multi-step sequences reach further
9
- # than single calls ever did.
10
- module OutcomeTracker
11
- private
12
-
13
- def record_outcome(catalog, entry, args)
14
- aggregate = entry[:aggregate]
15
- parent_scalar = identity_scalar_of(aggregate, args)
16
-
17
- @known_ids[aggregate.hecks_name] << parent_scalar if entry[:entity].nil? && entry[:command].creates?
18
- record_grant(args) if catalog[:grant_verbs].include?(entry[:verb])
19
-
20
- populator = populator_for_entry(catalog, entry)
21
- return unless populator
22
-
23
- key = append_pool_key(populator, args)
24
-
25
- # The auto-minted case (CommandInterpreter#entity_element): the
26
- # element just landed at count-so-far + 1. The explicit case: this
27
- # generator itself supplied whatever value sits at
28
- # args[identity_argument] — no guessing needed, it's read straight
29
- # back.
30
- new_id =
31
- if populator[:identity_argument]
32
- ValueGenerator.scalar_of(args[populator[:identity_argument].to_s])
33
- else
34
- (@entity_known_ids[key].size + 1).to_s
35
- end
36
- @entity_known_ids[key] << new_id
37
-
38
- # The caller-supplied identity tuple, kept whole — every mapped
39
- # identity argument and the exact value this step offered for it
40
- # — so the adversarial duplicate-identity mutation (BUG#13's own
41
- # shape, `Folder.AddSlip` twice under the same `reference`) can
42
- # offer it again later against the same parent, composite
43
- # identities included (BUG#13's fix explicitly did not cover
44
- # those; this is how a sequence gets to ask).
45
- return if populator[:identity_arguments].empty?
46
-
47
- @appended_identities[key] << populator[:identity_arguments].to_h { |name| [name.to_s, args[name.to_s]] }
48
- end
49
-
50
- # A grant this sequence made for real — the authorization provider's
51
- # declared `grant:` verb (`Governance::RoleAssignment.Assign` in
52
- # every boot today) succeeded with these exact args, so `actor_id` now holds
53
- # `role_name` in this boot's own store, on both replay sides. The
54
- # `actor_known` caller shape (adversary.rb) replays that identity
55
- # against a command gated on the same role: the one way a
56
- # generated step reaches `holds_role?`'s authorized branch rather
57
- # than only its refusing one.
58
- def record_grant(args)
59
- role = ValueGenerator.scalar_of(args["role_name"]).to_s
60
- actor = ValueGenerator.scalar_of(args["actor_id"]).to_s
61
- @granted[role] << actor unless role.empty? || actor.empty?
62
- end
63
-
64
- # The pool an appended element lands in — the aggregate's own
65
- # identity, then one scalar per owning hop (an aggregate-level
66
- # append has none; `Board.AddCard` has `Board`'s own `number`,
67
- # read straight back off the args this step addressed it by).
68
- def append_pool_key(populator, args)
69
- parent_scalar = identity_scalar_of(populator[:aggregate], args)
70
- owner_scalars = populator[:owner_chain].map do |piece|
71
- ValueGenerator.scalar_of(args[(piece.identified_by || :id).to_s])
72
- end
73
- entity_pool_key(populator[:aggregate].hecks_name,
74
- populator[:owner_chain].map(&:hecks_name) + [populator[:entity].hecks_name],
75
- [parent_scalar] + owner_scalars)
76
- end
77
-
78
- # `"Agg.Board#w1"` for a depth-1 pool — byte-identical to the key
79
- # this always used, so nothing a pinned seed draws from moves —
80
- # and `"Agg.Board.Card#w1/1"` one hop deeper.
81
- def entity_pool_key(aggregate_name, chain_names, scalars)
82
- "#{aggregate_name}.#{chain_names.join('.')}##{scalars.join('/')}"
83
- end
84
-
85
- # The scalar this step's own aggregate identity resolves to, from the
86
- # args a creating (or entity-populating) command actually
87
- # dispatched — a single declared head (or the untyped default
88
- # `:id`) reads straight off its own top-level arg the way this
89
- # always has (`ValueGenerator.scalar_of`, opening the identity value
90
- # object). A genuinely composite identity (`composite_identity?`,
91
- # shared with StepBuilder#add_identity! — see its own comment) has
92
- # no such top-level arg at all: `add_identity!` deliberately leaves
93
- # a composite creating command's own individually-declared fields
94
- # alone rather than forcing a synthetic `id` neither the bluebook
95
- # nor the command ever declared, so this joins those fields itself,
96
- # in the same declaration order `Runtime::Identity.of` joins them
97
- # at dispatch (`Naming.identity`, `Naming::IDENTITY_JOIN`) — which
98
- # is exactly what makes the result usable later as the bare `id:`
99
- # a composite Revoke/act-again command expects (`Identity.from`'s
100
- # own untyped fallback, reached when `identity_of` finds no
101
- # top-level field args to join, reads a bare `id:` straight
102
- # through unattributed).
103
- def identity_scalar_of(aggregate, args)
104
- parent_key = (aggregate.identified_by || :id).to_s
105
- return ValueGenerator.scalar_of(args[parent_key]) unless composite_identity?(aggregate)
106
-
107
- parts = aggregate.identity_paths.map { |path| ValueGenerator.scalar_of(args[path.to_s.split(".").first]) }
108
- Naming.identity(parts)
109
- end
110
-
111
- def pick_known(name)
112
- pool = @known_ids[name]
113
- return ValueGenerator.random_id(@random) if pool.empty? || @random.rand < ValueGenerator::INVALID_REFERENCE_PROBABILITY
114
-
115
- pool.sample(random: @random)
116
- end
117
-
118
- def pick_entity_known(key)
119
- pool = @entity_known_ids[key]
120
- return ValueGenerator.random_id(@random) if pool.empty? || @random.rand < ValueGenerator::INVALID_REFERENCE_PROBABILITY
121
-
122
- pool.sample(random: @random)
123
- end
124
- end
125
- end
126
- end
127
- end
@@ -1,116 +0,0 @@
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
- pool += deep_entity_bias(rest) if adversarial?
20
-
21
- steer(pool).sample(random: @random)
22
- end
23
-
24
- # BUG#11's own preference (adversary.rb) — an entity command two
25
- # or more hops deep is weighted up the same way an unexercised
26
- # verb is, only in adversarial mode: the eligibility rules above
27
- # still decide what is possible, and a default-mode pool is
28
- # exactly the pool it always was.
29
- def deep_entity_bias(rest)
30
- deep = rest.select { |entry| (entry[:chain] || []).size >= Adversary::DEEP_ENTITY_DEPTH }
31
- deep * Adversary::DEEP_ENTITY_WEIGHT
32
- end
33
-
34
- # While there is nothing to find, making something is the only useful move.
35
- #
36
- # A flat weight is right once the domain has records in it, and badly
37
- # wrong before: banking declares ten queries, and from an empty store
38
- # exactly one creating command is satisfiable — Customer.Register, the
39
- # verb the whole cascade waits on. Two entries against ten left runs
40
- # spending their entire budget querying a store nothing had been written
41
- # to ; one seed answered ten empty queries in a row and emitted nothing
42
- # at all.
43
- #
44
- # So while nothing exists, creation matches everything else put together.
45
- # After that the ordinary weight applies and the run gets on with
46
- # exercising what it made.
47
- def creating_weight(rest_size)
48
- return CREATING_WEIGHT if @known_ids.each_value.any?(&:any?)
49
-
50
- [rest_size, CREATING_WEIGHT].max
51
- end
52
-
53
- def actionable?(catalog, entry)
54
- @known_ids[entry[:aggregate].hecks_name].any? && satisfiable?(catalog, entry)
55
- end
56
-
57
- # A rootless report (no `reference_to` at all) has nothing to wait
58
- # for — it reads whole tables, the same "always eligible" position
59
- # `catalog[:queries]` itself takes. A rooted one needs a real
60
- # instance of its own `reference_target` to ask about first, same
61
- # rule `actionable?` already gives an instance command — asking
62
- # `Banking.disputed_payment_count` before any Account exists would
63
- # just be refused (`refuse_object_reference`/`NotFound`) the same
64
- # way `Account.Credit` would be, for the identical reason.
65
- def read_model_actionable?(entry)
66
- entry[:model].reference_target.nil? || @known_ids[entry[:model].reference_target].any?
67
- end
68
-
69
- # A command that references nothing that exists cannot succeed, so it is
70
- # not offered until something does.
71
- #
72
- # `ValueGenerator.reference_value` has no real id to hand over when its
73
- # pool is empty, so it mints `missing-…` and the dispatch is refused
74
- # before it starts — a guaranteed waste of a step, and worse, one that
75
- # never fills the pool it was waiting on. Banking cascades from it:
76
- # Account.Open needs a Customer, ATMCard.Issue needs an Account,
77
- # Transfer.Request needs two, so a run that opened with the wrong verb
78
- # spent its whole budget being refused.
79
- #
80
- # This is the rule instance commands already follow — `known_ids.any?` —
81
- # applied to what a command references rather than to what it acts on.
82
- # A target no creating command in this corpus can make (a cross-domain
83
- # reference, which `CommandRules#resolve_references` skips anyway) is
84
- # exempt, or the whole domain would starve waiting for it.
85
- def satisfiable?(catalog, entry)
86
- entry[:command].attributes.select(&:reference?).all? do |attribute|
87
- target = attribute.type.target_name.to_s
88
- !catalog[:creatable].include?(target) || @known_ids[target].any?
89
- end
90
- end
91
-
92
- # Coverage-guided rather than uniformly random : a verb this sequence has
93
- # not dispatched yet is weighted up, so a run spends its budget on the
94
- # commands it has not reached instead of re-rolling the ones it has. The
95
- # eligibility rules above still decide what is possible — this only decides
96
- # what is likely, so a verb gated behind state it does not have yet stays
97
- # out of the pool entirely rather than being preferred forever.
98
- def steer(pool)
99
- fresh = pool.reject { |entry| @exercised.include?(entry[:verb]) }
100
- steered = fresh.empty? ? pool : pool + (fresh * UNEXERCISED_WEIGHT)
101
- favor(steered)
102
- end
103
-
104
- # `favor:` (CoverageCampaign's rare verbs) — weighted up when
105
- # eligible, never made eligible. An empty favor list returns the
106
- # pool untouched, so an unguided seed draws exactly what it did.
107
- def favor(pool)
108
- return pool if @favor.empty?
109
-
110
- favored = pool.uniq.select { |entry| @favor.include?(entry[:verb]) }
111
- pool + (favored * SequenceGenerator::FAVOR_WEIGHT)
112
- end
113
- end
114
- end
115
- end
116
- end
@@ -1,138 +0,0 @@
1
- require_relative "../../query_specification/field_path"
2
- require_relative "../../runtime/value"
3
-
4
- module Hecks
5
- module Fuzzing
6
- class SequenceGenerator
7
- # Query arguments drawn from what the sequence has already written.
8
- #
9
- # A query is only a real test of an adapter when its argument names a row
10
- # that exists. A `where(site_reference: :site_reference)` asked with a
11
- # freshly generated word matches nothing on every adapter, so an adapter
12
- # that compiles the comparison wrongly (and so also matches nothing) is
13
- # indistinguishable from a correct one. Every argument `args_for` draws
14
- # is independent of the store, and the chance that one lands on a stored
15
- # value is small enough that a whole persistence-parity sweep can pass
16
- # without a single query returning a row.
17
- #
18
- # ## What is written, and what is bound
19
- #
20
- # After each command that took effect, every aggregate a query filters
21
- # on is read back from the repository, and the values of exactly the
22
- # fields those queries compare (`bound_fields`) are remembered as one
23
- # row per record. A row is what the store holds, not what a command was
24
- # given, so a value a `sets` mapping renamed, a policy wrote in another
25
- # aggregate, or a lifecycle transition assigned is covered too. A later
26
- # query step usually takes its arguments from one remembered row, so
27
- # several parameters filter the same record. The rest of the time it
28
- # keeps the generated argument, which is what reaches the empty answer.
29
- #
30
- # ## Why a separate random stream
31
- #
32
- # The choice draws from its own `Random`, seeded from the sequence's
33
- # seed and restarted wherever `@random` is (`restart_random`). Drawing
34
- # from `@random` would shift every later step of every seed. This way
35
- # each seed generates the same commands, in the same order, with the
36
- # same arguments as before, and only the arguments of a query step
37
- # change. A query changes no state, so nothing downstream moves.
38
- module QueryBinding
39
- # How often a query step whose aggregate has written rows takes its
40
- # arguments from one of them. High, because a query that misses a
41
- # stored row tests nothing, and the miss is still drawn the other
42
- # quarter of the time.
43
- BOUND_QUERY_PROBABILITY = 0.75
44
-
45
- # Comparators whose argument is one scalar or value, so a stored value
46
- # is a legitimate operand. `in` takes a list and `none_in_state` an
47
- # `"Aggregate:state"` string; a stored value is neither.
48
- BINDABLE_OPERATORS = %i[eq ne lt lte gt gte].freeze
49
-
50
- # Keeps the binding stream apart from `@random`'s, which is seeded
51
- # with the bare seed.
52
- BINDING_SEED_OFFSET = 0x9e3779b1
53
-
54
- private
55
-
56
- # `{"Domain::Aggregate" => {domain:, aggregate:, fields: [String]}}`
57
- # for every aggregate some query filters on, where `fields` is the
58
- # union of the stored fields those queries compare.
59
- def build_query_bindings(runtime)
60
- runtime.registry.bluebooks.each_with_object({}) do |(domain_name, bluebook), bindings|
61
- bluebook.aggregates.each do |aggregate|
62
- fields = aggregate.queries.flat_map { |query| bound_fields(query).values }.uniq
63
- next if fields.empty?
64
-
65
- bindings["#{domain_name}::#{aggregate.hecks_name}"] =
66
- { domain: domain_name, aggregate: aggregate, fields: fields }
67
- end
68
- end
69
- end
70
-
71
- # `{parameter name => stored field}` for each `where` clause whose
72
- # operand is one of the query's own parameters. A clause on a literal,
73
- # or aimed at another aggregate's field (`target`), binds nothing.
74
- def bound_fields(query)
75
- query.wheres.each_with_object({}) do |clause, fields|
76
- next unless clause.value.is_a?(Symbol) && clause.target.nil?
77
- next unless BINDABLE_OPERATORS.include?(clause.op.to_sym)
78
-
79
- fields[clause.value.to_s] ||= clause.field.to_s
80
- end
81
- end
82
-
83
- # Remembers the compared fields of every stored record of every
84
- # aggregate a query filters on, as one row per distinct record shape.
85
- def harvest_written_rows(runtime, catalog)
86
- catalog[:query_bindings].each do |key, binding|
87
- repository = runtime.registry.repository(binding[:domain], binding[:aggregate])
88
- repository.all.each do |record|
89
- row = binding[:fields].to_h { |field| [field, written_value(field_of(record, field))] }.compact
90
- @written_rows[key] << row unless row.empty? || @written_rows[key].include?(row)
91
- end
92
- end
93
- end
94
-
95
- def field_of(record, field) = QuerySpecification::FieldPath.dig(record.state, field)
96
-
97
- # A stored value as the JSON-shaped argument a step carries: string
98
- # keys, nested values opened. `nil` for anything a query argument
99
- # cannot be (a missing value, a list, a timestamp object).
100
- def written_value(value)
101
- case value
102
- when Runtime::Value then written_value(value.to_h)
103
- when Hash then value.to_h { |key, inner| [key.to_s, written_value(inner)] }
104
- when String, Integer, Float, true, false then value
105
- end
106
- end
107
-
108
- # Replaces the generated arguments of a query step with a remembered
109
- # row's, most of the time. Only a parameter the step already carries
110
- # is replaced, so an optional argument left out, or a required one a
111
- # malformation dropped, stays that way.
112
- def bind_to_written_row!(args, entry)
113
- return if entry[:entity]
114
-
115
- rows = @written_rows[entry[:verb].split(".").first]
116
- params = bound_fields(entry[:query]).select { |param, _| args.key?(param) }
117
- return if rows.empty? || params.empty?
118
- return unless @binding_random.rand < BOUND_QUERY_PROBABILITY
119
-
120
- row = rows.select { |candidate| params.values.any? { |field| candidate.key?(field) } }
121
- .sample(random: @binding_random)
122
- params.each do |param, field|
123
- args[param] = shaped_like(args[param], row[field]) if row&.key?(field)
124
- end
125
- end
126
-
127
- # A single-field value object is written `{"value" => x}` but may be
128
- # asked with the bare `x`; keeps whichever spelling the generator
129
- # already chose for this argument.
130
- def shaped_like(current, written)
131
- return written unless written.is_a?(Hash) && written.size == 1 && !current.is_a?(Hash)
132
-
133
- written.values.first
134
- end
135
- end
136
- end
137
- end
138
- end