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
@@ -5,12 +5,12 @@ module Hecks
5
5
  # Command, ValueObject, Query, ...); see `Contract`'s own header
6
6
  # (contract.rb) for the struct format each entry below fills in.
7
7
  class Assembly
8
- # ONE TABLE, WHERE THERE WERE FIVE HAND-WRITTEN MIRRORS OF IT.
8
+ # One table, where there were five hand-written mirrors of it.
9
9
  #
10
10
  # `bluebook.bluebook` already declares what every construct is made of, and
11
11
  # `Plan` already reads it — parent, fields, lists, setters. What the language
12
12
  # cannot say is the three things Ruby needs to rebuild one, so those are here
13
- # and ONLY those:
13
+ # and only those:
14
14
  #
15
15
  # holder which class holds it
16
16
  # make :declare for a construct that became a class, :new for an
@@ -19,23 +19,28 @@ module Hecks
19
19
  # fields each keyword the constructor takes, as
20
20
  # keyword => [key in the declaration, how to read it]
21
21
  #
22
- # A READER is a symbol naming a method on Marks, or :plain for a value that
22
+ # A reader is a symbol naming a method on Marks, or :plain for a value that
23
23
  # needs no decoding, or [:each, reader] for a list. Nothing here is a rule
24
24
  # about whether a declaration is admissible — the language settled that on the
25
25
  # way in. This is only how a spelling becomes an object again.
26
26
  #
27
- # WHY A TABLE AND NOT A METHOD PER CATEGORY. The judge used to carry one
28
- # hand-written branch per category, and the price was fourteen verbs the
29
- # language declared and the walk never offered — every rule hanging off them
30
- # decoration, and nothing red, because a branch that does not exist cannot
31
- # fail. An assembler with a method per category is the same shape. So the
32
- # table is checked AGAINST the language by spec/assembly_spec: a field the
33
- # language declares that no contract consumes is a failure, not a silence.
27
+ # Why a table and not a method per category: a hand-written branch per
28
+ # category can decorate a verb the language declares without the walk ever
29
+ # offering it, and nothing goes red, because a branch that does not exist
30
+ # cannot fail. An assembler with a method per category is the same shape.
31
+ # The table avoids it because spec/assembly_spec checks it against the
32
+ # language: a field the language declares that no contract consumes is a
33
+ # failure, not a silence.
34
34
  #
35
35
  # The `derived` list is how a field says it needs no assembling — a parent
36
36
  # pointer the containment tree already knows, or something computed from what
37
37
  # is here (`query_name` is `Naming.snake(name)`, `creates?` is whether a verb
38
38
  # names a root). Naming one is a claim, and the coverage gate holds it.
39
+ #
40
+ # @param category [String, Symbol] the construct category name, such as
41
+ # `"Aggregate"` or `:Command`
42
+ # @return [Bluebook::Assembly::Contract] the category's field contract
43
+ # @raise [KeyError] if no contract is registered for `category`
39
44
  def self.contract(category) = CONTRACTS.fetch(category.to_s)
40
45
 
41
46
  CONTRACTS = {
@@ -47,7 +52,8 @@ module Hecks
47
52
  vision: [:vision, :plain],
48
53
  classification: [:classification, :plain],
49
54
  formerly_known_as: [:formerly_known_as, :plain],
50
- attaches_to: [:attaches_to, :plain]
55
+ attaches_to: [:attaches_to, :plain],
56
+ provides: [:provides, :plain]
51
57
  },
52
58
  rows: { normalisations: :normalisation_table },
53
59
  derived: { normalisations: :elsewhere }
@@ -60,7 +66,7 @@ module Hecks
60
66
  description: [:description, :plain],
61
67
  identified_by: [:identified_by, :plain],
62
68
  attributes: [:attributes, [:each, :attribute]],
63
- # THE AGGREGATE BOUNDARY, and the precondition a command may
69
+ # The aggregate boundary, and the precondition a command may
64
70
  # reference by name (S10, ADR 0025 — "Rules"). Same reader
65
71
  # shapes ValueObject's own `invariants`/Command's own
66
72
  # `givens` already use — `invariant` builds an Invariant,
@@ -101,7 +107,7 @@ module Hecks
101
107
  ensures: [:ensures, [:each, :given]],
102
108
  mutations: [:mutations, [:each, :mutation]],
103
109
  emits: [:emits, :plain],
104
- # LIFECYCLE STATE AS A COMMAND GUARD (S10, ADR 0025) — a
110
+ # Lifecycle state as a command guard (S10, ADR 0025) — a
105
111
  # literal the same way `provenance`/`default:` already are;
106
112
  # one state or an array of them, or nil for a command with
107
113
  # no such guard.
@@ -138,7 +144,7 @@ module Hecks
138
144
  wheres: [:wheres, [:each, :where_clause]],
139
145
  order_by: [:order_by, :order_by],
140
146
  limit: [:limit, :limit],
141
- # Held by the language as an OPEN MAP, so every one of these reads the
147
+ # Held by the language as an open map, so every one of these reads the
142
148
  # same way and a ninth option needs no new field on either side.
143
149
  offset: [:offset, [:option, :offset]],
144
150
  cursor: [:cursor, [:option, :cursor]],
@@ -162,20 +168,20 @@ module Hecks
162
168
  fields: {
163
169
  name: [:name, :plain],
164
170
  description: [:description, :plain],
165
- # A LIST OF PATHS, exactly as an aggregate's is. The two used to differ
166
- # — a Symbol here and a String there, which byte equality with `to_h`
167
- # COULD NOT SEE because both render as a string, so the assembled graph
168
- # got a String and `element_of` looked up `args["sequence"]` in a
169
- # symbol-keyed payload and found nothing: "Reverse acts on one
170
- # LedgerEntry — pass sequence:", while passing sequence. There is one
171
- # spelling now, and no room left for that difference.
171
+ # A list of paths, exactly as an aggregate's is, with no room left for
172
+ # the two to differ. A Symbol here and a String there would look
173
+ # identical to `to_h`'s byte-equality check, since both render as a
174
+ # string, while diverging at runtime: the assembled graph would carry a
175
+ # String, and `element_of`'s lookup of `args["sequence"]` in a
176
+ # symbol-keyed payload would find nothing — refusing "Reverse acts on
177
+ # one LedgerEntry — pass sequence:" while sequence was in fact passed.
172
178
  identified_by: [:identified_by, :plain],
173
179
  attributes: [:attributes, [:each, :shape_field]],
174
- # ADR 0028 — the SAME shape Aggregate's own `preconditions`
180
+ # ADR 0028 — the same shape Aggregate's own `preconditions`
175
181
  # already carries, one level down: a piece's own named
176
182
  # `given`, referenced back by one of its own commands.
177
183
  preconditions: [:preconditions, [:each, :given]],
178
- # Round 7 — the SAME shape Aggregate's own `invariants`
184
+ # Round 7 — the same shape Aggregate's own `invariants`
179
185
  # already carries, one level down: checked against every
180
186
  # instance of this piece, not the aggregate's own flat state.
181
187
  invariants: [:invariants, [:each, :invariant]]
@@ -195,17 +201,18 @@ module Hecks
195
201
  "Policy" => Contract.new(
196
202
  holder: Policy, make: :new,
197
203
  fields: {
198
- name: [:name, :plain],
199
- aggregate: [:aggregate, :plain],
200
- on_event: [:on_event, :plain],
201
- trigger_command: [:trigger_command, :plain],
202
- target_domain: [:target_domain, :plain],
203
- where: [:where, :plain],
204
- for_each: [:for_each, :plain],
205
- with_spec: [:with_spec, :bindings]
204
+ name: [:name, :plain],
205
+ aggregate: [:aggregate, :plain],
206
+ on_event: [:on_event, :plain],
207
+ trigger_command: [:trigger_command, :plain],
208
+ target_domain: [:target_domain, :plain],
209
+ expect_undelivered: [:expect_undelivered, :plain],
210
+ where: [:where, :plain],
211
+ for_each: [:for_each, :plain],
212
+ with_spec: [:with_spec, :bindings]
206
213
  },
207
214
  rows: { with_spec: :with_spec_rows },
208
- reads: { with_spec: [:from, :with_spec] },
215
+ reads: { with_spec: [:from, :with_spec], expect_undelivered: :expect_undelivered? },
209
216
  derived: { position: :walk }
210
217
  ),
211
218
 
@@ -213,7 +220,7 @@ module Hecks
213
220
  holder: ProcessManager, make: :new,
214
221
  fields: {
215
222
  name: [:name, :plain],
216
- # A SYMBOL. `SagaInterpreter` does `event.payload[pm.correlates_by]` — a
223
+ # **A symbol**. `SagaInterpreter` does `event.payload[pm.correlates_by]` — a
217
224
  # hash lookup on a symbol-keyed payload — and `value == pm.correlates_by`
218
225
  # when resolving a leg s bindings. A String there finds nothing and
219
226
  # resolves to nothing, so the wire never advanced and a drawer that
@@ -224,7 +231,7 @@ module Hecks
224
231
  states: [:states, :plain]
225
232
  },
226
233
  reads: { states: :names },
227
- # S17, ADR 0026 — `handlers` is a REAL feature of the LANGUAGE's
234
+ # S17, ADR 0026 — `handlers` is a real feature of the language's
228
235
  # own "ProcessManager" declaration now (`attribute :handlers,
229
236
  # list_of(Handler)`, reaction.bluebook), consumed by the judge
230
237
  # walking `Plan`'s own containment tree (`Handler`'s own
@@ -237,7 +244,7 @@ module Hecks
237
244
  # S17, ADR 0026 — Handler is a genuine entity now, nested under
238
245
  # ProcessManager (`entity "Handler"`, reaction.bluebook). Neither
239
246
  # `position` nor `handler` is a stored field any more: a saga
240
- # answers each event ONCE, so `event_type` is Handler's own real,
247
+ # answers each event once, so `event_type` is Handler's own real,
241
248
  # non-positional identity (no walk-minted `position` to derive),
242
249
  # and the process manager it belongs to is structural now — which
243
250
  # list this element sits in, not a stored field to fold a parent
@@ -251,7 +258,7 @@ module Hecks
251
258
  },
252
259
  # `dispatches` — same reason ProcessManager's own `handlers`
253
260
  # claim, above, is `:children` : a real feature of the
254
- # LANGUAGE's own "Handler" declaration (`attribute :dispatches,
261
+ # language's own "Handler" declaration (`attribute :dispatches,
255
262
  # list_of(Dispatch)`), consumed by the judge walking `Plan`'s
256
263
  # own containment tree rather than by any field this contract
257
264
  # reads.
@@ -261,31 +268,31 @@ module Hecks
261
268
  # S17, ADR 0026 — Dispatch is a genuine entity now, nested under
262
269
  # Handler (`entity "Dispatch"`, process_manager.bluebook) — two
263
270
  # levels deep, "no life outside its Handler" (the ADR's own
264
- # words). `command_name` ALONE used to be Dispatch's own
265
- # identity, until items #151/#152 (`process_manager.bluebook`'s
266
- # own `DispatchPosition` comment) found it collided the instant a
267
- # real handler fanned the same command out more than once —
268
- # `position` now joins it, walk-minted the same way every other
271
+ # words). `command_name` alone is not Dispatch's own identity —
272
+ # items #151/#152 (`process_manager.bluebook`'s own
273
+ # `DispatchPosition` comment) found it collides the instant a
274
+ # real handler fans the same command out more than once, so
275
+ # `position` joins it, walk-minted the same way every other
269
276
  # category's own `position` is (`derived: { position: :walk }`,
270
277
  # the same entry ProcessManager's own contract carries above) —
271
278
  # never a stored field on `DispatchSpec` itself, exactly like
272
279
  # `handler` before it.
273
- # `compensates_command_name`/`compensates_with_spec` — FOLDED,
274
- # the SAME kind `Lifecycle`'s own `state_field`/`default` claim
275
- # (one IR OBJECT, `DispatchSpec#compensates`, feeding two
280
+ # `compensates_command_name`/`compensates_with_spec` — folded,
281
+ # the same kind `Lifecycle`'s own `state_field`/`default` claim
282
+ # (one IR object, `DispatchSpec#compensates`, feeding two
276
283
  # separate language fields): `Readings#field_value`'s own
277
284
  # `contract.folded` branch reads
278
285
  # `node.compensates.to_h[:command_name]` / `[:with_spec]` for
279
- # the JUDGE'S offering side, nil-safe when there is no
286
+ # the judge's offering side, nil-safe when there is no
280
287
  # compensation at all (`through`'s own `return nil unless
281
- # held`). `compensates_with_spec` ALSO needs its own row shaper
282
- # (`compensates_with_spec_rows`, readings.rb) for the JUDGE'S
288
+ # held`). `compensates_with_spec` also needs its own row shaper
289
+ # (`compensates_with_spec_rows`, readings.rb) for the judge's
283
290
  # list-offering side — the with_spec pairs still need one
284
291
  # "BindCompensation" append per pair, the same reason
285
292
  # `with_spec` itself needs `with_spec_rows`, one level deeper.
286
293
  # `Reconstruction#dispatch` reads the two flat fields back off
287
294
  # the row and assembles the nested `DispatchSpec` by hand — a
288
- # nested OBJECT is not one of the shapes `declaration()`'s own
295
+ # nested object is not one of the shapes `declaration()`'s own
289
296
  # generic per-field hash-build can produce, the identical reason
290
297
  # `handler`/`process_manager` pass a `:children` list through
291
298
  # `extra:` instead.
@@ -372,7 +379,7 @@ module Hecks
372
379
  # is what `Assembly::Build` needs (fed the native `to_h`
373
380
  # value directly, already `true`/`nil`/a String), and the
374
381
  # `reads:` entry below is what `Reconstruction` needs
375
- # instead (fed the STRINGIFIED meta-domain row).
382
+ # instead (fed the stringified meta-domain row).
376
383
  count: [:count, :plain],
377
384
  median_field: [:median_field, :plain],
378
385
  # A read model inherits every option an ask has, so it reads them the
@@ -392,12 +399,12 @@ module Hecks
392
399
  # `nil` — even though a read model's row never carries `wheres` as a
393
400
  # native field the way Query's does (`[:each, :where_clause]` over an
394
401
  # absent `row[:wheres]` is `Array(nil).map { ... }`, i.e. `[]`, always).
395
- # The REAL values, when a read model declares any, arrive through
402
+ # The real values, when a read model declares any, arrive through
396
403
  # `options_of(row)`'s merge in `read_model` below (dispatched as
397
404
  # generic Option rows, `read_model_option_rows`/`filter_options` — not
398
405
  # as dedicated where-clause rows), which overrides this default. Until
399
406
  # `ReadModel#to_h` spelled `wheres`/`order_by`/`limit`
400
- # unconditionally, `round_trip_spec`'s own "SOURCE KEYS ONLY" +compare
407
+ # unconditionally, `round_trip_spec`'s own "source keys only" +compare
401
408
  # never asked about this key at all, so the `nil`-vs-`[]` gap between
402
409
  # this contract's default and `to_h`'s own `[]` default went unnoticed.
403
410
  # `order_by`/`limit` need no matching entry — the generic reader's
@@ -419,7 +426,7 @@ module Hecks
419
426
  }
420
427
  ),
421
428
 
422
- # A member's pairs are an OPEN MAP, which is why Member is its own root in
429
+ # A member's pairs are an open map, which is why Member is its own root in
423
430
  # the language. The IR keeps them as a plain hash on the value object, so
424
431
  # they are assembled with their shape rather than as a construct.
425
432
  # S17, ADR 0026 — Member is a genuine entity now, nested under
@@ -6,10 +6,10 @@ module Hecks
6
6
  # `to_h` spells things as text so the export stands on its own, and every
7
7
  # one of those spellings has to come back apart here. This is the same family
8
8
  # of work `MetaValidator::Shapes` does for the reconstruction — the difference
9
- # is that Shapes rebuilds HASHES and this rebuilds OBJECTS, so it has to
9
+ # is that Shapes rebuilds hashes and this rebuilds objects, so it has to
10
10
  # recover types rather than just strings.
11
11
  #
12
- # ENCODING LOSSES ARE THE LARGEST FAMILY OF BUG IN THIS CODEBASE, and every
12
+ # Encoding losses are the largest family of bug in this codebase, and every
13
13
  # member has the same shape: reading an object where `to_h` holds a spelling.
14
14
  # So each method below names the spelling it inverts.
15
15
  module Marks
@@ -18,6 +18,14 @@ module Hecks
18
18
  # `Attribute#to_h` spells a type with `to_s`, so a reference arrives as
19
19
  # "Reference<Customer>" and has to become an edge again. Everything else is
20
20
  # a name and stays one.
21
+ #
22
+ # @param field [Hash{Symbol => Object}] one attribute's declared row, keyed
23
+ # `:type` (String, a plain type name or `"Reference<Target>"`), `:name`
24
+ # (String), `:list` and `:optional` (read as truthy), `:default` (Object,
25
+ # nil), `:pattern` (String, nil), `:admits` (String, nil) and
26
+ # `:relationship` (String, nil), each the raw declared value
27
+ # @return [Bluebook::Attribute] the attribute, with `type` resolved to a
28
+ # `Bluebook::Reference` when the row spelled one
21
29
  def attribute(field)
22
30
  type = field[:type].to_s
23
31
  target = type[/\AReference<(.+)>\z/, 1]
@@ -27,7 +35,7 @@ module Hecks
27
35
  type: target ? Reference.new(target) : type,
28
36
  list: field[:list] ? true : false,
29
37
  default: field[:default],
30
- # The LAST place optionality can be dropped, and the one that was
38
+ # The last place optionality can be dropped, and the one that was
31
39
  # dropping it. Every bluebook in the registry is the round-trip
32
40
  # product — MetaValidator dispatches the declaration in and reads it
33
41
  # back — so a fact this constructor does not carry is a fact the
@@ -35,9 +43,9 @@ module Hecks
35
43
  # wrote it.
36
44
  optional: field[:optional] ? true : false,
37
45
  pattern: field[:pattern],
38
- # THE SAME LESSON, ONE FACT LATER. `admits` is not on `to_h` — the
46
+ # **The same lesson, one fact later**. `admits` is not on `to_h` — the
39
47
  # wire does not carry it, on purpose — but it must still survive the
40
- # round trip, because the grammar registry keeps the ASSEMBLED graph
48
+ # round trip, because the grammar registry keeps the assembled graph
41
49
  # and downstream projections read the link off that. Dropped here, the
42
50
  # language could not say `admits` about itself no matter how plainly
43
51
  # the source wrote it, which is word for word what the note above
@@ -50,27 +58,45 @@ module Hecks
50
58
  # The same shape. Two names because a head's field and a verb's argument are
51
59
  # different things in the language even though the IR keeps one class for
52
60
  # both — `Aggregate.Attribute` and `Command.Argument` are separate verbs.
61
+ #
62
+ # @param field [Hash{Symbol => Object}] see `attribute`
63
+ # @return [Bluebook::Attribute] see `attribute`
53
64
  def shape_field(field) = attribute(field)
54
65
 
55
- # ONE PART OF AN IDENTITY. It goes in as a row so the language can hold an
66
+ # One part of an identity. It goes in as a row so the language can hold an
56
67
  # ordered list of them, and comes back out as the path it always was —
57
68
  # a String, because `identity_paths` splits paths and never symbols.
69
+ #
70
+ # @param part [Hash{Symbol => Object}] one identity-path row, keyed `:value`
71
+ # (Object, read via `to_s`)
72
+ # @return [String] the path segment
58
73
  def identity_path(part) = part[:value].to_s
59
74
 
60
- # A member's fields — an OPEN MAP, which is why Member is its own root in
75
+ # A member's fields — an open map, which is why Member is its own root in
61
76
  # the language and why the pairs arrive as a list rather than a value object.
62
77
  #
63
- # The values are UNMARKED, because `ValueObject#to_h` spells them with `to_s`
78
+ # The values are unmarked, because `ValueObject#to_h` spells them with `to_s`
64
79
  # and the language stores them as text: `member code: "JPY", minor_units: 0`
65
80
  # came back with a minor_units of "0", and a closed set that admits the string
66
81
  # would refuse the number the caller passes.
82
+ #
83
+ # @param pairs [Array<Array(String, Object)>] one member's own field pairs,
84
+ # each `[field_name, raw_value]` as declared
85
+ # @return [Hash{Symbol => Object}] the member's fields, keyed by field name,
86
+ # each value read back to its native type through `unmark_scalar`
67
87
  def member(pairs)
68
88
  pairs.to_h { |key, value| [key.to_sym, unmark_scalar(value)] }
69
89
  end
70
90
 
71
91
  # A read model's gathered head. The keys must be symbols whichever way the
72
- # declaration arrived, and `as` must be one too: it NAMES the reader the
92
+ # declaration arrived, and `as` must be one too: it names the reader the
73
93
  # projection answers to, and `ReadModel#to_h` spells it `to_s`.
94
+ #
95
+ # @param row [Hash{Symbol => Object}] one aggregate-head row, keyed
96
+ # `:aggregate` (String), `:as` (String, the reader name) and `:many`
97
+ # (Boolean)
98
+ # @return [Hash{Symbol => Object}] the head, with every key a Symbol and
99
+ # `:as` read back to a Symbol
74
100
  def head(row)
75
101
  row.to_h { |key, value| [key.to_sym, key.to_sym == :as ? value.to_sym : value] }
76
102
  end
@@ -78,12 +104,20 @@ module Hecks
78
104
  # A group_by field's own name — the builder's native shape is already
79
105
  # `{field: :symbol}`, so this matches it rather than leaving `field`
80
106
  # as the String `Shapes#group_by_field` reads back.
107
+ #
108
+ # @param row [Hash{Symbol => String}] one group-by row, keyed `:field`
109
+ # @return [Hash{field: Symbol}] the row with `:field` read back to a Symbol
81
110
  def group_by_field(row) = { field: row[:field].to_sym }
82
111
 
83
112
  # A scalar that was written as itself rather than inspected — a member's
84
113
  # value, where the language holds text and the type has to be read back from
85
114
  # the shape of it. Unlike `read`, a bare word stays a String here, because
86
115
  # a closed set admits words far more often than symbols.
116
+ #
117
+ # @param value [Object] the raw declared scalar, read via `to_s`
118
+ # @return [true, false, Integer, Float, String] `true`/`false` for those
119
+ # literal words, a number when the text matches a bare integer or decimal
120
+ # pattern, or the string itself otherwise
87
121
  def unmark_scalar(value)
88
122
  text = value.to_s
89
123
  return true if text == "true"
@@ -97,12 +131,32 @@ module Hecks
97
131
  # A saga's argument bindings. Each value rides Literal's spelling, which
98
132
  # marks a Symbol with a leading colon — lose it and an argument reads as a
99
133
  # string of the same name.
134
+ #
135
+ # @param with [Array<Array(String, String)>, nil] the declared bindings,
136
+ # each `[argument_name, literal_spelling]` pair, or `nil` for none
137
+ # @return [Hash{Symbol => Object}] the bindings, keyed by argument name,
138
+ # each value read back through `Literal.read`
100
139
  def bindings(with) = Array(with).to_h { |key, value| [key.to_sym, read(value)] }
101
140
 
141
+ # Builds one aggregate or value object invariant from its declared row.
142
+ #
143
+ # @param rule [Hash{Symbol => String}] one invariant's declared row, keyed
144
+ # `:description` and `:canonical` (the canonical-form rendering of the
145
+ # rule's expression)
146
+ # @return [Bluebook::Invariant] the invariant, with `predicate`/`ast` left
147
+ # `nil` — `Expression::Evaluator` parses `canonical` on demand when
148
+ # neither is set
102
149
  def invariant(rule)
103
150
  Invariant.new(description: rule[:description], canonical: rule[:canonical])
104
151
  end
105
152
 
153
+ # Builds one command precondition from its declared row.
154
+ #
155
+ # @param rule [Hash{Symbol => String}] one given's declared row, keyed
156
+ # `:description` and `:canonical` (the canonical-form rendering of the
157
+ # rule's expression)
158
+ # @return [Bluebook::Given] the given, with `predicate`/`ast` left `nil` —
159
+ # `Expression::Evaluator` parses `canonical` on demand when neither is set
106
160
  def given(rule)
107
161
  Given.new(description: rule[:description], canonical: rule[:canonical])
108
162
  end
@@ -111,6 +165,11 @@ module Hecks
111
165
  # All three fields are identifiers, unlike Invariant/Given's own
112
166
  # free text, so — like `attribute`'s own `name`/`type` below —
113
167
  # they come back as Symbols.
168
+ #
169
+ # @param row [Hash{Symbol => String}] one projected-field's declared row,
170
+ # keyed `:name`, `:reference` and `:remote_field` (each an identifier)
171
+ # @return [Bluebook::ProjectedField] the projected field, with each
172
+ # identifier read back to a Symbol
114
173
  def projected_field(row)
115
174
  ProjectedField.new(name: row[:name].to_sym, reference: row[:reference].to_sym,
116
175
  remote_field: row[:remote_field].to_sym)
@@ -118,16 +177,24 @@ module Hecks
118
177
 
119
178
  # `Mutation#to_h` branches on the operation, so this does too.
120
179
  #
121
- # An APPEND binds several fields at once, each either an ARGUMENT (a
122
- # Symbol, wearing its colon) or a LITERAL — the distinction that is the
180
+ # An append binds several fields at once, each either an argument (a
181
+ # Symbol, wearing its colon) or a literal — the distinction that is the
123
182
  # whole reason `append: { direction: "out" }` was once indistinguishable
124
183
  # from an argument named `out`.
184
+ #
185
+ # @param change [Hash{Symbol => Object}] one mutation's declared row, keyed
186
+ # `:target` (String), `:op` (String, such as `"set"`, `"append"`,
187
+ # `"delegate"` or `"corrects"`), `:fields` (Array, for the append-shaped
188
+ # ops) and `:source` (Hash, for any other op)
189
+ # @return [Bluebook::Mutation] the mutation, with `source` a
190
+ # `Hash{Symbol => Object}` of field bindings for an append-shaped op, or
191
+ # `classified`'s own result for any other
125
192
  def mutation(change)
126
193
  target = change[:target].to_sym
127
194
  op = change[:op].to_sym
128
195
 
129
196
  # `:delegate`/`:corrects` (CommandBuilder#delegates_to's and
130
- # #corrects_impl's own comments) ride the SAME multi-binding
197
+ # #corrects_impl's own comments) ride the same multi-binding
131
198
  # shape `:append` does.
132
199
  return Mutation.new(target: target, op: op, source: appended(change[:fields])) if [:append, :delegate,
133
200
  :corrects].include?(op)
@@ -135,12 +202,27 @@ module Hecks
135
202
  Mutation.new(target: target, op: op, source: classified(change[:source]))
136
203
  end
137
204
 
205
+ # Reads an append mutation's field bindings back off their declared rows.
206
+ #
207
+ # @param fields [Array<Array(String, String)>, nil] the append's declared
208
+ # field bindings, each `[field_name, literal_spelling]` pair, or `nil`
209
+ # for none
210
+ # @return [Hash{Symbol => Object}] the bindings, keyed by field name, each
211
+ # value read back through `Literal.read`
138
212
  def appended(fields)
139
213
  Array(fields).to_h { |field, source| [field.to_sym, read(source)] }
140
214
  end
141
215
 
142
- # A SET reads one thing, and `classified_source` said which: an argument by
216
+ # A set reads one thing, and `classified_source` said which: an argument by
143
217
  # name, or a literal by value.
218
+ #
219
+ # @param source [Hash{Symbol => Object}, nil] the mutation's declared
220
+ # source, keyed `:kind` (String, `"argument"`, `"state"` or a literal
221
+ # kind) plus `:name` (String, for `"argument"`/`"state"`) or `:value`
222
+ # (Object, for a literal), or `nil` for no source
223
+ # @return [Symbol, Hecks::StateRef, Object, nil] the command argument name,
224
+ # a state self-reference, the literal value, or `nil` when `source` is
225
+ # `nil`
144
226
  def classified(source)
145
227
  return nil if source.nil?
146
228
 
@@ -151,41 +233,57 @@ module Hecks
151
233
  end
152
234
  end
153
235
 
154
- # EVERY LITERAL FIELD ON THE WIRE, read back — one spelling, one reader.
236
+ # Every literal field on the wire, read back — one spelling, one reader.
155
237
  #
156
238
  # A where-clause value, a saga's argument bindings, an append binding, a
157
239
  # limit: all of them ride Literal's self-describing form, so all of them
158
- # come back through here. There were two readers (`read` and `unmark`)
159
- # that disagreed about quoted strings and numbers, and which one a call
160
- # site got was a coin toss the comments had to keep apologising for.
161
- #
162
- # AN OBJECT LITERAL is the one that bit. A saga leg binds `narrative: {
163
- # text: "transfer out" }` — a value object's fields written inline — and
164
- # `to_s` on a Hash used to be its inspect form, so it came back as text.
165
- # Read as a string it reached the runtime as `"{:text=>\"transfer out\"}"`,
166
- # coercion refused it, the debit leg was never delivered, and the whole
167
- # settlement wire stopped: banking emitted TransferRequested five times and
168
- # TransferDebited never. A whole-history replay gate caught what every other
169
- # gate missed, because a saga that silently does nothing looks exactly like
170
- # a saga with nothing to do.
240
+ # come back through this one reader, never a second one that might
241
+ # disagree about a quoted string or a number.
242
+ #
243
+ # An object literal is the one that bites hardest. A saga leg binds
244
+ # `narrative: { text: "transfer out" }` — a value object's fields written
245
+ # inline — and reading it as a plain string rather than through `Literal`
246
+ # would reach the runtime as `"{:text=>\"transfer out\"}"`, which coercion
247
+ # refuses: the debit leg is never delivered, and the whole settlement wire
248
+ # stops — banking emits TransferRequested five times and TransferDebited
249
+ # never. A whole-history replay gate is what catches this, because a saga
250
+ # that silently does nothing looks exactly like a saga with nothing to do.
251
+ #
252
+ # @param value [String, Object] wire spelling produced by `Literal.render`,
253
+ # or a bare word
254
+ # @return [nil, true, false, Integer, Float, Symbol, Hecks::StateRef,
255
+ # String, Hash, Array] the value read back — see `Literal.read`
171
256
  def read(value) = Literal.read(value)
172
257
 
173
258
  # `target:` (ADR 0055) — read straight off the wire, unconverted:
174
- # it's already the bare aggregate-name STRING `WhereClause#to_h`/
259
+ # it's already the bare aggregate-name string `WhereClause#to_h`/
175
260
  # `OrderBy#to_h`/`LimitSpec#to_h` wrote (`resolve_target`'s own
176
261
  # `Naming.demodulise` already ran once, at DSL-build time; this is
177
- # the REPLAY path every real boot actually goes through, reading
262
+ # the replay path every real boot actually goes through, reading
178
263
  # that same wire shape back — see this class's own header). Absent
179
264
  # from `clause`/`declared` entirely on older wire data that never
180
265
  # declared `on:` — `clause[:target]`/`declared[:target]` reads
181
266
  # `nil` for a missing key exactly like an explicit `nil` would,
182
267
  # so this is additive, not a migration.
268
+ #
269
+ # @param clause [Hash{Symbol => Object}] one where-clause's declared row,
270
+ # keyed `:field` (String), `:op` (String, a comparator name), `:value`
271
+ # (String, Literal's spelling) and `:target` (String, `nil` when absent)
272
+ # @return [QuerySpecification::Common::WhereClause] the clause, with `op`
273
+ # read to a Symbol and `value` read back through `Literal.read`
183
274
  def where_clause(clause)
184
275
  QuerySpecification::Common::WhereClause.new(
185
276
  field: clause[:field], op: clause[:op].to_sym, value: read(clause[:value]), target: clause[:target]
186
277
  )
187
278
  end
188
279
 
280
+ # Builds a query's declared ordering, if it declares one.
281
+ #
282
+ # @param declared [Hash{Symbol => Object}, nil] the declared order-by row,
283
+ # keyed `:field` (String), `:direction` (String) and `:target` (String,
284
+ # `nil` when absent), or `nil` when the construct declares no ordering
285
+ # @return [QuerySpecification::Common::OrderBy, nil] the order-by, with
286
+ # `direction` read to a Symbol, or `nil` when `declared` is `nil`
189
287
  def order_by(declared)
190
288
  return nil unless declared
191
289
 
@@ -194,13 +292,21 @@ module Hecks
194
292
  )
195
293
  end
196
294
 
295
+ # Builds a query's declared limit, if it declares one.
296
+ #
297
+ # @param declared [Hash{Symbol => Object}, nil] the declared limit row,
298
+ # keyed `:value` (String, Literal's spelling) and `:target` (String,
299
+ # `nil` when absent), or `nil` when the construct declares no limit
300
+ # @return [QuerySpecification::Common::LimitSpec, nil] the limit, with
301
+ # `value` read back through `Literal.read`, or `nil` when `declared` is
302
+ # `nil`
197
303
  def limit(declared)
198
304
  return nil unless declared
199
305
 
200
306
  QuerySpecification::Common::LimitSpec.new(value: read(declared[:value]), target: declared[:target])
201
307
  end
202
308
 
203
- # EVERY OTHER SPECIFICATION OPTION, from one table.
309
+ # Every other specification option, from one table.
204
310
  #
205
311
  # Each entry names the struct and which of its members carry a value that
206
312
  # rode Literal's spelling rather than plain text. A
@@ -219,6 +325,20 @@ module Hecks
219
325
  # with `to_s`, so the colon is not there to strip.
220
326
  SYMBOLIC = %i[mode policy tenant].freeze
221
327
 
328
+ # Builds one query specification option from its declared row and `OPTIONS`.
329
+ #
330
+ # @param name [Symbol] the option's name, a key of `OPTIONS` (`:offset`,
331
+ # `:cursor`, `:null_semantics`, `:authorization` or `:inspection`)
332
+ # @param declared [Hash{Symbol => Object}, nil] the option's declared row,
333
+ # or `nil` when the construct does not declare this option
334
+ # @return [QuerySpecification::Common::OffsetSpec,
335
+ # QuerySpecification::Common::CursorSpec,
336
+ # QuerySpecification::Common::NullSemantics,
337
+ # QuerySpecification::Common::AuthorizationSpec,
338
+ # QuerySpecification::Common::InspectionSpec, nil] the option built
339
+ # through the holder `OPTIONS` names for `name`, or `nil` when `declared`
340
+ # is `nil`
341
+ # @raise [KeyError] if `name` is not a key of `OPTIONS`
222
342
  def option(name, declared)
223
343
  return nil if declared.nil?
224
344
 
@@ -226,6 +346,15 @@ module Hecks
226
346
  holder.new(**Hash(declared).to_h { |key, value| [key, option_value(key, value, marked)] })
227
347
  end
228
348
 
349
+ # Reads one option member's declared value back to its native type.
350
+ #
351
+ # @param key [Symbol] the option member's name being read
352
+ # @param value [Object, nil] the declared value for that member
353
+ # @param marked [Array<Symbol>] the members of this option that ride
354
+ # Literal's spelling, from `OPTIONS`
355
+ # @return [Object, nil] `value` read back through `Literal.read` when
356
+ # `key` is marked, a Symbol when `key` is one of `SYMBOLIC`, the raw
357
+ # `value` otherwise, or `nil` when `value` is `nil`
229
358
  def option_value(key, value, marked)
230
359
  return nil if value.nil?
231
360
  return read(value) if marked.include?(key)