hecks 5.0.0 → 6.0.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 (1277) hide show
  1. checksums.yaml +4 -4
  2. data/examples/pizzas/bluebook/pizzas.bluebook +1 -0
  3. data/exe/hecks +14 -2
  4. data/lib/hecks/adapters/driven/claude_code.rb +1 -1
  5. data/lib/hecks/adapters/driven/d1.rb +2 -1
  6. data/lib/hecks/adapters/driven/folder.rb +35 -28
  7. data/lib/hecks/adapters/driven/folder_overlay.rb +51 -0
  8. data/lib/hecks/adapters/driven/google_authentication.adapter +3 -0
  9. data/lib/hecks/adapters/driven/google_authentication.rb +99 -0
  10. data/lib/hecks/adapters/driven/governance_authorization.rb +32 -36
  11. data/lib/hecks/adapters/driven/heki/journal.rb +45 -0
  12. data/lib/hecks/adapters/driven/identity_registry.rb +1 -1
  13. data/lib/hecks/adapters/driven/in_memory_ordering.rb +3 -2
  14. data/lib/hecks/adapters/driven/key_keeper.rb +42 -0
  15. data/lib/hecks/adapters/driven/lambda/client.rb +8 -2
  16. data/lib/hecks/adapters/driven/lambda/remote_dispatcher.rb +185 -0
  17. data/lib/hecks/adapters/driven/lambda.rb +14 -2
  18. data/lib/hecks/adapters/driven/memory/dispatch_claims.rb +25 -0
  19. data/lib/hecks/adapters/driven/memory.rb +15 -4
  20. data/lib/hecks/adapters/driven/postgres/codec.rb +1 -1
  21. data/lib/hecks/adapters/driven/postgres/journal.rb +46 -0
  22. data/lib/hecks/adapters/driven/postgres/outbox.rb +31 -5
  23. data/lib/hecks/adapters/driven/postgres/projection.rb +43 -0
  24. data/lib/hecks/adapters/driven/postgres/repository.rb +1 -1
  25. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +2 -1
  26. data/lib/hecks/adapters/driven/postgres/set_dialect.rb +44 -0
  27. data/lib/hecks/adapters/driven/postgres.adapter +8 -0
  28. data/lib/hecks/adapters/driven/postgres.rb +8 -3
  29. data/lib/hecks/adapters/driven/postgres_dump/connection.rb +57 -0
  30. data/lib/hecks/adapters/driven/postgres_dump.rb +117 -0
  31. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/boot.rb +6 -3
  32. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/class_methods.rb +1 -1
  33. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/dialect.rb +1 -1
  34. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/chain_sql.rb +8 -4
  35. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/domain_rename.rb +2 -2
  36. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/era_integrity.rb +2 -2
  37. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/era_store.rb +3 -3
  38. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/mint_transaction.rb +2 -2
  39. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/sql_templates.rb +6 -2
  40. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/tail_merge.rb +1 -1
  41. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage.rb +1 -1
  42. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager/context.rb +15 -5
  43. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager/coverage_check.rb +13 -11
  44. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager/era_resolver.rb +23 -6
  45. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager/merge_coordinator.rb +2 -2
  46. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager/minter.rb +12 -9
  47. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager/refusals.rb +13 -1
  48. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager.rb +6 -6
  49. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/query_phases.rb +8 -2
  50. data/lib/hecks/adapters/driven/postgres_era.adapter +7 -1
  51. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era.rb +14 -13
  52. data/lib/hecks/adapters/driven/prism.rb +4 -3
  53. data/lib/hecks/adapters/driven/projection/read_model_walk.rb +126 -0
  54. data/lib/hecks/adapters/driven/projection/row_decoding.rb +57 -0
  55. data/lib/hecks/adapters/driven/set_transform/assignments.rb +101 -0
  56. data/lib/hecks/adapters/driven/set_transform/expressions.rb +138 -0
  57. data/lib/hecks/adapters/driven/set_transform.rb +81 -0
  58. data/lib/hecks/adapters/driven/sql_keyset.rb +61 -0
  59. data/lib/hecks/adapters/driven/sql_query_builder.rb +28 -9
  60. data/lib/hecks/adapters/driven/sqlite/codec.rb +1 -1
  61. data/lib/hecks/adapters/driven/sqlite/ddl.rb +7 -0
  62. data/lib/hecks/adapters/driven/sqlite/dialect.rb +2 -1
  63. data/lib/hecks/adapters/driven/sqlite/dispatch_claims.rb +28 -0
  64. data/lib/hecks/adapters/driven/sqlite/journal.rb +46 -0
  65. data/lib/hecks/adapters/driven/sqlite/outbox.rb +3 -2
  66. data/lib/hecks/adapters/driven/sqlite/projection.rb +9 -135
  67. data/lib/hecks/adapters/driven/sqlite/repository.rb +1 -1
  68. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +16 -4
  69. data/lib/hecks/adapters/driven/sqlite/set_dialect.rb +39 -0
  70. data/lib/hecks/adapters/driven/sqlite.adapter +2 -0
  71. data/lib/hecks/adapters/driven/sqlite.rb +11 -2
  72. data/lib/hecks/adapters/driven.rb +8 -3
  73. data/lib/hecks/adapters/driving/cli.rb +1 -1
  74. data/lib/hecks/adapters/driving/cli_runner/answers.rb +23 -16
  75. data/lib/hecks/adapters/driving/cli_runner/resolution.rb +25 -24
  76. data/lib/hecks/adapters/driving/cli_runner/settling.rb +9 -5
  77. data/lib/hecks/adapters/driving/cli_runner/streaming.rb +1 -1
  78. data/lib/hecks/adapters/driving/cli_runner/suggestions.rb +15 -7
  79. data/lib/hecks/adapters/driving/cli_runner/verb_names.rb +49 -0
  80. data/lib/hecks/adapters/driving/cli_runner.rb +4 -2
  81. data/lib/hecks/adapters/driving/github_webhook.rb +2 -1
  82. data/lib/hecks/adapters/driving/handle/verb_methods.rb +46 -0
  83. data/lib/hecks/adapters/driving/handle.rb +9 -17
  84. data/lib/hecks/adapters/driving/json.rb +11 -8
  85. data/lib/hecks/adapters/driving/launcher_options.rb +37 -32
  86. data/lib/hecks/adapters/driving/mcp_scope/env_problems.rb +3 -1
  87. data/lib/hecks/adapters/driving/mcp_scope.rb +8 -7
  88. data/lib/hecks/adapters/driving/newcomer.rb +6 -5
  89. data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +13 -12
  90. data/lib/hecks/adapters/driving/ruby/bulk_verbs.rb +127 -0
  91. data/lib/hecks/adapters/driving/ruby/{chapter.rb → declaration.rb} +27 -27
  92. data/lib/hecks/adapters/driving/ruby/query_rows.rb +27 -0
  93. data/lib/hecks/adapters/driving/ruby.rb +25 -24
  94. data/lib/hecks/adapters/driving/usage_cache.rb +14 -3
  95. data/lib/hecks/assembly.rb +101 -0
  96. data/lib/hecks/behaviors/dsl.rb +13 -3
  97. data/lib/hecks/behaviors/expectations/boot.rb +7 -6
  98. data/lib/hecks/behaviors/expectations/checks.rb +1 -1
  99. data/lib/hecks/behaviors/expectations/dispatching.rb +10 -9
  100. data/lib/hecks/behaviors/expectations/qualify.rb +9 -9
  101. data/lib/hecks/behaviors/expectations.rb +24 -10
  102. data/lib/hecks/behaviors/ir.rb +4 -2
  103. data/lib/hecks/behaviors/rspec.rb +1 -1
  104. data/lib/hecks/behaviors/runner.rb +31 -4
  105. data/lib/hecks/bench/environment.rb +6 -2
  106. data/lib/hecks/bench/postgres_probe.rb +3 -1
  107. data/lib/hecks/bench/report.rb +4 -1
  108. data/lib/hecks/bench/suite.rb +9 -3
  109. data/lib/hecks/bluebook/acknowledgements.rb +81 -0
  110. data/lib/hecks/bluebook/aggregate.rb +13 -4
  111. data/lib/hecks/bluebook/ask_resolution.rb +17 -17
  112. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +8 -5
  113. data/lib/hecks/bluebook/assembly/contracts/structure.rb +11 -7
  114. data/lib/hecks/bluebook/assembly/marks.rb +10 -1
  115. data/lib/hecks/bluebook/assembly/scalars.rb +8 -2
  116. data/lib/hecks/bluebook/assembly/specializer.rb +2 -2
  117. data/lib/hecks/bluebook/assembly.rb +10 -10
  118. data/lib/hecks/bluebook/behaviour/aggregate.rb +2 -1
  119. data/lib/hecks/bluebook/behaviour/attribute.rb +12 -0
  120. data/lib/hecks/bluebook/behaviour/command.rb +1 -1
  121. data/lib/hecks/bluebook/behaviour/{chapter.rb → declaration.rb} +17 -17
  122. data/lib/hecks/bluebook/behaviour/domain_port.rb +1 -1
  123. data/lib/hecks/bluebook/behaviour/entity.rb +1 -0
  124. data/lib/hecks/bluebook/behaviour/hecksagon.rb +8 -1
  125. data/lib/hecks/bluebook/behaviour/process_manager.rb +4 -1
  126. data/lib/hecks/bluebook/behaviour/query.rb +5 -0
  127. data/lib/hecks/bluebook/behaviour/read_model.rb +4 -1
  128. data/lib/hecks/bluebook/behaviour/traits.rb +13 -0
  129. data/lib/hecks/bluebook/capabilities/attribute_values.rb +17 -16
  130. data/lib/hecks/bluebook/capabilities/package_declared.rb +65 -0
  131. data/lib/hecks/bluebook/capabilities.rb +32 -132
  132. data/lib/hecks/bluebook/command.rb +22 -7
  133. data/lib/hecks/bluebook/context_map.rb +104 -0
  134. data/lib/hecks/bluebook/{chapter.rb → declaration.rb} +13 -13
  135. data/lib/hecks/bluebook/dsl/adapter_builder.rb +28 -4
  136. data/lib/hecks/bluebook/dsl/aggregate_builder/draining.rb +2 -2
  137. data/lib/hecks/bluebook/dsl/aggregate_builder/one_of_reference.rb +54 -0
  138. data/lib/hecks/bluebook/dsl/aggregate_builder/query_field_sealing.rb +11 -2
  139. data/lib/hecks/bluebook/dsl/aggregate_builder/query_sealing.rb +15 -5
  140. data/lib/hecks/bluebook/dsl/aggregate_builder/relationships.rb +21 -3
  141. data/lib/hecks/bluebook/dsl/aggregate_builder/rules.rb +22 -22
  142. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +2 -2
  143. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +27 -22
  144. data/lib/hecks/bluebook/dsl/bluebook_builder/pending_givens.rb +22 -22
  145. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/computed_sources.rb +27 -21
  146. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/correlation_keys.rb +3 -5
  147. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/event_shapes.rb +5 -4
  148. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/fan_out_rows.rb +3 -3
  149. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/hop_tails.rb +13 -5
  150. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/projected_fields.rb +4 -4
  151. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/provisions.rb +38 -84
  152. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/query_hops.rb +5 -5
  153. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/references.rb +6 -6
  154. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/with_projections.rb +6 -6
  155. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +3 -3
  156. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +67 -67
  157. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
  158. data/lib/hecks/bluebook/dsl/command_builder/folded_sets.rb +48 -0
  159. data/lib/hecks/bluebook/dsl/command_builder.rb +28 -15
  160. data/lib/hecks/bluebook/dsl/context_map_builder.rb +73 -0
  161. data/lib/hecks/bluebook/dsl/entity_builder/rules.rb +17 -17
  162. data/lib/hecks/bluebook/dsl/entity_builder/scope.rb +3 -3
  163. data/lib/hecks/bluebook/dsl/entity_builder/sealing.rb +2 -2
  164. data/lib/hecks/bluebook/dsl/entity_builder.rb +3 -3
  165. data/lib/hecks/bluebook/dsl/hecksagon_builder/{chapter_scope.rb → bluebook_scope.rb} +14 -14
  166. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +33 -27
  167. data/lib/hecks/bluebook/dsl/identity_declaration.rb +6 -2
  168. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +4 -1
  169. data/lib/hecks/bluebook/dsl/query_builder.rb +14 -8
  170. data/lib/hecks/bluebook/dsl/read_model_builder/sealing.rb +6 -11
  171. data/lib/hecks/bluebook/dsl/read_model_builder.rb +1 -2
  172. data/lib/hecks/bluebook/dsl/word_gate.rb +2 -1
  173. data/lib/hecks/bluebook/dsl/world_builder.rb +2 -2
  174. data/lib/hecks/bluebook/dsl.rb +3 -2
  175. data/lib/hecks/bluebook/expression/ast_json/emitters.rb +4 -0
  176. data/lib/hecks/bluebook/expression/ast_json.rb +5 -3
  177. data/lib/hecks/bluebook/expression/ast_reader/blocks.rb +41 -0
  178. data/lib/hecks/bluebook/expression/ast_reader.rb +2 -10
  179. data/lib/hecks/bluebook/expression/canonical_form.rb +9 -4
  180. data/lib/hecks/bluebook/expression/evaluator/parser.rb +4 -1
  181. data/lib/hecks/bluebook/expression/evaluator.rb +13 -9
  182. data/lib/hecks/bluebook/expression/projection.json +6 -0
  183. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +50 -2
  184. data/lib/hecks/bluebook/expression/resolver/lookups.rb +2 -1
  185. data/lib/hecks/bluebook/expression/resolver/operations.rb +1 -0
  186. data/lib/hecks/bluebook/expression/resolver/parsing.rb +21 -15
  187. data/lib/hecks/bluebook/expression/resolver/scanning.rb +4 -1
  188. data/lib/hecks/bluebook/expression.rb +1 -1
  189. data/lib/hecks/bluebook/hecksagon.rb +48 -23
  190. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +4 -1
  191. data/lib/hecks/bluebook/meta_validator/judge/cells.rb +1 -1
  192. data/lib/hecks/bluebook/meta_validator/judge/identity.rb +2 -1
  193. data/lib/hecks/bluebook/meta_validator/modes.rb +27 -27
  194. data/lib/hecks/bluebook/meta_validator/plan.rb +5 -5
  195. data/lib/hecks/bluebook/meta_validator/readings/fields.rb +3 -3
  196. data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +11 -3
  197. data/lib/hecks/bluebook/meta_validator/reconstruction/reading.rb +1 -1
  198. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +19 -19
  199. data/lib/hecks/bluebook/meta_validator/shapes.rb +6 -1
  200. data/lib/hecks/bluebook/meta_validator/syntax_boot/disk_cache.rb +18 -15
  201. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +21 -15
  202. data/lib/hecks/bluebook/meta_validator/verdict_cache/prebuilt.rb +4 -1
  203. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +11 -10
  204. data/lib/hecks/bluebook/meta_validator.rb +25 -22
  205. data/lib/hecks/bluebook/model_check/aggregate_checks.rb +68 -0
  206. data/lib/hecks/bluebook/model_check/ask_checks.rb +6 -5
  207. data/lib/hecks/bluebook/model_check/client_profile.rb +8 -42
  208. data/lib/hecks/bluebook/model_check/context_checks.rb +97 -0
  209. data/lib/hecks/bluebook/model_check/context_map_checks.rb +158 -0
  210. data/lib/hecks/bluebook/model_check/lifecycle_checks.rb +27 -5
  211. data/lib/hecks/bluebook/model_check/one_of_checks.rb +106 -0
  212. data/lib/hecks/bluebook/model_check/orphan_checks.rb +129 -0
  213. data/lib/hecks/bluebook/model_check/redundant_relationships.rb +38 -0
  214. data/lib/hecks/bluebook/model_check/reference_checks.rb +11 -1
  215. data/lib/hecks/bluebook/model_check/saga_checks.rb +8 -3
  216. data/lib/hecks/bluebook/model_check/verbs.rb +4 -4
  217. data/lib/hecks/bluebook/model_check.rb +32 -12
  218. data/lib/hecks/bluebook/named_pattern.rb +12 -1
  219. data/lib/hecks/bluebook/one_of_union.rb +35 -0
  220. data/lib/hecks/bluebook/pattern_subset.rb +12 -3
  221. data/lib/hecks/bluebook/project_loader.rb +3 -2
  222. data/lib/hecks/bluebook/project_register.rb +21 -5
  223. data/lib/hecks/bluebook/query.rb +4 -1
  224. data/lib/hecks/bluebook/read_model.rb +5 -5
  225. data/lib/hecks/bluebook/reference.rb +3 -3
  226. data/lib/hecks/bluebook/smoke_test.rb +15 -13
  227. data/lib/hecks/bluebook/synthesizer.rb +48 -13
  228. data/lib/hecks/bluebook/translation_expression/parser.rb +14 -5
  229. data/lib/hecks/bluebook.rb +6 -3
  230. data/lib/hecks/bulk/bluebook/adapters/row_sweeper.adapter +7 -0
  231. data/lib/hecks/bulk/bluebook/bulk.behaviors +18 -0
  232. data/lib/hecks/bulk/bluebook/bulk.bluebook +126 -0
  233. data/lib/hecks/bulk/bluebook/bulk.hecksagon +11 -0
  234. data/lib/hecks/bulk/bluebook/bulk.ports.hecksagon +13 -0
  235. data/lib/hecks/bulk/plan.rb +60 -0
  236. data/lib/hecks/bulk/row_sweeper.rb +34 -0
  237. data/lib/hecks/bulk/set_arguments.rb +68 -0
  238. data/lib/hecks/bulk/set_eligibility.rb +50 -0
  239. data/lib/hecks/bulk/set_planner.rb +100 -0
  240. data/lib/hecks/bulk/set_sweep.rb +78 -0
  241. data/lib/hecks/bulk/sweep.rb +67 -0
  242. data/lib/hecks/cli/check_era.rb +6 -6
  243. data/lib/hecks/cli/command_table.rb +2 -2
  244. data/lib/hecks/cli/console.rb +11 -6
  245. data/lib/hecks/cli/document.rb +14 -9
  246. data/lib/hecks/cli/domain_writer.rb +4 -1
  247. data/lib/hecks/cli/expected_era.rb +139 -0
  248. data/lib/hecks/cli/expression_projection.rb +2 -2
  249. data/lib/hecks/cli/follow.rb +3 -3
  250. data/lib/hecks/cli/history.rb +4 -1
  251. data/lib/hecks/cli/interview_draft.rb +8 -1
  252. data/lib/hecks/cli/interview_record.rb +7 -3
  253. data/lib/hecks/cli/interview_run.rb +7 -5
  254. data/lib/hecks/cli/interview_session/proposals.rb +5 -2
  255. data/lib/hecks/cli/interview_session.rb +3 -2
  256. data/lib/hecks/cli/interview_shape/lifecycle.rb +92 -0
  257. data/lib/hecks/cli/interview_shape.rb +19 -73
  258. data/lib/hecks/cli/model_check/examination.rb +63 -14
  259. data/lib/hecks/cli/model_check/sources.rb +4 -2
  260. data/lib/hecks/cli/model_check.rb +1 -1
  261. data/lib/hecks/cli/persistence_notice.rb +45 -0
  262. data/lib/hecks/cli/present.rb +1 -1
  263. data/lib/hecks/cli/project_cli/launcher_source.rb +11 -9
  264. data/lib/hecks/cli/project_cli/templates.rb +24 -3
  265. data/lib/hecks/cli/project_cli.rb +11 -13
  266. data/lib/hecks/cli/project_diagrams.rb +20 -20
  267. data/lib/hecks/cli/project_glossary.rb +14 -14
  268. data/lib/hecks/cli/reference.rb +1 -1
  269. data/lib/hecks/cli/refresh_projections.rb +16 -11
  270. data/lib/hecks/cli/run.rb +27 -5
  271. data/lib/hecks/cli/seed_semantics_corpus.rb +14 -4
  272. data/lib/hecks/cli/shape.rb +3 -4
  273. data/lib/hecks/cli/smoke_http/transport.rb +3 -2
  274. data/lib/hecks/cli/statements.rb +7 -7
  275. data/lib/hecks/cli/stores.rb +70 -2
  276. data/lib/hecks/cli/stress_concurrency_specs/reporting.rb +4 -1
  277. data/lib/hecks/codemod.rb +3 -3
  278. data/lib/hecks/composer/adapters/claude_composer.rb +3 -2
  279. data/lib/hecks/composer/agent.rb +1 -1
  280. data/lib/hecks/composer/assistant.rb +2 -2
  281. data/lib/hecks/composer/behavior_run.rb +7 -1
  282. data/lib/hecks/composer/bluebook/composer.bluebook +18 -25
  283. data/lib/hecks/composer/bluebook/composer.hecksagon +1 -1
  284. data/lib/hecks/composer/board.rb +6 -6
  285. data/lib/hecks/composer/board_edit/command_text.rb +4 -1
  286. data/lib/hecks/composer/board_edit/source.rb +18 -9
  287. data/lib/hecks/composer/board_edit.rb +2 -2
  288. data/lib/hecks/composer/check.rb +22 -22
  289. data/lib/hecks/composer/domains.rb +1 -1
  290. data/lib/hecks/composer/live.rb +19 -13
  291. data/lib/hecks/composer/outline.rb +4 -1
  292. data/lib/hecks/composer/page.html +1 -1
  293. data/lib/hecks/composer/panels.rb +6 -3
  294. data/lib/hecks/composer/parse.rb +53 -36
  295. data/lib/hecks/composer/session.rb +22 -16
  296. data/lib/hecks/composer/toolbox.rb +9 -6
  297. data/lib/hecks/composer/web.rb +3 -2
  298. data/lib/hecks/composer/workbench.rb +12 -9
  299. data/lib/hecks/composer.rb +1 -1
  300. data/lib/hecks/construct.rb +7 -7
  301. data/lib/hecks/corpus/adapters.rb +2 -1
  302. data/lib/hecks/corpus/routes.rb +25 -5
  303. data/lib/hecks/corpus/rust.rb +59 -24
  304. data/lib/hecks/corpus/sources.rb +12 -11
  305. data/lib/hecks/corpus/worlds.rb +2 -1
  306. data/lib/hecks/corpus.rb +30 -22
  307. data/lib/hecks/deploy/bluebook/deploy.bluebook +304 -518
  308. data/lib/hecks/doc/reference/harvester.rb +4 -1
  309. data/lib/hecks/doc/reference/readme.rb +11 -6
  310. data/lib/hecks/doc/reference/rendering.rb +11 -5
  311. data/lib/hecks/doc/reference.rb +7 -5
  312. data/lib/hecks/embryonaut_bluebook/lock.rb +5 -1
  313. data/lib/hecks/embryonaut_bluebook/manifest.rb +7 -1
  314. data/lib/hecks/embryonaut_bluebook/registry/changelog.rb +4 -1
  315. data/lib/hecks/embryonaut_bluebook/registry/versions.rb +13 -2
  316. data/lib/hecks/embryonaut_bluebook/registry.rb +5 -2
  317. data/lib/hecks/embryonaut_bluebook/shape.rb +1 -1
  318. data/lib/hecks/embryonaut_bluebook/vendor.rb +5 -2
  319. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +15 -1
  320. data/lib/hecks/embryonaut_bluebook.rb +26 -12
  321. data/lib/hecks/engine_agreement.rb +4 -1
  322. data/lib/hecks/forms/app/commands.rb +35 -2
  323. data/lib/hecks/forms/app/records.rb +12 -12
  324. data/lib/hecks/forms/app/responses.rb +1 -1
  325. data/lib/hecks/forms/app.rb +22 -19
  326. data/lib/hecks/forms/command_form_renderer.rb +2 -2
  327. data/lib/hecks/forms/field_controls.rb +6 -1
  328. data/lib/hecks/forms/field_shape.rb +14 -6
  329. data/lib/hecks/forms/html.rb +8 -7
  330. data/lib/hecks/forms/html_pattern.rb +80 -0
  331. data/lib/hecks/forms/index_renderer.rb +11 -11
  332. data/lib/hecks/forms/params.rb +2 -1
  333. data/lib/hecks/forms/port_argument.rb +4 -1
  334. data/lib/hecks/forms/query_form_renderer.rb +1 -1
  335. data/lib/hecks/forms/record_renderer.rb +2 -2
  336. data/lib/hecks/forms/record_table.rb +1 -1
  337. data/lib/hecks/forms/reference_options.rb +2 -1
  338. data/lib/hecks/forms/text_shape.rb +4 -1
  339. data/lib/hecks/forms/value_object_shape.rb +1 -1
  340. data/lib/hecks/forms.rb +7 -7
  341. data/lib/hecks/fqn.rb +8 -2
  342. data/lib/hecks/framework/bluebook/change.bluebook +10 -15
  343. data/lib/hecks/framework/bluebook/change.hecksagon +1 -1
  344. data/lib/hecks/framework/bluebook/compliance.bluebook +27 -126
  345. data/lib/hecks/framework/bluebook/console_settings.bluebook +199 -114
  346. data/lib/hecks/framework/bluebook/framework.hecksagon +7 -0
  347. data/lib/hecks/framework/bluebook/governance.bluebook +36 -14
  348. data/lib/hecks/framework/bluebook/identity.bluebook +1 -1
  349. data/lib/hecks/framework/bluebook/privacy.bluebook +123 -19
  350. data/lib/hecks/framework/bluebook/privacy.key_keeper.adapter +7 -0
  351. data/lib/hecks/framework/bluebook/privacy.ports.hecksagon +19 -0
  352. data/lib/hecks/framework/compliance.oidc.json +6 -36
  353. data/lib/hecks/framework/console_settings.oidc.json +39 -24
  354. data/lib/hecks/framework/privacy.oidc.json +25 -0
  355. data/lib/hecks/framework.rb +24 -8
  356. data/lib/hecks/framework_bluebooks.rb +156 -0
  357. data/lib/hecks/freezer.rb +7 -1
  358. data/lib/hecks/fuzzing/combination_miner.rb +9 -6
  359. data/lib/hecks/fuzzing/concurrent_dispatch/world_copy.rb +3 -1
  360. data/lib/hecks/fuzzing/concurrent_dispatch.rb +2 -2
  361. data/lib/hecks/fuzzing/domain_generator.rb +8 -3
  362. data/lib/hecks/fuzzing/era_boundary.rb +1 -1
  363. data/lib/hecks/fuzzing/failure_triage.rb +13 -6
  364. data/lib/hecks/fuzzing/form_census/{chapter_forms.rb → bluebook_forms.rb} +12 -12
  365. data/lib/hecks/fuzzing/form_census.rb +14 -12
  366. data/lib/hecks/fuzzing/generated_domain_check.rb +2 -1
  367. data/lib/hecks/fuzzing/isolated_boot/postgres_scratch.rb +5 -5
  368. data/lib/hecks/fuzzing/isolated_boot/scratch_schema.rb +19 -9
  369. data/lib/hecks/fuzzing/isolated_boot/worlds.rb +13 -8
  370. data/lib/hecks/fuzzing/isolated_boot.rb +4 -5
  371. data/lib/hecks/fuzzing/mutation/checks.rb +2 -1
  372. data/lib/hecks/fuzzing/mutation/finders.rb +25 -8
  373. data/lib/hecks/fuzzing/mutation/observation.rb +3 -1
  374. data/lib/hecks/fuzzing/mutation/source_map.rb +3 -1
  375. data/lib/hecks/fuzzing/nondeterministic.rb +10 -4
  376. data/lib/hecks/fuzzing/outside_world.rb +1 -1
  377. data/lib/hecks/fuzzing/projection_agreement/binding.rb +63 -0
  378. data/lib/hecks/fuzzing/projection_agreement/comparison.rb +104 -0
  379. data/lib/hecks/fuzzing/projection_agreement.rb +68 -0
  380. data/lib/hecks/fuzzing/properties/aggregation_recompute.rb +1 -1
  381. data/lib/hecks/fuzzing/properties/bulk_parity.rb +118 -0
  382. data/lib/hecks/fuzzing/properties/catalog.rb +7 -0
  383. data/lib/hecks/fuzzing/properties/cursor_walks.rb +56 -0
  384. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +39 -1
  385. data/lib/hecks/fuzzing/properties/group_by_recompute.rb +6 -2
  386. data/lib/hecks/fuzzing/properties/grouped_reduction_recompute.rb +88 -0
  387. data/lib/hecks/fuzzing/properties/guards.rb +4 -1
  388. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +12 -2
  389. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +2 -1
  390. data/lib/hecks/fuzzing/properties/mutation_recompute.rb +3 -2
  391. data/lib/hecks/fuzzing/properties/piece_invariants.rb +3 -2
  392. data/lib/hecks/fuzzing/properties/query_rows.rb +5 -3
  393. data/lib/hecks/fuzzing/properties/querying.rb +6 -3
  394. data/lib/hecks/fuzzing/properties/read_model_names.rb +8 -2
  395. data/lib/hecks/fuzzing/properties/read_models.rb +2 -1
  396. data/lib/hecks/fuzzing/properties/refusals.rb +35 -0
  397. data/lib/hecks/fuzzing/properties/role_checks.rb +3 -2
  398. data/lib/hecks/fuzzing/properties/set_transform_parity.rb +73 -0
  399. data/lib/hecks/fuzzing/properties/tenant_scope.rb +3 -2
  400. data/lib/hecks/fuzzing/properties.rb +6 -0
  401. data/lib/hecks/fuzzing/qa_settings.rb +4 -4
  402. data/lib/hecks/fuzzing/replay/fan_out.rb +4 -3
  403. data/lib/hecks/fuzzing/replay/filters.rb +2 -2
  404. data/lib/hecks/fuzzing/replay/guard_check.rb +2 -2
  405. data/lib/hecks/fuzzing/replay/mutation_trace.rb +23 -10
  406. data/lib/hecks/fuzzing/replay/observation.rb +11 -7
  407. data/lib/hecks/fuzzing/replay/read_only_steps.rb +40 -3
  408. data/lib/hecks/fuzzing/replay/refusal_trace.rb +44 -0
  409. data/lib/hecks/fuzzing/replay/role_check.rb +2 -2
  410. data/lib/hecks/fuzzing/replay/session.rb +38 -18
  411. data/lib/hecks/fuzzing/replay/validation_reports.rb +61 -0
  412. data/lib/hecks/fuzzing/replay.rb +2 -2
  413. data/lib/hecks/fuzzing/rust_gap_manifest.rb +3 -3
  414. data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +18 -8
  415. data/lib/hecks/fuzzing/self_consistency/sagas.rb +2 -2
  416. data/lib/hecks/fuzzing/self_consistency/value_objects.rb +9 -2
  417. data/lib/hecks/fuzzing/self_consistency.rb +4 -8
  418. data/lib/hecks/fuzzing/sequence_generator/adversary/argument_mutations.rb +15 -5
  419. data/lib/hecks/fuzzing/sequence_generator/adversary/caller_draw.rb +14 -10
  420. data/lib/hecks/fuzzing/sequence_generator/adversary/precedence_mutations.rb +1 -1
  421. data/lib/hecks/fuzzing/sequence_generator/argument_drawing.rb +2 -1
  422. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +21 -13
  423. data/lib/hecks/fuzzing/sequence_generator/picker.rb +1 -1
  424. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +3 -3
  425. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +2 -2
  426. data/lib/hecks/fuzzing/sequence_generator.rb +1 -2
  427. data/lib/hecks/fuzzing/shrinker.rb +4 -1
  428. data/lib/hecks/fuzzing/value_generator.rb +14 -8
  429. data/lib/hecks/fuzzing.rb +1 -0
  430. data/lib/hecks/grammar/dialects.rb +56 -0
  431. data/lib/hecks/grammar/evolve/arguments.rb +1 -1
  432. data/lib/hecks/grammar/evolve/keywords.rb +1 -1
  433. data/lib/hecks/grammar/evolve/seeds.rb +34 -8
  434. data/lib/hecks/grammar/expression.bluebook +3 -0
  435. data/lib/hecks/grammar/expression_operators.json +1071 -1
  436. data/lib/hecks/grammar/operators.rb +45 -22
  437. data/lib/hecks/grammar/translation.bluebook +2 -0
  438. data/lib/hecks/grammar/translation_ledger.rb +45 -0
  439. data/lib/hecks/grammar/translation_rules.json +646 -0
  440. data/lib/hecks/grammar.rb +27 -23
  441. data/lib/hecks/hecks/adapters/claude_agents.adapter +7 -0
  442. data/lib/hecks/hecks/adapters/claude_agents.rb +119 -0
  443. data/lib/hecks/hecks/adapters/claude_transcript.rb +71 -0
  444. data/lib/hecks/hecks/adapters/codebase/cache_clearing.rb +32 -0
  445. data/lib/hecks/hecks/adapters/codebase/cargo_tests.rb +50 -0
  446. data/lib/hecks/hecks/adapters/codebase/composing.rb +3 -2
  447. data/lib/hecks/hecks/adapters/codebase/conformance.rb +17 -1
  448. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +1 -1
  449. data/lib/hecks/hecks/adapters/codebase/declarations.rb +144 -0
  450. data/lib/hecks/hecks/adapters/codebase/inspection.rb +5 -4
  451. data/lib/hecks/hecks/adapters/codebase/language.rb +1 -0
  452. data/lib/hecks/hecks/adapters/codebase/npm_registry.rb +7 -1
  453. data/lib/hecks/hecks/adapters/codebase/previewing.rb +62 -0
  454. data/lib/hecks/hecks/adapters/codebase/promotion/gate.rb +1 -1
  455. data/lib/hecks/hecks/adapters/codebase/reading.rb +94 -0
  456. data/lib/hecks/hecks/adapters/codebase/regeneration.rb +6 -2
  457. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +1 -1
  458. data/lib/hecks/hecks/adapters/codebase/ruby_child.rb +5 -2
  459. data/lib/hecks/hecks/adapters/codebase/search.rb +91 -0
  460. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +37 -2
  461. data/lib/hecks/hecks/adapters/codebase/static_directory.rb +59 -0
  462. data/lib/hecks/hecks/adapters/codebase/style.rb +2 -2
  463. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +30 -11
  464. data/lib/hecks/hecks/adapters/codebase/test_suite.rb +41 -3
  465. data/lib/hecks/hecks/adapters/console_capture.rb +27 -0
  466. data/lib/hecks/hecks/adapters/deploy_plans.rb +12 -3
  467. data/lib/hecks/hecks/adapters/deploy_toolchain/answers.rb +14 -5
  468. data/lib/hecks/hecks/adapters/deploy_toolchain/scripts.rb +6 -3
  469. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +3 -3
  470. data/lib/hecks/hecks/adapters/desktop_alert/alert.rb +50 -0
  471. data/lib/hecks/hecks/adapters/desktop_alert.adapter +7 -0
  472. data/lib/hecks/hecks/adapters/desktop_alert.rb +99 -0
  473. data/lib/hecks/hecks/adapters/finding_github.rb +4 -1
  474. data/lib/hecks/hecks/adapters/first_admin.rb +16 -9
  475. data/lib/hecks/hecks/adapters/git.rb +5 -5
  476. data/lib/hecks/hecks/adapters/git_checkout.adapter +7 -0
  477. data/lib/hecks/hecks/adapters/git_checkout.rb +99 -0
  478. data/lib/hecks/hecks/adapters/git_landing/merger.rb +85 -0
  479. data/lib/hecks/hecks/adapters/git_landing.adapter +7 -0
  480. data/lib/hecks/hecks/adapters/git_landing.rb +103 -0
  481. data/lib/hecks/hecks/adapters/git_launcher.adapter +7 -0
  482. data/lib/hecks/hecks/adapters/git_launcher.rb +82 -0
  483. data/lib/hecks/hecks/adapters/git_refs.rb +8 -4
  484. data/lib/hecks/hecks/adapters/git_train/bench.rb +76 -0
  485. data/lib/hecks/hecks/adapters/git_train/carriage.rb +116 -0
  486. data/lib/hecks/hecks/adapters/git_train/local_branch.rb +69 -0
  487. data/lib/hecks/hecks/adapters/git_train/reading.rb +26 -0
  488. data/lib/hecks/hecks/adapters/git_train/survey.rb +60 -0
  489. data/lib/hecks/hecks/adapters/git_train.adapter +7 -0
  490. data/lib/hecks/hecks/adapters/git_train.rb +88 -0
  491. data/lib/hecks/hecks/adapters/git_worktrees.rb +81 -0
  492. data/lib/hecks/hecks/adapters/host_http.rb +5 -4
  493. data/lib/hecks/hecks/adapters/in_process_access.rb +3 -2
  494. data/lib/hecks/hecks/adapters/in_process_boot.rb +62 -35
  495. data/lib/hecks/hecks/adapters/in_process_operations.rb +30 -4
  496. data/lib/hecks/hecks/adapters/journal_store/changes.rb +7 -7
  497. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +14 -7
  498. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +9 -7
  499. data/lib/hecks/hecks/adapters/journal_store/examination.rb +3 -3
  500. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +17 -5
  501. data/lib/hecks/hecks/adapters/journal_store/readings/attestation.rb +3 -3
  502. data/lib/hecks/hecks/adapters/journal_store/readings/scaffolding.rb +7 -4
  503. data/lib/hecks/hecks/adapters/journal_store/readings.rb +2 -2
  504. data/lib/hecks/hecks/adapters/journal_store.rb +2 -2
  505. data/lib/hecks/hecks/adapters/ledger/chain.rb +63 -0
  506. data/lib/hecks/hecks/adapters/ledger/journal.rb +105 -0
  507. data/lib/hecks/hecks/adapters/ledger.adapter +7 -0
  508. data/lib/hecks/hecks/adapters/ledger.rb +89 -0
  509. data/lib/hecks/hecks/adapters/local_files.rb +15 -1
  510. data/lib/hecks/hecks/adapters/pg_admin.rb +7 -1
  511. data/lib/hecks/hecks/adapters/rails_app_files.rb +64 -0
  512. data/lib/hecks/hecks/adapters/rehearsal/audit_report.rb +4 -1
  513. data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +9 -9
  514. data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +2 -2
  515. data/lib/hecks/hecks/adapters/rehearsal/postgres_census.rb +4 -4
  516. data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +3 -3
  517. data/lib/hecks/hecks/adapters/rehearsal/postgres_store.rb +4 -1
  518. data/lib/hecks/hecks/adapters/rehearsal/rust_audit.rb +1 -1
  519. data/lib/hecks/hecks/adapters/rehearsal/subject.rb +34 -5
  520. data/lib/hecks/hecks/adapters/rust_toolchain.rb +5 -3
  521. data/lib/hecks/hecks/adapters/settings_reading.adapter +7 -0
  522. data/lib/hecks/hecks/adapters/settings_reading.rb +142 -0
  523. data/lib/hecks/hecks/adapters/shell.rb +3 -0
  524. data/lib/hecks/hecks/adapters/site_toolchain.rb +8 -8
  525. data/lib/hecks/hecks/adapters/world_clearing.rb +2 -1
  526. data/lib/hecks/hecks/agents.bluebook +246 -0
  527. data/lib/hecks/hecks/codebase.behaviors +13 -1
  528. data/lib/hecks/hecks/codebase.bluebook +554 -586
  529. data/lib/hecks/hecks/composer.behaviors +10 -10
  530. data/lib/hecks/hecks/context_map.hecksagon +7 -6
  531. data/lib/hecks/hecks/custodian.behaviors +96 -1
  532. data/lib/hecks/hecks/custodian.bluebook +553 -157
  533. data/lib/hecks/hecks/deploy.behaviors +1 -1
  534. data/lib/hecks/hecks/deploy.world +3 -4
  535. data/lib/hecks/hecks/hecks.behaviors +2 -2
  536. data/lib/hecks/hecks/hecks.bluebook +9 -14
  537. data/lib/hecks/hecks/hecks.contextmap +25 -0
  538. data/lib/hecks/hecks/hecks.hecksagon +139 -6
  539. data/lib/hecks/hecks/hecks.world +22 -21
  540. data/lib/hecks/hecks/ledger.bluebook +190 -0
  541. data/lib/hecks/hecks/rehearsal.bluebook +43 -77
  542. data/lib/hecks/hecks/shapes.json +17 -0
  543. data/lib/hecks/hecks/site.behaviors +1 -1
  544. data/lib/hecks/hecks/site.world +3 -3
  545. data/lib/hecks/hecks/tooling.bluebook +170 -174
  546. data/lib/hecks/hecks/translations/2-148748.bluebook +2 -0
  547. data/lib/hecks/hecks/translations/2-15a2e4.bluebook +2 -0
  548. data/lib/hecks/hecks/translations/2-1b447c.bluebook +5 -0
  549. data/lib/hecks/hecks/translations/2-240474.bluebook +2 -0
  550. data/lib/hecks/hecks/translations/2-44446d.bluebook +2 -0
  551. data/lib/hecks/hecks/translations/2-46ca26.bluebook +2 -0
  552. data/lib/hecks/hecks/translations/2-473eab.bluebook +2 -0
  553. data/lib/hecks/hecks/translations/2-75cc28.bluebook +2 -0
  554. data/lib/hecks/hecks/translations/2-781c07.bluebook +2 -0
  555. data/lib/hecks/hecks/translations/2-84c18b.bluebook +2 -0
  556. data/lib/hecks/hecks/translations/2-aa5fff.bluebook +2 -0
  557. data/lib/hecks/hecks/translations/2-bfdb48.bluebook +2 -0
  558. data/lib/hecks/hecks/translations/2-cd96d8.bluebook +10 -0
  559. data/lib/hecks/hecks/translations/2-d43f71.bluebook +2 -0
  560. data/lib/hecks/hecks/translations/2-eea577.bluebook +2 -0
  561. data/lib/hecks/hecks/translations/2-f53248.bluebook +2 -0
  562. data/lib/hecks/hecks/translations/2-fbb677.bluebook +2 -0
  563. data/lib/hecks/hecks/translations/2-ffdf12.bluebook +6 -0
  564. data/lib/hecks/ir.rb +8 -1
  565. data/lib/hecks/kernel_capabilities/templates.rb +4 -3
  566. data/lib/hecks/kernel_capabilities.rb +6 -6
  567. data/lib/hecks/language/adapter.bluebook +61 -0
  568. data/lib/hecks/language/bluebook/aggregate.bluebook +36 -6
  569. data/lib/hecks/language/bluebook/attaches/paging.bluebook +1 -1
  570. data/lib/hecks/language/bluebook/bluebook.behaviors +6 -6
  571. data/lib/hecks/language/bluebook/bluebook.bluebook +28 -51
  572. data/lib/hecks/language/bluebook/bluebook.hecksagon +1 -1
  573. data/lib/hecks/language/bluebook/command.bluebook +34 -2
  574. data/lib/hecks/language/bluebook/entity.bluebook +2 -2
  575. data/lib/hecks/language/bluebook/policy.behaviors +2 -2
  576. data/lib/hecks/language/bluebook/policy.bluebook +3 -3
  577. data/lib/hecks/language/bluebook/process_manager.bluebook +3 -3
  578. data/lib/hecks/language/bluebook/projection.bluebook +11 -10
  579. data/lib/hecks/language/bluebook/query.bluebook +1 -0
  580. data/lib/hecks/language/bluebook/shape.bluebook +1 -1
  581. data/lib/hecks/language/bluebook/syntax.bluebook +3 -1
  582. data/lib/hecks/language/bluebook/vocabulary.bluebook +287 -6
  583. data/lib/hecks/language/context_map/context_map.bluebook +90 -0
  584. data/lib/hecks/language/hecksagon/domain_port.bluebook +2 -2
  585. data/lib/hecks/language/hecksagon/hecksagon.bluebook +8 -8
  586. data/lib/hecks/language/oidc.json +10 -0
  587. data/lib/hecks/language/port.bluebook +1 -1
  588. data/lib/hecks/language/sentence/sentence.bluebook +210 -0
  589. data/lib/hecks/language/world/world.bluebook +6 -6
  590. data/lib/hecks/literal.rb +8 -2
  591. data/lib/hecks/naming.rb +44 -14
  592. data/lib/hecks/persistence_legacy_fixture/postgres_stores.rb +7 -6
  593. data/lib/hecks/persistence_legacy_fixture/regenerate.rb +6 -2
  594. data/lib/hecks/ports/access_control.rb +2 -2
  595. data/lib/hecks/ports/agent/answers.rb +2 -2
  596. data/lib/hecks/ports/agent.rb +2 -2
  597. data/lib/hecks/ports/authentication.port +6 -0
  598. data/lib/hecks/ports/authentication.rb +67 -0
  599. data/lib/hecks/ports/authorization.rb +3 -3
  600. data/lib/hecks/ports/dispatch.port +5 -0
  601. data/lib/hecks/ports/dispatch.rb +28 -0
  602. data/lib/hecks/ports/key_vault.rb +9 -6
  603. data/lib/hecks/ports/loading.rb +23 -7
  604. data/lib/hecks/ports/persistence/append_only.rb +18 -1
  605. data/lib/hecks/ports/persistence/binding_policy.rb +70 -11
  606. data/lib/hecks/ports/persistence/codec_boundary.rb +3 -2
  607. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +10 -10
  608. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +4 -5
  609. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +11 -11
  610. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +1 -1
  611. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +6 -6
  612. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -3
  613. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +1 -1
  614. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +32 -19
  615. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +2 -2
  616. data/lib/hecks/ports/persistence/plugins/era.rb +0 -1
  617. data/lib/hecks/ports/persistence/remote_runtime.rb +3 -2
  618. data/lib/hecks/ports/persistence/set_statement.rb +29 -0
  619. data/lib/hecks/ports/persistence/set_transforming.rb +30 -0
  620. data/lib/hecks/ports/persistence/state_codec.rb +3 -3
  621. data/lib/hecks/ports/persistence.rb +0 -4
  622. data/lib/hecks/ports/projection.rb +4 -1
  623. data/lib/hecks/ports/query/cursor.rb +131 -0
  624. data/lib/hecks/ports/query/in_memory.rb +17 -10
  625. data/lib/hecks/ports/query.rb +4 -2
  626. data/lib/hecks/ports.rb +1 -0
  627. data/lib/hecks/projection_files/expression_tables.rb +3 -3
  628. data/lib/hecks/projection_files/sentence_table.rb +17 -0
  629. data/lib/hecks/projection_files.rb +16 -13
  630. data/lib/hecks/projections/bootstrap_table.rb +3 -3
  631. data/lib/hecks/projections/command_schema/shape.rb +95 -0
  632. data/lib/hecks/projections/command_schema.rb +74 -0
  633. data/lib/hecks/projections/context_map_diagram.rb +135 -0
  634. data/lib/hecks/projections/context_relationships.rb +125 -0
  635. data/lib/hecks/projections/deploy/box/access.rb +7 -4
  636. data/lib/hecks/projections/deploy/box/caddy.rb +2 -1
  637. data/lib/hecks/projections/deploy/box/compose.rb +4 -8
  638. data/lib/hecks/projections/deploy/box/edge.rb +2 -1
  639. data/lib/hecks/projections/deploy/box/hosting.rb +4 -2
  640. data/lib/hecks/projections/deploy/box/hosting_parts.rb +27 -12
  641. data/lib/hecks/projections/deploy/box/logging.rb +2 -3
  642. data/lib/hecks/projections/deploy/box/roll_recipe.rb +1 -3
  643. data/lib/hecks/projections/deploy/box/roll_scripts.rb +1 -16
  644. data/lib/hecks/projections/deploy/box/settings/containers.rb +29 -15
  645. data/lib/hecks/projections/deploy/box/settings/patterns.rb +5 -6
  646. data/lib/hecks/projections/deploy/box/settings/types.rb +5 -10
  647. data/lib/hecks/projections/deploy/box/settings.rb +9 -8
  648. data/lib/hecks/projections/deploy/box/stack_yaml.rb +10 -14
  649. data/lib/hecks/projections/deploy/box/templates/box.yaml.tmpl +0 -1
  650. data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +16 -3
  651. data/lib/hecks/projections/deploy/box/templates/hosting.mk.tmpl +7 -6
  652. data/lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl +2 -6
  653. data/lib/hecks/projections/deploy/box/templates/render-compose.sh.tmpl +2 -6
  654. data/lib/hecks/projections/deploy/box.rb +16 -9
  655. data/lib/hecks/projections/deploy/fargate/assembly.rb +7 -2
  656. data/lib/hecks/projections/deploy/fargate/cdn/options.rb +4 -1
  657. data/lib/hecks/projections/deploy/fargate/check.rb +3 -1
  658. data/lib/hecks/projections/deploy/fargate/containers/rendering.rb +8 -5
  659. data/lib/hecks/projections/deploy/fargate/containers/routing.rb +2 -1
  660. data/lib/hecks/projections/deploy/fargate/monitoring/reading.rb +4 -1
  661. data/lib/hecks/projections/deploy/fargate/sections.rb +7 -4
  662. data/lib/hecks/projections/deploy/fargate/settings/ids.rb +4 -1
  663. data/lib/hecks/projections/deploy/fargate/settings/tuning.rb +13 -4
  664. data/lib/hecks/projections/deploy/fargate/stack/blocks.rb +6 -1
  665. data/lib/hecks/projections/deploy/fargate/stack/readers.rb +2 -1
  666. data/lib/hecks/projections/deploy/fargate/stack.rb +11 -6
  667. data/lib/hecks/projections/deploy/fargate/yaml.rb +10 -3
  668. data/lib/hecks/projections/deploy/fargate.rb +8 -3
  669. data/lib/hecks/projections/deploy/lambda/stack/blocks.rb +4 -2
  670. data/lib/hecks/projections/deploy/lambda/stack/deploy_chain.rb +2 -1
  671. data/lib/hecks/projections/deploy/lambda/stack/reading.rb +12 -9
  672. data/lib/hecks/projections/deploy/lambda/stack/reading_web.rb +7 -2
  673. data/lib/hecks/projections/deploy/lambda/stack/web.rb +2 -1
  674. data/lib/hecks/projections/deploy/lambda.rb +8 -3
  675. data/lib/hecks/projections/deploy/preview/containers.rb +14 -1
  676. data/lib/hecks/projections/deploy/preview/preview.sh.erb +2 -2
  677. data/lib/hecks/projections/deploy/preview/routing_yaml.rb +8 -6
  678. data/lib/hecks/projections/deploy/preview/settings.rb +23 -5
  679. data/lib/hecks/projections/deploy/preview/task_yaml.rb +1 -1
  680. data/lib/hecks/projections/deploy/preview/yaml_text.rb +2 -1
  681. data/lib/hecks/projections/deploy/scripts/settings.rb +5 -2
  682. data/lib/hecks/projections/deploy/shared.rb +12 -1
  683. data/lib/hecks/projections/deploy/shared_database_instance.rb +1 -1
  684. data/lib/hecks/projections/deploy/smoke.rb +3 -1
  685. data/lib/hecks/projections/deploy/template_diff/normalizer.rb +4 -1
  686. data/lib/hecks/projections/deploy/templates/lambda/mint_era_shared.tmpl +2 -2
  687. data/lib/hecks/projections/deploy/templates/lambda/nat_comment_oauth.tmpl +1 -1
  688. data/lib/hecks/projections/deploy/vercel.rb +1 -1
  689. data/lib/hecks/projections/diagrams/dispatch.rb +5 -3
  690. data/lib/hecks/projections/diagrams/statecharts.rb +8 -3
  691. data/lib/hecks/projections/diagrams/structure.rb +9 -6
  692. data/lib/hecks/projections/diagrams.rb +9 -7
  693. data/lib/hecks/projections/glossary/entries.rb +4 -4
  694. data/lib/hecks/projections/glossary/html/page.rb +4 -1
  695. data/lib/hecks/projections/glossary/html/parser.rb +13 -4
  696. data/lib/hecks/projections/glossary/html/sections.rb +4 -1
  697. data/lib/hecks/projections/glossary/html.rb +10 -3
  698. data/lib/hecks/projections/glossary/markdown.rb +4 -3
  699. data/lib/hecks/projections/glossary/mermaid.rb +8 -5
  700. data/lib/hecks/projections/glossary/sections.rb +1 -1
  701. data/lib/hecks/projections/glossary/sensitivity.rb +11 -4
  702. data/lib/hecks/projections/glossary/sentences/actions.rb +5 -2
  703. data/lib/hecks/projections/glossary/sentences/queries.rb +2 -1
  704. data/lib/hecks/projections/glossary/sentences/value_objects.rb +15 -6
  705. data/lib/hecks/projections/glossary/sentences.rb +6 -3
  706. data/lib/hecks/projections/glossary/slugs.rb +8 -1
  707. data/lib/hecks/projections/glossary.rb +3 -3
  708. data/lib/hecks/projections/model.rb +10 -6
  709. data/lib/hecks/projections/oidc.rb +19 -7
  710. data/lib/hecks/projections/parser_table.rb +10 -10
  711. data/lib/hecks/projections/phrase_table.rb +105 -0
  712. data/lib/hecks/projections/plain_english.rb +14 -4
  713. data/lib/hecks/projections/rails_app.rb +77 -0
  714. data/lib/hecks/projections/reference.rb +2 -2
  715. data/lib/hecks/projections/rust_vocabulary/arguments.rb +1 -1
  716. data/lib/hecks/projections/rust_vocabulary/tables.rb +20 -0
  717. data/lib/hecks/projections/rust_vocabulary.rb +14 -17
  718. data/lib/hecks/projections/site/admin.rb +7 -7
  719. data/lib/hecks/projections/site/admin_cms.rb +1 -1
  720. data/lib/hecks/projections/site/cms_editor/blocks.rb +5 -2
  721. data/lib/hecks/projections/site/cms_editor/checks.rb +9 -6
  722. data/lib/hecks/projections/site/cms_editor/color.rb +4 -1
  723. data/lib/hecks/projections/site/cms_editor/composition.rb +20 -18
  724. data/lib/hecks/projections/site/cms_editor/destructive.rb +4 -1
  725. data/lib/hecks/projections/site/cms_editor/drafts.rb +6 -2
  726. data/lib/hecks/projections/site/cms_editor/key_kinds.rb +4 -1
  727. data/lib/hecks/projections/site/cms_editor/limits.rb +1 -1
  728. data/lib/hecks/projections/site/cms_editor/media.rb +1 -1
  729. data/lib/hecks/projections/site/cms_editor/moment.rb +3 -1
  730. data/lib/hecks/projections/site/cms_editor/pickers.rb +1 -1
  731. data/lib/hecks/projections/site/cms_editor/preview.rb +10 -4
  732. data/lib/hecks/projections/site/cms_editor/relations.rb +3 -2
  733. data/lib/hecks/projections/site/cms_editor/scale_checks.rb +32 -25
  734. data/lib/hecks/projections/site/cms_editor/scheduling.rb +1 -1
  735. data/lib/hecks/projections/site/cms_editor/schema.rb +31 -29
  736. data/lib/hecks/projections/site/cms_editor/setting.rb +34 -27
  737. data/lib/hecks/projections/site/cms_editor/targets.rb +9 -6
  738. data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +3 -3
  739. data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +2 -2
  740. data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +1 -1
  741. data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +4 -4
  742. data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +14 -14
  743. data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +12 -12
  744. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +12 -12
  745. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +1 -1
  746. data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +2 -2
  747. data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +11 -11
  748. data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +3 -3
  749. data/lib/hecks/projections/site/cms_editor/theme_checks.rb +8 -2
  750. data/lib/hecks/projections/site/cms_editor.rb +19 -16
  751. data/lib/hecks/projections/site/edge/checks.rb +1 -1
  752. data/lib/hecks/projections/site/edge/function_checks.rb +1 -1
  753. data/lib/hecks/projections/site/edge/function_code.rb +4 -1
  754. data/lib/hecks/projections/site/edge/pattern.rb +4 -1
  755. data/lib/hecks/projections/site/edge/route_checks.rb +2 -2
  756. data/lib/hecks/projections/site/edge/rules.rb +1 -1
  757. data/lib/hecks/projections/site/edge.rb +14 -8
  758. data/lib/hecks/projections/site/live_edge.rb +2 -2
  759. data/lib/hecks/projections/site/payload/fields/definitions.rb +1 -1
  760. data/lib/hecks/projections/site/payload/lifecycle/text.rb +1 -1
  761. data/lib/hecks/projections/site/payload/model.rb +16 -12
  762. data/lib/hecks/projections/site/payload/specs.rb +4 -2
  763. data/lib/hecks/projections/site/payload_driver.rb +9 -8
  764. data/lib/hecks/projections/site/probe.rb +12 -3
  765. data/lib/hecks/projections/site/regions.rb +9 -1
  766. data/lib/hecks/projections/site/role_probe.rb +7 -7
  767. data/lib/hecks/projections/site/root_rows.rb +4 -4
  768. data/lib/hecks/projections/site/routes_ts/listings.rb +13 -6
  769. data/lib/hecks/projections/site/routes_ts.rb +6 -3
  770. data/lib/hecks/projections/site/site_admin.rb +1 -1
  771. data/lib/hecks/projections/site/site_cdn.rb +3 -2
  772. data/lib/hecks/projections/site/site_host.rb +1 -1
  773. data/lib/hecks/projections/site/site_root.rb +4 -4
  774. data/lib/hecks/projections/site/table/checks.rb +4 -1
  775. data/lib/hecks/projections/site/table/navigation_checks.rb +2 -2
  776. data/lib/hecks/projections/site/table/row_builder.rb +7 -7
  777. data/lib/hecks/projections/site/table/row_defaults.rb +1 -1
  778. data/lib/hecks/projections/site/table.rb +32 -26
  779. data/lib/hecks/projections/statements/actions.rb +4 -1
  780. data/lib/hecks/projections/statements/relationships.rb +15 -2
  781. data/lib/hecks/projections/statements.rb +2 -2
  782. data/lib/hecks/projections/vocabulary.rb +6 -6
  783. data/lib/hecks/projections/world_skeleton.rb +68 -0
  784. data/lib/hecks/projections.rb +5 -0
  785. data/lib/hecks/projector/cli_audience.rb +12 -9
  786. data/lib/hecks/projector/cli_projector/claims.rb +8 -8
  787. data/lib/hecks/projector/cli_projector/command_specs.rb +9 -5
  788. data/lib/hecks/projector/cli_projector/listing.rb +20 -8
  789. data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
  790. data/lib/hecks/projector/cli_projector/query_specs.rb +1 -1
  791. data/lib/hecks/projector/cli_projector/usage.rb +12 -8
  792. data/lib/hecks/projector/cli_projector.rb +2 -2
  793. data/lib/hecks/projector/docs_projector/behaviour.rb +5 -4
  794. data/lib/hecks/projector/docs_projector/closing.rb +3 -3
  795. data/lib/hecks/projector/docs_projector.rb +12 -7
  796. data/lib/hecks/projector/exporter/adapters.rb +15 -2
  797. data/lib/hecks/projector/exporter/bindings.rb +39 -134
  798. data/lib/hecks/projector/exporter/plugins.rb +55 -0
  799. data/lib/hecks/projector/exporter.rb +2 -0
  800. data/lib/hecks/projector/narrate_projector/commands.rb +4 -3
  801. data/lib/hecks/projector/narrate_projector/reactions.rb +2 -2
  802. data/lib/hecks/projector/narrate_projector.rb +9 -8
  803. data/lib/hecks/projector/target.rb +8 -8
  804. data/lib/hecks/projector.rb +9 -9
  805. data/lib/hecks/quality_control/adapters/agent.rb +2 -1
  806. data/lib/hecks/quality_control/adapters/github_checks.adapter +1 -1
  807. data/lib/hecks/quality_control/adapters/github_issues.adapter +1 -1
  808. data/lib/hecks/quality_control/adapters/github_issues.rb +6 -2
  809. data/lib/hecks/quality_control/adapters/target_tools.rb +9 -3
  810. data/lib/hecks/quality_control/cli/child.rb +1 -0
  811. data/lib/hecks/quality_control/cli/qa_domain_novelty/verdict.rb +1 -1
  812. data/lib/hecks/quality_control/cli/qa_generated_domains/evaluation.rb +12 -2
  813. data/lib/hecks/quality_control/cli/qa_generated_domains/promotion.rb +4 -1
  814. data/lib/hecks/quality_control/cli/qa_generated_domains.rb +4 -1
  815. data/lib/hecks/quality_control/cli/qa_log_bug/proof.rb +16 -3
  816. data/lib/hecks/quality_control/cli/qa_mine_combinations/booting.rb +4 -2
  817. data/lib/hecks/quality_control/cli/qa_mutate.rb +161 -0
  818. data/lib/hecks/quality_control/cli/qa_sweep/all_mode/report.rb +4 -1
  819. data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +2 -1
  820. data/lib/hecks/quality_control/cli/qa_sweep.rb +4 -1
  821. data/lib/hecks/quality_control/cli/qa_tick/output.rb +28 -26
  822. data/lib/hecks/quality_control/cli/qa_tick/preflight.rb +12 -3
  823. data/lib/hecks/quality_control/cli/qa_tick.rb +24 -8
  824. data/lib/hecks/quality_control/quality_control.bluebook +93 -92
  825. data/lib/hecks/quality_control/quality_control.hecksagon +3 -3
  826. data/lib/hecks/quality_control/quality_control.ports.hecksagon +3 -2
  827. data/lib/hecks/query_ir/rules.rb +9 -9
  828. data/lib/hecks/query_ir/touchpoints.rb +5 -2
  829. data/lib/hecks/query_ir.rb +3 -3
  830. data/lib/hecks/query_specification/common/comparison.rb +1 -1
  831. data/lib/hecks/query_specification/common/dsl.rb +3 -4
  832. data/lib/hecks/query_specification/hop_path.rb +7 -4
  833. data/lib/hecks/rails_app/bluebook/rails_app.bluebook +60 -0
  834. data/lib/hecks/rails_app/bluebook/rails_app.hecksagon +4 -0
  835. data/lib/hecks/refused.rb +104 -0
  836. data/lib/hecks/release/approval.rb +9 -2
  837. data/lib/hecks/release/gem_pin.rb +7 -3
  838. data/lib/hecks/release/lane.rb +1 -1
  839. data/lib/hecks/release/runner/clean_tree.rb +4 -1
  840. data/lib/hecks/release/runner/console.rb +2 -1
  841. data/lib/hecks/router/aggregate_reader.rb +3 -4
  842. data/lib/hecks/router/namespace_installer.rb +31 -17
  843. data/lib/hecks/router.rb +2 -10
  844. data/lib/hecks/runtime/adapter_lookup.rb +30 -7
  845. data/lib/hecks/runtime/adapter_namespace.rb +21 -0
  846. data/lib/hecks/runtime/asking.rb +30 -0
  847. data/lib/hecks/runtime/caller.rb +22 -0
  848. data/lib/hecks/runtime/capability_graph.rb +2 -1
  849. data/lib/hecks/runtime/collecting.rb +86 -0
  850. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +13 -8
  851. data/lib/hecks/runtime/command_interpreter/delegation.rb +46 -17
  852. data/lib/hecks/runtime/command_interpreter/hydration.rb +17 -21
  853. data/lib/hecks/runtime/command_interpreter/list_mutations.rb +2 -1
  854. data/lib/hecks/runtime/command_interpreter/persisting.rb +13 -12
  855. data/lib/hecks/runtime/command_interpreter.rb +17 -10
  856. data/lib/hecks/runtime/command_rules/admissibility.rb +17 -10
  857. data/lib/hecks/runtime/command_rules/authorization.rb +8 -6
  858. data/lib/hecks/runtime/command_rules/guard_state.rb +10 -10
  859. data/lib/hecks/runtime/command_rules/invariants.rb +3 -1
  860. data/lib/hecks/runtime/command_rules/missing_reference.rb +25 -0
  861. data/lib/hecks/runtime/command_rules/one_of_references.rb +44 -0
  862. data/lib/hecks/runtime/command_rules/references.rb +14 -14
  863. data/lib/hecks/runtime/command_rules.rb +2 -0
  864. data/lib/hecks/runtime/declarations.rb +66 -0
  865. data/lib/hecks/runtime/dependency_planning/analyzer.rb +2 -1
  866. data/lib/hecks/runtime/dependency_planning/expression_reads.rb +3 -2
  867. data/lib/hecks/runtime/dependency_planning/mutation_analysis.rb +2 -1
  868. data/lib/hecks/runtime/dispatch_claim.rb +72 -0
  869. data/lib/hecks/runtime/dispatch_context.rb +41 -0
  870. data/lib/hecks/runtime/dispatcher/queries.rb +17 -0
  871. data/lib/hecks/runtime/dispatcher/routing.rb +34 -12
  872. data/lib/hecks/runtime/dispatcher/validating.rb +73 -0
  873. data/lib/hecks/runtime/dispatcher.rb +39 -23
  874. data/lib/hecks/runtime/entity_element/location.rb +15 -7
  875. data/lib/hecks/runtime/entity_element/mutations.rb +4 -1
  876. data/lib/hecks/runtime/entity_interpreter/enforcement.rb +1 -1
  877. data/lib/hecks/runtime/entity_interpreter/locating.rb +1 -1
  878. data/lib/hecks/runtime/entity_interpreter/saving.rb +5 -5
  879. data/lib/hecks/runtime/entity_interpreter.rb +11 -4
  880. data/lib/hecks/runtime/errors.rb +30 -17
  881. data/lib/hecks/runtime/identity.rb +9 -0
  882. data/lib/hecks/runtime/instance.rb +3 -2
  883. data/lib/hecks/runtime/interpreting.rb +63 -6
  884. data/lib/hecks/runtime/invocation_builder.rb +7 -4
  885. data/lib/hecks/runtime/invocation_facts.rb +13 -8
  886. data/lib/hecks/runtime/loader/boot_steps.rb +3 -2
  887. data/lib/hecks/runtime/loader/described.rb +4 -0
  888. data/lib/hecks/runtime/loader.rb +52 -62
  889. data/lib/hecks/runtime/outbox/redrive.rb +105 -0
  890. data/lib/hecks/runtime/outbox/relay.rb +11 -39
  891. data/lib/hecks/runtime/outbox/runnable.rb +60 -0
  892. data/lib/hecks/runtime/outbox.rb +15 -1
  893. data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -1
  894. data/lib/hecks/runtime/policy_interpreter/fan_out.rb +8 -35
  895. data/lib/hecks/runtime/policy_interpreter.rb +8 -0
  896. data/lib/hecks/runtime/port_operation_interpreter.rb +27 -13
  897. data/lib/hecks/runtime/query_interpreter/outside_answer.rb +1 -1
  898. data/lib/hecks/runtime/query_interpreter/paging.rb +42 -0
  899. data/lib/hecks/runtime/query_interpreter/plugin_answer.rb +54 -0
  900. data/lib/hecks/runtime/query_interpreter/store_specification.rb +32 -0
  901. data/lib/hecks/runtime/query_interpreter.rb +16 -9
  902. data/lib/hecks/runtime/reaction_invocation/target_resolution.rb +10 -4
  903. data/lib/hecks/runtime/reaction_invocation.rb +5 -2
  904. data/lib/hecks/runtime/read_model_interpreter/head_ordering.rb +1 -1
  905. data/lib/hecks/runtime/read_model_interpreter/reductions.rb +23 -15
  906. data/lib/hecks/runtime/read_model_interpreter/shaping.rb +18 -7
  907. data/lib/hecks/runtime/read_model_interpreter.rb +5 -2
  908. data/lib/hecks/runtime/reference_hop.rb +3 -2
  909. data/lib/hecks/runtime/refusal_wording.rb +15 -6
  910. data/lib/hecks/runtime/registry/adapter_capabilities.rb +43 -0
  911. data/lib/hecks/runtime/registry/ask_verification.rb +15 -4
  912. data/lib/hecks/runtime/registry/{chapter_verification.rb → bluebook_verification.rb} +23 -23
  913. data/lib/hecks/runtime/registry/context_maps.rb +30 -0
  914. data/lib/hecks/runtime/registry/declarations.rb +8 -6
  915. data/lib/hecks/runtime/registry/durability_warnings.rb +18 -2
  916. data/lib/hecks/runtime/registry/hecksagon_rules.rb +27 -24
  917. data/lib/hecks/runtime/registry/plugin_query_verification.rb +45 -0
  918. data/lib/hecks/runtime/registry/providers.rb +34 -99
  919. data/lib/hecks/runtime/registry/query_verification.rb +30 -21
  920. data/lib/hecks/runtime/registry/runtime_state.rb +4 -3
  921. data/lib/hecks/runtime/registry/saga_persistence.rb +7 -16
  922. data/lib/hecks/runtime/registry/verification.rb +9 -5
  923. data/lib/hecks/runtime/registry/world_defaults.rb +8 -5
  924. data/lib/hecks/runtime/registry.rb +52 -27
  925. data/lib/hecks/runtime/row_sweep.rb +104 -0
  926. data/lib/hecks/runtime/saga_interpreter/compensation.rb +5 -4
  927. data/lib/hecks/runtime/saga_interpreter/delivery.rb +13 -10
  928. data/lib/hecks/runtime/saga_interpreter/redrive.rb +141 -0
  929. data/lib/hecks/runtime/saga_interpreter/transitions.rb +35 -6
  930. data/lib/hecks/runtime/saga_interpreter.rb +36 -4
  931. data/lib/hecks/runtime/saga_pending_dispatch.rb +4 -2
  932. data/lib/hecks/runtime/scalar_field.rb +73 -0
  933. data/lib/hecks/runtime/tenant_scope.rb +2 -1
  934. data/lib/hecks/runtime/validation.rb +24 -0
  935. data/lib/hecks/runtime/value/admission.rb +11 -11
  936. data/lib/hecks/runtime/value/coercion.rb +7 -7
  937. data/lib/hecks/runtime/value/field_checks.rb +13 -13
  938. data/lib/hecks/runtime/value/invariant_violation.rb +4 -1
  939. data/lib/hecks/runtime/value/references.rb +7 -4
  940. data/lib/hecks/runtime/value/validation.rb +7 -4
  941. data/lib/hecks/runtime/wasm_plugin.rb +98 -0
  942. data/lib/hecks/runtime.rb +14 -14
  943. data/lib/hecks/rust_build/command_schemas.rb +27 -0
  944. data/lib/hecks/rust_build/conformance/surface.rb +50 -7
  945. data/lib/hecks/rust_build/conformance.rb +25 -3
  946. data/lib/hecks/rust_build/conformance_fuzz.rb +2 -2
  947. data/lib/hecks/rust_build/domain_name.rb +4 -1
  948. data/lib/hecks/rust_build/host.rb +15 -9
  949. data/lib/hecks/rust_build/native_build.rb +2 -1
  950. data/lib/hecks/rust_build/prebuilt_binary.rb +25 -5
  951. data/lib/hecks/rust_build/project_rust/codegen_run.rb +11 -10
  952. data/lib/hecks/rust_build/project_rust/domain_loader.rb +15 -2
  953. data/lib/hecks/rust_build/project_rust/root_mod/notes.rb +1 -1
  954. data/lib/hecks/rust_build/project_rust/target_ir.rb +12 -3
  955. data/lib/hecks/rust_build/project_rust.rb +39 -19
  956. data/lib/hecks/rust_build/target_dir.rb +40 -0
  957. data/lib/hecks/rust_build/wasm.rb +46 -17
  958. data/lib/hecks/rust_build/wasm_browser.rb +8 -2
  959. data/lib/hecks/rust_build.rb +29 -2
  960. data/lib/hecks/sentence_form.rb +212 -0
  961. data/lib/hecks/served_options.rb +2 -1
  962. data/lib/hecks/shape_ledger.rb +149 -0
  963. data/lib/hecks/site/bluebook/site.bluebook +39 -62
  964. data/lib/hecks/site/bluebook/site.ports.hecksagon +1 -1
  965. data/lib/hecks/sme/bluebook/sme.bluebook +7 -1
  966. data/lib/hecks/storehouse/audit.rb +4 -1
  967. data/lib/hecks/storehouse/bluebook_words.rb +35 -0
  968. data/lib/hecks/storehouse/dispatching.rb +6 -3
  969. data/lib/hecks/storehouse/introspection.rb +4 -4
  970. data/lib/hecks/storehouse/querying.rb +2 -2
  971. data/lib/hecks/storehouse.rb +14 -4
  972. data/lib/hecks/switches.rb +21 -0
  973. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +5 -9
  974. data/lib/hecks/text.rb +57 -0
  975. data/lib/hecks/tickets/bluebook/tickets.bluebook +30 -51
  976. data/lib/hecks/tickets/bluebook/tickets.hecksagon +1 -1
  977. data/lib/hecks/tickets/bluebook/tickets.ports.hecksagon +1 -1
  978. data/lib/hecks/tools/argument_gate_matrix/dispatching.rb +1 -1
  979. data/lib/hecks/tools/argument_gate_matrix/rows.rb +7 -7
  980. data/lib/hecks/tools/argument_gate_matrix/violations.rb +1 -1
  981. data/lib/hecks/tools/argument_gate_matrix.rb +3 -3
  982. data/lib/hecks/tools/ci_gate_decision.rb +5 -2
  983. data/lib/hecks/tools/ci_gates.rb +1 -1
  984. data/lib/hecks/tools/comment_style/caps_words.rb +11 -8
  985. data/lib/hecks/tools/comment_style/dictionary.rb +7 -2
  986. data/lib/hecks/tools/comment_style/fixing.rb +10 -5
  987. data/lib/hecks/tools/comment_style/long_blocks.rb +1 -1
  988. data/lib/hecks/tools/comment_style/masking.rb +5 -5
  989. data/lib/hecks/tools/comment_style/run.rb +4 -1
  990. data/lib/hecks/tools/comment_style/source_file.rb +10 -3
  991. data/lib/hecks/tools/comment_style/structure_checks.rb +13 -4
  992. data/lib/hecks/tools/comment_style/text_checks.rb +1 -1
  993. data/lib/hecks/tools/comment_style.rb +25 -2
  994. data/lib/hecks/tools/deploy_recipe/generation.rb +1 -1
  995. data/lib/hecks/tools/deploy_recipe/targets.rb +1 -1
  996. data/lib/hecks/tools/deploy_recipe/worlds.rb +15 -9
  997. data/lib/hecks/tools/deploy_recipe.rb +17 -10
  998. data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +1 -1
  999. data/lib/hecks/tools/deploy_recipe_lint/makefile.rb +7 -1
  1000. data/lib/hecks/tools/drop_implicit_append_fields.rb +4 -1
  1001. data/lib/hecks/tools/era_projection_backfill.rb +11 -6
  1002. data/lib/hecks/tools/era_reattest.rb +11 -6
  1003. data/lib/hecks/tools/fuzz_sweep/findings.rb +18 -2
  1004. data/lib/hecks/tools/fuzz_sweep/options.rb +5 -1
  1005. data/lib/hecks/tools/fuzz_sweep.rb +17 -5
  1006. data/lib/hecks/tools/gate.rb +27 -5
  1007. data/lib/hecks/tools/hoist_local_givens/rewriting.rb +3 -2
  1008. data/lib/hecks/tools/hoist_local_givens.rb +2 -4
  1009. data/lib/hecks/tools/lanes/prebuilt_workflow.rb +1 -1
  1010. data/lib/hecks/tools/lanes/rows.rb +9 -3
  1011. data/lib/hecks/tools/lanes/ruleset.rb +1 -1
  1012. data/lib/hecks/tools/lanes/watch_workflow.rb +1 -1
  1013. data/lib/hecks/tools/lanes/workflow.rb +1 -1
  1014. data/lib/hecks/tools/lanes.rb +1 -1
  1015. data/lib/hecks/tools/oidc_manifests.rb +16 -19
  1016. data/lib/hecks/tools/regeneration_run.rb +22 -17
  1017. data/lib/hecks/tools/release_only.rb +5 -2
  1018. data/lib/hecks/tools/rust_comment_style/caps_words.rb +5 -5
  1019. data/lib/hecks/tools/rust_comment_style/fixing.rb +1 -1
  1020. data/lib/hecks/tools/rust_comment_style/masking.rb +7 -4
  1021. data/lib/hecks/tools/rust_comment_style/source_file.rb +7 -2
  1022. data/lib/hecks/tools/rust_comment_style/text_checks.rb +1 -1
  1023. data/lib/hecks/tools/rust_comment_style/tokenizer.rb +4 -1
  1024. data/lib/hecks/tools/site_routes/editor_files.rb +16 -13
  1025. data/lib/hecks/tools/site_routes/root_files.rb +14 -13
  1026. data/lib/hecks/tools/site_routes/templates.rb +5 -4
  1027. data/lib/hecks/tools/site_routes.rb +18 -15
  1028. data/lib/hecks/tools/tail_merge.rb +3 -3
  1029. data/lib/hecks/tools/tenant_provisioning.rb +6 -6
  1030. data/lib/hecks/tools/tools_doc.rb +16 -16
  1031. data/lib/hecks/tools/translation_audit/domain_loading.rb +3 -3
  1032. data/lib/hecks/tools/translation_audit.rb +15 -4
  1033. data/lib/hecks/tools/translation_scaffold/reporting.rb +4 -1
  1034. data/lib/hecks/tools/translation_scaffold.rb +4 -14
  1035. data/lib/hecks/vendoring/git_source.rb +7 -4
  1036. data/lib/hecks/version.rb +1 -1
  1037. data/lib/hecks/vocabulary.rb +110 -3
  1038. data/lib/hecks.rb +37 -18
  1039. data/lib/rubocop/cop/hecks/inline_regexp.rb +29 -0
  1040. data/lib/rubocop/cop/hecks/no_transport_in_runtime.rb +78 -0
  1041. data/lib/rubocop/cop/hecks/rails_model_rules.rb +46 -0
  1042. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +1 -1
  1043. data/lib/rubocop/cop/hecks/train_wreck.rb +69 -0
  1044. data/lib/rubocop/cop/hecks/unnamed_pipeline.rb +51 -0
  1045. data/prebuilt/31cf81e12f16236ad26249be590cfea3799c9db67432e6a4ef54b801c355b6ae.marshal +0 -0
  1046. data/prebuilt/68ecb2bd3abb1a221007db4f2b0375834c13aaccac8f2b99778e8517f11aa113.marshal +0 -0
  1047. data/prebuilt/verdicts-0328c2403383837d780ad5ed81d1da351c56436b894cf9015b5d56b8f1f1bf10.json +1 -0
  1048. data/qa/settings.yml +9 -0
  1049. data/rust/Cargo.lock +8 -1
  1050. data/rust/Cargo.toml +6 -7
  1051. data/rust/build/src/binding_pass.rs +13 -17
  1052. data/rust/build/src/cargo_sync.rs +1 -1
  1053. data/rust/build/src/lineage_pass.rs +4 -71
  1054. data/rust/build/src/pipeline.rs +99 -70
  1055. data/rust/build/src/resolve.rs +85 -29
  1056. data/rust/build/src/seams_pass.rs +117 -99
  1057. data/rust/build/src/world_pass.rs +21 -12
  1058. data/rust/codegen/src/domain_generator.rs +24 -2
  1059. data/rust/codegen/src/expr_emitter.rs +6 -0
  1060. data/rust/codegen/src/json_codec.rs +48 -10
  1061. data/rust/codegen/src/main.rs +35 -34
  1062. data/rust/codegen/src/manifest.rs +1 -1
  1063. data/rust/codegen/src/naming.rs +5 -5
  1064. data/rust/codegen/src/one_of_references.rs +124 -0
  1065. data/rust/codegen/src/presenters.rs +3 -3
  1066. data/rust/codegen/src/queries.rs +56 -7
  1067. data/rust/codegen/src/reactions.rs +19 -14
  1068. data/rust/codegen/src/read_models.rs +19 -14
  1069. data/rust/codegen/src/registry.rs +89 -15
  1070. data/rust/codegen/src/sidecars.rs +1 -1
  1071. data/rust/expr/Cargo.lock +54 -0
  1072. data/rust/expr/Cargo.toml +52 -0
  1073. data/rust/{src/kernel → expr/src}/attribute_shapes/composite.rs +45 -14
  1074. data/rust/{src/kernel → expr/src}/attribute_shapes/list.rs +1 -1
  1075. data/rust/{src/kernel → expr/src}/attribute_shapes/mod.rs +2 -2
  1076. data/rust/{src/kernel → expr/src}/attribute_shapes/optional.rs +1 -1
  1077. data/rust/{src/kernel → expr/src}/attribute_shapes/scalar.rs +3 -3
  1078. data/rust/{src/kernel → expr/src}/expr.rs +30 -8
  1079. data/rust/{src/kernel → expr/src}/expression_operators/arithmetic.rs +41 -18
  1080. data/rust/{src/kernel → expr/src}/expression_operators/comparison.rs +54 -8
  1081. data/rust/{src/kernel → expr/src}/expression_operators/enumeration.rs +73 -6
  1082. data/rust/{src/kernel → expr/src}/expression_operators/logical.rs +2 -2
  1083. data/rust/expr/src/expression_operators/membership.rs +33 -0
  1084. data/rust/{src/kernel → expr/src}/expression_operators/mod.rs +2 -2
  1085. data/rust/expr/src/expression_operators/pattern_match.rs +179 -0
  1086. data/rust/{src/kernel → expr/src}/expression_operators/positional.rs +8 -6
  1087. data/rust/{src/kernel → expr/src}/expression_operators/presence.rs +7 -6
  1088. data/rust/{src/kernel → expr/src}/expression_operators/sign_test.rs +14 -4
  1089. data/rust/{src/kernel → expr/src}/expression_operators/sized.rs +16 -9
  1090. data/rust/{src/kernel → expr/src}/expression_operators/text.rs +30 -17
  1091. data/rust/{src/kernel → expr/src}/expression_operators/to_string.rs +8 -6
  1092. data/rust/expr/src/lib.rs +17 -0
  1093. data/rust/expr/src/refusal.rs +62 -0
  1094. data/rust/expr/src/rendering.rs +135 -0
  1095. data/rust/{src/kernel → expr/src}/ruby_float.rs +7 -6
  1096. data/rust/host/Cargo.lock +137 -123
  1097. data/rust/host/Cargo.toml +7 -3
  1098. data/rust/host/HECKS_RELEASE +1 -1
  1099. data/rust/host/core/Cargo.toml +64 -0
  1100. data/rust/host/core/clippy.toml +4 -0
  1101. data/rust/host/core/src/arg_limits.rs +48 -0
  1102. data/rust/host/core/src/clock.rs +146 -0
  1103. data/rust/host/core/src/dispatch.rs +920 -0
  1104. data/rust/host/core/src/expr_json.rs +1137 -0
  1105. data/rust/host/{src → core/src}/host.rs +13 -14
  1106. data/rust/host/{src → core/src}/ir.rs +227 -18
  1107. data/rust/host/core/src/journal.rs +126 -0
  1108. data/rust/host/{src → core/src}/lambda_client.rs +27 -96
  1109. data/rust/host/core/src/lib.rs +37 -0
  1110. data/rust/host/{src → core/src}/needs.rs +66 -20
  1111. data/rust/host/core/src/plugins.rs +269 -0
  1112. data/rust/host/core/src/presentation.rs +471 -0
  1113. data/rust/host/{src → core/src}/presentation_write.rs +75 -43
  1114. data/rust/host/{src → core/src}/query_step.rs +54 -16
  1115. data/rust/host/{src → core/src}/reference_validate.rs +6 -6
  1116. data/rust/host/{src → core/src}/storage_shape.rs +2 -2
  1117. data/rust/host/core/src/store/memory.rs +635 -0
  1118. data/rust/host/core/src/store.rs +418 -0
  1119. data/rust/host/{src → core/src}/ui_schema.rs +8 -8
  1120. data/rust/host/core/src/validation.rs +129 -0
  1121. data/rust/host/core/src/wasm_runner.rs +410 -0
  1122. data/rust/host/fuzz/.gitignore +4 -0
  1123. data/rust/host/fuzz/Cargo.lock +4479 -0
  1124. data/rust/host/fuzz/Cargo.toml +42 -0
  1125. data/rust/host/fuzz/README.md +55 -0
  1126. data/rust/host/fuzz/fuzz_targets/postgres_equivalence.rs +24 -0
  1127. data/rust/host/fuzz/src/backend.rs +273 -0
  1128. data/rust/host/fuzz/src/equivalence.rs +195 -0
  1129. data/rust/host/fuzz/src/footprint.rs +182 -0
  1130. data/rust/host/fuzz/src/lib.rs +17 -0
  1131. data/rust/host/fuzz/src/ops.rs +345 -0
  1132. data/rust/host/fuzz/tests/smoke.rs +50 -0
  1133. data/rust/host/src/api.rs +316 -105
  1134. data/rust/host/src/auth/google_signin.rs +183 -0
  1135. data/rust/host/src/auth.rs +527 -370
  1136. data/rust/host/src/aws_lambda_invoker.rs +61 -0
  1137. data/rust/host/src/bin/expr_harness.rs +9 -4
  1138. data/rust/host/src/bin/ledger.rs +84 -0
  1139. data/rust/host/src/bin/lineage_harness.rs +17 -21
  1140. data/rust/host/src/bin/mint_harness.rs +11 -36
  1141. data/rust/host/src/bin/validate_harness.rs +1 -6
  1142. data/rust/host/src/boot.rs +465 -174
  1143. data/rust/host/src/boundary_fuzz/ir.rs +10 -5
  1144. data/rust/host/src/boundary_fuzz/server.rs +6 -5
  1145. data/rust/host/src/checkout.rs +4 -1
  1146. data/rust/host/src/commerce_ir.rs +126 -105
  1147. data/rust/host/src/{dispatch.rs → dispatch_tests.rs} +367 -847
  1148. data/rust/host/src/extension.rs +3 -4
  1149. data/rust/host/src/hecksagon_boundary_tests.rs +149 -0
  1150. data/rust/host/src/ledger.rs +683 -0
  1151. data/rust/host/src/lib.rs +21 -28
  1152. data/rust/host/src/payments/tests.rs +136 -38
  1153. data/rust/host/src/payments.rs +36 -32
  1154. data/rust/host/src/platform_signin/extension.rs +1 -1
  1155. data/rust/host/src/platform_signin/mapping_tests.rs +12 -9
  1156. data/rust/host/src/platform_signin/session.rs +7 -14
  1157. data/rust/host/src/platform_signin/verify.rs +0 -2
  1158. data/rust/host/src/presentation_tests.rs +382 -0
  1159. data/rust/host/src/rate_limit.rs +18 -2
  1160. data/rust/host/src/server.rs +222 -43
  1161. data/rust/host/src/{journal.rs → store/journal_sql.rs} +21 -127
  1162. data/rust/host/src/{mint.rs → store/mint.rs} +12 -12
  1163. data/rust/host/src/store/postgres.rs +493 -0
  1164. data/rust/host/src/store.rs +97 -0
  1165. data/rust/host/src/store_conformance.rs +1265 -0
  1166. data/rust/host/src/web/commerce.rs +10 -18
  1167. data/rust/host/src/web/newsletter/cooldown.rs +107 -22
  1168. data/rust/host/src/web/newsletter/naming.rs +104 -0
  1169. data/rust/host/src/web/newsletter/tests.rs +53 -0
  1170. data/rust/host/src/web/newsletter.rs +73 -50
  1171. data/rust/host/src/web/newsletter_send.rs +12 -13
  1172. data/rust/host/src/web/registration_receipt.rs +2 -2
  1173. data/rust/host/src/web/registrations.rs +754 -375
  1174. data/rust/host/src/web/resend_webhook.rs +3 -3
  1175. data/rust/host/src/web/signin_tests.rs +131 -0
  1176. data/rust/host/src/web.rs +1479 -149
  1177. data/rust/lsp/README.md +27 -27
  1178. data/rust/lsp/src/diagnostics.rs +75 -15
  1179. data/rust/lsp/src/main.rs +101 -5
  1180. data/rust/parser/fuzz/Cargo.toml +2 -2
  1181. data/rust/parser/fuzz/README.md +3 -3
  1182. data/rust/parser/fuzz/fuzz_targets/{chapter.rs → bluebook.rs} +4 -4
  1183. data/rust/parser/fuzz/fuzz_targets/resolve.rs +2 -2
  1184. data/rust/parser/src/build/named_pattern.rs +22 -0
  1185. data/rust/parser/src/build/references.rs +52 -0
  1186. data/rust/parser/src/diag.rs +22 -1
  1187. data/rust/parser/src/emit.rs +68 -4
  1188. data/rust/parser/src/expr/ast_json.rs +10 -0
  1189. data/rust/parser/src/expr/resolver.rs +49 -9
  1190. data/rust/parser/src/ir.rs +34 -7
  1191. data/rust/parser/src/keywords.rs +454 -457
  1192. data/rust/parser/src/lex.rs +115 -87
  1193. data/rust/parser/src/main.rs +180 -32
  1194. data/rust/parser/src/parse/adapter.rs +28 -0
  1195. data/rust/parser/src/parse/aggregate.rs +55 -35
  1196. data/rust/parser/src/parse/command.rs +100 -15
  1197. data/rust/parser/src/parse/{chapter.rs → declaration.rs} +85 -54
  1198. data/rust/parser/src/parse/domain_port.rs +60 -9
  1199. data/rust/parser/src/parse/entity.rs +35 -32
  1200. data/rust/parser/src/parse/file.rs +1 -1
  1201. data/rust/parser/src/parse/hecksagon.rs +15 -5
  1202. data/rust/parser/src/parse/mod.rs +84 -2
  1203. data/rust/parser/src/parse/process_manager.rs +1 -1
  1204. data/rust/parser/src/parse/query.rs +13 -3
  1205. data/rust/parser/src/parse/read_model.rs +2 -1
  1206. data/rust/parser/src/phrases.rs +1258 -0
  1207. data/rust/parser/src/render_ruby.rs +540 -0
  1208. data/rust/parser/src/sentence/changes.rs +437 -0
  1209. data/rust/parser/src/sentence/command_step.rs +206 -0
  1210. data/rust/parser/src/sentence/complete.rs +276 -0
  1211. data/rust/parser/src/sentence/entity.rs +41 -0
  1212. data/rust/parser/src/sentence/examples.rs +1012 -0
  1213. data/rust/parser/src/sentence/format.rs +882 -0
  1214. data/rust/parser/src/sentence/lower.rs +1366 -0
  1215. data/rust/parser/src/sentence/mod.rs +444 -0
  1216. data/rust/parser/src/sentence/names.rs +69 -0
  1217. data/rust/parser/src/sentence/options.rs +154 -0
  1218. data/rust/parser/src/sentence/pattern.rs +433 -0
  1219. data/rust/parser/src/sentence/process.rs +196 -0
  1220. data/rust/parser/src/sentence/queries.rs +431 -0
  1221. data/rust/parser/src/sentence/report.rs +283 -0
  1222. data/rust/parser/src/sentence/token.rs +106 -0
  1223. data/rust/parser/src/sentence/values.rs +498 -0
  1224. data/rust/src/exemplar/queries.rs +1 -0
  1225. data/rust/src/exemplar/reactions.rs +1 -1
  1226. data/rust/src/exemplar/registry.rs +30 -0
  1227. data/rust/src/kernel/cli.rs +219 -19
  1228. data/rust/src/kernel/collecting.rs +174 -0
  1229. data/rust/src/kernel/dispatch.rs +55 -27
  1230. data/rust/src/kernel/mod.rs +7 -67
  1231. data/rust/src/kernel/named_query.rs +225 -11
  1232. data/rust/src/kernel/orchestrate.rs +213 -25
  1233. data/rust/src/kernel/orchestrate_guard_tests.rs +3 -0
  1234. data/rust/src/kernel/query_cursor.rs +251 -0
  1235. data/rust/src/kernel/query_ordering.rs +6 -6
  1236. data/rust/src/kernel/read_model.rs +162 -17
  1237. data/rust/src/kernel/repository.rs +107 -17
  1238. data/rust/src/kernel/vocab/mod.rs +4 -0
  1239. data/rust/src/kernel/vocab/reducer.rs +109 -0
  1240. data/rust/src/kernel/vocab/refusal_class.rs +124 -0
  1241. data/rust/src/kernel/vocab/refusal_template.rs +2 -2
  1242. data/rust/src/lib.rs +1 -1
  1243. data/rust/web/Cargo.lock +47 -0
  1244. metadata +328 -93
  1245. data/lib/hecks/chapters.rb +0 -147
  1246. data/lib/hecks/gate/stages.yml +0 -234
  1247. data/lib/hecks/hecks/governance.world +0 -7
  1248. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +0 -137
  1249. data/lib/hecks/ports/persistence/postgres_dump/connection.rb +0 -59
  1250. data/lib/hecks/ports/persistence/postgres_dump.rb +0 -100
  1251. data/lib/hecks/projections/deploy/box/templates/tunnel-egress.yaml.tmpl +0 -11
  1252. data/lib/hecks/projector/exporter/capability_values.rb +0 -55
  1253. data/lib/hecks/runtime/remote_dispatcher.rb +0 -176
  1254. data/prebuilt/ad68f9e06c8068c2a4176b57abf3083d591d5d8980c2b3e897b97331f965b643.marshal +0 -0
  1255. data/prebuilt/ee22ec8642c4006717ba2b80f46e5efb81de459eef9dfec043e838c0ad4fd720.marshal +0 -0
  1256. data/prebuilt/verdicts-b69f5dcbba79c20daa0493476fa7c317004a30aa2bc3d3dc6a722ea70ae2b43b.json +0 -1
  1257. data/rust/host/src/clock.rs +0 -8
  1258. data/rust/host/src/expr_json.rs +0 -1983
  1259. data/rust/host/src/presentation.rs +0 -842
  1260. data/rust/host/src/wasm_runner.rs +0 -206
  1261. data/rust/src/kernel/expression_operators/membership.rs +0 -20
  1262. data/rust/src/kernel/expression_operators/pattern_match.rs +0 -108
  1263. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/events.rb +0 -0
  1264. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/head_writes.rb +0 -0
  1265. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/journal.rb +0 -0
  1266. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/base_sql.rb +0 -0
  1267. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/field_cache.rb +0 -0
  1268. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/head_compiler.rb +0 -0
  1269. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/merge_writes.rb +0 -0
  1270. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/provisioning.rb +0 -0
  1271. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/resumable_backfill.rb +0 -0
  1272. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/transform_installer.rb +0 -0
  1273. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/transform_sql.rb +0 -0
  1274. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/sagas.rb +0 -0
  1275. /data/rust/host/{src → core/src}/field_hints.rs +0 -0
  1276. /data/rust/host/{src → core/src}/log.rs +0 -0
  1277. /data/rust/host/{src → core/src}/reference_transform.rs +0 -0
@@ -1,745 +1,10 @@
1
- //! Ties the journal and the sandbox together: replays history, then the
2
- //! new step, persisting it only if the module accepts it.
3
-
4
- use crate::host::Host;
5
- use crate::journal;
6
- use crate::lambda_client::{self};
7
- use crate::wasm_runner;
8
- use std::path::Path;
9
- use tokio::sync::Mutex;
10
- use tokio_postgres::Client;
11
-
12
- pub struct Outcome {
13
- pub result: serde_json::Value,
14
- pub accepted: bool,
15
- }
16
-
17
- /// Who a command is dispatched for: the role they state and, when the host knows who they are, the
18
- /// actor Governance holds assignments for. Both absent is an unidentified caller.
19
- #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
20
- pub struct Caller<'a> {
21
- pub role: Option<&'a str>,
22
- pub actor_id: Option<&'a str>,
23
- }
24
-
25
- /// The role an unidentified caller is dispatched under when roles are enforced: no command declares
26
- /// it, so a command that declares any role refuses it.
27
- pub const ANONYMOUS_ROLE: &str = "Anonymous";
28
-
29
- /// What the host does about the role a command declares (`HECKS_ROLE_ENFORCEMENT`).
30
- #[derive(Clone, Copy, Debug, PartialEq, Eq)]
31
- pub enum Enforcement {
32
- /// A caller with no role is unchecked, as before.
33
- Off,
34
- /// An unidentified or unassigned caller is let through and logged as `would_refuse_role`; a
35
- /// caller that states a role is checked exactly as under `Enforce`, so shadow never accepts
36
- /// what `Off` would not.
37
- Shadow,
38
- /// An unidentified or unassigned caller is refused.
39
- Enforce,
40
- }
41
-
42
- impl Enforcement {
43
- /// Reads `off` (the default), `shadow` or `enforce`; anything else is `Off`.
44
- pub fn parse(value: Option<&str>) -> Self {
45
- match value.map(|v| v.trim().to_ascii_lowercase()).as_deref() {
46
- Some("shadow") => Self::Shadow,
47
- Some("enforce") => Self::Enforce,
48
- _ => Self::Off,
49
- }
50
- }
51
-
52
- pub fn from_env() -> Self {
53
- static MODE: std::sync::OnceLock<Enforcement> = std::sync::OnceLock::new();
54
- *MODE.get_or_init(|| Self::parse(std::env::var("HECKS_ROLE_ENFORCEMENT").ok().as_deref()))
55
- }
56
-
57
- /// The caller the kernel is asked to check: an unidentified caller becomes the anonymous role
58
- /// unless enforcement is off.
59
- pub fn effective(self, caller: Caller<'_>) -> Caller<'_> {
60
- if self != Self::Off && caller.role.is_none() && caller.actor_id.is_none() {
61
- return Caller {
62
- role: Some(ANONYMOUS_ROLE),
63
- actor_id: None,
64
- };
65
- }
66
- caller
67
- }
68
- }
69
-
70
- /// Where a dispatch comes from. A host-internal dispatch is the host acting on its own behalf (a
71
- /// signup, a provisioning step, a presentation write); it names no caller and is never subject to
72
- /// `HECKS_ROLE_ENFORCEMENT`, which guards what callers outside the host may do.
73
- #[derive(Clone, Copy, Debug, PartialEq)]
74
- enum Origin {
75
- External,
76
- HostInternal,
77
- }
78
-
79
- /// Whether the kernel refused the command for the caller's role.
80
- fn refused_for_role(result: &serde_json::Value) -> bool {
81
- result
82
- .get("refusals")
83
- .and_then(|r| r.as_array())
84
- .is_some_and(|all| {
85
- all.iter()
86
- .any(|r| r.get("kind").and_then(|k| k.as_str()) == Some("Unauthorized"))
87
- })
88
- }
89
-
90
- // Distinguishes a module refusal from a kernel failure to run at all.
91
- #[derive(Debug, PartialEq)]
92
- enum KernelAnswer {
93
- Accepted,
94
- Refused,
95
- Failed(String),
96
- }
97
-
98
- // A run that never started has no `refusals` array at all, so absence of
99
- // refusals doesn't by itself mean the run succeeded.
100
- fn classify(result: &serde_json::Value) -> KernelAnswer {
101
- if let Some(error) = result.get("error") {
102
- return KernelAnswer::Failed(
103
- error
104
- .as_str()
105
- .map_or_else(|| error.to_string(), str::to_string),
106
- );
107
- }
108
- let refused = result
109
- .get("refusals")
110
- .and_then(|r| r.as_array())
111
- .is_some_and(|r| !r.is_empty());
112
- if refused {
113
- KernelAnswer::Refused
114
- } else {
115
- KernelAnswer::Accepted
116
- }
117
- }
118
-
119
- // A kernel failure becomes `Err`, not a result with no instances read as
120
- // an empty world.
121
- fn parse_kernel_output(output: &str) -> anyhow::Result<serde_json::Value> {
122
- let result: serde_json::Value = serde_json::from_str(output)?;
123
- match classify(&result) {
124
- KernelAnswer::Failed(message) => {
125
- anyhow::bail!("the kernel failed before it could run the command: {message}")
126
- }
127
- KernelAnswer::Accepted | KernelAnswer::Refused => Ok(result),
128
- }
129
- }
130
-
131
- // Kept as one opaque object under the journal's `args` column; the kernel
132
- // unwraps `to`/`with` itself, so this needs no journal schema migration.
133
- pub fn routed_invocation(
134
- to: serde_json::Value,
135
- facts: serde_json::Value,
136
- ) -> anyhow::Result<serde_json::Value> {
137
- if !facts.is_object() {
138
- anyhow::bail!("with must be an object of command facts");
139
- }
140
- let mut invocation = serde_json::Map::new();
141
- invocation.insert("to".to_string(), to);
142
- invocation.insert("with".to_string(), facts);
143
- Ok(serde_json::Value::Object(invocation))
144
- }
145
-
146
- // No `to` route: used only when a command creates a new aggregate, so
147
- // there is no receiver yet to route to.
148
- pub fn facts_invocation(facts: serde_json::Value) -> anyhow::Result<serde_json::Value> {
149
- if !facts.is_object() {
150
- anyhow::bail!("with must be an object of command facts");
151
- }
152
- let mut invocation = serde_json::Map::new();
153
- invocation.insert("with".to_string(), facts);
154
- Ok(serde_json::Value::Object(invocation))
155
- }
156
-
157
- // Route-aware entry point; `handle` below stays the compatibility path
158
- // for callers still sending the older mixed args shape.
159
- pub async fn handle_routed(
160
- host: Host<'_>,
161
- verb: &str,
162
- to: serde_json::Value,
163
- facts: serde_json::Value,
164
- role: Option<&str>,
165
- ) -> anyhow::Result<Outcome> {
166
- handle(host, verb, routed_invocation(to, facts)?, role).await
167
- }
168
-
169
- /// `handle_routed` for a caller the host can name.
170
- pub async fn handle_routed_as(
171
- host: Host<'_>,
172
- verb: &str,
173
- to: serde_json::Value,
174
- facts: serde_json::Value,
175
- caller: Caller<'_>,
176
- ) -> anyhow::Result<Outcome> {
177
- handle_as(host, verb, routed_invocation(to, facts)?, caller).await
178
- }
179
-
180
- // Facts-only counterpart to `handle_routed`.
181
- pub async fn handle_facts(
182
- host: Host<'_>,
183
- verb: &str,
184
- facts: serde_json::Value,
185
- role: Option<&str>,
186
- ) -> anyhow::Result<Outcome> {
187
- handle(host, verb, facts_invocation(facts)?, role).await
188
- }
189
-
190
- /// `handle_facts` for a caller the host can name.
191
- pub async fn handle_facts_as(
192
- host: Host<'_>,
193
- verb: &str,
194
- facts: serde_json::Value,
195
- caller: Caller<'_>,
196
- ) -> anyhow::Result<Outcome> {
197
- handle_as(host, verb, facts_invocation(facts)?, caller).await
198
- }
199
-
200
- // A Mutex, not a bare Arc<Client>: `handle` needs `Client::transaction`,
201
- // which takes `&mut Client`. Locking also means concurrent callers on
202
- // this connection can't interleave statements.
203
- //
204
- // `config` is required, not optional: `main.rs` already refused to boot
205
- // unless the configured domain/era is provisioned and current, so by the
206
- // time `handle` runs that schema is guaranteed to exist.
207
- //
208
- // `role` is optional, matching `Adapters::Lambda::Client#dispatch`'s own
209
- // `role: nil` default: a step with no role looks the same on the wire
210
- // whether or not a caller is bound.
211
- //
212
- // This entry point and `handle_facts`/`handle_routed` are the host's own: a call with no role is a
213
- // host-internal dispatch and is unchecked in every enforcement mode. A call that names a role is
214
- // still checked against it. Callers outside the host go through the `_as` entry points.
215
- pub async fn handle(
216
- host: Host<'_>,
217
- verb: &str,
218
- args: serde_json::Value,
219
- role: Option<&str>,
220
- ) -> anyhow::Result<Outcome> {
221
- let origin = if role.is_none() {
222
- Origin::HostInternal
223
- } else {
224
- Origin::External
225
- };
226
- dispatch_in(
227
- Enforcement::from_env(),
228
- origin,
229
- host,
230
- verb,
231
- args,
232
- Caller {
233
- role,
234
- actor_id: None,
235
- },
236
- )
237
- .await
238
- }
239
-
240
- /// Dispatches for a caller, applying `HECKS_ROLE_ENFORCEMENT`: unidentified callers are refused (or,
241
- /// in shadow, logged as would-be refusals and let through).
242
- pub async fn handle_as(
243
- host: Host<'_>,
244
- verb: &str,
245
- args: serde_json::Value,
246
- caller: Caller<'_>,
247
- ) -> anyhow::Result<Outcome> {
248
- dispatch_in(
249
- Enforcement::from_env(),
250
- Origin::External,
251
- host,
252
- verb,
253
- args,
254
- caller,
255
- )
256
- .await
257
- }
258
-
259
- async fn dispatch_in(
260
- mode: Enforcement,
261
- origin: Origin,
262
- host: Host<'_>,
263
- verb: &str,
264
- args: serde_json::Value,
265
- caller: Caller<'_>,
266
- ) -> anyhow::Result<Outcome> {
267
- let checked = if origin == Origin::HostInternal {
268
- caller
269
- } else {
270
- mode.effective(caller)
271
- };
272
- let outcome = run(host, verb, args.clone(), checked).await?;
273
- // Only a caller that stated no role (unidentified, or an actor Governance holds no grant for)
274
- // is let through; a stated role that does not match stays refused, as it is under `Off`.
275
- if mode == Enforcement::Shadow
276
- && caller.role.is_none()
277
- && !outcome.accepted
278
- && refused_for_role(&outcome.result)
279
- {
280
- crate::log::error(
281
- "would_refuse_role",
282
- serde_json::json!({
283
- "verb": verb, "role": checked.role, "actor_id": checked.actor_id,
284
- "refusals": outcome.result.get("refusals"),
285
- }),
286
- );
287
- return run(host, verb, args, Caller::default()).await;
288
- }
289
- Ok(outcome)
290
- }
291
-
292
- async fn run(
293
- host: Host<'_>,
294
- verb: &str,
295
- args: serde_json::Value,
296
- caller: Caller<'_>,
297
- ) -> anyhow::Result<Outcome> {
298
- let Host {
299
- client,
300
- wasm_path,
301
- config,
302
- invoker,
303
- ..
304
- } = host;
305
- let role = caller.role;
306
- let mut guard = client.lock().await;
307
- let txn = guard.transaction().await?;
308
-
309
- // Locks the whole read-then-append sequence, not just the INSERT:
310
- // two concurrent invocations could otherwise both rehydrate against
311
- // the same prior steps and both pass a uniqueness check the domain
312
- // thinks it's enforcing. Domain-scoped so unrelated domains sharing
313
- // one Postgres instance don't serialize against each other.
314
- txn.execute(
315
- "SELECT pg_advisory_xact_lock(hashtext('hecks_lambda_journal.' || $1::text))",
316
- &[&config.domain],
317
- )
318
- .await?;
319
-
320
- // Reads the last snapshot plus only the journal rows after it,
321
- // instead of replaying the whole history every time. `None` covers
322
- // a brand-new domain and one with pre-snapshot history alike; both
323
- // fall back to one full replay, which then leaves a snapshot behind.
324
- let snapshot = journal::load_snapshot(&txn).await?;
325
- // `sagas_backfilled` is a latch, not a live check of whether
326
- // `hecks_lambda_sagas` is empty (empty is the ordinary state, not a
327
- // signal). Only matters for a domain with real history predating
328
- // saga durability, where the snapshot cache would otherwise never
329
- // re-derive sagas from the journal.
330
- let needs_saga_backfill = snapshot.as_ref().is_some_and(|s| !s.sagas_backfilled);
331
- let mut steps = match &snapshot {
332
- Some(s) if !needs_saga_backfill => journal::load_steps_after(&txn, s.ordinal).await?,
333
- _ => journal::load_steps(&txn).await?,
334
- };
335
- // Role goes only on this outermost step; replayed history already
336
- // carries whatever role it was dispatched with. Built as a mutable
337
- // step, not inlined into the `json!` literal, so a roleless call
338
- // still omits the `"role"` key entirely rather than sending null.
339
- // A fact the command `needs` is answered here, before the kernel's gates read the arguments and
340
- // before the step is journaled, so a replay re-dispatches the recorded answer (ADR 0081).
341
- let mut args = args;
342
- if let Some(domain_ir) = crate::ir::ir() {
343
- crate::needs::fill_needs(domain_ir, verb, &mut args, &crate::needs::ProcessClock);
344
- // An argument with a declared default is filled the same way, so the journal holds it too.
345
- crate::needs::fill_defaults(domain_ir, verb, &mut args);
346
- }
347
- let mut step = serde_json::json!({ "verb": verb, "args": args.clone() });
348
- if let Some(role) = role {
349
- step["role"] = serde_json::Value::String(role.to_string());
350
- }
351
- if let Some(actor_id) = caller.actor_id {
352
- step["actor_id"] = serde_json::Value::String(actor_id.to_string());
353
- }
354
- // Stamped only on this live step; replayed history already carries
355
- // its own real timestamp, and re-stamping today's time onto a
356
- // replay would be wrong, not just redundant.
357
- step["occurred_at"] = serde_json::Value::String(crate::auth::httpdate_now());
358
- steps.push(step);
359
- // Must match `steps`' own choice above: a full replay needs an empty
360
- // seed, or the kernel replays into a world that already has every
361
- // record being created again, refusing each as a false AlreadyExists.
362
- let seed = if needs_saga_backfill {
363
- serde_json::json!({})
364
- } else {
365
- snapshot
366
- .as_ref()
367
- .map_or_else(|| serde_json::json!({}), |s| s.seed.clone())
368
- };
369
-
370
- // Read inside the same advisory-locked transaction: the lock covers
371
- // saga state too, not just the aggregate journal/snapshot.
372
- let saga_rows = journal::load_sagas(&txn).await?;
373
- let sagas_seed = serde_json::json!(saga_rows
374
- .iter()
375
- .map(|r| serde_json::json!({
376
- "process_manager": r.process_manager,
377
- "correlation": r.correlation,
378
- "state": r.state,
379
- "memory": r.memory,
380
- "completed_compensations": r.completed_compensations,
381
- }))
382
- .collect::<Vec<_>>());
383
-
384
- let mut input = serde_json::json!({ "seed": seed, "steps": steps, "sagas": sagas_seed });
385
- // Lets the kernel answer a reaction's command that needs a fact the same way (ADR 0081).
386
- if let Some(domain_ir) = crate::ir::ir() {
387
- let table = crate::needs::table(domain_ir);
388
- if table.as_object().is_some_and(|t| !t.is_empty()) {
389
- input["needs"] = table;
390
- }
391
- let defaults = crate::needs::defaults_table(domain_ir);
392
- if defaults.as_object().is_some_and(|t| !t.is_empty()) {
393
- input["defaults"] = defaults;
394
- }
395
- }
396
- let input = input.to_string();
397
- // `wasm_runner::run` is sync, and wasmtime-wasi's sync bridge spins up
398
- // its own tokio runtime internally — fatal on a thread already
399
- // driving one. `spawn_blocking` moves it off this async runtime.
400
- let owned_wasm_path = wasm_path.to_path_buf();
401
- let output =
402
- tokio::task::spawn_blocking(move || wasm_runner::run(&owned_wasm_path, &input)).await??;
403
- // A kernel failure returns here, before anything is written: dropping `txn`
404
- // rolls it back, so no journal row, snapshot, saga or mutation is saved.
405
- let mut result = parse_kernel_output(&output)?;
406
-
407
- // See journal.rs's own header: every prior step in this replay
408
- // already succeeded once, deterministically, so the only step that
409
- // can legitimately show up in `refusals` is the new one just
410
- // appended last.
411
- let accepted = classify(&result) == KernelAnswer::Accepted;
412
-
413
- if accepted {
414
- let ordinal = journal::append(&txn, verb, &args).await?;
415
-
416
- // The kernel's "instances" output is already the exact seed
417
- // shape, so nothing here re-derives or filters it.
418
- let new_seed = result
419
- .get("instances")
420
- .cloned()
421
- .unwrap_or_else(|| serde_json::json!({}));
422
- journal::save_snapshot(&txn, ordinal, &new_seed).await?;
423
-
424
- // Persisted in the same transaction, so a saga checkpoint can
425
- // never commit independently of the command that produced it.
426
- // Reconciled against `saga_rows` (pre-run state): present now
427
- // is upserted, present before but absent now is deleted.
428
- if let Some(saga_snapshot) = result.get("saga_snapshot").and_then(|v| v.as_array()) {
429
- let mut still_present: std::collections::HashSet<(String, String)> =
430
- std::collections::HashSet::new();
431
- for entry in saga_snapshot {
432
- let process_manager = entry
433
- .get("process_manager")
434
- .and_then(|v| v.as_str())
435
- .ok_or_else(|| {
436
- anyhow::anyhow!("saga_snapshot entry missing \"process_manager\": {entry}")
437
- })?;
438
- let correlation = entry
439
- .get("correlation")
440
- .and_then(|v| v.as_str())
441
- .ok_or_else(|| {
442
- anyhow::anyhow!("saga_snapshot entry missing \"correlation\": {entry}")
443
- })?;
444
- let state = entry.get("state").and_then(|v| v.as_str()).ok_or_else(|| {
445
- anyhow::anyhow!("saga_snapshot entry missing \"state\": {entry}")
446
- })?;
447
- let memory = entry
448
- .get("memory")
449
- .cloned()
450
- .unwrap_or_else(|| serde_json::json!({}));
451
- let completed_compensations = entry
452
- .get("completed_compensations")
453
- .cloned()
454
- .unwrap_or_else(|| serde_json::json!([]));
455
-
456
- journal::save_saga(
457
- &txn,
458
- process_manager,
459
- correlation,
460
- state,
461
- &memory,
462
- &completed_compensations,
463
- )
464
- .await?;
465
- still_present.insert((process_manager.to_string(), correlation.to_string()));
466
- }
467
- for row in &saga_rows {
468
- let key = (row.process_manager.clone(), row.correlation.clone());
469
- if !still_present.contains(&key) {
470
- journal::delete_saga(&txn, &row.process_manager, &row.correlation).await?;
471
- }
472
- }
473
- }
474
-
475
- // One entry per step in `steps`, so the last entry is this
476
- // call's own new step. Skipped when `config.era` is `None`: a
477
- // domain with nothing lineage-capable bound never provisions a
478
- // head-snapshot table to write into (ADR 0034).
479
- let step_mutations = if config.era.is_some() {
480
- result
481
- .get("mutations")
482
- .and_then(|m| m.as_array())
483
- .and_then(|steps| steps.last())
484
- .and_then(|last| last.as_array())
485
- .cloned()
486
- .unwrap_or_default()
487
- } else {
488
- Vec::new()
489
- };
490
-
491
- for mutation in &step_mutations {
492
- let aggregate = mutation
493
- .get("aggregate")
494
- .and_then(|v| v.as_str())
495
- .ok_or_else(|| {
496
- anyhow::anyhow!("mutation record missing \"aggregate\": {mutation}")
497
- })?;
498
- // `era.is_some()` means the lineage subsystem exists, not that
499
- // this aggregate has a mirror: `mint` only provisions head
500
- // snapshots for the capable set, so mirroring anything else
501
- // would upsert into a relation nobody created.
502
- if !config.mirrors(aggregate) {
503
- continue;
504
- }
505
- let id = mutation
506
- .get("id")
507
- .and_then(|v| v.as_str())
508
- .ok_or_else(|| anyhow::anyhow!("mutation record missing \"id\": {mutation}"))?;
509
- let operation = mutation
510
- .get("operation")
511
- .and_then(|v| v.as_str())
512
- .ok_or_else(|| {
513
- anyhow::anyhow!("mutation record missing \"operation\": {mutation}")
514
- })?;
515
- let state = mutation
516
- .get("state")
517
- .ok_or_else(|| anyhow::anyhow!("mutation record missing \"state\": {mutation}"))?;
518
-
519
- journal::append_lineage_mutation(
520
- &txn,
521
- config,
522
- &journal::Mutation {
523
- aggregate,
524
- id,
525
- operation,
526
- state,
527
- },
528
- )
529
- .await?;
530
- }
531
- }
532
-
533
- // Only this step's own matches, same reason as `step_mutations`
534
- // above: replaying a prior step's reaction would re-invoke a sibling
535
- // Lambda on every later replay. Captured before commit, delivered after.
536
- let pending_cross_domain = result
537
- .get("cross_domain_reactions")
538
- .and_then(|r| r.as_array())
539
- .and_then(|steps| steps.last())
540
- .and_then(|last| last.as_array())
541
- .cloned()
542
- .unwrap_or_default();
543
-
544
- // Commits (and releases the advisory lock) whether or not anything
545
- // was appended — a refused command still needs the lock released
546
- // for the next invocation to proceed.
547
- txn.commit().await?;
548
-
549
- // Delivered after commit, deliberately: the local command already
550
- // succeeded and is durable, so a cross-Lambda notification is
551
- // best-effort, not a precondition of it. A fault that survives every
552
- // retry still fails this call via `?`, but never unwinds an
553
- // already-committed transaction.
554
- // Every reaction gets its own attempt even after an earlier fails:
555
- // returning early would silently drop the rest, undelivered and
556
- // never dead-lettered (`SafeDepositBox.Surrender` fires two from one
557
- // command). `first_failure` remembers only the first error without
558
- // cutting the loop short.
559
- let mut cross_domain_deliveries = Vec::new();
560
- let mut first_failure: Option<anyhow::Error> = None;
561
- for reaction in &pending_cross_domain {
562
- match lambda_client::deliver_with_retry(invoker, reaction).await {
563
- Ok(record) => {
564
- if let Some(response) = result.as_object_mut() {
565
- if let Some(reactions) =
566
- response.get_mut("reactions").and_then(|r| r.as_array_mut())
567
- {
568
- reactions.push(serde_json::json!({
569
- "policy": record.policy, "on": reaction.get("on").and_then(|v| v.as_str()).unwrap_or_default(),
570
- "trigger": record.target_verb, "delivered": record.delivered, "reason": record.reason,
571
- }));
572
- }
573
- }
574
- cross_domain_deliveries.push(record.to_json());
575
- }
576
- Err(failure) => {
577
- let error_text = format!("{:#}", failure.error);
578
- crate::log::error(
579
- "cross_domain_delivery_failed",
580
- serde_json::json!({
581
- "policy": failure.policy, "target_domain": failure.target_domain,
582
- "target_verb": failure.target_verb, "attempts": failure.attempts, "error": error_text,
583
- }),
584
- );
585
- journal::record_dead_letter(
586
- &*guard,
587
- &failure.policy,
588
- &failure.target_domain,
589
- &failure.target_verb,
590
- &failure.payload,
591
- &error_text,
592
- failure.attempts as i32,
593
- )
594
- .await?;
595
- if first_failure.is_none() {
596
- first_failure = Some(failure.error);
597
- }
598
- }
599
- }
600
- }
601
- if let Some(error) = first_failure {
602
- return Err(error);
603
- }
604
- if let Some(response) = result.as_object_mut() {
605
- response.insert(
606
- "cross_domain_deliveries".to_string(),
607
- serde_json::Value::Array(cross_domain_deliveries),
608
- );
609
- }
610
-
611
- log_command(verb, role, accepted, &result);
612
- Ok(Outcome { result, accepted })
613
- }
1
+ //! The dispatch path against a real Postgres and the Memory store: what `rust_host_core`'s
2
+ //! dispatch does is only observable through a backend, and the backends live here.
614
3
 
615
- // One line per dispatched command: what was asked (verb, role — never
616
- // the facts) and what the domain answered.
617
- fn log_command(verb: &str, role: Option<&str>, accepted: bool, result: &serde_json::Value) {
618
- let fields = command_fields(verb, role, accepted, result);
619
- if accepted {
620
- crate::log::info("command", fields);
621
- } else {
622
- crate::log::error("command", fields);
623
- }
624
- }
625
-
626
- fn command_fields(
627
- verb: &str,
628
- role: Option<&str>,
629
- accepted: bool,
630
- result: &serde_json::Value,
631
- ) -> serde_json::Value {
632
- let events: Vec<serde_json::Value> = result
633
- .get("events")
634
- .and_then(|v| v.as_array())
635
- .map(|items| {
636
- items
637
- .iter()
638
- .filter_map(|item| item.get("name").cloned())
639
- .collect()
640
- })
641
- .unwrap_or_default();
642
- serde_json::json!({
643
- "verb": verb, "role": role, "accepted": accepted, "events": events,
644
- "refusals": result.get("refusals").cloned().unwrap_or_else(|| serde_json::json!([])),
645
- })
646
- }
647
-
648
- // Read-only: the same seed path `handle` uses, minus the new step and
649
- // any write. No advisory lock needed since nothing here writes.
650
- // Returns the whole kernel result verbatim, not just `instances`, so a
651
- // caller can tell an empty `refusals` from one that was never checked.
652
- pub async fn read(client: &Mutex<Client>, wasm_path: &Path) -> anyhow::Result<serde_json::Value> {
653
- let guard = client.lock().await;
654
- let snapshot = journal::load_snapshot(&*guard).await?;
655
-
656
- if let Some(s) = &snapshot {
657
- let steps_since = journal::load_steps_after(&*guard, s.ordinal).await?;
658
- drop(guard);
659
-
660
- if steps_since.is_empty() {
661
- // No wasm invocation needed: the snapshot's seed already is
662
- // this domain's current "instances" output. Empty
663
- // events/refusals is correct too — a read reports current
664
- // state, never event/refusal history.
665
- return Ok(serde_json::json!({ "instances": s.seed, "events": [], "refusals": [] }));
666
- }
667
-
668
- // Self-healing: if steps ever go unaccounted for after the last
669
- // snapshot, seeding from it and replaying just the gap is
670
- // still correct and cheap.
671
- let input = serde_json::json!({ "seed": &s.seed, "steps": steps_since }).to_string();
672
- let owned_wasm_path = wasm_path.to_path_buf();
673
- let output =
674
- tokio::task::spawn_blocking(move || wasm_runner::run(&owned_wasm_path, &input))
675
- .await??;
676
- return parse_kernel_output(&output);
677
- }
678
-
679
- // No snapshot: either a brand-new domain, or one with history from
680
- // before this snapshot cache existed — either way, one full replay
681
- // catches it up.
682
- let steps = journal::load_steps(&*guard).await?;
683
- drop(guard);
684
- let input = serde_json::json!({ "steps": steps }).to_string();
685
- let owned_wasm_path = wasm_path.to_path_buf();
686
- let output =
687
- tokio::task::spawn_blocking(move || wasm_runner::run(&owned_wasm_path, &input)).await??;
688
- parse_kernel_output(&output)
689
- }
690
-
691
- // A declared query, answered against current state. Seeds from `read`
692
- // rather than replaying history itself, so it sees exactly the state a
693
- // read would report and pays at most one wasm invocation.
694
- pub async fn query(
695
- client: &Mutex<Client>,
696
- wasm_path: &Path,
697
- question: &str,
698
- args: serde_json::Value,
699
- ) -> anyhow::Result<serde_json::Value> {
700
- // A fact the query `needs` is answered before the kernel checks its arguments (ADR 0081).
701
- let mut args = args;
702
- if let Some(domain_ir) = crate::ir::ir() {
703
- crate::needs::fill_query_needs(domain_ir, question, &mut args, &crate::needs::ProcessClock);
704
- }
705
- let state = read(client, wasm_path).await?;
706
- let seed = state
707
- .get("instances")
708
- .cloned()
709
- .unwrap_or_else(|| serde_json::json!({}));
710
- let input = serde_json::json!({ "seed": seed, "steps": [{ "query": question, "args": args }] })
711
- .to_string();
712
- let owned_wasm_path = wasm_path.to_path_buf();
713
- let output =
714
- tokio::task::spawn_blocking(move || wasm_runner::run(&owned_wasm_path, &input)).await??;
715
- parse_kernel_output(&output)
716
- }
717
-
718
- /// Answers `answer` (a kernel answer already computed and persisted) in the bare shape a caller
719
- /// reads, by one more wasm run of the kernel's `{"present": answer}` input: each single-field
720
- /// value object as its sole field. Nothing is stored or replayed.
721
- pub async fn present(
722
- wasm_path: &Path,
723
- answer: serde_json::Value,
724
- ) -> anyhow::Result<serde_json::Value> {
725
- let owned_wasm_path = wasm_path.to_path_buf();
726
- tokio::task::spawn_blocking(move || {
727
- present_with(&answer, |input| wasm_runner::run(&owned_wasm_path, input))
728
- })
729
- .await?
730
- }
731
-
732
- /// Presents `answer` as `present` does, with `run` standing in for the kernel (text in, text out).
733
- pub(crate) fn present_with(
734
- answer: &serde_json::Value,
735
- run: impl FnOnce(&str) -> anyhow::Result<String>,
736
- ) -> anyhow::Result<serde_json::Value> {
737
- let input = serde_json::json!({ "present": answer }).to_string();
738
- parse_kernel_output(&run(&input)?)
739
- }
4
+ #![allow(unused_imports)]
740
5
 
741
6
  #[cfg(test)]
742
- pub(crate) mod tests {
7
+ pub mod tests {
743
8
 
744
9
  #[test]
745
10
  fn a_command_log_line_names_the_verb_events_and_refusals_but_never_the_facts() {
@@ -755,15 +20,22 @@ pub(crate) mod tests {
755
20
  assert_eq!(fields["refusals"][0]["kind"], "AlreadyExists");
756
21
  assert!(!fields.to_string().contains("a@b.c"));
757
22
  }
758
- use super::*;
23
+ use crate::host::Host;
24
+ use crate::journal;
759
25
  use crate::journal::LineageConfig;
26
+ use crate::lambda_client;
760
27
  use crate::lambda_client::LambdaInvoker;
761
- use tokio_postgres::NoTls;
28
+ use crate::store::journal_sql;
29
+ use crate::store::{Accepted, DeadLetter, LineageWrite, Store, StoreExt, StoreFixtures};
30
+ use crate::wasm_runner;
31
+ use rust_host_core::dispatch::*;
32
+ use std::path::Path;
33
+ use tokio_postgres::{Client, NoTls};
762
34
 
763
35
  // A throwaway Postgres database per test, uniquely named so
764
36
  // `cargo test`'s default parallelism doesn't race two tests against
765
37
  // the same journal table.
766
- pub(crate) async fn scratch_db(name: &str) -> Mutex<Client> {
38
+ pub async fn scratch_db(name: &str) -> Store {
767
39
  let (admin, conn) = tokio_postgres::connect(&crate::test_pg::conninfo("postgres"), NoTls)
768
40
  .await
769
41
  .expect("connect to postgres");
@@ -785,14 +57,29 @@ pub(crate) mod tests {
785
57
  tokio::spawn(async move {
786
58
  let _ = conn.await;
787
59
  });
788
- journal::ensure_schema(&client).await.unwrap();
789
- Mutex::new(client)
60
+ journal_sql::ensure_schema(&client).await.unwrap();
61
+ crate::store::postgres_store(client)
62
+ }
63
+
64
+ /// An empty in-memory store, for tests that need a journal and nothing Postgres-specific.
65
+ pub fn memory_store() -> Store {
66
+ Store::memory(crate::store::MemoryStore::new())
67
+ }
68
+
69
+ /// The config a memory-backed host runs under: no era, so nothing mirrors into lineage.
70
+ pub fn memory_config(domain: &str) -> LineageConfig {
71
+ LineageConfig {
72
+ domain: domain.to_string(),
73
+ era: None,
74
+ mirrored: None,
75
+ seal_every: None,
76
+ }
790
77
  }
791
78
 
792
79
  // Not Ruby's real provisioning — just enough structure for
793
- // `journal::current_era` and `append_lineage_mutation` to succeed,
80
+ // `journal_sql::current_era` and `append_lineage_mutation` to succeed,
794
81
  // without reproducing Ruby's full DDL.
795
- pub(crate) async fn provision_lineage(
82
+ pub async fn provision_lineage(
796
83
  client: &Client,
797
84
  domain: &str,
798
85
  era: i32,
@@ -814,7 +101,7 @@ pub(crate) mod tests {
814
101
  .await
815
102
  .unwrap();
816
103
 
817
- let journal_table = format!("hecks_journal_{}", journal::snake(domain));
104
+ let journal_table = format!("hecks_journal_{}", journal_sql::snake(domain));
818
105
  client
819
106
  .batch_execute(&format!(
820
107
  "CREATE TABLE IF NOT EXISTS \"{journal_table}\" (
@@ -823,7 +110,8 @@ pub(crate) mod tests {
823
110
  aggregate text NOT NULL,
824
111
  aggregate_id text NOT NULL,
825
112
  operation text NOT NULL,
826
- state jsonb
113
+ state jsonb,
114
+ mirrors jsonb
827
115
  )"
828
116
  ))
829
117
  .await
@@ -832,9 +120,9 @@ pub(crate) mod tests {
832
120
  for name in aggregate_storage_names {
833
121
  // Domain-qualified (ADR 0059), matching what a real write
834
122
  // targets.
835
- let snapshot_table = journal::qualified_name(
123
+ let snapshot_table = journal_sql::qualified_name(
836
124
  domain,
837
- &format!("{}_head_snapshot_{era}", journal::snake(name)),
125
+ &format!("{}_head_snapshot_{era}", journal_sql::snake(name)),
838
126
  );
839
127
  client
840
128
  .batch_execute(&format!(
@@ -877,6 +165,7 @@ pub(crate) mod tests {
877
165
  domain: "Banking".to_string(),
878
166
  era: Some(1),
879
167
  mirrored: None,
168
+ seal_every: None,
880
169
  };
881
170
  let refused = handle_facts(
882
171
  Host::new(&client, &wasm, &everything, &invoker),
@@ -908,6 +197,7 @@ pub(crate) mod tests {
908
197
  domain: "Banking".to_string(),
909
198
  era: Some(1),
910
199
  mirrored: Some(std::collections::BTreeSet::new()),
200
+ seal_every: None,
911
201
  };
912
202
  let outcome = handle_facts(
913
203
  Host::new(&client, &wasm, &declared, &invoker),
@@ -953,6 +243,7 @@ pub(crate) mod tests {
953
243
  domain: "Banking".to_string(),
954
244
  era: Some(1),
955
245
  mirrored: Some(std::iter::once("Banking::Customer".to_string()).collect()),
246
+ seal_every: None,
956
247
  };
957
248
 
958
249
  handle_facts(
@@ -978,11 +269,12 @@ pub(crate) mod tests {
978
269
  assert_eq!(mirrored, 1);
979
270
  }
980
271
 
981
- fn test_config(domain: &str, era: i32) -> LineageConfig {
272
+ pub fn test_config(domain: &str, era: i32) -> LineageConfig {
982
273
  LineageConfig {
983
274
  domain: domain.to_string(),
984
275
  era: Some(era),
985
276
  mirrored: None,
277
+ seal_every: None,
986
278
  }
987
279
  }
988
280
 
@@ -1010,11 +302,11 @@ pub(crate) mod tests {
1010
302
  assert!(failed.is_err());
1011
303
  }
1012
304
 
1013
- pub(crate) fn wasm_path() -> std::path::PathBuf {
305
+ pub fn wasm_path() -> std::path::PathBuf {
1014
306
  std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../dist/banking.wasm")
1015
307
  }
1016
308
 
1017
- fn register(reference: &str) -> serde_json::Value {
309
+ pub fn register(reference: &str) -> serde_json::Value {
1018
310
  serde_json::json!({
1019
311
  "reference": { "value": reference },
1020
312
  "name": { "given": "Ada", "family": "Lovelace" },
@@ -1026,9 +318,8 @@ pub(crate) mod tests {
1026
318
  // query answered against whatever state the journal currently holds.
1027
319
  #[tokio::test]
1028
320
  async fn a_declared_query_answers_against_current_state() {
1029
- let client = scratch_db("rust_host_dispatch_test_query").await;
1030
- provision_lineage(&*client.lock().await, "Banking", 1, &["Customer"]).await;
1031
- let config = test_config("Banking", 1);
321
+ let client = crate::dispatch_tests::tests::memory_store();
322
+ let config = crate::dispatch_tests::tests::memory_config("Banking");
1032
323
 
1033
324
  handle(
1034
325
  Host::new(&client, &wasm_path(), &config, &lambda_client::NeverInvoker),
@@ -1110,7 +401,7 @@ pub(crate) mod tests {
1110
401
  assert!(outcome.accepted);
1111
402
  assert_eq!(outcome.result["refusals"].as_array().unwrap().len(), 0);
1112
403
  let guard = client.lock().await;
1113
- let steps = journal::load_steps(&*guard).await.unwrap();
404
+ let steps = journal_sql::load_steps(&*guard).await.unwrap();
1114
405
  assert_eq!(steps.len(), 1);
1115
406
 
1116
407
  // The lineage write itself: a row landed in the era-tagged,
@@ -1152,20 +443,59 @@ pub(crate) mod tests {
1152
443
  assert_eq!(id, "CUST-0001");
1153
444
  }
1154
445
 
446
+ // The seal is part of the commit, not a tool run afterwards: the second accepted command
447
+ // leaves a seal through the second lineage row, and the chain walks clean from it.
448
+ #[tokio::test]
449
+ async fn a_commit_seals_the_lineage_chain_every_so_many_rows() {
450
+ let client = scratch_db("rust_host_dispatch_seals").await;
451
+ provision_lineage(&*client.lock().await, "Banking", 1, &["Customer"]).await;
452
+ let config = LineageConfig {
453
+ seal_every: Some(2),
454
+ ..test_config("Banking", 1)
455
+ };
456
+
457
+ for id in ["CUST-0001", "CUST-0002", "CUST-0003"] {
458
+ let outcome = handle(
459
+ Host::new(&client, &wasm_path(), &config, &lambda_client::NeverInvoker),
460
+ "Banking::Customer.Register",
461
+ register(id),
462
+ None,
463
+ )
464
+ .await
465
+ .unwrap();
466
+ assert!(outcome.accepted, "{id} should be accepted");
467
+ }
468
+
469
+ let guard = client.lock().await;
470
+ let seals = crate::ledger::anchors(&*guard, "Banking").await.unwrap();
471
+ assert_eq!(
472
+ seals.iter().map(|seal| seal.through).collect::<Vec<_>>(),
473
+ vec![2],
474
+ "one seal, taken by the commit that made two rows unsealed"
475
+ );
476
+ assert_eq!(
477
+ crate::ledger::verify(&*guard, "Banking").await.unwrap(),
478
+ crate::ledger::Finding::Verified(crate::ledger::Report {
479
+ seals: 1,
480
+ entries: 2,
481
+ unsealed: 1
482
+ })
483
+ );
484
+ }
485
+
1155
486
  // Proves `occurred_at` end to end through the real wasm module: the
1156
487
  // string this crate stamps and sends is the one that comes back out
1157
488
  // in the returned event, not just what the in-kernel unit tests prove.
1158
489
  #[tokio::test]
1159
490
  async fn occurred_at_is_stamped_onto_every_returned_event_with_this_crate_s_own_real_clock() {
1160
- let client = scratch_db("rust_host_dispatch_test_occurred_at").await;
1161
- provision_lineage(&*client.lock().await, "Banking", 1, &["Customer"]).await;
491
+ let client = crate::dispatch_tests::tests::memory_store();
1162
492
 
1163
- let before = crate::auth::httpdate_now();
493
+ let before = crate::clock::iso8601_now();
1164
494
  let outcome = handle(
1165
495
  Host::new(
1166
496
  &client,
1167
497
  &wasm_path(),
1168
- &test_config("Banking", 1),
498
+ &crate::dispatch_tests::tests::memory_config("Banking"),
1169
499
  &lambda_client::NeverInvoker,
1170
500
  ),
1171
501
  "Banking::Customer.Register",
@@ -1174,7 +504,7 @@ pub(crate) mod tests {
1174
504
  )
1175
505
  .await
1176
506
  .unwrap();
1177
- let after = crate::auth::httpdate_now();
507
+ let after = crate::clock::iso8601_now();
1178
508
 
1179
509
  assert!(outcome.accepted, "{:?}", outcome.result["refusals"]);
1180
510
  let events = outcome.result["events"].as_array().unwrap();
@@ -1193,9 +523,8 @@ pub(crate) mod tests {
1193
523
 
1194
524
  #[tokio::test]
1195
525
  async fn rehydrates_prior_history_before_evaluating_the_new_command() {
1196
- let client = scratch_db("rust_host_dispatch_test_2").await;
1197
- provision_lineage(&*client.lock().await, "Banking", 1, &["Customer"]).await;
1198
- let config = test_config("Banking", 1);
526
+ let client = crate::dispatch_tests::tests::memory_store();
527
+ let config = crate::dispatch_tests::tests::memory_config("Banking");
1199
528
 
1200
529
  let first = handle(
1201
530
  Host::new(&client, &wasm_path(), &config, &lambda_client::NeverInvoker),
@@ -1228,7 +557,7 @@ pub(crate) mod tests {
1228
557
  "duplicate registration should be refused, proving prior state was rehydrated: {:?}",
1229
558
  second.result
1230
559
  );
1231
- let steps = journal::load_steps(&*client.lock().await).await.unwrap();
560
+ let steps = client.journal_steps().await;
1232
561
  assert_eq!(
1233
562
  steps.len(),
1234
563
  1,
@@ -1261,11 +590,11 @@ pub(crate) mod tests {
1261
590
  );
1262
591
 
1263
592
  let guard = client.lock().await;
1264
- let snapshot = journal::load_snapshot(&*guard)
593
+ let snapshot = journal_sql::load_snapshot(&*guard)
1265
594
  .await
1266
595
  .unwrap()
1267
596
  .expect("a snapshot should exist after the first accepted command");
1268
- let tail = journal::load_steps_after(&*guard, snapshot.ordinal)
597
+ let tail = journal_sql::load_steps_after(&*guard, snapshot.ordinal)
1269
598
  .await
1270
599
  .unwrap();
1271
600
  assert!(tail.is_empty(), "the snapshot should already reflect every accepted command, leaving nothing to replay");
@@ -1279,7 +608,7 @@ pub(crate) mod tests {
1279
608
  );
1280
609
  }
1281
610
 
1282
- let final_snapshot = journal::load_snapshot(&*client.lock().await)
611
+ let final_snapshot = journal_sql::load_snapshot(&*client.lock().await)
1283
612
  .await
1284
613
  .unwrap()
1285
614
  .unwrap();
@@ -1356,7 +685,7 @@ pub(crate) mod tests {
1356
685
  .unwrap();
1357
686
  assert!(first.accepted, "{:?}", first.result);
1358
687
 
1359
- let whole_world = journal::load_snapshot(&*client.lock().await)
688
+ let whole_world = journal_sql::load_snapshot(&*client.lock().await)
1360
689
  .await
1361
690
  .unwrap()
1362
691
  .unwrap()
@@ -1467,7 +796,7 @@ pub(crate) mod tests {
1467
796
  .await
1468
797
  .unwrap();
1469
798
  assert!(second.accepted, "{:?}", second.result);
1470
- let after = journal::load_snapshot(&*client.lock().await)
799
+ let after = journal_sql::load_snapshot(&*client.lock().await)
1471
800
  .await
1472
801
  .unwrap()
1473
802
  .unwrap()
@@ -1482,7 +811,7 @@ pub(crate) mod tests {
1482
811
  }
1483
812
 
1484
813
  // The snapshot's ordinal and exact seed text, and how many commands are journaled.
1485
- async fn snapshot_and_journal(client: &Mutex<Client>) -> (i64, String, i64) {
814
+ async fn snapshot_and_journal(client: &Store) -> (i64, String, i64) {
1486
815
  let guard = client.lock().await;
1487
816
  let snapshot = guard
1488
817
  .query_one("SELECT ordinal, seed::text FROM hecks_lambda_snapshot", &[])
@@ -1504,9 +833,8 @@ pub(crate) mod tests {
1504
833
  // appended — silently violating the uniqueness the domain
1505
834
  // itself enforces. With the lock serializing the whole
1506
835
  // read-then-append sequence, exactly one must be accepted.
1507
- let client = scratch_db("rust_host_dispatch_test_3").await;
1508
- provision_lineage(&*client.lock().await, "Banking", 1, &["Customer"]).await;
1509
- let config = test_config("Banking", 1);
836
+ let client = crate::dispatch_tests::tests::memory_store();
837
+ let config = crate::dispatch_tests::tests::memory_config("Banking");
1510
838
  let wasm_path = wasm_path();
1511
839
 
1512
840
  let (first, second) = tokio::join!(
@@ -1530,7 +858,7 @@ pub(crate) mod tests {
1530
858
  "exactly one of two concurrent duplicate registrations should be accepted: {:?} / {:?}",
1531
859
  first.result, second.result
1532
860
  );
1533
- let steps = journal::load_steps(&*client.lock().await).await.unwrap();
861
+ let steps = client.journal_steps().await;
1534
862
  assert_eq!(
1535
863
  steps.len(),
1536
864
  1,
@@ -1540,14 +868,13 @@ pub(crate) mod tests {
1540
868
 
1541
869
  #[tokio::test]
1542
870
  async fn read_reflects_prior_dispatches_without_persisting_anything_new() {
1543
- let client = scratch_db("rust_host_dispatch_test_4").await;
1544
- provision_lineage(&*client.lock().await, "Banking", 1, &["Customer"]).await;
871
+ let client = crate::dispatch_tests::tests::memory_store();
1545
872
 
1546
873
  handle(
1547
874
  Host::new(
1548
875
  &client,
1549
876
  &wasm_path(),
1550
- &test_config("Banking", 1),
877
+ &crate::dispatch_tests::tests::memory_config("Banking"),
1551
878
  &lambda_client::NeverInvoker,
1552
879
  ),
1553
880
  "Banking::Customer.Register",
@@ -1574,19 +901,19 @@ pub(crate) mod tests {
1574
901
 
1575
902
  // The sharp proof a read never appends: the journal is exactly
1576
903
  // as long after the read as it was before it.
1577
- let steps = journal::load_steps(&*client.lock().await).await.unwrap();
904
+ let steps = client.journal_steps().await;
1578
905
  assert_eq!(steps.len(), 1, "a read must never persist a journal row");
1579
906
  }
1580
907
 
1581
908
  // Records every call and answers with a canned "nothing refused"
1582
909
  // body, proving a cross-domain policy reaches `lambda_client::deliver`
1583
910
  // with the right name/payload end to end, short of a real AWS Lambda.
1584
- struct RecordingInvoker {
911
+ pub struct RecordingInvoker {
1585
912
  calls: std::sync::Mutex<Vec<(String, String)>>,
1586
913
  }
1587
914
 
1588
915
  impl RecordingInvoker {
1589
- fn new() -> Self {
916
+ pub fn new() -> Self {
1590
917
  Self {
1591
918
  calls: std::sync::Mutex::new(Vec::new()),
1592
919
  }
@@ -1613,15 +940,8 @@ pub(crate) mod tests {
1613
940
 
1614
941
  #[tokio::test]
1615
942
  async fn a_cross_domain_policy_delivers_through_the_real_rehydrate_replay_path() {
1616
- let client = scratch_db("rust_host_dispatch_test_6").await;
1617
- provision_lineage(
1618
- &*client.lock().await,
1619
- "Banking",
1620
- 1,
1621
- &["Customer", "Account"],
1622
- )
1623
- .await;
1624
- let config = test_config("Banking", 1);
943
+ let client = crate::dispatch_tests::tests::memory_store();
944
+ let config = crate::dispatch_tests::tests::memory_config("Banking");
1625
945
  let invoker = RecordingInvoker::new();
1626
946
 
1627
947
  handle(
@@ -1693,7 +1013,7 @@ pub(crate) mod tests {
1693
1013
  .find(|r| r["policy"] == "ReviewOnFreeze")
1694
1014
  .expect("ReviewOnFreeze should appear in \"reactions\" too");
1695
1015
  assert_eq!(merged["on"], "AccountFrozen");
1696
- assert_eq!(merged["trigger"], "Compliance::AccountFreezeReview.Open");
1016
+ assert_eq!(merged["trigger"], "Compliance::Review.Open");
1697
1017
  assert_eq!(merged["delivered"], true);
1698
1018
 
1699
1019
  {
@@ -1704,9 +1024,10 @@ pub(crate) mod tests {
1704
1024
  let sent: serde_json::Value = serde_json::from_str(payload).unwrap();
1705
1025
  // Fully qualified, not the bare "Compliance.OpenReview":
1706
1026
  // generated match arms are always "Domain::Aggregate.Command",
1707
- // and Compliance's own aggregate is named `AccountFreezeReview`.
1708
- assert_eq!(sent["verb"], "Compliance::AccountFreezeReview.Open");
1709
- assert_eq!(sent["args"]["number"]["value"], "acct-freeze-me");
1027
+ // and Compliance's own aggregate is named `Review`.
1028
+ assert_eq!(sent["verb"], "Compliance::Review.Open");
1029
+ assert_eq!(sent["args"]["subject_kind"], "account_freeze");
1030
+ assert_eq!(sent["args"]["subject_key"], "acct-freeze-me");
1710
1031
  }
1711
1032
 
1712
1033
  // Never re-delivered on a later replay: `pending_cross_domain`
@@ -1734,9 +1055,8 @@ pub(crate) mod tests {
1734
1055
  // registry — real, corpus-declared.
1735
1056
  #[tokio::test]
1736
1057
  async fn a_role_gated_command_is_actually_checked_against_the_caller_role() {
1737
- let client = scratch_db("rust_host_dispatch_test_7").await;
1738
- provision_lineage(&*client.lock().await, "Banking", 1, &["Customer"]).await;
1739
- let config = test_config("Banking", 1);
1058
+ let client = crate::dispatch_tests::tests::memory_store();
1059
+ let config = crate::dispatch_tests::tests::memory_config("Banking");
1740
1060
 
1741
1061
  // The correct role succeeds, same as every other registration,
1742
1062
  // just now with a caller actually bound and matching.
@@ -1781,7 +1101,7 @@ pub(crate) mod tests {
1781
1101
 
1782
1102
  // And the refused command was never persisted — same discipline
1783
1103
  // every other refusal in this file is held to.
1784
- let steps = journal::load_steps(&*client.lock().await).await.unwrap();
1104
+ let steps = client.journal_steps().await;
1785
1105
  assert_eq!(
1786
1106
  steps.len(),
1787
1107
  1,
@@ -1789,6 +1109,25 @@ pub(crate) mod tests {
1789
1109
  );
1790
1110
  }
1791
1111
 
1112
+ // ADR 0081 keeps a needed fact the caller supplied, but only the host and its peers may: a
1113
+ // request's `now` would move a time-window guard.
1114
+ #[test]
1115
+ fn only_a_request_from_outside_the_host_has_its_needed_facts_replaced() {
1116
+ let ir = serde_json::json!({"name": "Lease", "aggregates": [{"name": "Slot", "commands": [
1117
+ {"name": "Take", "attributes": [{"name": "now", "type": "Integer"}],
1118
+ "needs": [{"fact": "now"}]}
1119
+ ]}]});
1120
+ let fill = |origin: Origin| {
1121
+ let mut args = serde_json::json!({"with": {"now": 42}});
1122
+ let clock = crate::needs::FixedClock(7);
1123
+ fill_arguments(origin, &ir, "Lease::Slot.Take", &mut args, &clock);
1124
+ args["with"]["now"].clone()
1125
+ };
1126
+ assert_eq!(fill(Origin::Request), serde_json::json!(7));
1127
+ assert_eq!(fill(Origin::External), serde_json::json!(42));
1128
+ assert_eq!(fill(Origin::HostInternal), serde_json::json!(42));
1129
+ }
1130
+
1792
1131
  #[test]
1793
1132
  fn enforcement_reads_off_shadow_and_enforce_and_treats_anything_else_as_off() {
1794
1133
  assert_eq!(Enforcement::parse(None), Enforcement::Off);
@@ -1825,9 +1164,8 @@ pub(crate) mod tests {
1825
1164
  #[tokio::test]
1826
1165
  async fn an_unidentified_caller_is_refused_when_enforced_and_let_through_when_off_or_shadowed()
1827
1166
  {
1828
- let client = scratch_db("rust_host_dispatch_test_enforce").await;
1829
- provision_lineage(&*client.lock().await, "Banking", 1, &["Customer"]).await;
1830
- let config = test_config("Banking", 1);
1167
+ let client = crate::dispatch_tests::tests::memory_store();
1168
+ let config = crate::dispatch_tests::tests::memory_config("Banking");
1831
1169
  let go = |mode: Enforcement, id: &'static str| {
1832
1170
  let client = &client;
1833
1171
  let config = &config;
@@ -1872,7 +1210,7 @@ pub(crate) mod tests {
1872
1210
  shadowed.result
1873
1211
  );
1874
1212
 
1875
- let steps = journal::load_steps(&*client.lock().await).await.unwrap();
1213
+ let steps = client.journal_steps().await;
1876
1214
  assert_eq!(
1877
1215
  steps.len(),
1878
1216
  2,
@@ -1883,9 +1221,8 @@ pub(crate) mod tests {
1883
1221
  // Shadow may only let through what Off would: a caller that stated a wrong role is refused.
1884
1222
  #[tokio::test]
1885
1223
  async fn shadow_refuses_a_stated_wrong_role_and_still_lets_an_unidentified_caller_through() {
1886
- let client = scratch_db("rust_host_dispatch_test_shadow_stated").await;
1887
- provision_lineage(&*client.lock().await, "Banking", 1, &["Customer"]).await;
1888
- let config = test_config("Banking", 1);
1224
+ let client = crate::dispatch_tests::tests::memory_store();
1225
+ let config = crate::dispatch_tests::tests::memory_config("Banking");
1889
1226
  let go = |mode: Enforcement, origin: Origin, caller: Caller<'static>, id: &'static str| {
1890
1227
  let client = &client;
1891
1228
  let config = &config;
@@ -1938,7 +1275,7 @@ pub(crate) mod tests {
1938
1275
  nobody.result
1939
1276
  );
1940
1277
 
1941
- let steps = journal::load_steps(&*client.lock().await).await.unwrap();
1278
+ let steps = client.journal_steps().await;
1942
1279
  assert_eq!(
1943
1280
  steps.len(),
1944
1281
  1,
@@ -1950,9 +1287,8 @@ pub(crate) mod tests {
1950
1287
  // unidentified caller is refused under enforce.
1951
1288
  #[tokio::test]
1952
1289
  async fn a_host_internal_dispatch_is_unchecked_under_enforce_but_an_external_one_is_refused() {
1953
- let client = scratch_db("rust_host_dispatch_test_internal").await;
1954
- provision_lineage(&*client.lock().await, "Banking", 1, &["Customer"]).await;
1955
- let config = test_config("Banking", 1);
1290
+ let client = crate::dispatch_tests::tests::memory_store();
1291
+ let config = crate::dispatch_tests::tests::memory_config("Banking");
1956
1292
  let go = |mode: Enforcement, origin: Origin, id: &'static str| {
1957
1293
  let client = &client;
1958
1294
  let config = &config;
@@ -2263,15 +1599,8 @@ pub(crate) mod tests {
2263
1599
  #[tokio::test]
2264
1600
  async fn a_cross_domain_delivery_that_exhausts_every_retry_dead_letters_and_still_fails_the_invocation(
2265
1601
  ) {
2266
- let client = scratch_db("rust_host_dispatch_test_8").await;
2267
- provision_lineage(
2268
- &*client.lock().await,
2269
- "Banking",
2270
- 1,
2271
- &["Customer", "Account"],
2272
- )
2273
- .await;
2274
- let config = test_config("Banking", 1);
1602
+ let client = crate::dispatch_tests::tests::memory_store();
1603
+ let config = crate::dispatch_tests::tests::memory_config("Banking");
2275
1604
  let invoker = AlwaysFailingInvoker::new();
2276
1605
 
2277
1606
  handle(
@@ -2331,42 +1660,28 @@ pub(crate) mod tests {
2331
1660
  // The local command still committed: cross-domain delivery runs
2332
1661
  // strictly after commit, so a delivery failure never unwinds it.
2333
1662
  // The dead letter existing at all proves the match already ran.
2334
- let guard = client.lock().await;
2335
- let dead_letters = guard
2336
- .query("SELECT policy, target_domain, target_verb, attempts FROM hecks_cross_domain_dead_letters", &[])
2337
- .await
2338
- .unwrap();
1663
+ let dead_letters = client.dead_letter_rows().await;
2339
1664
  assert_eq!(
2340
1665
  dead_letters.len(),
2341
1666
  1,
2342
1667
  "exactly one exhausted delivery should have been dead-lettered: {dead_letters:?}"
2343
1668
  );
2344
- let policy: String = dead_letters[0].get(0);
2345
- let target_domain: String = dead_letters[0].get(1);
2346
- let target_verb: String = dead_letters[0].get(2);
2347
- let attempts: i32 = dead_letters[0].get(3);
1669
+ let (policy, target_domain, target_verb, attempts) = dead_letters[0].clone();
2348
1670
  assert_eq!(policy, "ReviewOnFreeze");
2349
1671
  assert_eq!(target_domain, "Compliance");
2350
- assert_eq!(target_verb, "Compliance::AccountFreezeReview.Open");
1672
+ assert_eq!(target_verb, "Compliance::Review.Open");
2351
1673
  assert_eq!(attempts, lambda_client::MAX_DELIVERY_ATTEMPTS as i32);
2352
1674
  }
2353
1675
 
2354
1676
  // Pins a real drop: a second cross-domain reaction from the same
2355
1677
  // step must not go dark just because the first one's delivery
2356
1678
  // exhausted its retries. `SafeDepositBox.Surrender` emits both
2357
- // `BoxSurrendered` (-> Compliance) and `KeyReturnDue` (-> Notifications)
1679
+ // `BoxSurrendered` (-> Compliance) and `KeyReturnRequested` (-> Notifications)
2358
1680
  // from one command.
2359
1681
  #[tokio::test]
2360
1682
  async fn a_failed_cross_domain_delivery_does_not_drop_a_sibling_reaction_from_the_same_step() {
2361
- let client = scratch_db("rust_host_dispatch_test_11").await;
2362
- provision_lineage(
2363
- &*client.lock().await,
2364
- "Banking",
2365
- 1,
2366
- &["Customer", "SafeDepositBox"],
2367
- )
2368
- .await;
2369
- let config = test_config("Banking", 1);
1683
+ let client = crate::dispatch_tests::tests::memory_store();
1684
+ let config = crate::dispatch_tests::tests::memory_config("Banking");
2370
1685
  let invoker = AlwaysFailingInvoker::new();
2371
1686
 
2372
1687
  handle(
@@ -2422,19 +1737,224 @@ pub(crate) mod tests {
2422
1737
  the fix under test is exactly this: a fresh Vec would only ever show one policy's worth"
2423
1738
  );
2424
1739
 
2425
- let guard = client.lock().await;
2426
- let dead_letters = guard
2427
- .query(
2428
- "SELECT policy FROM hecks_cross_domain_dead_letters ORDER BY policy",
2429
- &[],
2430
- )
1740
+ let policies: Vec<String> = client
1741
+ .dead_letter_rows()
2431
1742
  .await
2432
- .unwrap();
2433
- let policies: Vec<String> = dead_letters.iter().map(|row| row.get(0)).collect();
1743
+ .into_iter()
1744
+ .map(|row| row.0)
1745
+ .collect();
2434
1746
  assert_eq!(
2435
1747
  policies,
2436
1748
  vec!["FlagKeyReturn".to_string(), "ReviewOnBoxSurrender".to_string()],
2437
1749
  "BOTH sibling reactions must be dead-lettered, not just whichever ran first: {policies:?}"
2438
1750
  );
2439
1751
  }
1752
+ /// The wasm plugin fixture (spec/fixtures/rust_host/pluginned): its kernel and its IR, which
1753
+ /// carries the doubler plugin that answers `Ledger.Doubled` and refuses `Ledger.Tripled`.
1754
+ fn pluginned() -> (std::path::PathBuf, serde_json::Value) {
1755
+ let dist = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../dist");
1756
+ let ir = std::fs::read_to_string(dist.join("pluginned.ir.json"))
1757
+ .expect("build the pluginned fixture");
1758
+ (
1759
+ dist.join("pluginned.wasm"),
1760
+ serde_json::from_str(&ir).unwrap(),
1761
+ )
1762
+ }
1763
+
1764
+ async fn ask_pluginned(
1765
+ client: &Store,
1766
+ question: &str,
1767
+ args: serde_json::Value,
1768
+ ) -> serde_json::Value {
1769
+ let (wasm, ir) = pluginned();
1770
+ let asked = serde_json::json!({ "query": question, "args": args });
1771
+ crate::query_step::answer(&asked, Some(&ir), "Pluginned", client, &wasm)
1772
+ .await
1773
+ .unwrap()
1774
+ }
1775
+
1776
+ /// A query a wasm plugin answers, end to end: a command through the real kernel stores the
1777
+ /// ledger, and the query step hands the plugin that record's state, read back from Postgres.
1778
+ #[tokio::test]
1779
+ async fn a_plugin_answers_with_the_referenced_record_as_stored() {
1780
+ let client = scratch_db("rust_host_dispatch_test_plugin").await;
1781
+ provision_lineage(&*client.lock().await, "Pluginned", 1, &["Ledger"]).await;
1782
+ let (wasm, _) = pluginned();
1783
+ let config = test_config("Pluginned", 1);
1784
+ let opened = handle(
1785
+ Host::new(&client, &wasm, &config, &lambda_client::NeverInvoker),
1786
+ "Pluginned::Ledger.Open",
1787
+ serde_json::json!({ "name": { "value": "house" }, "note": { "value": "rent" } }),
1788
+ None,
1789
+ )
1790
+ .await
1791
+ .unwrap();
1792
+ assert!(
1793
+ opened.accepted,
1794
+ "opening the ledger: {}",
1795
+ opened.result["refusals"]
1796
+ );
1797
+
1798
+ let answer = ask_pluginned(
1799
+ &client,
1800
+ "Pluginned::Ledger.Doubled",
1801
+ serde_json::json!({ "entry": "house", "amount": 21 }),
1802
+ )
1803
+ .await;
1804
+
1805
+ assert_eq!(
1806
+ answer["queries"][0]["rows"],
1807
+ serde_json::json!([{ "amount": 42, "note": "rent" }])
1808
+ );
1809
+ }
1810
+
1811
+ #[tokio::test]
1812
+ async fn a_plugin_refusal_and_a_missing_record_come_back_as_refusals() {
1813
+ let client = scratch_db("rust_host_dispatch_test_plugin_refusals").await;
1814
+ provision_lineage(&*client.lock().await, "Pluginned", 1, &["Ledger"]).await;
1815
+
1816
+ let refused = ask_pluginned(
1817
+ &client,
1818
+ "Pluginned::Ledger.Tripled",
1819
+ serde_json::json!({ "amount": 1 }),
1820
+ )
1821
+ .await;
1822
+ let missing = ask_pluginned(
1823
+ &client,
1824
+ "Pluginned::Ledger.Doubled",
1825
+ serde_json::json!({ "entry": "nowhere", "amount": 1 }),
1826
+ )
1827
+ .await;
1828
+
1829
+ assert_eq!(refused["refusals"][0]["kind"], "PluginRefusal");
1830
+ let refusal = refused["refusals"][0]["error"].as_str().unwrap();
1831
+ assert!(
1832
+ refusal.contains("doubler does not answer Pluginned::Ledger.Tripled"),
1833
+ "{refusal}"
1834
+ );
1835
+ assert_eq!(missing["refusals"][0]["kind"], "NotFound");
1836
+ assert_eq!(
1837
+ missing["refusals"][0]["error"],
1838
+ "entry: no Ledger \"nowhere\""
1839
+ );
1840
+ }
1841
+ }
1842
+
1843
+ #[cfg(test)]
1844
+ mod preview_tests {
1845
+ use crate::dispatch_tests::tests::{memory_config, memory_store, register, wasm_path};
1846
+ use crate::host::Host;
1847
+ use crate::journal;
1848
+ use crate::lambda_client;
1849
+ use crate::store::{Accepted, DeadLetter, LineageWrite, Store, StoreExt, StoreFixtures};
1850
+ use crate::wasm_runner;
1851
+ use rust_host_core::dispatch::*;
1852
+ use std::path::Path;
1853
+
1854
+ // The client envelope a preview of `verb` answers for `invocation`.
1855
+ async fn preview_of(
1856
+ client: &Store,
1857
+ verb: &str,
1858
+ invocation: serde_json::Value,
1859
+ ) -> serde_json::Value {
1860
+ let config = memory_config("Banking");
1861
+ let wasm = wasm_path();
1862
+ let host = Host::new(client, &wasm, &config, &lambda_client::NeverInvoker);
1863
+ let outcome = preview_as(host, verb, invocation, Caller::default())
1864
+ .await
1865
+ .unwrap();
1866
+ crate::validation::answer(&outcome.result)
1867
+ }
1868
+
1869
+ async fn preview(client: &Store, facts: serde_json::Value) -> serde_json::Value {
1870
+ preview_of(
1871
+ client,
1872
+ "Banking::Customer.Register",
1873
+ facts_invocation(facts).unwrap(),
1874
+ )
1875
+ .await
1876
+ }
1877
+
1878
+ async fn register_for_real(client: &Store, id: &str) -> Outcome {
1879
+ let config = memory_config("Banking");
1880
+ let wasm = wasm_path();
1881
+ let host = Host::new(client, &wasm, &config, &lambda_client::NeverInvoker);
1882
+ handle_facts(host, "Banking::Customer.Register", register(id), None)
1883
+ .await
1884
+ .unwrap()
1885
+ }
1886
+
1887
+ #[tokio::test]
1888
+ async fn a_preview_of_an_acceptable_command_answers_ok_and_keeps_nothing() {
1889
+ let client = memory_store();
1890
+ assert_eq!(
1891
+ preview(&client, register("CUST-P1")).await,
1892
+ serde_json::json!({ "ok": true, "refusals": [] })
1893
+ );
1894
+ assert!(
1895
+ register_for_real(&client, "CUST-P1").await.accepted,
1896
+ "the preview left a record behind, so the real registration was refused"
1897
+ );
1898
+ }
1899
+
1900
+ #[tokio::test]
1901
+ async fn a_preview_of_a_refused_command_answers_why_with_its_kind_and_stage() {
1902
+ let client = memory_store();
1903
+ register_for_real(&client, "CUST-P2").await;
1904
+ let answer = preview(&client, register("CUST-P2")).await;
1905
+ assert_eq!(answer["ok"], false);
1906
+ let refusal = &answer["refusals"][0];
1907
+ assert_eq!(refusal["refusal"], "AlreadyExists");
1908
+ assert_eq!(
1909
+ (refusal["kind"].as_str(), refusal["stage"].as_str()),
1910
+ (Some("refused"), Some("state"))
1911
+ );
1912
+ }
1913
+
1914
+ #[tokio::test]
1915
+ async fn a_preview_names_an_unknown_argument_in_the_arguments_stage() {
1916
+ let client = memory_store();
1917
+ let mut facts = register("CUST-P3");
1918
+ facts["bogus"] = serde_json::json!(1);
1919
+ let answer = preview(&client, facts).await;
1920
+ let refusal = &answer["refusals"][0];
1921
+ assert_eq!(refusal["refusal"], "UnknownArgument");
1922
+ assert_eq!(refusal["stage"], "arguments");
1923
+ }
1924
+
1925
+ #[tokio::test]
1926
+ async fn a_preview_against_an_existing_record_is_routed_to_it() {
1927
+ let client = memory_store();
1928
+ register_for_real(&client, "CUST-P4").await;
1929
+ let close = routed_invocation(serde_json::json!("CUST-P4"), serde_json::json!({})).unwrap();
1930
+ assert_eq!(
1931
+ preview_of(&client, "Banking::Customer.Close", close).await,
1932
+ serde_json::json!({ "ok": true, "refusals": [] })
1933
+ );
1934
+ }
1935
+
1936
+ #[tokio::test]
1937
+ async fn a_preview_against_a_record_that_does_not_exist_is_kind_not_found() {
1938
+ let client = memory_store();
1939
+ let close = routed_invocation(serde_json::json!("NOBODY"), serde_json::json!({})).unwrap();
1940
+ let answer = preview_of(&client, "Banking::Customer.Close", close).await;
1941
+ assert_eq!(answer["ok"], false);
1942
+ assert_eq!(answer["refusals"][0]["kind"], "not_found");
1943
+ }
1944
+
1945
+ // The body protocol: `"dry_run": true` beside a command, answered as the client envelope even
1946
+ // when the request also asks for `bare`.
1947
+ #[tokio::test]
1948
+ async fn the_body_protocol_answers_a_dry_run_as_the_client_envelope() {
1949
+ let client = memory_store();
1950
+ let config = memory_config("Banking");
1951
+ let wasm = wasm_path();
1952
+ let host = Host::new(&client, &wasm, &config, &lambda_client::NeverInvoker);
1953
+ let body = serde_json::json!({
1954
+ "verb": "Banking::Customer.Register", "with": register("CUST-P5"),
1955
+ "dry_run": true, "bare": true,
1956
+ });
1957
+ let answer = crate::server::dispatch_body(body, host).await.unwrap();
1958
+ assert_eq!(answer, serde_json::json!({ "ok": true, "refusals": [] }));
1959
+ }
2440
1960
  }