hecks 1.3.0 → 1.5.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 (373) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hecks/adapters/driven/claude_code.rb +72 -7
  3. data/lib/hecks/adapters/driven/d1.rb +187 -23
  4. data/lib/hecks/adapters/driven/folder.rb +83 -10
  5. data/lib/hecks/adapters/driven/google_authentication.rb +33 -12
  6. data/lib/hecks/adapters/driven/governance_authorization.rb +87 -18
  7. data/lib/hecks/adapters/driven/heki/journal.rb +13 -4
  8. data/lib/hecks/adapters/driven/heki/saga_store.rb +56 -10
  9. data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
  10. data/lib/hecks/adapters/driven/heki.rb +106 -9
  11. data/lib/hecks/adapters/driven/identity_registry.rb +12 -2
  12. data/lib/hecks/adapters/driven/in_memory_ordering.rb +25 -3
  13. data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
  14. data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
  15. data/lib/hecks/adapters/driven/lambda/client.rb +67 -14
  16. data/lib/hecks/adapters/driven/lambda.rb +82 -35
  17. data/lib/hecks/adapters/driven/local_storage.rb +83 -10
  18. data/lib/hecks/adapters/driven/memory.rb +205 -9
  19. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +21 -1
  20. data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
  21. data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
  22. data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
  23. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
  24. data/lib/hecks/adapters/driven/postgres.rb +175 -28
  25. data/lib/hecks/adapters/driven/prism.rb +50 -11
  26. data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
  27. data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
  28. data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
  29. data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
  30. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
  31. data/lib/hecks/adapters/driven/sqlite.rb +181 -21
  32. data/lib/hecks/adapters/driven/system_clock.rb +3 -0
  33. data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
  34. data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
  35. data/lib/hecks/adapters/driven.rb +6 -4
  36. data/lib/hecks/adapters/driving/github_webhook.rb +31 -18
  37. data/lib/hecks/behaviors/dsl.rb +60 -2
  38. data/lib/hecks/behaviors/expectations.rb +190 -29
  39. data/lib/hecks/behaviors/ir.rb +12 -1
  40. data/lib/hecks/behaviors/rspec.rb +9 -1
  41. data/lib/hecks/behaviors/runner.rb +21 -2
  42. data/lib/hecks/behaviors.rb +9 -1
  43. data/lib/hecks/bluebook/aggregate.rb +43 -13
  44. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +17 -10
  45. data/lib/hecks/bluebook/assembly/build.rb +26 -5
  46. data/lib/hecks/bluebook/assembly/contract.rb +98 -23
  47. data/lib/hecks/bluebook/assembly/contracts.rb +59 -52
  48. data/lib/hecks/bluebook/assembly/marks.rb +159 -30
  49. data/lib/hecks/bluebook/assembly/specializer.rb +38 -21
  50. data/lib/hecks/bluebook/assembly.rb +32 -14
  51. data/lib/hecks/bluebook/attribute.rb +26 -12
  52. data/lib/hecks/bluebook/behaviour/aggregate.rb +43 -11
  53. data/lib/hecks/bluebook/behaviour/attribute.rb +18 -5
  54. data/lib/hecks/bluebook/behaviour/chapter.rb +76 -5
  55. data/lib/hecks/bluebook/behaviour/command.rb +55 -25
  56. data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -7
  57. data/lib/hecks/bluebook/behaviour/entity.rb +20 -8
  58. data/lib/hecks/bluebook/behaviour/hexagon.rb +30 -4
  59. data/lib/hecks/bluebook/behaviour/lifecycle.rb +27 -6
  60. data/lib/hecks/bluebook/behaviour/policy.rb +42 -17
  61. data/lib/hecks/bluebook/behaviour/process_manager.rb +39 -8
  62. data/lib/hecks/bluebook/behaviour/query.rb +6 -1
  63. data/lib/hecks/bluebook/behaviour/read_model.rb +29 -8
  64. data/lib/hecks/bluebook/behaviour/traits.rb +48 -12
  65. data/lib/hecks/bluebook/behaviour/value_object.rb +21 -9
  66. data/lib/hecks/bluebook/capabilities.rb +27 -0
  67. data/lib/hecks/bluebook/chapter.rb +51 -9
  68. data/lib/hecks/bluebook/command.rb +62 -17
  69. data/lib/hecks/bluebook/domain_port.rb +34 -9
  70. data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
  71. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
  72. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +282 -123
  73. data/lib/hecks/bluebook/dsl/attribute_collector.rb +112 -75
  74. data/lib/hecks/bluebook/dsl/binding_proxy.rb +81 -2
  75. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +486 -117
  76. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +179 -47
  77. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
  78. data/lib/hecks/bluebook/dsl/command_builder.rb +284 -122
  79. data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
  80. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
  81. data/lib/hecks/bluebook/dsl/entity_builder.rb +191 -61
  82. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
  83. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +130 -30
  84. data/lib/hecks/bluebook/dsl/identity_declaration.rb +38 -21
  85. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
  86. data/lib/hecks/bluebook/dsl/policy_builder.rb +86 -36
  87. data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
  88. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
  89. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +111 -47
  90. data/lib/hecks/bluebook/dsl/query_builder.rb +37 -8
  91. data/lib/hecks/bluebook/dsl/read_model_builder.rb +127 -52
  92. data/lib/hecks/bluebook/dsl/rule_reference.rb +97 -43
  93. data/lib/hecks/bluebook/dsl/translation_builder.rb +150 -44
  94. data/lib/hecks/bluebook/dsl/value_object_builder.rb +68 -20
  95. data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
  96. data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
  97. data/lib/hecks/bluebook/entity.rb +40 -11
  98. data/lib/hecks/bluebook/expression/ast_json.rb +128 -36
  99. data/lib/hecks/bluebook/expression/ast_reader.rb +32 -3
  100. data/lib/hecks/bluebook/expression/canonical_form.rb +55 -16
  101. data/lib/hecks/bluebook/expression/evaluator.rb +221 -43
  102. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +54 -18
  103. data/lib/hecks/bluebook/expression/resolver.rb +369 -128
  104. data/lib/hecks/bluebook/hexagon.rb +35 -1
  105. data/lib/hecks/bluebook/lifecycle.rb +12 -1
  106. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +2 -1
  107. data/lib/hecks/bluebook/meta_validator/judge.rb +126 -108
  108. data/lib/hecks/bluebook/meta_validator/plan.rb +81 -46
  109. data/lib/hecks/bluebook/meta_validator/port_judge.rb +3 -2
  110. data/lib/hecks/bluebook/meta_validator/readings.rb +200 -50
  111. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +68 -41
  112. data/lib/hecks/bluebook/meta_validator/shapes.rb +166 -21
  113. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +286 -46
  114. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +11 -10
  115. data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -5
  116. data/lib/hecks/bluebook/meta_validator.rb +235 -139
  117. data/lib/hecks/bluebook/model_check.rb +434 -104
  118. data/lib/hecks/bluebook/pattern_subset.rb +32 -10
  119. data/lib/hecks/bluebook/policy.rb +15 -13
  120. data/lib/hecks/bluebook/process_manager.rb +27 -14
  121. data/lib/hecks/bluebook/project_discovery.rb +5 -0
  122. data/lib/hecks/bluebook/project_loader.rb +40 -0
  123. data/lib/hecks/bluebook/project_register.rb +50 -6
  124. data/lib/hecks/bluebook/query.rb +31 -4
  125. data/lib/hecks/bluebook/read_model.rb +35 -15
  126. data/lib/hecks/bluebook/reference.rb +26 -13
  127. data/lib/hecks/bluebook/smoke_test.rb +46 -23
  128. data/lib/hecks/bluebook/synthesizer.rb +46 -12
  129. data/lib/hecks/bluebook/translation.rb +34 -5
  130. data/lib/hecks/bluebook/value_object.rb +29 -11
  131. data/lib/hecks/bluebook.rb +5 -6
  132. data/lib/hecks/codemod.rb +138 -50
  133. data/lib/hecks/construct.rb +21 -7
  134. data/lib/hecks/corpus.rb +438 -0
  135. data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
  136. data/lib/hecks/doc/reference.rb +200 -31
  137. data/lib/hecks/embryonaut_bluebook.rb +38 -15
  138. data/lib/hecks/facade/cli_door.rb +69 -10
  139. data/lib/hecks/facade/cli_runner.rb +105 -24
  140. data/lib/hecks/facade/command_request.rb +23 -0
  141. data/lib/hecks/facade/handle.rb +155 -35
  142. data/lib/hecks/facade/json_door.rb +106 -25
  143. data/lib/hecks/facade/surface/aggregate_door.rb +50 -27
  144. data/lib/hecks/facade/surface/chapter.rb +26 -17
  145. data/lib/hecks/facade/surface.rb +16 -3
  146. data/lib/hecks/facade.rb +15 -4
  147. data/lib/hecks/forms/app.rb +46 -30
  148. data/lib/hecks/forms/command_form_renderer.rb +70 -9
  149. data/lib/hecks/forms/field_renderer.rb +142 -6
  150. data/lib/hecks/forms/field_shape.rb +183 -19
  151. data/lib/hecks/forms/html.rb +51 -7
  152. data/lib/hecks/forms/index_renderer.rb +14 -2
  153. data/lib/hecks/forms/page.rb +14 -0
  154. data/lib/hecks/forms/params.rb +120 -23
  155. data/lib/hecks/forms/port_argument.rb +14 -2
  156. data/lib/hecks/forms/query_form_renderer.rb +65 -2
  157. data/lib/hecks/forms/record_renderer.rb +60 -2
  158. data/lib/hecks/forms/record_table.rb +28 -1
  159. data/lib/hecks/forms/reference_options.rb +24 -0
  160. data/lib/hecks/forms/value_object_shape.rb +13 -3
  161. data/lib/hecks/forms.rb +24 -4
  162. data/lib/hecks/fqn.rb +59 -1
  163. data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
  164. data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
  165. data/lib/hecks/framework/oidc.json +15 -0
  166. data/lib/hecks/framework.rb +79 -25
  167. data/lib/hecks/freezer.rb +27 -11
  168. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +241 -106
  169. data/lib/hecks/fuzzing/combination_miner.rb +178 -0
  170. data/lib/hecks/fuzzing/concurrent_dispatch.rb +241 -45
  171. data/lib/hecks/fuzzing/coverage_campaign.rb +161 -0
  172. data/lib/hecks/fuzzing/differential.rb +192 -0
  173. data/lib/hecks/fuzzing/domain_generator.rb +871 -0
  174. data/lib/hecks/fuzzing/era_boundary.rb +68 -19
  175. data/lib/hecks/fuzzing/form_census.rb +121 -20
  176. data/lib/hecks/fuzzing/generated_domain_check.rb +171 -0
  177. data/lib/hecks/fuzzing/invalid_value_generator.rb +45 -6
  178. data/lib/hecks/fuzzing/isolated_boot.rb +137 -80
  179. data/lib/hecks/fuzzing/nondeterministic.rb +79 -0
  180. data/lib/hecks/fuzzing/persistence_parity.rb +111 -21
  181. data/lib/hecks/fuzzing/properties/corrections.rb +34 -9
  182. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +300 -51
  183. data/lib/hecks/fuzzing/properties/guards.rb +86 -42
  184. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +80 -32
  185. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +40 -35
  186. data/lib/hecks/fuzzing/properties/outbox.rb +70 -32
  187. data/lib/hecks/fuzzing/properties/querying.rb +82 -28
  188. data/lib/hecks/fuzzing/properties.rb +84 -51
  189. data/lib/hecks/fuzzing/qa_settings.rb +164 -0
  190. data/lib/hecks/fuzzing/replay.rb +241 -133
  191. data/lib/hecks/fuzzing/rotation_priority.rb +48 -28
  192. data/lib/hecks/fuzzing/rust_gap_manifest.rb +139 -0
  193. data/lib/hecks/fuzzing/self_consistency.rb +315 -137
  194. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +46 -40
  195. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +18 -11
  196. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +13 -12
  197. data/lib/hecks/fuzzing/sequence_generator/picker.rb +21 -12
  198. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +59 -32
  199. data/lib/hecks/fuzzing/sequence_generator.rb +158 -23
  200. data/lib/hecks/fuzzing/shrinker.rb +309 -0
  201. data/lib/hecks/fuzzing/structural_skips.rb +37 -130
  202. data/lib/hecks/fuzzing/sweep_depth.rb +12 -4
  203. data/lib/hecks/fuzzing/target_capabilities.rb +107 -29
  204. data/lib/hecks/fuzzing/value_generator.rb +110 -22
  205. data/lib/hecks/fuzzing.rb +5 -0
  206. data/lib/hecks/grammar/evolve.rb +188 -12
  207. data/lib/hecks/grammar.rb +53 -7
  208. data/lib/hecks/ir.rb +51 -20
  209. data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
  210. data/lib/hecks/language/bluebook/policy.bluebook +11 -1
  211. data/lib/hecks/language/bluebook/vocabulary.bluebook +348 -13
  212. data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
  213. data/lib/hecks/language/oidc.json +5 -0
  214. data/lib/hecks/literal.rb +41 -9
  215. data/lib/hecks/naming.rb +112 -31
  216. data/lib/hecks/ports/access_control.rb +53 -2
  217. data/lib/hecks/ports/agent/answers.rb +83 -6
  218. data/lib/hecks/ports/agent.rb +119 -35
  219. data/lib/hecks/ports/authentication.rb +44 -4
  220. data/lib/hecks/ports/authorization.rb +50 -11
  221. data/lib/hecks/ports/clock.rb +42 -23
  222. data/lib/hecks/ports/extraction.rb +16 -0
  223. data/lib/hecks/ports/identity_assignment.rb +23 -2
  224. data/lib/hecks/ports/identity_generation.rb +17 -3
  225. data/lib/hecks/ports/identity_resolution.rb +17 -1
  226. data/lib/hecks/ports/key_vault.port +6 -0
  227. data/lib/hecks/ports/key_vault.rb +58 -0
  228. data/lib/hecks/ports/loading.rb +4 -0
  229. data/lib/hecks/ports/persistence/append_only.rb +172 -8
  230. data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
  231. data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
  232. data/lib/hecks/ports/persistence/execution.rb +4 -0
  233. data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
  234. data/lib/hecks/ports/persistence/plugin.rb +42 -4
  235. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
  236. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
  237. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
  238. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
  239. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
  240. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
  241. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
  242. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
  243. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
  244. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +118 -66
  245. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
  246. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +34 -5
  247. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
  248. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
  249. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
  250. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +33 -9
  251. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
  252. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +95 -6
  253. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
  254. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +314 -90
  255. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
  256. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
  257. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
  258. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +48 -8
  259. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
  260. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
  261. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
  262. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +58 -19
  263. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +96 -5
  264. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
  265. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +9 -1
  266. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
  267. data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
  268. data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
  269. data/lib/hecks/ports/persistence/repository_factory.rb +23 -3
  270. data/lib/hecks/ports/persistence/state_codec.rb +319 -0
  271. data/lib/hecks/ports/persistence.rb +36 -1
  272. data/lib/hecks/ports/projection.rb +61 -7
  273. data/lib/hecks/ports/query/in_memory.rb +41 -8
  274. data/lib/hecks/ports/query/ordering.rb +21 -6
  275. data/lib/hecks/ports/query.rb +35 -0
  276. data/lib/hecks/ports.rb +1 -0
  277. data/lib/hecks/projections/bootstrap_table.rb +147 -0
  278. data/lib/hecks/projections/diagrams.rb +315 -79
  279. data/lib/hecks/projections/glossary/html.rb +91 -3
  280. data/lib/hecks/projections/glossary/markdown.rb +50 -3
  281. data/lib/hecks/projections/glossary/mermaid.rb +49 -1
  282. data/lib/hecks/projections/glossary/sections.rb +1 -1
  283. data/lib/hecks/projections/glossary/sentences.rb +110 -5
  284. data/lib/hecks/projections/glossary.rb +173 -31
  285. data/lib/hecks/projections/ir.rb +1 -1
  286. data/lib/hecks/projections/model/deviations.rb +62 -17
  287. data/lib/hecks/projections/model.rb +75 -21
  288. data/lib/hecks/projections/oidc.rb +45 -8
  289. data/lib/hecks/projections/parser_table.rb +53 -12
  290. data/lib/hecks/projections/reference.rb +15 -3
  291. data/lib/hecks/projections/rust_vocabulary.rb +646 -0
  292. data/lib/hecks/projections/shape.rb +10 -2
  293. data/lib/hecks/projections/statements.rb +69 -22
  294. data/lib/hecks/projections/vocabulary.rb +26 -9
  295. data/lib/hecks/projections.rb +5 -3
  296. data/lib/hecks/projector/cli_projector.rb +245 -37
  297. data/lib/hecks/projector/docs_projector.rb +154 -28
  298. data/lib/hecks/projector/exporter.rb +104 -29
  299. data/lib/hecks/projector/ir_projector.rb +7 -1
  300. data/lib/hecks/projector/narrate_projector.rb +144 -23
  301. data/lib/hecks/projector/target.rb +42 -18
  302. data/lib/hecks/projector.rb +86 -18
  303. data/lib/hecks/query_ir.rb +94 -47
  304. data/lib/hecks/query_specification/common/comparators.rb +19 -3
  305. data/lib/hecks/query_specification/common/comparison.rb +109 -27
  306. data/lib/hecks/query_specification/common/dsl.rb +65 -9
  307. data/lib/hecks/query_specification/common/null_policy.rb +61 -15
  308. data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
  309. data/lib/hecks/query_specification/common/options.rb +25 -0
  310. data/lib/hecks/query_specification/field_path.rb +69 -15
  311. data/lib/hecks/query_specification/hop_path.rb +57 -20
  312. data/lib/hecks/query_specification/read_model/specification.rb +4 -0
  313. data/lib/hecks/rendering.rb +9 -3
  314. data/lib/hecks/router/namespace_installer.rb +16 -3
  315. data/lib/hecks/router.rb +56 -1
  316. data/lib/hecks/runtime/aggregate_lock.rb +20 -11
  317. data/lib/hecks/runtime/boot_gates.rb +21 -3
  318. data/lib/hecks/runtime/caller.rb +40 -8
  319. data/lib/hecks/runtime/capability_graph.rb +13 -2
  320. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +40 -41
  321. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +45 -67
  322. data/lib/hecks/runtime/command_interpreter.rb +195 -113
  323. data/lib/hecks/runtime/command_rules/admissibility.rb +231 -80
  324. data/lib/hecks/runtime/command_rules/arithmetic.rb +154 -96
  325. data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
  326. data/lib/hecks/runtime/command_rules/emission.rb +18 -1
  327. data/lib/hecks/runtime/command_rules/references.rb +145 -55
  328. data/lib/hecks/runtime/command_rules.rb +3 -0
  329. data/lib/hecks/runtime/dependency_planning.rb +56 -11
  330. data/lib/hecks/runtime/dispatcher.rb +263 -92
  331. data/lib/hecks/runtime/entity_element.rb +304 -67
  332. data/lib/hecks/runtime/entity_interpreter.rb +149 -88
  333. data/lib/hecks/runtime/errors.rb +37 -23
  334. data/lib/hecks/runtime/event.rb +15 -10
  335. data/lib/hecks/runtime/identity.rb +91 -23
  336. data/lib/hecks/runtime/instance.rb +103 -18
  337. data/lib/hecks/runtime/interpreting.rb +24 -16
  338. data/lib/hecks/runtime/invocation.rb +358 -0
  339. data/lib/hecks/runtime/loader.rb +105 -19
  340. data/lib/hecks/runtime/outbox.rb +164 -26
  341. data/lib/hecks/runtime/policy_interpreter.rb +73 -60
  342. data/lib/hecks/runtime/port_operation_interpreter.rb +42 -19
  343. data/lib/hecks/runtime/query_interpreter.rb +93 -74
  344. data/lib/hecks/runtime/reaction_invocation.rb +73 -28
  345. data/lib/hecks/runtime/read_model_interpreter.rb +60 -44
  346. data/lib/hecks/runtime/rebuild_sweep.rb +32 -4
  347. data/lib/hecks/runtime/reference_hop.rb +48 -6
  348. data/lib/hecks/runtime/refusal_wording.rb +142 -115
  349. data/lib/hecks/runtime/registry/saga_persistence.rb +32 -21
  350. data/lib/hecks/runtime/registry/verification.rb +153 -28
  351. data/lib/hecks/runtime/registry.rb +202 -32
  352. data/lib/hecks/runtime/remote_dispatcher.rb +125 -24
  353. data/lib/hecks/runtime/routing.rb +36 -154
  354. data/lib/hecks/runtime/saga_interpreter/correlation.rb +25 -27
  355. data/lib/hecks/runtime/saga_interpreter.rb +90 -76
  356. data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
  357. data/lib/hecks/runtime/tenant_check.rb +33 -13
  358. data/lib/hecks/runtime/tenant_scope.rb +23 -5
  359. data/lib/hecks/runtime/value/admission.rb +75 -30
  360. data/lib/hecks/runtime/value/coercion.rb +555 -142
  361. data/lib/hecks/runtime/value/entity_list_coercion.rb +132 -60
  362. data/lib/hecks/runtime/value.rb +71 -21
  363. data/lib/hecks/runtime.rb +39 -7
  364. data/lib/hecks/storehouse.rb +368 -72
  365. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
  366. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
  367. data/lib/hecks/version.rb +3 -3
  368. data/lib/hecks/vocabulary.rb +205 -4
  369. data/lib/hecks.rb +91 -11
  370. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +19 -11
  371. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +24 -12
  372. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +38 -14
  373. metadata +28 -2
@@ -13,28 +13,31 @@ module Hecks
13
13
  # working, golden-tested example of the shape every future projector
14
14
  # (`:rust`, `:ul`, `:openid`, ...) is meant to follow.
15
15
  #
16
- # The unit is ONE bluebook's IR, not a whole booted registry — matching
16
+ # The unit is one bluebook's IR, not a whole booted registry — matching
17
17
  # every real projection target (Rust/UL/OIDC all project one domain at
18
18
  # a time), and deliberately narrower than `Exporter.call`'s own
19
19
  # multi-domain shape.
20
- # THREE KINDS OF "PROJECT", TOLD APART BY WHAT THEY NEED AS INPUT.
21
- # Only the first belongs in this registry.
22
20
  #
23
- # A PROJECTION takes a chapter's DECLARATION and answers something
24
- # that DESCRIBES the domain: its IR, its storage shape, an OIDC scope
21
+ # ## Three kinds of "project"
22
+ #
23
+ # Told apart by what they need as input. Only the first belongs in this
24
+ # registry.
25
+ #
26
+ # a projection takes a chapter's declaration and answers something
27
+ # that describes the domain: its IR, its storage shape, an OIDC scope
25
28
  # manifest, the parser's keyword table, the reference pages. Inert,
26
29
  # derived, and runnable against any chapter that carries what it
27
30
  # declares it needs. These are what `register` holds.
28
31
  #
29
- # AN EXPORT takes a declaration AND its BINDINGS and answers
30
- # something that IS the domain, running elsewhere — rust/project.rb's
32
+ # an export takes a declaration and its bindings and answers
33
+ # something that is the domain, running elsewhere — rust/project.rb's
31
34
  # generated crate, the WASM artifact, the SAM template
32
35
  # bin/project_deploy renders. It needs the `.world`/`.hecksagon` a
33
36
  # projection never looks at, because a running system has to know how
34
37
  # it is wired. That is the whole reason bin/project_deploy cannot use
35
38
  # this protocol: `call(bluebook:, options:)` has no channel for it.
36
39
  #
37
- # A STATE PROJECTION takes RECORDS — a domain after dispatch — and is
40
+ # a state projection takes records — a domain after dispatch — and is
38
41
  # a read-model question wearing the same word.
39
42
  # `bin/expression_projection` is the one of these: its operators are
40
43
  # not declared anywhere, they are what exists after
@@ -42,8 +45,10 @@ module Hecks
42
45
  # dispatches. Converting it into this registry would be a category
43
46
  # error, however much its name suggests otherwise.
44
47
  #
45
- # ONE WORD, THREE OTHER MEANINGS — worth naming too, because grepping
46
- # "projection" turns all of these up and none is the above:
48
+ # ## One word, three other meanings
49
+ #
50
+ # Worth naming too, because grepping "projection" turns all of these up
51
+ # and none is the above:
47
52
  #
48
53
  # Ports::Projection read-model catch-up, events folded into state
49
54
  # bin/project forces that catch-up by hand
@@ -61,14 +66,33 @@ module Hecks
61
66
  # deliberately unguarded: a spec re-registering a stub under the same
62
67
  # name between examples is the ordinary case, not a footgun to fence
63
68
  # against.
69
+ # Adds `projector` to the registry under `name`, replacing anything
70
+ # already registered there.
71
+ #
72
+ # @param name [String, Symbol] the key `projector` is looked up by (converted to a symbol)
73
+ # @param projector [Module, Class, #call] anything answering `call(bluebook:, options:)`
74
+ # @return [void]
64
75
  def register(name, projector)
65
76
  registry[name.to_sym] = projector
66
77
  end
67
78
 
79
+ # Runs the projector registered under `name` against `bluebook`,
80
+ # after refusing a construct it does not admit.
81
+ #
68
82
  # `bluebook:` is kept as the keyword because it is the shipped
69
83
  # spelling and every existing caller uses it — but what it accepts is
70
84
  # any construct that emits IR, and `admits!` is what decides whether
71
- # THIS target can actually take the one handed over.
85
+ # this target can actually take the one handed over.
86
+ #
87
+ # @param name [String, Symbol] the registered projector's key
88
+ # @param bluebook [Bluebook::Behaviour::Chapter, Hecks::IR] the chapter or
89
+ # IR-emitting construct to project
90
+ # @param options [Hash] projector-specific options, passed through unchanged
91
+ # @return [Object] whatever the projector's own `call` returns: typically a
92
+ # `Hash`/`String` artifact, or a `Hash{String => String}` file tree
93
+ # @raise [UnknownProjector] if no projector is registered under `name`
94
+ # @raise [WrongConstruct] if `bluebook` lacks a capability or aggregate the
95
+ # projector requires
72
96
  def call(name, bluebook:, options: {})
73
97
  projector = registry.fetch(name.to_sym) do
74
98
  raise UnknownProjector, "no projector registered for #{name.inspect} — registered: #{registered.sort.inspect}"
@@ -77,16 +101,27 @@ module Hecks
77
101
  projector.call(bluebook: bluebook, options: options)
78
102
  end
79
103
 
80
- # A projection names the CAPABILITIES it needs; this refuses a
81
- # construct that lacks one, before the projector runs.
104
+ # Refuses `construct` if it lacks a capability or declared aggregate
105
+ # `projector` requires. A projection names the capabilities it needs;
106
+ # this is what enforces that, before the projector runs.
82
107
  #
83
- # ONE CHECK COVERS BOTH SHAPES. An ordinary construct INCLUDES its
108
+ # One check covers both shapes. An ordinary construct includes its
84
109
  # capabilities and a class-shaped one — Command, Entity, ValueObject
85
- # — EXTENDS them, and `is_a?` consults the singleton chain, so it
110
+ # — extends them, and `is_a?` consults the singleton chain, so it
86
111
  # answers for an extended module as readily as an included one. This
87
112
  # started as two checks on the assumption it would not; a spec
88
113
  # asserting the assumption failed, which is the only reason the
89
114
  # redundant half was noticed.
115
+ #
116
+ # @param name [String, Symbol] the projector's registered key, used in the message
117
+ # when refusing
118
+ # @param projector [Module, Class, #call] the target being checked; consulted for
119
+ # `projection_requires` and `projection_declares` when it answers them
120
+ # @param construct [Bluebook::Behaviour::Chapter, Hecks::IR] the chapter or
121
+ # IR-emitting construct offered to the projector
122
+ # @return [void]
123
+ # @raise [WrongConstruct] if `construct` lacks a required capability, or the
124
+ # chapter it is declares no aggregate the projector needs
90
125
  def admits!(name, projector, construct)
91
126
  needed = projector.respond_to?(:projection_requires) ? projector.projection_requires : []
92
127
  missing = needed.reject { |capability| capable?(construct, capability) }
@@ -105,18 +140,38 @@ module Hecks
105
140
  "#{construct.name} declares no such aggregate."
106
141
  end
107
142
 
143
+ # Tells whether `construct` has the capability `admits!` requires of it.
144
+ #
145
+ # @param construct [Bluebook::Behaviour::Chapter, Hecks::IR] the construct to check
146
+ # @param capability [Module] the capability module to check for
147
+ # @return [Boolean] true if `construct` is a `capability`
108
148
  def capable?(construct, capability) = construct.is_a?(capability)
109
149
 
110
- # What KIND of artifact a registered target emits — asked of the
150
+ # What kind of artifact a registered target emits — asked of the
111
151
  # projection rather than inferred from what it returned.
152
+ #
153
+ # @param name [String, Symbol] the registered projector's key
154
+ # @return [Symbol] `:files` for a path => contents tree, `:artifact` (the
155
+ # default, including for an unregistered `name`) for a single Hash or String
112
156
  def emits_for(name)
113
157
  projector = registry.fetch(name.to_sym) { return :artifact }
114
158
  projector.respond_to?(:projection_emits) ? projector.projection_emits : :artifact
115
159
  end
116
160
 
161
+ # Tells whether a projector is registered under `name`.
162
+ #
163
+ # @param name [String, Symbol] the key to look up
164
+ # @return [Boolean] true if a projector is registered under `name`
117
165
  def registered?(name) = registry.key?(name.to_sym)
166
+
167
+ # Lists every key currently registered.
168
+ #
169
+ # @return [Array<Symbol>] every key currently registered
118
170
  def registered = registry.keys
119
171
 
172
+ # Gives the live registry, initializing it on first use.
173
+ #
174
+ # @return [Hash{Symbol => Module, Class, #call}] the live key => projector registry
120
175
  def registry
121
176
  @registry ||= {}
122
177
  end
@@ -126,13 +181,17 @@ module Hecks
126
181
  # (`:oidc`). Both resolve here, so the constant form is added
127
182
  # surface rather than a replacement — every `Projector.call(:ir, ...)`
128
183
  # written before this existed keeps working untouched.
184
+ #
185
+ # @param target [String, Symbol, #projection_key] a registered key, or a target
186
+ # that declared its own key with `Target#projects_as`
187
+ # @return [String, Symbol] the key to call the target under
129
188
  def key_for(target)
130
189
  return target.projection_key if target.respond_to?(:projection_key) && target.projection_key
131
190
 
132
191
  target
133
192
  end
134
193
 
135
- # WRITING IS THE CALLER'S CHOICE, NOT THE PROJECTOR'S. A projector
194
+ # Writing is the caller's choice, not the projector's. A projector
136
195
  # returns an artifact and never touches disk, which is what lets
137
196
  # spec/projector_spec.rb compare `:ir`'s output against a golden
138
197
  # fixture without a tmpdir. `out:` is the only thing that writes.
@@ -141,6 +200,11 @@ module Hecks
141
200
  # than from inspecting the artifact. A Hash of path => contents and a
142
201
  # Hash that simply happens to hold strings are the same object to
143
202
  # Ruby; only the projection knows which it meant.
203
+ #
204
+ # @param artifact [Hash, String] the projector's output; a file tree when `as: :files`
205
+ # @param out [String] the path to write to; a directory when `as: :files`
206
+ # @param as [Symbol] `:files` to write a tree, anything else to write one file
207
+ # @return [String, Array<String>] the path written, or every path written when `as: :files`
144
208
  def write(artifact, out, as: :artifact)
145
209
  return write_tree(artifact, out) if as == :files
146
210
 
@@ -150,6 +214,10 @@ module Hecks
150
214
 
151
215
  # Answers the paths written, in the order given — so a caller can
152
216
  # report what happened without re-deriving it from the tree.
217
+ #
218
+ # @param files [Hash{String => String}] relative path => contents
219
+ # @param directory [String] the directory to write the tree under
220
+ # @return [Array<String>] each file's full written path, in `files`' order
153
221
  def write_tree(files, directory)
154
222
  require "fileutils"
155
223
  files.map do |relative, contents|
@@ -178,7 +246,7 @@ Hecks::Projector.register(:docs, Hecks::Projector::DocsProjector)
178
246
  Hecks::Projector.register(:narrate, Hecks::Projector::NarrateProjector)
179
247
  Hecks::Projector.register(:cli, Hecks::Projector::CliProjector)
180
248
 
181
- # The TARGETS are required from lib/hecks.rb, immediately after this
249
+ # The targets are required from lib/hecks.rb, immediately after this
182
250
  # file — deliberately not from here. A target requires this file (it
183
251
  # needs `Target` and the registry), so requiring them back from here
184
252
  # would close a genuine `circular require considered harmful` loop. Ruby
@@ -4,7 +4,7 @@ require_relative "projections/model"
4
4
  require_relative "fuzzing/properties"
5
5
 
6
6
  module Hecks
7
- # THE SHARED CORE behind `bin/query_ir` (a text CLI) and
7
+ # The shared core behind `bin/query_ir` (a text CLI) and
8
8
  # `bin/hecks_query_ir_mcp` (an MCP server exposing the same two
9
9
  # queries as tools) — one implementation, two front ends, the same
10
10
  # reason `Hecks::Codemod` exists once rather than per-script.
@@ -14,7 +14,7 @@ module Hecks
14
14
  Codemod = Hecks::Codemod
15
15
  Deviations = Hecks::Projections::Model::Deviations
16
16
 
17
- # THE SAME MAPPING spec/model_shape_conformance_spec.rb's own
17
+ # The same mapping spec/model_shape_conformance_spec.rb's own
18
18
  # MODEL_CONSTRUCTS holds — kept here rather than shared from the
19
19
  # spec (a spec file is not a library other code should require),
20
20
  # matching Deviations' own doc comment: "the generator and the gate
@@ -37,6 +37,11 @@ module Hecks
37
37
 
38
38
  module_function
39
39
 
40
+ # Reads the fields the language declares for one construct kind, from the grammar itself.
41
+ #
42
+ # @param name [String] a `CONSTRUCTS` key, such as `"Aggregate"`
43
+ # @return [Array<Symbol>] the attribute names the self-hosted meta-domain
44
+ # grammar declares for `name`
40
45
  def meta_declared(name)
41
46
  Hecks::Bluebook::MetaValidator.grammar_registry
42
47
  .bluebook("Bluebook").aggregate(name).attributes.map(&:name)
@@ -44,9 +49,14 @@ module Hecks
44
49
 
45
50
  # The real structural diff between what a Ruby IR class emits
46
51
  # (Class.ir_spec.keys) and what the self-hosted meta-domain declares
47
- # for it — the SAME comparison spec/model_shape_conformance_spec.rb
52
+ # for it — the same comparison spec/model_shape_conformance_spec.rb
48
53
  # makes, reusing its own Deviations data so this can never silently
49
54
  # drift from what that gate actually checks.
55
+ #
56
+ # @param name [String] a `CONSTRUCTS` key, such as `"Aggregate"`
57
+ # @return [Hash{Symbol => Object}] `:name`, `:declared`, `:emitted`, plus
58
+ # `:missing_from_ruby` and `:unaccounted_in_ruby` — each an `Array<Symbol>`
59
+ # @raise [ArgumentError] if `name` is not a `CONSTRUCTS` key
50
60
  def construct_diff(name)
51
61
  klass = CONSTRUCTS.fetch(name) do
52
62
  raise ArgumentError, "no such construct #{name.inspect} — known: #{CONSTRUCTS.keys.join(', ')}"
@@ -54,8 +64,8 @@ module Hecks
54
64
  declared = meta_declared(name)
55
65
  emitted = klass.ir_spec.keys
56
66
 
57
- accounted = declared.reject { |field| Deviations::PARENT_REF.call(field) } -
58
- Deviations::JUDGE_ONLY -
67
+ accounted = declared.reject { |field| Deviations.parent_ref?(field) } -
68
+ Deviations.judge_only(name) -
59
69
  Deviations.folded(name).values.flatten -
60
70
  Deviations.off_the_wire(name) -
61
71
  Deviations.dynamic_tail(name) -
@@ -72,30 +82,38 @@ module Hecks
72
82
  missing_from_ruby: accounted - emitted, unaccounted_in_ruby: unaccounted }
73
83
  end
74
84
 
85
+ # Diffs one or every construct kind at once.
86
+ #
87
+ # @param names [Array<String>] `CONSTRUCTS` keys to diff, every construct when empty
88
+ # @return [Array<Hash>] one `construct_diff` result per name
75
89
  def constructs(names = [])
76
90
  targets = names.empty? ? CONSTRUCTS.keys : names
77
91
  targets.map { |name| construct_diff(name) }
78
92
  end
79
93
 
80
- # Every given/ensures/invariant DECLARATION reachable from a booted
94
+ # Every given/ensures/invariant declaration reachable from a booted
81
95
  # registry, walked recursively — every owner's own `.preconditions`/
82
96
  # `.invariants` (block-declared rules), every value object's own
83
97
  # `.invariants`, and every command's own `.givens`/`.ensures`
84
- # (`.givens` too, not just `.ensures` — a command's own LOCAL
98
+ # (`.givens` too, not just `.ensures` — a command's own local
85
99
  # `given("x") { block }` not yet hoisted to its owner, round 4's own
86
100
  # starting shape, would otherwise be invisible).
87
101
  #
88
- # NOT keyed by object identity (a real, hard-won correction — see
102
+ # Not keyed by object identity (a real, hard-won correction — see
89
103
  # the comment on `duplicates`' own dedup below for why: a bare
90
104
  # `given("x")` reference and its owner's own block declaration are
91
- # the SAME Ruby object at DSL build time, but `MetaValidator.call`
92
- # (S14 — every bluebook is judged by dispatching its OWN IR into the
105
+ # the same Ruby object at DSL build time, but `MetaValidator.call`
106
+ # (S14 — every bluebook is judged by dispatching its own IR into the
93
107
  # self-hosted grammar, then reconstructed via `Assembly.call` from
94
108
  # flat rows) rebuilds the whole graph fresh from there. By the time
95
- # any caller reads `chapter.aggregates`, EVERY given/invariant is
109
+ # any caller reads `chapter.aggregates`, every given/invariant is
96
110
  # already a distinct object, whether it was block-declared or
97
111
  # bare-referenced — object identity carries no signal past that
98
112
  # point, for any construct, not just this one).
113
+ #
114
+ # @param registry [Runtime::Registry] the booted registry to walk
115
+ # @param chapter_name [String, nil] one chapter to walk, every booted chapter when nil
116
+ # @return [Array<Rule>]
99
117
  def collect_rules(registry, chapter_name = nil)
100
118
  rules = []
101
119
  chapters = chapter_name ? [registry.bluebook(chapter_name)] : registry.bluebooks.values
@@ -114,7 +132,7 @@ module Hecks
114
132
  rules
115
133
  end
116
134
 
117
- # THE RECURSIVE WALK `collect_rules` drives — pulled out of that method
135
+ # The recursive walk `collect_rules` drives — pulled out of that method
118
136
  # (pure extraction, identical traversal and Rule shapes) as its own
119
137
  # named, self-recursive method rather than a lambda closing over the
120
138
  # same locals. `rules` is the one piece of state every call shares —
@@ -147,17 +165,21 @@ module Hecks
147
165
  end
148
166
  private_class_method :walk_construct_rules
149
167
 
150
- # A rule's OWNER — the construct path a "(declared)" location names
168
+ # A rule's owner — the construct path a "(declared)" location names
151
169
  # directly, or (for a command-level `.givens`/`.ensures` entry) the
152
170
  # path with its trailing `.CommandName` segment stripped. Two rules
153
- # sharing an owner are the SAME declaration read twice (an owner's
171
+ # sharing an owner are the same declaration read twice (an owner's
154
172
  # own precondition, and a command under it referencing that
155
173
  # precondition by name) — not two independent ones.
156
174
  #
157
- # PUBLIC, not a `duplicates`-only internal — `bin/codemod_hoist_
175
+ # Public, not a `duplicates`-only internal — `bin/codemod_hoist_
158
176
  # local_givens` reads it directly to group `collect_rules`' own
159
177
  # output by owner itself, the same reading `duplicates`' own
160
178
  # `declaration_count` makes.
179
+ #
180
+ # @param location [String] a `Rule#location`
181
+ # @return [String] the owning construct's path, with `" (declared)"` or a trailing
182
+ # `.CommandName` segment stripped
161
183
  def owner_of(location)
162
184
  return location.sub(/ \(declared\)\z/, "") if location.end_with?(" (declared)")
163
185
 
@@ -167,27 +189,33 @@ module Hecks
167
189
  # Grouped by (kind, description, canonical), not canonical text
168
190
  # alone — a generic one-liner like `!value.to_s.empty?` legitimately
169
191
  # recurs dozens of times for unrelated fields; the real signal is
170
- # the SAME RULE (same description, same predicate), which is also
192
+ # the same rule (same description, same predicate), which is also
171
193
  # exactly what the given/invariant reference mechanism itself
172
194
  # resolves on.
173
195
  #
174
- # DEDUPED BY OWNER, not object identity (`collect_rules`' own
196
+ # Deduped by owner, not object identity (`collect_rules`' own
175
197
  # comment has the full story — identity is gone by the time this
176
198
  # reads the registry). Within a group, every command-level rule
177
- # whose OWNER already has its own "(declared)" entry in the same
199
+ # whose owner already has its own "(declared)" entry in the same
178
200
  # group is just that declaration read again through a reference —
179
201
  # `Account.Open`/`Account.Credit`/etc. all naming `Account`'s own
180
- # `given("customer is active")` count as Account's ONE declaration,
202
+ # `given("customer is active")` count as Account's one declaration,
181
203
  # not nine. A command-level rule with no matching owner declaration
182
- # (a LOCAL, not-yet-hoisted `given("x") { block }`) counts as its
204
+ # (a local, not-yet-hoisted `given("x") { block }`) counts as its
183
205
  # own standalone declaration — two different commands independently
184
- # writing the identical local predicate IS two declarations, a real
206
+ # writing the identical local predicate is two declarations, a real
185
207
  # hoisting opportunity. A group is reported only when it adds up to
186
- # MORE than one real declaration this way.
208
+ # more than one real declaration this way.
187
209
  #
188
210
  # `domains: []` means "the self-hosted meta-domain only" — pass real
189
211
  # domain directories explicitly to include them, or `nil` (the
190
212
  # default) for meta-domain plus every real example.
213
+ #
214
+ # @param domains [Array<String>, nil] domain root directories to scan; every real
215
+ # example (`Codemod::EXAMPLE_ROOTS`) when nil, none beyond the meta-domain when `[]`
216
+ # @param include_meta [Boolean] whether to also scan the self-hosted meta-domain
217
+ # @return [Array<Hash>] one entry per duplicate group: `:kind`, `:description`,
218
+ # `:canonical`, and `:locations` (`Array<String>`)
191
219
  def duplicates(domains: nil, include_meta: true)
192
220
  domains ||= Codemod::EXAMPLE_ROOTS
193
221
  all_rules = []
@@ -210,48 +238,48 @@ module Hecks
210
238
  end
211
239
  end
212
240
 
213
- # `given` ONLY (not `invariant`/`ensures`) also covers CROSS-ENTITY
241
+ # `given` only (not `invariant`/`ensures`) also covers cross-entity
214
242
  # coverage — one piece's own entity-level declaration, shared with
215
- # any OTHER piece nested under the SAME root aggregate (real corpus
243
+ # any other piece nested under the same root aggregate (real corpus
216
244
  # this closes: SafeDepositBox's own `Visit`/`KeyIssuance`, two
217
245
  # different pieces on one head). A rule owned by a nested entity
218
246
  # (its own owner path has more than one segment) is covered when
219
- # SOME "(declared)" entry exists ANYWHERE under that SAME root
220
- # aggregate — not just under its OWN exact owner — matching the
247
+ # some "(declared)" entry exists anywhere under that same root
248
+ # aggregate — not just under its own exact owner — matching the
221
249
  # DSL's own pool, threaded unchanged through an aggregate's whole
222
250
  # entity tree (`AggregateBuilder#entity`'s own comment).
223
251
  #
224
- # A KNOWN, ACCEPTED GAP this does NOT (and structurally cannot)
225
- # close: CHAPTER-WIDE given sharing (`AggregateBuilder#given`'s own
252
+ # A known, accepted gap this does not (and structurally cannot)
253
+ # close: chapter-wide given sharing (`AggregateBuilder#given`'s own
226
254
  # bare form, `docs/implemented/resolution-rules/chapter-given.md`) — `Account`,
227
255
  # `SafeDepositBox`, and `OnboardingCase` each still show as their
228
- # own "(declared)" owner here even AFTER `SafeDepositBox`/
256
+ # own "(declared)" owner here even after `SafeDepositBox`/
229
257
  # `OnboardingCase` were converted to bare chapter-wide references,
230
- # because a REFERENCED given still write-throughs into its own
231
- # aggregate's `@named_givens` — the SAME reason `collect_rules`'
258
+ # because a referenced given still write-throughs into its own
259
+ # aggregate's `@named_givens` — the same reason `collect_rules`'
232
260
  # own top comment already gives for why object identity carries no
233
- # signal past a bluebook's own build: the EXPORTED IR cannot tell
261
+ # signal past a bluebook's own build: the exported IR cannot tell
234
262
  # "I declared this myself" apart from "I referenced someone else's
235
263
  # declaration," because by the time anything reads `chapter.
236
264
  # aggregates`, both look identical. Closing this would mean reading
237
- # SOURCE TEXT (bare `given(desc)` vs. block `given(desc) { ... }`),
265
+ # source text (bare `given(desc)` vs. block `given(desc) { ... }`),
238
266
  # not the built IR this query is deliberately built on — a
239
267
  # different, source-level tool, not a fix to this one. Treat a
240
268
  # still-flagged group naming multiple aggregates as "verify by
241
- # hand whether this is ALREADY a chapter-wide reference before
269
+ # hand whether this is already a chapter-wide reference before
242
270
  # assuming it's fresh duplication," not as an automatic signal
243
271
  # either way.
244
272
  #
245
- # THE IDENTICAL GAP, ONE LEVEL DOWN: chapter-wide ENTITY-scoped
273
+ # The identical gap, one level down: chapter-wide entity-scoped
246
274
  # sharing (`EntityBuilder#given`'s own bare form,
247
275
  # `docs/implemented/resolution-rules/chapter-entity-given.md`) hits this same wall for
248
276
  # the same structural reason — `SafeDepositBox.Visit` still shows
249
277
  # as its own "(declared)" owner here even after becoming a bare
250
278
  # reference to `Account.LedgerEntry`'s declaration, because a piece
251
279
  # resolving a chapter-wide reference still write-throughs the
252
- # resolved `Given` into its own `@named_givens` (so ITS OWN
280
+ # resolved `Given` into its own `@named_givens` (so its own
253
281
  # commands can read it back locally without a second hop). This is
254
- # not a NEW limitation this feature introduces — it is the exact
282
+ # not a new limitation this feature introduces — it is the exact
255
283
  # same IR-cannot-distinguish-declared-from-referenced fact, one
256
284
  # scope wider. `bin/query_ir duplicates` confirms this directly:
257
285
  # `Account.LedgerEntry (declared)` and `SafeDepositBox.Visit
@@ -260,6 +288,9 @@ module Hecks
260
288
  # case above, before assuming a group naming two pieces under
261
289
  # different aggregates is fresh duplication rather than an already-
262
290
  # resolved chapter-wide reference.
291
+ #
292
+ # @param rules [Array<Rule>] one duplicate-key group's own rules
293
+ # @return [Integer] how many of `rules` are real, independent declarations
263
294
  def declaration_count(rules)
264
295
  declared = rules.select { |r| r.location.end_with?(" (declared)") }
265
296
  declared_owners = declared.to_set { |r| owner_of(r.location) }
@@ -278,10 +309,12 @@ module Hecks
278
309
  end
279
310
  private_class_method :declaration_count
280
311
 
281
- # SHARED TEXT FORMATTING — both `bin/query_ir` (a text CLI) and
312
+ # Shared text formatting — both `bin/query_ir` (a text CLI) and
282
313
  # `bin/hecks_query_ir_mcp` (an MCP tool result, itself a text
283
314
  # block) want the identical human-readable rendering; only the
284
- # OUTER framing differs (plain stdout vs. a JSON-RPC content array).
315
+ # outer framing differs (plain stdout vs. a JSON-RPC content array).
316
+ # @param diffs [Array<Hash>] `constructs`' own output
317
+ # @return [String] the human-readable rendering
285
318
  def format_constructs(diffs)
286
319
  diffs.map do |diff|
287
320
  lines = ["== #{diff[:name]} =="]
@@ -303,31 +336,37 @@ module Hecks
303
336
  end.join("\n\n")
304
337
  end
305
338
 
306
- # ONE HAND-TYPED CONSTRUCT-NAME PER RECONSTRUCTION METHOD — the only
307
- # two `MetaValidator::Reconstruction` methods NOT driven generically
339
+ # **One hand-typed construct-name per reconstruction method** — the only
340
+ # two `MetaValidator::Reconstruction` methods not driven generically
308
341
  # through `Assembly::Contracts`' own table (its own header explains
309
342
  # why: `aggregate(row)`/`entity(row)` predate the table and were
310
343
  # never migrated). `impact_preview`'s own touchpoint 4 is checked
311
- # ONLY for these two — every other construct is read generically, so
344
+ # only for these two — every other construct is read generically, so
312
345
  # asking "does Command's own reconstruction method mention this
313
346
  # field" is a question with no method to check.
314
347
  RECONSTRUCTION_METHODS = { "Aggregate" => :aggregate, "Entity" => :entity }.freeze
315
348
 
316
- # THE SIX TOUCHPOINTS `.claude/skills/bluebook-construct-creator/
349
+ # The six touchpoints `.claude/skills/bluebook-construct-creator/
317
350
  # SKILL.md` walks in prose, checked structurally instead of by hand
318
- # — for a construct/field pair NOT yet fully propagated (typically
351
+ # — for a construct/field pair not yet fully propagated (typically
319
352
  # mid-round, deciding what's left), or as a sanity check before the
320
353
  # final gate sweep of a round already believed done. Every check
321
- # here is BEST-EFFORT and ADVISORY, not a gate: a `false` does not
354
+ # here is best-effort and advisory, not a gate: a `false` does not
322
355
  # always mean "not yet done" (a field can be legitimately exempt —
323
356
  # `Deviations`' own named categories, `GUARANTEED_BY_CONSTRUCTION`,
324
357
  # or `META_DOMAIN_KNOWN_GAPS`, the last of which lives in
325
- # spec/fuzzing/meta_domain_coverage_spec.rb, a SPEC file this
358
+ # spec/fuzzing/meta_domain_coverage_spec.rb, a spec file this
326
359
  # module deliberately never requires — see `CONSTRUCTS`' own
327
360
  # comment on the same principle). Read the touchpoint's own
328
361
  # existing gate (`model_shape_conformance_spec.rb`,
329
362
  # `assembly_spec.rb`, `meta_domain_coverage_spec.rb`) before trusting
330
363
  # a `false` here as a real gap.
364
+ # @param name [String] a `CONSTRUCTS` key, such as `"Aggregate"`
365
+ # @param field [String, Symbol] the declared field to check propagation for
366
+ # @return [Hash{Symbol => Object}] `:name`, `:field`, and `:touchpoints` — an
367
+ # `Array<Hash>` of `:touchpoint` (String) and `:present` (Boolean, or nil when the
368
+ # touchpoint does not apply to `name`)
369
+ # @raise [ArgumentError] if `name` is not a `CONSTRUCTS` key
331
370
  def impact_preview(name, field)
332
371
  CONSTRUCTS.fetch(name) { raise ArgumentError, "no such construct #{name.inspect} — known: #{CONSTRUCTS.keys.join(', ')}" }
333
372
  field = field.to_s
@@ -365,7 +404,7 @@ module Hecks
365
404
  # the method's own body ends at the next line indented no deeper
366
405
  # than its own `def` — the same boundary Ruby itself uses, read back
367
406
  # textually because there is no live AST here, only a file to grep a
368
- # slice of. `nil` (not `false`) for every OTHER construct — this
407
+ # slice of. `nil` (not `false`) for every other construct — this
369
408
  # touchpoint genuinely does not apply to them (`RECONSTRUCTION_
370
409
  # METHODS` only names the two hand-typed methods), and collapsing
371
410
  # "does not apply" into "not done" would misreport a construct that
@@ -402,6 +441,10 @@ module Hecks
402
441
  end
403
442
  private_class_method :rust_mentions?
404
443
 
444
+ # Renders one field's touchpoint checklist as text.
445
+ #
446
+ # @param preview [Hash] `impact_preview`'s own output
447
+ # @return [String] the human-readable rendering
405
448
  def format_impact_preview(preview)
406
449
  lines = ["== #{preview[:name]}##{preview[:field]} =="]
407
450
  preview[:touchpoints].each do |t|
@@ -421,6 +464,10 @@ module Hecks
421
464
  lines.join("\n")
422
465
  end
423
466
 
467
+ # Renders the duplicate-rule groups as text.
468
+ #
469
+ # @param groups [Array<Hash>] `duplicates`' own output
470
+ # @return [String] the human-readable rendering
424
471
  def format_duplicates(groups)
425
472
  return "no duplicate given/invariant/ensures rule found" if groups.empty?
426
473
 
@@ -7,10 +7,16 @@ module Hecks
7
7
  # vocabulary — and .render_value, the wire-rendering entry point
8
8
  # shared by every literal-bearing spec struct.
9
9
  module QuerySpecification
10
+ # The vocabulary every query-shaped construct shares — the clause structs
11
+ # (`WhereClause`, `OrderBy`, `LimitSpec`...), the `DSL` mixin that parses
12
+ # them, and the comparison and null rules every engine answers by. It is
13
+ # its own namespace so a plain `Bluebook::Query` and a
14
+ # `ReadModel::Specification` say one thing the same way rather than each
15
+ # carrying a copy.
10
16
  module Common
11
17
  # `none_in_state`, vendored addition not (yet) upstream hecks
12
- # (migration plan task 4): a CROSS-AGGREGATE ANTI-JOIN comparator --
13
- # `where ref: { none_in_state: "Claim:held" }` holds true when NO
18
+ # (migration plan task 4): a cross-aggregate anti-join comparator --
19
+ # `where ref: { none_in_state: "Claim:held" }` holds true when no
14
20
  # record in the named aggregate, keyed by this record's own field
15
21
  # value, is currently in the named state. plan.bluebook's own
16
22
  # description: "a keyed point lookup (HashMap hit), never a scan" --
@@ -20,10 +26,20 @@ module Hecks
20
26
  COMPARATORS = Hecks::Vocabulary.symbols("QueryComparator")
21
27
  end
22
28
 
29
+ # Renders a literal captured in a query clause as its self-describing
30
+ # wire spelling, for a spec struct's `to_h`.
31
+ #
23
32
  # The specification structs' own name for the one wire spelling — see
24
- # Hecks::Literal, which every other `to_h`-bound literal field now
33
+ # `Hecks::Literal`, which every other `to_h`-bound literal field
25
34
  # shares. Kept as a word here because the structs below read better
26
35
  # saying what they are doing than naming the module that does it.
36
+ #
37
+ # @param value [nil, Symbol, String, StateRef, Boolean, Integer, Float, Hash, Array] the
38
+ # literal as the bluebook author wrote it; Hash values and Array elements are
39
+ # rendered recursively
40
+ # @return [String] the wire text: a Symbol keeps its colon, a String its double quotes,
41
+ # and a number, a boolean and `nil` are bare
42
+ # @raise [ArgumentError] if `value`, or a value nested in it, is of any other class
27
43
  def self.render_value(value) = Literal.render(value)
28
44
  end
29
45
  end