hecks 5.0.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1277) hide show
  1. checksums.yaml +4 -4
  2. data/examples/pizzas/bluebook/pizzas.bluebook +1 -0
  3. data/exe/hecks +14 -2
  4. data/lib/hecks/adapters/driven/claude_code.rb +1 -1
  5. data/lib/hecks/adapters/driven/d1.rb +2 -1
  6. data/lib/hecks/adapters/driven/folder.rb +35 -28
  7. data/lib/hecks/adapters/driven/folder_overlay.rb +51 -0
  8. data/lib/hecks/adapters/driven/google_authentication.adapter +3 -0
  9. data/lib/hecks/adapters/driven/google_authentication.rb +99 -0
  10. data/lib/hecks/adapters/driven/governance_authorization.rb +32 -36
  11. data/lib/hecks/adapters/driven/heki/journal.rb +45 -0
  12. data/lib/hecks/adapters/driven/identity_registry.rb +1 -1
  13. data/lib/hecks/adapters/driven/in_memory_ordering.rb +3 -2
  14. data/lib/hecks/adapters/driven/key_keeper.rb +42 -0
  15. data/lib/hecks/adapters/driven/lambda/client.rb +8 -2
  16. data/lib/hecks/adapters/driven/lambda/remote_dispatcher.rb +185 -0
  17. data/lib/hecks/adapters/driven/lambda.rb +14 -2
  18. data/lib/hecks/adapters/driven/memory/dispatch_claims.rb +25 -0
  19. data/lib/hecks/adapters/driven/memory.rb +15 -4
  20. data/lib/hecks/adapters/driven/postgres/codec.rb +1 -1
  21. data/lib/hecks/adapters/driven/postgres/journal.rb +46 -0
  22. data/lib/hecks/adapters/driven/postgres/outbox.rb +31 -5
  23. data/lib/hecks/adapters/driven/postgres/projection.rb +43 -0
  24. data/lib/hecks/adapters/driven/postgres/repository.rb +1 -1
  25. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +2 -1
  26. data/lib/hecks/adapters/driven/postgres/set_dialect.rb +44 -0
  27. data/lib/hecks/adapters/driven/postgres.adapter +8 -0
  28. data/lib/hecks/adapters/driven/postgres.rb +8 -3
  29. data/lib/hecks/adapters/driven/postgres_dump/connection.rb +57 -0
  30. data/lib/hecks/adapters/driven/postgres_dump.rb +117 -0
  31. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/boot.rb +6 -3
  32. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/class_methods.rb +1 -1
  33. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/dialect.rb +1 -1
  34. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/chain_sql.rb +8 -4
  35. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/domain_rename.rb +2 -2
  36. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/era_integrity.rb +2 -2
  37. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/era_store.rb +3 -3
  38. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/mint_transaction.rb +2 -2
  39. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/sql_templates.rb +6 -2
  40. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/tail_merge.rb +1 -1
  41. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage.rb +1 -1
  42. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager/context.rb +15 -5
  43. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager/coverage_check.rb +13 -11
  44. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager/era_resolver.rb +23 -6
  45. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager/merge_coordinator.rb +2 -2
  46. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager/minter.rb +12 -9
  47. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager/refusals.rb +13 -1
  48. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager.rb +6 -6
  49. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/query_phases.rb +8 -2
  50. data/lib/hecks/adapters/driven/postgres_era.adapter +7 -1
  51. data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era.rb +14 -13
  52. data/lib/hecks/adapters/driven/prism.rb +4 -3
  53. data/lib/hecks/adapters/driven/projection/read_model_walk.rb +126 -0
  54. data/lib/hecks/adapters/driven/projection/row_decoding.rb +57 -0
  55. data/lib/hecks/adapters/driven/set_transform/assignments.rb +101 -0
  56. data/lib/hecks/adapters/driven/set_transform/expressions.rb +138 -0
  57. data/lib/hecks/adapters/driven/set_transform.rb +81 -0
  58. data/lib/hecks/adapters/driven/sql_keyset.rb +61 -0
  59. data/lib/hecks/adapters/driven/sql_query_builder.rb +28 -9
  60. data/lib/hecks/adapters/driven/sqlite/codec.rb +1 -1
  61. data/lib/hecks/adapters/driven/sqlite/ddl.rb +7 -0
  62. data/lib/hecks/adapters/driven/sqlite/dialect.rb +2 -1
  63. data/lib/hecks/adapters/driven/sqlite/dispatch_claims.rb +28 -0
  64. data/lib/hecks/adapters/driven/sqlite/journal.rb +46 -0
  65. data/lib/hecks/adapters/driven/sqlite/outbox.rb +3 -2
  66. data/lib/hecks/adapters/driven/sqlite/projection.rb +9 -135
  67. data/lib/hecks/adapters/driven/sqlite/repository.rb +1 -1
  68. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +16 -4
  69. data/lib/hecks/adapters/driven/sqlite/set_dialect.rb +39 -0
  70. data/lib/hecks/adapters/driven/sqlite.adapter +2 -0
  71. data/lib/hecks/adapters/driven/sqlite.rb +11 -2
  72. data/lib/hecks/adapters/driven.rb +8 -3
  73. data/lib/hecks/adapters/driving/cli.rb +1 -1
  74. data/lib/hecks/adapters/driving/cli_runner/answers.rb +23 -16
  75. data/lib/hecks/adapters/driving/cli_runner/resolution.rb +25 -24
  76. data/lib/hecks/adapters/driving/cli_runner/settling.rb +9 -5
  77. data/lib/hecks/adapters/driving/cli_runner/streaming.rb +1 -1
  78. data/lib/hecks/adapters/driving/cli_runner/suggestions.rb +15 -7
  79. data/lib/hecks/adapters/driving/cli_runner/verb_names.rb +49 -0
  80. data/lib/hecks/adapters/driving/cli_runner.rb +4 -2
  81. data/lib/hecks/adapters/driving/github_webhook.rb +2 -1
  82. data/lib/hecks/adapters/driving/handle/verb_methods.rb +46 -0
  83. data/lib/hecks/adapters/driving/handle.rb +9 -17
  84. data/lib/hecks/adapters/driving/json.rb +11 -8
  85. data/lib/hecks/adapters/driving/launcher_options.rb +37 -32
  86. data/lib/hecks/adapters/driving/mcp_scope/env_problems.rb +3 -1
  87. data/lib/hecks/adapters/driving/mcp_scope.rb +8 -7
  88. data/lib/hecks/adapters/driving/newcomer.rb +6 -5
  89. data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +13 -12
  90. data/lib/hecks/adapters/driving/ruby/bulk_verbs.rb +127 -0
  91. data/lib/hecks/adapters/driving/ruby/{chapter.rb → declaration.rb} +27 -27
  92. data/lib/hecks/adapters/driving/ruby/query_rows.rb +27 -0
  93. data/lib/hecks/adapters/driving/ruby.rb +25 -24
  94. data/lib/hecks/adapters/driving/usage_cache.rb +14 -3
  95. data/lib/hecks/assembly.rb +101 -0
  96. data/lib/hecks/behaviors/dsl.rb +13 -3
  97. data/lib/hecks/behaviors/expectations/boot.rb +7 -6
  98. data/lib/hecks/behaviors/expectations/checks.rb +1 -1
  99. data/lib/hecks/behaviors/expectations/dispatching.rb +10 -9
  100. data/lib/hecks/behaviors/expectations/qualify.rb +9 -9
  101. data/lib/hecks/behaviors/expectations.rb +24 -10
  102. data/lib/hecks/behaviors/ir.rb +4 -2
  103. data/lib/hecks/behaviors/rspec.rb +1 -1
  104. data/lib/hecks/behaviors/runner.rb +31 -4
  105. data/lib/hecks/bench/environment.rb +6 -2
  106. data/lib/hecks/bench/postgres_probe.rb +3 -1
  107. data/lib/hecks/bench/report.rb +4 -1
  108. data/lib/hecks/bench/suite.rb +9 -3
  109. data/lib/hecks/bluebook/acknowledgements.rb +81 -0
  110. data/lib/hecks/bluebook/aggregate.rb +13 -4
  111. data/lib/hecks/bluebook/ask_resolution.rb +17 -17
  112. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +8 -5
  113. data/lib/hecks/bluebook/assembly/contracts/structure.rb +11 -7
  114. data/lib/hecks/bluebook/assembly/marks.rb +10 -1
  115. data/lib/hecks/bluebook/assembly/scalars.rb +8 -2
  116. data/lib/hecks/bluebook/assembly/specializer.rb +2 -2
  117. data/lib/hecks/bluebook/assembly.rb +10 -10
  118. data/lib/hecks/bluebook/behaviour/aggregate.rb +2 -1
  119. data/lib/hecks/bluebook/behaviour/attribute.rb +12 -0
  120. data/lib/hecks/bluebook/behaviour/command.rb +1 -1
  121. data/lib/hecks/bluebook/behaviour/{chapter.rb → declaration.rb} +17 -17
  122. data/lib/hecks/bluebook/behaviour/domain_port.rb +1 -1
  123. data/lib/hecks/bluebook/behaviour/entity.rb +1 -0
  124. data/lib/hecks/bluebook/behaviour/hecksagon.rb +8 -1
  125. data/lib/hecks/bluebook/behaviour/process_manager.rb +4 -1
  126. data/lib/hecks/bluebook/behaviour/query.rb +5 -0
  127. data/lib/hecks/bluebook/behaviour/read_model.rb +4 -1
  128. data/lib/hecks/bluebook/behaviour/traits.rb +13 -0
  129. data/lib/hecks/bluebook/capabilities/attribute_values.rb +17 -16
  130. data/lib/hecks/bluebook/capabilities/package_declared.rb +65 -0
  131. data/lib/hecks/bluebook/capabilities.rb +32 -132
  132. data/lib/hecks/bluebook/command.rb +22 -7
  133. data/lib/hecks/bluebook/context_map.rb +104 -0
  134. data/lib/hecks/bluebook/{chapter.rb → declaration.rb} +13 -13
  135. data/lib/hecks/bluebook/dsl/adapter_builder.rb +28 -4
  136. data/lib/hecks/bluebook/dsl/aggregate_builder/draining.rb +2 -2
  137. data/lib/hecks/bluebook/dsl/aggregate_builder/one_of_reference.rb +54 -0
  138. data/lib/hecks/bluebook/dsl/aggregate_builder/query_field_sealing.rb +11 -2
  139. data/lib/hecks/bluebook/dsl/aggregate_builder/query_sealing.rb +15 -5
  140. data/lib/hecks/bluebook/dsl/aggregate_builder/relationships.rb +21 -3
  141. data/lib/hecks/bluebook/dsl/aggregate_builder/rules.rb +22 -22
  142. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +2 -2
  143. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +27 -22
  144. data/lib/hecks/bluebook/dsl/bluebook_builder/pending_givens.rb +22 -22
  145. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/computed_sources.rb +27 -21
  146. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/correlation_keys.rb +3 -5
  147. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/event_shapes.rb +5 -4
  148. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/fan_out_rows.rb +3 -3
  149. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/hop_tails.rb +13 -5
  150. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/projected_fields.rb +4 -4
  151. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/provisions.rb +38 -84
  152. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/query_hops.rb +5 -5
  153. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/references.rb +6 -6
  154. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/with_projections.rb +6 -6
  155. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +3 -3
  156. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +67 -67
  157. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
  158. data/lib/hecks/bluebook/dsl/command_builder/folded_sets.rb +48 -0
  159. data/lib/hecks/bluebook/dsl/command_builder.rb +28 -15
  160. data/lib/hecks/bluebook/dsl/context_map_builder.rb +73 -0
  161. data/lib/hecks/bluebook/dsl/entity_builder/rules.rb +17 -17
  162. data/lib/hecks/bluebook/dsl/entity_builder/scope.rb +3 -3
  163. data/lib/hecks/bluebook/dsl/entity_builder/sealing.rb +2 -2
  164. data/lib/hecks/bluebook/dsl/entity_builder.rb +3 -3
  165. data/lib/hecks/bluebook/dsl/hecksagon_builder/{chapter_scope.rb → bluebook_scope.rb} +14 -14
  166. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +33 -27
  167. data/lib/hecks/bluebook/dsl/identity_declaration.rb +6 -2
  168. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +4 -1
  169. data/lib/hecks/bluebook/dsl/query_builder.rb +14 -8
  170. data/lib/hecks/bluebook/dsl/read_model_builder/sealing.rb +6 -11
  171. data/lib/hecks/bluebook/dsl/read_model_builder.rb +1 -2
  172. data/lib/hecks/bluebook/dsl/word_gate.rb +2 -1
  173. data/lib/hecks/bluebook/dsl/world_builder.rb +2 -2
  174. data/lib/hecks/bluebook/dsl.rb +3 -2
  175. data/lib/hecks/bluebook/expression/ast_json/emitters.rb +4 -0
  176. data/lib/hecks/bluebook/expression/ast_json.rb +5 -3
  177. data/lib/hecks/bluebook/expression/ast_reader/blocks.rb +41 -0
  178. data/lib/hecks/bluebook/expression/ast_reader.rb +2 -10
  179. data/lib/hecks/bluebook/expression/canonical_form.rb +9 -4
  180. data/lib/hecks/bluebook/expression/evaluator/parser.rb +4 -1
  181. data/lib/hecks/bluebook/expression/evaluator.rb +13 -9
  182. data/lib/hecks/bluebook/expression/projection.json +6 -0
  183. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +50 -2
  184. data/lib/hecks/bluebook/expression/resolver/lookups.rb +2 -1
  185. data/lib/hecks/bluebook/expression/resolver/operations.rb +1 -0
  186. data/lib/hecks/bluebook/expression/resolver/parsing.rb +21 -15
  187. data/lib/hecks/bluebook/expression/resolver/scanning.rb +4 -1
  188. data/lib/hecks/bluebook/expression.rb +1 -1
  189. data/lib/hecks/bluebook/hecksagon.rb +48 -23
  190. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +4 -1
  191. data/lib/hecks/bluebook/meta_validator/judge/cells.rb +1 -1
  192. data/lib/hecks/bluebook/meta_validator/judge/identity.rb +2 -1
  193. data/lib/hecks/bluebook/meta_validator/modes.rb +27 -27
  194. data/lib/hecks/bluebook/meta_validator/plan.rb +5 -5
  195. data/lib/hecks/bluebook/meta_validator/readings/fields.rb +3 -3
  196. data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +11 -3
  197. data/lib/hecks/bluebook/meta_validator/reconstruction/reading.rb +1 -1
  198. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +19 -19
  199. data/lib/hecks/bluebook/meta_validator/shapes.rb +6 -1
  200. data/lib/hecks/bluebook/meta_validator/syntax_boot/disk_cache.rb +18 -15
  201. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +21 -15
  202. data/lib/hecks/bluebook/meta_validator/verdict_cache/prebuilt.rb +4 -1
  203. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +11 -10
  204. data/lib/hecks/bluebook/meta_validator.rb +25 -22
  205. data/lib/hecks/bluebook/model_check/aggregate_checks.rb +68 -0
  206. data/lib/hecks/bluebook/model_check/ask_checks.rb +6 -5
  207. data/lib/hecks/bluebook/model_check/client_profile.rb +8 -42
  208. data/lib/hecks/bluebook/model_check/context_checks.rb +97 -0
  209. data/lib/hecks/bluebook/model_check/context_map_checks.rb +158 -0
  210. data/lib/hecks/bluebook/model_check/lifecycle_checks.rb +27 -5
  211. data/lib/hecks/bluebook/model_check/one_of_checks.rb +106 -0
  212. data/lib/hecks/bluebook/model_check/orphan_checks.rb +129 -0
  213. data/lib/hecks/bluebook/model_check/redundant_relationships.rb +38 -0
  214. data/lib/hecks/bluebook/model_check/reference_checks.rb +11 -1
  215. data/lib/hecks/bluebook/model_check/saga_checks.rb +8 -3
  216. data/lib/hecks/bluebook/model_check/verbs.rb +4 -4
  217. data/lib/hecks/bluebook/model_check.rb +32 -12
  218. data/lib/hecks/bluebook/named_pattern.rb +12 -1
  219. data/lib/hecks/bluebook/one_of_union.rb +35 -0
  220. data/lib/hecks/bluebook/pattern_subset.rb +12 -3
  221. data/lib/hecks/bluebook/project_loader.rb +3 -2
  222. data/lib/hecks/bluebook/project_register.rb +21 -5
  223. data/lib/hecks/bluebook/query.rb +4 -1
  224. data/lib/hecks/bluebook/read_model.rb +5 -5
  225. data/lib/hecks/bluebook/reference.rb +3 -3
  226. data/lib/hecks/bluebook/smoke_test.rb +15 -13
  227. data/lib/hecks/bluebook/synthesizer.rb +48 -13
  228. data/lib/hecks/bluebook/translation_expression/parser.rb +14 -5
  229. data/lib/hecks/bluebook.rb +6 -3
  230. data/lib/hecks/bulk/bluebook/adapters/row_sweeper.adapter +7 -0
  231. data/lib/hecks/bulk/bluebook/bulk.behaviors +18 -0
  232. data/lib/hecks/bulk/bluebook/bulk.bluebook +126 -0
  233. data/lib/hecks/bulk/bluebook/bulk.hecksagon +11 -0
  234. data/lib/hecks/bulk/bluebook/bulk.ports.hecksagon +13 -0
  235. data/lib/hecks/bulk/plan.rb +60 -0
  236. data/lib/hecks/bulk/row_sweeper.rb +34 -0
  237. data/lib/hecks/bulk/set_arguments.rb +68 -0
  238. data/lib/hecks/bulk/set_eligibility.rb +50 -0
  239. data/lib/hecks/bulk/set_planner.rb +100 -0
  240. data/lib/hecks/bulk/set_sweep.rb +78 -0
  241. data/lib/hecks/bulk/sweep.rb +67 -0
  242. data/lib/hecks/cli/check_era.rb +6 -6
  243. data/lib/hecks/cli/command_table.rb +2 -2
  244. data/lib/hecks/cli/console.rb +11 -6
  245. data/lib/hecks/cli/document.rb +14 -9
  246. data/lib/hecks/cli/domain_writer.rb +4 -1
  247. data/lib/hecks/cli/expected_era.rb +139 -0
  248. data/lib/hecks/cli/expression_projection.rb +2 -2
  249. data/lib/hecks/cli/follow.rb +3 -3
  250. data/lib/hecks/cli/history.rb +4 -1
  251. data/lib/hecks/cli/interview_draft.rb +8 -1
  252. data/lib/hecks/cli/interview_record.rb +7 -3
  253. data/lib/hecks/cli/interview_run.rb +7 -5
  254. data/lib/hecks/cli/interview_session/proposals.rb +5 -2
  255. data/lib/hecks/cli/interview_session.rb +3 -2
  256. data/lib/hecks/cli/interview_shape/lifecycle.rb +92 -0
  257. data/lib/hecks/cli/interview_shape.rb +19 -73
  258. data/lib/hecks/cli/model_check/examination.rb +63 -14
  259. data/lib/hecks/cli/model_check/sources.rb +4 -2
  260. data/lib/hecks/cli/model_check.rb +1 -1
  261. data/lib/hecks/cli/persistence_notice.rb +45 -0
  262. data/lib/hecks/cli/present.rb +1 -1
  263. data/lib/hecks/cli/project_cli/launcher_source.rb +11 -9
  264. data/lib/hecks/cli/project_cli/templates.rb +24 -3
  265. data/lib/hecks/cli/project_cli.rb +11 -13
  266. data/lib/hecks/cli/project_diagrams.rb +20 -20
  267. data/lib/hecks/cli/project_glossary.rb +14 -14
  268. data/lib/hecks/cli/reference.rb +1 -1
  269. data/lib/hecks/cli/refresh_projections.rb +16 -11
  270. data/lib/hecks/cli/run.rb +27 -5
  271. data/lib/hecks/cli/seed_semantics_corpus.rb +14 -4
  272. data/lib/hecks/cli/shape.rb +3 -4
  273. data/lib/hecks/cli/smoke_http/transport.rb +3 -2
  274. data/lib/hecks/cli/statements.rb +7 -7
  275. data/lib/hecks/cli/stores.rb +70 -2
  276. data/lib/hecks/cli/stress_concurrency_specs/reporting.rb +4 -1
  277. data/lib/hecks/codemod.rb +3 -3
  278. data/lib/hecks/composer/adapters/claude_composer.rb +3 -2
  279. data/lib/hecks/composer/agent.rb +1 -1
  280. data/lib/hecks/composer/assistant.rb +2 -2
  281. data/lib/hecks/composer/behavior_run.rb +7 -1
  282. data/lib/hecks/composer/bluebook/composer.bluebook +18 -25
  283. data/lib/hecks/composer/bluebook/composer.hecksagon +1 -1
  284. data/lib/hecks/composer/board.rb +6 -6
  285. data/lib/hecks/composer/board_edit/command_text.rb +4 -1
  286. data/lib/hecks/composer/board_edit/source.rb +18 -9
  287. data/lib/hecks/composer/board_edit.rb +2 -2
  288. data/lib/hecks/composer/check.rb +22 -22
  289. data/lib/hecks/composer/domains.rb +1 -1
  290. data/lib/hecks/composer/live.rb +19 -13
  291. data/lib/hecks/composer/outline.rb +4 -1
  292. data/lib/hecks/composer/page.html +1 -1
  293. data/lib/hecks/composer/panels.rb +6 -3
  294. data/lib/hecks/composer/parse.rb +53 -36
  295. data/lib/hecks/composer/session.rb +22 -16
  296. data/lib/hecks/composer/toolbox.rb +9 -6
  297. data/lib/hecks/composer/web.rb +3 -2
  298. data/lib/hecks/composer/workbench.rb +12 -9
  299. data/lib/hecks/composer.rb +1 -1
  300. data/lib/hecks/construct.rb +7 -7
  301. data/lib/hecks/corpus/adapters.rb +2 -1
  302. data/lib/hecks/corpus/routes.rb +25 -5
  303. data/lib/hecks/corpus/rust.rb +59 -24
  304. data/lib/hecks/corpus/sources.rb +12 -11
  305. data/lib/hecks/corpus/worlds.rb +2 -1
  306. data/lib/hecks/corpus.rb +30 -22
  307. data/lib/hecks/deploy/bluebook/deploy.bluebook +304 -518
  308. data/lib/hecks/doc/reference/harvester.rb +4 -1
  309. data/lib/hecks/doc/reference/readme.rb +11 -6
  310. data/lib/hecks/doc/reference/rendering.rb +11 -5
  311. data/lib/hecks/doc/reference.rb +7 -5
  312. data/lib/hecks/embryonaut_bluebook/lock.rb +5 -1
  313. data/lib/hecks/embryonaut_bluebook/manifest.rb +7 -1
  314. data/lib/hecks/embryonaut_bluebook/registry/changelog.rb +4 -1
  315. data/lib/hecks/embryonaut_bluebook/registry/versions.rb +13 -2
  316. data/lib/hecks/embryonaut_bluebook/registry.rb +5 -2
  317. data/lib/hecks/embryonaut_bluebook/shape.rb +1 -1
  318. data/lib/hecks/embryonaut_bluebook/vendor.rb +5 -2
  319. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +15 -1
  320. data/lib/hecks/embryonaut_bluebook.rb +26 -12
  321. data/lib/hecks/engine_agreement.rb +4 -1
  322. data/lib/hecks/forms/app/commands.rb +35 -2
  323. data/lib/hecks/forms/app/records.rb +12 -12
  324. data/lib/hecks/forms/app/responses.rb +1 -1
  325. data/lib/hecks/forms/app.rb +22 -19
  326. data/lib/hecks/forms/command_form_renderer.rb +2 -2
  327. data/lib/hecks/forms/field_controls.rb +6 -1
  328. data/lib/hecks/forms/field_shape.rb +14 -6
  329. data/lib/hecks/forms/html.rb +8 -7
  330. data/lib/hecks/forms/html_pattern.rb +80 -0
  331. data/lib/hecks/forms/index_renderer.rb +11 -11
  332. data/lib/hecks/forms/params.rb +2 -1
  333. data/lib/hecks/forms/port_argument.rb +4 -1
  334. data/lib/hecks/forms/query_form_renderer.rb +1 -1
  335. data/lib/hecks/forms/record_renderer.rb +2 -2
  336. data/lib/hecks/forms/record_table.rb +1 -1
  337. data/lib/hecks/forms/reference_options.rb +2 -1
  338. data/lib/hecks/forms/text_shape.rb +4 -1
  339. data/lib/hecks/forms/value_object_shape.rb +1 -1
  340. data/lib/hecks/forms.rb +7 -7
  341. data/lib/hecks/fqn.rb +8 -2
  342. data/lib/hecks/framework/bluebook/change.bluebook +10 -15
  343. data/lib/hecks/framework/bluebook/change.hecksagon +1 -1
  344. data/lib/hecks/framework/bluebook/compliance.bluebook +27 -126
  345. data/lib/hecks/framework/bluebook/console_settings.bluebook +199 -114
  346. data/lib/hecks/framework/bluebook/framework.hecksagon +7 -0
  347. data/lib/hecks/framework/bluebook/governance.bluebook +36 -14
  348. data/lib/hecks/framework/bluebook/identity.bluebook +1 -1
  349. data/lib/hecks/framework/bluebook/privacy.bluebook +123 -19
  350. data/lib/hecks/framework/bluebook/privacy.key_keeper.adapter +7 -0
  351. data/lib/hecks/framework/bluebook/privacy.ports.hecksagon +19 -0
  352. data/lib/hecks/framework/compliance.oidc.json +6 -36
  353. data/lib/hecks/framework/console_settings.oidc.json +39 -24
  354. data/lib/hecks/framework/privacy.oidc.json +25 -0
  355. data/lib/hecks/framework.rb +24 -8
  356. data/lib/hecks/framework_bluebooks.rb +156 -0
  357. data/lib/hecks/freezer.rb +7 -1
  358. data/lib/hecks/fuzzing/combination_miner.rb +9 -6
  359. data/lib/hecks/fuzzing/concurrent_dispatch/world_copy.rb +3 -1
  360. data/lib/hecks/fuzzing/concurrent_dispatch.rb +2 -2
  361. data/lib/hecks/fuzzing/domain_generator.rb +8 -3
  362. data/lib/hecks/fuzzing/era_boundary.rb +1 -1
  363. data/lib/hecks/fuzzing/failure_triage.rb +13 -6
  364. data/lib/hecks/fuzzing/form_census/{chapter_forms.rb → bluebook_forms.rb} +12 -12
  365. data/lib/hecks/fuzzing/form_census.rb +14 -12
  366. data/lib/hecks/fuzzing/generated_domain_check.rb +2 -1
  367. data/lib/hecks/fuzzing/isolated_boot/postgres_scratch.rb +5 -5
  368. data/lib/hecks/fuzzing/isolated_boot/scratch_schema.rb +19 -9
  369. data/lib/hecks/fuzzing/isolated_boot/worlds.rb +13 -8
  370. data/lib/hecks/fuzzing/isolated_boot.rb +4 -5
  371. data/lib/hecks/fuzzing/mutation/checks.rb +2 -1
  372. data/lib/hecks/fuzzing/mutation/finders.rb +25 -8
  373. data/lib/hecks/fuzzing/mutation/observation.rb +3 -1
  374. data/lib/hecks/fuzzing/mutation/source_map.rb +3 -1
  375. data/lib/hecks/fuzzing/nondeterministic.rb +10 -4
  376. data/lib/hecks/fuzzing/outside_world.rb +1 -1
  377. data/lib/hecks/fuzzing/projection_agreement/binding.rb +63 -0
  378. data/lib/hecks/fuzzing/projection_agreement/comparison.rb +104 -0
  379. data/lib/hecks/fuzzing/projection_agreement.rb +68 -0
  380. data/lib/hecks/fuzzing/properties/aggregation_recompute.rb +1 -1
  381. data/lib/hecks/fuzzing/properties/bulk_parity.rb +118 -0
  382. data/lib/hecks/fuzzing/properties/catalog.rb +7 -0
  383. data/lib/hecks/fuzzing/properties/cursor_walks.rb +56 -0
  384. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +39 -1
  385. data/lib/hecks/fuzzing/properties/group_by_recompute.rb +6 -2
  386. data/lib/hecks/fuzzing/properties/grouped_reduction_recompute.rb +88 -0
  387. data/lib/hecks/fuzzing/properties/guards.rb +4 -1
  388. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +12 -2
  389. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +2 -1
  390. data/lib/hecks/fuzzing/properties/mutation_recompute.rb +3 -2
  391. data/lib/hecks/fuzzing/properties/piece_invariants.rb +3 -2
  392. data/lib/hecks/fuzzing/properties/query_rows.rb +5 -3
  393. data/lib/hecks/fuzzing/properties/querying.rb +6 -3
  394. data/lib/hecks/fuzzing/properties/read_model_names.rb +8 -2
  395. data/lib/hecks/fuzzing/properties/read_models.rb +2 -1
  396. data/lib/hecks/fuzzing/properties/refusals.rb +35 -0
  397. data/lib/hecks/fuzzing/properties/role_checks.rb +3 -2
  398. data/lib/hecks/fuzzing/properties/set_transform_parity.rb +73 -0
  399. data/lib/hecks/fuzzing/properties/tenant_scope.rb +3 -2
  400. data/lib/hecks/fuzzing/properties.rb +6 -0
  401. data/lib/hecks/fuzzing/qa_settings.rb +4 -4
  402. data/lib/hecks/fuzzing/replay/fan_out.rb +4 -3
  403. data/lib/hecks/fuzzing/replay/filters.rb +2 -2
  404. data/lib/hecks/fuzzing/replay/guard_check.rb +2 -2
  405. data/lib/hecks/fuzzing/replay/mutation_trace.rb +23 -10
  406. data/lib/hecks/fuzzing/replay/observation.rb +11 -7
  407. data/lib/hecks/fuzzing/replay/read_only_steps.rb +40 -3
  408. data/lib/hecks/fuzzing/replay/refusal_trace.rb +44 -0
  409. data/lib/hecks/fuzzing/replay/role_check.rb +2 -2
  410. data/lib/hecks/fuzzing/replay/session.rb +38 -18
  411. data/lib/hecks/fuzzing/replay/validation_reports.rb +61 -0
  412. data/lib/hecks/fuzzing/replay.rb +2 -2
  413. data/lib/hecks/fuzzing/rust_gap_manifest.rb +3 -3
  414. data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +18 -8
  415. data/lib/hecks/fuzzing/self_consistency/sagas.rb +2 -2
  416. data/lib/hecks/fuzzing/self_consistency/value_objects.rb +9 -2
  417. data/lib/hecks/fuzzing/self_consistency.rb +4 -8
  418. data/lib/hecks/fuzzing/sequence_generator/adversary/argument_mutations.rb +15 -5
  419. data/lib/hecks/fuzzing/sequence_generator/adversary/caller_draw.rb +14 -10
  420. data/lib/hecks/fuzzing/sequence_generator/adversary/precedence_mutations.rb +1 -1
  421. data/lib/hecks/fuzzing/sequence_generator/argument_drawing.rb +2 -1
  422. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +21 -13
  423. data/lib/hecks/fuzzing/sequence_generator/picker.rb +1 -1
  424. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +3 -3
  425. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +2 -2
  426. data/lib/hecks/fuzzing/sequence_generator.rb +1 -2
  427. data/lib/hecks/fuzzing/shrinker.rb +4 -1
  428. data/lib/hecks/fuzzing/value_generator.rb +14 -8
  429. data/lib/hecks/fuzzing.rb +1 -0
  430. data/lib/hecks/grammar/dialects.rb +56 -0
  431. data/lib/hecks/grammar/evolve/arguments.rb +1 -1
  432. data/lib/hecks/grammar/evolve/keywords.rb +1 -1
  433. data/lib/hecks/grammar/evolve/seeds.rb +34 -8
  434. data/lib/hecks/grammar/expression.bluebook +3 -0
  435. data/lib/hecks/grammar/expression_operators.json +1071 -1
  436. data/lib/hecks/grammar/operators.rb +45 -22
  437. data/lib/hecks/grammar/translation.bluebook +2 -0
  438. data/lib/hecks/grammar/translation_ledger.rb +45 -0
  439. data/lib/hecks/grammar/translation_rules.json +646 -0
  440. data/lib/hecks/grammar.rb +27 -23
  441. data/lib/hecks/hecks/adapters/claude_agents.adapter +7 -0
  442. data/lib/hecks/hecks/adapters/claude_agents.rb +119 -0
  443. data/lib/hecks/hecks/adapters/claude_transcript.rb +71 -0
  444. data/lib/hecks/hecks/adapters/codebase/cache_clearing.rb +32 -0
  445. data/lib/hecks/hecks/adapters/codebase/cargo_tests.rb +50 -0
  446. data/lib/hecks/hecks/adapters/codebase/composing.rb +3 -2
  447. data/lib/hecks/hecks/adapters/codebase/conformance.rb +17 -1
  448. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +1 -1
  449. data/lib/hecks/hecks/adapters/codebase/declarations.rb +144 -0
  450. data/lib/hecks/hecks/adapters/codebase/inspection.rb +5 -4
  451. data/lib/hecks/hecks/adapters/codebase/language.rb +1 -0
  452. data/lib/hecks/hecks/adapters/codebase/npm_registry.rb +7 -1
  453. data/lib/hecks/hecks/adapters/codebase/previewing.rb +62 -0
  454. data/lib/hecks/hecks/adapters/codebase/promotion/gate.rb +1 -1
  455. data/lib/hecks/hecks/adapters/codebase/reading.rb +94 -0
  456. data/lib/hecks/hecks/adapters/codebase/regeneration.rb +6 -2
  457. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +1 -1
  458. data/lib/hecks/hecks/adapters/codebase/ruby_child.rb +5 -2
  459. data/lib/hecks/hecks/adapters/codebase/search.rb +91 -0
  460. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +37 -2
  461. data/lib/hecks/hecks/adapters/codebase/static_directory.rb +59 -0
  462. data/lib/hecks/hecks/adapters/codebase/style.rb +2 -2
  463. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +30 -11
  464. data/lib/hecks/hecks/adapters/codebase/test_suite.rb +41 -3
  465. data/lib/hecks/hecks/adapters/console_capture.rb +27 -0
  466. data/lib/hecks/hecks/adapters/deploy_plans.rb +12 -3
  467. data/lib/hecks/hecks/adapters/deploy_toolchain/answers.rb +14 -5
  468. data/lib/hecks/hecks/adapters/deploy_toolchain/scripts.rb +6 -3
  469. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +3 -3
  470. data/lib/hecks/hecks/adapters/desktop_alert/alert.rb +50 -0
  471. data/lib/hecks/hecks/adapters/desktop_alert.adapter +7 -0
  472. data/lib/hecks/hecks/adapters/desktop_alert.rb +99 -0
  473. data/lib/hecks/hecks/adapters/finding_github.rb +4 -1
  474. data/lib/hecks/hecks/adapters/first_admin.rb +16 -9
  475. data/lib/hecks/hecks/adapters/git.rb +5 -5
  476. data/lib/hecks/hecks/adapters/git_checkout.adapter +7 -0
  477. data/lib/hecks/hecks/adapters/git_checkout.rb +99 -0
  478. data/lib/hecks/hecks/adapters/git_landing/merger.rb +85 -0
  479. data/lib/hecks/hecks/adapters/git_landing.adapter +7 -0
  480. data/lib/hecks/hecks/adapters/git_landing.rb +103 -0
  481. data/lib/hecks/hecks/adapters/git_launcher.adapter +7 -0
  482. data/lib/hecks/hecks/adapters/git_launcher.rb +82 -0
  483. data/lib/hecks/hecks/adapters/git_refs.rb +8 -4
  484. data/lib/hecks/hecks/adapters/git_train/bench.rb +76 -0
  485. data/lib/hecks/hecks/adapters/git_train/carriage.rb +116 -0
  486. data/lib/hecks/hecks/adapters/git_train/local_branch.rb +69 -0
  487. data/lib/hecks/hecks/adapters/git_train/reading.rb +26 -0
  488. data/lib/hecks/hecks/adapters/git_train/survey.rb +60 -0
  489. data/lib/hecks/hecks/adapters/git_train.adapter +7 -0
  490. data/lib/hecks/hecks/adapters/git_train.rb +88 -0
  491. data/lib/hecks/hecks/adapters/git_worktrees.rb +81 -0
  492. data/lib/hecks/hecks/adapters/host_http.rb +5 -4
  493. data/lib/hecks/hecks/adapters/in_process_access.rb +3 -2
  494. data/lib/hecks/hecks/adapters/in_process_boot.rb +62 -35
  495. data/lib/hecks/hecks/adapters/in_process_operations.rb +30 -4
  496. data/lib/hecks/hecks/adapters/journal_store/changes.rb +7 -7
  497. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +14 -7
  498. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +9 -7
  499. data/lib/hecks/hecks/adapters/journal_store/examination.rb +3 -3
  500. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +17 -5
  501. data/lib/hecks/hecks/adapters/journal_store/readings/attestation.rb +3 -3
  502. data/lib/hecks/hecks/adapters/journal_store/readings/scaffolding.rb +7 -4
  503. data/lib/hecks/hecks/adapters/journal_store/readings.rb +2 -2
  504. data/lib/hecks/hecks/adapters/journal_store.rb +2 -2
  505. data/lib/hecks/hecks/adapters/ledger/chain.rb +63 -0
  506. data/lib/hecks/hecks/adapters/ledger/journal.rb +105 -0
  507. data/lib/hecks/hecks/adapters/ledger.adapter +7 -0
  508. data/lib/hecks/hecks/adapters/ledger.rb +89 -0
  509. data/lib/hecks/hecks/adapters/local_files.rb +15 -1
  510. data/lib/hecks/hecks/adapters/pg_admin.rb +7 -1
  511. data/lib/hecks/hecks/adapters/rails_app_files.rb +64 -0
  512. data/lib/hecks/hecks/adapters/rehearsal/audit_report.rb +4 -1
  513. data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +9 -9
  514. data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +2 -2
  515. data/lib/hecks/hecks/adapters/rehearsal/postgres_census.rb +4 -4
  516. data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +3 -3
  517. data/lib/hecks/hecks/adapters/rehearsal/postgres_store.rb +4 -1
  518. data/lib/hecks/hecks/adapters/rehearsal/rust_audit.rb +1 -1
  519. data/lib/hecks/hecks/adapters/rehearsal/subject.rb +34 -5
  520. data/lib/hecks/hecks/adapters/rust_toolchain.rb +5 -3
  521. data/lib/hecks/hecks/adapters/settings_reading.adapter +7 -0
  522. data/lib/hecks/hecks/adapters/settings_reading.rb +142 -0
  523. data/lib/hecks/hecks/adapters/shell.rb +3 -0
  524. data/lib/hecks/hecks/adapters/site_toolchain.rb +8 -8
  525. data/lib/hecks/hecks/adapters/world_clearing.rb +2 -1
  526. data/lib/hecks/hecks/agents.bluebook +246 -0
  527. data/lib/hecks/hecks/codebase.behaviors +13 -1
  528. data/lib/hecks/hecks/codebase.bluebook +554 -586
  529. data/lib/hecks/hecks/composer.behaviors +10 -10
  530. data/lib/hecks/hecks/context_map.hecksagon +7 -6
  531. data/lib/hecks/hecks/custodian.behaviors +96 -1
  532. data/lib/hecks/hecks/custodian.bluebook +553 -157
  533. data/lib/hecks/hecks/deploy.behaviors +1 -1
  534. data/lib/hecks/hecks/deploy.world +3 -4
  535. data/lib/hecks/hecks/hecks.behaviors +2 -2
  536. data/lib/hecks/hecks/hecks.bluebook +9 -14
  537. data/lib/hecks/hecks/hecks.contextmap +25 -0
  538. data/lib/hecks/hecks/hecks.hecksagon +139 -6
  539. data/lib/hecks/hecks/hecks.world +22 -21
  540. data/lib/hecks/hecks/ledger.bluebook +190 -0
  541. data/lib/hecks/hecks/rehearsal.bluebook +43 -77
  542. data/lib/hecks/hecks/shapes.json +17 -0
  543. data/lib/hecks/hecks/site.behaviors +1 -1
  544. data/lib/hecks/hecks/site.world +3 -3
  545. data/lib/hecks/hecks/tooling.bluebook +170 -174
  546. data/lib/hecks/hecks/translations/2-148748.bluebook +2 -0
  547. data/lib/hecks/hecks/translations/2-15a2e4.bluebook +2 -0
  548. data/lib/hecks/hecks/translations/2-1b447c.bluebook +5 -0
  549. data/lib/hecks/hecks/translations/2-240474.bluebook +2 -0
  550. data/lib/hecks/hecks/translations/2-44446d.bluebook +2 -0
  551. data/lib/hecks/hecks/translations/2-46ca26.bluebook +2 -0
  552. data/lib/hecks/hecks/translations/2-473eab.bluebook +2 -0
  553. data/lib/hecks/hecks/translations/2-75cc28.bluebook +2 -0
  554. data/lib/hecks/hecks/translations/2-781c07.bluebook +2 -0
  555. data/lib/hecks/hecks/translations/2-84c18b.bluebook +2 -0
  556. data/lib/hecks/hecks/translations/2-aa5fff.bluebook +2 -0
  557. data/lib/hecks/hecks/translations/2-bfdb48.bluebook +2 -0
  558. data/lib/hecks/hecks/translations/2-cd96d8.bluebook +10 -0
  559. data/lib/hecks/hecks/translations/2-d43f71.bluebook +2 -0
  560. data/lib/hecks/hecks/translations/2-eea577.bluebook +2 -0
  561. data/lib/hecks/hecks/translations/2-f53248.bluebook +2 -0
  562. data/lib/hecks/hecks/translations/2-fbb677.bluebook +2 -0
  563. data/lib/hecks/hecks/translations/2-ffdf12.bluebook +6 -0
  564. data/lib/hecks/ir.rb +8 -1
  565. data/lib/hecks/kernel_capabilities/templates.rb +4 -3
  566. data/lib/hecks/kernel_capabilities.rb +6 -6
  567. data/lib/hecks/language/adapter.bluebook +61 -0
  568. data/lib/hecks/language/bluebook/aggregate.bluebook +36 -6
  569. data/lib/hecks/language/bluebook/attaches/paging.bluebook +1 -1
  570. data/lib/hecks/language/bluebook/bluebook.behaviors +6 -6
  571. data/lib/hecks/language/bluebook/bluebook.bluebook +28 -51
  572. data/lib/hecks/language/bluebook/bluebook.hecksagon +1 -1
  573. data/lib/hecks/language/bluebook/command.bluebook +34 -2
  574. data/lib/hecks/language/bluebook/entity.bluebook +2 -2
  575. data/lib/hecks/language/bluebook/policy.behaviors +2 -2
  576. data/lib/hecks/language/bluebook/policy.bluebook +3 -3
  577. data/lib/hecks/language/bluebook/process_manager.bluebook +3 -3
  578. data/lib/hecks/language/bluebook/projection.bluebook +11 -10
  579. data/lib/hecks/language/bluebook/query.bluebook +1 -0
  580. data/lib/hecks/language/bluebook/shape.bluebook +1 -1
  581. data/lib/hecks/language/bluebook/syntax.bluebook +3 -1
  582. data/lib/hecks/language/bluebook/vocabulary.bluebook +287 -6
  583. data/lib/hecks/language/context_map/context_map.bluebook +90 -0
  584. data/lib/hecks/language/hecksagon/domain_port.bluebook +2 -2
  585. data/lib/hecks/language/hecksagon/hecksagon.bluebook +8 -8
  586. data/lib/hecks/language/oidc.json +10 -0
  587. data/lib/hecks/language/port.bluebook +1 -1
  588. data/lib/hecks/language/sentence/sentence.bluebook +210 -0
  589. data/lib/hecks/language/world/world.bluebook +6 -6
  590. data/lib/hecks/literal.rb +8 -2
  591. data/lib/hecks/naming.rb +44 -14
  592. data/lib/hecks/persistence_legacy_fixture/postgres_stores.rb +7 -6
  593. data/lib/hecks/persistence_legacy_fixture/regenerate.rb +6 -2
  594. data/lib/hecks/ports/access_control.rb +2 -2
  595. data/lib/hecks/ports/agent/answers.rb +2 -2
  596. data/lib/hecks/ports/agent.rb +2 -2
  597. data/lib/hecks/ports/authentication.port +6 -0
  598. data/lib/hecks/ports/authentication.rb +67 -0
  599. data/lib/hecks/ports/authorization.rb +3 -3
  600. data/lib/hecks/ports/dispatch.port +5 -0
  601. data/lib/hecks/ports/dispatch.rb +28 -0
  602. data/lib/hecks/ports/key_vault.rb +9 -6
  603. data/lib/hecks/ports/loading.rb +23 -7
  604. data/lib/hecks/ports/persistence/append_only.rb +18 -1
  605. data/lib/hecks/ports/persistence/binding_policy.rb +70 -11
  606. data/lib/hecks/ports/persistence/codec_boundary.rb +3 -2
  607. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +10 -10
  608. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +4 -5
  609. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +11 -11
  610. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +1 -1
  611. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +6 -6
  612. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -3
  613. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +1 -1
  614. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +32 -19
  615. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +2 -2
  616. data/lib/hecks/ports/persistence/plugins/era.rb +0 -1
  617. data/lib/hecks/ports/persistence/remote_runtime.rb +3 -2
  618. data/lib/hecks/ports/persistence/set_statement.rb +29 -0
  619. data/lib/hecks/ports/persistence/set_transforming.rb +30 -0
  620. data/lib/hecks/ports/persistence/state_codec.rb +3 -3
  621. data/lib/hecks/ports/persistence.rb +0 -4
  622. data/lib/hecks/ports/projection.rb +4 -1
  623. data/lib/hecks/ports/query/cursor.rb +131 -0
  624. data/lib/hecks/ports/query/in_memory.rb +17 -10
  625. data/lib/hecks/ports/query.rb +4 -2
  626. data/lib/hecks/ports.rb +1 -0
  627. data/lib/hecks/projection_files/expression_tables.rb +3 -3
  628. data/lib/hecks/projection_files/sentence_table.rb +17 -0
  629. data/lib/hecks/projection_files.rb +16 -13
  630. data/lib/hecks/projections/bootstrap_table.rb +3 -3
  631. data/lib/hecks/projections/command_schema/shape.rb +95 -0
  632. data/lib/hecks/projections/command_schema.rb +74 -0
  633. data/lib/hecks/projections/context_map_diagram.rb +135 -0
  634. data/lib/hecks/projections/context_relationships.rb +125 -0
  635. data/lib/hecks/projections/deploy/box/access.rb +7 -4
  636. data/lib/hecks/projections/deploy/box/caddy.rb +2 -1
  637. data/lib/hecks/projections/deploy/box/compose.rb +4 -8
  638. data/lib/hecks/projections/deploy/box/edge.rb +2 -1
  639. data/lib/hecks/projections/deploy/box/hosting.rb +4 -2
  640. data/lib/hecks/projections/deploy/box/hosting_parts.rb +27 -12
  641. data/lib/hecks/projections/deploy/box/logging.rb +2 -3
  642. data/lib/hecks/projections/deploy/box/roll_recipe.rb +1 -3
  643. data/lib/hecks/projections/deploy/box/roll_scripts.rb +1 -16
  644. data/lib/hecks/projections/deploy/box/settings/containers.rb +29 -15
  645. data/lib/hecks/projections/deploy/box/settings/patterns.rb +5 -6
  646. data/lib/hecks/projections/deploy/box/settings/types.rb +5 -10
  647. data/lib/hecks/projections/deploy/box/settings.rb +9 -8
  648. data/lib/hecks/projections/deploy/box/stack_yaml.rb +10 -14
  649. data/lib/hecks/projections/deploy/box/templates/box.yaml.tmpl +0 -1
  650. data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +16 -3
  651. data/lib/hecks/projections/deploy/box/templates/hosting.mk.tmpl +7 -6
  652. data/lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl +2 -6
  653. data/lib/hecks/projections/deploy/box/templates/render-compose.sh.tmpl +2 -6
  654. data/lib/hecks/projections/deploy/box.rb +16 -9
  655. data/lib/hecks/projections/deploy/fargate/assembly.rb +7 -2
  656. data/lib/hecks/projections/deploy/fargate/cdn/options.rb +4 -1
  657. data/lib/hecks/projections/deploy/fargate/check.rb +3 -1
  658. data/lib/hecks/projections/deploy/fargate/containers/rendering.rb +8 -5
  659. data/lib/hecks/projections/deploy/fargate/containers/routing.rb +2 -1
  660. data/lib/hecks/projections/deploy/fargate/monitoring/reading.rb +4 -1
  661. data/lib/hecks/projections/deploy/fargate/sections.rb +7 -4
  662. data/lib/hecks/projections/deploy/fargate/settings/ids.rb +4 -1
  663. data/lib/hecks/projections/deploy/fargate/settings/tuning.rb +13 -4
  664. data/lib/hecks/projections/deploy/fargate/stack/blocks.rb +6 -1
  665. data/lib/hecks/projections/deploy/fargate/stack/readers.rb +2 -1
  666. data/lib/hecks/projections/deploy/fargate/stack.rb +11 -6
  667. data/lib/hecks/projections/deploy/fargate/yaml.rb +10 -3
  668. data/lib/hecks/projections/deploy/fargate.rb +8 -3
  669. data/lib/hecks/projections/deploy/lambda/stack/blocks.rb +4 -2
  670. data/lib/hecks/projections/deploy/lambda/stack/deploy_chain.rb +2 -1
  671. data/lib/hecks/projections/deploy/lambda/stack/reading.rb +12 -9
  672. data/lib/hecks/projections/deploy/lambda/stack/reading_web.rb +7 -2
  673. data/lib/hecks/projections/deploy/lambda/stack/web.rb +2 -1
  674. data/lib/hecks/projections/deploy/lambda.rb +8 -3
  675. data/lib/hecks/projections/deploy/preview/containers.rb +14 -1
  676. data/lib/hecks/projections/deploy/preview/preview.sh.erb +2 -2
  677. data/lib/hecks/projections/deploy/preview/routing_yaml.rb +8 -6
  678. data/lib/hecks/projections/deploy/preview/settings.rb +23 -5
  679. data/lib/hecks/projections/deploy/preview/task_yaml.rb +1 -1
  680. data/lib/hecks/projections/deploy/preview/yaml_text.rb +2 -1
  681. data/lib/hecks/projections/deploy/scripts/settings.rb +5 -2
  682. data/lib/hecks/projections/deploy/shared.rb +12 -1
  683. data/lib/hecks/projections/deploy/shared_database_instance.rb +1 -1
  684. data/lib/hecks/projections/deploy/smoke.rb +3 -1
  685. data/lib/hecks/projections/deploy/template_diff/normalizer.rb +4 -1
  686. data/lib/hecks/projections/deploy/templates/lambda/mint_era_shared.tmpl +2 -2
  687. data/lib/hecks/projections/deploy/templates/lambda/nat_comment_oauth.tmpl +1 -1
  688. data/lib/hecks/projections/deploy/vercel.rb +1 -1
  689. data/lib/hecks/projections/diagrams/dispatch.rb +5 -3
  690. data/lib/hecks/projections/diagrams/statecharts.rb +8 -3
  691. data/lib/hecks/projections/diagrams/structure.rb +9 -6
  692. data/lib/hecks/projections/diagrams.rb +9 -7
  693. data/lib/hecks/projections/glossary/entries.rb +4 -4
  694. data/lib/hecks/projections/glossary/html/page.rb +4 -1
  695. data/lib/hecks/projections/glossary/html/parser.rb +13 -4
  696. data/lib/hecks/projections/glossary/html/sections.rb +4 -1
  697. data/lib/hecks/projections/glossary/html.rb +10 -3
  698. data/lib/hecks/projections/glossary/markdown.rb +4 -3
  699. data/lib/hecks/projections/glossary/mermaid.rb +8 -5
  700. data/lib/hecks/projections/glossary/sections.rb +1 -1
  701. data/lib/hecks/projections/glossary/sensitivity.rb +11 -4
  702. data/lib/hecks/projections/glossary/sentences/actions.rb +5 -2
  703. data/lib/hecks/projections/glossary/sentences/queries.rb +2 -1
  704. data/lib/hecks/projections/glossary/sentences/value_objects.rb +15 -6
  705. data/lib/hecks/projections/glossary/sentences.rb +6 -3
  706. data/lib/hecks/projections/glossary/slugs.rb +8 -1
  707. data/lib/hecks/projections/glossary.rb +3 -3
  708. data/lib/hecks/projections/model.rb +10 -6
  709. data/lib/hecks/projections/oidc.rb +19 -7
  710. data/lib/hecks/projections/parser_table.rb +10 -10
  711. data/lib/hecks/projections/phrase_table.rb +105 -0
  712. data/lib/hecks/projections/plain_english.rb +14 -4
  713. data/lib/hecks/projections/rails_app.rb +77 -0
  714. data/lib/hecks/projections/reference.rb +2 -2
  715. data/lib/hecks/projections/rust_vocabulary/arguments.rb +1 -1
  716. data/lib/hecks/projections/rust_vocabulary/tables.rb +20 -0
  717. data/lib/hecks/projections/rust_vocabulary.rb +14 -17
  718. data/lib/hecks/projections/site/admin.rb +7 -7
  719. data/lib/hecks/projections/site/admin_cms.rb +1 -1
  720. data/lib/hecks/projections/site/cms_editor/blocks.rb +5 -2
  721. data/lib/hecks/projections/site/cms_editor/checks.rb +9 -6
  722. data/lib/hecks/projections/site/cms_editor/color.rb +4 -1
  723. data/lib/hecks/projections/site/cms_editor/composition.rb +20 -18
  724. data/lib/hecks/projections/site/cms_editor/destructive.rb +4 -1
  725. data/lib/hecks/projections/site/cms_editor/drafts.rb +6 -2
  726. data/lib/hecks/projections/site/cms_editor/key_kinds.rb +4 -1
  727. data/lib/hecks/projections/site/cms_editor/limits.rb +1 -1
  728. data/lib/hecks/projections/site/cms_editor/media.rb +1 -1
  729. data/lib/hecks/projections/site/cms_editor/moment.rb +3 -1
  730. data/lib/hecks/projections/site/cms_editor/pickers.rb +1 -1
  731. data/lib/hecks/projections/site/cms_editor/preview.rb +10 -4
  732. data/lib/hecks/projections/site/cms_editor/relations.rb +3 -2
  733. data/lib/hecks/projections/site/cms_editor/scale_checks.rb +32 -25
  734. data/lib/hecks/projections/site/cms_editor/scheduling.rb +1 -1
  735. data/lib/hecks/projections/site/cms_editor/schema.rb +31 -29
  736. data/lib/hecks/projections/site/cms_editor/setting.rb +34 -27
  737. data/lib/hecks/projections/site/cms_editor/targets.rb +9 -6
  738. data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +3 -3
  739. data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +2 -2
  740. data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +1 -1
  741. data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +4 -4
  742. data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +14 -14
  743. data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +12 -12
  744. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +12 -12
  745. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +1 -1
  746. data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +2 -2
  747. data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +11 -11
  748. data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +3 -3
  749. data/lib/hecks/projections/site/cms_editor/theme_checks.rb +8 -2
  750. data/lib/hecks/projections/site/cms_editor.rb +19 -16
  751. data/lib/hecks/projections/site/edge/checks.rb +1 -1
  752. data/lib/hecks/projections/site/edge/function_checks.rb +1 -1
  753. data/lib/hecks/projections/site/edge/function_code.rb +4 -1
  754. data/lib/hecks/projections/site/edge/pattern.rb +4 -1
  755. data/lib/hecks/projections/site/edge/route_checks.rb +2 -2
  756. data/lib/hecks/projections/site/edge/rules.rb +1 -1
  757. data/lib/hecks/projections/site/edge.rb +14 -8
  758. data/lib/hecks/projections/site/live_edge.rb +2 -2
  759. data/lib/hecks/projections/site/payload/fields/definitions.rb +1 -1
  760. data/lib/hecks/projections/site/payload/lifecycle/text.rb +1 -1
  761. data/lib/hecks/projections/site/payload/model.rb +16 -12
  762. data/lib/hecks/projections/site/payload/specs.rb +4 -2
  763. data/lib/hecks/projections/site/payload_driver.rb +9 -8
  764. data/lib/hecks/projections/site/probe.rb +12 -3
  765. data/lib/hecks/projections/site/regions.rb +9 -1
  766. data/lib/hecks/projections/site/role_probe.rb +7 -7
  767. data/lib/hecks/projections/site/root_rows.rb +4 -4
  768. data/lib/hecks/projections/site/routes_ts/listings.rb +13 -6
  769. data/lib/hecks/projections/site/routes_ts.rb +6 -3
  770. data/lib/hecks/projections/site/site_admin.rb +1 -1
  771. data/lib/hecks/projections/site/site_cdn.rb +3 -2
  772. data/lib/hecks/projections/site/site_host.rb +1 -1
  773. data/lib/hecks/projections/site/site_root.rb +4 -4
  774. data/lib/hecks/projections/site/table/checks.rb +4 -1
  775. data/lib/hecks/projections/site/table/navigation_checks.rb +2 -2
  776. data/lib/hecks/projections/site/table/row_builder.rb +7 -7
  777. data/lib/hecks/projections/site/table/row_defaults.rb +1 -1
  778. data/lib/hecks/projections/site/table.rb +32 -26
  779. data/lib/hecks/projections/statements/actions.rb +4 -1
  780. data/lib/hecks/projections/statements/relationships.rb +15 -2
  781. data/lib/hecks/projections/statements.rb +2 -2
  782. data/lib/hecks/projections/vocabulary.rb +6 -6
  783. data/lib/hecks/projections/world_skeleton.rb +68 -0
  784. data/lib/hecks/projections.rb +5 -0
  785. data/lib/hecks/projector/cli_audience.rb +12 -9
  786. data/lib/hecks/projector/cli_projector/claims.rb +8 -8
  787. data/lib/hecks/projector/cli_projector/command_specs.rb +9 -5
  788. data/lib/hecks/projector/cli_projector/listing.rb +20 -8
  789. data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
  790. data/lib/hecks/projector/cli_projector/query_specs.rb +1 -1
  791. data/lib/hecks/projector/cli_projector/usage.rb +12 -8
  792. data/lib/hecks/projector/cli_projector.rb +2 -2
  793. data/lib/hecks/projector/docs_projector/behaviour.rb +5 -4
  794. data/lib/hecks/projector/docs_projector/closing.rb +3 -3
  795. data/lib/hecks/projector/docs_projector.rb +12 -7
  796. data/lib/hecks/projector/exporter/adapters.rb +15 -2
  797. data/lib/hecks/projector/exporter/bindings.rb +39 -134
  798. data/lib/hecks/projector/exporter/plugins.rb +55 -0
  799. data/lib/hecks/projector/exporter.rb +2 -0
  800. data/lib/hecks/projector/narrate_projector/commands.rb +4 -3
  801. data/lib/hecks/projector/narrate_projector/reactions.rb +2 -2
  802. data/lib/hecks/projector/narrate_projector.rb +9 -8
  803. data/lib/hecks/projector/target.rb +8 -8
  804. data/lib/hecks/projector.rb +9 -9
  805. data/lib/hecks/quality_control/adapters/agent.rb +2 -1
  806. data/lib/hecks/quality_control/adapters/github_checks.adapter +1 -1
  807. data/lib/hecks/quality_control/adapters/github_issues.adapter +1 -1
  808. data/lib/hecks/quality_control/adapters/github_issues.rb +6 -2
  809. data/lib/hecks/quality_control/adapters/target_tools.rb +9 -3
  810. data/lib/hecks/quality_control/cli/child.rb +1 -0
  811. data/lib/hecks/quality_control/cli/qa_domain_novelty/verdict.rb +1 -1
  812. data/lib/hecks/quality_control/cli/qa_generated_domains/evaluation.rb +12 -2
  813. data/lib/hecks/quality_control/cli/qa_generated_domains/promotion.rb +4 -1
  814. data/lib/hecks/quality_control/cli/qa_generated_domains.rb +4 -1
  815. data/lib/hecks/quality_control/cli/qa_log_bug/proof.rb +16 -3
  816. data/lib/hecks/quality_control/cli/qa_mine_combinations/booting.rb +4 -2
  817. data/lib/hecks/quality_control/cli/qa_mutate.rb +161 -0
  818. data/lib/hecks/quality_control/cli/qa_sweep/all_mode/report.rb +4 -1
  819. data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +2 -1
  820. data/lib/hecks/quality_control/cli/qa_sweep.rb +4 -1
  821. data/lib/hecks/quality_control/cli/qa_tick/output.rb +28 -26
  822. data/lib/hecks/quality_control/cli/qa_tick/preflight.rb +12 -3
  823. data/lib/hecks/quality_control/cli/qa_tick.rb +24 -8
  824. data/lib/hecks/quality_control/quality_control.bluebook +93 -92
  825. data/lib/hecks/quality_control/quality_control.hecksagon +3 -3
  826. data/lib/hecks/quality_control/quality_control.ports.hecksagon +3 -2
  827. data/lib/hecks/query_ir/rules.rb +9 -9
  828. data/lib/hecks/query_ir/touchpoints.rb +5 -2
  829. data/lib/hecks/query_ir.rb +3 -3
  830. data/lib/hecks/query_specification/common/comparison.rb +1 -1
  831. data/lib/hecks/query_specification/common/dsl.rb +3 -4
  832. data/lib/hecks/query_specification/hop_path.rb +7 -4
  833. data/lib/hecks/rails_app/bluebook/rails_app.bluebook +60 -0
  834. data/lib/hecks/rails_app/bluebook/rails_app.hecksagon +4 -0
  835. data/lib/hecks/refused.rb +104 -0
  836. data/lib/hecks/release/approval.rb +9 -2
  837. data/lib/hecks/release/gem_pin.rb +7 -3
  838. data/lib/hecks/release/lane.rb +1 -1
  839. data/lib/hecks/release/runner/clean_tree.rb +4 -1
  840. data/lib/hecks/release/runner/console.rb +2 -1
  841. data/lib/hecks/router/aggregate_reader.rb +3 -4
  842. data/lib/hecks/router/namespace_installer.rb +31 -17
  843. data/lib/hecks/router.rb +2 -10
  844. data/lib/hecks/runtime/adapter_lookup.rb +30 -7
  845. data/lib/hecks/runtime/adapter_namespace.rb +21 -0
  846. data/lib/hecks/runtime/asking.rb +30 -0
  847. data/lib/hecks/runtime/caller.rb +22 -0
  848. data/lib/hecks/runtime/capability_graph.rb +2 -1
  849. data/lib/hecks/runtime/collecting.rb +86 -0
  850. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +13 -8
  851. data/lib/hecks/runtime/command_interpreter/delegation.rb +46 -17
  852. data/lib/hecks/runtime/command_interpreter/hydration.rb +17 -21
  853. data/lib/hecks/runtime/command_interpreter/list_mutations.rb +2 -1
  854. data/lib/hecks/runtime/command_interpreter/persisting.rb +13 -12
  855. data/lib/hecks/runtime/command_interpreter.rb +17 -10
  856. data/lib/hecks/runtime/command_rules/admissibility.rb +17 -10
  857. data/lib/hecks/runtime/command_rules/authorization.rb +8 -6
  858. data/lib/hecks/runtime/command_rules/guard_state.rb +10 -10
  859. data/lib/hecks/runtime/command_rules/invariants.rb +3 -1
  860. data/lib/hecks/runtime/command_rules/missing_reference.rb +25 -0
  861. data/lib/hecks/runtime/command_rules/one_of_references.rb +44 -0
  862. data/lib/hecks/runtime/command_rules/references.rb +14 -14
  863. data/lib/hecks/runtime/command_rules.rb +2 -0
  864. data/lib/hecks/runtime/declarations.rb +66 -0
  865. data/lib/hecks/runtime/dependency_planning/analyzer.rb +2 -1
  866. data/lib/hecks/runtime/dependency_planning/expression_reads.rb +3 -2
  867. data/lib/hecks/runtime/dependency_planning/mutation_analysis.rb +2 -1
  868. data/lib/hecks/runtime/dispatch_claim.rb +72 -0
  869. data/lib/hecks/runtime/dispatch_context.rb +41 -0
  870. data/lib/hecks/runtime/dispatcher/queries.rb +17 -0
  871. data/lib/hecks/runtime/dispatcher/routing.rb +34 -12
  872. data/lib/hecks/runtime/dispatcher/validating.rb +73 -0
  873. data/lib/hecks/runtime/dispatcher.rb +39 -23
  874. data/lib/hecks/runtime/entity_element/location.rb +15 -7
  875. data/lib/hecks/runtime/entity_element/mutations.rb +4 -1
  876. data/lib/hecks/runtime/entity_interpreter/enforcement.rb +1 -1
  877. data/lib/hecks/runtime/entity_interpreter/locating.rb +1 -1
  878. data/lib/hecks/runtime/entity_interpreter/saving.rb +5 -5
  879. data/lib/hecks/runtime/entity_interpreter.rb +11 -4
  880. data/lib/hecks/runtime/errors.rb +30 -17
  881. data/lib/hecks/runtime/identity.rb +9 -0
  882. data/lib/hecks/runtime/instance.rb +3 -2
  883. data/lib/hecks/runtime/interpreting.rb +63 -6
  884. data/lib/hecks/runtime/invocation_builder.rb +7 -4
  885. data/lib/hecks/runtime/invocation_facts.rb +13 -8
  886. data/lib/hecks/runtime/loader/boot_steps.rb +3 -2
  887. data/lib/hecks/runtime/loader/described.rb +4 -0
  888. data/lib/hecks/runtime/loader.rb +52 -62
  889. data/lib/hecks/runtime/outbox/redrive.rb +105 -0
  890. data/lib/hecks/runtime/outbox/relay.rb +11 -39
  891. data/lib/hecks/runtime/outbox/runnable.rb +60 -0
  892. data/lib/hecks/runtime/outbox.rb +15 -1
  893. data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -1
  894. data/lib/hecks/runtime/policy_interpreter/fan_out.rb +8 -35
  895. data/lib/hecks/runtime/policy_interpreter.rb +8 -0
  896. data/lib/hecks/runtime/port_operation_interpreter.rb +27 -13
  897. data/lib/hecks/runtime/query_interpreter/outside_answer.rb +1 -1
  898. data/lib/hecks/runtime/query_interpreter/paging.rb +42 -0
  899. data/lib/hecks/runtime/query_interpreter/plugin_answer.rb +54 -0
  900. data/lib/hecks/runtime/query_interpreter/store_specification.rb +32 -0
  901. data/lib/hecks/runtime/query_interpreter.rb +16 -9
  902. data/lib/hecks/runtime/reaction_invocation/target_resolution.rb +10 -4
  903. data/lib/hecks/runtime/reaction_invocation.rb +5 -2
  904. data/lib/hecks/runtime/read_model_interpreter/head_ordering.rb +1 -1
  905. data/lib/hecks/runtime/read_model_interpreter/reductions.rb +23 -15
  906. data/lib/hecks/runtime/read_model_interpreter/shaping.rb +18 -7
  907. data/lib/hecks/runtime/read_model_interpreter.rb +5 -2
  908. data/lib/hecks/runtime/reference_hop.rb +3 -2
  909. data/lib/hecks/runtime/refusal_wording.rb +15 -6
  910. data/lib/hecks/runtime/registry/adapter_capabilities.rb +43 -0
  911. data/lib/hecks/runtime/registry/ask_verification.rb +15 -4
  912. data/lib/hecks/runtime/registry/{chapter_verification.rb → bluebook_verification.rb} +23 -23
  913. data/lib/hecks/runtime/registry/context_maps.rb +30 -0
  914. data/lib/hecks/runtime/registry/declarations.rb +8 -6
  915. data/lib/hecks/runtime/registry/durability_warnings.rb +18 -2
  916. data/lib/hecks/runtime/registry/hecksagon_rules.rb +27 -24
  917. data/lib/hecks/runtime/registry/plugin_query_verification.rb +45 -0
  918. data/lib/hecks/runtime/registry/providers.rb +34 -99
  919. data/lib/hecks/runtime/registry/query_verification.rb +30 -21
  920. data/lib/hecks/runtime/registry/runtime_state.rb +4 -3
  921. data/lib/hecks/runtime/registry/saga_persistence.rb +7 -16
  922. data/lib/hecks/runtime/registry/verification.rb +9 -5
  923. data/lib/hecks/runtime/registry/world_defaults.rb +8 -5
  924. data/lib/hecks/runtime/registry.rb +52 -27
  925. data/lib/hecks/runtime/row_sweep.rb +104 -0
  926. data/lib/hecks/runtime/saga_interpreter/compensation.rb +5 -4
  927. data/lib/hecks/runtime/saga_interpreter/delivery.rb +13 -10
  928. data/lib/hecks/runtime/saga_interpreter/redrive.rb +141 -0
  929. data/lib/hecks/runtime/saga_interpreter/transitions.rb +35 -6
  930. data/lib/hecks/runtime/saga_interpreter.rb +36 -4
  931. data/lib/hecks/runtime/saga_pending_dispatch.rb +4 -2
  932. data/lib/hecks/runtime/scalar_field.rb +73 -0
  933. data/lib/hecks/runtime/tenant_scope.rb +2 -1
  934. data/lib/hecks/runtime/validation.rb +24 -0
  935. data/lib/hecks/runtime/value/admission.rb +11 -11
  936. data/lib/hecks/runtime/value/coercion.rb +7 -7
  937. data/lib/hecks/runtime/value/field_checks.rb +13 -13
  938. data/lib/hecks/runtime/value/invariant_violation.rb +4 -1
  939. data/lib/hecks/runtime/value/references.rb +7 -4
  940. data/lib/hecks/runtime/value/validation.rb +7 -4
  941. data/lib/hecks/runtime/wasm_plugin.rb +98 -0
  942. data/lib/hecks/runtime.rb +14 -14
  943. data/lib/hecks/rust_build/command_schemas.rb +27 -0
  944. data/lib/hecks/rust_build/conformance/surface.rb +50 -7
  945. data/lib/hecks/rust_build/conformance.rb +25 -3
  946. data/lib/hecks/rust_build/conformance_fuzz.rb +2 -2
  947. data/lib/hecks/rust_build/domain_name.rb +4 -1
  948. data/lib/hecks/rust_build/host.rb +15 -9
  949. data/lib/hecks/rust_build/native_build.rb +2 -1
  950. data/lib/hecks/rust_build/prebuilt_binary.rb +25 -5
  951. data/lib/hecks/rust_build/project_rust/codegen_run.rb +11 -10
  952. data/lib/hecks/rust_build/project_rust/domain_loader.rb +15 -2
  953. data/lib/hecks/rust_build/project_rust/root_mod/notes.rb +1 -1
  954. data/lib/hecks/rust_build/project_rust/target_ir.rb +12 -3
  955. data/lib/hecks/rust_build/project_rust.rb +39 -19
  956. data/lib/hecks/rust_build/target_dir.rb +40 -0
  957. data/lib/hecks/rust_build/wasm.rb +46 -17
  958. data/lib/hecks/rust_build/wasm_browser.rb +8 -2
  959. data/lib/hecks/rust_build.rb +29 -2
  960. data/lib/hecks/sentence_form.rb +212 -0
  961. data/lib/hecks/served_options.rb +2 -1
  962. data/lib/hecks/shape_ledger.rb +149 -0
  963. data/lib/hecks/site/bluebook/site.bluebook +39 -62
  964. data/lib/hecks/site/bluebook/site.ports.hecksagon +1 -1
  965. data/lib/hecks/sme/bluebook/sme.bluebook +7 -1
  966. data/lib/hecks/storehouse/audit.rb +4 -1
  967. data/lib/hecks/storehouse/bluebook_words.rb +35 -0
  968. data/lib/hecks/storehouse/dispatching.rb +6 -3
  969. data/lib/hecks/storehouse/introspection.rb +4 -4
  970. data/lib/hecks/storehouse/querying.rb +2 -2
  971. data/lib/hecks/storehouse.rb +14 -4
  972. data/lib/hecks/switches.rb +21 -0
  973. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +5 -9
  974. data/lib/hecks/text.rb +57 -0
  975. data/lib/hecks/tickets/bluebook/tickets.bluebook +30 -51
  976. data/lib/hecks/tickets/bluebook/tickets.hecksagon +1 -1
  977. data/lib/hecks/tickets/bluebook/tickets.ports.hecksagon +1 -1
  978. data/lib/hecks/tools/argument_gate_matrix/dispatching.rb +1 -1
  979. data/lib/hecks/tools/argument_gate_matrix/rows.rb +7 -7
  980. data/lib/hecks/tools/argument_gate_matrix/violations.rb +1 -1
  981. data/lib/hecks/tools/argument_gate_matrix.rb +3 -3
  982. data/lib/hecks/tools/ci_gate_decision.rb +5 -2
  983. data/lib/hecks/tools/ci_gates.rb +1 -1
  984. data/lib/hecks/tools/comment_style/caps_words.rb +11 -8
  985. data/lib/hecks/tools/comment_style/dictionary.rb +7 -2
  986. data/lib/hecks/tools/comment_style/fixing.rb +10 -5
  987. data/lib/hecks/tools/comment_style/long_blocks.rb +1 -1
  988. data/lib/hecks/tools/comment_style/masking.rb +5 -5
  989. data/lib/hecks/tools/comment_style/run.rb +4 -1
  990. data/lib/hecks/tools/comment_style/source_file.rb +10 -3
  991. data/lib/hecks/tools/comment_style/structure_checks.rb +13 -4
  992. data/lib/hecks/tools/comment_style/text_checks.rb +1 -1
  993. data/lib/hecks/tools/comment_style.rb +25 -2
  994. data/lib/hecks/tools/deploy_recipe/generation.rb +1 -1
  995. data/lib/hecks/tools/deploy_recipe/targets.rb +1 -1
  996. data/lib/hecks/tools/deploy_recipe/worlds.rb +15 -9
  997. data/lib/hecks/tools/deploy_recipe.rb +17 -10
  998. data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +1 -1
  999. data/lib/hecks/tools/deploy_recipe_lint/makefile.rb +7 -1
  1000. data/lib/hecks/tools/drop_implicit_append_fields.rb +4 -1
  1001. data/lib/hecks/tools/era_projection_backfill.rb +11 -6
  1002. data/lib/hecks/tools/era_reattest.rb +11 -6
  1003. data/lib/hecks/tools/fuzz_sweep/findings.rb +18 -2
  1004. data/lib/hecks/tools/fuzz_sweep/options.rb +5 -1
  1005. data/lib/hecks/tools/fuzz_sweep.rb +17 -5
  1006. data/lib/hecks/tools/gate.rb +27 -5
  1007. data/lib/hecks/tools/hoist_local_givens/rewriting.rb +3 -2
  1008. data/lib/hecks/tools/hoist_local_givens.rb +2 -4
  1009. data/lib/hecks/tools/lanes/prebuilt_workflow.rb +1 -1
  1010. data/lib/hecks/tools/lanes/rows.rb +9 -3
  1011. data/lib/hecks/tools/lanes/ruleset.rb +1 -1
  1012. data/lib/hecks/tools/lanes/watch_workflow.rb +1 -1
  1013. data/lib/hecks/tools/lanes/workflow.rb +1 -1
  1014. data/lib/hecks/tools/lanes.rb +1 -1
  1015. data/lib/hecks/tools/oidc_manifests.rb +16 -19
  1016. data/lib/hecks/tools/regeneration_run.rb +22 -17
  1017. data/lib/hecks/tools/release_only.rb +5 -2
  1018. data/lib/hecks/tools/rust_comment_style/caps_words.rb +5 -5
  1019. data/lib/hecks/tools/rust_comment_style/fixing.rb +1 -1
  1020. data/lib/hecks/tools/rust_comment_style/masking.rb +7 -4
  1021. data/lib/hecks/tools/rust_comment_style/source_file.rb +7 -2
  1022. data/lib/hecks/tools/rust_comment_style/text_checks.rb +1 -1
  1023. data/lib/hecks/tools/rust_comment_style/tokenizer.rb +4 -1
  1024. data/lib/hecks/tools/site_routes/editor_files.rb +16 -13
  1025. data/lib/hecks/tools/site_routes/root_files.rb +14 -13
  1026. data/lib/hecks/tools/site_routes/templates.rb +5 -4
  1027. data/lib/hecks/tools/site_routes.rb +18 -15
  1028. data/lib/hecks/tools/tail_merge.rb +3 -3
  1029. data/lib/hecks/tools/tenant_provisioning.rb +6 -6
  1030. data/lib/hecks/tools/tools_doc.rb +16 -16
  1031. data/lib/hecks/tools/translation_audit/domain_loading.rb +3 -3
  1032. data/lib/hecks/tools/translation_audit.rb +15 -4
  1033. data/lib/hecks/tools/translation_scaffold/reporting.rb +4 -1
  1034. data/lib/hecks/tools/translation_scaffold.rb +4 -14
  1035. data/lib/hecks/vendoring/git_source.rb +7 -4
  1036. data/lib/hecks/version.rb +1 -1
  1037. data/lib/hecks/vocabulary.rb +110 -3
  1038. data/lib/hecks.rb +37 -18
  1039. data/lib/rubocop/cop/hecks/inline_regexp.rb +29 -0
  1040. data/lib/rubocop/cop/hecks/no_transport_in_runtime.rb +78 -0
  1041. data/lib/rubocop/cop/hecks/rails_model_rules.rb +46 -0
  1042. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +1 -1
  1043. data/lib/rubocop/cop/hecks/train_wreck.rb +69 -0
  1044. data/lib/rubocop/cop/hecks/unnamed_pipeline.rb +51 -0
  1045. data/prebuilt/31cf81e12f16236ad26249be590cfea3799c9db67432e6a4ef54b801c355b6ae.marshal +0 -0
  1046. data/prebuilt/68ecb2bd3abb1a221007db4f2b0375834c13aaccac8f2b99778e8517f11aa113.marshal +0 -0
  1047. data/prebuilt/verdicts-0328c2403383837d780ad5ed81d1da351c56436b894cf9015b5d56b8f1f1bf10.json +1 -0
  1048. data/qa/settings.yml +9 -0
  1049. data/rust/Cargo.lock +8 -1
  1050. data/rust/Cargo.toml +6 -7
  1051. data/rust/build/src/binding_pass.rs +13 -17
  1052. data/rust/build/src/cargo_sync.rs +1 -1
  1053. data/rust/build/src/lineage_pass.rs +4 -71
  1054. data/rust/build/src/pipeline.rs +99 -70
  1055. data/rust/build/src/resolve.rs +85 -29
  1056. data/rust/build/src/seams_pass.rs +117 -99
  1057. data/rust/build/src/world_pass.rs +21 -12
  1058. data/rust/codegen/src/domain_generator.rs +24 -2
  1059. data/rust/codegen/src/expr_emitter.rs +6 -0
  1060. data/rust/codegen/src/json_codec.rs +48 -10
  1061. data/rust/codegen/src/main.rs +35 -34
  1062. data/rust/codegen/src/manifest.rs +1 -1
  1063. data/rust/codegen/src/naming.rs +5 -5
  1064. data/rust/codegen/src/one_of_references.rs +124 -0
  1065. data/rust/codegen/src/presenters.rs +3 -3
  1066. data/rust/codegen/src/queries.rs +56 -7
  1067. data/rust/codegen/src/reactions.rs +19 -14
  1068. data/rust/codegen/src/read_models.rs +19 -14
  1069. data/rust/codegen/src/registry.rs +89 -15
  1070. data/rust/codegen/src/sidecars.rs +1 -1
  1071. data/rust/expr/Cargo.lock +54 -0
  1072. data/rust/expr/Cargo.toml +52 -0
  1073. data/rust/{src/kernel → expr/src}/attribute_shapes/composite.rs +45 -14
  1074. data/rust/{src/kernel → expr/src}/attribute_shapes/list.rs +1 -1
  1075. data/rust/{src/kernel → expr/src}/attribute_shapes/mod.rs +2 -2
  1076. data/rust/{src/kernel → expr/src}/attribute_shapes/optional.rs +1 -1
  1077. data/rust/{src/kernel → expr/src}/attribute_shapes/scalar.rs +3 -3
  1078. data/rust/{src/kernel → expr/src}/expr.rs +30 -8
  1079. data/rust/{src/kernel → expr/src}/expression_operators/arithmetic.rs +41 -18
  1080. data/rust/{src/kernel → expr/src}/expression_operators/comparison.rs +54 -8
  1081. data/rust/{src/kernel → expr/src}/expression_operators/enumeration.rs +73 -6
  1082. data/rust/{src/kernel → expr/src}/expression_operators/logical.rs +2 -2
  1083. data/rust/expr/src/expression_operators/membership.rs +33 -0
  1084. data/rust/{src/kernel → expr/src}/expression_operators/mod.rs +2 -2
  1085. data/rust/expr/src/expression_operators/pattern_match.rs +179 -0
  1086. data/rust/{src/kernel → expr/src}/expression_operators/positional.rs +8 -6
  1087. data/rust/{src/kernel → expr/src}/expression_operators/presence.rs +7 -6
  1088. data/rust/{src/kernel → expr/src}/expression_operators/sign_test.rs +14 -4
  1089. data/rust/{src/kernel → expr/src}/expression_operators/sized.rs +16 -9
  1090. data/rust/{src/kernel → expr/src}/expression_operators/text.rs +30 -17
  1091. data/rust/{src/kernel → expr/src}/expression_operators/to_string.rs +8 -6
  1092. data/rust/expr/src/lib.rs +17 -0
  1093. data/rust/expr/src/refusal.rs +62 -0
  1094. data/rust/expr/src/rendering.rs +135 -0
  1095. data/rust/{src/kernel → expr/src}/ruby_float.rs +7 -6
  1096. data/rust/host/Cargo.lock +137 -123
  1097. data/rust/host/Cargo.toml +7 -3
  1098. data/rust/host/HECKS_RELEASE +1 -1
  1099. data/rust/host/core/Cargo.toml +64 -0
  1100. data/rust/host/core/clippy.toml +4 -0
  1101. data/rust/host/core/src/arg_limits.rs +48 -0
  1102. data/rust/host/core/src/clock.rs +146 -0
  1103. data/rust/host/core/src/dispatch.rs +920 -0
  1104. data/rust/host/core/src/expr_json.rs +1137 -0
  1105. data/rust/host/{src → core/src}/host.rs +13 -14
  1106. data/rust/host/{src → core/src}/ir.rs +227 -18
  1107. data/rust/host/core/src/journal.rs +126 -0
  1108. data/rust/host/{src → core/src}/lambda_client.rs +27 -96
  1109. data/rust/host/core/src/lib.rs +37 -0
  1110. data/rust/host/{src → core/src}/needs.rs +66 -20
  1111. data/rust/host/core/src/plugins.rs +269 -0
  1112. data/rust/host/core/src/presentation.rs +471 -0
  1113. data/rust/host/{src → core/src}/presentation_write.rs +75 -43
  1114. data/rust/host/{src → core/src}/query_step.rs +54 -16
  1115. data/rust/host/{src → core/src}/reference_validate.rs +6 -6
  1116. data/rust/host/{src → core/src}/storage_shape.rs +2 -2
  1117. data/rust/host/core/src/store/memory.rs +635 -0
  1118. data/rust/host/core/src/store.rs +418 -0
  1119. data/rust/host/{src → core/src}/ui_schema.rs +8 -8
  1120. data/rust/host/core/src/validation.rs +129 -0
  1121. data/rust/host/core/src/wasm_runner.rs +410 -0
  1122. data/rust/host/fuzz/.gitignore +4 -0
  1123. data/rust/host/fuzz/Cargo.lock +4479 -0
  1124. data/rust/host/fuzz/Cargo.toml +42 -0
  1125. data/rust/host/fuzz/README.md +55 -0
  1126. data/rust/host/fuzz/fuzz_targets/postgres_equivalence.rs +24 -0
  1127. data/rust/host/fuzz/src/backend.rs +273 -0
  1128. data/rust/host/fuzz/src/equivalence.rs +195 -0
  1129. data/rust/host/fuzz/src/footprint.rs +182 -0
  1130. data/rust/host/fuzz/src/lib.rs +17 -0
  1131. data/rust/host/fuzz/src/ops.rs +345 -0
  1132. data/rust/host/fuzz/tests/smoke.rs +50 -0
  1133. data/rust/host/src/api.rs +316 -105
  1134. data/rust/host/src/auth/google_signin.rs +183 -0
  1135. data/rust/host/src/auth.rs +527 -370
  1136. data/rust/host/src/aws_lambda_invoker.rs +61 -0
  1137. data/rust/host/src/bin/expr_harness.rs +9 -4
  1138. data/rust/host/src/bin/ledger.rs +84 -0
  1139. data/rust/host/src/bin/lineage_harness.rs +17 -21
  1140. data/rust/host/src/bin/mint_harness.rs +11 -36
  1141. data/rust/host/src/bin/validate_harness.rs +1 -6
  1142. data/rust/host/src/boot.rs +465 -174
  1143. data/rust/host/src/boundary_fuzz/ir.rs +10 -5
  1144. data/rust/host/src/boundary_fuzz/server.rs +6 -5
  1145. data/rust/host/src/checkout.rs +4 -1
  1146. data/rust/host/src/commerce_ir.rs +126 -105
  1147. data/rust/host/src/{dispatch.rs → dispatch_tests.rs} +367 -847
  1148. data/rust/host/src/extension.rs +3 -4
  1149. data/rust/host/src/hecksagon_boundary_tests.rs +149 -0
  1150. data/rust/host/src/ledger.rs +683 -0
  1151. data/rust/host/src/lib.rs +21 -28
  1152. data/rust/host/src/payments/tests.rs +136 -38
  1153. data/rust/host/src/payments.rs +36 -32
  1154. data/rust/host/src/platform_signin/extension.rs +1 -1
  1155. data/rust/host/src/platform_signin/mapping_tests.rs +12 -9
  1156. data/rust/host/src/platform_signin/session.rs +7 -14
  1157. data/rust/host/src/platform_signin/verify.rs +0 -2
  1158. data/rust/host/src/presentation_tests.rs +382 -0
  1159. data/rust/host/src/rate_limit.rs +18 -2
  1160. data/rust/host/src/server.rs +222 -43
  1161. data/rust/host/src/{journal.rs → store/journal_sql.rs} +21 -127
  1162. data/rust/host/src/{mint.rs → store/mint.rs} +12 -12
  1163. data/rust/host/src/store/postgres.rs +493 -0
  1164. data/rust/host/src/store.rs +97 -0
  1165. data/rust/host/src/store_conformance.rs +1265 -0
  1166. data/rust/host/src/web/commerce.rs +10 -18
  1167. data/rust/host/src/web/newsletter/cooldown.rs +107 -22
  1168. data/rust/host/src/web/newsletter/naming.rs +104 -0
  1169. data/rust/host/src/web/newsletter/tests.rs +53 -0
  1170. data/rust/host/src/web/newsletter.rs +73 -50
  1171. data/rust/host/src/web/newsletter_send.rs +12 -13
  1172. data/rust/host/src/web/registration_receipt.rs +2 -2
  1173. data/rust/host/src/web/registrations.rs +754 -375
  1174. data/rust/host/src/web/resend_webhook.rs +3 -3
  1175. data/rust/host/src/web/signin_tests.rs +131 -0
  1176. data/rust/host/src/web.rs +1479 -149
  1177. data/rust/lsp/README.md +27 -27
  1178. data/rust/lsp/src/diagnostics.rs +75 -15
  1179. data/rust/lsp/src/main.rs +101 -5
  1180. data/rust/parser/fuzz/Cargo.toml +2 -2
  1181. data/rust/parser/fuzz/README.md +3 -3
  1182. data/rust/parser/fuzz/fuzz_targets/{chapter.rs → bluebook.rs} +4 -4
  1183. data/rust/parser/fuzz/fuzz_targets/resolve.rs +2 -2
  1184. data/rust/parser/src/build/named_pattern.rs +22 -0
  1185. data/rust/parser/src/build/references.rs +52 -0
  1186. data/rust/parser/src/diag.rs +22 -1
  1187. data/rust/parser/src/emit.rs +68 -4
  1188. data/rust/parser/src/expr/ast_json.rs +10 -0
  1189. data/rust/parser/src/expr/resolver.rs +49 -9
  1190. data/rust/parser/src/ir.rs +34 -7
  1191. data/rust/parser/src/keywords.rs +454 -457
  1192. data/rust/parser/src/lex.rs +115 -87
  1193. data/rust/parser/src/main.rs +180 -32
  1194. data/rust/parser/src/parse/adapter.rs +28 -0
  1195. data/rust/parser/src/parse/aggregate.rs +55 -35
  1196. data/rust/parser/src/parse/command.rs +100 -15
  1197. data/rust/parser/src/parse/{chapter.rs → declaration.rs} +85 -54
  1198. data/rust/parser/src/parse/domain_port.rs +60 -9
  1199. data/rust/parser/src/parse/entity.rs +35 -32
  1200. data/rust/parser/src/parse/file.rs +1 -1
  1201. data/rust/parser/src/parse/hecksagon.rs +15 -5
  1202. data/rust/parser/src/parse/mod.rs +84 -2
  1203. data/rust/parser/src/parse/process_manager.rs +1 -1
  1204. data/rust/parser/src/parse/query.rs +13 -3
  1205. data/rust/parser/src/parse/read_model.rs +2 -1
  1206. data/rust/parser/src/phrases.rs +1258 -0
  1207. data/rust/parser/src/render_ruby.rs +540 -0
  1208. data/rust/parser/src/sentence/changes.rs +437 -0
  1209. data/rust/parser/src/sentence/command_step.rs +206 -0
  1210. data/rust/parser/src/sentence/complete.rs +276 -0
  1211. data/rust/parser/src/sentence/entity.rs +41 -0
  1212. data/rust/parser/src/sentence/examples.rs +1012 -0
  1213. data/rust/parser/src/sentence/format.rs +882 -0
  1214. data/rust/parser/src/sentence/lower.rs +1366 -0
  1215. data/rust/parser/src/sentence/mod.rs +444 -0
  1216. data/rust/parser/src/sentence/names.rs +69 -0
  1217. data/rust/parser/src/sentence/options.rs +154 -0
  1218. data/rust/parser/src/sentence/pattern.rs +433 -0
  1219. data/rust/parser/src/sentence/process.rs +196 -0
  1220. data/rust/parser/src/sentence/queries.rs +431 -0
  1221. data/rust/parser/src/sentence/report.rs +283 -0
  1222. data/rust/parser/src/sentence/token.rs +106 -0
  1223. data/rust/parser/src/sentence/values.rs +498 -0
  1224. data/rust/src/exemplar/queries.rs +1 -0
  1225. data/rust/src/exemplar/reactions.rs +1 -1
  1226. data/rust/src/exemplar/registry.rs +30 -0
  1227. data/rust/src/kernel/cli.rs +219 -19
  1228. data/rust/src/kernel/collecting.rs +174 -0
  1229. data/rust/src/kernel/dispatch.rs +55 -27
  1230. data/rust/src/kernel/mod.rs +7 -67
  1231. data/rust/src/kernel/named_query.rs +225 -11
  1232. data/rust/src/kernel/orchestrate.rs +213 -25
  1233. data/rust/src/kernel/orchestrate_guard_tests.rs +3 -0
  1234. data/rust/src/kernel/query_cursor.rs +251 -0
  1235. data/rust/src/kernel/query_ordering.rs +6 -6
  1236. data/rust/src/kernel/read_model.rs +162 -17
  1237. data/rust/src/kernel/repository.rs +107 -17
  1238. data/rust/src/kernel/vocab/mod.rs +4 -0
  1239. data/rust/src/kernel/vocab/reducer.rs +109 -0
  1240. data/rust/src/kernel/vocab/refusal_class.rs +124 -0
  1241. data/rust/src/kernel/vocab/refusal_template.rs +2 -2
  1242. data/rust/src/lib.rs +1 -1
  1243. data/rust/web/Cargo.lock +47 -0
  1244. metadata +328 -93
  1245. data/lib/hecks/chapters.rb +0 -147
  1246. data/lib/hecks/gate/stages.yml +0 -234
  1247. data/lib/hecks/hecks/governance.world +0 -7
  1248. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +0 -137
  1249. data/lib/hecks/ports/persistence/postgres_dump/connection.rb +0 -59
  1250. data/lib/hecks/ports/persistence/postgres_dump.rb +0 -100
  1251. data/lib/hecks/projections/deploy/box/templates/tunnel-egress.yaml.tmpl +0 -11
  1252. data/lib/hecks/projector/exporter/capability_values.rb +0 -55
  1253. data/lib/hecks/runtime/remote_dispatcher.rb +0 -176
  1254. data/prebuilt/ad68f9e06c8068c2a4176b57abf3083d591d5d8980c2b3e897b97331f965b643.marshal +0 -0
  1255. data/prebuilt/ee22ec8642c4006717ba2b80f46e5efb81de459eef9dfec043e838c0ad4fd720.marshal +0 -0
  1256. data/prebuilt/verdicts-b69f5dcbba79c20daa0493476fa7c317004a30aa2bc3d3dc6a722ea70ae2b43b.json +0 -1
  1257. data/rust/host/src/clock.rs +0 -8
  1258. data/rust/host/src/expr_json.rs +0 -1983
  1259. data/rust/host/src/presentation.rs +0 -842
  1260. data/rust/host/src/wasm_runner.rs +0 -206
  1261. data/rust/src/kernel/expression_operators/membership.rs +0 -20
  1262. data/rust/src/kernel/expression_operators/pattern_match.rs +0 -108
  1263. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/events.rb +0 -0
  1264. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/head_writes.rb +0 -0
  1265. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/journal.rb +0 -0
  1266. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/base_sql.rb +0 -0
  1267. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/field_cache.rb +0 -0
  1268. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/head_compiler.rb +0 -0
  1269. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/merge_writes.rb +0 -0
  1270. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/provisioning.rb +0 -0
  1271. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/resumable_backfill.rb +0 -0
  1272. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/transform_installer.rb +0 -0
  1273. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/transform_sql.rb +0 -0
  1274. /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/sagas.rb +0 -0
  1275. /data/rust/host/{src → core/src}/field_hints.rs +0 -0
  1276. /data/rust/host/{src → core/src}/log.rs +0 -0
  1277. /data/rust/host/{src → core/src}/reference_transform.rs +0 -0
@@ -0,0 +1,1137 @@
1
+ //! The host's JSON door to the expression evaluator it shares with the kernel (`hecks_expr`):
2
+ //! `parse` reads an invariant's `ast` node into the shared `Expr`, and `interpret` evaluates it
3
+ //! over an instance read as JSON, with the Ruby runtime's semantics (`Evaluator`/`Resolver`): the
4
+ //! same answers, null handling and error wording, so a value object's invariants mint the same way.
5
+ //!
6
+ //! There is one evaluator. What is the host's own is the reading of data: the AST arrives as JSON
7
+ //! with owned names, and the instance is a JSON object rather than a generated struct.
8
+
9
+ use hecks_expr::expr::{
10
+ self as shared, BlockMode, Comparison, EvalContext, Field, Fielded, NoFields, StripSide,
11
+ };
12
+ pub use hecks_expr::expr::{Expr, Value};
13
+ use serde_json::Value as Json;
14
+ use std::collections::{BTreeMap, HashMap, HashSet};
15
+ use std::sync::{Mutex, OnceLock, PoisonError};
16
+
17
+ /// The `'static` spelling of a name, shared by every parse of it. The shared `Expr` holds names as
18
+ /// `&'static str` (generated code writes them as literals); an IR's names are a finite set, so each
19
+ /// distinct one is leaked once and a repeated parse costs nothing more.
20
+ fn intern(text: &str) -> &'static str {
21
+ static NAMES: OnceLock<Mutex<HashSet<&'static str>>> = OnceLock::new();
22
+ let mut names = NAMES
23
+ .get_or_init(Mutex::default)
24
+ .lock()
25
+ .unwrap_or_else(PoisonError::into_inner);
26
+ if let Some(known) = names.get(text) {
27
+ return known;
28
+ }
29
+ let leaked: &'static str = Box::leak(text.to_string().into_boxed_str());
30
+ names.insert(leaked);
31
+ leaked
32
+ }
33
+
34
+ /// The `'static` spelling of a path of segments, interned the same way.
35
+ fn intern_path(segments: &[String]) -> &'static [&'static str] {
36
+ static PATHS: OnceLock<Mutex<HashMap<Vec<String>, &'static [&'static str]>>> = OnceLock::new();
37
+ let mut paths = PATHS
38
+ .get_or_init(Mutex::default)
39
+ .lock()
40
+ .unwrap_or_else(PoisonError::into_inner);
41
+ if let Some(known) = paths.get(segments) {
42
+ return known;
43
+ }
44
+ let interned: Vec<&'static str> = segments.iter().map(|s| intern(s)).collect();
45
+ let leaked: &'static [&'static str] = Box::leak(interned.into_boxed_slice());
46
+ paths.insert(segments.to_vec(), leaked);
47
+ leaked
48
+ }
49
+
50
+ /// Parses an invariant's `ast` node into an `Expr`.
51
+ ///
52
+ /// One JSON object per node, tagged by `"op"`; field names match the Hash keys `ast_json.rb`
53
+ /// emits. An unrecognized `"op"`, or a malformed shape under a known one, is an `Err`.
54
+ pub fn parse(json: &Json) -> Result<Expr, String> {
55
+ let op = json
56
+ .get("op")
57
+ .and_then(Json::as_str)
58
+ .ok_or_else(|| format!("{json} has no string \"op\" field"))?;
59
+
60
+ let field = |name: &str| {
61
+ json.get(name)
62
+ .ok_or_else(|| format!("{op} node has no {name:?} field: {json}"))
63
+ };
64
+ let expr = |name: &str| parse(field(name)?);
65
+ let boxed = |name: &str| -> Result<Box<Expr>, String> { Ok(Box::new(expr(name)?)) };
66
+ let str_field = |name: &str| -> Result<String, String> {
67
+ field(name)?
68
+ .as_str()
69
+ .map(str::to_string)
70
+ .ok_or_else(|| format!("{op}'s {name:?} isn't a string: {json}"))
71
+ };
72
+ let bool_field = |name: &str| -> Result<bool, String> {
73
+ field(name)?
74
+ .as_bool()
75
+ .ok_or_else(|| format!("{op}'s {name:?} isn't a boolean: {json}"))
76
+ };
77
+ // `lookup.path` and `find.path` are segment arrays, never dotted strings.
78
+ let path_field = |name: &str| -> Result<Vec<String>, String> {
79
+ field(name)?
80
+ .as_array()
81
+ .ok_or_else(|| format!("{op}'s {name:?} isn't an array: {json}"))?
82
+ .iter()
83
+ .map(|v| {
84
+ v.as_str()
85
+ .map(str::to_string)
86
+ .ok_or_else(|| format!("{op}'s {name:?} has a non-string element: {json}"))
87
+ })
88
+ .collect::<Result<Vec<_>, _>>()
89
+ };
90
+ let comparison = || -> Result<Comparison, String> {
91
+ let cmp = field("cmp")?;
92
+ let flag = |name: &str| {
93
+ cmp.get(name)
94
+ .and_then(Json::as_bool)
95
+ .ok_or_else(|| format!("{op}'s cmp has no {name}: {json}"))
96
+ };
97
+ Ok(Comparison {
98
+ less_than: flag("less_than")?,
99
+ equal: flag("equal")?,
100
+ negated: flag("negated")?,
101
+ })
102
+ };
103
+
104
+ Ok(match op {
105
+ "or" => Expr::Or(boxed("left")?, boxed("right")?),
106
+ "and" => Expr::And(boxed("left")?, boxed("right")?),
107
+ "not" => Expr::Not(boxed("expr")?),
108
+ "compare" => Expr::Compare {
109
+ op: comparison()?,
110
+ left: boxed("left")?,
111
+ right: boxed("right")?,
112
+ },
113
+ "include" => Expr::Include {
114
+ haystack: boxed("haystack")?,
115
+ needle: boxed("needle")?,
116
+ },
117
+ "int" => Expr::Int(
118
+ field("value")?
119
+ .as_i64()
120
+ .ok_or_else(|| format!("int's value isn't an integer: {json}"))?,
121
+ ),
122
+ "float" => Expr::Float(
123
+ field("value")?
124
+ .as_f64()
125
+ .ok_or_else(|| format!("float's value isn't a number: {json}"))?,
126
+ ),
127
+ "str" => Expr::Str(str_field("value")?),
128
+ "bool" => Expr::Bool(bool_field("value")?),
129
+ "nil" => Expr::Nil,
130
+ "add" => Expr::Add(boxed("left")?, boxed("right")?),
131
+ "sub" => Expr::Sub(boxed("left")?, boxed("right")?),
132
+ "mul" => Expr::Mul(boxed("left")?, boxed("right")?),
133
+ "div" => Expr::Div(boxed("left")?, boxed("right")?),
134
+ "sign_test" => Expr::SignTest {
135
+ op: comparison()?,
136
+ receiver: boxed("receiver")?,
137
+ },
138
+ "empty" => Expr::Empty(boxed("receiver")?),
139
+ "to_s" => Expr::ToS(boxed("receiver")?),
140
+ "modulo" => Expr::Modulo {
141
+ receiver: boxed("receiver")?,
142
+ divisor: boxed("divisor")?,
143
+ },
144
+ "size" => Expr::Size(boxed("receiver")?),
145
+ "lookup" => Expr::Lookup(intern(&path_field("path")?.join("."))),
146
+ "block_predicate" => Expr::BlockPredicate {
147
+ mode: match str_field("mode")?.as_str() {
148
+ "all" => BlockMode::All,
149
+ "any" => BlockMode::Any,
150
+ "none" => BlockMode::None,
151
+ other => {
152
+ return Err(format!(
153
+ "block_predicate's mode {other:?} is none of all/any/none: {json}"
154
+ ))
155
+ }
156
+ },
157
+ receiver: boxed("receiver")?,
158
+ param: intern(&str_field("param")?),
159
+ predicate: boxed("predicate")?,
160
+ },
161
+ "find" => Expr::Find {
162
+ receiver: boxed("receiver")?,
163
+ param: intern(&str_field("param")?),
164
+ predicate: boxed("predicate")?,
165
+ path: intern_path(&path_field("path")?),
166
+ },
167
+ "sum" => Expr::Sum {
168
+ receiver: boxed("receiver")?,
169
+ param: intern(&str_field("param")?),
170
+ predicate: boxed("predicate")?,
171
+ },
172
+ "array" => Expr::Array(
173
+ field("elements")?
174
+ .as_array()
175
+ .ok_or_else(|| format!("array's elements isn't an array: {json}"))?
176
+ .iter()
177
+ .map(parse)
178
+ .collect::<Result<Vec<_>, _>>()?,
179
+ ),
180
+ "matches_regex" => Expr::MatchesRegex {
181
+ receiver: boxed("receiver")?,
182
+ pattern: str_field("pattern")?,
183
+ flags: str_field("flags")?,
184
+ },
185
+ "presence" => Expr::Presence {
186
+ receiver: boxed("receiver")?,
187
+ negated: bool_field("negated")?,
188
+ },
189
+ "assignment" => Expr::Assignment {
190
+ receiver: boxed("receiver")?,
191
+ negated: bool_field("negated")?,
192
+ },
193
+ "split" => Expr::Split {
194
+ receiver: boxed("receiver")?,
195
+ separator: str_field("separator")?,
196
+ },
197
+ "strip" => Expr::Strip {
198
+ receiver: boxed("receiver")?,
199
+ side: match str_field("side")?.as_str() {
200
+ "both" => StripSide::Both,
201
+ "left" => StripSide::Left,
202
+ "right" => StripSide::Right,
203
+ other => {
204
+ return Err(format!(
205
+ "strip's side {other:?} is none of both/left/right: {json}"
206
+ ))
207
+ }
208
+ },
209
+ },
210
+ "starts_with" => Expr::StartsWith {
211
+ receiver: boxed("receiver")?,
212
+ substring: str_field("substring")?,
213
+ },
214
+ "ends_with" => Expr::EndsWith {
215
+ receiver: boxed("receiver")?,
216
+ substring: str_field("substring")?,
217
+ },
218
+ "first" => Expr::First(boxed("receiver")?),
219
+ "last" => Expr::Last(boxed("receiver")?),
220
+ other => return Err(format!("unrecognized expression op {other:?}: {json}")),
221
+ })
222
+ }
223
+
224
+ /// The value of a JSON scalar, array or object as the evaluator reads it.
225
+ fn value_of(json: &Json) -> Result<Value, String> {
226
+ match json {
227
+ Json::Null => Ok(Value::Nil),
228
+ Json::Bool(b) => Ok(Value::Bool(*b)),
229
+ Json::Number(n) => n
230
+ .as_i64()
231
+ .map(Value::Int)
232
+ .or_else(|| n.as_f64().map(Value::Float))
233
+ .ok_or_else(|| format!("{n} is a number this interpreter cannot represent")),
234
+ Json::String(s) => Ok(Value::Str(s.clone())),
235
+ Json::Array(items) => Ok(Value::Array(
236
+ items.iter().map(value_of).collect::<Result<Vec<_>, _>>()?,
237
+ )),
238
+ Json::Object(fields) => Ok(Value::Object(
239
+ fields
240
+ .iter()
241
+ .map(|(key, field)| Ok((key.clone(), value_of(field)?)))
242
+ .collect::<Result<BTreeMap<_, _>, String>>()?,
243
+ )),
244
+ }
245
+ }
246
+
247
+ /// One field of an instance, read the way the evaluator walks it.
248
+ enum Slot {
249
+ Scalar(Value),
250
+ Object(Node),
251
+ List(Vec<Self>),
252
+ }
253
+
254
+ impl Slot {
255
+ fn from_json(json: &Json) -> Result<Self, String> {
256
+ match json {
257
+ Json::Object(_) => Ok(Self::Object(Node::from_json(json, false)?)),
258
+ Json::Array(items) => Ok(Self::List(
259
+ items
260
+ .iter()
261
+ .map(Self::from_json)
262
+ .collect::<Result<Vec<_>, _>>()?,
263
+ )),
264
+ scalar => Ok(Self::Scalar(value_of(scalar)?)),
265
+ }
266
+ }
267
+
268
+ /// This slot as a plain value, an object slot becoming a `Value::Object`.
269
+ fn value(&self) -> Value {
270
+ match self {
271
+ Self::Scalar(value) => value.clone(),
272
+ Self::Object(node) => node.object_value(),
273
+ Self::List(items) => Value::Array(items.iter().map(Self::value).collect()),
274
+ }
275
+ }
276
+
277
+ fn field(&self) -> Field<'_> {
278
+ match self {
279
+ Self::Object(node) => Field::Nested(node),
280
+ other => Field::Value(other.value()),
281
+ }
282
+ }
283
+ }
284
+
285
+ /// An instance object as the evaluator's `Fielded`: the head of a lookup must exist, but a
286
+ /// nested object answers nil for a field it lacks (Ruby's `Hash#[]`).
287
+ struct Node {
288
+ fields: BTreeMap<String, Slot>,
289
+ root: bool,
290
+ }
291
+
292
+ impl Node {
293
+ fn from_json(json: &Json, root: bool) -> Result<Self, String> {
294
+ let fields = match json {
295
+ Json::Object(fields) => fields
296
+ .iter()
297
+ .map(|(key, field)| Ok((key.clone(), Slot::from_json(field)?)))
298
+ .collect::<Result<BTreeMap<_, _>, String>>()?,
299
+ _ => BTreeMap::new(),
300
+ };
301
+ Ok(Self { fields, root })
302
+ }
303
+
304
+ fn object_value(&self) -> Value {
305
+ Value::Object(
306
+ self.fields
307
+ .iter()
308
+ .map(|(key, slot)| (key.clone(), slot.value()))
309
+ .collect(),
310
+ )
311
+ }
312
+ }
313
+
314
+ impl Fielded for Node {
315
+ fn field(&self, name: &str) -> Option<Field<'_>> {
316
+ match self.fields.get(name) {
317
+ Some(slot) => Some(slot.field()),
318
+ None if self.root => None,
319
+ None => Some(Field::Value(Value::Nil)),
320
+ }
321
+ }
322
+
323
+ /// `Resolver.unwrap_scalar`: a one-field value object reads as its one value.
324
+ fn as_scalar(&self) -> Option<Value> {
325
+ (self.fields.len() == 1)
326
+ .then(|| self.fields.values().next().map(Slot::value))
327
+ .flatten()
328
+ }
329
+
330
+ fn to_value(&self) -> Option<Value> {
331
+ Some(self.object_value())
332
+ }
333
+
334
+ fn items(&self, name: &str) -> Option<Vec<Field<'_>>> {
335
+ match self.fields.get(name) {
336
+ Some(Slot::List(items)) => Some(items.iter().map(Slot::field).collect()),
337
+ _ => None,
338
+ }
339
+ }
340
+ }
341
+
342
+ /// Evaluates `expr` over `instance`, the JSON object a value object's fields arrived as.
343
+ pub fn interpret(expr: &Expr, instance: &Json) -> Result<Value, String> {
344
+ let node = Node::from_json(instance, true)?;
345
+ let ctx = EvalContext {
346
+ args: &node,
347
+ instance: &NoFields,
348
+ };
349
+ shared::interpret(expr, &ctx).map_err(|refusal| refusal.to_string())
350
+ }
351
+
352
+ #[cfg(test)]
353
+ mod tests {
354
+ use super::*;
355
+
356
+ #[allow(clippy::needless_pass_by_value)] // reason: test helper called with inline literals
357
+ fn field(name: &str, value: Json) -> Json {
358
+ serde_json::json!({ name: value })
359
+ }
360
+
361
+ #[test]
362
+ fn cents_gte_zero_holds_for_a_non_negative_amount() {
363
+ // `cents >= 0`, the shape `AstJson.emit_predicate` builds.
364
+ let ast = serde_json::json!({"op":"compare","cmp":{"less_than":true,"equal":false,"negated":true},
365
+ "left":{"op":"lookup","path":["cents"]},"right":{"op":"int","value":0}});
366
+ let expr = parse(&ast).expect("valid Expr JSON");
367
+
368
+ assert_eq!(
369
+ interpret(&expr, &field("cents", serde_json::json!(1200))),
370
+ Ok(Value::Bool(true))
371
+ );
372
+ assert_eq!(
373
+ interpret(&expr, &field("cents", serde_json::json!(0))),
374
+ Ok(Value::Bool(true))
375
+ );
376
+ assert_eq!(
377
+ interpret(&expr, &field("cents", serde_json::json!(-1))),
378
+ Ok(Value::Bool(false))
379
+ );
380
+ }
381
+
382
+ #[test]
383
+ fn a_compound_and_of_four_comparisons_matches_chess_square_bounds() {
384
+ // `file >= 0 && file <= 7 && rank >= 0 && rank <= 7`, chess.bluebook's `Square`.
385
+ fn cmp(
386
+ less_than: bool,
387
+ equal: bool,
388
+ negated: bool,
389
+ field: &str,
390
+ n: i64,
391
+ ) -> serde_json::Value {
392
+ serde_json::json!({"op":"compare","cmp":{"less_than":less_than,"equal":equal,"negated":negated},
393
+ "left":{"op":"lookup","path":[field]},"right":{"op":"int","value":n}})
394
+ }
395
+ let ast = serde_json::json!({"op":"and","left":{"op":"and","left":{"op":"and",
396
+ "left": cmp(true, false, true, "file", 0),
397
+ "right": cmp(true, true, false, "file", 7)},
398
+ "right": cmp(true, false, true, "rank", 0)},
399
+ "right": cmp(true, true, false, "rank", 7)});
400
+ let expr = parse(&ast).expect("valid Expr JSON");
401
+
402
+ let on_board = serde_json::json!({"file": 3, "rank": 7});
403
+ let off_board = serde_json::json!({"file": 8, "rank": 0});
404
+ assert_eq!(interpret(&expr, &on_board), Ok(Value::Bool(true)));
405
+ assert_eq!(interpret(&expr, &off_board), Ok(Value::Bool(false)));
406
+ }
407
+
408
+ #[test]
409
+ fn a_blank_string_fails_the_not_empty_to_s_pattern() {
410
+ // `!value.to_s.empty?`, the most common real invariant shape.
411
+ let ast = serde_json::json!({"op":"not","expr":{"op":"empty","receiver":{"op":"to_s","receiver":{"op":"lookup","path":["value"]}}}});
412
+ let expr = parse(&ast).expect("valid Expr JSON");
413
+
414
+ assert_eq!(
415
+ interpret(&expr, &field("value", serde_json::json!("Margherita"))),
416
+ Ok(Value::Bool(true))
417
+ );
418
+ assert_eq!(
419
+ interpret(&expr, &field("value", serde_json::json!(""))),
420
+ Ok(Value::Bool(false))
421
+ );
422
+ }
423
+
424
+ #[test]
425
+ fn sign_test_positive_matches_a_real_amount_invariant() {
426
+ // `value.positive?` compiles to NOT(value < 0 or value == 0).
427
+ let ast = serde_json::json!({"op":"sign_test","cmp":{"less_than":true,"equal":true,"negated":true},"receiver":{"op":"lookup","path":["value"]}});
428
+ let expr = parse(&ast).expect("valid Expr JSON");
429
+
430
+ assert_eq!(
431
+ interpret(&expr, &field("value", serde_json::json!(5))),
432
+ Ok(Value::Bool(true))
433
+ );
434
+ assert_eq!(
435
+ interpret(&expr, &field("value", serde_json::json!(0))),
436
+ Ok(Value::Bool(false))
437
+ );
438
+ assert_eq!(
439
+ interpret(&expr, &field("value", serde_json::json!(-5))),
440
+ Ok(Value::Bool(false))
441
+ );
442
+ }
443
+
444
+ #[test]
445
+ fn int_and_float_compare_equal_across_kinds_the_same_way_the_kernel_does() {
446
+ let ast = serde_json::json!({"op":"compare","cmp":{"less_than":false,"equal":true,"negated":false},
447
+ "left":{"op":"lookup","path":["cents"]},"right":{"op":"float","value":3.0}});
448
+ let expr = parse(&ast).expect("valid Expr JSON");
449
+
450
+ assert_eq!(
451
+ interpret(&expr, &field("cents", serde_json::json!(3))),
452
+ Ok(Value::Bool(true))
453
+ );
454
+ }
455
+
456
+ #[test]
457
+ fn an_unrecognized_op_refuses_to_parse_rather_than_defaulting() {
458
+ let ast = serde_json::json!({"op":"frobnicate"});
459
+ let err = parse(&ast).unwrap_err();
460
+ assert!(err.contains("unrecognized"), "{err}");
461
+ }
462
+
463
+ // Expected values below are real Ruby results (`Resolver.resolve` / `Evaluator.call`); the
464
+ // differential spec `spec/rust_host_expr_json_conformance_spec.rb` re-checks them live.
465
+ use serde_json::json;
466
+
467
+ #[allow(clippy::needless_pass_by_value)] // reason: test helper called with inline literals
468
+ fn run(ast: Json, instance: Json) -> Result<Value, String> {
469
+ interpret(&parse(&ast).expect("valid Expr JSON"), &instance)
470
+ }
471
+
472
+ fn lookup_of(name: &str) -> Json {
473
+ json!({"op":"lookup","path":[name]})
474
+ }
475
+
476
+ fn int(n: i64) -> Json {
477
+ json!({"op":"int","value":n})
478
+ }
479
+
480
+ fn string(s: &str) -> Json {
481
+ json!({"op":"str","value":s})
482
+ }
483
+
484
+ #[allow(clippy::needless_pass_by_value)] // reason: test helper called with inline literals
485
+ fn call(op: &str, receiver: Json) -> Json {
486
+ json!({"op":op,"receiver":receiver})
487
+ }
488
+
489
+ #[allow(clippy::needless_pass_by_value)] // reason: test helper called with inline literals
490
+ fn eq(left: Json, right: Json) -> Json {
491
+ json!({"op":"compare","cmp":{"less_than":false,"equal":true,"negated":false},"left":left,"right":right})
492
+ }
493
+
494
+ fn strs(items: &[&str]) -> Value {
495
+ Value::Array(items.iter().map(|s| Value::Str(s.to_string())).collect())
496
+ }
497
+
498
+ #[test]
499
+ fn add_sums_numbers_and_promotes_to_float() {
500
+ let add = |l: Json, r: Json| json!({"op":"add","left":l,"right":r});
501
+ assert_eq!(run(add(int(2), int(3)), json!({})), Ok(Value::Int(5)));
502
+ assert_eq!(
503
+ run(add(int(2), json!({"op":"float","value":1.5})), json!({})),
504
+ Ok(Value::Float(3.5))
505
+ );
506
+ assert_eq!(
507
+ run(add(lookup_of("a"), int(1)), json!({"a": 9})),
508
+ Ok(Value::Int(10))
509
+ );
510
+ }
511
+
512
+ #[test]
513
+ fn add_refuses_a_non_number_and_an_overflow_with_rubys_words() {
514
+ let add = |l: Json, r: Json| json!({"op":"add","left":l,"right":r});
515
+ assert_eq!(
516
+ run(add(string("a"), int(1)), json!({})),
517
+ Err("addition expects a number, got \"a\"".to_string())
518
+ );
519
+ assert_eq!(
520
+ run(add(lookup_of("a"), int(1)), json!({"a": null})),
521
+ Err("addition expects a number, got nil".to_string())
522
+ );
523
+ assert_eq!(
524
+ run(add(int(1), lookup_of("a")), json!({"a": [1]})),
525
+ Err("addition expects a number, got [1]".to_string())
526
+ );
527
+ assert_eq!(
528
+ run(add(int(i64::MAX), int(1)), json!({})),
529
+ Err(
530
+ "addition overflowed: 9223372036854775807 + 1 does not fit in a 64-bit integer"
531
+ .to_string()
532
+ )
533
+ );
534
+ let huge = json!({"op":"float","value":1.7e308});
535
+ assert_eq!(
536
+ run(add(huge.clone(), huge), json!({})),
537
+ Err("addition overflowed: 1.7e+308 + 1.7e+308 is not a finite number".to_string())
538
+ );
539
+ }
540
+
541
+ #[test]
542
+ fn sub_mul_div_follow_rubys_limits_and_floor_integer_division() {
543
+ let op = |name: &str, l: Json, r: Json| json!({"op":name,"left":l,"right":r});
544
+ assert_eq!(run(op("sub", int(5), int(2)), json!({})), Ok(Value::Int(3)));
545
+ assert_eq!(
546
+ run(
547
+ op("mul", int(6), json!({"op":"float","value":0.5})),
548
+ json!({})
549
+ ),
550
+ Ok(Value::Float(3.0))
551
+ );
552
+ assert_eq!(
553
+ run(op("div", int(-7), int(2)), json!({})),
554
+ Ok(Value::Int(-4))
555
+ );
556
+ assert_eq!(
557
+ run(op("div", int(7), int(-2)), json!({})),
558
+ Ok(Value::Int(-4))
559
+ );
560
+ assert_eq!(
561
+ run(op("div", int(7), int(0)), json!({})),
562
+ Err("divided by 0".to_string())
563
+ );
564
+ assert_eq!(
565
+ run(op("mul", int(i64::MAX), int(2)), json!({})),
566
+ Err("multiplication overflowed: 9223372036854775807 * 2 does not fit in a 64-bit integer".to_string())
567
+ );
568
+ assert_eq!(
569
+ run(op("div", int(i64::MIN), int(-1)), json!({})),
570
+ Err(
571
+ "division overflowed: -9223372036854775808 / -1 does not fit in a 64-bit integer"
572
+ .to_string()
573
+ )
574
+ );
575
+ assert_eq!(
576
+ run(op("sub", lookup_of("a"), int(1)), json!({"a": null})),
577
+ Err("subtraction expects a number, got nil".to_string())
578
+ );
579
+ }
580
+
581
+ #[test]
582
+ fn modulo_floors_like_ruby_for_integers_and_floats() {
583
+ let modulo = |r: Json, d: Json| json!({"op":"modulo","receiver":r,"divisor":d});
584
+ assert_eq!(run(modulo(int(-7), int(3)), json!({})), Ok(Value::Int(2)));
585
+ assert_eq!(run(modulo(int(7), int(-3)), json!({})), Ok(Value::Int(-2)));
586
+ assert_eq!(
587
+ run(modulo(int(i64::MIN), int(-1)), json!({})),
588
+ Ok(Value::Int(0))
589
+ );
590
+ assert_eq!(
591
+ run(modulo(json!({"op":"float","value":5.5}), int(2)), json!({})),
592
+ Ok(Value::Float(1.5))
593
+ );
594
+ assert_eq!(
595
+ run(
596
+ modulo(int(7), json!({"op":"float","value":-2.5})),
597
+ json!({})
598
+ ),
599
+ Ok(Value::Float(-0.5))
600
+ );
601
+ assert_eq!(
602
+ run(
603
+ modulo(json!({"op":"float","value":-1.0}), int(2)),
604
+ json!({})
605
+ ),
606
+ Ok(Value::Float(1.0))
607
+ );
608
+ }
609
+
610
+ #[test]
611
+ fn modulo_refuses_a_zero_divisor_and_a_non_number() {
612
+ let modulo = |r: Json, d: Json| json!({"op":"modulo","receiver":r,"divisor":d});
613
+ assert_eq!(
614
+ run(modulo(int(5), int(0)), json!({})),
615
+ Err("divided by 0".to_string())
616
+ );
617
+ assert_eq!(
618
+ run(modulo(int(5), json!({"op":"float","value":0.0})), json!({})),
619
+ Err("divided by 0".to_string())
620
+ );
621
+ assert_eq!(
622
+ run(modulo(string("a"), int(2)), json!({})),
623
+ Err("modulo expects a number, got \"a\"".to_string())
624
+ );
625
+ assert_eq!(
626
+ run(modulo(int(2), lookup_of("d")), json!({"d": null})),
627
+ Err("modulo expects a number, got nil".to_string())
628
+ );
629
+ }
630
+
631
+ #[test]
632
+ fn include_tests_list_membership_with_numeric_coercion_and_substrings() {
633
+ let include = |h: Json, n: Json| json!({"op":"include","haystack":h,"needle":n});
634
+ assert_eq!(
635
+ run(
636
+ include(lookup_of("tags"), string("b")),
637
+ json!({"tags": ["a", "b"]})
638
+ ),
639
+ Ok(Value::Bool(true))
640
+ );
641
+ assert_eq!(
642
+ run(
643
+ include(lookup_of("tags"), string("z")),
644
+ json!({"tags": ["a", "b"]})
645
+ ),
646
+ Ok(Value::Bool(false))
647
+ );
648
+ assert_eq!(
649
+ run(
650
+ include(lookup_of("n"), json!({"op":"float","value":2.0})),
651
+ json!({"n": [1, 2]})
652
+ ),
653
+ Ok(Value::Bool(true))
654
+ );
655
+ assert_eq!(
656
+ run(
657
+ include(lookup_of("n"), lookup_of("m")),
658
+ json!({"n": [1, null], "m": null})
659
+ ),
660
+ Ok(Value::Bool(true))
661
+ );
662
+ assert_eq!(
663
+ run(
664
+ include(lookup_of("s"), string("ell")),
665
+ json!({"s": "hello"})
666
+ ),
667
+ Ok(Value::Bool(true))
668
+ );
669
+ assert_eq!(
670
+ run(include(lookup_of("s"), string("")), json!({"s": "hello"})),
671
+ Ok(Value::Bool(true))
672
+ );
673
+ }
674
+
675
+ #[test]
676
+ fn include_on_anything_but_a_list_or_string_is_false_and_a_string_needs_a_string() {
677
+ let include = |h: Json, n: Json| json!({"op":"include","haystack":h,"needle":n});
678
+ assert_eq!(
679
+ run(include(lookup_of("s"), string("a")), json!({"s": null})),
680
+ Ok(Value::Bool(false))
681
+ );
682
+ assert_eq!(
683
+ run(include(lookup_of("s"), string("a")), json!({"s": 5})),
684
+ Ok(Value::Bool(false))
685
+ );
686
+ assert_eq!(
687
+ run(include(lookup_of("s"), int(1)), json!({"s": "abc"})),
688
+ Err("no implicit conversion of Integer into String".to_string())
689
+ );
690
+ assert_eq!(
691
+ run(
692
+ include(lookup_of("s"), lookup_of("n")),
693
+ json!({"s": "abc", "n": null})
694
+ ),
695
+ Err("no implicit conversion of nil into String".to_string())
696
+ );
697
+ }
698
+
699
+ #[test]
700
+ fn array_literals_evaluate_each_element_and_compose_with_first_last_and_include() {
701
+ let array = json!({"op":"array","elements":[int(1), json!({"op":"float","value":2.5}), string("a"), json!({"op":"nil"})]});
702
+ assert_eq!(
703
+ run(array.clone(), json!({})),
704
+ Ok(Value::Array(vec![
705
+ Value::Int(1),
706
+ Value::Float(2.5),
707
+ Value::Str("a".to_string()),
708
+ Value::Nil
709
+ ]))
710
+ );
711
+ assert_eq!(
712
+ run(call("first", array.clone()), json!({})),
713
+ Ok(Value::Int(1))
714
+ );
715
+ assert_eq!(run(call("last", array), json!({})), Ok(Value::Nil));
716
+ assert_eq!(
717
+ run(json!({"op":"array","elements":[]}), json!({})),
718
+ Ok(Value::Array(vec![]))
719
+ );
720
+ }
721
+
722
+ #[test]
723
+ fn matches_regex_reads_ruby_anchors_flags_and_ascii_classes() {
724
+ let matches = |pattern: &str, flags: &str| json!({"op":"matches_regex","receiver":lookup_of("x"),"pattern":pattern,"flags":flags});
725
+ assert_eq!(
726
+ run(matches("^b$", ""), json!({"x": "a\nb"})),
727
+ Ok(Value::Bool(true))
728
+ );
729
+ assert_eq!(
730
+ run(matches("\\Ab\\z", ""), json!({"x": "a\nb"})),
731
+ Ok(Value::Bool(false))
732
+ );
733
+ assert_eq!(
734
+ run(matches("a.b", ""), json!({"x": "a\nb"})),
735
+ Ok(Value::Bool(false))
736
+ );
737
+ assert_eq!(
738
+ run(matches("a.b", "m"), json!({"x": "a\nb"})),
739
+ Ok(Value::Bool(true))
740
+ );
741
+ assert_eq!(
742
+ run(matches("A", "i"), json!({"x": "a"})),
743
+ Ok(Value::Bool(true))
744
+ );
745
+ assert_eq!(
746
+ run(matches("\\d", ""), json!({"x": "\u{663}"})),
747
+ Ok(Value::Bool(false))
748
+ );
749
+ assert_eq!(
750
+ run(matches("\\A[\\w.]+\\z", ""), json!({"x": "a.b_c"})),
751
+ Ok(Value::Bool(true))
752
+ );
753
+ assert_eq!(
754
+ run(matches("a b", "x"), json!({"x": "ab"})),
755
+ Ok(Value::Bool(true))
756
+ );
757
+ assert_eq!(
758
+ run(matches("\\Aa\\Z", ""), json!({"x": "a\n"})),
759
+ Ok(Value::Bool(true))
760
+ );
761
+ }
762
+
763
+ #[test]
764
+ fn matches_regex_coerces_scalars_and_refuses_the_rest_by_class() {
765
+ let matches = json!({"op":"matches_regex","receiver":lookup_of("x"),"pattern":"\\A[0-9.]+\\z","flags":""});
766
+ assert_eq!(
767
+ run(matches.clone(), json!({"x": 12})),
768
+ Ok(Value::Bool(true))
769
+ );
770
+ assert_eq!(
771
+ run(matches.clone(), json!({"x": 1.5})),
772
+ Ok(Value::Bool(true))
773
+ );
774
+ assert_eq!(
775
+ run(
776
+ json!({"op":"matches_regex","receiver":lookup_of("x"),"pattern":"\\A\\z","flags":""}),
777
+ json!({"x": null})
778
+ ),
779
+ Ok(Value::Bool(true))
780
+ );
781
+ assert_eq!(
782
+ run(matches.clone(), json!({"x": true})),
783
+ Err("match? expects a scalar, got TrueClass".to_string())
784
+ );
785
+ assert_eq!(
786
+ run(matches.clone(), json!({"x": false})),
787
+ Err("match? expects a scalar, got FalseClass".to_string())
788
+ );
789
+ assert_eq!(
790
+ run(matches, json!({"x": [1]})),
791
+ Err("match? expects a scalar, got Array".to_string())
792
+ );
793
+ }
794
+
795
+ #[test]
796
+ fn matches_regex_refuses_an_invalid_pattern_by_name() {
797
+ let err = run(
798
+ json!({"op":"matches_regex","receiver":string("a"),"pattern":"(","flags":""}),
799
+ json!({}),
800
+ )
801
+ .unwrap_err();
802
+ assert!(
803
+ err.starts_with("match? given an invalid pattern \"(\" — "),
804
+ "{err}"
805
+ );
806
+ }
807
+
808
+ #[test]
809
+ fn block_predicates_aggregate_with_ruby_answers_for_an_empty_list() {
810
+ let block = |mode: &str| {
811
+ json!({"op":"block_predicate","mode":mode,"receiver":lookup_of("seats"),"param":"s",
812
+ "predicate":eq(json!({"op":"lookup","path":["s","taken"]}), json!({"op":"bool","value":false}))})
813
+ };
814
+ let seats = json!({"seats": [{"n": 1, "taken": true}, {"n": 2, "taken": false}]});
815
+ assert_eq!(run(block("any"), seats.clone()), Ok(Value::Bool(true)));
816
+ assert_eq!(run(block("all"), seats.clone()), Ok(Value::Bool(false)));
817
+ assert_eq!(run(block("none"), seats), Ok(Value::Bool(false)));
818
+ let empty = json!({"seats": []});
819
+ assert_eq!(run(block("all"), empty.clone()), Ok(Value::Bool(true)));
820
+ assert_eq!(run(block("any"), empty.clone()), Ok(Value::Bool(false)));
821
+ assert_eq!(run(block("none"), empty), Ok(Value::Bool(true)));
822
+ }
823
+
824
+ #[test]
825
+ fn a_block_predicate_evaluates_every_element_so_a_later_error_still_surfaces() {
826
+ // `all?` would stop at the first false in a short-circuit loop; Ruby maps first.
827
+ let ast = json!({"op":"block_predicate","mode":"all","receiver":lookup_of("xs"),"param":"x",
828
+ "predicate":json!({"op":"sign_test","cmp":{"less_than":true,"equal":true,"negated":true},"receiver":lookup_of("x")})});
829
+ assert_eq!(
830
+ run(ast, json!({"xs": [-1, "a"]})),
831
+ Err("positive? expects a number, got \"a\"".to_string())
832
+ );
833
+ }
834
+
835
+ #[test]
836
+ fn a_block_predicate_over_a_non_list_refuses_with_the_mode_named() {
837
+ for (mode, word) in [("all", "all?"), ("any", "any?"), ("none", "none?")] {
838
+ let ast = json!({"op":"block_predicate","mode":mode,"receiver":lookup_of("x"),"param":"y","predicate":{"op":"bool","value":true}});
839
+ assert_eq!(
840
+ run(ast.clone(), json!({"x": 5})),
841
+ Err(format!("{word} expects a list, got 5"))
842
+ );
843
+ assert_eq!(
844
+ run(ast, json!({"x": null})),
845
+ Err(format!("{word} expects a list, got nil"))
846
+ );
847
+ }
848
+ }
849
+
850
+ #[test]
851
+ fn the_block_parameter_shadows_a_same_named_field_only_inside_the_block() {
852
+ let ast = json!({"op":"block_predicate","mode":"any","receiver":lookup_of("xs"),"param":"n",
853
+ "predicate":{"op":"and","left":eq(lookup_of("n"), int(2)),"right":eq(lookup_of("m"), int(9))}});
854
+ assert_eq!(
855
+ run(ast.clone(), json!({"xs": [1, 2], "m": 9, "n": 100})),
856
+ Ok(Value::Bool(true))
857
+ );
858
+ assert_eq!(
859
+ run(ast, json!({"xs": [1, 2], "m": 8})),
860
+ Ok(Value::Bool(false))
861
+ );
862
+ }
863
+
864
+ #[test]
865
+ fn nested_blocks_see_the_outer_parameter() {
866
+ let inner = json!({"op":"block_predicate","mode":"none","receiver":lookup_of("xs"),"param":"o",
867
+ "predicate":eq(lookup_of("o"), json!({"op":"add","left":lookup_of("s"),"right":int(1)}))});
868
+ let outer = json!({"op":"block_predicate","mode":"any","receiver":lookup_of("xs"),"param":"s","predicate":inner});
869
+ // Every element has a successor except the largest, so `any?` holds.
870
+ assert_eq!(run(outer, json!({"xs": [1, 2, 4]})), Ok(Value::Bool(true)));
871
+ }
872
+
873
+ #[test]
874
+ fn an_unbound_name_in_a_block_refuses_as_ruby_does() {
875
+ let ast = json!({"op":"block_predicate","mode":"any","receiver":lookup_of("xs"),"param":"x","predicate":lookup_of("ghost")});
876
+ assert_eq!(
877
+ run(ast, json!({"xs": [1]})),
878
+ Err("cannot resolve \"ghost\" — no such attribute or argument".to_string())
879
+ );
880
+ }
881
+
882
+ #[test]
883
+ fn find_answers_the_first_match_projected_through_its_path_and_nil_on_a_miss() {
884
+ let find = |wanted: bool, path: &[&str]| {
885
+ json!({"op":"find","receiver":lookup_of("legs"),"param":"l","path":path,
886
+ "predicate":eq(json!({"op":"lookup","path":["l","open"]}), json!({"op":"bool","value":wanted}))})
887
+ };
888
+ let legs = json!({"legs": [{"to": "A", "open": false}, {"to": "B", "open": true}, {"to": "C", "open": true}]});
889
+ assert_eq!(
890
+ run(find(true, &["to"]), legs.clone()),
891
+ Ok(Value::Str("B".to_string()))
892
+ );
893
+ assert_eq!(run(find(true, &["nope"]), legs.clone()), Ok(Value::Nil));
894
+ assert!(matches!(run(find(true, &[]), legs), Ok(Value::Object(_))));
895
+ assert_eq!(
896
+ run(
897
+ find(true, &["to"]),
898
+ json!({"legs": [{"to": "A", "open": false}]})
899
+ ),
900
+ Ok(Value::Nil)
901
+ );
902
+ assert_eq!(
903
+ run(find(true, &["to"]), json!({"legs": []})),
904
+ Ok(Value::Nil)
905
+ );
906
+ }
907
+
908
+ #[test]
909
+ fn find_stops_at_the_first_match_but_refuses_a_non_list() {
910
+ // The second element would raise; `find` never reaches it, as Ruby's `Array#find`.
911
+ let ast = json!({"op":"find","receiver":lookup_of("xs"),"param":"x","path":[],
912
+ "predicate":json!({"op":"sign_test","cmp":{"less_than":true,"equal":true,"negated":true},"receiver":lookup_of("x")})});
913
+ assert_eq!(run(ast.clone(), json!({"xs": [3, "a"]})), Ok(Value::Int(3)));
914
+ assert_eq!(
915
+ run(ast, json!({"xs": "abc"})),
916
+ Err("find expects a list, got \"abc\"".to_string())
917
+ );
918
+ }
919
+
920
+ #[test]
921
+ fn presence_and_assignment_follow_blank_and_nil() {
922
+ let presence =
923
+ |negated: bool| json!({"op":"presence","receiver":lookup_of("x"),"negated":negated});
924
+ let assignment =
925
+ |negated: bool| json!({"op":"assignment","receiver":lookup_of("x"),"negated":negated});
926
+ for (value, present) in [
927
+ (json!(null), false),
928
+ (json!(false), false),
929
+ (json!(""), false),
930
+ (json!([]), false),
931
+ (json!(" "), true),
932
+ (json!(0), true),
933
+ (json!(true), true),
934
+ (json!([0]), true),
935
+ ] {
936
+ assert_eq!(
937
+ run(presence(false), json!({"x": value.clone()})),
938
+ Ok(Value::Bool(present)),
939
+ "present? of {value}"
940
+ );
941
+ assert_eq!(
942
+ run(presence(true), json!({"x": value.clone()})),
943
+ Ok(Value::Bool(!present)),
944
+ "blank? of {value}"
945
+ );
946
+ }
947
+ // `set?` asks only `!nil?`: an assigned empty or false value is set.
948
+ assert_eq!(
949
+ run(assignment(false), json!({"x": ""})),
950
+ Ok(Value::Bool(true))
951
+ );
952
+ assert_eq!(
953
+ run(assignment(false), json!({"x": false})),
954
+ Ok(Value::Bool(true))
955
+ );
956
+ assert_eq!(
957
+ run(assignment(false), json!({"x": null})),
958
+ Ok(Value::Bool(false))
959
+ );
960
+ assert_eq!(
961
+ run(assignment(true), json!({"x": null})),
962
+ Ok(Value::Bool(true))
963
+ );
964
+ }
965
+
966
+ #[test]
967
+ fn split_follows_ruby_for_awk_empty_and_literal_separators() {
968
+ let split = |sep: &str| json!({"op":"split","receiver":lookup_of("x"),"separator":sep});
969
+ assert_eq!(
970
+ run(split(" "), json!({"x": " a b c "})),
971
+ Ok(strs(&["a", "b", "c"]))
972
+ );
973
+ assert_eq!(run(split(""), json!({"x": "aé"})), Ok(strs(&["a", "é"])));
974
+ assert_eq!(
975
+ run(split(","), json!({"x": "a,b,,"})),
976
+ Ok(strs(&["a", "b"]))
977
+ );
978
+ assert_eq!(run(split(","), json!({"x": ",a"})), Ok(strs(&["", "a"])));
979
+ assert_eq!(run(split(","), json!({"x": ""})), Ok(strs(&[])));
980
+ assert_eq!(
981
+ run(split(","), json!({"x": 5})),
982
+ Err("split expects a string, got 5".to_string())
983
+ );
984
+ assert_eq!(
985
+ run(split(","), json!({"x": null})),
986
+ Err("split expects a string, got nil".to_string())
987
+ );
988
+ }
989
+
990
+ #[test]
991
+ fn first_and_last_answer_nil_for_an_empty_list_and_refuse_a_non_list() {
992
+ assert_eq!(
993
+ run(call("first", lookup_of("x")), json!({"x": [7, 8]})),
994
+ Ok(Value::Int(7))
995
+ );
996
+ assert_eq!(
997
+ run(call("last", lookup_of("x")), json!({"x": [7, 8]})),
998
+ Ok(Value::Int(8))
999
+ );
1000
+ assert_eq!(
1001
+ run(call("first", lookup_of("x")), json!({"x": []})),
1002
+ Ok(Value::Nil)
1003
+ );
1004
+ assert_eq!(
1005
+ run(call("last", lookup_of("x")), json!({"x": "abc"})),
1006
+ Err("last expects a list, got \"abc\"".to_string())
1007
+ );
1008
+ assert_eq!(
1009
+ run(call("first", lookup_of("x")), json!({"x": null})),
1010
+ Err("first expects a list, got nil".to_string())
1011
+ );
1012
+ }
1013
+
1014
+ #[test]
1015
+ fn strip_trims_rubys_whitespace_and_needs_a_string() {
1016
+ let strip_of = |side: &str, text: &str| {
1017
+ run(
1018
+ json!({"op":"strip","receiver":lookup_of("x"),"side":side}),
1019
+ json!({"x": text}),
1020
+ )
1021
+ };
1022
+ assert_eq!(strip_of("both", " \t a b \0"), Ok(Value::Str("a b".into())));
1023
+ assert_eq!(strip_of("left", " a "), Ok(Value::Str("a ".into())));
1024
+ assert_eq!(strip_of("right", " a "), Ok(Value::Str(" a".into())));
1025
+ assert_eq!(
1026
+ strip_of("both", "\u{a0}a"),
1027
+ Ok(Value::Str("\u{a0}a".into()))
1028
+ );
1029
+ assert_eq!(
1030
+ run(
1031
+ json!({"op":"strip","receiver":lookup_of("x"),"side":"both"}),
1032
+ json!({"x": null})
1033
+ ),
1034
+ Err("strip expects a string, got nil".to_string())
1035
+ );
1036
+ }
1037
+
1038
+ #[test]
1039
+ fn starts_with_and_ends_with_need_a_string() {
1040
+ let starts = json!({"op":"starts_with","receiver":lookup_of("x"),"substring":"ab"});
1041
+ let ends = json!({"op":"ends_with","receiver":lookup_of("x"),"substring":"bc"});
1042
+ assert_eq!(
1043
+ run(starts.clone(), json!({"x": "abc"})),
1044
+ Ok(Value::Bool(true))
1045
+ );
1046
+ assert_eq!(
1047
+ run(ends.clone(), json!({"x": "abc"})),
1048
+ Ok(Value::Bool(true))
1049
+ );
1050
+ assert_eq!(run(ends, json!({"x": "abd"})), Ok(Value::Bool(false)));
1051
+ assert_eq!(
1052
+ run(starts.clone(), json!({"x": null})),
1053
+ Err("start_with? expects a string, got nil".to_string())
1054
+ );
1055
+ assert_eq!(
1056
+ run(starts, json!({"x": [1]})),
1057
+ Err("start_with? expects a string, got [1]".to_string())
1058
+ );
1059
+ }
1060
+
1061
+ #[test]
1062
+ fn a_path_through_a_string_list_or_integer_walks_like_ruby() {
1063
+ let path = |segments: &[&str]| json!({"op":"lookup","path":segments});
1064
+ // `String#[]` answers the segment when it is a substring.
1065
+ assert_eq!(
1066
+ run(path(&["x", "foo"]), json!({"x": "abcfoo"})),
1067
+ Ok(Value::Str("foo".to_string()))
1068
+ );
1069
+ assert_eq!(
1070
+ run(path(&["x", "nil?"]), json!({"x": "abc"})),
1071
+ Ok(Value::Nil)
1072
+ );
1073
+ assert_eq!(run(path(&["x", "a"]), json!({"x": 1.5})), Ok(Value::Nil));
1074
+ assert_eq!(run(path(&["x", "a"]), json!({"x": true})), Ok(Value::Nil));
1075
+ assert_eq!(
1076
+ run(path(&["x", "a"]), json!({"x": [1]})),
1077
+ Err("cannot read \"a\" from [1]".to_string())
1078
+ );
1079
+ assert_eq!(
1080
+ run(path(&["x", "a"]), json!({"x": 5})),
1081
+ Err("cannot read \"a\" from 5".to_string())
1082
+ );
1083
+ }
1084
+
1085
+ #[test]
1086
+ fn a_one_field_object_reads_as_its_value_and_a_wider_one_stays_an_object() {
1087
+ assert_eq!(
1088
+ run(lookup_of("x"), json!({"x": {"value": 7}})),
1089
+ Ok(Value::Int(7))
1090
+ );
1091
+ assert!(matches!(
1092
+ run(lookup_of("x"), json!({"x": {"a": 1, "b": 2}})),
1093
+ Ok(Value::Object(_))
1094
+ ));
1095
+ assert_eq!(
1096
+ run(
1097
+ json!({"op":"lookup","path":["x","b"]}),
1098
+ json!({"x": {"a": 1, "b": 2}})
1099
+ ),
1100
+ Ok(Value::Int(2))
1101
+ );
1102
+ }
1103
+
1104
+ #[test]
1105
+ fn errors_are_worded_as_rubys_describe() {
1106
+ assert_eq!(
1107
+ run(call("size", lookup_of("x")), json!({"x": 5})),
1108
+ Err("size expects a list or string, got 5".to_string())
1109
+ );
1110
+ assert_eq!(
1111
+ run(call("empty", lookup_of("x")), json!({"x": null})),
1112
+ Err("empty? expects a list or string, got nil".to_string())
1113
+ );
1114
+ assert_eq!(
1115
+ run(call("to_s", lookup_of("x")), json!({"x": [1, "a"]})),
1116
+ Err("to_s expects a scalar, got [1,\"a\"]".to_string())
1117
+ );
1118
+ assert_eq!(
1119
+ run(
1120
+ json!({"op":"compare","cmp":{"less_than":true,"equal":false,"negated":false},"left":lookup_of("x"),"right":int(1)}),
1121
+ json!({"x": "a"})
1122
+ ),
1123
+ Err("comparison of String with 1 failed".to_string())
1124
+ );
1125
+ assert_eq!(
1126
+ run(
1127
+ json!({"op":"sign_test","cmp":{"less_than":false,"equal":true,"negated":false},"receiver":lookup_of("x")}),
1128
+ json!({"x": "a"})
1129
+ ),
1130
+ Err("zero? expects a number, got \"a\"".to_string())
1131
+ );
1132
+ assert_eq!(
1133
+ run(lookup_of("ghost"), json!({})),
1134
+ Err("cannot resolve \"ghost\" — no such attribute or argument".to_string())
1135
+ );
1136
+ }
1137
+ }