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
@@ -6,10 +6,10 @@ module Hecks
6
6
  # The language now spells its fields exactly as the IR spells them, so the
7
7
  # judge reads almost everything straight through: `command.givens`,
8
8
  # `value_object.invariants`, `read_model.aggregate_heads`. What remains here
9
- # is not naming drift — it is places where the IR's SHAPE differs from the
9
+ # is not naming drift — it is places where the IR's shape differs from the
10
10
  # language's, and no amount of renaming would close that:
11
11
  #
12
- # transitions one declaration expands to SEVERAL rows, because `from`
12
+ # transitions one declaration expands to several rows, because `from`
13
13
  # may be a list of states
14
14
  # value_objects the IR holds objects ; the language holds their names
15
15
  # normalisations not on the bluebook at all — they come from the canonical
@@ -18,14 +18,21 @@ module Hecks
18
18
  # lifecycle one IR object feeding two separate fields
19
19
  #
20
20
  # Everything in this file is a difference in shape. If something here is
21
- # only a difference in NAME, it is in the wrong file: rename the language.
21
+ # only a difference in name, it is in the wrong file: rename the language.
22
22
  module Readings
23
23
  # A list the walk is about to offer, as rows it can shape into dispatches.
24
24
  #
25
- # FROM THE TABLE. This was nine hand-written cases keyed "Category.list", and
25
+ # From the table. This was nine hand-written cases keyed "Category.list", and
26
26
  # every one of them was a fact `Assembly::Contracts` is the right place to
27
27
  # keep: which shaper turns this list into rows. A list with no shaper reads
28
28
  # straight off the node, which is most of them.
29
+ #
30
+ # @param category [String] the node's own construct category, such
31
+ # as `"Command"` or `"ValueObject"`
32
+ # @param list_name [String] the appendable list's own field name
33
+ # @param node [Object] the built IR node being judged
34
+ # @return [Array<Hash>] one row per element of `node`'s own
35
+ # `list_name`, shaped by the category's own shaper if it has one
29
36
  def rows_for(category, list_name, node)
30
37
  shaper = Assembly.contract(category).shaper(list_name)
31
38
  return Array(node.public_send(list_name)) unless shaper
@@ -34,44 +41,84 @@ module Hecks
34
41
  end
35
42
 
36
43
  # A where-clause is read through its own to_h, which is where the IR spells a
37
- # symbol argument as ":ceiling". Reading the OBJECT instead lost the colon,
44
+ # symbol argument as ":ceiling". Reading the object instead lost the colon,
38
45
  # and nothing downstream could tell an argument from a literal of the same
39
46
  # name.
47
+ #
48
+ # @param node [Object] the built IR node being judged (a `Query` or
49
+ # `ReadModel`)
50
+ # @return [Array<Hash{Symbol => Object}>] each declared `where`
51
+ # clause's own fields
40
52
  def where_rows(node) = Array(node.wheres).map(&:to_h)
41
53
 
42
- # The language holds a value object's NAME here ; the IR holds the object.
54
+ # The language holds a value object's name here ; the IR holds the object.
55
+ #
56
+ # @param node [Object] the built IR node being judged (an
57
+ # `Aggregate`)
58
+ # @return [Array<Hash{Symbol => String}>] `{name: ...}` for every
59
+ # declared value object
43
60
  def value_object_names(node) = node.value_objects.map { |shape| { name: shape.hecks_name } }
44
61
 
45
- # AN IDENTITY IS A LIST OF PARTS, so it is offered one part at a time — the
62
+ # An identity is a list of parts, so it is offered one part at a time — the
46
63
  # same way attributes and transitions are. The IR holds the paths ; the
47
- # language holds a row per path, and the ORDER between them is the whole
64
+ # language holds a row per path, and the order between them is the whole
48
65
  # meaning, because the identity is their join.
66
+ #
67
+ # @param node [Object] the built IR node being judged (an
68
+ # `Aggregate` or `Entity`)
69
+ # @return [Array<Hash{Symbol => String}>] `{value: ...}` for every
70
+ # identity path part, in order
49
71
  def identity_rows(node) = node.identity_paths.map { |path| { value: path } }
50
72
 
51
73
  # Through to_h, which is where Bluebook.render_value spells a symbol argument as
52
74
  # ":source". The raw with_spec lost the colon, and a binding that reads an
53
75
  # argument became indistinguishable from one carrying a literal string.
76
+ #
77
+ # @param node [Object] the built IR node being judged (a `Dispatch`)
78
+ # @return [Array<Hash{Symbol => Object}>] `pair_rows`' own return
79
+ # for `node`'s own `with_spec`
54
80
  def with_spec_rows(node) = pair_rows(node.to_h[:with_spec])
55
81
 
56
- # THE SAME READ, ONE LEVEL IN — `compensates` folds `DispatchSpec`
82
+ # The same read, one level in — `compensates` folds `DispatchSpec`
57
83
  # into the language's own `compensates_command_name`/
58
84
  # `compensates_with_spec` (`Assembly::Contracts`' own comment on
59
- # "Dispatch"), so its OWN with_spec pairs live nested one hash
85
+ # "Dispatch"), so its own with_spec pairs live nested one hash
60
86
  # down from where `with_spec_rows` looks. `&.dig(...)` — no
61
87
  # compensation at all is not an error, it is `pair_rows(nil)`, empty.
88
+ #
89
+ # @param node [Object] the built IR node being judged (a `Dispatch`)
90
+ # @return [Array<Hash{Symbol => Object}>] `pair_rows`' own return
91
+ # for `node`'s own `compensates.with_spec`, `[]` when `node` has
92
+ # no compensation
62
93
  def compensates_with_spec_rows(node) = pair_rows(node.to_h[:compensates]&.dig(:with_spec))
63
94
 
64
95
  # A read model carries the same options an ask does, plus its filters — see
65
96
  # option_rows.
97
+ #
98
+ # @param node [Object] the built IR node being judged (a
99
+ # `ReadModel`)
100
+ # @return [Array<Hash{Symbol => Object}>] `option_rows`' own return,
101
+ # with filters included
66
102
  def read_model_option_rows(node) = option_rows(node, filters: true)
67
103
 
68
104
  # The canonical-form table is the expression grammar's, not this chapter's, so
69
105
  # the node is not consulted at all.
106
+ #
107
+ # @param _node [Object] unused; every node shares the one table
108
+ # @return [Array<Hash{Symbol => Object}>] `normalisation_rows`' own
109
+ # return
70
110
  def normalisation_table(_node) = normalisation_rows
71
111
 
72
112
  # `lifecycle :status do transition "Retire" => "retired", from: ["issued", "active"] end`
73
- # is ONE declaration and TWO transitions. Offering it once would leave the
113
+ # is one declaration and two transitions. Offering it once would leave the
74
114
  # second unjudged, which is the whole failure this judge exists to avoid.
115
+ #
116
+ # @param node [Object] the built IR node being judged (an
117
+ # `Aggregate` or `Entity`)
118
+ # @return [Array<Hash{Symbol => Object}>] `:command`, `:from_state`,
119
+ # and `:to_state`, one row per declared `from` state (or one row
120
+ # with a `nil` `:from_state` for an unconstrained transition);
121
+ # `[]` if `node` declares no lifecycle
75
122
  def transition_rows(node)
76
123
  lifecycle = node.respond_to?(:lifecycle) ? node.lifecycle : nil
77
124
  return [] unless lifecycle
@@ -84,17 +131,21 @@ module Hecks
84
131
  end
85
132
  end
86
133
 
87
- # An OPEN MAP — a member's fields, a dispatch's argument bindings — has no
134
+ # An open map — a member's fields, a dispatch's argument bindings — has no
88
135
  # value object that can hold it, so each entry becomes its own row. This is
89
136
  # why Member and Dispatch are roots in the language rather than lists.
137
+ #
138
+ # @param map [Hash, nil] the open map to flatten, or `nil`
139
+ # @return [Array<Hash{Symbol => Object}>] `{key:, value:}` for every
140
+ # entry, `[]` when `map` is `nil`
90
141
  def pair_rows(map)
91
142
  Array(map&.to_h).map { |key, value| { key: key, value: value } }
92
143
  end
93
144
 
94
- # EVERY SPECIFICATION OPTION AN ASK CARRIES, flattened to rows.
145
+ # Every specification option an ask carries, flattened to rows.
95
146
  #
96
147
  # `offset`, `cursor`, `nulls`, `authorize` and `inspect_query` are
97
- # five options, one compound (authorize names a policy AND a
148
+ # five options, one compound (authorize names a policy and a
98
149
  # tenant). `extra_options_to_h` already spells every one of them
99
150
  # and drops the absent ones, so this reads that rather than
100
151
  # naming them here — a sixth option needs no change on either
@@ -103,18 +154,18 @@ module Hecks
103
154
  # `filters: true` adds a read model's wheres, order_by and limit —
104
155
  # `at` tells repeated rows apart, so two wheres do not collapse.
105
156
  #
106
- # THE LANGUAGE MAY HOLD MORE THAN `to_h` CARRIES, and this is where that
157
+ # The language may hold more than `to_h` carries, and this is where that
107
158
  # mattered. Until 2026-08-11, `ReadModel#to_h` omitted all three —
108
159
  # `extra_options_to_h` rejects them by name, still does — so a read
109
160
  # model's filtering had never been in the wire contract, and I first
110
161
  # read that as a wall: if the wire cannot carry it, the language cannot
111
162
  # hold it, and a graph assembled from the language must lose it.
112
163
  #
113
- # That was the wrong conclusion. `to_h` is a PROJECTION ; the language
114
- # is the SOURCE. They have to agree about everything
164
+ # That was the wrong conclusion. `to_h` is a projection ; the language
165
+ # is the source. They have to agree about everything
115
166
  # to_h spells, not about everything the language knows. Held as option
116
167
  # rows, the filters survived the round trip regardless of whether the
117
- # wire carried them too — which is exactly why, when a LATER task
168
+ # wire carried them too — which is exactly why, when a later task
118
169
  # (Rust read-model codegen) needed `wheres`/`order_by`/`limit` on the
119
170
  # wire for an unrelated reason, `ReadModel#to_h` could be extended to
120
171
  # spell them (the same mechanism `Query#to_h` already used) without
@@ -125,6 +176,14 @@ module Hecks
125
176
  #
126
177
  # Named `wheres`, `order_by` and `limit` so they gather back into exactly the
127
178
  # declaration keys the assembly already reads.
179
+ #
180
+ # @param node [Object] the built IR node being judged (a `Query` or
181
+ # `ReadModel`)
182
+ # @param filters [Boolean] whether to also flatten `node`'s own
183
+ # `wheres`/`order_by`/`limit`
184
+ # @return [Array<Hash{Symbol => Object}>] `[]` if `node` answers no
185
+ # `extra_options_to_h`; otherwise `parts`' own rows for every
186
+ # option `node` carries
128
187
  def option_rows(node, filters: false)
129
188
  return [] unless node.respond_to?(:extra_options_to_h)
130
189
 
@@ -139,6 +198,14 @@ module Hecks
139
198
  end
140
199
  end
141
200
 
201
+ # Reads a read model's own filtering fields, in the shape
202
+ # `option_rows` expects to flatten alongside its ordinary options.
203
+ #
204
+ # @param node [Object] the built IR node being judged (a
205
+ # `ReadModel`)
206
+ # @return [Hash{Symbol => Object}] `:wheres` (`Array<Hash>`),
207
+ # `:order_by`, and `:limit` (each `Hash` or absent), absent keys
208
+ # dropped
142
209
  def filter_options(node)
143
210
  {
144
211
  wheres: Array(node.wheres).map(&:to_h),
@@ -147,24 +214,40 @@ module Hecks
147
214
  }.reject { |_, held| held.nil? || held == [] }
148
215
  end
149
216
 
217
+ # Flattens one option's own value (or one element of a repeated
218
+ # option) into key/value rows.
219
+ #
220
+ # @param option [Symbol] the option's own name
221
+ # @param held [Object] the option's own value (a Hash-coercible
222
+ # object) — one repetition's worth, for a repeated option
223
+ # @param at [Integer, nil] the repetition index, for a repeated
224
+ # option; `nil` otherwise
225
+ # @return [Array<Hash{Symbol => String, Object}>] `:option`, `:key`,
226
+ # `:value`, and `:at` (stringified, `nil` when `at` is `nil`), one
227
+ # per key of `held`
150
228
  def parts(option, held, at)
151
229
  Hash(held).map do |key, value|
152
230
  { option: option.to_s, key: key.to_s, value: value, at: at&.to_s }
153
231
  end
154
232
  end
155
233
 
156
- # A mutation is ONE declaration, but the language's Change holds a single
157
- # field/kind/source triple — and an append binds SEVERAL fields at once
234
+ # A mutation is one declaration, but the language's Change holds a single
235
+ # field/kind/source triple — and an append binds several fields at once
158
236
  # (`append: { name: :name, amount: :amount }`). So an append is offered
159
237
  # once per binding, and each one is judged.
160
238
  #
161
- # The judge used to send `field: v(""), kind: v("argument"), source: v("")`
162
- # here — three stubbed values, so every rule about what a mutation reads
163
- # was being handed a blank and could never refuse.
239
+ # Sending `field: v(""), kind: v("argument"), source: v("")` here
240
+ # instead — three stubbed values — would hand every rule about what
241
+ # a mutation reads a blank, so it could never refuse.
242
+ #
243
+ # @param node [Object] the built IR node being judged (a `Command`)
244
+ # @return [Array<Hash{Symbol => Object}>] one row per binding for an
245
+ # append/delegate/corrects mutation, or `set_row`'s own single-row
246
+ # array for a set/increment/decrement
164
247
  def mutation_rows(node)
165
248
  Array(node.mutations).flat_map do |mutation|
166
249
  # `:delegate`/`:corrects` (CommandBuilder#delegates_to's and
167
- # #corrects_impl's own comments) ride the SAME multi-binding
250
+ # #corrects_impl's own comments) ride the same multi-binding
168
251
  # shape `:append` does — `with: {...}`/the assembled
169
252
  # `as:`/`reason:`/`reverses:` hash is a field map, same as
170
253
  # append's own `fields:`.
@@ -174,7 +257,7 @@ module Hecks
174
257
  # Spelled the way Mutation#appended_fields spells it, because
175
258
  # Assembly::Marks reads this row back through the same reader it
176
259
  # reads that field with. `then_set :marks, append: { direction:
177
- # "out" }` binds a LITERAL, and storing it raw made it
260
+ # "out" }` binds a literal, and storing it raw made it
178
261
  # indistinguishable from an argument called out.
179
262
  { target: mutation.target, op: mutation.op, field: field,
180
263
  kind: argument.is_a?(Symbol) ? "argument" : "literal",
@@ -185,6 +268,11 @@ module Hecks
185
268
 
186
269
  # A set/increment/decrement reads one thing: a command argument, or a
187
270
  # literal written into the bluebook.
271
+ #
272
+ # @param mutation [Bluebook::Mutation] a set/increment/decrement
273
+ # mutation
274
+ # @return [Array(Hash{Symbol => Object})] a single-element array
275
+ # holding `:target`, `:op`, `:field`, `:kind`, and `:source`
188
276
  def set_row(mutation)
189
277
  classified = mutation.to_h[:source] || {}
190
278
 
@@ -196,6 +284,10 @@ module Hecks
196
284
  # The normalisation table belongs to the expression grammar, not to any one
197
285
  # bluebook — it is how the canonical form of a rule is spelled. The language
198
286
  # models it because a bluebook's rules are canonicalised on the way in.
287
+ #
288
+ # @return [Array<Hash{Symbol => Object}>] one row per admitted
289
+ # normalisation rule (`:strategy`, `:source_token`, `:replacement`,
290
+ # `:boundary`, `:position`), `[]` if the table cannot be read
199
291
  def normalisation_rows
200
292
  table = Expression::CanonicalForm.table
201
293
  return [] unless table
@@ -215,17 +307,19 @@ module Hecks
215
307
  []
216
308
  end
217
309
 
218
- # What the BLUEBOOK calls a node, whichever kind of thing the node is.
310
+ # What the bluebook calls a node, whichever kind of thing the node is.
219
311
  #
220
- # This used to sniff — `respond_to?(:hecks_name) ? … : node.name` — because
221
- # only value objects had crossed over. Every construct answers now, so
222
- # there is nothing to choose between. It disappears entirely when the DSL
223
- # stops handing the judge nodes at all.
312
+ # Every construct answers `hecks_name` now, so there is nothing to sniff
313
+ # (`respond_to?(:hecks_name) ? … : node.name`) or choose between. This
314
+ # disappears entirely when the DSL stops handing the judge nodes at all.
315
+ #
316
+ # @param node [Object] the built IR node being judged
317
+ # @return [String] the node's own declared name
224
318
  def declared_name(node) = node.hecks_name
225
319
 
226
320
  # One field of a Declare payload. Mostly a reader of the same name — the
227
321
  # exceptions are fields the IR keeps somewhere else, or not at all.
228
- # READ FROM THE TABLE, not from a branch per category.
322
+ # Read from the table, not from a branch per category.
229
323
  #
230
324
  # These were eight hand-written cases — `Entity.owner`, `Member.shape`, two
231
325
  # lifecycle members twice over, and three of a query's — each one restating
@@ -233,11 +327,23 @@ module Hecks
233
327
  # `:parent` there ; a folded field names the object and member it lives in.
234
328
  # So the exceptions are looked up rather than repeated, and a new fold is one
235
329
  # line in one file instead of two lines in two.
330
+ #
331
+ # @param category [String] the node's own construct category, such
332
+ # as `"Command"` or `"ValueObject"`
333
+ # @param node [Object] the built IR node being judged
334
+ # @param field [Symbol] the Declare payload field to read
335
+ # @param parent_id [String] the id the containment walk carries in
336
+ # from one level up, offered when `field` is the parent pointer
337
+ # @return [Object] `field`'s own value: `node`'s declared name,
338
+ # `parent_id`, a folded member's value (`through`), the read
339
+ # model's own limit value, an encoded literal (`provenance`), or
340
+ # `node.public_send(field)`; `nil` if `node` does not respond to
341
+ # `field`
236
342
  def field_value(category, node, field, parent_id)
237
343
  return declared_name(node) if field == :name
238
344
 
239
345
  contract = Assembly.contract(category)
240
- # A setter names its target as a STRING and a Declare field arrives a Symbol,
346
+ # A setter names its target as a string and a Declare field arrives a Symbol,
241
347
  # so the lookup keys on a Symbol either way. The case statement this replaced
242
348
  # was type-blind because it interpolated ; a Hash is not.
243
349
  named = field.to_sym
@@ -246,19 +352,19 @@ module Hecks
246
352
  object, member = contract.folded(named)
247
353
  return through(node, object, member) if member
248
354
 
249
- # `limit` is a language field AND an object in the IR — `Array(an_object)`
355
+ # `limit` is a language field and an object in the IR — `Array(an_object)`
250
356
  # wraps rather than destructures, so offering it stored
251
357
  # "#<struct LimitSpec value=3>".
252
358
  return node.limit&.to_h&.fetch(:value, nil) if "#{category}.#{field}" == "Query.limit"
253
359
 
254
- # `provenance from: {...}` is a HASH offered into a text field, and
360
+ # `provenance from: {...}` is a hash offered into a text field, and
255
361
  # handing it over raw let the runtime's own coercion spell it — which
256
362
  # meant Ruby's `Hash#to_s`, whose spelling changed under us between
257
363
  # 3.3 and 3.4. Encoded here, the same way `default:` already is and the
258
364
  # same way Shapes#provenance reads it back.
259
365
  return encode_literal(node.provenance) if field == :provenance
260
366
 
261
- # `identified_by` is no longer a FIELD of any declaration — it is a list,
367
+ # `identified_by` is no longer a field of any declaration — it is a list,
262
368
  # filled by Identify one part at a time, so it is read through `identity_rows`
263
369
  # like every other list rather than special-cased here. What this branch
264
370
  # existed to protect is now structural : a path cannot come back as its head,
@@ -268,9 +374,17 @@ module Hecks
268
374
  end
269
375
 
270
376
  # One member of the object a field folds into. `to_h` first, because the
271
- # member names are the ones the IR SPELLS — a Lifecycle's `default`, an
377
+ # member names are the ones the IR spells — a Lifecycle's `default`, an
272
378
  # OrderBy's `direction` — and reading the object raw is how a colon or a type
273
379
  # goes missing.
380
+ #
381
+ # @param node [Object] the built IR node being judged
382
+ # @param object [Symbol] the member object's own field name on
383
+ # `node`, such as `:lifecycle`
384
+ # @param member [Symbol] the field to read off that object, such as
385
+ # `:default`; `:transitions` returns the object itself
386
+ # @return [Object, nil] the member's own value, or `nil` if `node`
387
+ # does not respond to `object` or that object is absent
274
388
  def through(node, object, member)
275
389
  held = node.respond_to?(object) ? node.public_send(object) : nil
276
390
  return nil unless held
@@ -279,8 +393,15 @@ module Hecks
279
393
  end
280
394
 
281
395
  # What a setting command writes. A setter whose source is absent is not
282
- # dispatched at all — ABSENT is not EMPTY, and offering "" would turn every
396
+ # dispatched at all — absent is not empty, and offering "" would turn every
283
397
  # "if you declare it, declare something" rule into "you must declare it".
398
+ #
399
+ # @param category [String] the node's own construct category
400
+ # @param node [Object] the built IR node being judged
401
+ # @param target [Symbol] the setter's own target field
402
+ # @return [Object, nil] the target's own value: the closed set's own
403
+ # row count, a folded member's value (`through`), or
404
+ # `node.public_send(target)`; `nil` if absent
284
405
  def setter_value(category, node, target)
285
406
  # `rows` folds into `closed_set` and `members` between them, with no single
286
407
  # member to name, so it keeps its own reading — see Contract#folded.
@@ -292,65 +413,94 @@ module Hecks
292
413
  node.respond_to?(target) ? node.public_send(target) : nil
293
414
  end
294
415
 
295
- # Only a DECLARED closed set has a row count. An empty one is the defect,
416
+ # Only a declared closed set has a row count. An empty one is the defect,
296
417
  # so `rows` must stay absent rather than arrive as zero.
418
+ #
419
+ # @param node [Object] the built IR node being judged (a
420
+ # `ValueObject`)
421
+ # @return [Integer, nil] the closed set's own row count, or `nil` if
422
+ # `node` is not a declared closed set
297
423
  def closed_set_size(node)
298
424
  return nil unless node.respond_to?(:closed_set?) && node.closed_set?
299
425
 
300
426
  Array(node.members).size
301
427
  end
302
428
 
303
- # `Reference<Customer>` is an IR ENCODING, not a domain fact. The fact is
429
+ # `Reference<Customer>` is an IR encoding, not a domain fact. The fact is
304
430
  # that the attribute points at Customer's head — so the language is offered
305
431
  # that head's ID, and resolution does the rest. Encoding and decoding both
306
432
  # live here, because this is where the IR's shape differs from the
307
433
  # language's and nowhere else should know the spelling.
434
+ #
435
+ # @param row [Bluebook::Attribute] the attribute being offered
436
+ # @param aggregate_id [String] the owning head's own id, read for
437
+ # its chapter prefix
438
+ # @return [String, nil] the target head's own id, or `nil` if `row`
439
+ # is not a `reference_to` attribute
308
440
  def points_at(row, aggregate_id)
309
441
  return nil unless row.reference?
310
442
 
311
- # THE CHAPTER THIS HEAD IS IN, AND THE HEAD IT POINTS AT — which is exactly
443
+ # The chapter this head is in, and the head it points at — which is exactly
312
444
  # how an aggregate is identified, so it is built the same way rather than
313
- # spelled again with a separator of its own. This is dispatched as a REAL
314
- # REFERENCE VALUE (`Aggregate.Reference`'s `points_at:`), resolved by
315
- # `repository.find` against the target Aggregate-within-Meta record's OWN
316
- # stored id — so it MUST equal what that record's identity actually
445
+ # spelled again with a separator of its own. This is dispatched as a real
446
+ # reference value (`Aggregate.Reference`'s `points_at:`), resolved by
447
+ # `repository.find` against the target Aggregate-within-Meta record's own
448
+ # stored id — so it must equal what that record's identity actually
317
449
  # derives, not a wire-format spelling. `reference_type`, below, is the
318
450
  # separate later reader that un-derives it back into "Reference<X>".
319
451
  Naming.identity([aggregate_id.split(Naming::IDENTITY_JOIN).first, row.type.target_name])
320
452
  end
321
453
 
322
- # A LITERAL, written so it can be read back exactly.
454
+ # A literal, written so it can be read back exactly.
323
455
  #
324
456
  # The language holds a default and a literal mutation source as text, and
325
457
  # `to_s` threw the type away: 0.0 came back "0.0", and `{ value: "good" }`
326
458
  # came back its inspect string with nowhere to say it had been a hash. The
327
459
  # language already stores code as text — `canonical: "cents >= 0"` — so an
328
- # encoding is in keeping; it simply has to be SELF-DESCRIBING. That rule is
460
+ # encoding is in keeping; it simply has to be self-describing. That rule is
329
461
  # now Hecks::Literal's, stated once and shared with every other
330
462
  # to_h-bound literal field ; Shapes#decode_literal reads it back.
331
463
  #
332
464
  # nil stays nil rather than becoming "nil": absent is a real answer here,
333
465
  # and the language's own field is optional.
466
+ #
467
+ # @param value [Object] the value to encode: `nil`, `Symbol`,
468
+ # `String`, `Hecks::StateRef`, `true`, `false`, `Integer`, `Float`,
469
+ # `Hash`, or `Array` (recursively)
470
+ # @return [String, nil] the self-describing wire spelling, or `nil`
471
+ # when `value` is `nil`
334
472
  def encode_literal(value) = value.nil? ? nil : Literal.render(value)
335
473
 
336
474
  # The way back out: an aggregate id becomes the type the IR spells. The
337
- # id is a JOIN of chapter + name (Naming::IDENTITY_JOIN, the same join
475
+ # id is a join of chapter + name (Naming::IDENTITY_JOIN, the same join
338
476
  # `points_at` built it with, not the "::" a real bluebook's own type
339
477
  # names never carry) ; the wire format wants only the bare name.
478
+ #
479
+ # @param points_at_id [String] a target head's own id, as `points_at`
480
+ # built it
481
+ # @return [String] `"Reference<Name>"`, the bare target name
340
482
  def reference_type(points_at_id) = "Reference<#{points_at_id.to_s.split(Naming::IDENTITY_JOIN).last}>"
341
483
 
342
484
  # One value out of a row, named by the value object's field.
485
+ #
486
+ # @param row [Hash, Object] the row to read: a Hash, an object
487
+ # answering `field` directly, an object answering `to_h`, or a
488
+ # bare scalar
489
+ # @param field [Symbol] the field name to read
490
+ # @return [Object] `row[field]` for a Hash, `row.public_send(field)`
491
+ # when `row` answers `field` directly, `row.to_h[field]` for
492
+ # anything else to_h-able, or `row` itself for a bare scalar
343
493
  def row_value(row, field)
344
- # A Hash FIRST. Hash answers to `key` (Hash#key(value)) and to `value` on
494
+ # A Hash first. Hash answers to `key` (Hash#key(value)) and to `value` on
345
495
  # some rows, so asking respond_to? before checking for a Hash reads a
346
496
  # member pair through entirely the wrong method.
347
497
  return row[field] if row.is_a?(Hash)
348
498
  return row.public_send(field) if row.respond_to?(field)
349
- # A Struct answers to [] but RAISES for a member it does not have, so it
499
+ # A Struct answers to [] but raises for a member it does not have, so it
350
500
  # is read through to_h — a field the row simply lacks reads as absent.
351
501
  return row.to_h[field] if row.respond_to?(:to_h) && !row.is_a?(String)
352
502
 
353
- # A bare scalar row — `emits` is a list of event NAMES, and the
503
+ # A bare scalar row — `emits` is a list of event names, and the
354
504
  # Announcement value object has to call that string something.
355
505
  row
356
506
  end