hecks 1.2.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 (358) 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 +60 -2
  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/postgres_era.adapter +5 -0
  23. data/lib/hecks/adapters/driven/prism.rb +4 -4
  24. data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
  25. data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
  26. data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
  27. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
  28. data/lib/hecks/adapters/driven/sqlite.rb +181 -21
  29. data/lib/hecks/adapters/driven.rb +4 -4
  30. data/lib/hecks/adapters/driving/github_webhook.rb +145 -0
  31. data/lib/hecks/behaviors/dsl.rb +2 -2
  32. data/lib/hecks/behaviors/expectations.rb +51 -23
  33. data/lib/hecks/behaviors/ir.rb +1 -1
  34. data/lib/hecks/behaviors/rspec.rb +1 -1
  35. data/lib/hecks/behaviors/runner.rb +2 -2
  36. data/lib/hecks/behaviors.rb +1 -1
  37. data/lib/hecks/bluebook/aggregate.rb +13 -13
  38. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +10 -10
  39. data/lib/hecks/bluebook/assembly/build.rb +1 -1
  40. data/lib/hecks/bluebook/assembly/contract.rb +39 -16
  41. data/lib/hecks/bluebook/assembly/contracts.rb +40 -38
  42. data/lib/hecks/bluebook/assembly/marks.rb +18 -18
  43. data/lib/hecks/bluebook/assembly/specializer.rb +19 -21
  44. data/lib/hecks/bluebook/assembly.rb +9 -9
  45. data/lib/hecks/bluebook/attribute.rb +9 -9
  46. data/lib/hecks/bluebook/behaviour/aggregate.rb +11 -11
  47. data/lib/hecks/bluebook/behaviour/attribute.rb +5 -5
  48. data/lib/hecks/bluebook/behaviour/chapter.rb +23 -5
  49. data/lib/hecks/bluebook/behaviour/command.rb +23 -23
  50. data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -3
  51. data/lib/hecks/bluebook/behaviour/entity.rb +8 -8
  52. data/lib/hecks/bluebook/behaviour/hexagon.rb +4 -4
  53. data/lib/hecks/bluebook/behaviour/lifecycle.rb +5 -5
  54. data/lib/hecks/bluebook/behaviour/policy.rb +12 -12
  55. data/lib/hecks/bluebook/behaviour/process_manager.rb +7 -7
  56. data/lib/hecks/bluebook/behaviour/query.rb +1 -1
  57. data/lib/hecks/bluebook/behaviour/read_model.rb +8 -8
  58. data/lib/hecks/bluebook/behaviour/traits.rb +12 -12
  59. data/lib/hecks/bluebook/behaviour/value_object.rb +6 -6
  60. data/lib/hecks/bluebook/capabilities.rb +27 -0
  61. data/lib/hecks/bluebook/chapter.rb +28 -9
  62. data/lib/hecks/bluebook/command.rb +12 -12
  63. data/lib/hecks/bluebook/domain_port.rb +9 -9
  64. data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
  65. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
  66. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +96 -96
  67. data/lib/hecks/bluebook/dsl/attribute_collector.rb +41 -41
  68. data/lib/hecks/bluebook/dsl/binding_proxy.rb +22 -2
  69. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +111 -74
  70. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +48 -30
  71. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
  72. data/lib/hecks/bluebook/dsl/command_builder.rb +103 -103
  73. data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
  74. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
  75. data/lib/hecks/bluebook/dsl/entity_builder.rb +56 -56
  76. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
  77. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +89 -30
  78. data/lib/hecks/bluebook/dsl/identity_declaration.rb +17 -17
  79. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
  80. data/lib/hecks/bluebook/dsl/policy_builder.rb +30 -21
  81. data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
  82. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
  83. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +35 -35
  84. data/lib/hecks/bluebook/dsl/query_builder.rb +5 -5
  85. data/lib/hecks/bluebook/dsl/read_model_builder.rb +34 -34
  86. data/lib/hecks/bluebook/dsl/rule_reference.rb +41 -39
  87. data/lib/hecks/bluebook/dsl/translation_builder.rb +9 -9
  88. data/lib/hecks/bluebook/dsl/value_object_builder.rb +16 -16
  89. data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
  90. data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
  91. data/lib/hecks/bluebook/entity.rb +11 -11
  92. data/lib/hecks/bluebook/expression/ast_json.rb +20 -20
  93. data/lib/hecks/bluebook/expression/ast_reader.rb +3 -3
  94. data/lib/hecks/bluebook/expression/canonical_form.rb +9 -9
  95. data/lib/hecks/bluebook/expression/evaluator.rb +18 -18
  96. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +18 -18
  97. data/lib/hecks/bluebook/expression/resolver.rb +60 -62
  98. data/lib/hecks/bluebook/hexagon.rb +1 -1
  99. data/lib/hecks/bluebook/lifecycle.rb +1 -1
  100. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -1
  101. data/lib/hecks/bluebook/meta_validator/judge.rb +122 -86
  102. data/lib/hecks/bluebook/meta_validator/plan.rb +39 -39
  103. data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -2
  104. data/lib/hecks/bluebook/meta_validator/readings.rb +43 -43
  105. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +44 -37
  106. data/lib/hecks/bluebook/meta_validator/shapes.rb +25 -21
  107. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +145 -31
  108. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +6 -6
  109. data/lib/hecks/bluebook/meta_validator/world_judge.rb +5 -5
  110. data/lib/hecks/bluebook/meta_validator.rb +70 -70
  111. data/lib/hecks/bluebook/model_check.rb +301 -84
  112. data/lib/hecks/bluebook/pattern_subset.rb +9 -9
  113. data/lib/hecks/bluebook/policy.rb +15 -13
  114. data/lib/hecks/bluebook/process_manager.rb +14 -14
  115. data/lib/hecks/bluebook/project_register.rb +6 -6
  116. data/lib/hecks/bluebook/query.rb +4 -4
  117. data/lib/hecks/bluebook/read_model.rb +14 -14
  118. data/lib/hecks/bluebook/reference.rb +8 -8
  119. data/lib/hecks/bluebook/smoke_test.rb +19 -19
  120. data/lib/hecks/bluebook/synthesizer.rb +12 -12
  121. data/lib/hecks/bluebook/translation.rb +4 -4
  122. data/lib/hecks/bluebook/value_object.rb +6 -6
  123. data/lib/hecks/bluebook.rb +2 -2
  124. data/lib/hecks/codemod/legacy_dispatch_args.rb +299 -0
  125. data/lib/hecks/codemod/legacy_dispatch_recorder.rb +186 -0
  126. data/lib/hecks/codemod.rb +36 -35
  127. data/lib/hecks/construct.rb +6 -6
  128. data/lib/hecks/corpus.rb +317 -0
  129. data/lib/hecks/deprecation.rb +95 -0
  130. data/lib/hecks/doc/reference.rb +19 -19
  131. data/lib/hecks/embryonaut_bluebook.rb +11 -11
  132. data/lib/hecks/facade/cli_door.rb +69 -10
  133. data/lib/hecks/facade/cli_runner.rb +105 -24
  134. data/lib/hecks/facade/command_request.rb +23 -0
  135. data/lib/hecks/facade/handle.rb +79 -32
  136. data/lib/hecks/facade/json_door.rb +106 -25
  137. data/lib/hecks/facade/surface/aggregate_door.rb +42 -27
  138. data/lib/hecks/facade/surface/chapter.rb +26 -17
  139. data/lib/hecks/facade/surface.rb +16 -3
  140. data/lib/hecks/facade.rb +15 -4
  141. data/lib/hecks/forms/app.rb +46 -30
  142. data/lib/hecks/forms/command_form_renderer.rb +70 -9
  143. data/lib/hecks/forms/field_renderer.rb +142 -6
  144. data/lib/hecks/forms/field_shape.rb +183 -20
  145. data/lib/hecks/forms/html.rb +51 -7
  146. data/lib/hecks/forms/index_renderer.rb +14 -2
  147. data/lib/hecks/forms/params.rb +120 -23
  148. data/lib/hecks/forms/port_argument.rb +2 -2
  149. data/lib/hecks/forms/query_form_renderer.rb +2 -2
  150. data/lib/hecks/forms/record_renderer.rb +2 -2
  151. data/lib/hecks/forms/record_table.rb +1 -1
  152. data/lib/hecks/forms/value_object_shape.rb +3 -3
  153. data/lib/hecks/forms.rb +24 -4
  154. data/lib/hecks/fqn.rb +1 -1
  155. data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
  156. data/lib/hecks/framework.rb +48 -17
  157. data/lib/hecks/freezer.rb +11 -11
  158. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +90 -90
  159. data/lib/hecks/fuzzing/combination_miner.rb +119 -0
  160. data/lib/hecks/fuzzing/concurrent_dispatch.rb +361 -0
  161. data/lib/hecks/fuzzing/coverage_campaign.rb +118 -0
  162. data/lib/hecks/fuzzing/differential.rb +158 -0
  163. data/lib/hecks/fuzzing/domain_generator.rb +694 -0
  164. data/lib/hecks/fuzzing/era_boundary.rb +124 -0
  165. data/lib/hecks/fuzzing/form_census.rb +199 -0
  166. data/lib/hecks/fuzzing/generated_domain_check.rb +95 -0
  167. data/lib/hecks/fuzzing/invalid_value_generator.rb +6 -6
  168. data/lib/hecks/fuzzing/isolated_boot.rb +226 -38
  169. data/lib/hecks/fuzzing/nondeterministic.rb +67 -0
  170. data/lib/hecks/fuzzing/persistence_parity.rb +161 -0
  171. data/lib/hecks/fuzzing/properties/corrections.rb +100 -0
  172. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +321 -41
  173. data/lib/hecks/fuzzing/properties/guards.rb +129 -26
  174. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +32 -32
  175. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +31 -11
  176. data/lib/hecks/fuzzing/properties/outbox.rb +142 -0
  177. data/lib/hecks/fuzzing/properties/querying.rb +17 -17
  178. data/lib/hecks/fuzzing/properties.rb +84 -39
  179. data/lib/hecks/fuzzing/qa_settings.rb +152 -0
  180. data/lib/hecks/fuzzing/replay.rb +224 -114
  181. data/lib/hecks/fuzzing/rotation_priority.rb +94 -0
  182. data/lib/hecks/fuzzing/rust_gap_manifest.rb +113 -0
  183. data/lib/hecks/fuzzing/self_consistency.rb +676 -0
  184. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +526 -0
  185. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +101 -30
  186. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +55 -8
  187. data/lib/hecks/fuzzing/sequence_generator/picker.rb +31 -11
  188. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +123 -32
  189. data/lib/hecks/fuzzing/sequence_generator.rb +152 -31
  190. data/lib/hecks/fuzzing/shrinker.rb +197 -0
  191. data/lib/hecks/fuzzing/structural_skips.rb +39 -0
  192. data/lib/hecks/fuzzing/sweep_depth.rb +53 -0
  193. data/lib/hecks/fuzzing/target_capabilities.rb +180 -0
  194. data/lib/hecks/fuzzing/value_generator.rb +66 -14
  195. data/lib/hecks/fuzzing.rb +11 -0
  196. data/lib/hecks/grammar/evolve.rb +10 -10
  197. data/lib/hecks/grammar.rb +7 -7
  198. data/lib/hecks/ir.rb +13 -13
  199. data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
  200. data/lib/hecks/language/bluebook/policy.bluebook +11 -1
  201. data/lib/hecks/language/bluebook/vocabulary.bluebook +365 -15
  202. data/lib/hecks/language/oidc.json +5 -0
  203. data/lib/hecks/literal.rb +9 -9
  204. data/lib/hecks/naming.rb +89 -21
  205. data/lib/hecks/ports/access_control.rb +58 -2
  206. data/lib/hecks/ports/agent/answers.rb +83 -6
  207. data/lib/hecks/ports/agent.rb +119 -35
  208. data/lib/hecks/ports/authentication.rb +44 -4
  209. data/lib/hecks/ports/authorization.rb +53 -11
  210. data/lib/hecks/ports/clock.rb +42 -23
  211. data/lib/hecks/ports/extraction.rb +16 -0
  212. data/lib/hecks/ports/identity_assignment.rb +24 -2
  213. data/lib/hecks/ports/identity_generation.rb +17 -3
  214. data/lib/hecks/ports/identity_resolution.rb +18 -1
  215. data/lib/hecks/ports/loading.rb +4 -0
  216. data/lib/hecks/ports/persistence/append_only.rb +172 -8
  217. data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
  218. data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
  219. data/lib/hecks/ports/persistence/execution.rb +4 -0
  220. data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
  221. data/lib/hecks/ports/persistence/plugin.rb +42 -4
  222. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
  223. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
  224. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
  225. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
  226. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
  227. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
  228. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
  229. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
  230. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
  231. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +160 -58
  232. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
  233. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +28 -5
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
  237. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +44 -6
  238. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
  239. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +57 -4
  240. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
  241. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +338 -83
  242. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
  243. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
  244. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
  245. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +42 -8
  246. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
  247. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
  248. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
  249. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -19
  250. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +5 -5
  251. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +1 -1
  252. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
  253. data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
  254. data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
  255. data/lib/hecks/ports/persistence/repository_factory.rb +29 -6
  256. data/lib/hecks/ports/persistence/state_codec.rb +319 -0
  257. data/lib/hecks/ports/persistence.rb +36 -1
  258. data/lib/hecks/ports/projection.rb +61 -7
  259. data/lib/hecks/ports/query/in_memory.rb +3 -3
  260. data/lib/hecks/ports/query/ordering.rb +6 -6
  261. data/lib/hecks/ports/query.rb +35 -0
  262. data/lib/hecks/projections/bootstrap_table.rb +112 -0
  263. data/lib/hecks/projections/diagrams.rb +75 -75
  264. data/lib/hecks/projections/glossary/html.rb +250 -0
  265. data/lib/hecks/projections/glossary/markdown.rb +105 -0
  266. data/lib/hecks/projections/glossary/mermaid.rb +110 -0
  267. data/lib/hecks/projections/glossary/page.css +271 -0
  268. data/lib/hecks/projections/glossary/page.js +72 -0
  269. data/lib/hecks/projections/glossary/sections.rb +17 -0
  270. data/lib/hecks/projections/glossary/sentences.rb +205 -0
  271. data/lib/hecks/projections/glossary.rb +214 -286
  272. data/lib/hecks/projections/ir.rb +1 -1
  273. data/lib/hecks/projections/model/deviations.rb +18 -17
  274. data/lib/hecks/projections/model.rb +25 -21
  275. data/lib/hecks/projections/oidc.rb +7 -7
  276. data/lib/hecks/projections/parser_table.rb +5 -5
  277. data/lib/hecks/projections/reference.rb +3 -3
  278. data/lib/hecks/projections/rust_vocabulary.rb +443 -0
  279. data/lib/hecks/projections/shape.rb +2 -2
  280. data/lib/hecks/projections/statements.rb +11 -11
  281. data/lib/hecks/projections/vocabulary.rb +9 -9
  282. data/lib/hecks/projections.rb +5 -3
  283. data/lib/hecks/projector/cli_projector.rb +29 -29
  284. data/lib/hecks/projector/docs_projector.rb +13 -13
  285. data/lib/hecks/projector/exporter.rb +42 -21
  286. data/lib/hecks/projector/ir_projector.rb +1 -1
  287. data/lib/hecks/projector/narrate_projector.rb +15 -22
  288. data/lib/hecks/projector/target.rb +13 -13
  289. data/lib/hecks/projector.rb +15 -15
  290. data/lib/hecks/query_ir.rb +47 -47
  291. data/lib/hecks/query_specification/common/comparators.rb +19 -3
  292. data/lib/hecks/query_specification/common/comparison.rb +132 -24
  293. data/lib/hecks/query_specification/common/dsl.rb +65 -9
  294. data/lib/hecks/query_specification/common/null_policy.rb +57 -13
  295. data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
  296. data/lib/hecks/query_specification/common/options.rb +25 -0
  297. data/lib/hecks/query_specification/field_path.rb +69 -15
  298. data/lib/hecks/query_specification/hop_path.rb +57 -20
  299. data/lib/hecks/query_specification/read_model/specification.rb +4 -0
  300. data/lib/hecks/rendering.rb +3 -3
  301. data/lib/hecks/router/namespace_installer.rb +3 -3
  302. data/lib/hecks/router.rb +1 -1
  303. data/lib/hecks/runtime/aggregate_lock.rb +11 -11
  304. data/lib/hecks/runtime/boot_gates.rb +3 -3
  305. data/lib/hecks/runtime/caller.rb +8 -8
  306. data/lib/hecks/runtime/capability_graph.rb +2 -2
  307. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +22 -25
  308. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +64 -67
  309. data/lib/hecks/runtime/command_interpreter.rb +159 -102
  310. data/lib/hecks/runtime/command_rules/admissibility.rb +69 -69
  311. data/lib/hecks/runtime/command_rules/arithmetic.rb +137 -91
  312. data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
  313. data/lib/hecks/runtime/command_rules/emission.rb +18 -1
  314. data/lib/hecks/runtime/command_rules/references.rb +94 -19
  315. data/lib/hecks/runtime/command_rules.rb +3 -0
  316. data/lib/hecks/runtime/dependency_planning.rb +11 -11
  317. data/lib/hecks/runtime/dispatcher.rb +283 -90
  318. data/lib/hecks/runtime/entity_element.rb +282 -46
  319. data/lib/hecks/runtime/entity_interpreter.rb +160 -69
  320. data/lib/hecks/runtime/errors.rb +19 -19
  321. data/lib/hecks/runtime/event.rb +6 -6
  322. data/lib/hecks/runtime/identity.rb +22 -22
  323. data/lib/hecks/runtime/instance.rb +39 -14
  324. data/lib/hecks/runtime/interpreting.rb +12 -12
  325. data/lib/hecks/runtime/invocation.rb +276 -0
  326. data/lib/hecks/runtime/loader.rb +14 -14
  327. data/lib/hecks/runtime/outbox.rb +23 -23
  328. data/lib/hecks/runtime/policy_interpreter.rb +54 -54
  329. data/lib/hecks/runtime/port_operation_interpreter.rb +22 -19
  330. data/lib/hecks/runtime/query_interpreter.rb +111 -56
  331. data/lib/hecks/runtime/reaction_invocation.rb +76 -9
  332. data/lib/hecks/runtime/read_model_interpreter.rb +40 -40
  333. data/lib/hecks/runtime/rebuild_sweep.rb +4 -4
  334. data/lib/hecks/runtime/reference_hop.rb +6 -6
  335. data/lib/hecks/runtime/refusal_wording.rb +92 -112
  336. data/lib/hecks/runtime/registry/saga_persistence.rb +21 -21
  337. data/lib/hecks/runtime/registry/verification.rb +36 -26
  338. data/lib/hecks/runtime/registry.rb +56 -27
  339. data/lib/hecks/runtime/remote_dispatcher.rb +38 -23
  340. data/lib/hecks/runtime/routing.rb +10 -88
  341. data/lib/hecks/runtime/saga_interpreter/correlation.rb +17 -17
  342. data/lib/hecks/runtime/saga_interpreter.rb +93 -56
  343. data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
  344. data/lib/hecks/runtime/tenant_check.rb +9 -9
  345. data/lib/hecks/runtime/tenant_scope.rb +5 -5
  346. data/lib/hecks/runtime/value/admission.rb +75 -30
  347. data/lib/hecks/runtime/value/coercion.rb +379 -226
  348. data/lib/hecks/runtime/value/entity_list_coercion.rb +248 -0
  349. data/lib/hecks/runtime/value.rb +28 -23
  350. data/lib/hecks/runtime.rb +7 -7
  351. data/lib/hecks/storehouse.rb +64 -64
  352. data/lib/hecks/version.rb +3 -3
  353. data/lib/hecks/vocabulary.rb +207 -5
  354. data/lib/hecks.rb +13 -11
  355. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +11 -11
  356. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +14 -12
  357. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +11 -11
  358. metadata +43 -2
@@ -1,11 +1,13 @@
1
1
  require_relative "../errors"
2
2
  require_relative "../refusal_wording"
3
3
  require_relative "../value"
4
+ require_relative "../../rendering"
5
+ require_relative "../../ports/query/in_memory"
4
6
 
5
7
  module Hecks
6
8
  module Runtime
7
9
  class CommandRules
8
- # A reference must point at something that EXISTS.
10
+ # A reference must point at something that exists.
9
11
  #
10
12
  # `reference_to Customer` is the one guarantee an aggregate reference is
11
13
  # for, and it was declared 14 times across banking and enforced nowhere :
@@ -14,7 +16,7 @@ module Hecks
14
16
  # no corpus step ever passed a dangling reference.
15
17
  module References
16
18
  # Resolved here rather than in coercion because coercion is pure — it
17
- # holds no repository. A reference INTO ANOTHER DOMAIN is left alone : a
19
+ # holds no repository. A reference into another domain is left alone : a
18
20
  # cross-domain target may legitimately not be loaded, which is the same
19
21
  # reading `across` policies already get.
20
22
  #
@@ -40,6 +42,8 @@ module Hecks
40
42
  # This is what makes a `has_many` declared on an aggregate honest even
41
43
  # when a command supplies its list through an ordinary typed argument.
42
44
  def resolve_state_references(domain, construct, state)
45
+ own_tenant_field = tenant_field_for(construct)
46
+
43
47
  construct.attributes.each do |attribute|
44
48
  next unless attribute.reference?
45
49
 
@@ -51,6 +55,7 @@ module Hecks
51
55
  next unless target
52
56
 
53
57
  validate_reference_values(domain, target, held, list: attribute.list?)
58
+ enforce_tenant_boundary(domain, construct, attribute, target, held, state, own_tenant_field)
54
59
  end
55
60
 
56
61
  Array(construct.entities).each do |entity|
@@ -78,7 +83,7 @@ module Hecks
78
83
  "#{attribute.type.target_name} identity, got nil"
79
84
  end
80
85
 
81
- # The reference RESOLVES itself — through the chapter's own IR, so the
86
+ # The reference resolves itself — through the chapter's own IR, so the
82
87
  # bluebook's declared heads are the index. This used to regex the target's
83
88
  # name out of "Reference<Customer>" and then search
84
89
  # `registry.bluebook(domain).aggregates` for it — and later reached the
@@ -96,31 +101,101 @@ module Hecks
96
101
  next if @registry.repository(domain, target).find(key)
97
102
 
98
103
  raise NotFound,
99
- RefusalWording.render("NotFound", "reference_target_missing",
100
- target: target.name, heads: target.identity_heads.join(", "),
101
- key: key.inspect)
104
+ RefusalWording.render_site("NotFound", "reference_target_missing",
105
+ target: target.name, heads: target.identity_heads.join(", "),
106
+ key: key)
107
+ end
108
+ end
109
+
110
+ # Angle-8's own write-side half of `TenantScope.apply` (runtime/
111
+ # tenant_scope.rb) — the query-side mechanism this mirrors. That
112
+ # module turns a declared `authorize policy, tenant: :field` into a
113
+ # synthetic where-clause checked against the caller's own supplied
114
+ # tenant argument; there is no caller-identity/session system this
115
+ # runtime has to check a write's caller against (TenantScope's own
116
+ # header names that as a separate, still-open gap), so this checks
117
+ # the one thing that is available without one: whether the record
118
+ # being written and the record it references agree about which
119
+ # tenant they belong to. `lib/hecks/fuzzing/properties/guards.rb`'s
120
+ # `commands_respect_tenant_scope` states the identical claim,
121
+ # read off `history[:instances]` after the fact — this is what
122
+ # makes that claim hold by construction (a refused write is never
123
+ # stored) rather than merely checked for regression.
124
+ #
125
+ # Hooked into `resolve_state_references` rather than a new
126
+ # DISPATCH_ORDER step deliberately: that method already walks
127
+ # every `reference_to`-typed attribute against the settled,
128
+ # post-mutation state (the same moment `commands_respect_tenant_
129
+ # scope` itself inspects), already resolves the referenced record
130
+ # through the repository right above, and already runs from both
131
+ # `CommandInterpreter#step_save` and `EntityInterpreter#step_save`
132
+ # — one change, both interpreters covered, no new vocabulary step
133
+ # to keep in sync with `Vocabulary::AggregateDispatchOrder`/
134
+ # `EntityDispatchOrder`.
135
+ def enforce_tenant_boundary(domain, construct, attribute, target, held, state, own_tenant_field)
136
+ return unless own_tenant_field && state.key?(own_tenant_field)
137
+
138
+ target_tenant_field = tenant_field_for(target)
139
+ return unless target_tenant_field
140
+
141
+ own_tenant = Ports::Query::InMemory.comparable(state[own_tenant_field])
142
+
143
+ values = attribute.list? ? Array(held) : [held]
144
+ values.each do |value|
145
+ key = reference_key(value)
146
+ next if key.empty?
147
+
148
+ record = @registry.repository(domain, target).find(key)
149
+ next unless record&.state&.key?(target_tenant_field)
150
+
151
+ target_tenant = Ports::Query::InMemory.comparable(record.state[target_tenant_field])
152
+ next if target_tenant == own_tenant
153
+
154
+ raise Unauthorized,
155
+ RefusalWording.render_site("Unauthorized", "cross_tenant_reference",
156
+ aggregate: construct.hecks_name, field: own_tenant_field,
157
+ tenant: Rendering.describe(state[own_tenant_field]),
158
+ attribute: attribute.name, target: target.name,
159
+ target_field: target_tenant_field,
160
+ other: Rendering.describe(record.state[target_tenant_field]))
102
161
  end
103
162
  end
104
163
 
105
- # `value` is the referenced record's own id, EXACTLY as `Identity.of`
164
+ # The field an aggregate's own query names as tenant-scoping — the
165
+ # exact same lookup `Fuzzing::Properties::Guards#tenant_field_for`
166
+ # already established for the property that found this gap, reused
167
+ # here rather than reinvented: an aggregate's own declared tenant
168
+ # field is whichever field one of its own queries names in
169
+ # `authorize policy, tenant: :field`. `nil` for a construct that
170
+ # declares no such query — not every aggregate is tenant-scoped,
171
+ # and an entity never declares a query of its own at all today
172
+ # (`Entity.queries` is always empty in the real corpus), so this
173
+ # answers `nil` for every entity without needing to special-case
174
+ # one.
175
+ def tenant_field_for(construct)
176
+ authorization = construct.queries.filter_map(&:authorization).find(&:tenant)
177
+ authorization&.tenant&.to_sym
178
+ end
179
+
180
+ # `value` is the referenced record's own id, exactly as `Identity.of`
106
181
  # would build it for that record — a bare scalar for a single-field
107
182
  # identity (the overwhelming common case; Banking's own plain
108
183
  # `reference_to Customer` holds one already, so `Value.
109
184
  # materialize_unwrapped` is a no-op passthrough here), or a
110
185
  # `Naming.identity`-joined string for a compound one
111
186
  # (`belongs_to Translation, as: :translation_ref` — Translation's
112
- # own `identified_by :domain, :from, :to`, THREE fields). Before
187
+ # own `identified_by :domain, :from, :to`, three fields). Before
113
188
  # this, plain `value.to_s` on that compound case's own coerced
114
189
  # Value hit Ruby's default `Object#to_s` (a raw, run-to-run-random
115
190
  # memory address) instead of joining the record's real id — found
116
191
  # live via bin/fuzz on the self-hosted "translation" domain
117
- # (replay_is_deterministic), the SAME class of gap `Identity.from`
192
+ # (replay_is_deterministic), the same class of gap `Identity.from`
118
193
  # already had for a compound `identified_by`'s own bare (undotted)
119
194
  # attribute paths. `materialize_unwrapped` recurses a multi-
120
195
  # attribute value object to a plain Hash keyed by attribute name,
121
196
  # in declaration order — `Naming.identity` on `.values` reproduces
122
197
  # the identical join `Identity.of` itself would produce for the
123
- # SAME fields.
198
+ # same fields.
124
199
  def reference_key(value)
125
200
  unwrapped = Value.materialize_unwrapped(value)
126
201
  return Naming.identity(unwrapped.values).to_s if unwrapped.is_a?(Hash)
@@ -128,35 +203,35 @@ module Hecks
128
203
  unwrapped.to_s
129
204
  end
130
205
 
131
- # A COMMAND ARGUMENT's own related record, reachable by name from
206
+ # A command argument's own related record, reachable by name from
132
207
  # `given`/`ensures` — `disputed_by.status`, say, `CardPayment
133
208
  # .Dispute`'s own fresh `Reference<Customer>` argument — without
134
209
  # teaching the pure expression evaluator anything about
135
- # repositories. The lookup happens HERE, once, before evaluation;
210
+ # repositories. The lookup happens here, once, before evaluation;
136
211
  # `Resolver#lookup` just digs into a plain Hash exactly as it
137
212
  # always has.
138
213
  #
139
- # `owner` NARROWED TO `command` ONLY (S12, ADR 0025 — "rules
214
+ # `owner` narrowed to `command` only (S12, ADR 0025 — "rules
140
215
  # confined to their own aggregate boundary"): dereferencing the
141
- # DECLARING aggregate/entity's own STORED `reference_to` used to
216
+ # declaring aggregate/entity's own stored `reference_to` used to
142
217
  # be the other half of this method's job — a live query against
143
218
  # another aggregate's own repository, every time a `given`/
144
219
  # `ensures`/`invariant` ran. That half is gone; a cross-aggregate
145
- # fact a rule needs now has to be a `projects`-maintained LOCAL
220
+ # fact a rule needs now has to be a `projects`-maintained local
146
221
  # field (`AggregateBuilder#projects_impl`'s own comment), already
147
222
  # present in `subject`'s own state, no hydration needed. A
148
- # reference-typed COMMAND ARGUMENT stays in bounds, though — the
223
+ # reference-typed command argument stays in bounds, though — the
149
224
  # ADR's own boundary list names "its command arguments" as
150
225
  # readable, and nothing is stored yet for a fresh argument to
151
- # project from; resolving it once here, synchronous with THIS
226
+ # project from; resolving it once here, synchronous with this
152
227
  # command's own admission, is a different shape from a live query
153
- # against an ALREADY-PERSISTED reference. `enforce_givens`/
228
+ # against an already-persisted reference. `enforce_givens`/
154
229
  # `enforce_ensures` are this method's only two remaining callers,
155
230
  # both passing `command`/`args`, never a `subject`'s own
156
231
  # aggregate — verified before this comment was written, not
157
232
  # assumed.
158
233
  #
159
- # RECURSES into what it finds, so a chain deeper than one hop
234
+ # Recurses into what it finds, so a chain deeper than one hop
160
235
  # still resolves in one pass. Depth-bounded rather than cycle-
161
236
  # detected — nothing in this corpus dots more than two hops on a
162
237
  # fresh argument, and a bound is simpler than tracking visited
@@ -20,6 +20,9 @@ module Hecks
20
20
 
21
21
  attr_reader :registry
22
22
 
23
+ # @param registry [Runtime::Registry, nil] the booted registry the rules read bluebooks,
24
+ # repositories and the event log from; nil serves only the rules that read no
25
+ # registry, such as `sign_of`
23
26
  def initialize(registry)
24
27
  @registry = registry
25
28
  end
@@ -74,12 +74,12 @@ module Hecks
74
74
  STATEFUL_MUTATIONS = %i[append increment decrement multiply clamp remove].freeze
75
75
 
76
76
  # `root_aggregate:` — Wave 8's own audit surfaced a real bug here,
77
- # not merely a missing feature: for an ENTITY-owned command,
77
+ # not merely a missing feature: for an entity-owned command,
78
78
  # `EntityInterpreter` calls this with `aggregate:` set to the
79
- # ENTITY itself (`element_interpreter.rb`'s own `Analyzer.call
79
+ # entity itself (`element_interpreter.rb`'s own `Analyzer.call
80
80
  # (aggregate: entity, command:)`), so `owner_fields` was always
81
81
  # the entity's own attribute set. A `given`/`ensures` reading
82
- # `parent.X` legitimately means the ROOT aggregate's own field —
82
+ # `parent.X` legitimately means the root aggregate's own field —
83
83
  # a genuinely different owner — but `classify_path`'s `:parent`
84
84
  # branch checked that read against `owner_fields` (the entity's),
85
85
  # which can never contain a root-level field, so every entity
@@ -96,10 +96,10 @@ module Hecks
96
96
  @owner_fields << aggregate.lifecycle.field.to_sym if aggregate.lifecycle
97
97
  @root_owner_fields = root_aggregate.attributes.to_set(&:name)
98
98
  @root_owner_fields << root_aggregate.lifecycle.field.to_sym if root_aggregate.lifecycle
99
- # `projects` FIELDS (S12, ADR 0025) ARE OWNER STATE TOO — a
99
+ # `projects` fields (S12, ADR 0025) are owner state too — a
100
100
  # `given`/`ensures` reading one (e.g. `customer_status ==
101
101
  # "active"`) is reading this record's own stored field, same
102
- # as any attribute, even though nothing here WRITES it via a
102
+ # as any attribute, even though nothing here writes it via a
103
103
  # declared mutation (`CommandInterpreter#seed_projected_fields`
104
104
  # populates it outside this analysis entirely). Left out of
105
105
  # `known_writes` deliberately: `add_preservation_reads` then
@@ -107,7 +107,7 @@ module Hecks
107
107
  # a partial mutation, which is exactly right — a projected
108
108
  # field's freshness comes from the interpreter reseeding it on
109
109
  # save, not from anything a caller-supplied write set carries.
110
- # Applies to BOTH `owner_fields` and `root_owner_fields` — an
110
+ # Applies to both `owner_fields` and `root_owner_fields` — an
111
111
  # entity's own `parent.*` read can name the root aggregate's
112
112
  # projected field just as easily as one of its real attributes
113
113
  # (`Banking::Withdrawal.Dispute`'s own `parent.account_customer_
@@ -237,7 +237,7 @@ module Hecks
237
237
  end
238
238
  end
239
239
 
240
- # KNOWN, HARMLESS GAP: `corrects ..., as: :name`'s bound name
240
+ # Known, harmless gap: `corrects ..., as: :name`'s bound name
241
241
  # (admissibility.rb's `enforce_correction_target`/`enforce_givens`/
242
242
  # `enforce_ensures`) isn't special-cased here the way `:old`/
243
243
  # `:parent` are — a given/ensures referencing it falls through to
@@ -247,7 +247,7 @@ module Hecks
247
247
  # `hydrate_existing` path instead of the `ATOMIC_PUT` fast path.
248
248
  # Not a correctness bug — `as:`'s runtime binding (a plain `attrs`
249
249
  # merge, exactly like `old:`'s) resolves and evaluates correctly
250
- # regardless of what this STATIC analysis concludes — just a real,
250
+ # regardless of what this static analysis concludes — just a real,
251
251
  # deliberately-left optimization gap: closing it would mean
252
252
  # threading "which names this command declares as correction
253
253
  # bindings" into the Analyzer, which doesn't have that per-command
@@ -258,9 +258,9 @@ module Hecks
258
258
  name = head.to_sym
259
259
 
260
260
  if name == :parent
261
- # `root_owner_fields` — NOT `owner_fields`. For an entity-owned
262
- # command `owner_fields` is the ENTITY's own attribute set;
263
- # `parent.X` always means the ROOT aggregate's own field, a
261
+ # `root_owner_fields` — not `owner_fields`. For an entity-owned
262
+ # command `owner_fields` is the entity's own attribute set;
263
+ # `parent.X` always means the root aggregate's own field, a
264
264
  # genuinely different owner (`root_aggregate:`'s own header,
265
265
  # above, has the full bug this fixes). Identical for a plain
266
266
  # aggregate command, where root_aggregate defaults to aggregate