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
@@ -4,17 +4,19 @@ require_relative "resolver"
4
4
  module Hecks
5
5
  module Bluebook
6
6
  module Expression
7
- # ── AST → JSON — walks the REAL Evaluator/Resolver AST (the same
8
- # objects a live dispatch parses `given`/`ensures`/invariant text
9
- # into — see docs/implemented/guides/running-a-runtime.md's "The
10
- # expression grammar") and emits plain, JSON-serializable Ruby
11
- # Hashes, tagged by `"op"`.
7
+ # Walks the real Evaluator/Resolver AST (the same objects a live
8
+ # dispatch parses `given`/`ensures`/invariant text into — see
9
+ # docs/implemented/guides/running-a-runtime.md's "The expression
10
+ # grammar") and emits plain, JSON-serializable Ruby Hashes, tagged
11
+ # by `"op"`.
12
12
  #
13
- # THE SAME TWO-METHOD WALK `rust/project/expr_emitter.rb`'s own
14
- # `emit_bool`/`emit_resolver` already do, over the SAME AST — that
15
- # file's own methods build RUST SOURCE-CODE STRINGS for `rust/
13
+ # ## Why this exists beside `rust/project/expr_emitter.rb`
14
+ #
15
+ # The same two-method walk `rust/project/expr_emitter.rb`'s own
16
+ # `emit_bool`/`emit_resolver` already do, over the same AST — that
17
+ # file's own methods build Rust source-code strings for `rust/
16
18
  # project`'s codegen (`rust/src/kernel::expr::Expr` literals, baked
17
- # into a generated domain's own compiled binary); this builds DATA
19
+ # into a generated domain's own compiled binary); this builds data
18
20
  # instead, for a genuinely different consumer with a genuinely
19
21
  # different constraint: `rust/host` can never link the `rust`
20
22
  # (kernel) crate at all (a real, load-bearing build constraint —
@@ -22,41 +24,44 @@ module Hecks
22
24
  # path dependency would statically bake every domain's generated
23
25
  # dispatch code into every Lambda binary), so a value object's own
24
26
  # `invariant` predicate has to travel as something `rust/host` can
25
- # deserialize and interpret itself, at RUNTIME, from `ir.json` — the
27
+ # deserialize and interpret itself, at runtime, from `ir.json` — the
26
28
  # exact same relationship `rust/project`'s own `Expr` literals
27
29
  # already have to the compiled kernel, one layer further out.
28
30
  #
29
- # LIVES IN CORE `lib/hecks`, NOT `rust/project/` — `rust/project.rb`
30
- # is a separate, downstream toolchain
31
+ # ## Why it lives in core `lib/hecks`, not `rust/project/`
32
+ #
33
+ # `rust/project.rb` is a separate, downstream toolchain
31
34
  # (`lib/hecks/projector.rb`'s own header: "a whole separate Ruby
32
35
  # program"), never `require`d by core `lib/hecks/bluebook/*.rb`
33
36
  # files (confirmed: no core file does). `value_object.rb`'s own
34
- # `invariants:` IR emission needs this for EVERY domain's ordinary
37
+ # `invariants:` IR emission needs this for every domain's ordinary
35
38
  # `to_h`/`ir.json` export — golden fixtures, `hecks-parse`'s parity
36
39
  # comparisons, and any deploy artifact, not only a `bin/project_rust`
37
40
  # run — so it belongs beside `Evaluator`/`Resolver` themselves, not
38
41
  # bolted onto a tool that only sometimes runs.
39
42
  #
40
- # COMPLETE, not corpus-scoped: every node this grammar admits gets
41
- # a real arm, the identical "raise, don't silently drop" discipline
42
- # `expr_emitter.rb`'s own `emit_bool`/`emit_resolver` already hold
43
- # to — even though, as of this writing, no real corpus VALUE OBJECT
44
- # invariant exercises `Include`/`Modulo`/`BlockPredicate`/`Find`/
45
- # `Array`/`MatchesRegex`/`Presence`/`Assignment`/`Split`/`StartsWith`/
46
- # `EndsWith`/`First`/`Last` (only `given`/`ensures` clauses do, elsewhere in
47
- # the corpus — a different consumer of this same grammar).
48
- # `rust/host/src/expr_json.rs`'s own header names exactly which of
49
- # these its interpreter evaluates for real today versus refuses
50
- # cleanly — a narrower, deliberate, documented boundary on the
51
- # INTERPRETING side, not on this EMITTING side: an author is free
52
- # to write ANY real expression in a value object's own `invariant`,
53
- # and this always emits it faithfully; whether `rust/host` can yet
54
- # CHECK it at mint time is that file's own question to answer, not
55
- # this one's to pre-empt by refusing to even try.
43
+ # ## Complete, not corpus-scoped
44
+ #
45
+ # Every node this grammar admits gets a real arm, the identical
46
+ # "raise, don't silently drop" discipline `expr_emitter.rb`'s own
47
+ # `emit_bool`/`emit_resolver` already hold to — even though, as of
48
+ # this writing, no real corpus value object invariant exercises
49
+ # `Include`/`Modulo`/`BlockPredicate`/`Find`/`Array`/`MatchesRegex`/
50
+ # `Presence`/`Assignment`/`Split`/`StartsWith`/`EndsWith`/`First`/
51
+ # `Last` (only `given`/`ensures` clauses do, elsewhere in the corpus
52
+ # — a different consumer of this same grammar). `rust/host/src/
53
+ # expr_json.rs`'s own header names exactly which of these its
54
+ # interpreter evaluates for real today versus refuses cleanly — a
55
+ # narrower, deliberate, documented boundary on the interpreting
56
+ # side, not on this emitting side: an author is free to write any
57
+ # real expression in a value object's own `invariant`, and this
58
+ # always emits it faithfully; whether `rust/host` can yet check it
59
+ # at mint time is that file's own question to answer, not this
60
+ # one's to pre-empt by refusing to even try.
56
61
  module AstJson
57
62
  module_function
58
63
 
59
- # THE CLOSED OP ROSTER — every `"op"` tag the walkers below can
64
+ # **The closed op roster** — every `"op"` tag the walkers below can
60
65
  # emit, pinned so a reader (or a spec) can refuse a tag it does
61
66
  # not know instead of guessing. A new node kind is a new entry
62
67
  # here, a new arm below, and a new arm in every reader.
@@ -68,16 +73,26 @@ module Hecks
68
73
  matches_regex presence assignment split starts_with ends_with
69
74
  ].freeze
70
75
 
71
- # ONE RULE ROW, THE WAY EVERY RULE SITE EMITS IT — description and
76
+ # One rule row, the way every rule site emits it — description and
72
77
  # canonical text (what every reader has always had) plus the
73
78
  # structured form, derived from the same text. `ast` is a pure
74
79
  # function of `canonical`: the IR carries both so a reader that
75
80
  # only displays keeps the text, and a reader that evaluates never
76
81
  # re-parses it.
82
+ #
83
+ # @param rule [Bluebook::Given, Bluebook::Invariant] the rule to
84
+ # render; `rule.ast` is used when already computed, otherwise
85
+ # `rule.canonical` is parsed and emitted fresh
86
+ # @return [Hash{Symbol => Object}] `:description` (`String`),
87
+ # `:canonical` (`String`), and `:ast` (`Hash`, the JSON AST)
77
88
  def rule_row(rule)
78
89
  { description: rule.description, canonical: rule.canonical, ast: rule.ast || emit_predicate(rule.canonical) }
79
90
  end
80
91
 
92
+ # Parses `canonical` and emits its JSON AST in one step.
93
+ #
94
+ # @param canonical [String] a canonical predicate source string
95
+ # @return [Hash] the JSON-shaped boolean AST, tagged by `"op"`
81
96
  def emit_predicate(canonical)
82
97
  emit_bool(Evaluator.parse(canonical))
83
98
  end
@@ -89,6 +104,17 @@ module Hecks
89
104
  # engine reading it is a defect in the bluebook, refused at build.
90
105
  # Walks the emitted AST, so every rule site (givens, ensures,
91
106
  # invariants, preconditions, a policy's where) gets the one check.
107
+ #
108
+ # @param ast [Hash] a JSON AST node, as emitted by `emit_predicate`
109
+ # or `emit_bool`
110
+ # @param owner [String] the declaring bluebook's name, for the
111
+ # refusal message
112
+ # @param word [String] the rule's own kind and description
113
+ # (such as `"given \"total is positive\""`), for the refusal
114
+ # message
115
+ # @return [Hash] `ast`, unchanged
116
+ # @raise [Bluebook::DSL::Malformed] if any `matches_regex` node's
117
+ # pattern uses a construct `PatternSubset` does not admit
92
118
  def refuse_unshared_patterns!(ast, owner:, word:)
93
119
  return ast if Hecks::Bluebook::MetaValidator.shadow_parsing? # frozen era text is history
94
120
 
@@ -107,6 +133,11 @@ module Hecks
107
133
 
108
134
  # Every name a rule resolves at its root — the first segment of
109
135
  # each `lookup` path, unique, in first-seen order.
136
+ #
137
+ # @param ast [Hash] a JSON AST node, as emitted by `emit_predicate`
138
+ # or `emit_bool`
139
+ # @return [Array<String>] each `lookup` node's root name, unique,
140
+ # in first-seen order
110
141
  def lookup_heads(ast)
111
142
  heads = []
112
143
  each_node(ast) do |node|
@@ -115,6 +146,13 @@ module Hecks
115
146
  heads.uniq
116
147
  end
117
148
 
149
+ # Visits `node` and every Hash node nested inside it, depth-first.
150
+ #
151
+ # @param node [Hash, Array, Object] a JSON AST node, or an Array or
152
+ # scalar value found while walking one; a scalar is a silent no-op
153
+ # @yieldparam node [Hash] each Hash node reached, including `node`
154
+ # itself
155
+ # @return [void]
118
156
  def each_node(node, &block)
119
157
  case node
120
158
  when ::Hash
@@ -125,6 +163,17 @@ module Hecks
125
163
  end
126
164
  end
127
165
 
166
+ # Emits the JSON form of one boolean-position Evaluator node.
167
+ #
168
+ # @param node [Bluebook::Expression::Evaluator::Or,
169
+ # Bluebook::Expression::Evaluator::And,
170
+ # Bluebook::Expression::Evaluator::Not,
171
+ # Bluebook::Expression::Evaluator::Compare,
172
+ # Bluebook::Expression::Evaluator::Include,
173
+ # Bluebook::Expression::Evaluator::Resolve] the boolean-position
174
+ # AST node to emit
175
+ # @return [Hash] the JSON AST for `node`, tagged by `"op"`
176
+ # @raise [RuntimeError] if `node` is not one of the handled classes
128
177
  def emit_bool(node)
129
178
  case node
130
179
  when Evaluator::Or then { "op" => "or", "left" => emit_bool(node.left), "right" => emit_bool(node.right) }
@@ -143,21 +192,37 @@ module Hecks
143
192
  end
144
193
  end
145
194
 
195
+ # Emits the JSON form of one comparison operator.
196
+ #
197
+ # @param comparator [Bluebook::Expression::Evaluator::Operator] the
198
+ # operator to emit
199
+ # @return [Hash{String => Boolean}] `"less_than"`, `"equal"`, and
200
+ # `"negated"`, exactly as `comparator` carries them
146
201
  def emit_comparison(comparator)
147
202
  { "less_than" => comparator.compares_less_than, "equal" => comparator.compares_equal, "negated" => comparator.negated }
148
203
  end
149
204
 
150
205
  # The JSON-target sibling of `expr_emitter.rb`'s own
151
206
  # `emit_include` — see that method's own comment for the full
152
- # reasoning (a LITERAL array haystack has no `Expr::Include`-
153
- # representable shape on EITHER target, kernel or host, so both
154
- # rewrite it identically into an OR-of-equalities at emission
207
+ # reasoning (a literal array haystack has no `Expr::Include`-
208
+ # representable shape on either target, kernel or host, so both
209
+ # rewrite it identically into an or-of-equalities at emission
155
210
  # time rather than carrying a shape neither interpreter could
156
211
  # evaluate). A non-literal haystack still emits `include`
157
212
  # unchanged.
158
213
  EQ = Evaluator::OPERATORS.find { |op| op.symbol == "==" }
159
214
  private_constant :EQ
160
215
 
216
+ # Emits the JSON form of one `include` node.
217
+ #
218
+ # @param node [Bluebook::Expression::Evaluator::Include] the
219
+ # `include` node to emit
220
+ # @return [Hash] the JSON AST: `{"op" => "include", ...}` for a
221
+ # non-literal haystack, `{"op" => "bool", "value" => false}` for
222
+ # an empty literal array haystack, or an `"or"`-of-`"compare"`
223
+ # tree of equalities for a non-empty literal array haystack
224
+ # @raise [RuntimeError] if `node.haystack`, `node.needle`, or one of
225
+ # the haystack's literal elements is an unhandled resolver node
161
226
  def emit_include(node)
162
227
  return { "op" => "include", "haystack" => emit_resolver(node.haystack), "needle" => emit_resolver(node.needle) } \
163
228
  unless node.haystack.is_a?(Resolver::ArrayLiteral)
@@ -172,10 +237,37 @@ module Hecks
172
237
  end
173
238
 
174
239
  # One case arm per Resolver node type — the class header above is
175
- # explicit that this dispatch must stay COMPLETE and in one place
240
+ # explicit that this dispatch must stay complete and in one place
176
241
  # ("every node this grammar admits gets a real arm"); splitting it
177
242
  # into several methods would hide whether the set is still
178
243
  # exhaustive instead of making that visible at a glance.
244
+ #
245
+ # @param node [Bluebook::Expression::Resolver::IntegerLiteral,
246
+ # Bluebook::Expression::Resolver::FloatLiteral,
247
+ # Bluebook::Expression::Resolver::StringLiteral,
248
+ # Bluebook::Expression::Resolver::BoolLiteral,
249
+ # Bluebook::Expression::Resolver::NilLiteral,
250
+ # Bluebook::Expression::Resolver::Lookup,
251
+ # Bluebook::Expression::Resolver::Addition,
252
+ # Bluebook::Expression::Resolver::SignTest,
253
+ # Bluebook::Expression::Resolver::Empty,
254
+ # Bluebook::Expression::Resolver::ToS,
255
+ # Bluebook::Expression::Resolver::Modulo,
256
+ # Bluebook::Expression::Resolver::Size,
257
+ # Bluebook::Expression::Resolver::BlockPredicate,
258
+ # Bluebook::Expression::Resolver::Find,
259
+ # Bluebook::Expression::Resolver::ArrayLiteral,
260
+ # Bluebook::Expression::Resolver::MatchesRegex,
261
+ # Bluebook::Expression::Resolver::Presence,
262
+ # Bluebook::Expression::Resolver::Assignment,
263
+ # Bluebook::Expression::Resolver::Split,
264
+ # Bluebook::Expression::Resolver::StartsWith,
265
+ # Bluebook::Expression::Resolver::EndsWith,
266
+ # Bluebook::Expression::Resolver::First,
267
+ # Bluebook::Expression::Resolver::Last] the value-position AST
268
+ # node to emit
269
+ # @return [Hash] the JSON AST for `node`, tagged by `"op"`
270
+ # @raise [RuntimeError] if `node` is not one of the handled classes
179
271
  # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
180
272
  def emit_resolver(node)
181
273
  case node
@@ -184,7 +276,7 @@ module Hecks
184
276
  when Resolver::StringLiteral then { "op" => "str", "value" => node.value }
185
277
  when Resolver::BoolLiteral then { "op" => "bool", "value" => node.value }
186
278
  when Resolver::NilLiteral then { "op" => "nil" }
187
- # `path` is the SAME shape `find.path` already has — segments, not
279
+ # `path` is the same shape `find.path` already has — segments, not
188
280
  # a dotted string a reader would have to split by its own rule.
189
281
  when Resolver::Lookup then { "op" => "lookup", "path" => node.path.split(".") }
190
282
  when Resolver::Addition then { "op" => "add", "left" => emit_resolver(node.left), "right" => emit_resolver(node.right) }
@@ -4,8 +4,8 @@ require_relative "resolver"
4
4
  module Hecks
5
5
  module Bluebook
6
6
  module Expression
7
- # THE INVERSE OF `AstJson` — reads the `"op"`-tagged JSON a rule row
8
- # carries as `ast` back into the SAME `Evaluator`/`Resolver` node
7
+ # The inverse of `AstJson` — reads the `"op"`-tagged JSON a rule row
8
+ # carries as `ast` back into the same `Evaluator`/`Resolver` node
9
9
  # Structs `Evaluator.parse` builds from `canonical`. This is how the
10
10
  # runtime evaluates a rule without re-parsing its text: the one
11
11
  # parse happened at DSL-build time, behind `AstJson`; dispatch walks
@@ -19,15 +19,26 @@ module Hecks
19
19
  # the spec proved the round trip first, then the runtime adopted it.
20
20
  #
21
21
  # One deliberate asymmetry, inherited: `AstJson` rewrites a
22
- # LITERAL-array `.include?` into an OR of equalities (see
22
+ # literal-array `.include?` into an or of equalities (see
23
23
  # `emit_include`), so reading never produces an `Include` over an
24
24
  # `ArrayLiteral`. Evaluation is unchanged by that rewrite, which is
25
25
  # exactly what the equivalence spec pins.
26
26
  module AstReader
27
27
  module_function
28
28
 
29
+ # Reads a whole predicate's own `"op"`-tagged JSON back into its AST.
30
+ #
31
+ # @param json [Hash{String => Object}] the `"op"`-tagged JSON a rule row's own
32
+ # `ast` carries
33
+ # @return [Evaluator::Or, Evaluator::And, Evaluator::Not, Evaluator::Compare,
34
+ # Evaluator::Include, Evaluator::Resolve] the boolean/comparison AST node
29
35
  def read_predicate(json) = read_bool(json)
30
36
 
37
+ # Reads one boolean/comparison node, recursing into its own children.
38
+ #
39
+ # @param json [Hash{String => Object}] the `"op"`-tagged JSON for one node
40
+ # @return [Evaluator::Or, Evaluator::And, Evaluator::Not, Evaluator::Compare,
41
+ # Evaluator::Include, Evaluator::Resolve] the boolean/comparison AST node
31
42
  def read_bool(json)
32
43
  case json.fetch("op")
33
44
  when "or" then Evaluator::Or.new(left: read_bool(json["left"]), right: read_bool(json["right"]))
@@ -46,6 +57,10 @@ module Hecks
46
57
  # `Operator` carrying that exact triple is the one `parse` would
47
58
  # have chosen, because the roster (`expression/projection.json`)
48
59
  # holds one symbol per triple.
60
+ #
61
+ # @param cmp [Hash{String => Boolean}] `{"less_than"=>, "equal"=>, "negated"=>}`
62
+ # @return [Evaluator::Operator] the operator carrying that exact triple
63
+ # @raise [RuntimeError] if no operator in `Evaluator::OPERATORS` has that triple
49
64
  def operator(cmp)
50
65
  Evaluator::OPERATORS.find do |op|
51
66
  op.compares_less_than == cmp.fetch("less_than") &&
@@ -54,6 +69,15 @@ module Hecks
54
69
  end or raise "no comparison operator has the triple #{cmp.inspect}"
55
70
  end
56
71
 
72
+ # Reads one dotted/arithmetic leaf node, recursing into its own children.
73
+ #
74
+ # @param json [Hash{String => Object}] the `"op"`-tagged JSON for one leaf node
75
+ # @return [Object] a `Resolver` AST node — one of `IntegerLiteral`, `FloatLiteral`,
76
+ # `StringLiteral`, `BoolLiteral`, `NilLiteral`, `ArrayLiteral`, `Lookup`,
77
+ # `Addition`, `SignTest`, `Empty`, `ToS`, `Modulo`, `Size`, `First`, `Last`,
78
+ # `BlockPredicate`, `Find`, `MatchesRegex`, `Presence`, `Assignment`, `Split`,
79
+ # `StartsWith`, or `EndsWith`
80
+ # @raise [RuntimeError] if `json["op"]` names no known resolver op
57
81
  # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity -- one arm
58
82
  # per AstJson op is the point; splitting the case would hide the roster.
59
83
  def read_resolver(json)
@@ -98,6 +122,11 @@ module Hecks
98
122
  # triple is what evaluates. Recover the spelling from the
99
123
  # vocabulary so a rebuilt node refuses with the same message the
100
124
  # parsed one would.
125
+ #
126
+ # @param operator [Evaluator::Operator] the comparison operator a sign test's own
127
+ # triple resolved to
128
+ # @return [String] the sign test's own vocabulary name, or `operator.symbol` when
129
+ # no sign test shares its triple
101
130
  def sign_test_name(operator)
102
131
  Resolver::SIGN_TEST_OPERATORS.key(operator.symbol) || operator.symbol
103
132
  end
@@ -4,7 +4,7 @@ require_relative "../../vocabulary"
4
4
  module Hecks
5
5
  module Bluebook
6
6
  module Expression
7
- # Rewrites a predicate's SOURCE TEXT into one canonical spelling
7
+ # Rewrites a predicate's source text into one canonical spelling
8
8
  # before it's ever parsed or hashed — collapsing whitespace and
9
9
  # folding admitted synonyms (e.g. `.length` → `.size`) via the
10
10
  # `RULES` table projected from the grammar chapter, so two byte-
@@ -16,7 +16,7 @@ module Hecks
16
16
 
17
17
  STRATEGIES = Hecks::Vocabulary.fetch("NormalisationStrategy")
18
18
 
19
- # READ, NOT RESTATED — the admitted normalisation rules, projected
19
+ # **Read, not restated** — the admitted normalisation rules, projected
20
20
  # from the grammar chapter by bin/expression_projection exactly as
21
21
  # the evaluator's operator table is. See Evaluator::PROJECTION for
22
22
  # why a projection rather than a boot.
@@ -26,6 +26,13 @@ module Hecks
26
26
 
27
27
  module_function
28
28
 
29
+ # Returns the admitted normalisation rules as plain Hashes, for
30
+ # embedding in a bluebook's read-back contract.
31
+ #
32
+ # @return [Array<Hash{Symbol => String}>] one hash per rule, in
33
+ # declared `position` order, with keys `:strategy`,
34
+ # `:source_token`, `:replacement`, `:boundary`, and `:position`
35
+ # (stringified)
29
36
  def table
30
37
  RULES.sort_by(&:position).map do |rule|
31
38
  {
@@ -38,10 +45,25 @@ module Hecks
38
45
  end
39
46
  end
40
47
 
48
+ # Rewrites `source` into its canonical spelling by applying every
49
+ # admitted `RULES` entry, in declared `position` order.
50
+ #
51
+ # @param source [String] the raw predicate source to normalise
52
+ # @return [String] the canonicalised text, stripped of leading and
53
+ # trailing whitespace
41
54
  def apply(source)
42
55
  RULES.sort_by(&:position).reduce(source.to_s) { |text, rule| step(text, rule) }.strip
43
56
  end
44
57
 
58
+ # Applies one normalisation `rule` to `text`, dispatching on the
59
+ # rule's strategy.
60
+ #
61
+ # @param text [String] the source text (or string-literal segment) to
62
+ # transform
63
+ # @param rule [Rule] the normalisation rule to apply
64
+ # @return [String] `text` after the rule is applied
65
+ # @raise [ArgumentError] if `rule.strategy` names a strategy this
66
+ # method does not implement
45
67
  def step(text, rule)
46
68
  case rule.strategy
47
69
  when "collapse_whitespace" then map_outside_strings(text) { |segment| segment.gsub(/\s+/, " ") }
@@ -51,6 +73,14 @@ module Hecks
51
73
  end
52
74
  end
53
75
 
76
+ # Substitutes every occurrence of `rule.source_token` in `text` with
77
+ # `rule.replacement`, honoring the rule's word-boundary setting.
78
+ #
79
+ # @param text [String] the source text (or string-literal segment) to
80
+ # transform
81
+ # @param rule [Rule] the `"replace"`-strategy rule supplying the
82
+ # token, replacement, and boundary
83
+ # @return [String] `text` with the substitution applied
54
84
  def replace(text, rule)
55
85
  map_outside_strings(text) do |segment|
56
86
  if rule.boundary == "none"
@@ -61,21 +91,30 @@ module Hecks
61
91
  end
62
92
  end
63
93
 
64
- # Applies a normalisation rule to the text OUTSIDE quoted string
65
- # literals only, copying every quoted run through byte-for-byte.
66
- # Every rule here (collapse_whitespace, the `.length`→`.size` fold)
67
- # used to run quote-blind — `"a b"` collapsed to `"a b"` and
68
- # `"a.length"` folded to `"a.size"` just as readily as the real
69
- # source outside the quotes, silently rewriting what a predicate
70
- # compares a string attribute against, not merely how the
71
- # predicate itself is spelled. A canonical string literal's
72
- # CONTENTS are data, never syntax to normalise.
94
+ # Applies the given block to the text outside quoted string literals
95
+ # only, copying every quoted run through byte-for-byte. Every rule
96
+ # here (collapse_whitespace, the `.length`→`.size` fold) must stay
97
+ # quote-aware: run it quote-blind and `"a b"` collapses to `"a b"`,
98
+ # `"a.length"` folds to `"a.size"`, just as readily as the real
99
+ # source outside the quotes — silently rewriting what a predicate
100
+ # compares a string attribute against, not merely how the predicate
101
+ # itself is spelled. A canonical string literal's contents are data,
102
+ # never syntax to normalise.
73
103
  #
74
104
  # Handles both `"` and `'` delimiters (this grammar's own
75
105
  # `Resolver.quoted?` admits either), quote-aware exactly the way
76
106
  # `Evaluator.top_level_index`/`Resolver.array_elements` already are
77
107
  # elsewhere in this sublanguage. An unterminated quote (malformed
78
108
  # input) is passed through raw rather than risk mangling it further.
109
+ #
110
+ # @param text [String] the source text to scan for quoted string
111
+ # literals
112
+ # @yieldparam segment [String] a run of `text` outside any quoted
113
+ # literal
114
+ # @yieldreturn [String] the transformed replacement for `segment`
115
+ # @return [String] `text` with the block applied to every unquoted
116
+ # run, and every quoted run (`"`- or `'`-delimited) copied through
117
+ # unchanged
79
118
  def map_outside_strings(text)
80
119
  result = +""
81
120
  buffer = +""
@@ -93,9 +132,9 @@ module Hecks
93
132
  result << yield(buffer)
94
133
  # `char.dup`, not `char.to_s` (a no-op on a String — always
95
134
  # returns self, never a copy) and not `+char` either
96
- # (`String#+@` only dups a FROZEN receiver; `each_char`'s
135
+ # (`String#+@` only dups a frozen receiver; `each_char`'s
97
136
  # yielded strings aren't frozen, so `+char` is just as
98
- # much a no-op here). Without a REAL copy, `buffer` and
137
+ # much a no-op here). Without a real copy, `buffer` and
99
138
  # `quote` alias the same mutable object: the very next
100
139
  # `buffer << char` grows `quote` right along with it, so
101
140
  # `char == quote` can only ever compare a single character
@@ -103,12 +142,12 @@ module Hecks
103
142
  # literal — everything after a predicate's first quoted
104
143
  # string silently skipped normalisation for the rest of
105
144
  # the text, undetected because passing text through
106
- # unnormalised is silent. MASKED by every existing spec
107
- # here, which only checks that quoted CONTENTS survive
145
+ # unnormalised is silent. Masked by every existing spec
146
+ # here, which only checks that quoted contents survive
108
147
  # untouched (the M7 fix this method exists for) — that
109
148
  # still holds by accident once the bug makes the "outside"
110
149
  # branch unreachable. Found live: a multi-line `given`/
111
- # `ensures` block whose ONLY quoted literal closes before a
150
+ # `ensures` block whose only quoted literal closes before a
112
151
  # later line — the newline and that later line's own
113
152
  # indentation went uncollapsed, diverging from
114
153
  # `hecks-parse`'s own (correct) single-space join.