hecks 0.2.0 → 1.0.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 (419) hide show
  1. checksums.yaml +5 -5
  2. data/lib/hecks/adapters/driven/claude_code.adapter +3 -0
  3. data/lib/hecks/adapters/driven/claude_code.rb +127 -0
  4. data/lib/hecks/adapters/driven/d1.adapter +12 -0
  5. data/lib/hecks/adapters/driven/d1.rb +427 -0
  6. data/lib/hecks/adapters/driven/folder.adapter +3 -0
  7. data/lib/hecks/adapters/driven/folder.rb +199 -0
  8. data/lib/hecks/adapters/driven/google_authentication.adapter +3 -0
  9. data/lib/hecks/adapters/driven/google_authentication.rb +100 -0
  10. data/lib/hecks/adapters/driven/governance_authorization.adapter +3 -0
  11. data/lib/hecks/adapters/driven/governance_authorization.rb +101 -0
  12. data/lib/hecks/adapters/driven/heki/journal.rb +56 -0
  13. data/lib/hecks/adapters/driven/heki/saga_store.rb +99 -0
  14. data/lib/hecks/adapters/driven/heki/snapshot.rb +65 -0
  15. data/lib/hecks/adapters/driven/heki.adapter +4 -0
  16. data/lib/hecks/adapters/driven/heki.rb +179 -0
  17. data/lib/hecks/adapters/driven/identity_registry.adapter +3 -0
  18. data/lib/hecks/adapters/driven/identity_registry.rb +26 -0
  19. data/lib/hecks/adapters/driven/in_memory_ordering.rb +51 -0
  20. data/lib/hecks/adapters/driven/lambda/client.rb +63 -0
  21. data/lib/hecks/adapters/driven/lambda.adapter +4 -0
  22. data/lib/hecks/adapters/driven/lambda.rb +145 -0
  23. data/lib/hecks/adapters/driven/memory.adapter +3 -0
  24. data/lib/hecks/adapters/driven/memory.rb +103 -0
  25. data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +3 -0
  26. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +28 -0
  27. data/lib/hecks/adapters/driven/postgres/codec.rb +88 -0
  28. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +207 -0
  29. data/lib/hecks/adapters/driven/postgres.adapter +5 -0
  30. data/lib/hecks/adapters/driven/postgres.rb +438 -0
  31. data/lib/hecks/adapters/driven/postgres_era.adapter +17 -0
  32. data/lib/hecks/adapters/driven/prism.adapter +3 -0
  33. data/lib/hecks/adapters/driven/prism.rb +80 -0
  34. data/lib/hecks/adapters/driven/secure_random_identity.adapter +3 -0
  35. data/lib/hecks/adapters/driven/secure_random_identity.rb +14 -0
  36. data/lib/hecks/adapters/driven/sql_query_builder.rb +221 -0
  37. data/lib/hecks/adapters/driven/sqlite/codec.rb +80 -0
  38. data/lib/hecks/adapters/driven/sqlite/projection.rb +172 -0
  39. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +192 -0
  40. data/lib/hecks/adapters/driven/sqlite.adapter +9 -0
  41. data/lib/hecks/adapters/driven/sqlite.rb +306 -0
  42. data/lib/hecks/adapters/driven/system_clock.adapter +3 -0
  43. data/lib/hecks/adapters/driven/system_clock.rb +14 -0
  44. data/lib/hecks/adapters/driven.rb +54 -0
  45. data/lib/hecks/adapters.rb +6 -0
  46. data/lib/hecks/behaviors/dsl.rb +125 -0
  47. data/lib/hecks/behaviors/expectations.rb +340 -0
  48. data/lib/hecks/behaviors/ir.rb +31 -0
  49. data/lib/hecks/behaviors/rspec.rb +42 -0
  50. data/lib/hecks/behaviors/runner.rb +96 -0
  51. data/lib/hecks/behaviors.rb +25 -0
  52. data/lib/hecks/bluebook/aggregate.rb +108 -0
  53. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +134 -0
  54. data/lib/hecks/bluebook/assembly/build.rb +48 -0
  55. data/lib/hecks/bluebook/assembly/contract.rb +112 -0
  56. data/lib/hecks/bluebook/assembly/contracts.rb +438 -0
  57. data/lib/hecks/bluebook/assembly/marks.rb +228 -0
  58. data/lib/hecks/bluebook/assembly/specializer.rb +70 -0
  59. data/lib/hecks/bluebook/assembly.rb +91 -0
  60. data/lib/hecks/bluebook/attribute.rb +96 -0
  61. data/lib/hecks/bluebook/behaviour/aggregate.rb +83 -0
  62. data/lib/hecks/bluebook/behaviour/attribute.rb +27 -0
  63. data/lib/hecks/bluebook/behaviour/chapter.rb +72 -0
  64. data/lib/hecks/bluebook/behaviour/command.rb +116 -0
  65. data/lib/hecks/bluebook/behaviour/domain_port.rb +25 -0
  66. data/lib/hecks/bluebook/behaviour/entity.rb +59 -0
  67. data/lib/hecks/bluebook/behaviour/hexagon.rb +50 -0
  68. data/lib/hecks/bluebook/behaviour/lifecycle.rb +68 -0
  69. data/lib/hecks/bluebook/behaviour/policy.rb +52 -0
  70. data/lib/hecks/bluebook/behaviour/process_manager.rb +51 -0
  71. data/lib/hecks/bluebook/behaviour/query.rb +10 -0
  72. data/lib/hecks/bluebook/behaviour/read_model.rb +29 -0
  73. data/lib/hecks/bluebook/behaviour/traits.rb +81 -0
  74. data/lib/hecks/bluebook/behaviour/value_object.rb +33 -0
  75. data/lib/hecks/bluebook/chapter.rb +78 -0
  76. data/lib/hecks/bluebook/command.rb +124 -0
  77. data/lib/hecks/bluebook/domain_port.rb +102 -0
  78. data/lib/hecks/bluebook/dsl/adapter_builder.rb +34 -0
  79. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +1018 -0
  80. data/lib/hecks/bluebook/dsl/attribute_collector.rb +348 -0
  81. data/lib/hecks/bluebook/dsl/binding_proxy.rb +71 -0
  82. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +1087 -0
  83. data/lib/hecks/bluebook/dsl/command_builder.rb +714 -0
  84. data/lib/hecks/bluebook/dsl/const_shim.rb +81 -0
  85. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +79 -0
  86. data/lib/hecks/bluebook/dsl/entity_builder.rb +430 -0
  87. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +366 -0
  88. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +163 -0
  89. data/lib/hecks/bluebook/dsl/identity_declaration.rb +191 -0
  90. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +44 -0
  91. data/lib/hecks/bluebook/dsl/malformed.rb +7 -0
  92. data/lib/hecks/bluebook/dsl/policy_builder.rb +135 -0
  93. data/lib/hecks/bluebook/dsl/port_builder.rb +39 -0
  94. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +142 -0
  95. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +306 -0
  96. data/lib/hecks/bluebook/dsl/query_builder.rb +113 -0
  97. data/lib/hecks/bluebook/dsl/read_model_builder.rb +233 -0
  98. data/lib/hecks/bluebook/dsl/rule_reference.rb +173 -0
  99. data/lib/hecks/bluebook/dsl/translation_builder.rb +243 -0
  100. data/lib/hecks/bluebook/dsl/value_object_builder.rb +178 -0
  101. data/lib/hecks/bluebook/dsl/word_gate.rb +228 -0
  102. data/lib/hecks/bluebook/dsl/world_builder.rb +117 -0
  103. data/lib/hecks/bluebook/dsl.rb +45 -0
  104. data/lib/hecks/bluebook/entity.rb +103 -0
  105. data/lib/hecks/bluebook/expression/canonical_form.rb +123 -0
  106. data/lib/hecks/bluebook/expression/evaluator.rb +304 -0
  107. data/lib/hecks/bluebook/expression/projection.json +218 -0
  108. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +233 -0
  109. data/lib/hecks/bluebook/expression/resolver.rb +781 -0
  110. data/lib/hecks/bluebook/expression.rb +13 -0
  111. data/lib/hecks/bluebook/hexagon.rb +60 -0
  112. data/lib/hecks/bluebook/lifecycle.rb +42 -0
  113. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +54 -0
  114. data/lib/hecks/bluebook/meta_validator/judge.rb +621 -0
  115. data/lib/hecks/bluebook/meta_validator/plan.rb +332 -0
  116. data/lib/hecks/bluebook/meta_validator/port_judge.rb +51 -0
  117. data/lib/hecks/bluebook/meta_validator/readings.rb +360 -0
  118. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +351 -0
  119. data/lib/hecks/bluebook/meta_validator/shapes.rb +266 -0
  120. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +255 -0
  121. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +138 -0
  122. data/lib/hecks/bluebook/meta_validator/world_judge.rb +78 -0
  123. data/lib/hecks/bluebook/meta_validator.rb +542 -0
  124. data/lib/hecks/bluebook/model_check.rb +445 -0
  125. data/lib/hecks/bluebook/pattern_subset.rb +184 -0
  126. data/lib/hecks/bluebook/policy.rb +41 -0
  127. data/lib/hecks/bluebook/process_manager.rb +128 -0
  128. data/lib/hecks/bluebook/project_discovery.rb +30 -0
  129. data/lib/hecks/bluebook/project_loader.rb +40 -0
  130. data/lib/hecks/bluebook/project_register.rb +82 -0
  131. data/lib/hecks/bluebook/query.rb +61 -0
  132. data/lib/hecks/bluebook/read_model.rb +109 -0
  133. data/lib/hecks/bluebook/reference.rb +74 -0
  134. data/lib/hecks/bluebook/smoke_test.rb +166 -0
  135. data/lib/hecks/bluebook/synthesizer.rb +95 -0
  136. data/lib/hecks/bluebook/translation.rb +92 -0
  137. data/lib/hecks/bluebook/value_object.rb +66 -0
  138. data/lib/hecks/bluebook.rb +74 -0
  139. data/lib/hecks/codemod.rb +342 -0
  140. data/lib/hecks/construct.rb +71 -0
  141. data/lib/hecks/deploy/bluebook/deploy.bluebook +219 -0
  142. data/lib/hecks/deploy/bluebook/deploy.hecksagon +4 -0
  143. data/lib/hecks/deploy/oidc.json +18 -0
  144. data/lib/hecks/doc/reference.rb +410 -0
  145. data/lib/hecks/embryonaut_bluebook.rb +75 -0
  146. data/lib/hecks/facade/cli_door.rb +119 -0
  147. data/lib/hecks/facade/cli_runner.rb +190 -0
  148. data/lib/hecks/facade/command_request.rb +105 -0
  149. data/lib/hecks/facade/handle.rb +173 -0
  150. data/lib/hecks/facade/json_door.rb +166 -0
  151. data/lib/hecks/facade/surface/aggregate_door.rb +185 -0
  152. data/lib/hecks/facade/surface/chapter.rb +107 -0
  153. data/lib/hecks/facade/surface.rb +48 -0
  154. data/lib/hecks/facade.rb +44 -0
  155. data/lib/hecks/forms/app.rb +341 -0
  156. data/lib/hecks/forms/command_form_renderer.rb +113 -0
  157. data/lib/hecks/forms/examples/banking_console.bluebook +3 -0
  158. data/lib/hecks/forms/field_renderer.rb +177 -0
  159. data/lib/hecks/forms/field_shape.rb +232 -0
  160. data/lib/hecks/forms/html.rb +84 -0
  161. data/lib/hecks/forms/index_renderer.rb +35 -0
  162. data/lib/hecks/forms/page.rb +157 -0
  163. data/lib/hecks/forms/params.rb +161 -0
  164. data/lib/hecks/forms/port_argument.rb +46 -0
  165. data/lib/hecks/forms/query_form_renderer.rb +114 -0
  166. data/lib/hecks/forms/record_renderer.rb +119 -0
  167. data/lib/hecks/forms/record_table.rb +68 -0
  168. data/lib/hecks/forms/reference_options.rb +30 -0
  169. data/lib/hecks/forms/value_object_shape.rb +46 -0
  170. data/lib/hecks/forms.rb +54 -0
  171. data/lib/hecks/fqn.rb +94 -0
  172. data/lib/hecks/framework/bluebook/compliance.bluebook +1 -0
  173. data/lib/hecks/framework/bluebook/console_settings.bluebook +489 -0
  174. data/lib/hecks/framework/bluebook/framework.hecksagon +32 -0
  175. data/lib/hecks/framework/bluebook/governance.bluebook +145 -0
  176. data/lib/hecks/framework/bluebook/identity.bluebook +90 -0
  177. data/lib/hecks/framework/oidc.json +39 -0
  178. data/lib/hecks/framework.rb +90 -0
  179. data/lib/hecks/freezer.rb +67 -0
  180. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +527 -0
  181. data/lib/hecks/fuzzing/invalid_value_generator.rb +86 -0
  182. data/lib/hecks/fuzzing/isolated_boot.rb +286 -0
  183. data/lib/hecks/fuzzing/properties.rb +1192 -0
  184. data/lib/hecks/fuzzing/replay.rb +668 -0
  185. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +93 -0
  186. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +80 -0
  187. data/lib/hecks/fuzzing/sequence_generator/picker.rb +96 -0
  188. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +250 -0
  189. data/lib/hecks/fuzzing/sequence_generator.rb +131 -0
  190. data/lib/hecks/fuzzing/value_generator.rb +200 -0
  191. data/lib/hecks/fuzzing.rb +12 -0
  192. data/lib/hecks/grammar/evolve.rb +324 -0
  193. data/lib/hecks/grammar/expression.bluebook +420 -0
  194. data/lib/hecks/grammar/expression_operators.json +1648 -0
  195. data/lib/hecks/grammar/grammar.hecksagon +20 -0
  196. data/lib/hecks/grammar/oidc.json +69 -0
  197. data/lib/hecks/grammar/translation.bluebook +203 -0
  198. data/lib/hecks/grammar.rb +178 -0
  199. data/lib/hecks/ir.rb +126 -0
  200. data/lib/hecks/language/adapter.bluebook +116 -0
  201. data/lib/hecks/language/bluebook/aggregate.bluebook +581 -0
  202. data/lib/hecks/language/bluebook/attaches/paging.bluebook +76 -0
  203. data/lib/hecks/language/bluebook/bluebook.bluebook +256 -0
  204. data/lib/hecks/language/bluebook/bluebook.hecksagon +24 -0
  205. data/lib/hecks/language/bluebook/command.bluebook +471 -0
  206. data/lib/hecks/language/bluebook/entity.bluebook +392 -0
  207. data/lib/hecks/language/bluebook/policy.bluebook +189 -0
  208. data/lib/hecks/language/bluebook/process_manager.bluebook +381 -0
  209. data/lib/hecks/language/bluebook/projection.bluebook +267 -0
  210. data/lib/hecks/language/bluebook/query.bluebook +245 -0
  211. data/lib/hecks/language/bluebook/shape.bluebook +292 -0
  212. data/lib/hecks/language/bluebook/syntax.bluebook +448 -0
  213. data/lib/hecks/language/bluebook/vocabulary.bluebook +379 -0
  214. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +51 -0
  215. data/lib/hecks/language/hecksagon/domain_port.bluebook +76 -0
  216. data/lib/hecks/language/hecksagon/hecksagon.bluebook +131 -0
  217. data/lib/hecks/language/hecksagon/port_operation.bluebook +102 -0
  218. data/lib/hecks/language/oidc.json +333 -0
  219. data/lib/hecks/language/port.bluebook +120 -0
  220. data/lib/hecks/language/translation/translation.bluebook +110 -0
  221. data/lib/hecks/language/translation/translation_aggregate.bluebook +267 -0
  222. data/lib/hecks/language/world/wiring.bluebook +62 -0
  223. data/lib/hecks/language/world/world.bluebook +84 -0
  224. data/lib/hecks/literal.rb +125 -0
  225. data/lib/hecks/naming.rb +174 -0
  226. data/lib/hecks/ports/access_control.port +9 -0
  227. data/lib/hecks/ports/access_control.rb +62 -0
  228. data/lib/hecks/ports/agent/answers.rb +104 -0
  229. data/lib/hecks/ports/agent.port +8 -0
  230. data/lib/hecks/ports/agent.rb +167 -0
  231. data/lib/hecks/ports/authentication.port +6 -0
  232. data/lib/hecks/ports/authentication.rb +50 -0
  233. data/lib/hecks/ports/authorization.port +7 -0
  234. data/lib/hecks/ports/authorization.rb +62 -0
  235. data/lib/hecks/ports/clock.port +5 -0
  236. data/lib/hecks/ports/clock.rb +62 -0
  237. data/lib/hecks/ports/extraction.port +5 -0
  238. data/lib/hecks/ports/extraction.rb +37 -0
  239. data/lib/hecks/ports/identity_assignment.port +5 -0
  240. data/lib/hecks/ports/identity_assignment.rb +45 -0
  241. data/lib/hecks/ports/identity_generation.port +5 -0
  242. data/lib/hecks/ports/identity_generation.rb +49 -0
  243. data/lib/hecks/ports/identity_resolution.port +5 -0
  244. data/lib/hecks/ports/identity_resolution.rb +40 -0
  245. data/lib/hecks/ports/loading.port +4 -0
  246. data/lib/hecks/ports/loading.rb +13 -0
  247. data/lib/hecks/ports/persistence/append_only.rb +138 -0
  248. data/lib/hecks/ports/persistence/binding_policy.rb +56 -0
  249. data/lib/hecks/ports/persistence/execution.rb +23 -0
  250. data/lib/hecks/ports/persistence/null_saga_store.rb +25 -0
  251. data/lib/hecks/ports/persistence/plugin.rb +54 -0
  252. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +169 -0
  253. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +128 -0
  254. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +161 -0
  255. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +61 -0
  256. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +304 -0
  257. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +171 -0
  258. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +190 -0
  259. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +478 -0
  260. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +166 -0
  261. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +314 -0
  262. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +168 -0
  263. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +170 -0
  264. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +134 -0
  265. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +137 -0
  266. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +89 -0
  267. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +85 -0
  268. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +43 -0
  269. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +132 -0
  270. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +74 -0
  271. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +758 -0
  272. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +120 -0
  273. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +31 -0
  274. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +46 -0
  275. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +102 -0
  276. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +46 -0
  277. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +70 -0
  278. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +72 -0
  279. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +120 -0
  280. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +183 -0
  281. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +41 -0
  282. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +28 -0
  283. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +30 -0
  284. data/lib/hecks/ports/persistence/plugins/era/translation.rb +11 -0
  285. data/lib/hecks/ports/persistence/plugins/era.rb +48 -0
  286. data/lib/hecks/ports/persistence/remote_runtime.rb +42 -0
  287. data/lib/hecks/ports/persistence/repository_factory.rb +30 -0
  288. data/lib/hecks/ports/persistence.port +4 -0
  289. data/lib/hecks/ports/persistence.rb +39 -0
  290. data/lib/hecks/ports/projection.port +4 -0
  291. data/lib/hecks/ports/projection.rb +96 -0
  292. data/lib/hecks/ports/query/in_memory.rb +60 -0
  293. data/lib/hecks/ports/query/ordering.rb +41 -0
  294. data/lib/hecks/ports/query.rb +36 -0
  295. data/lib/hecks/ports.rb +27 -0
  296. data/lib/hecks/projections/diagrams.rb +642 -0
  297. data/lib/hecks/projections/ir.rb +18 -0
  298. data/lib/hecks/projections/model/deviations.rb +98 -0
  299. data/lib/hecks/projections/model.rb +145 -0
  300. data/lib/hecks/projections/oidc.rb +110 -0
  301. data/lib/hecks/projections/parser_table.rb +159 -0
  302. data/lib/hecks/projections/reference.rb +38 -0
  303. data/lib/hecks/projections/shape.rb +44 -0
  304. data/lib/hecks/projections/statements.rb +110 -0
  305. data/lib/hecks/projections/vocabulary.rb +100 -0
  306. data/lib/hecks/projections.rb +32 -0
  307. data/lib/hecks/projector/cli_projector.rb +446 -0
  308. data/lib/hecks/projector/docs_projector.rb +321 -0
  309. data/lib/hecks/projector/exporter.rb +158 -0
  310. data/lib/hecks/projector/ir_projector.rb +18 -0
  311. data/lib/hecks/projector/narrate_projector.rb +243 -0
  312. data/lib/hecks/projector/target.rb +97 -0
  313. data/lib/hecks/projector.rb +186 -0
  314. data/lib/hecks/query_ir.rb +411 -0
  315. data/lib/hecks/query_specification/common/authorization_spec.rb +9 -0
  316. data/lib/hecks/query_specification/common/comparators.rb +25 -0
  317. data/lib/hecks/query_specification/common/comparison.rb +174 -0
  318. data/lib/hecks/query_specification/common/cursor_spec.rb +9 -0
  319. data/lib/hecks/query_specification/common/dsl.rb +58 -0
  320. data/lib/hecks/query_specification/common/inspection_spec.rb +9 -0
  321. data/lib/hecks/query_specification/common/limit_spec.rb +9 -0
  322. data/lib/hecks/query_specification/common/null_policy.rb +92 -0
  323. data/lib/hecks/query_specification/common/null_semantics.rb +11 -0
  324. data/lib/hecks/query_specification/common/offset_spec.rb +9 -0
  325. data/lib/hecks/query_specification/common/options.rb +32 -0
  326. data/lib/hecks/query_specification/common/order_by.rb +9 -0
  327. data/lib/hecks/query_specification/common/specification.rb +10 -0
  328. data/lib/hecks/query_specification/common/where_clause.rb +9 -0
  329. data/lib/hecks/query_specification/field_path.rb +107 -0
  330. data/lib/hecks/query_specification/hop_path.rb +132 -0
  331. data/lib/hecks/query_specification/read_model/specification.rb +18 -0
  332. data/lib/hecks/query_specification.rb +14 -0
  333. data/lib/hecks/rendering.rb +48 -0
  334. data/lib/hecks/router/namespace_installer.rb +157 -0
  335. data/lib/hecks/router.rb +70 -0
  336. data/lib/hecks/runtime/aggregate_lock.rb +45 -0
  337. data/lib/hecks/runtime/boot_gates.rb +41 -0
  338. data/lib/hecks/runtime/caller.rb +66 -0
  339. data/lib/hecks/runtime/capability_graph.rb +44 -0
  340. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +135 -0
  341. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +260 -0
  342. data/lib/hecks/runtime/command_interpreter.rb +497 -0
  343. data/lib/hecks/runtime/command_rules/admissibility.rb +357 -0
  344. data/lib/hecks/runtime/command_rules/arithmetic.rb +260 -0
  345. data/lib/hecks/runtime/command_rules/authorization.rb +65 -0
  346. data/lib/hecks/runtime/command_rules/emission.rb +34 -0
  347. data/lib/hecks/runtime/command_rules/references.rb +189 -0
  348. data/lib/hecks/runtime/command_rules.rb +28 -0
  349. data/lib/hecks/runtime/dependency_planning.rb +245 -0
  350. data/lib/hecks/runtime/dispatcher.rb +301 -0
  351. data/lib/hecks/runtime/entity_element.rb +253 -0
  352. data/lib/hecks/runtime/entity_interpreter.rb +299 -0
  353. data/lib/hecks/runtime/errors.rb +122 -0
  354. data/lib/hecks/runtime/event.rb +51 -0
  355. data/lib/hecks/runtime/identity.rb +156 -0
  356. data/lib/hecks/runtime/instance.rb +175 -0
  357. data/lib/hecks/runtime/interpreting.rb +90 -0
  358. data/lib/hecks/runtime/loader.rb +184 -0
  359. data/lib/hecks/runtime/policy_interpreter.rb +366 -0
  360. data/lib/hecks/runtime/port_operation_interpreter.rb +210 -0
  361. data/lib/hecks/runtime/query_interpreter.rb +286 -0
  362. data/lib/hecks/runtime/reaction_invocation.rb +253 -0
  363. data/lib/hecks/runtime/read_model_interpreter.rb +341 -0
  364. data/lib/hecks/runtime/rebuild_sweep.rb +74 -0
  365. data/lib/hecks/runtime/reference_hop.rb +99 -0
  366. data/lib/hecks/runtime/refusal_wording.rb +126 -0
  367. data/lib/hecks/runtime/registry/saga_persistence.rb +142 -0
  368. data/lib/hecks/runtime/registry/verification.rb +246 -0
  369. data/lib/hecks/runtime/registry.rb +279 -0
  370. data/lib/hecks/runtime/remote_dispatcher.rb +143 -0
  371. data/lib/hecks/runtime/routing.rb +96 -0
  372. data/lib/hecks/runtime/saga_interpreter/correlation.rb +97 -0
  373. data/lib/hecks/runtime/saga_interpreter.rb +503 -0
  374. data/lib/hecks/runtime/saga_pending_dispatch.rb +45 -0
  375. data/lib/hecks/runtime/tenant_check.rb +84 -0
  376. data/lib/hecks/runtime/tenant_scope.rb +55 -0
  377. data/lib/hecks/runtime/value/admission.rb +130 -0
  378. data/lib/hecks/runtime/value/coercion.rb +565 -0
  379. data/lib/hecks/runtime/value/invariant_violation.rb +5 -0
  380. data/lib/hecks/runtime/value.rb +125 -0
  381. data/lib/hecks/runtime.rb +107 -0
  382. data/lib/hecks/storehouse.rb +632 -0
  383. data/lib/hecks/version.rb +16 -0
  384. data/lib/hecks/vocabulary.rb +218 -0
  385. data/lib/hecks.rb +123 -6
  386. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +90 -0
  387. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +128 -0
  388. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +160 -0
  389. metadata +412 -222
  390. data/bin/hecks +0 -7
  391. data/bin/hecks-package +0 -65
  392. data/bin/hecks_console +0 -12
  393. data/bin/hecks_serverless +0 -6
  394. data/lib/cli/build.rb +0 -14
  395. data/lib/cli/command_runner.rb +0 -28
  396. data/lib/cli/console.rb +0 -10
  397. data/lib/cli/generate.rb +0 -37
  398. data/lib/cli/hecks-cli.rb +0 -27
  399. data/lib/cli/test.rb +0 -57
  400. data/lib/console/commands.rb +0 -8
  401. data/lib/console/hecks-console.rb +0 -1
  402. data/lib/packager/README.md +0 -0
  403. data/lib/packager/app_runner.rb +0 -21
  404. data/lib/packager/args.rb +0 -26
  405. data/lib/packager/compatibility/fixnum.rb +0 -6
  406. data/lib/packager/hecks.rb +0 -39
  407. data/lib/packager/query_runner.rb +0 -21
  408. data/lib/packager/resources/Dockerfile +0 -11
  409. data/lib/packager/resources/app_binary +0 -7
  410. data/lib/packager/resources/bundle_config +0 -3
  411. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz +0 -0
  412. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-osx.tar.gz +0 -0
  413. data/lib/serverless/Domain +0 -32
  414. data/lib/serverless/cli.rb +0 -75
  415. data/lib/serverless/resources/command_name.js +0 -5
  416. data/lib/serverless/resources/environment.js +0 -7
  417. data/lib/serverless/resources/handler.js.tt +0 -28
  418. data/lib/serverless/resources/run_binary.js +0 -22
  419. data/lib/serverless/resources/serverless.yml +0 -20
@@ -0,0 +1,411 @@
1
+ require "set"
2
+ require_relative "bluebook"
3
+ require_relative "codemod"
4
+ require_relative "projections/model"
5
+ require_relative "fuzzing/properties"
6
+
7
+ module Hecks
8
+ # THE SHARED CORE behind `bin/query_ir` (a text CLI) and
9
+ # `bin/hecks_query_ir_mcp` (an MCP server exposing the same two
10
+ # queries as tools) — one implementation, two front ends, the same
11
+ # reason `Hecks::Codemod` exists once rather than per-script.
12
+ # Every method here returns structured data (Hashes/Arrays/Structs),
13
+ # never formatted text — formatting is each front end's own job.
14
+ module QueryIR
15
+ Codemod = Hecks::Codemod
16
+ Deviations = Hecks::Projections::Model::Deviations
17
+
18
+ # THE SAME MAPPING spec/model_shape_conformance_spec.rb's own
19
+ # MODEL_CONSTRUCTS holds — kept here rather than shared from the
20
+ # spec (a spec file is not a library other code should require),
21
+ # matching Deviations' own doc comment: "the generator and the gate
22
+ # read one source" — Deviations is that one source; this table is
23
+ # small and genuinely construct-list bookkeeping, not a rule that
24
+ # can drift silently the way a Deviations entry could.
25
+ CONSTRUCTS = {
26
+ "Bluebook" => Hecks::Bluebook::Chapter,
27
+ "Aggregate" => Hecks::Bluebook::Aggregate,
28
+ "Command" => Hecks::Bluebook::Command,
29
+ "Entity" => Hecks::Bluebook::Entity,
30
+ "ValueObject" => Hecks::Bluebook::ValueObject,
31
+ "Policy" => Hecks::Bluebook::Policy,
32
+ "Query" => Hecks::Bluebook::Query,
33
+ "ReadModel" => Hecks::Bluebook::ReadModel,
34
+ "ProcessManager" => Hecks::Bluebook::ProcessManager
35
+ }.freeze
36
+
37
+ Rule = Struct.new(:kind, :description, :canonical, :location, keyword_init: true)
38
+
39
+ module_function
40
+
41
+ def meta_declared(name)
42
+ Hecks::Bluebook::MetaValidator.grammar_registry
43
+ .bluebook("Bluebook").aggregate(name).attributes.map(&:name)
44
+ end
45
+
46
+ # The real structural diff between what a Ruby IR class emits
47
+ # (Class.ir_spec.keys) and what the self-hosted meta-domain declares
48
+ # for it — the SAME comparison spec/model_shape_conformance_spec.rb
49
+ # makes, reusing its own Deviations data so this can never silently
50
+ # drift from what that gate actually checks.
51
+ def construct_diff(name)
52
+ klass = CONSTRUCTS.fetch(name) {
53
+ raise ArgumentError, "no such construct #{name.inspect} — known: #{CONSTRUCTS.keys.join(', ')}"
54
+ }
55
+ declared = meta_declared(name)
56
+ emitted = klass.ir_spec.keys
57
+
58
+ accounted = declared.reject { |field| Deviations::PARENT_REF.call(field) } -
59
+ Deviations::JUDGE_ONLY -
60
+ Deviations.folded(name).values.flatten -
61
+ Deviations.off_the_wire(name) -
62
+ Deviations.dynamic_tail(name) -
63
+ Deviations.unpacked(name).keys
64
+
65
+ unaccounted = emitted -
66
+ declared -
67
+ Deviations.contained(name) -
68
+ Deviations.folded(name).keys -
69
+ Deviations.computed(name) -
70
+ Deviations.unpacked(name).values.flatten
71
+
72
+ { name: name, declared: declared, emitted: emitted,
73
+ missing_from_ruby: accounted - emitted, unaccounted_in_ruby: unaccounted }
74
+ end
75
+
76
+ def constructs(names = [])
77
+ targets = names.empty? ? CONSTRUCTS.keys : names
78
+ targets.map { |name| construct_diff(name) }
79
+ end
80
+
81
+ # Every given/ensures/invariant DECLARATION reachable from a booted
82
+ # registry, walked recursively — every owner's own `.preconditions`/
83
+ # `.invariants` (block-declared rules), every value object's own
84
+ # `.invariants`, and every command's own `.givens`/`.ensures`
85
+ # (`.givens` too, not just `.ensures` — a command's own LOCAL
86
+ # `given("x") { block }` not yet hoisted to its owner, round 4's own
87
+ # starting shape, would otherwise be invisible).
88
+ #
89
+ # NOT keyed by object identity (a real, hard-won correction — see
90
+ # the comment on `duplicates`' own dedup below for why: a bare
91
+ # `given("x")` reference and its owner's own block declaration are
92
+ # the SAME Ruby object at DSL build time, but `MetaValidator.call`
93
+ # (S14 — every bluebook is judged by dispatching its OWN IR into the
94
+ # self-hosted grammar, then reconstructed via `Assembly.call` from
95
+ # flat rows) rebuilds the whole graph fresh from there. By the time
96
+ # any caller reads `chapter.aggregates`, EVERY given/invariant is
97
+ # already a distinct object, whether it was block-declared or
98
+ # bare-referenced — object identity carries no signal past that
99
+ # point, for any construct, not just this one).
100
+ def collect_rules(registry, chapter_name = nil)
101
+ rules = []
102
+ chapters = chapter_name ? [registry.bluebook(chapter_name)] : registry.bluebooks.values
103
+
104
+ walk_construct = lambda do |construct, path|
105
+ (construct.respond_to?(:preconditions) ? construct.preconditions : []).each do |rule|
106
+ rules << Rule.new(kind: "given", description: rule.description, canonical: rule.canonical,
107
+ location: "#{path} (declared)")
108
+ end
109
+ (construct.respond_to?(:invariants) ? construct.invariants : []).each do |rule|
110
+ rules << Rule.new(kind: "invariant", description: rule.description, canonical: rule.canonical,
111
+ location: "#{path} (declared)")
112
+ end
113
+ construct.commands.each do |command|
114
+ command.givens.each do |rule|
115
+ rules << Rule.new(kind: "given", description: rule.description, canonical: rule.canonical,
116
+ location: "#{path}.#{command.hecks_name}")
117
+ end
118
+ command.ensures.each do |rule|
119
+ rules << Rule.new(kind: "ensures", description: rule.description, canonical: rule.canonical,
120
+ location: "#{path}.#{command.hecks_name}")
121
+ end
122
+ end
123
+ construct.entities.each { |piece|
124
+ walk_construct.call(piece, "#{path}.#{piece.hecks_name}")
125
+ } if construct.respond_to?(:entities)
126
+ end
127
+
128
+ chapters.each do |chapter|
129
+ chapter.aggregates.each do |aggregate|
130
+ walk_construct.call(aggregate, aggregate.hecks_name)
131
+ aggregate.value_objects.each do |vo|
132
+ vo.invariants.each do |rule|
133
+ rules << Rule.new(kind: "invariant", description: rule.description, canonical: rule.canonical,
134
+ location: "#{aggregate.hecks_name}::#{vo.hecks_name} (declared)")
135
+ end
136
+ end
137
+ end
138
+ end
139
+ rules
140
+ end
141
+
142
+ # A rule's OWNER — the construct path a "(declared)" location names
143
+ # directly, or (for a command-level `.givens`/`.ensures` entry) the
144
+ # path with its trailing `.CommandName` segment stripped. Two rules
145
+ # sharing an owner are the SAME declaration read twice (an owner's
146
+ # own precondition, and a command under it referencing that
147
+ # precondition by name) — not two independent ones.
148
+ #
149
+ # PUBLIC, not a `duplicates`-only internal — `bin/codemod_hoist_
150
+ # local_givens` reads it directly to group `collect_rules`' own
151
+ # output by owner itself, the same reading `duplicates`' own
152
+ # `declaration_count` makes.
153
+ def owner_of(location)
154
+ return location.sub(/ \(declared\)\z/, "") if location.end_with?(" (declared)")
155
+
156
+ location.rpartition(".").first
157
+ end
158
+
159
+ # Grouped by (kind, description, canonical), not canonical text
160
+ # alone — a generic one-liner like `!value.to_s.empty?` legitimately
161
+ # recurs dozens of times for unrelated fields; the real signal is
162
+ # the SAME RULE (same description, same predicate), which is also
163
+ # exactly what the given/invariant reference mechanism itself
164
+ # resolves on.
165
+ #
166
+ # DEDUPED BY OWNER, not object identity (`collect_rules`' own
167
+ # comment has the full story — identity is gone by the time this
168
+ # reads the registry). Within a group, every command-level rule
169
+ # whose OWNER already has its own "(declared)" entry in the same
170
+ # group is just that declaration read again through a reference —
171
+ # `Account.Open`/`Account.Credit`/etc. all naming `Account`'s own
172
+ # `given("customer is active")` count as Account's ONE declaration,
173
+ # not nine. A command-level rule with no matching owner declaration
174
+ # (a LOCAL, not-yet-hoisted `given("x") { block }`) counts as its
175
+ # own standalone declaration — two different commands independently
176
+ # writing the identical local predicate IS two declarations, a real
177
+ # hoisting opportunity. A group is reported only when it adds up to
178
+ # MORE than one real declaration this way.
179
+ #
180
+ # `domains: []` means "the self-hosted meta-domain only" — pass real
181
+ # domain directories explicitly to include them, or `nil` (the
182
+ # default) for meta-domain plus every real example.
183
+ def duplicates(domains: nil, include_meta: true)
184
+ domains ||= Codemod::EXAMPLE_ROOTS
185
+ all_rules = []
186
+ all_rules.concat(collect_rules(Codemod.meta_registry)) if include_meta
187
+
188
+ domains.each do |domain_dir|
189
+ bluebook_files = Dir.glob(File.join(domain_dir, "bluebook", "*.bluebook"))
190
+ next if bluebook_files.empty?
191
+
192
+ registry = Codemod.load_bluebook(bluebook_files)
193
+ all_rules.concat(collect_rules(registry))
194
+ end
195
+
196
+ all_rules.group_by { |r| [r.kind, r.description, r.canonical] }
197
+ .map { |key, rules| [key, rules, declaration_count(rules)] }
198
+ .select { |_, _, count| count > 1 }
199
+ .map { |(kind, description, canonical), rules, _|
200
+ { kind: kind, description: description, canonical: canonical,
201
+ locations: rules.map(&:location) }
202
+ }
203
+ end
204
+
205
+ # `given` ONLY (not `invariant`/`ensures`) also covers CROSS-ENTITY
206
+ # coverage — one piece's own entity-level declaration, shared with
207
+ # any OTHER piece nested under the SAME root aggregate (real corpus
208
+ # this closes: SafeDepositBox's own `Visit`/`KeyIssuance`, two
209
+ # different pieces on one head). A rule owned by a nested entity
210
+ # (its own owner path has more than one segment) is covered when
211
+ # SOME "(declared)" entry exists ANYWHERE under that SAME root
212
+ # aggregate — not just under its OWN exact owner — matching the
213
+ # DSL's own pool, threaded unchanged through an aggregate's whole
214
+ # entity tree (`AggregateBuilder#entity`'s own comment).
215
+ #
216
+ # A KNOWN, ACCEPTED GAP this does NOT (and structurally cannot)
217
+ # close: CHAPTER-WIDE given sharing (`AggregateBuilder#given`'s own
218
+ # bare form, `docs/implemented/resolution-rules/chapter-given.md`) — `Account`,
219
+ # `SafeDepositBox`, and `OnboardingCase` each still show as their
220
+ # own "(declared)" owner here even AFTER `SafeDepositBox`/
221
+ # `OnboardingCase` were converted to bare chapter-wide references,
222
+ # because a REFERENCED given still write-throughs into its own
223
+ # aggregate's `@named_givens` — the SAME reason `collect_rules`'
224
+ # own top comment already gives for why object identity carries no
225
+ # signal past a bluebook's own build: the EXPORTED IR cannot tell
226
+ # "I declared this myself" apart from "I referenced someone else's
227
+ # declaration," because by the time anything reads `chapter.
228
+ # aggregates`, both look identical. Closing this would mean reading
229
+ # SOURCE TEXT (bare `given(desc)` vs. block `given(desc) { ... }`),
230
+ # not the built IR this query is deliberately built on — a
231
+ # different, source-level tool, not a fix to this one. Treat a
232
+ # still-flagged group naming multiple aggregates as "verify by
233
+ # hand whether this is ALREADY a chapter-wide reference before
234
+ # assuming it's fresh duplication," not as an automatic signal
235
+ # either way.
236
+ #
237
+ # THE IDENTICAL GAP, ONE LEVEL DOWN: chapter-wide ENTITY-scoped
238
+ # sharing (`EntityBuilder#given`'s own bare form,
239
+ # `docs/implemented/resolution-rules/chapter-entity-given.md`) hits this same wall for
240
+ # the same structural reason — `SafeDepositBox.Visit` still shows
241
+ # as its own "(declared)" owner here even after becoming a bare
242
+ # reference to `Account.LedgerEntry`'s declaration, because a piece
243
+ # resolving a chapter-wide reference still write-throughs the
244
+ # resolved `Given` into its own `@named_givens` (so ITS OWN
245
+ # commands can read it back locally without a second hop). This is
246
+ # not a NEW limitation this feature introduces — it is the exact
247
+ # same IR-cannot-distinguish-declared-from-referenced fact, one
248
+ # scope wider. `bin/query_ir duplicates` confirms this directly:
249
+ # `Account.LedgerEntry (declared)` and `SafeDepositBox.Visit
250
+ # (declared)` both appear under the same `given: "customer is
251
+ # active"` group — verify by hand, same as the aggregate-level
252
+ # case above, before assuming a group naming two pieces under
253
+ # different aggregates is fresh duplication rather than an already-
254
+ # resolved chapter-wide reference.
255
+ def declaration_count(rules)
256
+ declared = rules.select { |r| r.location.end_with?(" (declared)") }
257
+ declared_owners = declared.map { |r| owner_of(r.location) }.to_set
258
+ declared_given_roots = declared.select { |r| r.kind == "given" }
259
+ .map { |r| owner_of(r.location).split(".").first }.to_set
260
+
261
+ standalone = rules.reject do |r|
262
+ owner = owner_of(r.location)
263
+ next true if declared_owners.include?(owner)
264
+ next true if r.kind == "given" && owner.include?(".") && declared_given_roots.include?(owner.split(".").first)
265
+
266
+ false
267
+ end
268
+
269
+ declared_owners.size + standalone.size
270
+ end
271
+ private_class_method :declaration_count
272
+
273
+ # SHARED TEXT FORMATTING — both `bin/query_ir` (a text CLI) and
274
+ # `bin/hecks_query_ir_mcp` (an MCP tool result, itself a text
275
+ # block) want the identical human-readable rendering; only the
276
+ # OUTER framing differs (plain stdout vs. a JSON-RPC content array).
277
+ def format_constructs(diffs)
278
+ diffs.map do |diff|
279
+ lines = ["== #{diff[:name]} =="]
280
+ lines << " emits: #{diff[:emitted].join(', ')}"
281
+ lines << " declares: #{diff[:declared].join(', ')}"
282
+ if diff[:missing_from_ruby].empty? && diff[:unaccounted_in_ruby].empty?
283
+ lines << " clean — every declared field is emitted (or a named deviation), nothing emitted is undeclared"
284
+ else
285
+ lines << " MISSING FROM RUBY (declared, not emitted, not a named deviation): #{diff[:missing_from_ruby].join(', ')}" unless diff[:missing_from_ruby].empty?
286
+ lines << " UNACCOUNTED IN RUBY (emitted, not declared, not a named deviation): #{diff[:unaccounted_in_ruby].join(', ')}" unless diff[:unaccounted_in_ruby].empty?
287
+ end
288
+ lines.join("\n")
289
+ end.join("\n\n")
290
+ end
291
+
292
+ # ONE HAND-TYPED CONSTRUCT-NAME PER RECONSTRUCTION METHOD — the only
293
+ # two `MetaValidator::Reconstruction` methods NOT driven generically
294
+ # through `Assembly::Contracts`' own table (its own header explains
295
+ # why: `aggregate(row)`/`entity(row)` predate the table and were
296
+ # never migrated). `impact_preview`'s own touchpoint 4 is checked
297
+ # ONLY for these two — every other construct is read generically, so
298
+ # asking "does Command's own reconstruction method mention this
299
+ # field" is a question with no method to check.
300
+ RECONSTRUCTION_METHODS = { "Aggregate" => :aggregate, "Entity" => :entity }.freeze
301
+
302
+ # THE SIX TOUCHPOINTS `.claude/skills/bluebook-construct-creator/
303
+ # SKILL.md` walks in prose, checked structurally instead of by hand
304
+ # — for a construct/field pair NOT yet fully propagated (typically
305
+ # mid-round, deciding what's left), or as a sanity check before the
306
+ # final gate sweep of a round already believed done. Every check
307
+ # here is BEST-EFFORT and ADVISORY, not a gate: a `false` does not
308
+ # always mean "not yet done" (a field can be legitimately exempt —
309
+ # `Deviations`' own named categories, `GUARANTEED_BY_CONSTRUCTION`,
310
+ # or `META_DOMAIN_KNOWN_GAPS`, the last of which lives in
311
+ # spec/fuzzing/meta_domain_coverage_spec.rb, a SPEC file this
312
+ # module deliberately never requires — see `CONSTRUCTS`' own
313
+ # comment on the same principle). Read the touchpoint's own
314
+ # existing gate (`model_shape_conformance_spec.rb`,
315
+ # `assembly_spec.rb`, `meta_domain_coverage_spec.rb`) before trusting
316
+ # a `false` here as a real gap.
317
+ def impact_preview(name, field)
318
+ CONSTRUCTS.fetch(name) { raise ArgumentError, "no such construct #{name.inspect} — known: #{CONSTRUCTS.keys.join(', ')}" }
319
+ field = field.to_s
320
+
321
+ {
322
+ name: name,
323
+ field: field,
324
+ touchpoints: [
325
+ { touchpoint: "meta-domain grammar declares it", present: meta_declared(name).map(&:to_s).include?(field) },
326
+ { touchpoint: "docs/resolution-rules/ names it", present: resolution_rule_mentions?(field) },
327
+ { touchpoint: "Assembly::Contracts consumes it", present: contract_consumes?(name, field) },
328
+ { touchpoint: "Reconstruction's hand-typed method reads it", present: reconstruction_reads?(name, field) },
329
+ { touchpoint: "fuzzer FEATURE_COVERAGE/GUARANTEED_BY_CONSTRUCTION claims it", present: fuzzer_claims?(name, field) },
330
+ { touchpoint: "Rust mirror (rust/parser/src/parse/*.rs) mentions it", present: rust_mentions?(field) }
331
+ ]
332
+ }
333
+ end
334
+
335
+ def resolution_rule_mentions?(field)
336
+ paths = Dir.glob(File.join(Codemod::ROOT, "docs/resolution-rules/*.md")) +
337
+ Dir.glob(File.join(Codemod::ROOT, "docs/implemented/resolution-rules/*.md"))
338
+ paths.any? { |path| File.read(path).include?(field) }
339
+ end
340
+ private_class_method :resolution_rule_mentions?
341
+
342
+ def contract_consumes?(name, field)
343
+ contract = Hecks::Bluebook::Assembly.contract(name)
344
+ contract.fields.key?(field.to_sym) || contract.derived.key?(field.to_sym)
345
+ rescue KeyError
346
+ false
347
+ end
348
+ private_class_method :contract_consumes?
349
+
350
+ # `Method#source_location` finds where the hand-typed method starts;
351
+ # the method's own body ends at the next line indented no deeper
352
+ # than its own `def` — the same boundary Ruby itself uses, read back
353
+ # textually because there is no live AST here, only a file to grep a
354
+ # slice of. `nil` (not `false`) for every OTHER construct — this
355
+ # touchpoint genuinely does not apply to them (`RECONSTRUCTION_
356
+ # METHODS` only names the two hand-typed methods), and collapsing
357
+ # "does not apply" into "not done" would misreport a construct that
358
+ # was never supposed to have this touchpoint at all.
359
+ def reconstruction_reads?(name, field)
360
+ method_name = RECONSTRUCTION_METHODS[name]
361
+ return nil unless method_name
362
+
363
+ file, start_line = Hecks::Bluebook::MetaValidator::Reconstruction.instance_method(method_name).source_location
364
+ lines = File.readlines(file)
365
+ def_line = lines[start_line - 1]
366
+ indent = def_line[/\A\s*/]
367
+
368
+ body = lines[start_line..].take_while { |line| line.strip.empty? || line[/\A\s*/].size > indent.size || !line.lstrip.start_with?("def ") }
369
+ body.join.include?("#{field}:")
370
+ end
371
+ private_class_method :reconstruction_reads?
372
+
373
+ def fuzzer_claims?(name, field)
374
+ key = "#{name}##{field}"
375
+ Hecks::Fuzzing::Properties::FEATURE_COVERAGE.values.flatten.include?(key) ||
376
+ Hecks::Fuzzing::Properties::GUARANTEED_BY_CONSTRUCTION.key?(key)
377
+ end
378
+ private_class_method :fuzzer_claims?
379
+
380
+ def rust_mentions?(field)
381
+ Dir.glob(File.join(Codemod::ROOT, "rust/parser/src/parse/*.rs")).any? { |path| File.read(path).include?(field) }
382
+ end
383
+ private_class_method :rust_mentions?
384
+
385
+ def format_impact_preview(preview)
386
+ lines = ["== #{preview[:name]}##{preview[:field]} =="]
387
+ preview[:touchpoints].each do |t|
388
+ mark = t[:present].nil? ? "n/a" : (t[:present] ? "yes" : "NOT YET")
389
+ lines << " [#{mark.rjust(7)}] #{t[:touchpoint]}"
390
+ end
391
+ done = preview[:touchpoints].count { |t| t[:present] == true }
392
+ total = preview[:touchpoints].count { |t| !t[:present].nil? }
393
+ lines << ""
394
+ lines << "#{done}/#{total} applicable touchpoint(s) show signs of this field — advisory, not a gate; " \
395
+ "a NOT YET can be a legitimate exemption (Deviations, GUARANTEED_BY_CONSTRUCTION, or a spec-only " \
396
+ "META_DOMAIN_KNOWN_GAPS entry this module deliberately never reads)."
397
+ lines.join("\n")
398
+ end
399
+
400
+ def format_duplicates(groups)
401
+ return "no duplicate given/invariant/ensures rule found" if groups.empty?
402
+
403
+ body = groups.map do |group|
404
+ ["== #{group[:kind]}: #{group[:description].inspect} — #{group[:canonical]} ==",
405
+ *group[:locations].map { |loc| " #{loc}" }].join("\n")
406
+ end.join("\n\n")
407
+
408
+ "#{body}\n\n#{groups.size} duplicate group(s), #{groups.sum { |g| g[:locations].size }} declarations total"
409
+ end
410
+ end
411
+ end
@@ -0,0 +1,9 @@
1
+ module Hecks
2
+ module QuerySpecification
3
+ module Common
4
+ AuthorizationSpec = Struct.new(:policy, :tenant, keyword_init: true) do
5
+ def to_h = { policy: policy.to_s, tenant: tenant && tenant.to_s }
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,25 @@
1
+ require_relative "../../literal"
2
+ require_relative "../../vocabulary"
3
+
4
+ module Hecks
5
+ module QuerySpecification
6
+ module Common
7
+ # `none_in_state`, vendored addition not (yet) upstream hecks
8
+ # (migration plan task 4): a CROSS-AGGREGATE ANTI-JOIN comparator --
9
+ # `where ref: { none_in_state: "Claim:held" }` holds true when NO
10
+ # record in the named aggregate, keyed by this record's own field
11
+ # value, is currently in the named state. plan.bluebook's own
12
+ # description: "a keyed point lookup (HashMap hit), never a scan" --
13
+ # a real, deliberate, pre-existing feature (WhereOp::NoneInState in
14
+ # the old Rust runtime), not invented here -- see Runtime::
15
+ # QueryInterpreter#holds?'s own comment for the evaluation side.
16
+ COMPARATORS = Hecks::Vocabulary.symbols("QueryComparator")
17
+ end
18
+
19
+ # The specification structs' own name for the one wire spelling — see
20
+ # Hecks::Literal, which every other `to_h`-bound literal field now
21
+ # shares. Kept as a word here because the structs below read better
22
+ # saying what they are doing than naming the module that does it.
23
+ def self.render_value(value) = Literal.render(value)
24
+ end
25
+ end
@@ -0,0 +1,174 @@
1
+ require_relative "null_policy"
2
+ require_relative "../../runtime/value"
3
+
4
+ module Hecks
5
+ module QuerySpecification
6
+ module Common
7
+ # THE ONE COMPARATOR TABLE. There were two, near-identical copies of
8
+ # this — `Ports::Query::InMemory` (the path a Memory- or Heki-backed
9
+ # aggregate query actually runs) and `Runtime::QueryInterpreter`
10
+ # (entity/sub-list queries, and any adapter implementing no `:query`)
11
+ # — plus a third partial reading of `comparable` inside
12
+ # `SqlQueryBuilder#query_value`.
13
+ #
14
+ # Two copies of one rule is not a style complaint here; it has cost
15
+ # real bugs twice already, both recorded in the comments those files
16
+ # carried. `none_in_state` was added to one copy only, so an ordinary
17
+ # Memory query's `none_in_state` clause fell to the `else` branch and
18
+ # silently excluded every row. And `comparable` DIVERGED without
19
+ # anyone noticing: one copy took the FIRST numeric member of a value
20
+ # object, the other only unwrapped when there was EXACTLY ONE — so a
21
+ # value object with two numeric members compared as a number on one
22
+ # path and as a whole Hash on the other.
23
+ #
24
+ # The callers still differ in how they REACH a value — one takes a
25
+ # registry argument, the other closes over `@registry`; one digs the
26
+ # field through `FieldPath`, the other is handed it — so they keep
27
+ # their own resolution and share only the comparison itself.
28
+ module Comparison
29
+ module_function
30
+
31
+ # A value object compared as a scalar, when which scalar is meant
32
+ # is not in doubt. Exactly one numeric member is unambiguous; a
33
+ # single-member value object is unambiguous whatever its type.
34
+ # Anything else is returned UNCHANGED rather than guessed at — two
35
+ # numeric members give no reason to prefer either, and picking the
36
+ # first silently compares a field the author never named.
37
+ #
38
+ # A declaration naming an ambiguous value object is refused when
39
+ # the bluebook loads (`AggregateBuilder`'s own query-field seal),
40
+ # so this branch is a backstop rather than the primary guard: which
41
+ # member is meant is knowable at declaration time, and a refusal
42
+ # naming the candidates is worth more than any runtime reading.
43
+ def comparable(value)
44
+ value = value.to_h if value.is_a?(Runtime::Value)
45
+ return value unless value.is_a?(Hash)
46
+
47
+ numerics = value.values.select { |field| field.is_a?(Numeric) }
48
+ return numerics.first if numerics.size == 1
49
+ return value.values.first if value.size == 1
50
+
51
+ value
52
+ end
53
+
54
+ # Which members a value object offers a scalar comparison, for a
55
+ # refusal that can name them. Empty when the value object is
56
+ # unambiguous — nothing to report.
57
+ def ambiguous_members(value_object)
58
+ numerics = value_object.attributes.select { |a| NUMERIC_TYPES.include?(a.type.to_s) }
59
+ return [] if numerics.size == 1 || value_object.attributes.size == 1
60
+
61
+ value_object.attributes.map(&:name)
62
+ end
63
+
64
+ NUMERIC_TYPES = %w[Integer Float Numeric].freeze
65
+
66
+ def holds?(operation, held, want, registry: nil)
67
+ # A NULL SATISFIES NO COMPARISON — NullPolicy.unmatchable? owns
68
+ # the rule and the reasoning, including why `none_in_state` is
69
+ # exempt from it.
70
+ return false if NullPolicy.unmatchable?(operation, held, want)
71
+
72
+ case operation.to_s
73
+ when "eq" then held == want
74
+ when "ne" then held != want
75
+ when "lt" then ordered?(held, want) && held < want
76
+ when "lte" then ordered?(held, want) && held <= want
77
+ when "gt" then ordered?(held, want) && held > want
78
+ when "gte" then ordered?(held, want) && held >= want
79
+ when "in" then any_member_in?(held, want)
80
+ when "contains" then contains?(held, want)
81
+ when "none_in_state" then none_in_state?(held, want, registry)
82
+ else
83
+ # Every declared comparator has a `when` above (Vocabulary::
84
+ # QueryComparator, held equal to this list by
85
+ # spec/vocabulary_table_spec) — this is not a real runtime
86
+ # path today, only a backstop against the day a tenth
87
+ # comparator reaches the grammar and this method doesn't
88
+ # grow to match. Silently reading an unrecognized comparator
89
+ # as `eq` is exactly the failure this table already shipped
90
+ # once (see this file's own header) — an operator this
91
+ # method can't evaluate must refuse, not guess.
92
+ raise Runtime::WiringError, "no comparator handles #{operation.to_s.inspect} — add one before declaring it"
93
+ end
94
+ end
95
+
96
+ # gt/gte/lt/lte are numeric-only and silently false otherwise — a
97
+ # where-clause never raises the way a given does, and that contract
98
+ # predates the extraction (lt was already exactly this permissive).
99
+ def ordered?(held, want) = held.is_a?(Numeric) && want.is_a?(Numeric)
100
+
101
+ # `in` reads a comma-separated list — a real Array survives
102
+ # untouched (a bluebook's own in-process value, before any wire
103
+ # serialisation), each element unwrapped the same way a scalar
104
+ # field is. This is `in`'s reading of ITS ARGUMENT (a caller may
105
+ # legitimately pass "a,b,c" meaning "any of these") — unrelated to
106
+ # `contains`, which reads the STORED field. See `contains?`.
107
+ def members(value)
108
+ return value.map { |element| comparable(element).to_s } if value.is_a?(Array)
109
+
110
+ value.to_s.split(",").map(&:strip)
111
+ end
112
+
113
+ # A folded reference hop asks whether the locally-held identity is
114
+ # among the matching target identities. A has_many relationship holds
115
+ # several identities, so the same question becomes an intersection:
116
+ # does ANY held identity occur in the wanted set? Scalar `in` retains
117
+ # its existing one-candidate behavior.
118
+ def any_member_in?(held, want)
119
+ wanted = members(want)
120
+ candidates = held.is_a?(Array) ? held : [held]
121
+
122
+ candidates.any? { |candidate| wanted.include?(comparable(candidate).to_s) }
123
+ end
124
+
125
+ # `contains` means two different things depending on what is HELD —
126
+ # real ELEMENT membership for a `list_of` field (a genuine Array
127
+ # arrives already, one element one member, nothing to split), and
128
+ # plain SUBSTRING for anything else. It used to fall through to
129
+ # `members`' comma-split for the scalar case too, silently reading
130
+ # a free-text field's own comma as a separator — which the SQL
131
+ # side's `instr`/`position` never did, so the two disagreed the
132
+ # moment a scalar's real content held a comma. Matching SQL's
133
+ # substring reading here keeps every engine answering `contains`
134
+ # identically for the same declared field.
135
+ def contains?(held, want)
136
+ return members(held).include?(want.to_s) if held.is_a?(Array)
137
+
138
+ held.to_s.include?(want.to_s)
139
+ end
140
+
141
+ # A CROSS-AGGREGATE ANTI-JOIN — `where ref: { none_in_state:
142
+ # "Claim:held" }` holds when NO record in the named aggregate,
143
+ # keyed by this record's own field value, is in the named state.
144
+ # No registry — no way to look the target up — reads as "not
145
+ # excluded", the same graceful default a missing record already
146
+ # falls back to. The target is searched by bare name across every
147
+ # loaded domain; ambiguity (two domains declaring one name) picks
148
+ # the first match rather than refusing, since a where-clause never
149
+ # raises (see `ordered?`).
150
+ def none_in_state?(held, want, registry)
151
+ return true unless registry
152
+
153
+ aggregate_name, state = want.to_s.split(":", 2)
154
+ target = find_aggregate_by_name(registry, aggregate_name)
155
+ return true unless target
156
+
157
+ target_domain, target_ir = target
158
+ record = registry.repository(target_domain, target_ir).find(held)
159
+ return true unless record
160
+
161
+ comparable(record.state[:state]) != state
162
+ end
163
+
164
+ def find_aggregate_by_name(registry, name)
165
+ registry.bluebooks.each do |domain, bluebook|
166
+ aggregate = bluebook.aggregates.find { |a| a.hecks_name == name }
167
+ return [domain, aggregate] if aggregate
168
+ end
169
+ nil
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,9 @@
1
+ module Hecks
2
+ module QuerySpecification
3
+ module Common
4
+ CursorSpec = Struct.new(:value, keyword_init: true) do
5
+ def to_h = { value: QuerySpecification.render_value(value) }
6
+ end
7
+ end
8
+ end
9
+ end