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,482 +0,0 @@
1
- require_relative "bluebook"
2
- require_relative "codemod"
3
- require_relative "projections/model"
4
- require_relative "fuzzing/properties"
5
-
6
- module Hecks
7
- # The shared core behind `bin/query_ir` (a text CLI) and
8
- # `bin/hecks_query_ir_mcp` (an MCP server exposing the same two
9
- # queries as tools) — one implementation, two front ends, the same
10
- # reason `Hecks::Codemod` exists once rather than per-script.
11
- # Every method here returns structured data (Hashes/Arrays/Structs),
12
- # never formatted text — formatting is each front end's own job.
13
- module QueryIR
14
- Codemod = Hecks::Codemod
15
- Deviations = Hecks::Projections::Model::Deviations
16
-
17
- # The same mapping spec/model_shape_conformance_spec.rb's own
18
- # MODEL_CONSTRUCTS holds — kept here rather than shared from the
19
- # spec (a spec file is not a library other code should require),
20
- # matching Deviations' own doc comment: "the generator and the gate
21
- # read one source" — Deviations is that one source; this table is
22
- # small and genuinely construct-list bookkeeping, not a rule that
23
- # can drift silently the way a Deviations entry could.
24
- CONSTRUCTS = {
25
- "Bluebook" => Hecks::Bluebook::Chapter,
26
- "Aggregate" => Hecks::Bluebook::Aggregate,
27
- "Command" => Hecks::Bluebook::Command,
28
- "Entity" => Hecks::Bluebook::Entity,
29
- "ValueObject" => Hecks::Bluebook::ValueObject,
30
- "Policy" => Hecks::Bluebook::Policy,
31
- "Query" => Hecks::Bluebook::Query,
32
- "ReadModel" => Hecks::Bluebook::ReadModel,
33
- "ProcessManager" => Hecks::Bluebook::ProcessManager
34
- }.freeze
35
-
36
- Rule = Struct.new(:kind, :description, :canonical, :location, keyword_init: true)
37
-
38
- module_function
39
-
40
- # Reads the fields the language declares for one construct kind, from the grammar itself.
41
- #
42
- # @param name [String] a `CONSTRUCTS` key, such as `"Aggregate"`
43
- # @return [Array<Symbol>] the attribute names the self-hosted meta-domain
44
- # grammar declares for `name`
45
- def meta_declared(name)
46
- Hecks::Bluebook::MetaValidator.grammar_registry
47
- .bluebook("Bluebook").aggregate(name).attributes.map(&:name)
48
- end
49
-
50
- # The real structural diff between what a Ruby IR class emits
51
- # (Class.ir_spec.keys) and what the self-hosted meta-domain declares
52
- # for it — the same comparison spec/model_shape_conformance_spec.rb
53
- # makes, reusing its own Deviations data so this can never silently
54
- # drift from what that gate actually checks.
55
- #
56
- # @param name [String] a `CONSTRUCTS` key, such as `"Aggregate"`
57
- # @return [Hash{Symbol => Object}] `:name`, `:declared`, `:emitted`, plus
58
- # `:missing_from_ruby` and `:unaccounted_in_ruby` — each an `Array<Symbol>`
59
- # @raise [ArgumentError] if `name` is not a `CONSTRUCTS` key
60
- def construct_diff(name)
61
- klass = CONSTRUCTS.fetch(name) do
62
- raise ArgumentError, "no such construct #{name.inspect} — known: #{CONSTRUCTS.keys.join(', ')}"
63
- end
64
- declared = meta_declared(name)
65
- emitted = klass.ir_spec.keys
66
-
67
- accounted = declared.reject { |field| Deviations.parent_ref?(field) } -
68
- Deviations.judge_only(name) -
69
- Deviations.folded(name).values.flatten -
70
- Deviations.off_the_wire(name) -
71
- Deviations.dynamic_tail(name) -
72
- Deviations.unpacked(name).keys
73
-
74
- unaccounted = emitted -
75
- declared -
76
- Deviations.contained(name) -
77
- Deviations.folded(name).keys -
78
- Deviations.computed(name) -
79
- Deviations.unpacked(name).values.flatten
80
-
81
- { name: name, declared: declared, emitted: emitted,
82
- missing_from_ruby: accounted - emitted, unaccounted_in_ruby: unaccounted }
83
- end
84
-
85
- # Diffs one or every construct kind at once.
86
- #
87
- # @param names [Array<String>] `CONSTRUCTS` keys to diff, every construct when empty
88
- # @return [Array<Hash>] one `construct_diff` result per name
89
- def constructs(names = [])
90
- targets = names.empty? ? CONSTRUCTS.keys : names
91
- targets.map { |name| construct_diff(name) }
92
- end
93
-
94
- # Every given/ensures/invariant declaration reachable from a booted
95
- # registry, walked recursively — every owner's own `.preconditions`/
96
- # `.invariants` (block-declared rules), every value object's own
97
- # `.invariants`, and every command's own `.givens`/`.ensures`
98
- # (`.givens` too, not just `.ensures` — a command's own local
99
- # `given("x") { block }` not yet hoisted to its owner, round 4's own
100
- # starting shape, would otherwise be invisible).
101
- #
102
- # Not keyed by object identity (a real, hard-won correction — see
103
- # the comment on `duplicates`' own dedup below for why: a bare
104
- # `given("x")` reference and its owner's own block declaration are
105
- # the same Ruby object at DSL build time, but `MetaValidator.call`
106
- # (S14 — every bluebook is judged by dispatching its own IR into the
107
- # self-hosted grammar, then reconstructed via `Assembly.call` from
108
- # flat rows) rebuilds the whole graph fresh from there. By the time
109
- # any caller reads `chapter.aggregates`, every given/invariant is
110
- # already a distinct object, whether it was block-declared or
111
- # bare-referenced — object identity carries no signal past that
112
- # point, for any construct, not just this one).
113
- #
114
- # @param registry [Runtime::Registry] the booted registry to walk
115
- # @param chapter_name [String, nil] one chapter to walk, every booted chapter when nil
116
- # @return [Array<Rule>]
117
- def collect_rules(registry, chapter_name = nil)
118
- rules = []
119
- chapters = chapter_name ? [registry.bluebook(chapter_name)] : registry.bluebooks.values
120
-
121
- chapters.each do |chapter|
122
- chapter.aggregates.each do |aggregate|
123
- walk_construct_rules(aggregate, aggregate.hecks_name, rules)
124
- aggregate.value_objects.each do |vo|
125
- vo.invariants.each do |rule|
126
- rules << Rule.new(kind: "invariant", description: rule.description, canonical: rule.canonical,
127
- location: "#{aggregate.hecks_name}::#{vo.hecks_name} (declared)")
128
- end
129
- end
130
- end
131
- end
132
- rules
133
- end
134
-
135
- # The recursive walk `collect_rules` drives — pulled out of that method
136
- # (pure extraction, identical traversal and Rule shapes) as its own
137
- # named, self-recursive method rather than a lambda closing over the
138
- # same locals. `rules` is the one piece of state every call shares —
139
- # threaded as a parameter and mutated in place, the same accumulator
140
- # role the lambda's own closure played.
141
- def walk_construct_rules(construct, path, rules)
142
- (construct.respond_to?(:preconditions) ? construct.preconditions : []).each do |rule|
143
- rules << Rule.new(kind: "given", description: rule.description, canonical: rule.canonical,
144
- location: "#{path} (declared)")
145
- end
146
- (construct.respond_to?(:invariants) ? construct.invariants : []).each do |rule|
147
- rules << Rule.new(kind: "invariant", description: rule.description, canonical: rule.canonical,
148
- location: "#{path} (declared)")
149
- end
150
- construct.commands.each do |command|
151
- command.givens.each do |rule|
152
- rules << Rule.new(kind: "given", description: rule.description, canonical: rule.canonical,
153
- location: "#{path}.#{command.hecks_name}")
154
- end
155
- command.ensures.each do |rule|
156
- rules << Rule.new(kind: "ensures", description: rule.description, canonical: rule.canonical,
157
- location: "#{path}.#{command.hecks_name}")
158
- end
159
- end
160
- return unless construct.respond_to?(:entities)
161
-
162
- construct.entities.each do |piece|
163
- walk_construct_rules(piece, "#{path}.#{piece.hecks_name}", rules)
164
- end
165
- end
166
- private_class_method :walk_construct_rules
167
-
168
- # A rule's owner — the construct path a "(declared)" location names
169
- # directly, or (for a command-level `.givens`/`.ensures` entry) the
170
- # path with its trailing `.CommandName` segment stripped. Two rules
171
- # sharing an owner are the same declaration read twice (an owner's
172
- # own precondition, and a command under it referencing that
173
- # precondition by name) — not two independent ones.
174
- #
175
- # Public, not a `duplicates`-only internal — `bin/codemod_hoist_
176
- # local_givens` reads it directly to group `collect_rules`' own
177
- # output by owner itself, the same reading `duplicates`' own
178
- # `declaration_count` makes.
179
- #
180
- # @param location [String] a `Rule#location`
181
- # @return [String] the owning construct's path, with `" (declared)"` or a trailing
182
- # `.CommandName` segment stripped
183
- def owner_of(location)
184
- return location.sub(/ \(declared\)\z/, "") if location.end_with?(" (declared)")
185
-
186
- location.rpartition(".").first
187
- end
188
-
189
- # Grouped by (kind, description, canonical), not canonical text
190
- # alone — a generic one-liner like `!value.to_s.empty?` legitimately
191
- # recurs dozens of times for unrelated fields; the real signal is
192
- # the same rule (same description, same predicate), which is also
193
- # exactly what the given/invariant reference mechanism itself
194
- # resolves on.
195
- #
196
- # Deduped by owner, not object identity (`collect_rules`' own
197
- # comment has the full story — identity is gone by the time this
198
- # reads the registry). Within a group, every command-level rule
199
- # whose owner already has its own "(declared)" entry in the same
200
- # group is just that declaration read again through a reference —
201
- # `Account.Open`/`Account.Credit`/etc. all naming `Account`'s own
202
- # `given("customer is active")` count as Account's one declaration,
203
- # not nine. A command-level rule with no matching owner declaration
204
- # (a local, not-yet-hoisted `given("x") { block }`) counts as its
205
- # own standalone declaration — two different commands independently
206
- # writing the identical local predicate is two declarations, a real
207
- # hoisting opportunity. A group is reported only when it adds up to
208
- # more than one real declaration this way.
209
- #
210
- # `domains: []` means "the self-hosted meta-domain only" — pass real
211
- # domain directories explicitly to include them, or `nil` (the
212
- # default) for meta-domain plus every real example.
213
- #
214
- # @param domains [Array<String>, nil] domain root directories to scan; every real
215
- # example (`Codemod::EXAMPLE_ROOTS`) when nil, none beyond the meta-domain when `[]`
216
- # @param include_meta [Boolean] whether to also scan the self-hosted meta-domain
217
- # @return [Array<Hash>] one entry per duplicate group: `:kind`, `:description`,
218
- # `:canonical`, and `:locations` (`Array<String>`)
219
- def duplicates(domains: nil, include_meta: true)
220
- domains ||= Codemod::EXAMPLE_ROOTS
221
- all_rules = []
222
- all_rules.concat(collect_rules(Codemod.meta_registry)) if include_meta
223
-
224
- domains.each do |domain_dir|
225
- bluebook_files = Dir.glob(File.join(domain_dir, "bluebook", "*.bluebook"))
226
- next if bluebook_files.empty?
227
-
228
- registry = Codemod.load_bluebook(bluebook_files)
229
- all_rules.concat(collect_rules(registry))
230
- end
231
-
232
- all_rules.group_by { |r| [r.kind, r.description, r.canonical] }
233
- .map { |key, rules| [key, rules, declaration_count(rules)] }
234
- .select { |_, _, count| count > 1 }
235
- .map do |(kind, description, canonical), rules, _|
236
- { kind: kind, description: description, canonical: canonical,
237
- locations: rules.map(&:location) }
238
- end
239
- end
240
-
241
- # `given` only (not `invariant`/`ensures`) also covers cross-entity
242
- # coverage — one piece's own entity-level declaration, shared with
243
- # any other piece nested under the same root aggregate (real corpus
244
- # this closes: SafeDepositBox's own `Visit`/`KeyIssuance`, two
245
- # different pieces on one head). A rule owned by a nested entity
246
- # (its own owner path has more than one segment) is covered when
247
- # some "(declared)" entry exists anywhere under that same root
248
- # aggregate — not just under its own exact owner — matching the
249
- # DSL's own pool, threaded unchanged through an aggregate's whole
250
- # entity tree (`AggregateBuilder#entity`'s own comment).
251
- #
252
- # A known, accepted gap this does not (and structurally cannot)
253
- # close: chapter-wide given sharing (`AggregateBuilder#given`'s own
254
- # bare form, `docs/implemented/resolution-rules/chapter-given.md`) — `Account`,
255
- # `SafeDepositBox`, and `OnboardingCase` each still show as their
256
- # own "(declared)" owner here even after `SafeDepositBox`/
257
- # `OnboardingCase` were converted to bare chapter-wide references,
258
- # because a referenced given still write-throughs into its own
259
- # aggregate's `@named_givens` — the same reason `collect_rules`'
260
- # own top comment already gives for why object identity carries no
261
- # signal past a bluebook's own build: the exported IR cannot tell
262
- # "I declared this myself" apart from "I referenced someone else's
263
- # declaration," because by the time anything reads `chapter.
264
- # aggregates`, both look identical. Closing this would mean reading
265
- # source text (bare `given(desc)` vs. block `given(desc) { ... }`),
266
- # not the built IR this query is deliberately built on — a
267
- # different, source-level tool, not a fix to this one. Treat a
268
- # still-flagged group naming multiple aggregates as "verify by
269
- # hand whether this is already a chapter-wide reference before
270
- # assuming it's fresh duplication," not as an automatic signal
271
- # either way.
272
- #
273
- # The identical gap, one level down: chapter-wide entity-scoped
274
- # sharing (`EntityBuilder#given`'s own bare form,
275
- # `docs/implemented/resolution-rules/chapter-entity-given.md`) hits this same wall for
276
- # the same structural reason — `SafeDepositBox.Visit` still shows
277
- # as its own "(declared)" owner here even after becoming a bare
278
- # reference to `Account.LedgerEntry`'s declaration, because a piece
279
- # resolving a chapter-wide reference still write-throughs the
280
- # resolved `Given` into its own `@named_givens` (so its own
281
- # commands can read it back locally without a second hop). This is
282
- # not a new limitation this feature introduces — it is the exact
283
- # same IR-cannot-distinguish-declared-from-referenced fact, one
284
- # scope wider. `bin/query_ir duplicates` confirms this directly:
285
- # `Account.LedgerEntry (declared)` and `SafeDepositBox.Visit
286
- # (declared)` both appear under the same `given: "customer is
287
- # active"` group — verify by hand, same as the aggregate-level
288
- # case above, before assuming a group naming two pieces under
289
- # different aggregates is fresh duplication rather than an already-
290
- # resolved chapter-wide reference.
291
- #
292
- # @param rules [Array<Rule>] one duplicate-key group's own rules
293
- # @return [Integer] how many of `rules` are real, independent declarations
294
- def declaration_count(rules)
295
- declared = rules.select { |r| r.location.end_with?(" (declared)") }
296
- declared_owners = declared.to_set { |r| owner_of(r.location) }
297
- declared_given_roots = declared.select { |r| r.kind == "given" }
298
- .to_set { |r| owner_of(r.location).split(".").first }
299
-
300
- standalone = rules.reject do |r|
301
- owner = owner_of(r.location)
302
- next true if declared_owners.include?(owner)
303
- next true if r.kind == "given" && owner.include?(".") && declared_given_roots.include?(owner.split(".").first)
304
-
305
- false
306
- end
307
-
308
- declared_owners.size + standalone.size
309
- end
310
- private_class_method :declaration_count
311
-
312
- # Shared text formatting — both `bin/query_ir` (a text CLI) and
313
- # `bin/hecks_query_ir_mcp` (an MCP tool result, itself a text
314
- # block) want the identical human-readable rendering; only the
315
- # outer framing differs (plain stdout vs. a JSON-RPC content array).
316
- # @param diffs [Array<Hash>] `constructs`' own output
317
- # @return [String] the human-readable rendering
318
- def format_constructs(diffs)
319
- diffs.map do |diff|
320
- lines = ["== #{diff[:name]} =="]
321
- lines << " emits: #{diff[:emitted].join(', ')}"
322
- lines << " declares: #{diff[:declared].join(', ')}"
323
- if diff[:missing_from_ruby].empty? && diff[:unaccounted_in_ruby].empty?
324
- lines << " clean — every declared field is emitted (or a named deviation), nothing emitted is undeclared"
325
- else
326
- unless diff[:missing_from_ruby].empty?
327
- lines << " MISSING FROM RUBY (declared, not emitted, not a named deviation): " \
328
- "#{diff[:missing_from_ruby].join(', ')}"
329
- end
330
- unless diff[:unaccounted_in_ruby].empty?
331
- lines << " UNACCOUNTED IN RUBY (emitted, not declared, not a named deviation): " \
332
- "#{diff[:unaccounted_in_ruby].join(', ')}"
333
- end
334
- end
335
- lines.join("\n")
336
- end.join("\n\n")
337
- end
338
-
339
- # **One hand-typed construct-name per reconstruction method** — the only
340
- # two `MetaValidator::Reconstruction` methods not driven generically
341
- # through `Assembly::Contracts`' own table (its own header explains
342
- # why: `aggregate(row)`/`entity(row)` predate the table and were
343
- # never migrated). `impact_preview`'s own touchpoint 4 is checked
344
- # only for these two — every other construct is read generically, so
345
- # asking "does Command's own reconstruction method mention this
346
- # field" is a question with no method to check.
347
- RECONSTRUCTION_METHODS = { "Aggregate" => :aggregate, "Entity" => :entity }.freeze
348
-
349
- # The six touchpoints `.claude/skills/bluebook-construct-creator/
350
- # SKILL.md` walks in prose, checked structurally instead of by hand
351
- # — for a construct/field pair not yet fully propagated (typically
352
- # mid-round, deciding what's left), or as a sanity check before the
353
- # final gate sweep of a round already believed done. Every check
354
- # here is best-effort and advisory, not a gate: a `false` does not
355
- # always mean "not yet done" (a field can be legitimately exempt —
356
- # `Deviations`' own named categories, `GUARANTEED_BY_CONSTRUCTION`,
357
- # or `META_DOMAIN_KNOWN_GAPS`, the last of which lives in
358
- # spec/fuzzing/meta_domain_coverage_spec.rb, a spec file this
359
- # module deliberately never requires — see `CONSTRUCTS`' own
360
- # comment on the same principle). Read the touchpoint's own
361
- # existing gate (`model_shape_conformance_spec.rb`,
362
- # `assembly_spec.rb`, `meta_domain_coverage_spec.rb`) before trusting
363
- # a `false` here as a real gap.
364
- # @param name [String] a `CONSTRUCTS` key, such as `"Aggregate"`
365
- # @param field [String, Symbol] the declared field to check propagation for
366
- # @return [Hash{Symbol => Object}] `:name`, `:field`, and `:touchpoints` — an
367
- # `Array<Hash>` of `:touchpoint` (String) and `:present` (Boolean, or nil when the
368
- # touchpoint does not apply to `name`)
369
- # @raise [ArgumentError] if `name` is not a `CONSTRUCTS` key
370
- def impact_preview(name, field)
371
- CONSTRUCTS.fetch(name) { raise ArgumentError, "no such construct #{name.inspect} — known: #{CONSTRUCTS.keys.join(', ')}" }
372
- field = field.to_s
373
-
374
- {
375
- name: name,
376
- field: field,
377
- touchpoints: [
378
- { touchpoint: "meta-domain grammar declares it", present: meta_declared(name).map(&:to_s).include?(field) },
379
- { touchpoint: "docs/resolution-rules/ names it", present: resolution_rule_mentions?(field) },
380
- { touchpoint: "Assembly::Contracts consumes it", present: contract_consumes?(name, field) },
381
- { touchpoint: "Reconstruction's hand-typed method reads it", present: reconstruction_reads?(name, field) },
382
- { touchpoint: "fuzzer FEATURE_COVERAGE/GUARANTEED_BY_CONSTRUCTION claims it", present: fuzzer_claims?(name, field) },
383
- { touchpoint: "Rust mirror (rust/parser/src/parse/*.rs) mentions it", present: rust_mentions?(field) }
384
- ]
385
- }
386
- end
387
-
388
- def resolution_rule_mentions?(field)
389
- paths = Dir.glob(File.join(Codemod::ROOT, "docs/resolution-rules/*.md")) +
390
- Dir.glob(File.join(Codemod::ROOT, "docs/implemented/resolution-rules/*.md"))
391
- paths.any? { |path| File.read(path).include?(field) }
392
- end
393
- private_class_method :resolution_rule_mentions?
394
-
395
- def contract_consumes?(name, field)
396
- contract = Hecks::Bluebook::Assembly.contract(name)
397
- contract.fields.key?(field.to_sym) || contract.derived.key?(field.to_sym)
398
- rescue KeyError
399
- false
400
- end
401
- private_class_method :contract_consumes?
402
-
403
- # `Method#source_location` finds where the hand-typed method starts;
404
- # the method's own body ends at the next line indented no deeper
405
- # than its own `def` — the same boundary Ruby itself uses, read back
406
- # textually because there is no live AST here, only a file to grep a
407
- # slice of. `nil` (not `false`) for every other construct — this
408
- # touchpoint genuinely does not apply to them (`RECONSTRUCTION_
409
- # METHODS` only names the two hand-typed methods), and collapsing
410
- # "does not apply" into "not done" would misreport a construct that
411
- # was never supposed to have this touchpoint at all.
412
- def reconstruction_reads?(name, field)
413
- method_name = RECONSTRUCTION_METHODS[name]
414
- # rubocop:disable-next Style/ReturnNilInPredicateMethodDefinition -- nil vs
415
- # false is a deliberate distinction here: nil means "not applicable" (no
416
- # hand-typed method to check), false means "applicable, and it fails" —
417
- # see the spec's own "not-applicable (nil), not false" example.
418
- return nil unless method_name
419
-
420
- file, start_line = Hecks::Bluebook::MetaValidator::Reconstruction.instance_method(method_name).source_location
421
- lines = File.readlines(file)
422
- def_line = lines[start_line - 1]
423
- indent = def_line[/\A\s*/]
424
-
425
- body = lines[start_line..].take_while do |line|
426
- line.strip.empty? || line[/\A\s*/].size > indent.size || !line.lstrip.start_with?("def ")
427
- end
428
- body.join.include?("#{field}:")
429
- end
430
- private_class_method :reconstruction_reads?
431
-
432
- def fuzzer_claims?(name, field)
433
- key = "#{name}##{field}"
434
- Hecks::Fuzzing::Properties::FEATURE_COVERAGE.values.flatten.include?(key) ||
435
- Hecks::Fuzzing::Properties::GUARANTEED_BY_CONSTRUCTION.key?(key)
436
- end
437
- private_class_method :fuzzer_claims?
438
-
439
- def rust_mentions?(field)
440
- Dir.glob(File.join(Codemod::ROOT, "rust/parser/src/parse/*.rs")).any? { |path| File.read(path).include?(field) }
441
- end
442
- private_class_method :rust_mentions?
443
-
444
- # Renders one field's touchpoint checklist as text.
445
- #
446
- # @param preview [Hash] `impact_preview`'s own output
447
- # @return [String] the human-readable rendering
448
- def format_impact_preview(preview)
449
- lines = ["== #{preview[:name]}##{preview[:field]} =="]
450
- preview[:touchpoints].each do |t|
451
- mark = if t[:present].nil?
452
- "n/a"
453
- else
454
- (t[:present] ? "yes" : "NOT YET")
455
- end
456
- lines << " [#{mark.rjust(7)}] #{t[:touchpoint]}"
457
- end
458
- done = preview[:touchpoints].count { |t| t[:present] == true }
459
- total = preview[:touchpoints].count { |t| !t[:present].nil? }
460
- lines << ""
461
- lines << "#{done}/#{total} applicable touchpoint(s) show signs of this field — advisory, not a gate; " \
462
- "a NOT YET can be a legitimate exemption (Deviations, GUARANTEED_BY_CONSTRUCTION, or a spec-only " \
463
- "META_DOMAIN_KNOWN_GAPS entry this module deliberately never reads)."
464
- lines.join("\n")
465
- end
466
-
467
- # Renders the duplicate-rule groups as text.
468
- #
469
- # @param groups [Array<Hash>] `duplicates`' own output
470
- # @return [String] the human-readable rendering
471
- def format_duplicates(groups)
472
- return "no duplicate given/invariant/ensures rule found" if groups.empty?
473
-
474
- body = groups.map do |group|
475
- ["== #{group[:kind]}: #{group[:description].inspect} — #{group[:canonical]} ==",
476
- *group[:locations].map { |loc| " #{loc}" }].join("\n")
477
- end.join("\n\n")
478
-
479
- "#{body}\n\n#{groups.size} duplicate group(s), #{groups.sum { |g| g[:locations].size }} declarations total"
480
- end
481
- end
482
- end