hecks 4.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 (1281) hide show
  1. checksums.yaml +4 -4
  2. data/examples/pizzas/bluebook/pizzas.bluebook +1 -0
  3. data/exe/hecks +15 -3
  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 +19 -17
  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 +12 -10
  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 +4 -2
  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 +19 -12
  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 +8 -3
  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/unwrapping.rb +60 -0
  941. data/lib/hecks/runtime/value/validation.rb +7 -4
  942. data/lib/hecks/runtime/value.rb +3 -27
  943. data/lib/hecks/runtime/wasm_plugin.rb +98 -0
  944. data/lib/hecks/runtime.rb +14 -14
  945. data/lib/hecks/rust_build/command_schemas.rb +27 -0
  946. data/lib/hecks/rust_build/conformance/surface.rb +50 -7
  947. data/lib/hecks/rust_build/conformance.rb +25 -3
  948. data/lib/hecks/rust_build/conformance_fuzz.rb +2 -2
  949. data/lib/hecks/rust_build/domain_name.rb +4 -1
  950. data/lib/hecks/rust_build/host.rb +15 -9
  951. data/lib/hecks/rust_build/native_build.rb +2 -1
  952. data/lib/hecks/rust_build/prebuilt_binary.rb +25 -5
  953. data/lib/hecks/rust_build/project_rust/codegen_run.rb +11 -10
  954. data/lib/hecks/rust_build/project_rust/domain_loader.rb +15 -2
  955. data/lib/hecks/rust_build/project_rust/root_mod/notes.rb +1 -1
  956. data/lib/hecks/rust_build/project_rust/target_ir.rb +12 -3
  957. data/lib/hecks/rust_build/project_rust.rb +39 -19
  958. data/lib/hecks/rust_build/target_dir.rb +40 -0
  959. data/lib/hecks/rust_build/wasm.rb +46 -17
  960. data/lib/hecks/rust_build/wasm_browser.rb +8 -2
  961. data/lib/hecks/rust_build.rb +29 -2
  962. data/lib/hecks/sentence_form.rb +212 -0
  963. data/lib/hecks/served_options.rb +2 -1
  964. data/lib/hecks/shape_ledger.rb +149 -0
  965. data/lib/hecks/site/bluebook/site.bluebook +39 -62
  966. data/lib/hecks/site/bluebook/site.ports.hecksagon +1 -1
  967. data/lib/hecks/sme/bluebook/sme.bluebook +7 -1
  968. data/lib/hecks/storehouse/audit.rb +4 -1
  969. data/lib/hecks/storehouse/bluebook_words.rb +35 -0
  970. data/lib/hecks/storehouse/dispatching.rb +6 -3
  971. data/lib/hecks/storehouse/introspection.rb +4 -4
  972. data/lib/hecks/storehouse/querying.rb +2 -2
  973. data/lib/hecks/storehouse.rb +14 -4
  974. data/lib/hecks/switches.rb +21 -0
  975. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +5 -9
  976. data/lib/hecks/text.rb +57 -0
  977. data/lib/hecks/tickets/bluebook/tickets.bluebook +30 -51
  978. data/lib/hecks/tickets/bluebook/tickets.hecksagon +1 -1
  979. data/lib/hecks/tickets/bluebook/tickets.ports.hecksagon +1 -1
  980. data/lib/hecks/tools/argument_gate_matrix/dispatching.rb +1 -1
  981. data/lib/hecks/tools/argument_gate_matrix/rows.rb +7 -7
  982. data/lib/hecks/tools/argument_gate_matrix/violations.rb +1 -1
  983. data/lib/hecks/tools/argument_gate_matrix.rb +3 -3
  984. data/lib/hecks/tools/ci_gate_decision.rb +5 -2
  985. data/lib/hecks/tools/ci_gates.rb +1 -1
  986. data/lib/hecks/tools/comment_style/caps_words.rb +11 -8
  987. data/lib/hecks/tools/comment_style/dictionary.rb +7 -2
  988. data/lib/hecks/tools/comment_style/fixing.rb +10 -5
  989. data/lib/hecks/tools/comment_style/long_blocks.rb +1 -1
  990. data/lib/hecks/tools/comment_style/masking.rb +5 -5
  991. data/lib/hecks/tools/comment_style/run.rb +4 -1
  992. data/lib/hecks/tools/comment_style/source_file.rb +10 -3
  993. data/lib/hecks/tools/comment_style/structure_checks.rb +13 -4
  994. data/lib/hecks/tools/comment_style/text_checks.rb +1 -1
  995. data/lib/hecks/tools/comment_style.rb +25 -2
  996. data/lib/hecks/tools/deploy_recipe/generation.rb +1 -1
  997. data/lib/hecks/tools/deploy_recipe/targets.rb +1 -1
  998. data/lib/hecks/tools/deploy_recipe/worlds.rb +15 -9
  999. data/lib/hecks/tools/deploy_recipe.rb +17 -10
  1000. data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +1 -1
  1001. data/lib/hecks/tools/deploy_recipe_lint/makefile.rb +7 -1
  1002. data/lib/hecks/tools/drop_implicit_append_fields.rb +4 -1
  1003. data/lib/hecks/tools/era_projection_backfill.rb +11 -6
  1004. data/lib/hecks/tools/era_reattest.rb +11 -6
  1005. data/lib/hecks/tools/fuzz_sweep/findings.rb +18 -2
  1006. data/lib/hecks/tools/fuzz_sweep/options.rb +5 -1
  1007. data/lib/hecks/tools/fuzz_sweep.rb +17 -5
  1008. data/lib/hecks/tools/gate.rb +27 -5
  1009. data/lib/hecks/tools/hoist_local_givens/rewriting.rb +3 -2
  1010. data/lib/hecks/tools/hoist_local_givens.rb +2 -4
  1011. data/lib/hecks/tools/lanes/prebuilt_workflow.rb +1 -1
  1012. data/lib/hecks/tools/lanes/rows.rb +9 -3
  1013. data/lib/hecks/tools/lanes/ruleset.rb +1 -1
  1014. data/lib/hecks/tools/lanes/watch_workflow.rb +1 -1
  1015. data/lib/hecks/tools/lanes/workflow.rb +1 -1
  1016. data/lib/hecks/tools/lanes.rb +1 -1
  1017. data/lib/hecks/tools/oidc_manifests.rb +16 -19
  1018. data/lib/hecks/tools/regeneration_run.rb +22 -17
  1019. data/lib/hecks/tools/release_only.rb +5 -2
  1020. data/lib/hecks/tools/rust_comment_style/caps_words.rb +5 -5
  1021. data/lib/hecks/tools/rust_comment_style/fixing.rb +1 -1
  1022. data/lib/hecks/tools/rust_comment_style/masking.rb +7 -4
  1023. data/lib/hecks/tools/rust_comment_style/source_file.rb +7 -2
  1024. data/lib/hecks/tools/rust_comment_style/text_checks.rb +1 -1
  1025. data/lib/hecks/tools/rust_comment_style/tokenizer.rb +4 -1
  1026. data/lib/hecks/tools/site_routes/editor_files.rb +16 -13
  1027. data/lib/hecks/tools/site_routes/root_files.rb +14 -13
  1028. data/lib/hecks/tools/site_routes/templates.rb +5 -4
  1029. data/lib/hecks/tools/site_routes.rb +18 -15
  1030. data/lib/hecks/tools/tail_merge.rb +3 -3
  1031. data/lib/hecks/tools/tenant_provisioning.rb +6 -6
  1032. data/lib/hecks/tools/tools_doc.rb +16 -16
  1033. data/lib/hecks/tools/translation_audit/domain_loading.rb +3 -3
  1034. data/lib/hecks/tools/translation_audit.rb +15 -4
  1035. data/lib/hecks/tools/translation_scaffold/reporting.rb +4 -1
  1036. data/lib/hecks/tools/translation_scaffold.rb +4 -14
  1037. data/lib/hecks/vendoring/git_source.rb +7 -4
  1038. data/lib/hecks/version.rb +1 -1
  1039. data/lib/hecks/vocabulary.rb +110 -3
  1040. data/lib/hecks.rb +37 -18
  1041. data/lib/rubocop/cop/hecks/inline_regexp.rb +29 -0
  1042. data/lib/rubocop/cop/hecks/no_transport_in_runtime.rb +78 -0
  1043. data/lib/rubocop/cop/hecks/rails_model_rules.rb +46 -0
  1044. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +1 -1
  1045. data/lib/rubocop/cop/hecks/train_wreck.rb +69 -0
  1046. data/lib/rubocop/cop/hecks/unnamed_pipeline.rb +51 -0
  1047. data/prebuilt/31cf81e12f16236ad26249be590cfea3799c9db67432e6a4ef54b801c355b6ae.marshal +0 -0
  1048. data/prebuilt/68ecb2bd3abb1a221007db4f2b0375834c13aaccac8f2b99778e8517f11aa113.marshal +0 -0
  1049. data/prebuilt/verdicts-0328c2403383837d780ad5ed81d1da351c56436b894cf9015b5d56b8f1f1bf10.json +1 -0
  1050. data/qa/settings.yml +9 -0
  1051. data/rust/Cargo.lock +8 -1
  1052. data/rust/Cargo.toml +6 -7
  1053. data/rust/build/src/binding_pass.rs +13 -17
  1054. data/rust/build/src/cargo_sync.rs +1 -1
  1055. data/rust/build/src/lineage_pass.rs +4 -71
  1056. data/rust/build/src/pipeline.rs +99 -70
  1057. data/rust/build/src/resolve.rs +85 -29
  1058. data/rust/build/src/seams_pass.rs +117 -99
  1059. data/rust/build/src/world_pass.rs +21 -12
  1060. data/rust/codegen/src/domain_generator.rs +30 -2
  1061. data/rust/codegen/src/expr_emitter.rs +6 -0
  1062. data/rust/codegen/src/json_codec.rs +48 -10
  1063. data/rust/codegen/src/main.rs +41 -34
  1064. data/rust/codegen/src/manifest.rs +1 -1
  1065. data/rust/codegen/src/naming.rs +5 -5
  1066. data/rust/codegen/src/one_of_references.rs +124 -0
  1067. data/rust/codegen/src/presenters.rs +335 -0
  1068. data/rust/codegen/src/queries.rs +56 -7
  1069. data/rust/codegen/src/reactions.rs +19 -14
  1070. data/rust/codegen/src/read_models.rs +20 -71
  1071. data/rust/codegen/src/registry.rs +89 -15
  1072. data/rust/codegen/src/sidecars.rs +1 -1
  1073. data/rust/expr/Cargo.lock +54 -0
  1074. data/rust/expr/Cargo.toml +52 -0
  1075. data/rust/{src/kernel → expr/src}/attribute_shapes/composite.rs +45 -14
  1076. data/rust/{src/kernel → expr/src}/attribute_shapes/list.rs +1 -1
  1077. data/rust/{src/kernel → expr/src}/attribute_shapes/mod.rs +2 -2
  1078. data/rust/{src/kernel → expr/src}/attribute_shapes/optional.rs +1 -1
  1079. data/rust/{src/kernel → expr/src}/attribute_shapes/scalar.rs +3 -3
  1080. data/rust/{src/kernel → expr/src}/expr.rs +30 -8
  1081. data/rust/{src/kernel → expr/src}/expression_operators/arithmetic.rs +41 -18
  1082. data/rust/{src/kernel → expr/src}/expression_operators/comparison.rs +54 -8
  1083. data/rust/{src/kernel → expr/src}/expression_operators/enumeration.rs +73 -6
  1084. data/rust/{src/kernel → expr/src}/expression_operators/logical.rs +2 -2
  1085. data/rust/expr/src/expression_operators/membership.rs +33 -0
  1086. data/rust/{src/kernel → expr/src}/expression_operators/mod.rs +2 -2
  1087. data/rust/expr/src/expression_operators/pattern_match.rs +179 -0
  1088. data/rust/{src/kernel → expr/src}/expression_operators/positional.rs +8 -6
  1089. data/rust/{src/kernel → expr/src}/expression_operators/presence.rs +7 -6
  1090. data/rust/{src/kernel → expr/src}/expression_operators/sign_test.rs +14 -4
  1091. data/rust/{src/kernel → expr/src}/expression_operators/sized.rs +16 -9
  1092. data/rust/{src/kernel → expr/src}/expression_operators/text.rs +30 -17
  1093. data/rust/{src/kernel → expr/src}/expression_operators/to_string.rs +8 -6
  1094. data/rust/expr/src/lib.rs +17 -0
  1095. data/rust/expr/src/refusal.rs +62 -0
  1096. data/rust/expr/src/rendering.rs +135 -0
  1097. data/rust/{src/kernel → expr/src}/ruby_float.rs +7 -6
  1098. data/rust/host/Cargo.lock +137 -123
  1099. data/rust/host/Cargo.toml +7 -3
  1100. data/rust/host/HECKS_RELEASE +1 -1
  1101. data/rust/host/core/Cargo.toml +64 -0
  1102. data/rust/host/core/clippy.toml +4 -0
  1103. data/rust/host/core/src/arg_limits.rs +48 -0
  1104. data/rust/host/core/src/clock.rs +146 -0
  1105. data/rust/host/core/src/dispatch.rs +920 -0
  1106. data/rust/host/core/src/expr_json.rs +1137 -0
  1107. data/rust/host/{src → core/src}/host.rs +13 -14
  1108. data/rust/host/{src → core/src}/ir.rs +227 -18
  1109. data/rust/host/core/src/journal.rs +126 -0
  1110. data/rust/host/{src → core/src}/lambda_client.rs +27 -96
  1111. data/rust/host/core/src/lib.rs +37 -0
  1112. data/rust/host/{src → core/src}/needs.rs +66 -20
  1113. data/rust/host/core/src/plugins.rs +269 -0
  1114. data/rust/host/core/src/presentation.rs +471 -0
  1115. data/rust/host/{src → core/src}/presentation_write.rs +75 -43
  1116. data/rust/host/{src → core/src}/query_step.rs +54 -16
  1117. data/rust/host/{src → core/src}/reference_validate.rs +6 -6
  1118. data/rust/host/{src → core/src}/storage_shape.rs +2 -2
  1119. data/rust/host/core/src/store/memory.rs +635 -0
  1120. data/rust/host/core/src/store.rs +418 -0
  1121. data/rust/host/{src → core/src}/ui_schema.rs +8 -8
  1122. data/rust/host/core/src/validation.rs +129 -0
  1123. data/rust/host/core/src/wasm_runner.rs +410 -0
  1124. data/rust/host/fuzz/.gitignore +4 -0
  1125. data/rust/host/fuzz/Cargo.lock +4479 -0
  1126. data/rust/host/fuzz/Cargo.toml +42 -0
  1127. data/rust/host/fuzz/README.md +55 -0
  1128. data/rust/host/fuzz/fuzz_targets/postgres_equivalence.rs +24 -0
  1129. data/rust/host/fuzz/src/backend.rs +273 -0
  1130. data/rust/host/fuzz/src/equivalence.rs +195 -0
  1131. data/rust/host/fuzz/src/footprint.rs +182 -0
  1132. data/rust/host/fuzz/src/lib.rs +17 -0
  1133. data/rust/host/fuzz/src/ops.rs +345 -0
  1134. data/rust/host/fuzz/tests/smoke.rs +50 -0
  1135. data/rust/host/src/api.rs +316 -105
  1136. data/rust/host/src/auth/google_signin.rs +183 -0
  1137. data/rust/host/src/auth.rs +527 -370
  1138. data/rust/host/src/aws_lambda_invoker.rs +61 -0
  1139. data/rust/host/src/bin/expr_harness.rs +9 -4
  1140. data/rust/host/src/bin/ledger.rs +84 -0
  1141. data/rust/host/src/bin/lineage_harness.rs +17 -21
  1142. data/rust/host/src/bin/mint_harness.rs +11 -36
  1143. data/rust/host/src/bin/validate_harness.rs +1 -6
  1144. data/rust/host/src/boot.rs +465 -174
  1145. data/rust/host/src/boundary_fuzz/ir.rs +10 -5
  1146. data/rust/host/src/boundary_fuzz/server.rs +6 -5
  1147. data/rust/host/src/checkout.rs +4 -1
  1148. data/rust/host/src/commerce_ir.rs +126 -105
  1149. data/rust/host/src/{dispatch.rs → dispatch_tests.rs} +391 -824
  1150. data/rust/host/src/extension.rs +3 -4
  1151. data/rust/host/src/hecksagon_boundary_tests.rs +149 -0
  1152. data/rust/host/src/ledger.rs +683 -0
  1153. data/rust/host/src/lib.rs +21 -28
  1154. data/rust/host/src/payments/tests.rs +136 -38
  1155. data/rust/host/src/payments.rs +36 -32
  1156. data/rust/host/src/platform_signin/extension.rs +1 -1
  1157. data/rust/host/src/platform_signin/mapping_tests.rs +12 -9
  1158. data/rust/host/src/platform_signin/session.rs +7 -14
  1159. data/rust/host/src/platform_signin/verify.rs +0 -2
  1160. data/rust/host/src/presentation_tests.rs +382 -0
  1161. data/rust/host/src/rate_limit.rs +18 -2
  1162. data/rust/host/src/server.rs +287 -36
  1163. data/rust/host/src/{journal.rs → store/journal_sql.rs} +21 -127
  1164. data/rust/host/src/{mint.rs → store/mint.rs} +12 -12
  1165. data/rust/host/src/store/postgres.rs +493 -0
  1166. data/rust/host/src/store.rs +97 -0
  1167. data/rust/host/src/store_conformance.rs +1265 -0
  1168. data/rust/host/src/web/commerce.rs +10 -18
  1169. data/rust/host/src/web/newsletter/cooldown.rs +107 -22
  1170. data/rust/host/src/web/newsletter/naming.rs +104 -0
  1171. data/rust/host/src/web/newsletter/tests.rs +53 -0
  1172. data/rust/host/src/web/newsletter.rs +73 -50
  1173. data/rust/host/src/web/newsletter_send.rs +12 -13
  1174. data/rust/host/src/web/registration_receipt.rs +2 -2
  1175. data/rust/host/src/web/registrations.rs +754 -375
  1176. data/rust/host/src/web/resend_webhook.rs +3 -3
  1177. data/rust/host/src/web/signin_tests.rs +131 -0
  1178. data/rust/host/src/web.rs +1479 -149
  1179. data/rust/lsp/README.md +27 -27
  1180. data/rust/lsp/src/diagnostics.rs +75 -15
  1181. data/rust/lsp/src/main.rs +101 -5
  1182. data/rust/parser/fuzz/Cargo.toml +2 -2
  1183. data/rust/parser/fuzz/README.md +3 -3
  1184. data/rust/parser/fuzz/fuzz_targets/{chapter.rs → bluebook.rs} +4 -4
  1185. data/rust/parser/fuzz/fuzz_targets/resolve.rs +2 -2
  1186. data/rust/parser/src/build/named_pattern.rs +22 -0
  1187. data/rust/parser/src/build/references.rs +52 -0
  1188. data/rust/parser/src/diag.rs +22 -1
  1189. data/rust/parser/src/emit.rs +68 -4
  1190. data/rust/parser/src/expr/ast_json.rs +10 -0
  1191. data/rust/parser/src/expr/resolver.rs +49 -9
  1192. data/rust/parser/src/ir.rs +34 -7
  1193. data/rust/parser/src/keywords.rs +454 -457
  1194. data/rust/parser/src/lex.rs +115 -87
  1195. data/rust/parser/src/main.rs +180 -32
  1196. data/rust/parser/src/parse/adapter.rs +28 -0
  1197. data/rust/parser/src/parse/aggregate.rs +55 -35
  1198. data/rust/parser/src/parse/command.rs +100 -15
  1199. data/rust/parser/src/parse/{chapter.rs → declaration.rs} +85 -54
  1200. data/rust/parser/src/parse/domain_port.rs +60 -9
  1201. data/rust/parser/src/parse/entity.rs +35 -32
  1202. data/rust/parser/src/parse/file.rs +1 -1
  1203. data/rust/parser/src/parse/hecksagon.rs +15 -5
  1204. data/rust/parser/src/parse/mod.rs +84 -2
  1205. data/rust/parser/src/parse/process_manager.rs +1 -1
  1206. data/rust/parser/src/parse/query.rs +13 -3
  1207. data/rust/parser/src/parse/read_model.rs +2 -1
  1208. data/rust/parser/src/phrases.rs +1258 -0
  1209. data/rust/parser/src/render_ruby.rs +540 -0
  1210. data/rust/parser/src/sentence/changes.rs +437 -0
  1211. data/rust/parser/src/sentence/command_step.rs +206 -0
  1212. data/rust/parser/src/sentence/complete.rs +276 -0
  1213. data/rust/parser/src/sentence/entity.rs +41 -0
  1214. data/rust/parser/src/sentence/examples.rs +1012 -0
  1215. data/rust/parser/src/sentence/format.rs +882 -0
  1216. data/rust/parser/src/sentence/lower.rs +1366 -0
  1217. data/rust/parser/src/sentence/mod.rs +444 -0
  1218. data/rust/parser/src/sentence/names.rs +69 -0
  1219. data/rust/parser/src/sentence/options.rs +154 -0
  1220. data/rust/parser/src/sentence/pattern.rs +433 -0
  1221. data/rust/parser/src/sentence/process.rs +196 -0
  1222. data/rust/parser/src/sentence/queries.rs +431 -0
  1223. data/rust/parser/src/sentence/report.rs +283 -0
  1224. data/rust/parser/src/sentence/token.rs +106 -0
  1225. data/rust/parser/src/sentence/values.rs +498 -0
  1226. data/rust/src/exemplar/queries.rs +1 -0
  1227. data/rust/src/exemplar/reactions.rs +1 -1
  1228. data/rust/src/exemplar/registry.rs +30 -0
  1229. data/rust/src/kernel/cli.rs +233 -20
  1230. data/rust/src/kernel/collecting.rs +174 -0
  1231. data/rust/src/kernel/dispatch.rs +55 -27
  1232. data/rust/src/kernel/json.rs +15 -1
  1233. data/rust/src/kernel/mod.rs +8 -67
  1234. data/rust/src/kernel/named_query.rs +225 -11
  1235. data/rust/src/kernel/orchestrate.rs +213 -25
  1236. data/rust/src/kernel/orchestrate_guard_tests.rs +3 -0
  1237. data/rust/src/kernel/present.rs +261 -0
  1238. data/rust/src/kernel/query_cursor.rs +251 -0
  1239. data/rust/src/kernel/query_ordering.rs +6 -6
  1240. data/rust/src/kernel/read_model.rs +162 -17
  1241. data/rust/src/kernel/repository.rs +107 -17
  1242. data/rust/src/kernel/vocab/mod.rs +4 -0
  1243. data/rust/src/kernel/vocab/reducer.rs +109 -0
  1244. data/rust/src/kernel/vocab/refusal_class.rs +124 -0
  1245. data/rust/src/kernel/vocab/refusal_template.rs +2 -2
  1246. data/rust/src/lib.rs +1 -1
  1247. data/rust/web/Cargo.lock +47 -0
  1248. metadata +331 -93
  1249. data/lib/hecks/chapters.rb +0 -147
  1250. data/lib/hecks/gate/stages.yml +0 -234
  1251. data/lib/hecks/hecks/governance.world +0 -7
  1252. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +0 -137
  1253. data/lib/hecks/ports/persistence/postgres_dump/connection.rb +0 -59
  1254. data/lib/hecks/ports/persistence/postgres_dump.rb +0 -100
  1255. data/lib/hecks/projections/deploy/box/templates/tunnel-egress.yaml.tmpl +0 -11
  1256. data/lib/hecks/projector/exporter/capability_values.rb +0 -55
  1257. data/lib/hecks/runtime/remote_dispatcher.rb +0 -176
  1258. data/prebuilt/02c09c73b272c1fb5a9ae6d87bb5e5e529208e00f6b4b597dc2c0fd70a9c0964.marshal +0 -0
  1259. data/prebuilt/bb84a835cf6a15136bad5ca03aa5bf93a5b92f5f72444618d3ef2412b987fb32.marshal +0 -0
  1260. data/prebuilt/verdicts-f1f10da14c8e8f01b4c9b7e3e2d74be8cd2bf4676364ed17fa16975d2a076d75.json +0 -1
  1261. data/rust/host/src/clock.rs +0 -8
  1262. data/rust/host/src/expr_json.rs +0 -1983
  1263. data/rust/host/src/presentation.rs +0 -842
  1264. data/rust/host/src/wasm_runner.rs +0 -206
  1265. data/rust/src/kernel/expression_operators/membership.rs +0 -20
  1266. data/rust/src/kernel/expression_operators/pattern_match.rs +0 -108
  1267. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/events.rb +0 -0
  1268. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/head_writes.rb +0 -0
  1269. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/journal.rb +0 -0
  1270. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/base_sql.rb +0 -0
  1271. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/field_cache.rb +0 -0
  1272. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/head_compiler.rb +0 -0
  1273. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/merge_writes.rb +0 -0
  1274. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/provisioning.rb +0 -0
  1275. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/resumable_backfill.rb +0 -0
  1276. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/transform_installer.rb +0 -0
  1277. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/transform_sql.rb +0 -0
  1278. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/sagas.rb +0 -0
  1279. /data/rust/host/{src → core/src}/field_hints.rs +0 -0
  1280. /data/rust/host/{src → core/src}/log.rs +0 -0
  1281. /data/rust/host/{src → core/src}/reference_transform.rs +0 -0
@@ -1,722 +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
- }
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.
610
3
 
611
- log_command(verb, role, accepted, &result);
612
- Ok(Outcome { result, accepted })
613
- }
614
-
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
- }
4
+ #![allow(unused_imports)]
717
5
 
718
6
  #[cfg(test)]
719
- pub(crate) mod tests {
7
+ pub mod tests {
720
8
 
721
9
  #[test]
722
10
  fn a_command_log_line_names_the_verb_events_and_refusals_but_never_the_facts() {
@@ -732,15 +20,22 @@ pub(crate) mod tests {
732
20
  assert_eq!(fields["refusals"][0]["kind"], "AlreadyExists");
733
21
  assert!(!fields.to_string().contains("a@b.c"));
734
22
  }
735
- use super::*;
23
+ use crate::host::Host;
24
+ use crate::journal;
736
25
  use crate::journal::LineageConfig;
26
+ use crate::lambda_client;
737
27
  use crate::lambda_client::LambdaInvoker;
738
- 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};
739
34
 
740
35
  // A throwaway Postgres database per test, uniquely named so
741
36
  // `cargo test`'s default parallelism doesn't race two tests against
742
37
  // the same journal table.
743
- pub(crate) async fn scratch_db(name: &str) -> Mutex<Client> {
38
+ pub async fn scratch_db(name: &str) -> Store {
744
39
  let (admin, conn) = tokio_postgres::connect(&crate::test_pg::conninfo("postgres"), NoTls)
745
40
  .await
746
41
  .expect("connect to postgres");
@@ -762,14 +57,29 @@ pub(crate) mod tests {
762
57
  tokio::spawn(async move {
763
58
  let _ = conn.await;
764
59
  });
765
- journal::ensure_schema(&client).await.unwrap();
766
- 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
+ }
767
77
  }
768
78
 
769
79
  // Not Ruby's real provisioning — just enough structure for
770
- // `journal::current_era` and `append_lineage_mutation` to succeed,
80
+ // `journal_sql::current_era` and `append_lineage_mutation` to succeed,
771
81
  // without reproducing Ruby's full DDL.
772
- pub(crate) async fn provision_lineage(
82
+ pub async fn provision_lineage(
773
83
  client: &Client,
774
84
  domain: &str,
775
85
  era: i32,
@@ -791,7 +101,7 @@ pub(crate) mod tests {
791
101
  .await
792
102
  .unwrap();
793
103
 
794
- let journal_table = format!("hecks_journal_{}", journal::snake(domain));
104
+ let journal_table = format!("hecks_journal_{}", journal_sql::snake(domain));
795
105
  client
796
106
  .batch_execute(&format!(
797
107
  "CREATE TABLE IF NOT EXISTS \"{journal_table}\" (
@@ -800,7 +110,8 @@ pub(crate) mod tests {
800
110
  aggregate text NOT NULL,
801
111
  aggregate_id text NOT NULL,
802
112
  operation text NOT NULL,
803
- state jsonb
113
+ state jsonb,
114
+ mirrors jsonb
804
115
  )"
805
116
  ))
806
117
  .await
@@ -809,9 +120,9 @@ pub(crate) mod tests {
809
120
  for name in aggregate_storage_names {
810
121
  // Domain-qualified (ADR 0059), matching what a real write
811
122
  // targets.
812
- let snapshot_table = journal::qualified_name(
123
+ let snapshot_table = journal_sql::qualified_name(
813
124
  domain,
814
- &format!("{}_head_snapshot_{era}", journal::snake(name)),
125
+ &format!("{}_head_snapshot_{era}", journal_sql::snake(name)),
815
126
  );
816
127
  client
817
128
  .batch_execute(&format!(
@@ -854,6 +165,7 @@ pub(crate) mod tests {
854
165
  domain: "Banking".to_string(),
855
166
  era: Some(1),
856
167
  mirrored: None,
168
+ seal_every: None,
857
169
  };
858
170
  let refused = handle_facts(
859
171
  Host::new(&client, &wasm, &everything, &invoker),
@@ -885,6 +197,7 @@ pub(crate) mod tests {
885
197
  domain: "Banking".to_string(),
886
198
  era: Some(1),
887
199
  mirrored: Some(std::collections::BTreeSet::new()),
200
+ seal_every: None,
888
201
  };
889
202
  let outcome = handle_facts(
890
203
  Host::new(&client, &wasm, &declared, &invoker),
@@ -930,6 +243,7 @@ pub(crate) mod tests {
930
243
  domain: "Banking".to_string(),
931
244
  era: Some(1),
932
245
  mirrored: Some(std::iter::once("Banking::Customer".to_string()).collect()),
246
+ seal_every: None,
933
247
  };
934
248
 
935
249
  handle_facts(
@@ -955,19 +269,44 @@ pub(crate) mod tests {
955
269
  assert_eq!(mirrored, 1);
956
270
  }
957
271
 
958
- fn test_config(domain: &str, era: i32) -> LineageConfig {
272
+ pub fn test_config(domain: &str, era: i32) -> LineageConfig {
959
273
  LineageConfig {
960
274
  domain: domain.to_string(),
961
275
  era: Some(era),
962
276
  mirrored: None,
277
+ seal_every: None,
963
278
  }
964
279
  }
965
280
 
966
- pub(crate) fn wasm_path() -> std::path::PathBuf {
281
+ #[test]
282
+ fn present_hands_the_kernel_the_answer_under_present_and_answers_what_it_returns() {
283
+ let answer = serde_json::json!({"instances": {"Shop::Item#a": {"name": {"value": "x"}}}});
284
+ let mut sent = String::new();
285
+ let presented = present_with(&answer, |input| {
286
+ sent = input.to_string();
287
+ Ok(r#"{"instances":{"Shop::Item#a":{"name":"x"}}}"#.to_string())
288
+ })
289
+ .unwrap();
290
+ assert_eq!(
291
+ serde_json::from_str::<serde_json::Value>(&sent).unwrap(),
292
+ serde_json::json!({ "present": answer })
293
+ );
294
+ assert_eq!(presented["instances"]["Shop::Item#a"]["name"], "x");
295
+ }
296
+
297
+ #[test]
298
+ fn present_is_an_error_when_the_kernel_fails() {
299
+ let failed = present_with(&serde_json::json!({}), |_| {
300
+ Ok(r#"{"error":"expected a top-level {\"steps\": [...]} object"}"#.to_string())
301
+ });
302
+ assert!(failed.is_err());
303
+ }
304
+
305
+ pub fn wasm_path() -> std::path::PathBuf {
967
306
  std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../dist/banking.wasm")
968
307
  }
969
308
 
970
- fn register(reference: &str) -> serde_json::Value {
309
+ pub fn register(reference: &str) -> serde_json::Value {
971
310
  serde_json::json!({
972
311
  "reference": { "value": reference },
973
312
  "name": { "given": "Ada", "family": "Lovelace" },
@@ -979,9 +318,8 @@ pub(crate) mod tests {
979
318
  // query answered against whatever state the journal currently holds.
980
319
  #[tokio::test]
981
320
  async fn a_declared_query_answers_against_current_state() {
982
- let client = scratch_db("rust_host_dispatch_test_query").await;
983
- provision_lineage(&*client.lock().await, "Banking", 1, &["Customer"]).await;
984
- 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");
985
323
 
986
324
  handle(
987
325
  Host::new(&client, &wasm_path(), &config, &lambda_client::NeverInvoker),
@@ -1063,7 +401,7 @@ pub(crate) mod tests {
1063
401
  assert!(outcome.accepted);
1064
402
  assert_eq!(outcome.result["refusals"].as_array().unwrap().len(), 0);
1065
403
  let guard = client.lock().await;
1066
- let steps = journal::load_steps(&*guard).await.unwrap();
404
+ let steps = journal_sql::load_steps(&*guard).await.unwrap();
1067
405
  assert_eq!(steps.len(), 1);
1068
406
 
1069
407
  // The lineage write itself: a row landed in the era-tagged,
@@ -1105,20 +443,59 @@ pub(crate) mod tests {
1105
443
  assert_eq!(id, "CUST-0001");
1106
444
  }
1107
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
+
1108
486
  // Proves `occurred_at` end to end through the real wasm module: the
1109
487
  // string this crate stamps and sends is the one that comes back out
1110
488
  // in the returned event, not just what the in-kernel unit tests prove.
1111
489
  #[tokio::test]
1112
490
  async fn occurred_at_is_stamped_onto_every_returned_event_with_this_crate_s_own_real_clock() {
1113
- let client = scratch_db("rust_host_dispatch_test_occurred_at").await;
1114
- provision_lineage(&*client.lock().await, "Banking", 1, &["Customer"]).await;
491
+ let client = crate::dispatch_tests::tests::memory_store();
1115
492
 
1116
- let before = crate::auth::httpdate_now();
493
+ let before = crate::clock::iso8601_now();
1117
494
  let outcome = handle(
1118
495
  Host::new(
1119
496
  &client,
1120
497
  &wasm_path(),
1121
- &test_config("Banking", 1),
498
+ &crate::dispatch_tests::tests::memory_config("Banking"),
1122
499
  &lambda_client::NeverInvoker,
1123
500
  ),
1124
501
  "Banking::Customer.Register",
@@ -1127,7 +504,7 @@ pub(crate) mod tests {
1127
504
  )
1128
505
  .await
1129
506
  .unwrap();
1130
- let after = crate::auth::httpdate_now();
507
+ let after = crate::clock::iso8601_now();
1131
508
 
1132
509
  assert!(outcome.accepted, "{:?}", outcome.result["refusals"]);
1133
510
  let events = outcome.result["events"].as_array().unwrap();
@@ -1146,9 +523,8 @@ pub(crate) mod tests {
1146
523
 
1147
524
  #[tokio::test]
1148
525
  async fn rehydrates_prior_history_before_evaluating_the_new_command() {
1149
- let client = scratch_db("rust_host_dispatch_test_2").await;
1150
- provision_lineage(&*client.lock().await, "Banking", 1, &["Customer"]).await;
1151
- 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");
1152
528
 
1153
529
  let first = handle(
1154
530
  Host::new(&client, &wasm_path(), &config, &lambda_client::NeverInvoker),
@@ -1181,7 +557,7 @@ pub(crate) mod tests {
1181
557
  "duplicate registration should be refused, proving prior state was rehydrated: {:?}",
1182
558
  second.result
1183
559
  );
1184
- let steps = journal::load_steps(&*client.lock().await).await.unwrap();
560
+ let steps = client.journal_steps().await;
1185
561
  assert_eq!(
1186
562
  steps.len(),
1187
563
  1,
@@ -1214,11 +590,11 @@ pub(crate) mod tests {
1214
590
  );
1215
591
 
1216
592
  let guard = client.lock().await;
1217
- let snapshot = journal::load_snapshot(&*guard)
593
+ let snapshot = journal_sql::load_snapshot(&*guard)
1218
594
  .await
1219
595
  .unwrap()
1220
596
  .expect("a snapshot should exist after the first accepted command");
1221
- let tail = journal::load_steps_after(&*guard, snapshot.ordinal)
597
+ let tail = journal_sql::load_steps_after(&*guard, snapshot.ordinal)
1222
598
  .await
1223
599
  .unwrap();
1224
600
  assert!(tail.is_empty(), "the snapshot should already reflect every accepted command, leaving nothing to replay");
@@ -1232,7 +608,7 @@ pub(crate) mod tests {
1232
608
  );
1233
609
  }
1234
610
 
1235
- let final_snapshot = journal::load_snapshot(&*client.lock().await)
611
+ let final_snapshot = journal_sql::load_snapshot(&*client.lock().await)
1236
612
  .await
1237
613
  .unwrap()
1238
614
  .unwrap();
@@ -1309,7 +685,7 @@ pub(crate) mod tests {
1309
685
  .unwrap();
1310
686
  assert!(first.accepted, "{:?}", first.result);
1311
687
 
1312
- let whole_world = journal::load_snapshot(&*client.lock().await)
688
+ let whole_world = journal_sql::load_snapshot(&*client.lock().await)
1313
689
  .await
1314
690
  .unwrap()
1315
691
  .unwrap()
@@ -1420,7 +796,7 @@ pub(crate) mod tests {
1420
796
  .await
1421
797
  .unwrap();
1422
798
  assert!(second.accepted, "{:?}", second.result);
1423
- let after = journal::load_snapshot(&*client.lock().await)
799
+ let after = journal_sql::load_snapshot(&*client.lock().await)
1424
800
  .await
1425
801
  .unwrap()
1426
802
  .unwrap()
@@ -1435,7 +811,7 @@ pub(crate) mod tests {
1435
811
  }
1436
812
 
1437
813
  // The snapshot's ordinal and exact seed text, and how many commands are journaled.
1438
- async fn snapshot_and_journal(client: &Mutex<Client>) -> (i64, String, i64) {
814
+ async fn snapshot_and_journal(client: &Store) -> (i64, String, i64) {
1439
815
  let guard = client.lock().await;
1440
816
  let snapshot = guard
1441
817
  .query_one("SELECT ordinal, seed::text FROM hecks_lambda_snapshot", &[])
@@ -1457,9 +833,8 @@ pub(crate) mod tests {
1457
833
  // appended — silently violating the uniqueness the domain
1458
834
  // itself enforces. With the lock serializing the whole
1459
835
  // read-then-append sequence, exactly one must be accepted.
1460
- let client = scratch_db("rust_host_dispatch_test_3").await;
1461
- provision_lineage(&*client.lock().await, "Banking", 1, &["Customer"]).await;
1462
- 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");
1463
838
  let wasm_path = wasm_path();
1464
839
 
1465
840
  let (first, second) = tokio::join!(
@@ -1483,7 +858,7 @@ pub(crate) mod tests {
1483
858
  "exactly one of two concurrent duplicate registrations should be accepted: {:?} / {:?}",
1484
859
  first.result, second.result
1485
860
  );
1486
- let steps = journal::load_steps(&*client.lock().await).await.unwrap();
861
+ let steps = client.journal_steps().await;
1487
862
  assert_eq!(
1488
863
  steps.len(),
1489
864
  1,
@@ -1493,14 +868,13 @@ pub(crate) mod tests {
1493
868
 
1494
869
  #[tokio::test]
1495
870
  async fn read_reflects_prior_dispatches_without_persisting_anything_new() {
1496
- let client = scratch_db("rust_host_dispatch_test_4").await;
1497
- provision_lineage(&*client.lock().await, "Banking", 1, &["Customer"]).await;
871
+ let client = crate::dispatch_tests::tests::memory_store();
1498
872
 
1499
873
  handle(
1500
874
  Host::new(
1501
875
  &client,
1502
876
  &wasm_path(),
1503
- &test_config("Banking", 1),
877
+ &crate::dispatch_tests::tests::memory_config("Banking"),
1504
878
  &lambda_client::NeverInvoker,
1505
879
  ),
1506
880
  "Banking::Customer.Register",
@@ -1527,19 +901,19 @@ pub(crate) mod tests {
1527
901
 
1528
902
  // The sharp proof a read never appends: the journal is exactly
1529
903
  // as long after the read as it was before it.
1530
- let steps = journal::load_steps(&*client.lock().await).await.unwrap();
904
+ let steps = client.journal_steps().await;
1531
905
  assert_eq!(steps.len(), 1, "a read must never persist a journal row");
1532
906
  }
1533
907
 
1534
908
  // Records every call and answers with a canned "nothing refused"
1535
909
  // body, proving a cross-domain policy reaches `lambda_client::deliver`
1536
910
  // with the right name/payload end to end, short of a real AWS Lambda.
1537
- struct RecordingInvoker {
911
+ pub struct RecordingInvoker {
1538
912
  calls: std::sync::Mutex<Vec<(String, String)>>,
1539
913
  }
1540
914
 
1541
915
  impl RecordingInvoker {
1542
- fn new() -> Self {
916
+ pub fn new() -> Self {
1543
917
  Self {
1544
918
  calls: std::sync::Mutex::new(Vec::new()),
1545
919
  }
@@ -1566,15 +940,8 @@ pub(crate) mod tests {
1566
940
 
1567
941
  #[tokio::test]
1568
942
  async fn a_cross_domain_policy_delivers_through_the_real_rehydrate_replay_path() {
1569
- let client = scratch_db("rust_host_dispatch_test_6").await;
1570
- provision_lineage(
1571
- &*client.lock().await,
1572
- "Banking",
1573
- 1,
1574
- &["Customer", "Account"],
1575
- )
1576
- .await;
1577
- 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");
1578
945
  let invoker = RecordingInvoker::new();
1579
946
 
1580
947
  handle(
@@ -1646,7 +1013,7 @@ pub(crate) mod tests {
1646
1013
  .find(|r| r["policy"] == "ReviewOnFreeze")
1647
1014
  .expect("ReviewOnFreeze should appear in \"reactions\" too");
1648
1015
  assert_eq!(merged["on"], "AccountFrozen");
1649
- assert_eq!(merged["trigger"], "Compliance::AccountFreezeReview.Open");
1016
+ assert_eq!(merged["trigger"], "Compliance::Review.Open");
1650
1017
  assert_eq!(merged["delivered"], true);
1651
1018
 
1652
1019
  {
@@ -1657,9 +1024,10 @@ pub(crate) mod tests {
1657
1024
  let sent: serde_json::Value = serde_json::from_str(payload).unwrap();
1658
1025
  // Fully qualified, not the bare "Compliance.OpenReview":
1659
1026
  // generated match arms are always "Domain::Aggregate.Command",
1660
- // and Compliance's own aggregate is named `AccountFreezeReview`.
1661
- assert_eq!(sent["verb"], "Compliance::AccountFreezeReview.Open");
1662
- 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");
1663
1031
  }
1664
1032
 
1665
1033
  // Never re-delivered on a later replay: `pending_cross_domain`
@@ -1687,9 +1055,8 @@ pub(crate) mod tests {
1687
1055
  // registry — real, corpus-declared.
1688
1056
  #[tokio::test]
1689
1057
  async fn a_role_gated_command_is_actually_checked_against_the_caller_role() {
1690
- let client = scratch_db("rust_host_dispatch_test_7").await;
1691
- provision_lineage(&*client.lock().await, "Banking", 1, &["Customer"]).await;
1692
- 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");
1693
1060
 
1694
1061
  // The correct role succeeds, same as every other registration,
1695
1062
  // just now with a caller actually bound and matching.
@@ -1734,7 +1101,7 @@ pub(crate) mod tests {
1734
1101
 
1735
1102
  // And the refused command was never persisted — same discipline
1736
1103
  // every other refusal in this file is held to.
1737
- let steps = journal::load_steps(&*client.lock().await).await.unwrap();
1104
+ let steps = client.journal_steps().await;
1738
1105
  assert_eq!(
1739
1106
  steps.len(),
1740
1107
  1,
@@ -1742,6 +1109,25 @@ pub(crate) mod tests {
1742
1109
  );
1743
1110
  }
1744
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
+
1745
1131
  #[test]
1746
1132
  fn enforcement_reads_off_shadow_and_enforce_and_treats_anything_else_as_off() {
1747
1133
  assert_eq!(Enforcement::parse(None), Enforcement::Off);
@@ -1778,9 +1164,8 @@ pub(crate) mod tests {
1778
1164
  #[tokio::test]
1779
1165
  async fn an_unidentified_caller_is_refused_when_enforced_and_let_through_when_off_or_shadowed()
1780
1166
  {
1781
- let client = scratch_db("rust_host_dispatch_test_enforce").await;
1782
- provision_lineage(&*client.lock().await, "Banking", 1, &["Customer"]).await;
1783
- 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");
1784
1169
  let go = |mode: Enforcement, id: &'static str| {
1785
1170
  let client = &client;
1786
1171
  let config = &config;
@@ -1825,7 +1210,7 @@ pub(crate) mod tests {
1825
1210
  shadowed.result
1826
1211
  );
1827
1212
 
1828
- let steps = journal::load_steps(&*client.lock().await).await.unwrap();
1213
+ let steps = client.journal_steps().await;
1829
1214
  assert_eq!(
1830
1215
  steps.len(),
1831
1216
  2,
@@ -1836,9 +1221,8 @@ pub(crate) mod tests {
1836
1221
  // Shadow may only let through what Off would: a caller that stated a wrong role is refused.
1837
1222
  #[tokio::test]
1838
1223
  async fn shadow_refuses_a_stated_wrong_role_and_still_lets_an_unidentified_caller_through() {
1839
- let client = scratch_db("rust_host_dispatch_test_shadow_stated").await;
1840
- provision_lineage(&*client.lock().await, "Banking", 1, &["Customer"]).await;
1841
- 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");
1842
1226
  let go = |mode: Enforcement, origin: Origin, caller: Caller<'static>, id: &'static str| {
1843
1227
  let client = &client;
1844
1228
  let config = &config;
@@ -1891,7 +1275,7 @@ pub(crate) mod tests {
1891
1275
  nobody.result
1892
1276
  );
1893
1277
 
1894
- let steps = journal::load_steps(&*client.lock().await).await.unwrap();
1278
+ let steps = client.journal_steps().await;
1895
1279
  assert_eq!(
1896
1280
  steps.len(),
1897
1281
  1,
@@ -1903,9 +1287,8 @@ pub(crate) mod tests {
1903
1287
  // unidentified caller is refused under enforce.
1904
1288
  #[tokio::test]
1905
1289
  async fn a_host_internal_dispatch_is_unchecked_under_enforce_but_an_external_one_is_refused() {
1906
- let client = scratch_db("rust_host_dispatch_test_internal").await;
1907
- provision_lineage(&*client.lock().await, "Banking", 1, &["Customer"]).await;
1908
- 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");
1909
1292
  let go = |mode: Enforcement, origin: Origin, id: &'static str| {
1910
1293
  let client = &client;
1911
1294
  let config = &config;
@@ -2216,15 +1599,8 @@ pub(crate) mod tests {
2216
1599
  #[tokio::test]
2217
1600
  async fn a_cross_domain_delivery_that_exhausts_every_retry_dead_letters_and_still_fails_the_invocation(
2218
1601
  ) {
2219
- let client = scratch_db("rust_host_dispatch_test_8").await;
2220
- provision_lineage(
2221
- &*client.lock().await,
2222
- "Banking",
2223
- 1,
2224
- &["Customer", "Account"],
2225
- )
2226
- .await;
2227
- 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");
2228
1604
  let invoker = AlwaysFailingInvoker::new();
2229
1605
 
2230
1606
  handle(
@@ -2284,42 +1660,28 @@ pub(crate) mod tests {
2284
1660
  // The local command still committed: cross-domain delivery runs
2285
1661
  // strictly after commit, so a delivery failure never unwinds it.
2286
1662
  // The dead letter existing at all proves the match already ran.
2287
- let guard = client.lock().await;
2288
- let dead_letters = guard
2289
- .query("SELECT policy, target_domain, target_verb, attempts FROM hecks_cross_domain_dead_letters", &[])
2290
- .await
2291
- .unwrap();
1663
+ let dead_letters = client.dead_letter_rows().await;
2292
1664
  assert_eq!(
2293
1665
  dead_letters.len(),
2294
1666
  1,
2295
1667
  "exactly one exhausted delivery should have been dead-lettered: {dead_letters:?}"
2296
1668
  );
2297
- let policy: String = dead_letters[0].get(0);
2298
- let target_domain: String = dead_letters[0].get(1);
2299
- let target_verb: String = dead_letters[0].get(2);
2300
- let attempts: i32 = dead_letters[0].get(3);
1669
+ let (policy, target_domain, target_verb, attempts) = dead_letters[0].clone();
2301
1670
  assert_eq!(policy, "ReviewOnFreeze");
2302
1671
  assert_eq!(target_domain, "Compliance");
2303
- assert_eq!(target_verb, "Compliance::AccountFreezeReview.Open");
1672
+ assert_eq!(target_verb, "Compliance::Review.Open");
2304
1673
  assert_eq!(attempts, lambda_client::MAX_DELIVERY_ATTEMPTS as i32);
2305
1674
  }
2306
1675
 
2307
1676
  // Pins a real drop: a second cross-domain reaction from the same
2308
1677
  // step must not go dark just because the first one's delivery
2309
1678
  // exhausted its retries. `SafeDepositBox.Surrender` emits both
2310
- // `BoxSurrendered` (-> Compliance) and `KeyReturnDue` (-> Notifications)
1679
+ // `BoxSurrendered` (-> Compliance) and `KeyReturnRequested` (-> Notifications)
2311
1680
  // from one command.
2312
1681
  #[tokio::test]
2313
1682
  async fn a_failed_cross_domain_delivery_does_not_drop_a_sibling_reaction_from_the_same_step() {
2314
- let client = scratch_db("rust_host_dispatch_test_11").await;
2315
- provision_lineage(
2316
- &*client.lock().await,
2317
- "Banking",
2318
- 1,
2319
- &["Customer", "SafeDepositBox"],
2320
- )
2321
- .await;
2322
- 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");
2323
1685
  let invoker = AlwaysFailingInvoker::new();
2324
1686
 
2325
1687
  handle(
@@ -2375,19 +1737,224 @@ pub(crate) mod tests {
2375
1737
  the fix under test is exactly this: a fresh Vec would only ever show one policy's worth"
2376
1738
  );
2377
1739
 
2378
- let guard = client.lock().await;
2379
- let dead_letters = guard
2380
- .query(
2381
- "SELECT policy FROM hecks_cross_domain_dead_letters ORDER BY policy",
2382
- &[],
2383
- )
1740
+ let policies: Vec<String> = client
1741
+ .dead_letter_rows()
2384
1742
  .await
2385
- .unwrap();
2386
- let policies: Vec<String> = dead_letters.iter().map(|row| row.get(0)).collect();
1743
+ .into_iter()
1744
+ .map(|row| row.0)
1745
+ .collect();
2387
1746
  assert_eq!(
2388
1747
  policies,
2389
1748
  vec!["FlagKeyReturn".to_string(), "ReviewOnBoxSurrender".to_string()],
2390
1749
  "BOTH sibling reactions must be dead-lettered, not just whichever ran first: {policies:?}"
2391
1750
  );
2392
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
+ }
2393
1960
  }