hecks 1.3.0 → 1.4.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 (355) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hecks/adapters/driven/claude_code.rb +7 -7
  3. data/lib/hecks/adapters/driven/d1.rb +187 -23
  4. data/lib/hecks/adapters/driven/folder.rb +10 -10
  5. data/lib/hecks/adapters/driven/google_authentication.rb +8 -8
  6. data/lib/hecks/adapters/driven/governance_authorization.rb +31 -13
  7. data/lib/hecks/adapters/driven/heki/journal.rb +5 -4
  8. data/lib/hecks/adapters/driven/heki/saga_store.rb +5 -5
  9. data/lib/hecks/adapters/driven/heki.rb +13 -7
  10. data/lib/hecks/adapters/driven/identity_registry.rb +2 -2
  11. data/lib/hecks/adapters/driven/in_memory_ordering.rb +3 -3
  12. data/lib/hecks/adapters/driven/lambda/client.rb +34 -9
  13. data/lib/hecks/adapters/driven/lambda.rb +39 -33
  14. data/lib/hecks/adapters/driven/local_storage.rb +17 -10
  15. data/lib/hecks/adapters/driven/memory.rb +205 -9
  16. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +1 -1
  17. data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
  18. data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
  19. data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
  20. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
  21. data/lib/hecks/adapters/driven/postgres.rb +175 -28
  22. data/lib/hecks/adapters/driven/prism.rb +4 -4
  23. data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
  24. data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
  25. data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
  26. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
  27. data/lib/hecks/adapters/driven/sqlite.rb +181 -21
  28. data/lib/hecks/adapters/driven.rb +4 -4
  29. data/lib/hecks/adapters/driving/github_webhook.rb +18 -18
  30. data/lib/hecks/behaviors/dsl.rb +2 -2
  31. data/lib/hecks/behaviors/expectations.rb +23 -23
  32. data/lib/hecks/behaviors/ir.rb +1 -1
  33. data/lib/hecks/behaviors/rspec.rb +1 -1
  34. data/lib/hecks/behaviors/runner.rb +2 -2
  35. data/lib/hecks/behaviors.rb +1 -1
  36. data/lib/hecks/bluebook/aggregate.rb +13 -13
  37. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +10 -10
  38. data/lib/hecks/bluebook/assembly/build.rb +1 -1
  39. data/lib/hecks/bluebook/assembly/contract.rb +39 -16
  40. data/lib/hecks/bluebook/assembly/contracts.rb +40 -38
  41. data/lib/hecks/bluebook/assembly/marks.rb +18 -18
  42. data/lib/hecks/bluebook/assembly/specializer.rb +19 -21
  43. data/lib/hecks/bluebook/assembly.rb +9 -9
  44. data/lib/hecks/bluebook/attribute.rb +9 -9
  45. data/lib/hecks/bluebook/behaviour/aggregate.rb +11 -11
  46. data/lib/hecks/bluebook/behaviour/attribute.rb +5 -5
  47. data/lib/hecks/bluebook/behaviour/chapter.rb +23 -5
  48. data/lib/hecks/bluebook/behaviour/command.rb +23 -23
  49. data/lib/hecks/bluebook/behaviour/domain_port.rb +7 -7
  50. data/lib/hecks/bluebook/behaviour/entity.rb +8 -8
  51. data/lib/hecks/bluebook/behaviour/hexagon.rb +4 -4
  52. data/lib/hecks/bluebook/behaviour/lifecycle.rb +5 -5
  53. data/lib/hecks/bluebook/behaviour/policy.rb +12 -12
  54. data/lib/hecks/bluebook/behaviour/process_manager.rb +7 -7
  55. data/lib/hecks/bluebook/behaviour/query.rb +1 -1
  56. data/lib/hecks/bluebook/behaviour/read_model.rb +8 -8
  57. data/lib/hecks/bluebook/behaviour/traits.rb +12 -12
  58. data/lib/hecks/bluebook/behaviour/value_object.rb +6 -6
  59. data/lib/hecks/bluebook/capabilities.rb +27 -0
  60. data/lib/hecks/bluebook/chapter.rb +28 -9
  61. data/lib/hecks/bluebook/command.rb +12 -12
  62. data/lib/hecks/bluebook/domain_port.rb +9 -9
  63. data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
  64. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
  65. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +96 -96
  66. data/lib/hecks/bluebook/dsl/attribute_collector.rb +41 -41
  67. data/lib/hecks/bluebook/dsl/binding_proxy.rb +22 -2
  68. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +111 -74
  69. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +48 -30
  70. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
  71. data/lib/hecks/bluebook/dsl/command_builder.rb +103 -103
  72. data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
  73. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
  74. data/lib/hecks/bluebook/dsl/entity_builder.rb +56 -56
  75. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
  76. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +89 -30
  77. data/lib/hecks/bluebook/dsl/identity_declaration.rb +17 -17
  78. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
  79. data/lib/hecks/bluebook/dsl/policy_builder.rb +30 -21
  80. data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
  81. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
  82. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +35 -35
  83. data/lib/hecks/bluebook/dsl/query_builder.rb +5 -5
  84. data/lib/hecks/bluebook/dsl/read_model_builder.rb +34 -34
  85. data/lib/hecks/bluebook/dsl/rule_reference.rb +41 -39
  86. data/lib/hecks/bluebook/dsl/translation_builder.rb +9 -9
  87. data/lib/hecks/bluebook/dsl/value_object_builder.rb +16 -16
  88. data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
  89. data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
  90. data/lib/hecks/bluebook/entity.rb +11 -11
  91. data/lib/hecks/bluebook/expression/ast_json.rb +20 -20
  92. data/lib/hecks/bluebook/expression/ast_reader.rb +3 -3
  93. data/lib/hecks/bluebook/expression/canonical_form.rb +9 -9
  94. data/lib/hecks/bluebook/expression/evaluator.rb +18 -18
  95. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +18 -18
  96. data/lib/hecks/bluebook/expression/resolver.rb +60 -62
  97. data/lib/hecks/bluebook/hexagon.rb +1 -1
  98. data/lib/hecks/bluebook/lifecycle.rb +1 -1
  99. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -1
  100. data/lib/hecks/bluebook/meta_validator/judge.rb +101 -87
  101. data/lib/hecks/bluebook/meta_validator/plan.rb +39 -39
  102. data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -2
  103. data/lib/hecks/bluebook/meta_validator/readings.rb +43 -43
  104. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +44 -37
  105. data/lib/hecks/bluebook/meta_validator/shapes.rb +25 -21
  106. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +145 -31
  107. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +6 -6
  108. data/lib/hecks/bluebook/meta_validator/world_judge.rb +5 -5
  109. data/lib/hecks/bluebook/meta_validator.rb +70 -70
  110. data/lib/hecks/bluebook/model_check.rb +170 -84
  111. data/lib/hecks/bluebook/pattern_subset.rb +9 -9
  112. data/lib/hecks/bluebook/policy.rb +15 -13
  113. data/lib/hecks/bluebook/process_manager.rb +14 -14
  114. data/lib/hecks/bluebook/project_register.rb +6 -6
  115. data/lib/hecks/bluebook/query.rb +4 -4
  116. data/lib/hecks/bluebook/read_model.rb +14 -14
  117. data/lib/hecks/bluebook/reference.rb +8 -8
  118. data/lib/hecks/bluebook/smoke_test.rb +19 -19
  119. data/lib/hecks/bluebook/synthesizer.rb +12 -12
  120. data/lib/hecks/bluebook/translation.rb +4 -4
  121. data/lib/hecks/bluebook/value_object.rb +6 -6
  122. data/lib/hecks/bluebook.rb +2 -2
  123. data/lib/hecks/codemod/legacy_dispatch_args.rb +299 -0
  124. data/lib/hecks/codemod/legacy_dispatch_recorder.rb +186 -0
  125. data/lib/hecks/codemod.rb +36 -35
  126. data/lib/hecks/construct.rb +6 -6
  127. data/lib/hecks/corpus.rb +317 -0
  128. data/lib/hecks/deprecation.rb +95 -0
  129. data/lib/hecks/doc/reference.rb +19 -19
  130. data/lib/hecks/embryonaut_bluebook.rb +11 -11
  131. data/lib/hecks/facade/cli_door.rb +69 -10
  132. data/lib/hecks/facade/cli_runner.rb +105 -24
  133. data/lib/hecks/facade/command_request.rb +23 -0
  134. data/lib/hecks/facade/handle.rb +79 -32
  135. data/lib/hecks/facade/json_door.rb +106 -25
  136. data/lib/hecks/facade/surface/aggregate_door.rb +42 -27
  137. data/lib/hecks/facade/surface/chapter.rb +26 -17
  138. data/lib/hecks/facade/surface.rb +16 -3
  139. data/lib/hecks/facade.rb +15 -4
  140. data/lib/hecks/forms/app.rb +46 -30
  141. data/lib/hecks/forms/command_form_renderer.rb +70 -9
  142. data/lib/hecks/forms/field_renderer.rb +142 -6
  143. data/lib/hecks/forms/field_shape.rb +180 -19
  144. data/lib/hecks/forms/html.rb +51 -7
  145. data/lib/hecks/forms/index_renderer.rb +14 -2
  146. data/lib/hecks/forms/params.rb +120 -23
  147. data/lib/hecks/forms/port_argument.rb +2 -2
  148. data/lib/hecks/forms/query_form_renderer.rb +2 -2
  149. data/lib/hecks/forms/record_renderer.rb +2 -2
  150. data/lib/hecks/forms/record_table.rb +1 -1
  151. data/lib/hecks/forms/value_object_shape.rb +3 -3
  152. data/lib/hecks/forms.rb +24 -4
  153. data/lib/hecks/fqn.rb +1 -1
  154. data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
  155. data/lib/hecks/framework.rb +48 -17
  156. data/lib/hecks/freezer.rb +11 -11
  157. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +90 -90
  158. data/lib/hecks/fuzzing/combination_miner.rb +119 -0
  159. data/lib/hecks/fuzzing/concurrent_dispatch.rb +140 -45
  160. data/lib/hecks/fuzzing/coverage_campaign.rb +118 -0
  161. data/lib/hecks/fuzzing/differential.rb +158 -0
  162. data/lib/hecks/fuzzing/domain_generator.rb +694 -0
  163. data/lib/hecks/fuzzing/era_boundary.rb +37 -18
  164. data/lib/hecks/fuzzing/form_census.rb +35 -20
  165. data/lib/hecks/fuzzing/generated_domain_check.rb +95 -0
  166. data/lib/hecks/fuzzing/invalid_value_generator.rb +6 -6
  167. data/lib/hecks/fuzzing/isolated_boot.rb +62 -62
  168. data/lib/hecks/fuzzing/nondeterministic.rb +67 -0
  169. data/lib/hecks/fuzzing/persistence_parity.rb +19 -21
  170. data/lib/hecks/fuzzing/properties/corrections.rb +9 -9
  171. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +154 -49
  172. data/lib/hecks/fuzzing/properties/guards.rb +42 -42
  173. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +32 -32
  174. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +22 -35
  175. data/lib/hecks/fuzzing/properties/outbox.rb +24 -24
  176. data/lib/hecks/fuzzing/properties/querying.rb +17 -17
  177. data/lib/hecks/fuzzing/properties.rb +64 -40
  178. data/lib/hecks/fuzzing/qa_settings.rb +152 -0
  179. data/lib/hecks/fuzzing/replay.rb +114 -114
  180. data/lib/hecks/fuzzing/rotation_priority.rb +10 -10
  181. data/lib/hecks/fuzzing/rust_gap_manifest.rb +113 -0
  182. data/lib/hecks/fuzzing/self_consistency.rb +141 -112
  183. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +35 -35
  184. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +18 -11
  185. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +13 -12
  186. data/lib/hecks/fuzzing/sequence_generator/picker.rb +21 -12
  187. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +58 -31
  188. data/lib/hecks/fuzzing/sequence_generator.rb +111 -23
  189. data/lib/hecks/fuzzing/shrinker.rb +197 -0
  190. data/lib/hecks/fuzzing/structural_skips.rb +23 -130
  191. data/lib/hecks/fuzzing/sweep_depth.rb +4 -4
  192. data/lib/hecks/fuzzing/target_capabilities.rb +49 -18
  193. data/lib/hecks/fuzzing/value_generator.rb +14 -14
  194. data/lib/hecks/fuzzing.rb +5 -0
  195. data/lib/hecks/grammar/evolve.rb +10 -10
  196. data/lib/hecks/grammar.rb +7 -7
  197. data/lib/hecks/ir.rb +13 -13
  198. data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
  199. data/lib/hecks/language/bluebook/policy.bluebook +11 -1
  200. data/lib/hecks/language/bluebook/vocabulary.bluebook +348 -13
  201. data/lib/hecks/language/oidc.json +5 -0
  202. data/lib/hecks/literal.rb +9 -9
  203. data/lib/hecks/naming.rb +25 -25
  204. data/lib/hecks/ports/access_control.rb +58 -2
  205. data/lib/hecks/ports/agent/answers.rb +83 -6
  206. data/lib/hecks/ports/agent.rb +119 -35
  207. data/lib/hecks/ports/authentication.rb +44 -4
  208. data/lib/hecks/ports/authorization.rb +53 -11
  209. data/lib/hecks/ports/clock.rb +42 -23
  210. data/lib/hecks/ports/extraction.rb +16 -0
  211. data/lib/hecks/ports/identity_assignment.rb +24 -2
  212. data/lib/hecks/ports/identity_generation.rb +17 -3
  213. data/lib/hecks/ports/identity_resolution.rb +18 -1
  214. data/lib/hecks/ports/loading.rb +4 -0
  215. data/lib/hecks/ports/persistence/append_only.rb +172 -8
  216. data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
  217. data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
  218. data/lib/hecks/ports/persistence/execution.rb +4 -0
  219. data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
  220. data/lib/hecks/ports/persistence/plugin.rb +42 -4
  221. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
  222. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
  223. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
  224. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
  225. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
  226. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
  227. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
  228. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
  229. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
  230. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +118 -66
  231. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
  232. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +28 -5
  233. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +33 -9
  237. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
  238. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +57 -4
  239. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
  240. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +314 -90
  241. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
  242. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
  243. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
  244. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +42 -8
  245. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
  246. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
  247. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
  248. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -19
  249. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +5 -5
  250. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +1 -1
  251. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
  252. data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
  253. data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
  254. data/lib/hecks/ports/persistence/repository_factory.rb +23 -3
  255. data/lib/hecks/ports/persistence/state_codec.rb +319 -0
  256. data/lib/hecks/ports/persistence.rb +36 -1
  257. data/lib/hecks/ports/projection.rb +61 -7
  258. data/lib/hecks/ports/query/in_memory.rb +3 -3
  259. data/lib/hecks/ports/query/ordering.rb +6 -6
  260. data/lib/hecks/ports/query.rb +35 -0
  261. data/lib/hecks/projections/bootstrap_table.rb +112 -0
  262. data/lib/hecks/projections/diagrams.rb +75 -75
  263. data/lib/hecks/projections/glossary/html.rb +3 -3
  264. data/lib/hecks/projections/glossary/markdown.rb +3 -3
  265. data/lib/hecks/projections/glossary/mermaid.rb +1 -1
  266. data/lib/hecks/projections/glossary/sections.rb +1 -1
  267. data/lib/hecks/projections/glossary/sentences.rb +5 -5
  268. data/lib/hecks/projections/glossary.rb +15 -15
  269. data/lib/hecks/projections/ir.rb +1 -1
  270. data/lib/hecks/projections/model/deviations.rb +18 -17
  271. data/lib/hecks/projections/model.rb +25 -21
  272. data/lib/hecks/projections/oidc.rb +7 -7
  273. data/lib/hecks/projections/parser_table.rb +5 -5
  274. data/lib/hecks/projections/reference.rb +3 -3
  275. data/lib/hecks/projections/rust_vocabulary.rb +443 -0
  276. data/lib/hecks/projections/shape.rb +2 -2
  277. data/lib/hecks/projections/statements.rb +11 -11
  278. data/lib/hecks/projections/vocabulary.rb +9 -9
  279. data/lib/hecks/projections.rb +5 -3
  280. data/lib/hecks/projector/cli_projector.rb +29 -29
  281. data/lib/hecks/projector/docs_projector.rb +13 -13
  282. data/lib/hecks/projector/exporter.rb +42 -21
  283. data/lib/hecks/projector/ir_projector.rb +1 -1
  284. data/lib/hecks/projector/narrate_projector.rb +11 -11
  285. data/lib/hecks/projector/target.rb +13 -13
  286. data/lib/hecks/projector.rb +15 -15
  287. data/lib/hecks/query_ir.rb +47 -47
  288. data/lib/hecks/query_specification/common/comparators.rb +19 -3
  289. data/lib/hecks/query_specification/common/comparison.rb +109 -27
  290. data/lib/hecks/query_specification/common/dsl.rb +65 -9
  291. data/lib/hecks/query_specification/common/null_policy.rb +57 -13
  292. data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
  293. data/lib/hecks/query_specification/common/options.rb +25 -0
  294. data/lib/hecks/query_specification/field_path.rb +69 -15
  295. data/lib/hecks/query_specification/hop_path.rb +57 -20
  296. data/lib/hecks/query_specification/read_model/specification.rb +4 -0
  297. data/lib/hecks/rendering.rb +3 -3
  298. data/lib/hecks/router/namespace_installer.rb +3 -3
  299. data/lib/hecks/router.rb +1 -1
  300. data/lib/hecks/runtime/aggregate_lock.rb +11 -11
  301. data/lib/hecks/runtime/boot_gates.rb +3 -3
  302. data/lib/hecks/runtime/caller.rb +8 -8
  303. data/lib/hecks/runtime/capability_graph.rb +2 -2
  304. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +22 -25
  305. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +42 -63
  306. data/lib/hecks/runtime/command_interpreter.rb +159 -102
  307. data/lib/hecks/runtime/command_rules/admissibility.rb +69 -69
  308. data/lib/hecks/runtime/command_rules/arithmetic.rb +137 -91
  309. data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
  310. data/lib/hecks/runtime/command_rules/emission.rb +18 -1
  311. data/lib/hecks/runtime/command_rules/references.rb +35 -35
  312. data/lib/hecks/runtime/command_rules.rb +3 -0
  313. data/lib/hecks/runtime/dependency_planning.rb +11 -11
  314. data/lib/hecks/runtime/dispatcher.rb +283 -90
  315. data/lib/hecks/runtime/entity_element.rb +151 -67
  316. data/lib/hecks/runtime/entity_interpreter.rb +108 -82
  317. data/lib/hecks/runtime/errors.rb +19 -19
  318. data/lib/hecks/runtime/event.rb +6 -6
  319. data/lib/hecks/runtime/identity.rb +22 -22
  320. data/lib/hecks/runtime/instance.rb +39 -14
  321. data/lib/hecks/runtime/interpreting.rb +12 -12
  322. data/lib/hecks/runtime/invocation.rb +276 -0
  323. data/lib/hecks/runtime/loader.rb +14 -14
  324. data/lib/hecks/runtime/outbox.rb +23 -23
  325. data/lib/hecks/runtime/policy_interpreter.rb +54 -54
  326. data/lib/hecks/runtime/port_operation_interpreter.rb +22 -19
  327. data/lib/hecks/runtime/query_interpreter.rb +55 -64
  328. data/lib/hecks/runtime/reaction_invocation.rb +20 -20
  329. data/lib/hecks/runtime/read_model_interpreter.rb +40 -40
  330. data/lib/hecks/runtime/rebuild_sweep.rb +4 -4
  331. data/lib/hecks/runtime/reference_hop.rb +6 -6
  332. data/lib/hecks/runtime/refusal_wording.rb +92 -115
  333. data/lib/hecks/runtime/registry/saga_persistence.rb +21 -21
  334. data/lib/hecks/runtime/registry/verification.rb +36 -26
  335. data/lib/hecks/runtime/registry.rb +45 -28
  336. data/lib/hecks/runtime/remote_dispatcher.rb +38 -23
  337. data/lib/hecks/runtime/routing.rb +10 -153
  338. data/lib/hecks/runtime/saga_interpreter/correlation.rb +17 -17
  339. data/lib/hecks/runtime/saga_interpreter.rb +64 -64
  340. data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
  341. data/lib/hecks/runtime/tenant_check.rb +9 -9
  342. data/lib/hecks/runtime/tenant_scope.rb +5 -5
  343. data/lib/hecks/runtime/value/admission.rb +75 -30
  344. data/lib/hecks/runtime/value/coercion.rb +318 -127
  345. data/lib/hecks/runtime/value/entity_list_coercion.rb +34 -34
  346. data/lib/hecks/runtime/value.rb +21 -21
  347. data/lib/hecks/runtime.rb +7 -7
  348. data/lib/hecks/storehouse.rb +64 -64
  349. data/lib/hecks/version.rb +3 -3
  350. data/lib/hecks/vocabulary.rb +205 -4
  351. data/lib/hecks.rb +13 -11
  352. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +11 -11
  353. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +14 -12
  354. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +11 -11
  355. metadata +22 -2
@@ -3,11 +3,11 @@ module Hecks
3
3
  module Bluebook
4
4
  module DSL
5
5
  # Parses a `.hecksagon` file's top-level DSL block into a `Hecksagon`
6
- # — a domain's own WIRING: which adapter binds to which verb
6
+ # — a domain's own wiring: which adapter binds to which verb
7
7
  # (`persisted_by`, `projected_by`, ...), which framework/vendored
8
8
  # bluebooks it attaches, which external events it subscribes to, and
9
9
  # its bare chapter-root port. Kept separate from the bluebook itself
10
- # (the domain's own declared MODEL) because wiring is an operational
10
+ # (the domain's own declared model) because wiring is an operational
11
11
  # decision, not a fact the domain states about itself.
12
12
  class HecksagonBuilder
13
13
  GRAMMAR_CONTEXT = "Hecksagon".freeze
@@ -20,6 +20,7 @@ module Hecks
20
20
 
21
21
  attr_reader :binds, :subscriptions, :framework_members, :vendored_bluebooks
22
22
 
23
+ # @param domain [String] name of the domain whose wiring this hecksagon declares
23
24
  def initialize(domain)
24
25
  @domain = domain
25
26
  @binds = []
@@ -28,44 +29,65 @@ module Hecks
28
29
  @vendored_bluebooks = []
29
30
  end
30
31
 
31
- # An event this hecksagon takes from OUTSIDE the domain's own
32
- # bluebook.
32
+ # Subscribes this hecksagon to an event it takes from outside the domain's own bluebook.
33
+ #
34
+ # @param event [String, Symbol] the external event's name
35
+ # @return [Array<String>] every subscription declared so far, this one last
33
36
  def subscribe(event) = @subscriptions << event.to_s
34
37
 
38
+ # Attaches a framework member to this domain and loads it into the current registry.
39
+ #
35
40
  # A framework/ member this domain wants attached —
36
41
  # Governance, Identity, whatever else lands beside them.
37
- # Attaching one is a WIRING decision, the same kind `persisted_by`/
42
+ # Attaching one is a wiring decision, the same kind `persisted_by`/
38
43
  # `projected_by` already are, so it lives here rather than as a
39
- # fact stated in the domain's own bluebook. Recorded onto THIS
44
+ # fact stated in the domain's own bluebook. Recorded onto this
40
45
  # hecksagon, the same way `subscribe` records onto its own
41
46
  # `subscriptions` — and loads the member's bluebook then its own
42
47
  # hecksagon into whatever registry this one is loading into, see
43
48
  # `Framework.load!`.
49
+ #
50
+ # @param name [String, Symbol] the member's name, such as `"Governance"`
51
+ # @return [Boolean, nil] true when this call loaded the member's bluebook, nil when the
52
+ # current registry already held it
53
+ # @raise [Runtime::WiringError] if no framework member has that name
44
54
  def uses_framework(name)
45
55
  @framework_members << name.to_s
46
56
  Hecks::Framework.load!(name)
47
57
  end
48
58
 
49
- # A VENDORED, EXTERNAL bluebook this domain wants attached same
59
+ # Attaches a vendored embryonaut bluebook to this domain and loads its files into the
60
+ # current registry.
61
+ #
62
+ # A vendored, external bluebook this domain wants attached — same
50
63
  # wiring-decision shape `uses_framework` already is, one level
51
64
  # further out: not a member shipped inside hecks's own lib/,
52
- # but a separate package (embryonaut_bluebooks) vendored into THIS
65
+ # but a separate package (embryonaut_bluebooks) vendored into this
53
66
  # project's own checkout. See EmbryonautBluebook's own header for
54
- # the full reasoning on why its ROOT can't be a fixed constant the
67
+ # the full reasoning on why its root can't be a fixed constant the
55
68
  # way Framework::ROOT is, and for the recovery provenance.
56
69
  #
57
- # RECORDED ONTO @vendored_bluebooks, same shape `uses_framework`
58
- # already gives @framework_members — a SEPARATE list on purpose:
70
+ # Recorded onto @vendored_bluebooks, same shape `uses_framework`
71
+ # already gives @framework_members — a separate list on purpose:
59
72
  # `framework_members` is load-bearing for `refuse_ungoverned_roles!`
60
- # (below) and for Governance's own attachment check; conflating
61
- # the two would make a vendored bluebook attachment satisfy a
62
- # Governance check it has nothing to do with.
73
+ # (`Registry::Verification`) and for Governance's own attachment
74
+ # check; conflating the two would make a vendored bluebook attachment
75
+ # satisfy a Governance check it has nothing to do with.
76
+ #
77
+ # @param name [String, Symbol] the vendored package's directory name under
78
+ # `vendor/embryonaut_bluebooks/`
79
+ # @return [Array<String>, nil] paths of the `.bluebook` files this call loaded, or nil
80
+ # when the current registry already held the bluebook
81
+ # @raise [Runtime::WiringError] if the current registry has no root to vendor from, or
82
+ # no vendored bluebook of that name exists under it
63
83
  def uses_embryonaut_bluebook(name)
64
84
  @vendored_bluebooks << name.to_s
65
85
  Hecks::EmbryonautBluebook.load!(name)
66
86
  end
67
87
 
68
- # THE PRIMARY PORT, BARE AT THE ROOT belongs to the CHAPTER as a
88
+ # Declares a port at the hecksagon's root and attaches it to the registered bluebook.
89
+ #
90
+ # The primary port, bare at the root — belongs to the chapter as a
69
91
  # whole, not one aggregate. `BindingProxy#port` is the aggregate-
70
92
  # scoped sibling (`Payments::Payment.port("Gateway") do ... end`);
71
93
  # this is what's left when a port isn't about any one record. The
@@ -73,14 +95,24 @@ module Hecks
73
95
  # loads after its bluebook, and this attaches to that real, final
74
96
  # object directly rather than building a second copy MetaValidator
75
97
  # would have to know how to reconstruct.
76
- # RENAMED FROM `port` — item #13's full metaprogrammed dispatch
77
- # (slice 5). Not bootstrap-reachable (checked directly no
78
- # core/attached chapter declares a Hecksagon of its own). Reached
79
- # through `WordGate#method_missing`'s new `word_gate_dispatch`,
80
- # called explicitly below since `HecksagonBuilder`'s own
81
- # class-level `method_missing` (the open-verb catch-all beneath
82
- # this) always wins over the module's see `word_gate.rb`'s own
83
- # header for the full mechanism.
98
+ #
99
+ # Answers the `port` word through the table's `calls:` column
100
+ # item #13's full metaprogrammed dispatch (slice 5). Not
101
+ # bootstrap-reachable (checked directly — no core/attached chapter
102
+ # declares a Hecksagon of its own). Reached through `WordGate`'s
103
+ # `word_gate_dispatch`, called explicitly below since
104
+ # `HecksagonBuilder`'s own class-level `method_missing` (the
105
+ # open-verb catch-all beneath this) always wins over the module's
106
+ # see `word_gate.rb`'s own header for the full mechanism.
107
+ #
108
+ # @param name [String] the port's name
109
+ # @yield the port body, evaluated against a `DomainPortBuilder`: either `verb`/`signal`
110
+ # or `operation`/`tells`/`asks` blocks
111
+ # @return [Bluebook::Port, Bluebook::DomainPort] the built port: a verb-shaped `Port`
112
+ # registered on the current registry, or a `DomainPort` attached to the bluebook
113
+ # @raise [Bluebook::DSL::Malformed] if the current registry holds no bluebook for this
114
+ # domain, or the body declares both a verb and operations, neither, or an operation
115
+ # the port grammar refuses
84
116
  def port_impl(name, &block)
85
117
  bluebook_ir = Hecks.current_registry.bluebook(@domain) or
86
118
  raise Malformed, "#{@domain} declares no such bluebook — a port needs one to belong to"
@@ -103,26 +135,33 @@ module Hecks
103
135
  bluebook_ir.add_port(built)
104
136
  end
105
137
 
106
- # NO ungoverned-role check here anymore see
107
- # Registry::Verification#refuse_ungoverned_roles!. Moved out of
108
- # per-block `build`, recovered alongside `environment:`
138
+ # Assembles the collected binds, subscriptions and attachments into a `Hecksagon`.
139
+ #
140
+ # No ungoverned-role check here see
141
+ # Registry::Verification#refuse_ungoverned_roles!. It lives outside
142
+ # per-block `build`, alongside `environment:`
109
143
  # (Runtime::Loader.boot's comment has the provenance): a domain
110
144
  # split across multiple hecksagon blocks (base + an
111
145
  # `environments/<name>.hecksagon` overlay) would have every
112
146
  # block but the one declaring `uses_framework "Governance"`
113
- # refused HERE, even though `Registry#add_hecksagon` merges them
147
+ # refused here, even though `Registry#add_hecksagon` merges them
114
148
  # into one Hecksagon before anything ever dispatches against it.
115
- # Checking the MERGED result once, at verify! time — after every
149
+ # Checking the merged result once, at verify! time — after every
116
150
  # file for this domain has loaded — is both more permissive (no
117
151
  # need to repeat `uses_framework` in every file) and strictly
118
152
  # more correct (a check against an incomplete, not-yet-merged
119
153
  # hecksagon can never see the real final shape).
154
+ #
155
+ # @return [Bluebook::Hecksagon] this block's wiring, which `Registry#add_hecksagon` merges
156
+ # with any other block declared for the same domain
120
157
  def build
121
158
  Hecksagon.new(domain: @domain, binds: @binds, subscriptions: @subscriptions,
122
159
  framework_members: @framework_members, vendored_bluebooks: @vendored_bluebooks)
123
160
  end
124
161
 
125
- # DOMAIN-LEVEL DEFAULT BINDS `persisted_by "Heki"` bare, at the top
162
+ # Records any verb the grammar does not own as a domain-wide bind to the named adapter.
163
+ #
164
+ # **Domain-level default binds** — `persisted_by "Heki"` bare, at the top
126
165
  # of a hecksagon block, applies to every aggregate in this domain
127
166
  # that doesn't declare its own override. Mirrors `BindingProxy`'s own
128
167
  # `method_missing` one level down (`aggregate:` filled in there,
@@ -130,12 +169,22 @@ module Hecks
130
169
  # `persisted_by`/`projected_by` specifically, so any future verb
131
170
  # gets a domain-level default for free too. See `Hecksagon#bind_for`
132
171
  # for the fallback lookup this feeds.
172
+ #
173
+ # @param verb [Symbol] the word called, such as `:persisted_by`
174
+ # @param args [Array<Object>] positional arguments; the first names the adapter
175
+ # @param kwargs [Hash{Symbol => Object}] keyword arguments; `:role` is kept on the bind
176
+ # @yield an optional block, called once after the bind is recorded
177
+ # @return [Object] this builder after recording a bind, or the grammar word's own result
178
+ # when `verb` is one the `Hecksagon` grammar admits
179
+ # @raise [NoMethodError] if `verb` is no grammar word and names no adapter
180
+ # @raise [Bluebook::DSL::Malformed] if the grammar admits `verb` only in another context,
181
+ # or admits it here and its implementation refuses the declaration
133
182
  def method_missing(verb, *args, **kwargs, &block)
134
183
  # A closed-set grammar word (`port`, today) gets first refusal
135
184
  # — item #13's full metaprogrammed dispatch (slice 5); see
136
185
  # `WordGate#word_gate_dispatch`'s own header for why this class
137
186
  # needs to call it explicitly rather than including it the
138
- # ordinary way. Only once THAT says "not admitted" does the
187
+ # ordinary way. Only once that says "not admitted" does the
139
188
  # genuinely open-ended `persisted_by "Heki"`-style bind
140
189
  # vocabulary below get a turn.
141
190
  result = word_gate_dispatch(verb, args, kwargs, block)
@@ -150,6 +199,16 @@ module Hecks
150
199
 
151
200
  def respond_to_missing?(_name, _include_private = false) = true
152
201
 
202
+ # Evaluates a `Hecks.hecksagon` block with bare `Domain::Aggregate` constants resolving to
203
+ # bind-collecting proxies, and returns the wiring it declared.
204
+ #
205
+ # @param domain [String] name of the domain being wired
206
+ # @yield the hecksagon body, evaluated with the builder as `self`; may be omitted
207
+ # @return [Bluebook::Hecksagon] the declared wiring
208
+ # @raise [Bluebook::DSL::Malformed] if a `port` names no registered bluebook or aggregate,
209
+ # or declares a shape the port grammar refuses
210
+ # @raise [Runtime::WiringError] if `uses_framework` or `uses_embryonaut_bluebook` names
211
+ # something that cannot be found
153
212
  def self.build(domain, &block)
154
213
  builder = new(domain)
155
214
  resolver = ->(name) { BindingProxy.namespace(name, builder.binds) }
@@ -1,27 +1,27 @@
1
1
  module Hecks
2
2
  module Bluebook
3
3
  module DSL
4
- # `identified_by` — SHARED by AggregateBuilder and EntityBuilder
4
+ # `identified_by` — shared by AggregateBuilder and EntityBuilder
5
5
  # only (S9, ADR 0025 — "EntityBuilder's duplicate identified_by and
6
6
  # resolve_pending_identity! go"): a piece's own identity cannot
7
7
  # spell differently from its aggregate's, and until this slice it
8
8
  # was hand-duplicated onto both rather than shared.
9
9
  #
10
- # A SEPARATE MODULE from AttributeCollector on purpose — every
10
+ # A separate module from AttributeCollector on purpose — every
11
11
  # `attribute()`-taking builder (Command, Query, PortOperation,
12
- # ValueObject, ...) `include`s that one too, and none of THEM
12
+ # ValueObject, ...) `include`s that one too, and none of them
13
13
  # declares an identity of its own ; folding `identified_by` in
14
14
  # there made it answer for six builders that never earned the
15
15
  # word (`syntax_conformance_spec.rb` catches exactly this — a
16
16
  # builder answering a method the grammar never grants it).
17
17
  #
18
- # Requires its includer to ALSO `include AttributeCollector`
18
+ # Requires its includer to also `include AttributeCollector`
19
19
  # (for `attributes`/`resolve_identity_field!`/`resolve_identity_
20
- # type!`, still declared there since every includer of THAT
20
+ # type!`, still declared there since every includer of that
21
21
  # module needs them) and to supply `identity_pool` (private) —
22
22
  # the value-object list a bare field's own type resolves
23
23
  # against: AggregateBuilder's own `@value_objects + closed_sets`,
24
- # or EntityBuilder's OWNER's, since a piece mints none of its own.
24
+ # or EntityBuilder's owner's, since a piece mints none of its own.
25
25
  module IdentityDeclaration
26
26
  # There are three live forms, deliberately distinguishable at the
27
27
  # declaration site:
@@ -33,9 +33,9 @@ module Hecks
33
33
  # One symbol is retired: it cannot say whether the author means a
34
34
  # value concept or a field-shaped database key. Frozen source still
35
35
  # reaches the old interpretation through `legacy_identified_by`.
36
- # RENAMED FROM `identified_by` — item #13's full metaprogrammed
36
+ # Renamed from `identified_by` — item #13's full metaprogrammed
37
37
  # dispatch (slice 4c), same shared-mixin shape `attribute_impl`
38
- # already proved in slice 3: ONE renamed method, both Aggregate
38
+ # already proved in slice 3: one renamed method, both Aggregate
39
39
  # and Entity Keyword rows name it in `calls:`. Bootstrap-
40
40
  # reachable (every self-hosted aggregate/entity declares an
41
41
  # identity), so in BOOTSTRAP_CALLS_FALLBACK for both contexts.
@@ -67,15 +67,15 @@ module Hecks
67
67
  &definition
68
68
  )
69
69
  rescue NameError => e
70
- # A REMOVED SPELLING MUST REFUSE LOUDLY, not degrade into a raw
70
+ # A removed spelling must refuse loudly, not degrade into a raw
71
71
  # Ruby error — the one contract `EraGuard.shadow_parse` leans
72
72
  # on to know a normal parse genuinely could not read this text
73
73
  # (only `Malformed` triggers its shadow-mode retry, era_guard.rb's
74
- # own comment). The OLD `identified_by { name.value }` — a block
75
- # whose text was NEVER CALLED, only extracted (legacy_
74
+ # own comment). The old `identified_by { name.value }` — a block
75
+ # whose text was never called, only extracted (legacy_
76
76
  # identified_by, below) — is exactly this shape: read under the
77
- # CURRENT grammar it is instead instance_eval'd as a value-object
78
- # DEFINITION, and a bare identifier like `name` inside it resolves
77
+ # current grammar it is instead instance_eval'd as a value-object
78
+ # definition, and a bare identifier like `name` inside it resolves
79
79
  # to nothing WordGate#method_missing recognizes, so Ruby itself
80
80
  # raises NameError. Left uncaught, that NameError skipped
81
81
  # shadow_parse's rescue entirely and reached callers as a raw
@@ -124,9 +124,9 @@ module Hecks
124
124
 
125
125
  private
126
126
 
127
- # RESOLVES whichever of the three `identified_by` shapes is
127
+ # Resolves whichever of the three `identified_by` shapes is
128
128
  # pending, against `identity_pool` — the includer's own private
129
- # hook. Called at BUILD time, not at `identified_by`'s own call
129
+ # hook. Called at build time, not at `identified_by`'s own call
130
130
  # time — see `AttributeCollector#resolve_identity_field!`'s own
131
131
  # comment on why.
132
132
  def resolve_pending_identity!
@@ -161,8 +161,8 @@ module Hecks
161
161
  raise Malformed, "#{@name} declares identified_by more than once"
162
162
  end
163
163
 
164
- # LEGACY — the two removed spellings (a value object + as:, and the
165
- # multi-line block), kept alive ONLY for `EraGuard.shadow_parse`
164
+ # Legacy — the two removed spellings (a value object + as:, and the
165
+ # multi-line block), kept alive only for `EraGuard.shadow_parse`
166
166
  # (S0a, ADR 0025) to still make sense of frozen era text that used
167
167
  # them; unreachable outside `MetaValidator.shadow_parsing?`.
168
168
  def legacy_identified_by(*targets, as:, &path)
@@ -12,16 +12,26 @@ module Hecks
12
12
 
13
13
  include WordGate
14
14
 
15
+ # @param field [Symbol, String] the attribute the state machine lives on, such as `:status`
16
+ # @param default [String, Symbol] the state a new record starts in
15
17
  def initialize(field, default:)
16
18
  @field = field
17
19
  @default = default
18
20
  @transitions = []
19
21
  end
20
22
 
21
- # RENAMED FROM `transition` item #13's full metaprogrammed
23
+ # Records one transition row per command in `mapping`, all sharing its `from:` guard.
24
+ #
25
+ # Answers the `transition` word, which the grammar table routes here
26
+ # through its `calls:` column — item #13's full metaprogrammed
22
27
  # dispatch (slice 4c). Bootstrap-reachable (syntax.bluebook's
23
28
  # own Keyword/Argument entities describe their `status`
24
- # lifecycle with it), so in BOOTSTRAP_CALLS_FALLBACK.
29
+ # lifecycle with it), so in `BOOTSTRAP_CALLS_FALLBACK`.
30
+ #
31
+ # @param mapping [Hash{String, Symbol => String, Symbol, Array<String>}] command name to
32
+ # target state, as in `"Purchase" => "sold"`; the optional `:from` key holds the
33
+ # state, or Array of states, the transition applies from, and nil or absent means any
34
+ # @return [Hash] the command-to-target pairs just recorded, `:from` removed
25
35
  def transition_impl(mapping)
26
36
  mapping = mapping.dup
27
37
  from = mapping.delete(:from)
@@ -34,11 +44,24 @@ module Hecks
34
44
  end
35
45
  end
36
46
 
47
+ # Assembles the declared transitions into a `Lifecycle`, refusing an ambiguous table.
48
+ #
49
+ # @return [Bluebook::Lifecycle] the state machine: its field, default and transitions
50
+ # @raise [Bluebook::DSL::Malformed] if two transitions for one command could both apply
51
+ # from the same state (C5.3); skipped while shadow-parsing frozen era text
37
52
  def build
38
53
  refuse_ambiguity!
39
54
  Lifecycle.new(field: @field, default: @default, transitions: @transitions)
40
55
  end
41
56
 
57
+ # Evaluates a `lifecycle` block against a fresh builder and returns what it built.
58
+ #
59
+ # @param field [Symbol, String] the attribute the state machine lives on
60
+ # @param default [String, Symbol] the state a new record starts in
61
+ # @yield the lifecycle body of `transition` rows, evaluated with the builder as `self`
62
+ # @return [Bluebook::Lifecycle] the built state machine
63
+ # @raise [Bluebook::DSL::Malformed] if two transitions for one command overlap, or the
64
+ # block uses a word the `Lifecycle` grammar does not admit
42
65
  def self.build(field, default:, &block)
43
66
  builder = new(field, default: default)
44
67
  builder.instance_eval(&block) if block
@@ -51,9 +74,9 @@ module Hecks
51
74
  # one command whose `from:` sets overlap (or where either has no
52
75
  # `from:` at all) were silently first-wins; refused where the state
53
76
  # machine can be read whole. Two transitions for one command from
54
- # DISJOINT states are the legitimate shape (`match_transition`
77
+ # disjoint states are the legitimate shape (`match_transition`
55
78
  # picks by the current state) and stay. A `from:` naming a state
56
- # nothing declares is NOT refused here: it is a reachability
79
+ # nothing declares is not refused here: it is a reachability
57
80
  # finding `bin/model_check` already reports (unreachable state,
58
81
  # dead transition), and a bluebook may declare it on purpose.
59
82
  def refuse_ambiguity!
@@ -17,10 +17,10 @@ module Hecks
17
17
  @name = name
18
18
  end
19
19
 
20
- # `on Account::AccountFrozen` — A BARE CONSTANT ACCEPTED (ADR
20
+ # `on Account::AccountFrozen` — a bare constant accepted (ADR
21
21
  # 0025, S6 — "events first-class"), resolved through `ConstShim`
22
22
  # the same way `trigger`/`dispatch` already resolve a command
23
- # reference (`Naming.event_ref`, that method's own header). NOT
23
+ # reference (`Naming.event_ref`, that method's own header). Not
24
24
  # a required spelling yet, unlike `trigger`'s own quoted-text
25
25
  # refusal — see `policy.bluebook`'s own KeywordSeed comment for
26
26
  # why: event names aren't 100% migrated across the live corpus
@@ -28,7 +28,7 @@ module Hecks
28
28
  # AccountFrozen"` (quoted) and `on Account::AccountFrozen`
29
29
  # (bare) stay admitted until a full migration lands.
30
30
  #
31
- # RENAMED FROM the generic single-fill coercion — item #13's
31
+ # Renamed from the generic single-fill coercion — item #13's
32
32
  # full metaprogrammed dispatch, slice 1 (whole-project
33
33
  # table-unification survey), now overridden here the same way
34
34
  # `trigger_impl` overrides its own generic default.
@@ -36,7 +36,7 @@ module Hecks
36
36
  @on_event = Naming.event_ref(event_ref)
37
37
  end
38
38
 
39
- # `with:` — WHAT THE TRIGGER IS GIVEN, when the event's own shape
39
+ # `with:` — what the trigger is given, when the event's own shape
40
40
  # is not it. Omitted, the whole event payload forwards verbatim,
41
41
  # which is what every policy did before this existed.
42
42
  #
@@ -46,10 +46,10 @@ module Hecks
46
46
  # supplies itself. The reason it exists is the reason a saga's
47
47
  # does — a reaction crosses an aggregate boundary, and the event
48
48
  # on one side is under no obligation to be shaped like the
49
- # command on the other. Without it the target has to DECLARE
49
+ # command on the other. Without it the target has to declare
50
50
  # every field the event happens to carry, whether it reads them
51
51
  # or not.
52
- # THE COMMAND ITSELF, NOT ITS NAME (ADR 0025, "events and
52
+ # The command itself, not its name (ADR 0025, "events and
53
53
  # reactions" — command references become first-class): `trigger
54
54
  # Account::Debit`, a bare constant `ConstShim` resolves the same
55
55
  # way `reference_to Account` always has, not a quoted verb string.
@@ -61,10 +61,10 @@ module Hecks
61
61
  # `Account::Debit` and `Banking::Account::Debit` mean the same
62
62
  # thing here).
63
63
  #
64
- # LEGACY UNDER SHADOW-PARSING (S0a's own bridge) — frozen era
64
+ # Legacy under shadow-parsing (S0a's own bridge) — frozen era
65
65
  # text still writes the quoted form.
66
66
  #
67
- # RENAMED FROM `trigger` — item #13's full metaprogrammed
67
+ # Renamed from `trigger` — item #13's full metaprogrammed
68
68
  # dispatch (slice 4), same reasoning as has_many_impl above: not
69
69
  # bootstrap-reachable, reached through calls: with no fallback
70
70
  # needed.
@@ -80,11 +80,19 @@ module Hecks
80
80
  @projection_declared = !with.nil?
81
81
  end
82
82
 
83
- # `across` item #13's full metaprogrammed dispatch, slice 1:
84
- # same shape as `on`, above.
83
+ # `across "Notifications"` names the domain a trigger reaches into.
84
+ # `expect_undelivered: true` declares that this domain expects that
85
+ # target never to be reached (no such domain, on purpose), which
86
+ # `ModelCheck` holds it to in both directions. Reached through
87
+ # `calls:` since it gained the named flag — the generic single-fill
88
+ # coercion takes no keyword arguments.
89
+ def across_impl(domain, expect_undelivered: false)
90
+ @target_domain = domain.to_s
91
+ @expect_undelivered = expect_undelivered == true
92
+ end
85
93
 
86
- # THE GUARD — same extraction CommandBuilder#given/#ensures already
87
- # use (Ports::Extraction reads the block's SOURCE ; the block itself
94
+ # **The guard** — same extraction CommandBuilder#given/#ensures already
95
+ # use (Ports::Extraction reads the block's source ; the block itself
88
96
  # is never called, here or at runtime — Runtime::PolicyInterpreter
89
97
  # evaluates the extracted text through the same
90
98
  # Bluebook::Expression::Evaluator a command's own given/ensures run
@@ -94,7 +102,7 @@ module Hecks
94
102
  # this policy does not apply to this event, exactly like an
95
103
  # `event_qualifier` miss, which carries no message either.
96
104
  #
97
- # Evaluated against the triggering EVENT's OWN PAYLOAD, not a
105
+ # Evaluated against the triggering event's own payload, not a
98
106
  # stored record — a policy reacts to what just happened, and has no
99
107
  # aggregate instance of its own to read state from.
100
108
  def where(&predicate)
@@ -112,7 +120,7 @@ module Hecks
112
120
  @where = canonical
113
121
  end
114
122
 
115
- # THE FAN-OUT SOURCE — a query verb, "Aggregate.query_name" or
123
+ # **The fan-out source** — a query verb, "Aggregate.query_name" or
116
124
  # "Domain::Aggregate.query_name", the same qualified-or-not shape a
117
125
  # saga's own `dispatch` command name already takes
118
126
  # (SagaInterpreter#qualified). Runtime::PolicyInterpreter runs the
@@ -123,13 +131,14 @@ module Hecks
123
131
 
124
132
  def build
125
133
  Policy.new(
126
- name: @name,
127
- on_event: @on_event,
128
- trigger_command: @trigger_command,
129
- target_domain: @target_domain,
130
- where: @where,
131
- for_each: @for_each,
132
- with_spec: @with_spec || []
134
+ name: @name,
135
+ on_event: @on_event,
136
+ trigger_command: @trigger_command,
137
+ target_domain: @target_domain,
138
+ expect_undelivered: @expect_undelivered || false,
139
+ where: @where,
140
+ for_each: @for_each,
141
+ with_spec: @with_spec || []
133
142
  ).tap { |policy| policy.instance_variable_set(:@projection_declared, !!@projection_declared) }
134
143
  end
135
144
 
@@ -2,37 +2,68 @@ require_relative "word_gate"
2
2
  module Hecks
3
3
  module Bluebook
4
4
  module DSL
5
- # Parses a top-level `.port` file's `Hecks.port "Name" do verb "x";
6
- # signal :effect end` body into a `Port` the adapter-facing shape (one
7
- # verb, one signal, an optional method contract in `answers`) a domain
8
- # calls OUT through, as opposed to `DomainPortBuilder`'s own inbound/
9
- # outbound operations.
5
+ # Parses a `verb "x"; signal :effect` port body into a `Port` — the
6
+ # adapter-facing shape (one verb, one signal, an optional method
7
+ # contract in `answers`) a domain calls out through, as opposed to
8
+ # `DomainPortBuilder`'s own inbound/outbound operations.
9
+ #
10
+ # `Hecks.port` (lib/hecks.rb) parses a top-level `.port` file through
11
+ # `DomainPortBuilder`, whose bare-verb branch builds the identical
12
+ # `Port`; this builder is the reference shape `spec/dsl_spec.rb` holds
13
+ # that branch to.
10
14
  class PortBuilder
11
15
  GRAMMAR_CONTEXT = "Port".freeze
12
16
 
13
17
  include WordGate
14
18
 
19
+ # @param name [String] the port's name, as written after `Hecks.port`
15
20
  def initialize(name)
16
21
  @name = name
17
22
  @signal = :reply
18
23
  @answers = []
19
24
  end
20
25
 
26
+ # Names the verb a domain's aggregates call this port by when a hecksagon binds it.
27
+ #
28
+ # @param value [String, Symbol] the verb, such as `"asked_by"` or `"persisted_by"`
29
+ # @return [String] the verb as stored
21
30
  def verb(value) = @verb = value.to_s
31
+
32
+ # Sets whether the domain waits for a value back; a port left unset signals `:reply`.
33
+ #
34
+ # @param value [Symbol, String] `:reply` when the adapter answers with a value,
35
+ # `:effect` when it is called only for its effect
36
+ # @return [Symbol] the signal as stored
22
37
  def signal(value) = @signal = value.to_sym
23
38
 
24
- # THE METHOD CONTRACT the fact a `.port` file's `verb`/`signal`
25
- # never carried: what an adapter must actually RESPOND TO for a
39
+ # Declares one method an adapter bound to this port must respond to.
40
+ #
41
+ # **The method contract** — the fact a `.port` file's `verb`/`signal`
42
+ # never carried: what an adapter must actually respond to for a
26
43
  # dispatch to reach it without a bare `NoMethodError`. Declared the
27
44
  # same repeatable way `AdapterBuilder#field`/`#secret` already are,
28
45
  # so `verify!` can check it with `respond_to?` at boot instead of
29
46
  # the runtime discovering it live.
47
+ #
48
+ # @param name [Symbol, String] the method name, such as `:ask`
49
+ # @return [Array<Symbol>] every method declared so far, this one last
30
50
  def answers(name) = @answers << name.to_sym
31
51
 
52
+ # Assembles the collected verb, signal and method contract, judged by the port language.
53
+ #
54
+ # @return [Bluebook::Port] the port, returned once the language accepts it
55
+ # @raise [Bluebook::DSL::Malformed] if the port language refuses the declaration
32
56
  def build
33
57
  MetaValidator.call_port(Port.new(name: @name, verb: @verb, signal: @signal, answers: @answers))
34
58
  end
35
59
 
60
+ # Evaluates a port block against a fresh builder and returns what it built.
61
+ #
62
+ # @param name [String] the port's name
63
+ # @yield the port body, evaluated with the builder as `self`; may be omitted
64
+ # @return [Bluebook::Port] the judged port
65
+ # @raise [Bluebook::DSL::Malformed] if the port language refuses the declaration, or
66
+ # the block uses a word the `Port` grammar does not admit
36
67
  def self.build(name, &block)
37
68
  builder = new(name)
38
69
  builder.instance_eval(&block) if block