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,180 +0,0 @@
1
- require_relative "../../bluebook/expression/evaluator"
2
-
3
- module Hecks
4
- module Fuzzing
5
- module Properties
6
- # The transactional outbox's own contract (`Runtime::Outbox`, that
7
- # file's own header), held to the history a replay actually produced
8
- # rather than trusted. Two facts, each independently checkable from
9
- # `history[:outbox_traces]` (`Replay#call`'s own before/after
10
- # capture, one entry per step whose dispatch enqueued at least one
11
- # row):
12
- #
13
- # ## The two checks
14
- #
15
- # 1. "delivery is inline by default" — a row this replay's own
16
- # dispatch enqueued must not still be `pending`/`claimed` once
17
- # that same call returns (nothing here ever simulates a
18
- # crash), and must not be `failed` either — `deliver_row`'s
19
- # own rescue only reaches `failed` for a genuine defect in the
20
- # relay's own consumer resolution (a row naming a policy/
21
- # process_manager `run_consumer`'s own independent registry
22
- # lookup cannot find — `WiringError`), never an ordinary
23
- # domain refusal (`PolicyInterpreter#deliver`/`SagaInterpreter#
24
- # advance` both rescue those themselves, recording `delivered:
25
- # false` on the reaction/saga log and letting `run_consumer`
26
- # return normally). Both checked for every row, `saga:` and
27
- # `policy:` alike.
28
- #
29
- # 2. A `policy:` row specifically — `PolicyInterpreter#deliver`
30
- # returns `nil` (no `reaction_log` entry appended at all)
31
- # exactly when its own `where` (or, for a fan-out policy, the
32
- # same `where`, gating the whole `for_each`) does not hold;
33
- # every other outcome (delivered, refused, a defect,
34
- # reaction-depth-reached) is still a non-nil record `#react`
35
- # appends. So a `delivered` policy row with no matching
36
- # `reaction_log` entry is legitimate only when that policy's
37
- # own `where`, independently re-evaluated here against the
38
- # row's own recorded event, genuinely does not hold. A
39
- # `for_each` policy's own fan-out correctness (how many rows
40
- # it should have dispatched to) is `fanout_dispatches_once_
41
- # per_matching_row`'s job, not this one's.
42
- #
43
- # ## Why a `saga:` row is exempt from check 2
44
- #
45
- # Deliberately: `Fanout.sagas`' own `listens?` (starts_on/ends_on/
46
- # handler_for matching the event name alone) says nothing about
47
- # whether a correlation resolves or a live instance exists, and
48
- # `begin_saga`/`end_saga` (saga_interpreter.rb) both have silent,
49
- # perfectly ordinary no-op paths that append nothing to `saga_log`
50
- # — `begin_saga` when an instance under that correlation already
51
- # exists, `end_saga` when no live instance exists to end (an
52
- # `AccountOpened` fired by opening an account directly, bypassing
53
- # the onboarding flow whose `ends_on` names that same event,
54
- # reproduces this exactly: `Fanout.listens?` enqueues the row
55
- # because the event name matches `ends_on`, `end_saga` finds
56
- # nothing under that correlation to delete, and neither logs a
57
- # word). A `saga:` row draining to `delivered` with zero matching
58
- # `saga_log` entries is therefore not a finding — only check 1
59
- # applies to it.
60
- #
61
- # ## Not a grammar construct
62
- #
63
- # `FEATURE_COVERAGE`'s own `dry_runs_leave_no_trace` precedent: the
64
- # outbox is a runtime door (`Runtime::Outbox`), not a word a
65
- # bluebook declares, so there is no feature string here to claim.
66
- module Outbox
67
- # Checks every outbox row a replay's history recorded against the
68
- # outbox's own contract (see this file's own header).
69
- #
70
- # @param history [Hash] a replayed history, as returned by `Fuzzing::Replay.call`
71
- # @return [true, String] true if every outbox row satisfies the contract; otherwise
72
- # a semicolon-joined message naming each offending row
73
- def outbox_rows_match_reactions(history)
74
- bluebooks = history.fetch(:bluebooks, {})
75
-
76
- offenders = Array(history[:outbox_traces]).flat_map do |trace|
77
- trace[:rows].flat_map { |row| outbox_row_offenders(row, trace, bluebooks) }
78
- end
79
-
80
- offenders.empty? || offenders.join("; ")
81
- end
82
-
83
- # Checks one outbox row against the outbox's own contract.
84
- #
85
- # @param row [Hash] one `Runtime::Outbox::Row#to_h` entry, `trace[:rows]`'s shape:
86
- # at least `:status`, `:delivery_id`, `:consumer`, `:event`, and (when failed)
87
- # `:error`
88
- # @param trace [Hash] this row's outbox trace entry, one of `history[:outbox_traces]`,
89
- # carrying `:rows` and `:reactions`
90
- # @param bluebooks [Hash{String => Bluebook::Chapter}] every loaded domain,
91
- # keyed by domain name
92
- # @return [Array<String>] zero or one offending message naming what this row got
93
- # wrong; empty when the row is fine or its status names no check
94
- def outbox_row_offenders(row, trace, bluebooks)
95
- on = row.dig(:event, :name)
96
-
97
- case row[:status]
98
- when "pending", "claimed"
99
- ["outbox row #{row[:delivery_id]} (#{row[:consumer]} on #{on}) never drained inline — status stayed " \
100
- "#{row[:status].inspect} though delivery is inline by contract (Runtime::Outbox's own header)"]
101
- when "failed"
102
- ["outbox row #{row[:delivery_id]} (#{row[:consumer]} on #{on}) failed to deliver: #{row[:error]} — " \
103
- "a domain refusal never reaches this far; a failed row names a defect in the relay's own consumer " \
104
- "resolution"]
105
- when "delivered"
106
- outbox_delivered_policy_offenders(row, on, trace, bluebooks)
107
- else
108
- []
109
- end
110
- end
111
-
112
- # Checks one delivered `policy:` outbox row for a missing `reaction_log` entry.
113
- #
114
- # See this file's own header for why a `saga:` row is exempt: its
115
- # own `listens?` gives no such guarantee, unlike a policy's single,
116
- # deterministic `where` gate.
117
- #
118
- # @param row [Hash] the delivered outbox row being checked
119
- # @param on [String] the row's event name, `row.dig(:event, :name)`
120
- # @param trace [Hash] this row's outbox trace entry, carrying `:reactions`
121
- # @param bluebooks [Hash{String => Bluebook::Chapter}] every loaded domain,
122
- # keyed by domain name
123
- # @return [Array<String>] zero or one offending message; empty when the row is
124
- # not a policy row, the policy is undeclared or fans out, or the policy's
125
- # `where` does not independently re-evaluate true
126
- def outbox_delivered_policy_offenders(row, on, trace, bluebooks)
127
- kind, fqn = row[:consumer].to_s.split(":", 2)
128
- return [] unless kind == "policy"
129
-
130
- home, name = fqn.to_s.split("::", 2)
131
- return [] if trace[:reactions].any? { |entry| entry[:policy] == name && entry[:on] == on }
132
-
133
- policy = bluebooks[home]&.policies&.find { |candidate| candidate.name == name }
134
- # Nothing declared under this name — inconclusive, not a claimed mismatch.
135
- return [] unless policy
136
- # Fan-out row count is fanout_dispatches_once_per_matching_row's job.
137
- return [] if policy.fans_out?
138
-
139
- held = independently_re_evaluate_policy_where(policy, row[:event])
140
- # False, or inconclusive (the where itself raised) — never a claimed mismatch.
141
- return [] if held != true
142
-
143
- ["outbox row #{row[:delivery_id]} (#{row[:consumer]} on #{on}) drained as delivered, but no matching " \
144
- "reaction_log entry exists and the policy's own where clause independently re-evaluates true — " \
145
- "PolicyInterpreter#deliver only ever returns nil (no reaction_log entry) when where does not hold"]
146
- end
147
-
148
- # `PolicyInterpreter#where_holds?`'s own two branches, reproduced —
149
- # never calling that method again, which would only ever agree
150
- # with itself (the same rule `resolve_dispatch_binding`'s own
151
- # comment states). `Evaluator.call` (the raw-string entry, parsed
152
- # and cached — never `call_rule`, which needs the policy's own
153
- # build-time `where_rule` AST, an object this history has no
154
- # reason to carry) is the exact same call `Replay#fan_out_finding`
155
- # already makes for the identical fact one property over
156
- # (`policy.where.to_s.empty? || Evaluator.call(policy.where, {},
157
- # payload)`), reused rather than re-derived a second, slightly
158
- # different way. `rescue`d to `nil`, not `false`: a where clause
159
- # that cannot be re-evaluated from the row's own recorded payload
160
- # alone is inconclusive, not proof either way — the same "never a
161
- # claimed pass or a claimed mismatch from a resolution this replay
162
- # cannot actually reproduce" discipline `build_guard_check`'s own
163
- # rescue clause already follows.
164
- #
165
- # @param policy [Bluebook::Policy] the policy whose `where` clause is re-evaluated
166
- # @param event [Hash] the outbox row's own recorded event, read for `:payload`
167
- # @return [Boolean, nil] whether `policy`'s `where` holds against `event`'s payload,
168
- # or nil if the where clause cannot be re-evaluated from it
169
- def independently_re_evaluate_policy_where(policy, event)
170
- return true if policy.where.to_s.empty?
171
-
172
- payload = (event[:payload] || {}).transform_keys(&:to_sym)
173
- Bluebook::Expression::Evaluator.call(policy.where, {}, payload)
174
- rescue StandardError
175
- nil
176
- end
177
- end
178
- end
179
- end
180
- end
@@ -1,260 +0,0 @@
1
- module Hecks
2
- module Fuzzing
3
- module Properties
4
- # Query-answering properties: a query's own answer matches a from-
5
- # scratch reference recomputation, paging partitions its rows the same
6
- # way, and the shared query/paging helpers both lean on.
7
-
8
- # Holds query_answers_match_reference and paging_offset_partitions_correctly,
9
- # plus the shared verb-resolution and eligible-rows/hop-chain helpers
10
- # (#query_for_verb, #query_eligible_rows, #resolve_hop_clause) other
11
- # property modules in this directory also call.
12
- module Querying
13
- # The query oracle — differential testing within the one runtime,
14
- # the shape the retired cross-runtime harness should always have
15
- # been. Every generated ask was answered twice at the same instant
16
- # (Replay records both): once through whatever the aggregate is
17
- # actually bound to (Memory's native hook is Ports::Query::InMemory;
18
- # a SQL binding would compile it), once through the reference
19
- # interpreter's own evaluation. The two are separate, live
20
- # implementations of the same comparator vocabulary, and they have
21
- # drifted before — an adapter that accepts what the reference says
22
- # matches nothing, or orders what it refuses to order, shows up
23
- # here as a finding no self-referential adapter spec could see.
24
- # M23 — `Replay` runs the native and reference engines independently
25
- # (each in its own begin/rescue — see that file's own comment at the
26
- # capture site), so this property can tell apart "both engines
27
- # refused" (fine — the ask was genuinely bad, nothing to compare)
28
- # from "one refused and the other did not" (a real divergence — the
29
- # two engines disagree about whether the ask was even valid, never
30
- # mind what it answers). `native_refused`/`reference_refused` are
31
- # read by key presence, not truthiness — `Replay` only ever adds
32
- # `:error`/`:reference_error` to an entry when that side actually
33
- # raised, so an absent key is an unambiguous "this side answered." A
34
- # read-model ask (no reference twin attempted at all, `asked[:query]`
35
- # without "::") is skipped entirely, same as always — there is no
36
- # second engine to disagree with.
37
- #
38
- # @param history [Hash] a replayed history as returned by `Replay.call`
39
- # @return [true, String] true if every native/reference query answer, and every
40
- # refusal of one, agrees; otherwise a message naming the offending query, its
41
- # args, and how the two engines disagreed
42
- def query_answers_match_reference(history)
43
- offenders = history.fetch(:queries).filter_map do |asked|
44
- next unless asked[:query].is_a?(String) && asked[:query].include?("::")
45
-
46
- native_refused = asked.key?(:error)
47
- reference_refused = asked.key?(:reference_error)
48
-
49
- if native_refused != reference_refused
50
- next "#{asked[:query]} #{asked[:args].inspect} — native " \
51
- "#{native_refused ? "refused (#{asked[:error]})" : 'answered'}, " \
52
- "but the reference interpreter #{reference_refused ? "refused (#{asked[:reference_error]})" : 'answered'} — " \
53
- "a refusal-shaped divergence, not just a differing row set"
54
- end
55
-
56
- next if native_refused
57
- next if asked[:rows] == asked[:reference_rows]
58
-
59
- "#{asked[:query]} #{asked[:args].inspect} answered #{asked[:rows].inspect} " \
60
- "natively but #{asked[:reference_rows].inspect} through the reference interpreter"
61
- end
62
-
63
- offenders.empty? || offenders.join("; ")
64
- end
65
-
66
- # The same "two engines, compared" shape query_answers_match_reference
67
- # already uses, aimed squarely at Query#options' offset/limit pair —
68
- # but recomputed from history[:instances] directly, a third,
69
- # independent computation, rather than comparing QueryInterpreter's
70
- # own native and reference paths against each other (which could
71
- # share the identical bug neither implementation happened to hit —
72
- # see #4's own fix, which touched both #interpret and
73
- # #reference_interpret at once). `order_by` declared alongside
74
- # `offset` or `limit` names a genuinely paged query. Ports::Query::
75
- # Ordering.apply is the same engine QueryInterpreter#ordered calls,
76
- # reused here rather than re-derived, so this oracle cannot drift
77
- # from what "in order" means without the interpreter drifting the
78
- # identical way — only the offset-then-limit .drop/.first slice
79
- # (#4's own fix) is independently reproduced, in plain Ruby.
80
- #
81
- # Real target: ATMCard.ByFee (`limit 3; offset 1`).
82
- #
83
- # One independent recomputation, order-dependent by construction
84
- # (declared query -> eligible rows -> ordered -> offset-sliced ->
85
- # limit-sliced -> compared against the real answer, per the doc
86
- # comment above): splitting it would scatter `rows`/`ordered`/
87
- # `skipped`/`expected` across method boundaries as params/returns
88
- # for a sequence that's only ever computed once, in this order.
89
- # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
90
- #
91
- # @param history [Hash] a replayed history as returned by `Replay.call`
92
- # @return [true, String] true if every paged query's real answer matches an
93
- # independent order/offset/limit recomputation over its eligible rows;
94
- # otherwise a message naming the query, its args, and the two answers
95
- def paging_offset_partitions_correctly(history)
96
- bluebooks = history.fetch(:bluebooks)
97
-
98
- offenders = history.fetch(:queries).filter_map do |asked|
99
- next if asked[:error] || !asked[:query].is_a?(String) || !asked[:query].include?("::")
100
-
101
- declared = query_for_verb(bluebooks, asked[:query])
102
- next unless declared&.order_by && (declared.offset || declared.limit)
103
-
104
- domain, aggregate_name, = Naming.split_verb(asked[:query])
105
- args = asked[:args] || {}
106
- rows = query_eligible_rows(asked.fetch(:instances_at), domain, aggregate_name, declared.wheres, args,
107
- bluebooks: bluebooks)
108
- ordered = Ports::Query::Ordering.apply(
109
- rows, declared.order_by, declared.null_semantics, identity: ->(row) { row[:id].to_s }
110
- ) { |row| Ports::Query::InMemory.comparable(QuerySpecification::FieldPath.dig(row, declared.order_by.field)) }
111
-
112
- skipped = declared.offset ? ordered.drop(resolve_paging_value(declared.offset.value, args).to_i) : ordered
113
- expected = declared.limit ? skipped.first(resolve_paging_value(declared.limit.value, args).to_i) : skipped
114
- actual = asked[:rows]
115
- next if actual == expected
116
-
117
- "#{asked[:query]} #{args.inspect} answered #{actual.inspect}, but independently recomputing " \
118
- "order/offset/limit from #{rows.length} eligible row(s) gives #{expected.inspect}"
119
- end
120
-
121
- offenders.empty? || offenders.join("; ")
122
- end
123
-
124
- # The declared Query itself, resolved from a replayed verb — the
125
- # same shape #command_for_verb resolves a command by, one
126
- # construct over. Entity-level queries (a dotted query_path) are
127
- # out of scope here — paging on an entity's own list has no real
128
- # corpus site yet, and the "one many-side head, one aggregate,
129
- # no FK-join" shape #query_eligible_rows assumes doesn't hold for
130
- # one.
131
- #
132
- # @param bluebooks [Hash{String => Bluebook::Chapter}] every loaded domain,
133
- # keyed by domain name
134
- # @param verb [String] the asked verb, `"Domain::Aggregate.Query"`
135
- # @return [Bluebook::Query, nil] the declared query, or nil if `verb` names an
136
- # entity-level query path, or a domain, aggregate, or query not found among
137
- # `bluebooks`
138
- def query_for_verb(bluebooks, verb)
139
- domain, aggregate_name, query_path = Naming.split_verb(verb)
140
- return nil unless query_path && !query_path.include?(".")
141
-
142
- bluebook = bluebooks[domain]
143
- aggregate = bluebook&.aggregate(aggregate_name)
144
- aggregate&.query(query_path)
145
- end
146
-
147
- # Independently recomputes which of an aggregate's own stored rows a
148
- # query's `wheres` admit.
149
- #
150
- # A query's own rows — unlike #eligible_rows (a ReadModel's
151
- # reduced/grouped many-side head, possibly FK-joined against a
152
- # root), a Query always asks about its own owning aggregate
153
- # directly ; no join, no reference_target. `id:` merged in the
154
- # same way #eligible_rows' own rows are, since a stable sort
155
- # (Ordering.apply's own `identity:`) and the real answer's own
156
- # `record.state.merge(id: record.id)` both need it.
157
- # `bluebooks:` — needed only to recognise and resolve a `/` hop
158
- # clause (`engagement/client/status`, hop_chain.bluebook's own
159
- # PricedAboveViaEngagement): a hop's head names one of the owning
160
- # aggregate's declared references, and only the declaration graph
161
- # can say which attribute that is and which aggregate it targets.
162
- # A local clause never consults it. Latent gap this closed, found
163
- # by the fuzzer itself the first time a generated sequence ever
164
- # built a full hop chain and had its paged query answer a row
165
- # (seed 1, the moment scalar_value_objects.bluebook joined the
166
- # fixtures corpus and shifted every seeded draw): the recompute
167
- # dug `engagement/client/status` as a local dotted path, found
168
- # nil, and declared every genuinely-eligible row ineligible — a
169
- # false property violation against a correct runtime answer,
170
- # reproducible on an untouched main with this same 4-step script.
171
- #
172
- # @param instances [Hash{String => Hash}] `history[:instances]`, or an
173
- # `:instances_at` snapshot from a replayed query entry
174
- # @param domain [String] the domain name the target aggregate belongs to
175
- # @param aggregate_name [String] the target aggregate's own declared name
176
- # @param wheres [Array<QuerySpecification::Common::WhereClause>] the clauses
177
- # every returned row must satisfy
178
- # @param args [Hash] the query's own call args, for a clause whose value is a
179
- # Symbol naming one
180
- # @param bluebooks [Hash{String => Bluebook::Chapter}] every loaded domain, keyed
181
- # by domain name; needed only to resolve a `/` hop clause
182
- # @return [Array<Hash>] each admitted row's own state, merged with its `id:`
183
- def query_eligible_rows(instances, domain, aggregate_name, wheres, args, bluebooks: {})
184
- aggregate = bluebooks[domain]&.aggregate(aggregate_name)
185
- prefix = "#{domain}::#{aggregate_name}#"
186
- instances.filter_map do |key, state|
187
- next unless key.start_with?(prefix)
188
-
189
- row = state.merge(id: key.split("#").last)
190
- next unless wheres.all? do |clause|
191
- resolved = resolve_hop_clause(instances, domain, aggregate, clause, args, bluebooks)
192
- held = Ports::Query::InMemory.comparable(QuerySpecification::FieldPath.dig(row, resolved.field))
193
- Ports::Query::InMemory.holds?(resolved, held, args)
194
- end
195
-
196
- row
197
- end
198
- end
199
-
200
- # Resolves one hop of a `/`-chained clause into a local `in` clause against
201
- # the target aggregate's own ids.
202
- #
203
- # `Runtime::ReferenceHop#fold`, independently restated over the
204
- # replay's own `:instances_at` snapshot instead of live
205
- # repositories — the same shape every other recompute in this
206
- # file takes (never the runtime's own code path, or the property
207
- # would be checking the runtime against itself). One hop peels
208
- # off the head (`HopPath.next_hop`, the identical one-step
209
- # primitive the live fold uses), the inner clause recurses
210
- # through `query_eligible_rows` against the target's own
211
- # snapshot rows (so a multi-hop tail resolves hop by hop, exactly
212
- # as the live path's own recursion does), and the ids that
213
- # answered fold back as the same local `in` membership clause the
214
- # live fold builds. A clause with no `/`, or one whose head this
215
- # aggregate's declarations cannot resolve, passes through
216
- # untouched and evaluates locally as it always did.
217
- #
218
- # @param instances [Hash{String => Hash}] `history[:instances]`, or an
219
- # `:instances_at` snapshot from a replayed query entry
220
- # @param domain [String] the domain name `aggregate` belongs to
221
- # @param aggregate [Bluebook::Aggregate, nil] the aggregate `clause` is
222
- # evaluated against; nil skips hop resolution entirely
223
- # @param clause [QuerySpecification::Common::WhereClause] the clause to resolve
224
- # @param args [Hash] the query's own call args, passed through to the inner
225
- # hop's own `query_eligible_rows` recursion
226
- # @param bluebooks [Hash{String => Bluebook::Chapter}] every loaded domain, keyed
227
- # by domain name
228
- # @return [QuerySpecification::Common::WhereClause] `clause` unchanged if it has
229
- # no resolvable hop head; otherwise a local `in` clause over the matching
230
- # target ids
231
- def resolve_hop_clause(instances, domain, aggregate, clause, args, bluebooks)
232
- return clause unless aggregate && QuerySpecification::HopPath.hop_head?(clause.field, aggregate.attributes)
233
-
234
- hop, rest = QuerySpecification::HopPath.next_hop(clause.field, aggregate.attributes)
235
- target = hop.target
236
- return clause unless target
237
-
238
- inner = QuerySpecification::Common::WhereClause.new(field: rest, op: clause.op, value: clause.value)
239
- ids = query_eligible_rows(instances, domain, target.hecks_name, [inner], args, bluebooks: bluebooks)
240
- .map { |row| row[:id].to_s }.uniq
241
-
242
- QuerySpecification::Common::WhereClause.new(field: hop.attribute.name, op: "in", value: ids)
243
- end
244
-
245
- # Resolves a declared limit/offset value against the query's own call args.
246
- #
247
- # `QueryInterpreter#resolve_query_value`, reproduced: a declared
248
- # limit/offset is either a literal or a Symbol naming an argument
249
- # the caller supplied.
250
- #
251
- # @param value [Integer, Symbol] the declared limit/offset value
252
- # @param args [Hash] the query's own call args
253
- # @return [Object] `value` unchanged if not a Symbol; otherwise `args[value]`
254
- def resolve_paging_value(value, args)
255
- value.is_a?(Symbol) ? args[value] : value
256
- end
257
- end
258
- end
259
- end
260
- end