hecks 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (408) hide show
  1. checksums.yaml +5 -5
  2. data/lib/hecks/adapters/driven/claude_code.adapter +3 -0
  3. data/lib/hecks/adapters/driven/claude_code.rb +127 -0
  4. data/lib/hecks/adapters/driven/d1.adapter +12 -0
  5. data/lib/hecks/adapters/driven/d1.rb +403 -0
  6. data/lib/hecks/adapters/driven/folder.adapter +3 -0
  7. data/lib/hecks/adapters/driven/folder.rb +199 -0
  8. data/lib/hecks/adapters/driven/google_authentication.adapter +3 -0
  9. data/lib/hecks/adapters/driven/google_authentication.rb +100 -0
  10. data/lib/hecks/adapters/driven/governance_authorization.adapter +3 -0
  11. data/lib/hecks/adapters/driven/governance_authorization.rb +62 -0
  12. data/lib/hecks/adapters/driven/heki/journal.rb +51 -0
  13. data/lib/hecks/adapters/driven/heki/saga_store.rb +0 -0
  14. data/lib/hecks/adapters/driven/heki/snapshot.rb +38 -0
  15. data/lib/hecks/adapters/driven/heki.adapter +4 -0
  16. data/lib/hecks/adapters/driven/heki.rb +148 -0
  17. data/lib/hecks/adapters/driven/identity_registry.adapter +3 -0
  18. data/lib/hecks/adapters/driven/identity_registry.rb +26 -0
  19. data/lib/hecks/adapters/driven/in_memory_ordering.rb +51 -0
  20. data/lib/hecks/adapters/driven/lambda/client.rb +63 -0
  21. data/lib/hecks/adapters/driven/lambda.adapter +4 -0
  22. data/lib/hecks/adapters/driven/lambda.rb +131 -0
  23. data/lib/hecks/adapters/driven/memory.adapter +3 -0
  24. data/lib/hecks/adapters/driven/memory.rb +103 -0
  25. data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +3 -0
  26. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +28 -0
  27. data/lib/hecks/adapters/driven/postgres/codec.rb +80 -0
  28. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +168 -0
  29. data/lib/hecks/adapters/driven/postgres.adapter +5 -0
  30. data/lib/hecks/adapters/driven/postgres.rb +391 -0
  31. data/lib/hecks/adapters/driven/postgres_era/lineage/era_store.rb +171 -0
  32. data/lib/hecks/adapters/driven/postgres_era/lineage/field_cache.rb +190 -0
  33. data/lib/hecks/adapters/driven/postgres_era/lineage/head_compiler.rb +429 -0
  34. data/lib/hecks/adapters/driven/postgres_era/lineage/mint_transaction.rb +166 -0
  35. data/lib/hecks/adapters/driven/postgres_era/lineage/provisioning.rb +286 -0
  36. data/lib/hecks/adapters/driven/postgres_era/lineage/resumable_backfill.rb +168 -0
  37. data/lib/hecks/adapters/driven/postgres_era/lineage/tail_merge.rb +163 -0
  38. data/lib/hecks/adapters/driven/postgres_era/lineage/transform_installer.rb +114 -0
  39. data/lib/hecks/adapters/driven/postgres_era/lineage.rb +137 -0
  40. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/coverage_check.rb +89 -0
  41. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/era_resolver.rb +82 -0
  42. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/merge_coordinator.rb +43 -0
  43. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/minter.rb +132 -0
  44. data/lib/hecks/adapters/driven/postgres_era/lineage_manager.rb +74 -0
  45. data/lib/hecks/adapters/driven/postgres_era.adapter +6 -0
  46. data/lib/hecks/adapters/driven/postgres_era.rb +671 -0
  47. data/lib/hecks/adapters/driven/prism.adapter +3 -0
  48. data/lib/hecks/adapters/driven/prism.rb +80 -0
  49. data/lib/hecks/adapters/driven/secure_random_identity.adapter +3 -0
  50. data/lib/hecks/adapters/driven/secure_random_identity.rb +14 -0
  51. data/lib/hecks/adapters/driven/sql_query_builder.rb +221 -0
  52. data/lib/hecks/adapters/driven/sqlite/codec.rb +63 -0
  53. data/lib/hecks/adapters/driven/sqlite/projection.rb +105 -0
  54. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +180 -0
  55. data/lib/hecks/adapters/driven/sqlite.adapter +9 -0
  56. data/lib/hecks/adapters/driven/sqlite.rb +285 -0
  57. data/lib/hecks/adapters/driven/system_clock.adapter +3 -0
  58. data/lib/hecks/adapters/driven/system_clock.rb +14 -0
  59. data/lib/hecks/adapters/driven.rb +36 -0
  60. data/lib/hecks/adapters.rb +6 -0
  61. data/lib/hecks/behaviors/dsl.rb +96 -0
  62. data/lib/hecks/behaviors/expectations.rb +280 -0
  63. data/lib/hecks/behaviors/ir.rb +31 -0
  64. data/lib/hecks/behaviors/rspec.rb +42 -0
  65. data/lib/hecks/behaviors/runner.rb +96 -0
  66. data/lib/hecks/behaviors.rb +25 -0
  67. data/lib/hecks/bluebook/aggregate.rb +108 -0
  68. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +134 -0
  69. data/lib/hecks/bluebook/assembly/build.rb +48 -0
  70. data/lib/hecks/bluebook/assembly/contract.rb +112 -0
  71. data/lib/hecks/bluebook/assembly/contracts.rb +409 -0
  72. data/lib/hecks/bluebook/assembly/marks.rb +227 -0
  73. data/lib/hecks/bluebook/assembly/specializer.rb +70 -0
  74. data/lib/hecks/bluebook/assembly.rb +78 -0
  75. data/lib/hecks/bluebook/attribute.rb +96 -0
  76. data/lib/hecks/bluebook/behaviour/aggregate.rb +83 -0
  77. data/lib/hecks/bluebook/behaviour/attribute.rb +27 -0
  78. data/lib/hecks/bluebook/behaviour/chapter.rb +72 -0
  79. data/lib/hecks/bluebook/behaviour/command.rb +116 -0
  80. data/lib/hecks/bluebook/behaviour/domain_port.rb +25 -0
  81. data/lib/hecks/bluebook/behaviour/entity.rb +59 -0
  82. data/lib/hecks/bluebook/behaviour/hexagon.rb +50 -0
  83. data/lib/hecks/bluebook/behaviour/lifecycle.rb +51 -0
  84. data/lib/hecks/bluebook/behaviour/policy.rb +52 -0
  85. data/lib/hecks/bluebook/behaviour/process_manager.rb +38 -0
  86. data/lib/hecks/bluebook/behaviour/query.rb +10 -0
  87. data/lib/hecks/bluebook/behaviour/read_model.rb +29 -0
  88. data/lib/hecks/bluebook/behaviour/traits.rb +81 -0
  89. data/lib/hecks/bluebook/behaviour/value_object.rb +33 -0
  90. data/lib/hecks/bluebook/chapter.rb +68 -0
  91. data/lib/hecks/bluebook/command.rb +124 -0
  92. data/lib/hecks/bluebook/domain_port.rb +102 -0
  93. data/lib/hecks/bluebook/dsl/adapter_builder.rb +34 -0
  94. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +906 -0
  95. data/lib/hecks/bluebook/dsl/attribute_collector.rb +327 -0
  96. data/lib/hecks/bluebook/dsl/binding_proxy.rb +71 -0
  97. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +1018 -0
  98. data/lib/hecks/bluebook/dsl/command_builder.rb +626 -0
  99. data/lib/hecks/bluebook/dsl/const_shim.rb +81 -0
  100. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +79 -0
  101. data/lib/hecks/bluebook/dsl/entity_builder.rb +305 -0
  102. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +366 -0
  103. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +163 -0
  104. data/lib/hecks/bluebook/dsl/identity_declaration.rb +191 -0
  105. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +44 -0
  106. data/lib/hecks/bluebook/dsl/malformed.rb +7 -0
  107. data/lib/hecks/bluebook/dsl/policy_builder.rb +120 -0
  108. data/lib/hecks/bluebook/dsl/port_builder.rb +30 -0
  109. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +142 -0
  110. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +207 -0
  111. data/lib/hecks/bluebook/dsl/query_builder.rb +113 -0
  112. data/lib/hecks/bluebook/dsl/read_model_builder.rb +233 -0
  113. data/lib/hecks/bluebook/dsl/rule_reference.rb +172 -0
  114. data/lib/hecks/bluebook/dsl/translation_builder.rb +243 -0
  115. data/lib/hecks/bluebook/dsl/value_object_builder.rb +178 -0
  116. data/lib/hecks/bluebook/dsl/word_gate.rb +221 -0
  117. data/lib/hecks/bluebook/dsl/world_builder.rb +77 -0
  118. data/lib/hecks/bluebook/dsl.rb +45 -0
  119. data/lib/hecks/bluebook/entity.rb +103 -0
  120. data/lib/hecks/bluebook/expression/canonical_form.rb +55 -0
  121. data/lib/hecks/bluebook/expression/evaluator.rb +261 -0
  122. data/lib/hecks/bluebook/expression/projection.json +170 -0
  123. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +233 -0
  124. data/lib/hecks/bluebook/expression/resolver.rb +630 -0
  125. data/lib/hecks/bluebook/expression.rb +13 -0
  126. data/lib/hecks/bluebook/hexagon.rb +60 -0
  127. data/lib/hecks/bluebook/lifecycle.rb +42 -0
  128. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +54 -0
  129. data/lib/hecks/bluebook/meta_validator/judge.rb +559 -0
  130. data/lib/hecks/bluebook/meta_validator/plan.rb +332 -0
  131. data/lib/hecks/bluebook/meta_validator/port_judge.rb +47 -0
  132. data/lib/hecks/bluebook/meta_validator/readings.rb +350 -0
  133. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +312 -0
  134. data/lib/hecks/bluebook/meta_validator/shapes.rb +246 -0
  135. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +255 -0
  136. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +138 -0
  137. data/lib/hecks/bluebook/meta_validator/world_judge.rb +78 -0
  138. data/lib/hecks/bluebook/meta_validator.rb +452 -0
  139. data/lib/hecks/bluebook/model_check.rb +318 -0
  140. data/lib/hecks/bluebook/pattern_subset.rb +120 -0
  141. data/lib/hecks/bluebook/policy.rb +41 -0
  142. data/lib/hecks/bluebook/process_manager.rb +86 -0
  143. data/lib/hecks/bluebook/project_discovery.rb +30 -0
  144. data/lib/hecks/bluebook/project_loader.rb +40 -0
  145. data/lib/hecks/bluebook/project_register.rb +82 -0
  146. data/lib/hecks/bluebook/query.rb +61 -0
  147. data/lib/hecks/bluebook/read_model.rb +109 -0
  148. data/lib/hecks/bluebook/reference.rb +74 -0
  149. data/lib/hecks/bluebook/smoke_test.rb +166 -0
  150. data/lib/hecks/bluebook/synthesizer.rb +95 -0
  151. data/lib/hecks/bluebook/translation.rb +92 -0
  152. data/lib/hecks/bluebook/value_object.rb +58 -0
  153. data/lib/hecks/bluebook.rb +74 -0
  154. data/lib/hecks/codemod.rb +342 -0
  155. data/lib/hecks/construct.rb +71 -0
  156. data/lib/hecks/deploy/bluebook/deploy.bluebook +219 -0
  157. data/lib/hecks/deploy/bluebook/deploy.hecksagon +4 -0
  158. data/lib/hecks/deploy/oidc.json +18 -0
  159. data/lib/hecks/doc/reference.rb +389 -0
  160. data/lib/hecks/embryonaut_bluebook.rb +75 -0
  161. data/lib/hecks/facade/cli_door.rb +116 -0
  162. data/lib/hecks/facade/cli_runner.rb +190 -0
  163. data/lib/hecks/facade/command_request.rb +105 -0
  164. data/lib/hecks/facade/handle.rb +173 -0
  165. data/lib/hecks/facade/json_door.rb +154 -0
  166. data/lib/hecks/facade/surface/aggregate_door.rb +185 -0
  167. data/lib/hecks/facade/surface/chapter.rb +107 -0
  168. data/lib/hecks/facade/surface.rb +48 -0
  169. data/lib/hecks/facade.rb +44 -0
  170. data/lib/hecks/forms/app.rb +300 -0
  171. data/lib/hecks/forms/command_form_renderer.rb +113 -0
  172. data/lib/hecks/forms/examples/banking_console.bluebook +3 -0
  173. data/lib/hecks/forms/field_renderer.rb +170 -0
  174. data/lib/hecks/forms/field_shape.rb +232 -0
  175. data/lib/hecks/forms/html.rb +53 -0
  176. data/lib/hecks/forms/index_renderer.rb +35 -0
  177. data/lib/hecks/forms/page.rb +157 -0
  178. data/lib/hecks/forms/params.rb +132 -0
  179. data/lib/hecks/forms/query_form_renderer.rb +114 -0
  180. data/lib/hecks/forms/record_renderer.rb +115 -0
  181. data/lib/hecks/forms/record_table.rb +63 -0
  182. data/lib/hecks/forms/reference_options.rb +30 -0
  183. data/lib/hecks/forms/value_object_shape.rb +46 -0
  184. data/lib/hecks/forms.rb +54 -0
  185. data/lib/hecks/fqn.rb +94 -0
  186. data/lib/hecks/framework/bluebook/compliance.bluebook +1 -0
  187. data/lib/hecks/framework/bluebook/console_settings.bluebook +489 -0
  188. data/lib/hecks/framework/bluebook/framework.hecksagon +32 -0
  189. data/lib/hecks/framework/bluebook/governance.bluebook +130 -0
  190. data/lib/hecks/framework/bluebook/identity.bluebook +90 -0
  191. data/lib/hecks/framework/oidc.json +39 -0
  192. data/lib/hecks/framework.rb +90 -0
  193. data/lib/hecks/freezer.rb +67 -0
  194. data/lib/hecks/fuzzing/invalid_value_generator.rb +86 -0
  195. data/lib/hecks/fuzzing/isolated_boot.rb +92 -0
  196. data/lib/hecks/fuzzing/properties.rb +1146 -0
  197. data/lib/hecks/fuzzing/replay.rb +635 -0
  198. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +93 -0
  199. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +54 -0
  200. data/lib/hecks/fuzzing/sequence_generator/picker.rb +96 -0
  201. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +185 -0
  202. data/lib/hecks/fuzzing/sequence_generator.rb +130 -0
  203. data/lib/hecks/fuzzing/value_generator.rb +177 -0
  204. data/lib/hecks/fuzzing.rb +12 -0
  205. data/lib/hecks/grammar/evolve.rb +291 -0
  206. data/lib/hecks/grammar/expression.bluebook +417 -0
  207. data/lib/hecks/grammar/expression_operators.json +1152 -0
  208. data/lib/hecks/grammar/grammar.hecksagon +20 -0
  209. data/lib/hecks/grammar/oidc.json +69 -0
  210. data/lib/hecks/grammar/translation.bluebook +203 -0
  211. data/lib/hecks/grammar.rb +178 -0
  212. data/lib/hecks/ir.rb +126 -0
  213. data/lib/hecks/language/adapter.bluebook +116 -0
  214. data/lib/hecks/language/bluebook/aggregate.bluebook +581 -0
  215. data/lib/hecks/language/bluebook/attaches/paging.bluebook +76 -0
  216. data/lib/hecks/language/bluebook/bluebook.bluebook +256 -0
  217. data/lib/hecks/language/bluebook/bluebook.hecksagon +24 -0
  218. data/lib/hecks/language/bluebook/command.bluebook +437 -0
  219. data/lib/hecks/language/bluebook/entity.bluebook +316 -0
  220. data/lib/hecks/language/bluebook/policy.bluebook +172 -0
  221. data/lib/hecks/language/bluebook/process_manager.bluebook +264 -0
  222. data/lib/hecks/language/bluebook/projection.bluebook +267 -0
  223. data/lib/hecks/language/bluebook/query.bluebook +245 -0
  224. data/lib/hecks/language/bluebook/shape.bluebook +292 -0
  225. data/lib/hecks/language/bluebook/syntax.bluebook +447 -0
  226. data/lib/hecks/language/bluebook/vocabulary.bluebook +364 -0
  227. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +51 -0
  228. data/lib/hecks/language/hecksagon/domain_port.bluebook +76 -0
  229. data/lib/hecks/language/hecksagon/hecksagon.bluebook +131 -0
  230. data/lib/hecks/language/hecksagon/port_operation.bluebook +102 -0
  231. data/lib/hecks/language/oidc.json +313 -0
  232. data/lib/hecks/language/port.bluebook +92 -0
  233. data/lib/hecks/language/translation/translation.bluebook +110 -0
  234. data/lib/hecks/language/translation/translation_aggregate.bluebook +267 -0
  235. data/lib/hecks/language/world/wiring.bluebook +62 -0
  236. data/lib/hecks/language/world/world.bluebook +84 -0
  237. data/lib/hecks/literal.rb +125 -0
  238. data/lib/hecks/naming.rb +121 -0
  239. data/lib/hecks/ports/access_control.port +4 -0
  240. data/lib/hecks/ports/access_control.rb +62 -0
  241. data/lib/hecks/ports/agent/answers.rb +104 -0
  242. data/lib/hecks/ports/agent.port +4 -0
  243. data/lib/hecks/ports/agent.rb +167 -0
  244. data/lib/hecks/ports/authentication.port +4 -0
  245. data/lib/hecks/ports/authentication.rb +50 -0
  246. data/lib/hecks/ports/authorization.port +4 -0
  247. data/lib/hecks/ports/authorization.rb +59 -0
  248. data/lib/hecks/ports/clock.port +4 -0
  249. data/lib/hecks/ports/clock.rb +62 -0
  250. data/lib/hecks/ports/extraction.port +4 -0
  251. data/lib/hecks/ports/extraction.rb +37 -0
  252. data/lib/hecks/ports/identity_assignment.port +4 -0
  253. data/lib/hecks/ports/identity_assignment.rb +45 -0
  254. data/lib/hecks/ports/identity_generation.port +4 -0
  255. data/lib/hecks/ports/identity_generation.rb +49 -0
  256. data/lib/hecks/ports/identity_resolution.port +4 -0
  257. data/lib/hecks/ports/identity_resolution.rb +40 -0
  258. data/lib/hecks/ports/loading.port +4 -0
  259. data/lib/hecks/ports/loading.rb +13 -0
  260. data/lib/hecks/ports/persistence/append_only.rb +102 -0
  261. data/lib/hecks/ports/persistence/binding_policy.rb +56 -0
  262. data/lib/hecks/ports/persistence/execution.rb +18 -0
  263. data/lib/hecks/ports/persistence/lineage.rb +277 -0
  264. data/lib/hecks/ports/persistence/null_saga_store.rb +25 -0
  265. data/lib/hecks/ports/persistence/remote_runtime.rb +42 -0
  266. data/lib/hecks/ports/persistence/repository_factory.rb +30 -0
  267. data/lib/hecks/ports/persistence.port +4 -0
  268. data/lib/hecks/ports/persistence.rb +39 -0
  269. data/lib/hecks/ports/projection.port +4 -0
  270. data/lib/hecks/ports/projection.rb +76 -0
  271. data/lib/hecks/ports/query/in_memory.rb +60 -0
  272. data/lib/hecks/ports/query/ordering.rb +41 -0
  273. data/lib/hecks/ports/query.rb +36 -0
  274. data/lib/hecks/ports.rb +27 -0
  275. data/lib/hecks/projections/diagrams.rb +413 -0
  276. data/lib/hecks/projections/ir.rb +18 -0
  277. data/lib/hecks/projections/model/deviations.rb +98 -0
  278. data/lib/hecks/projections/model.rb +145 -0
  279. data/lib/hecks/projections/oidc.rb +110 -0
  280. data/lib/hecks/projections/parser_table.rb +159 -0
  281. data/lib/hecks/projections/reference.rb +38 -0
  282. data/lib/hecks/projections/shape.rb +31 -0
  283. data/lib/hecks/projections/statements.rb +110 -0
  284. data/lib/hecks/projections/vocabulary.rb +100 -0
  285. data/lib/hecks/projections.rb +32 -0
  286. data/lib/hecks/projector/cli_projector.rb +437 -0
  287. data/lib/hecks/projector/docs_projector.rb +321 -0
  288. data/lib/hecks/projector/exporter.rb +141 -0
  289. data/lib/hecks/projector/ir_projector.rb +18 -0
  290. data/lib/hecks/projector/narrate_projector.rb +243 -0
  291. data/lib/hecks/projector/target.rb +97 -0
  292. data/lib/hecks/projector.rb +186 -0
  293. data/lib/hecks/query_ir.rb +392 -0
  294. data/lib/hecks/query_specification/common/authorization_spec.rb +9 -0
  295. data/lib/hecks/query_specification/common/comparators.rb +25 -0
  296. data/lib/hecks/query_specification/common/comparison.rb +174 -0
  297. data/lib/hecks/query_specification/common/cursor_spec.rb +9 -0
  298. data/lib/hecks/query_specification/common/dsl.rb +58 -0
  299. data/lib/hecks/query_specification/common/inspection_spec.rb +9 -0
  300. data/lib/hecks/query_specification/common/limit_spec.rb +9 -0
  301. data/lib/hecks/query_specification/common/null_policy.rb +80 -0
  302. data/lib/hecks/query_specification/common/null_semantics.rb +11 -0
  303. data/lib/hecks/query_specification/common/offset_spec.rb +9 -0
  304. data/lib/hecks/query_specification/common/options.rb +32 -0
  305. data/lib/hecks/query_specification/common/order_by.rb +9 -0
  306. data/lib/hecks/query_specification/common/specification.rb +10 -0
  307. data/lib/hecks/query_specification/common/where_clause.rb +9 -0
  308. data/lib/hecks/query_specification/field_path.rb +89 -0
  309. data/lib/hecks/query_specification/hop_path.rb +130 -0
  310. data/lib/hecks/query_specification/read_model/specification.rb +18 -0
  311. data/lib/hecks/query_specification.rb +14 -0
  312. data/lib/hecks/rendering.rb +48 -0
  313. data/lib/hecks/router/namespace_installer.rb +157 -0
  314. data/lib/hecks/router.rb +70 -0
  315. data/lib/hecks/runtime/caller.rb +50 -0
  316. data/lib/hecks/runtime/capability_graph.rb +44 -0
  317. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +124 -0
  318. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +248 -0
  319. data/lib/hecks/runtime/command_interpreter.rb +413 -0
  320. data/lib/hecks/runtime/command_rules/admissibility.rb +307 -0
  321. data/lib/hecks/runtime/command_rules/arithmetic.rb +254 -0
  322. data/lib/hecks/runtime/command_rules/authorization.rb +64 -0
  323. data/lib/hecks/runtime/command_rules/emission.rb +34 -0
  324. data/lib/hecks/runtime/command_rules/references.rb +181 -0
  325. data/lib/hecks/runtime/command_rules.rb +28 -0
  326. data/lib/hecks/runtime/dependency_planning.rb +231 -0
  327. data/lib/hecks/runtime/dispatcher.rb +286 -0
  328. data/lib/hecks/runtime/entity_element.rb +253 -0
  329. data/lib/hecks/runtime/entity_interpreter.rb +228 -0
  330. data/lib/hecks/runtime/era_check.rb +136 -0
  331. data/lib/hecks/runtime/era_guard/shape_diff.rb +128 -0
  332. data/lib/hecks/runtime/era_guard.rb +193 -0
  333. data/lib/hecks/runtime/era_tamper.rb +61 -0
  334. data/lib/hecks/runtime/errors.rb +100 -0
  335. data/lib/hecks/runtime/event.rb +51 -0
  336. data/lib/hecks/runtime/identity.rb +128 -0
  337. data/lib/hecks/runtime/instance.rb +120 -0
  338. data/lib/hecks/runtime/interpreting.rb +69 -0
  339. data/lib/hecks/runtime/loader.rb +143 -0
  340. data/lib/hecks/runtime/policy_interpreter.rb +366 -0
  341. data/lib/hecks/runtime/port_operation_interpreter.rb +210 -0
  342. data/lib/hecks/runtime/query_interpreter.rb +254 -0
  343. data/lib/hecks/runtime/reaction_invocation.rb +245 -0
  344. data/lib/hecks/runtime/read_model_interpreter.rb +266 -0
  345. data/lib/hecks/runtime/rebuild_sweep.rb +74 -0
  346. data/lib/hecks/runtime/reference_hop.rb +99 -0
  347. data/lib/hecks/runtime/refusal_wording.rb +124 -0
  348. data/lib/hecks/runtime/registry/saga_persistence.rb +70 -0
  349. data/lib/hecks/runtime/registry/verification.rb +158 -0
  350. data/lib/hecks/runtime/registry.rb +218 -0
  351. data/lib/hecks/runtime/remote_dispatcher.rb +143 -0
  352. data/lib/hecks/runtime/routing.rb +96 -0
  353. data/lib/hecks/runtime/saga_interpreter/correlation.rb +97 -0
  354. data/lib/hecks/runtime/saga_interpreter.rb +301 -0
  355. data/lib/hecks/runtime/storage_shape.rb +120 -0
  356. data/lib/hecks/runtime/tenant_check.rb +84 -0
  357. data/lib/hecks/runtime/tenant_scope.rb +55 -0
  358. data/lib/hecks/runtime/value/admission.rb +112 -0
  359. data/lib/hecks/runtime/value/coercion.rb +500 -0
  360. data/lib/hecks/runtime/value/invariant_violation.rb +5 -0
  361. data/lib/hecks/runtime/value.rb +125 -0
  362. data/lib/hecks/runtime.rb +95 -0
  363. data/lib/hecks/translation/audit/approval_digest.rb +31 -0
  364. data/lib/hecks/translation/audit/layer_one.rb +37 -0
  365. data/lib/hecks/translation/audit/layer_two.rb +77 -0
  366. data/lib/hecks/translation/audit/unfed_report.rb +42 -0
  367. data/lib/hecks/translation/audit.rb +70 -0
  368. data/lib/hecks/translation/reattest.rb +72 -0
  369. data/lib/hecks/translation/rule_compiler.rb +120 -0
  370. data/lib/hecks/translation/scaffold/differ.rb +183 -0
  371. data/lib/hecks/translation/scaffold/renderer.rb +41 -0
  372. data/lib/hecks/translation/scaffold/writer.rb +28 -0
  373. data/lib/hecks/translation/scaffold.rb +30 -0
  374. data/lib/hecks/translation.rb +11 -0
  375. data/lib/hecks/version.rb +16 -0
  376. data/lib/hecks/vocabulary.rb +213 -0
  377. data/lib/hecks.rb +118 -6
  378. metadata +401 -222
  379. data/bin/hecks +0 -7
  380. data/bin/hecks-package +0 -65
  381. data/bin/hecks_console +0 -12
  382. data/bin/hecks_serverless +0 -6
  383. data/lib/cli/build.rb +0 -14
  384. data/lib/cli/command_runner.rb +0 -28
  385. data/lib/cli/console.rb +0 -10
  386. data/lib/cli/generate.rb +0 -37
  387. data/lib/cli/hecks-cli.rb +0 -27
  388. data/lib/cli/test.rb +0 -57
  389. data/lib/console/commands.rb +0 -8
  390. data/lib/console/hecks-console.rb +0 -1
  391. data/lib/packager/README.md +0 -0
  392. data/lib/packager/app_runner.rb +0 -21
  393. data/lib/packager/args.rb +0 -26
  394. data/lib/packager/compatibility/fixnum.rb +0 -6
  395. data/lib/packager/hecks.rb +0 -39
  396. data/lib/packager/query_runner.rb +0 -21
  397. data/lib/packager/resources/Dockerfile +0 -11
  398. data/lib/packager/resources/app_binary +0 -7
  399. data/lib/packager/resources/bundle_config +0 -3
  400. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz +0 -0
  401. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-osx.tar.gz +0 -0
  402. data/lib/serverless/Domain +0 -32
  403. data/lib/serverless/cli.rb +0 -75
  404. data/lib/serverless/resources/command_name.js +0 -5
  405. data/lib/serverless/resources/environment.js +0 -7
  406. data/lib/serverless/resources/handler.js.tt +0 -28
  407. data/lib/serverless/resources/run_binary.js +0 -22
  408. data/lib/serverless/resources/serverless.yml +0 -20
@@ -0,0 +1,392 @@
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
+ def declaration_count(rules)
237
+ declared = rules.select { |r| r.location.end_with?(" (declared)") }
238
+ declared_owners = declared.map { |r| owner_of(r.location) }.to_set
239
+ declared_given_roots = declared.select { |r| r.kind == "given" }
240
+ .map { |r| owner_of(r.location).split(".").first }.to_set
241
+
242
+ standalone = rules.reject do |r|
243
+ owner = owner_of(r.location)
244
+ next true if declared_owners.include?(owner)
245
+ next true if r.kind == "given" && owner.include?(".") && declared_given_roots.include?(owner.split(".").first)
246
+
247
+ false
248
+ end
249
+
250
+ declared_owners.size + standalone.size
251
+ end
252
+ private_class_method :declaration_count
253
+
254
+ # SHARED TEXT FORMATTING — both `bin/query_ir` (a text CLI) and
255
+ # `bin/hecks_query_ir_mcp` (an MCP tool result, itself a text
256
+ # block) want the identical human-readable rendering; only the
257
+ # OUTER framing differs (plain stdout vs. a JSON-RPC content array).
258
+ def format_constructs(diffs)
259
+ diffs.map do |diff|
260
+ lines = ["== #{diff[:name]} =="]
261
+ lines << " emits: #{diff[:emitted].join(', ')}"
262
+ lines << " declares: #{diff[:declared].join(', ')}"
263
+ if diff[:missing_from_ruby].empty? && diff[:unaccounted_in_ruby].empty?
264
+ lines << " clean — every declared field is emitted (or a named deviation), nothing emitted is undeclared"
265
+ else
266
+ lines << " MISSING FROM RUBY (declared, not emitted, not a named deviation): #{diff[:missing_from_ruby].join(', ')}" unless diff[:missing_from_ruby].empty?
267
+ lines << " UNACCOUNTED IN RUBY (emitted, not declared, not a named deviation): #{diff[:unaccounted_in_ruby].join(', ')}" unless diff[:unaccounted_in_ruby].empty?
268
+ end
269
+ lines.join("\n")
270
+ end.join("\n\n")
271
+ end
272
+
273
+ # ONE HAND-TYPED CONSTRUCT-NAME PER RECONSTRUCTION METHOD — the only
274
+ # two `MetaValidator::Reconstruction` methods NOT driven generically
275
+ # through `Assembly::Contracts`' own table (its own header explains
276
+ # why: `aggregate(row)`/`entity(row)` predate the table and were
277
+ # never migrated). `impact_preview`'s own touchpoint 4 is checked
278
+ # ONLY for these two — every other construct is read generically, so
279
+ # asking "does Command's own reconstruction method mention this
280
+ # field" is a question with no method to check.
281
+ RECONSTRUCTION_METHODS = { "Aggregate" => :aggregate, "Entity" => :entity }.freeze
282
+
283
+ # THE SIX TOUCHPOINTS `.claude/skills/bluebook-construct-creator/
284
+ # SKILL.md` walks in prose, checked structurally instead of by hand
285
+ # — for a construct/field pair NOT yet fully propagated (typically
286
+ # mid-round, deciding what's left), or as a sanity check before the
287
+ # final gate sweep of a round already believed done. Every check
288
+ # here is BEST-EFFORT and ADVISORY, not a gate: a `false` does not
289
+ # always mean "not yet done" (a field can be legitimately exempt —
290
+ # `Deviations`' own named categories, `GUARANTEED_BY_CONSTRUCTION`,
291
+ # or `META_DOMAIN_KNOWN_GAPS`, the last of which lives in
292
+ # spec/fuzzing/meta_domain_coverage_spec.rb, a SPEC file this
293
+ # module deliberately never requires — see `CONSTRUCTS`' own
294
+ # comment on the same principle). Read the touchpoint's own
295
+ # existing gate (`model_shape_conformance_spec.rb`,
296
+ # `assembly_spec.rb`, `meta_domain_coverage_spec.rb`) before trusting
297
+ # a `false` here as a real gap.
298
+ def impact_preview(name, field)
299
+ CONSTRUCTS.fetch(name) { raise ArgumentError, "no such construct #{name.inspect} — known: #{CONSTRUCTS.keys.join(', ')}" }
300
+ field = field.to_s
301
+
302
+ {
303
+ name: name,
304
+ field: field,
305
+ touchpoints: [
306
+ { touchpoint: "meta-domain grammar declares it", present: meta_declared(name).map(&:to_s).include?(field) },
307
+ { touchpoint: "docs/resolution-rules/ names it", present: resolution_rule_mentions?(field) },
308
+ { touchpoint: "Assembly::Contracts consumes it", present: contract_consumes?(name, field) },
309
+ { touchpoint: "Reconstruction's hand-typed method reads it", present: reconstruction_reads?(name, field) },
310
+ { touchpoint: "fuzzer FEATURE_COVERAGE/GUARANTEED_BY_CONSTRUCTION claims it", present: fuzzer_claims?(name, field) },
311
+ { touchpoint: "Rust mirror (rust/parser/src/parse/*.rs) mentions it", present: rust_mentions?(field) }
312
+ ]
313
+ }
314
+ end
315
+
316
+ def resolution_rule_mentions?(field)
317
+ paths = Dir.glob(File.join(Codemod::ROOT, "docs/resolution-rules/*.md")) +
318
+ Dir.glob(File.join(Codemod::ROOT, "docs/implemented/resolution-rules/*.md"))
319
+ paths.any? { |path| File.read(path).include?(field) }
320
+ end
321
+ private_class_method :resolution_rule_mentions?
322
+
323
+ def contract_consumes?(name, field)
324
+ contract = Hecks::Bluebook::Assembly.contract(name)
325
+ contract.fields.key?(field.to_sym) || contract.derived.key?(field.to_sym)
326
+ rescue KeyError
327
+ false
328
+ end
329
+ private_class_method :contract_consumes?
330
+
331
+ # `Method#source_location` finds where the hand-typed method starts;
332
+ # the method's own body ends at the next line indented no deeper
333
+ # than its own `def` — the same boundary Ruby itself uses, read back
334
+ # textually because there is no live AST here, only a file to grep a
335
+ # slice of. `nil` (not `false`) for every OTHER construct — this
336
+ # touchpoint genuinely does not apply to them (`RECONSTRUCTION_
337
+ # METHODS` only names the two hand-typed methods), and collapsing
338
+ # "does not apply" into "not done" would misreport a construct that
339
+ # was never supposed to have this touchpoint at all.
340
+ def reconstruction_reads?(name, field)
341
+ method_name = RECONSTRUCTION_METHODS[name]
342
+ return nil unless method_name
343
+
344
+ file, start_line = Hecks::Bluebook::MetaValidator::Reconstruction.instance_method(method_name).source_location
345
+ lines = File.readlines(file)
346
+ def_line = lines[start_line - 1]
347
+ indent = def_line[/\A\s*/]
348
+
349
+ body = lines[start_line..].take_while { |line| line.strip.empty? || line[/\A\s*/].size > indent.size || !line.lstrip.start_with?("def ") }
350
+ body.join.include?("#{field}:")
351
+ end
352
+ private_class_method :reconstruction_reads?
353
+
354
+ def fuzzer_claims?(name, field)
355
+ key = "#{name}##{field}"
356
+ Hecks::Fuzzing::Properties::FEATURE_COVERAGE.values.flatten.include?(key) ||
357
+ Hecks::Fuzzing::Properties::GUARANTEED_BY_CONSTRUCTION.key?(key)
358
+ end
359
+ private_class_method :fuzzer_claims?
360
+
361
+ def rust_mentions?(field)
362
+ Dir.glob(File.join(Codemod::ROOT, "rust/parser/src/parse/*.rs")).any? { |path| File.read(path).include?(field) }
363
+ end
364
+ private_class_method :rust_mentions?
365
+
366
+ def format_impact_preview(preview)
367
+ lines = ["== #{preview[:name]}##{preview[:field]} =="]
368
+ preview[:touchpoints].each do |t|
369
+ mark = t[:present].nil? ? "n/a" : (t[:present] ? "yes" : "NOT YET")
370
+ lines << " [#{mark.rjust(7)}] #{t[:touchpoint]}"
371
+ end
372
+ done = preview[:touchpoints].count { |t| t[:present] == true }
373
+ total = preview[:touchpoints].count { |t| !t[:present].nil? }
374
+ lines << ""
375
+ lines << "#{done}/#{total} applicable touchpoint(s) show signs of this field — advisory, not a gate; " \
376
+ "a NOT YET can be a legitimate exemption (Deviations, GUARANTEED_BY_CONSTRUCTION, or a spec-only " \
377
+ "META_DOMAIN_KNOWN_GAPS entry this module deliberately never reads)."
378
+ lines.join("\n")
379
+ end
380
+
381
+ def format_duplicates(groups)
382
+ return "no duplicate given/invariant/ensures rule found" if groups.empty?
383
+
384
+ body = groups.map do |group|
385
+ ["== #{group[:kind]}: #{group[:description].inspect} — #{group[:canonical]} ==",
386
+ *group[:locations].map { |loc| " #{loc}" }].join("\n")
387
+ end.join("\n\n")
388
+
389
+ "#{body}\n\n#{groups.size} duplicate group(s), #{groups.sum { |g| g[:locations].size }} declarations total"
390
+ end
391
+ end
392
+ 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
@@ -0,0 +1,58 @@
1
+ require_relative "authorization_spec"
2
+ require_relative "comparators"
3
+ require_relative "cursor_spec"
4
+ require_relative "inspection_spec"
5
+ require_relative "limit_spec"
6
+ require_relative "null_semantics"
7
+ require_relative "offset_spec"
8
+ require_relative "order_by"
9
+ require_relative "where_clause"
10
+
11
+ module Hecks
12
+ module QuerySpecification
13
+ module Common
14
+ module DSL
15
+ # RENAMED FROM `where`/`order_by`/`limit`/`offset`/`authorize`
16
+ # (all below) — item #13's full metaprogrammed dispatch (slice
17
+ # 4c). A SHARED mixin, same shape `attribute_impl` proved in
18
+ # slice 3: ONE renamed method each, both Query and ReadModel
19
+ # Keyword rows name it in `calls:`. `where`/`order_by` are
20
+ # bootstrap-reachable (every core chapter's own `read_model`
21
+ # filters its roster with them), so both are in
22
+ # BOOTSTRAP_CALLS_FALLBACK for the ReadModel context; `limit`/
23
+ # `offset`/`authorize` are not (checked directly).
24
+ def where_impl(clauses)
25
+ @wheres ||= []
26
+ clauses.each do |field, value|
27
+ op, operand = split_comparator(value)
28
+ @wheres << WhereClause.new(field: field, op: op, value: operand)
29
+ end
30
+ end
31
+
32
+ def order_by_impl(field, direction = :asc)
33
+ @order_by = OrderBy.new(field: field, direction: direction)
34
+ end
35
+
36
+ def limit_impl(value) = @limit = LimitSpec.new(value: value)
37
+ def offset_impl(value) = @offset = OffsetSpec.new(value: value)
38
+ def cursor(value) = @cursor = CursorSpec.new(value: value)
39
+ def authorize_impl(policy, tenant: nil) = @authorization = AuthorizationSpec.new(policy: policy, tenant: tenant)
40
+ def nulls(mode) = @null_semantics = NullSemantics.new(mode: mode)
41
+ def inspect_query(mode = :sql) = @inspection = InspectionSpec.new(mode: mode)
42
+
43
+ private
44
+
45
+ def split_comparator(value)
46
+ return [:eq, value] unless value.is_a?(Hash)
47
+
48
+ op, operand = value.first
49
+ unless value.size == 1 && COMPARATORS.include?(op.to_sym)
50
+ raise ArgumentError,
51
+ "unknown comparator #{value.inspect} — expected one of #{COMPARATORS.join(', ')}"
52
+ end
53
+ [op.to_sym, operand]
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end