hecks 3.14.0 → 5.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 (427) hide show
  1. checksums.yaml +4 -4
  2. data/examples/pizzas/adapters/fake_processor.rb +34 -0
  3. data/examples/pizzas/adapters/payment_signature.rb +66 -0
  4. data/examples/pizzas/adapters/payment_webhook.rb +87 -0
  5. data/examples/pizzas/bluebook/pizzas.behaviors +33 -0
  6. data/examples/pizzas/bluebook/pizzas.hecksagon +5 -3
  7. data/examples/pizzas/config.ru +22 -0
  8. data/exe/hecks +1 -1
  9. data/lib/hecks/adapters/driven/prism.rb +0 -2
  10. data/lib/hecks/adapters/driven.rb +0 -2
  11. data/lib/hecks/adapters/driving/json.rb +9 -16
  12. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -2
  13. data/lib/hecks/bluebook/dsl/derivation_words.rb +58 -0
  14. data/lib/hecks/bluebook/dsl/translation_builder.rb +6 -37
  15. data/lib/hecks/bluebook/expression/ast_json.rb +0 -9
  16. data/lib/hecks/bluebook/hecksagon.rb +1 -1
  17. data/lib/hecks/bluebook/meta_validator/translation_judge/rules.rb +7 -10
  18. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +4 -8
  19. data/lib/hecks/bluebook/meta_validator.rb +0 -11
  20. data/lib/hecks/bluebook/translation.rb +18 -10
  21. data/lib/hecks/bluebook/translation_expression/evaluator.rb +63 -0
  22. data/lib/hecks/bluebook/translation_expression/parser.rb +127 -0
  23. data/lib/hecks/bluebook/translation_expression.rb +39 -0
  24. data/lib/hecks/bluebook/translation_plan.rb +70 -0
  25. data/lib/hecks/bluebook.rb +2 -0
  26. data/lib/hecks/chapters.rb +3 -3
  27. data/lib/hecks/cli/project_cli/launcher_source.rb +1 -1
  28. data/lib/hecks/composer/adapters/claude_composer.adapter +3 -0
  29. data/lib/hecks/composer/adapters/claude_composer.rb +83 -0
  30. data/lib/hecks/composer/adapters/fake_agent.adapter +3 -0
  31. data/lib/hecks/composer/adapters/fake_agent.rb +55 -0
  32. data/lib/hecks/composer/adapters/jev.adapter +3 -0
  33. data/lib/hecks/composer/adapters/jev.rb +66 -0
  34. data/lib/hecks/composer/agent.rb +113 -0
  35. data/lib/hecks/composer/assistant.rb +110 -0
  36. data/lib/hecks/composer/behavior_run.rb +53 -0
  37. data/lib/hecks/composer/bluebook/composer.bluebook +178 -0
  38. data/lib/hecks/composer/bluebook/composer.hecksagon +16 -0
  39. data/lib/hecks/composer/board.rb +65 -0
  40. data/lib/hecks/composer/board_edit/command_text.rb +39 -0
  41. data/lib/hecks/composer/board_edit/source.rb +84 -0
  42. data/lib/hecks/composer/board_edit.rb +144 -0
  43. data/lib/hecks/composer/check.rb +89 -0
  44. data/lib/hecks/composer/domains.rb +81 -0
  45. data/lib/hecks/composer/jobs.rb +134 -0
  46. data/lib/hecks/composer/layout.rb +37 -0
  47. data/lib/hecks/composer/live.rb +150 -0
  48. data/lib/hecks/composer/outline.rb +48 -0
  49. data/lib/hecks/composer/page.html +989 -0
  50. data/lib/hecks/composer/panels.rb +142 -0
  51. data/lib/hecks/composer/parse.rb +83 -0
  52. data/lib/hecks/composer/plain.rb +28 -0
  53. data/lib/hecks/composer/ports/composer_agent.port +7 -0
  54. data/lib/hecks/composer/session.rb +141 -0
  55. data/lib/hecks/composer/toolbox.rb +166 -0
  56. data/lib/hecks/composer/web.rb +156 -0
  57. data/lib/hecks/composer/workbench.rb +51 -0
  58. data/lib/hecks/composer/workspace.rb +81 -0
  59. data/lib/hecks/composer.rb +17 -0
  60. data/lib/hecks/corpus/adapters.rb +22 -0
  61. data/lib/hecks/corpus/rust.rb +1 -1
  62. data/lib/hecks/corpus/worlds.rb +19 -0
  63. data/lib/hecks/corpus.rb +5 -0
  64. data/lib/hecks/forms/app/commands.rb +2 -3
  65. data/lib/hecks/forms/app/queries.rb +2 -3
  66. data/lib/hecks/forms/app/records.rb +17 -5
  67. data/lib/hecks/forms/app/responses.rb +3 -1
  68. data/lib/hecks/forms/command_form_renderer.rb +42 -16
  69. data/lib/hecks/forms/field_controls.rb +2 -2
  70. data/lib/hecks/forms/field_renderer.rb +1 -1
  71. data/lib/hecks/forms/field_shape.rb +36 -17
  72. data/lib/hecks/forms/index_renderer.rb +2 -1
  73. data/lib/hecks/forms/page.rb +1 -1
  74. data/lib/hecks/forms/params.rb +0 -13
  75. data/lib/hecks/forms/query_form_renderer.rb +7 -6
  76. data/lib/hecks/forms/record_renderer.rb +11 -7
  77. data/lib/hecks/forms/rule_words.rb +100 -0
  78. data/lib/hecks/forms/value_object_shape.rb +2 -1
  79. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -3
  80. data/lib/hecks/fuzzing/isolated_boot/worlds.rb +3 -6
  81. data/lib/hecks/fuzzing/replay/filters.rb +2 -6
  82. data/lib/hecks/gate/stages.yml +25 -0
  83. data/lib/hecks/grammar/evolve/seeds.rb +0 -6
  84. data/lib/hecks/grammar/evolve.rb +0 -3
  85. data/lib/hecks/grammar/translation.bluebook +1 -1
  86. data/lib/hecks/hecks/adapters/codebase/composing.rb +56 -0
  87. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +3 -1
  88. data/lib/hecks/hecks/adapters/first_admin.rb +3 -3
  89. data/lib/hecks/hecks/adapters/journal_store/changes.rb +0 -26
  90. data/lib/hecks/hecks/adapters/journal_store/examination.rb +2 -23
  91. data/lib/hecks/hecks/adapters/journal_store/readings.rb +3 -11
  92. data/lib/hecks/hecks/adapters/journal_store.rb +0 -11
  93. data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +4 -11
  94. data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +0 -9
  95. data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +0 -4
  96. data/lib/hecks/hecks/codebase.behaviors +6 -0
  97. data/lib/hecks/hecks/codebase.bluebook +21 -0
  98. data/lib/hecks/hecks/composer.behaviors +68 -0
  99. data/lib/hecks/hecks/context_map.hecksagon +1 -1
  100. data/lib/hecks/hecks/custodian.behaviors +0 -6
  101. data/lib/hecks/hecks/custodian.bluebook +3 -80
  102. data/lib/hecks/hecks/hecks.hecksagon +2 -2
  103. data/lib/hecks/hecks/hecks.world +1 -1
  104. data/lib/hecks/hecks/rehearsal.bluebook +7 -13
  105. data/lib/hecks/kernel_capabilities/templates.rb +4 -4
  106. data/lib/hecks/kernel_capabilities.rb +1 -1
  107. data/lib/hecks/language/bluebook/vocabulary.bluebook +2 -3
  108. data/lib/hecks/language/translation/translation_aggregate.bluebook +21 -24
  109. data/lib/hecks/ports/access_control.rb +2 -2
  110. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +3 -41
  111. data/lib/hecks/ports/persistence/plugins/era/lineage/coverage.rb +7 -7
  112. data/lib/hecks/ports/persistence/plugins/era/lineage/rewrite.rb +25 -2
  113. data/lib/hecks/ports/persistence/plugins/era/lineage/rules.rb +4 -4
  114. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +17 -25
  115. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/base_sql.rb +0 -12
  116. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/domain_rename.rb +1 -1
  117. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +2 -22
  118. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +0 -1
  119. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +9 -3
  120. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +3 -29
  121. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/refusals.rb +2 -26
  122. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -33
  123. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -3
  124. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +2 -4
  125. data/lib/hecks/ports/persistence/plugins/era/translation/expression_sql.rb +52 -0
  126. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +23 -29
  127. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +1 -1
  128. data/lib/hecks/ports/persistence/plugins/era/translation.rb +0 -1
  129. data/lib/hecks/ports/persistence/plugins/era.rb +3 -6
  130. data/lib/hecks/ports.rb +0 -1
  131. data/lib/hecks/projection_files.rb +3 -1
  132. data/lib/hecks/projections/deploy/templates/lambda/nat_comment_oauth.tmpl +1 -1
  133. data/lib/hecks/projections/glossary/entries.rb +10 -4
  134. data/lib/hecks/projections/glossary/groups.rb +42 -0
  135. data/lib/hecks/projections/glossary/html/page.rb +7 -2
  136. data/lib/hecks/projections/glossary/html/parser.rb +1 -1
  137. data/lib/hecks/projections/glossary/html/sections.rb +31 -11
  138. data/lib/hecks/projections/glossary/html.rb +1 -2
  139. data/lib/hecks/projections/glossary/markdown.rb +31 -28
  140. data/lib/hecks/projections/glossary/mermaid.rb +3 -0
  141. data/lib/hecks/projections/glossary/page.css +143 -153
  142. data/lib/hecks/projections/glossary/page.js +63 -3
  143. data/lib/hecks/projections/glossary/sentences/actions.rb +88 -0
  144. data/lib/hecks/projections/glossary/sentences/paths.rb +77 -0
  145. data/lib/hecks/projections/glossary/sentences/queries.rb +90 -0
  146. data/lib/hecks/projections/glossary/sentences/value_objects.rb +32 -5
  147. data/lib/hecks/projections/glossary/sentences.rb +13 -3
  148. data/lib/hecks/projections/glossary/slugs.rb +4 -1
  149. data/lib/hecks/projections/glossary.rb +2 -1
  150. data/lib/hecks/projections/plain_english.rb +97 -0
  151. data/lib/hecks/projections/rust_vocabulary/arguments.rb +2 -2
  152. data/lib/hecks/projections/rust_vocabulary/enum_body.rb +20 -25
  153. data/lib/hecks/projections/rust_vocabulary/tests.rb +2 -2
  154. data/lib/hecks/projections/rust_vocabulary.rb +14 -6
  155. data/lib/hecks/projections/site/cms_editor/color.rb +0 -6
  156. data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +7 -2
  157. data/lib/hecks/projections/statements/actions.rb +99 -0
  158. data/lib/hecks/projections/statements/relationships.rb +86 -0
  159. data/lib/hecks/projections/statements.rb +15 -24
  160. data/lib/hecks/projector/exporter/adapters.rb +112 -0
  161. data/lib/hecks/projector/exporter/translations.rb +18 -35
  162. data/lib/hecks/projector/exporter/worlds.rb +103 -0
  163. data/lib/hecks/projector/exporter.rb +5 -1
  164. data/lib/hecks/quality_control/adapters/agent.rb +6 -7
  165. data/lib/hecks/quality_control/adapters/agent_profile.rb +16 -114
  166. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +1 -1
  167. data/lib/hecks/runtime/loader/boot_steps.rb +0 -20
  168. data/lib/hecks/runtime/outbox.rb +2 -9
  169. data/lib/hecks/runtime/port_operation_interpreter.rb +1 -9
  170. data/lib/hecks/runtime/remote_dispatcher.rb +2 -2
  171. data/lib/hecks/runtime/value/unwrapping.rb +60 -0
  172. data/lib/hecks/runtime/value.rb +10 -21
  173. data/lib/hecks/rust_build/project_rust/target_ir.rb +23 -4
  174. data/lib/hecks/rust_build/wasm.rb +0 -12
  175. data/lib/hecks/rust_build/write_if_changed.rb +12 -12
  176. data/lib/hecks/rust_format.rb +77 -0
  177. data/lib/hecks/tools/fuzz_sweep/findings.rb +3 -9
  178. data/lib/hecks/tools/fuzz_sweep.rb +1 -3
  179. data/lib/hecks/tools/translation_audit/verdicts.rb +3 -28
  180. data/lib/hecks/tools/translation_audit.rb +8 -10
  181. data/lib/hecks/tools/translation_scaffold/reporting.rb +1 -1
  182. data/lib/hecks/version.rb +1 -1
  183. data/lib/hecks/vocabulary.rb +2 -1
  184. data/lib/hecks.rb +3 -0
  185. data/prebuilt/{1a6f596be124228be93eba1d86dbc9d2933190fa389b284331241cb9f27115a0.marshal → ad68f9e06c8068c2a4176b57abf3083d591d5d8980c2b3e897b97331f965b643.marshal} +0 -0
  186. data/prebuilt/{a883a7e568667aa5ac904c82daff356bdf9e996252a36af6def4a4a43efb967e.marshal → ee22ec8642c4006717ba2b80f46e5efb81de459eef9dfec043e838c0ad4fd720.marshal} +0 -0
  187. data/prebuilt/{verdicts-872993b1aa26d73fe48f28251374b8585fed145d02aab42be0f00de386b6ff80.json → verdicts-b69f5dcbba79c20daa0493476fa7c317004a30aa2bc3d3dc6a722ea70ae2b43b.json} +1 -1
  188. data/rust/Cargo.toml +40 -0
  189. data/rust/build/Cargo.toml +36 -0
  190. data/rust/build/clippy.toml +4 -0
  191. data/rust/build/src/adapters_pass.rs +196 -0
  192. data/rust/build/src/binding_pass.rs +54 -13
  193. data/rust/build/src/build_artifact.rs +23 -5
  194. data/rust/build/src/cargo_sync.rs +69 -26
  195. data/rust/build/src/lineage_pass.rs +101 -87
  196. data/rust/build/src/main.rs +11 -2
  197. data/rust/build/src/pipeline.rs +80 -19
  198. data/rust/build/src/reserved_names.rs +6 -50
  199. data/rust/build/src/resolve.rs +82 -35
  200. data/rust/build/src/rustfmt.rs +100 -0
  201. data/rust/build/src/seams_pass.rs +85 -16
  202. data/rust/build/src/subprocess.rs +30 -6
  203. data/rust/build/src/tmp.rs +5 -2
  204. data/rust/build/src/translation_pass.rs +72 -22
  205. data/rust/build/src/world_pass.rs +109 -0
  206. data/rust/clippy.toml +4 -0
  207. data/rust/codegen/Cargo.toml +36 -0
  208. data/rust/codegen/clippy.toml +4 -0
  209. data/rust/codegen/src/asks.rs +40 -14
  210. data/rust/codegen/src/attr.rs +2 -2
  211. data/rust/codegen/src/bridging.rs +676 -117
  212. data/rust/codegen/src/commands.rs +979 -373
  213. data/rust/codegen/src/constraints.rs +88 -25
  214. data/rust/codegen/src/dependency_planning.rs +128 -34
  215. data/rust/codegen/src/domain_generator.rs +306 -170
  216. data/rust/codegen/src/exemplar.rs +87 -32
  217. data/rust/codegen/src/expr_emitter.rs +54 -6
  218. data/rust/codegen/src/fielded.rs +193 -41
  219. data/rust/codegen/src/hecks_naming.rs +124 -16
  220. data/rust/codegen/src/hecks_naming_tests.rs +81 -0
  221. data/rust/codegen/src/json.rs +80 -44
  222. data/rust/codegen/src/json_codec.rs +585 -149
  223. data/rust/codegen/src/literal.rs +104 -138
  224. data/rust/codegen/src/main.rs +151 -52
  225. data/rust/codegen/src/manifest.rs +76 -14
  226. data/rust/codegen/src/mutations.rs +1212 -239
  227. data/rust/codegen/src/naming.rs +111 -60
  228. data/rust/codegen/src/optional_pass.rs +90 -28
  229. data/rust/codegen/src/ports.rs +5 -4
  230. data/rust/codegen/src/prelude.rs +139 -49
  231. data/rust/codegen/src/presenters.rs +335 -0
  232. data/rust/codegen/src/queries.rs +601 -226
  233. data/rust/codegen/src/reactions.rs +268 -74
  234. data/rust/codegen/src/read_models.rs +574 -229
  235. data/rust/codegen/src/reference_specs.rs +16 -3
  236. data/rust/codegen/src/registry.rs +128 -79
  237. data/rust/codegen/src/reserved_names.rs +6 -50
  238. data/rust/codegen/src/shared.rs +71 -22
  239. data/rust/codegen/src/sidecars.rs +15 -5
  240. data/rust/codegen/src/skip_reason.rs +8 -2
  241. data/rust/codegen/src/types.rs +306 -108
  242. data/rust/host/Cargo.lock +1 -0
  243. data/rust/host/Cargo.toml +40 -4
  244. data/rust/host/HECKS_RELEASE +1 -1
  245. data/rust/host/clippy.toml +4 -0
  246. data/rust/host/src/api.rs +698 -218
  247. data/rust/host/src/auth.rs +491 -214
  248. data/rust/host/src/b64.rs +91 -0
  249. data/rust/host/src/bin/expr_harness.rs +6 -2
  250. data/rust/host/src/bin/lineage_harness.rs +47 -11
  251. data/rust/host/src/bin/mint_harness.rs +70 -37
  252. data/rust/host/src/bin/validate_harness.rs +16 -8
  253. data/rust/host/src/boot.rs +144 -68
  254. data/rust/host/src/boundary_fuzz/api.rs +130 -32
  255. data/rust/host/src/boundary_fuzz/auth.rs +108 -23
  256. data/rust/host/src/boundary_fuzz/expr_json.rs +85 -11
  257. data/rust/host/src/boundary_fuzz/ir.rs +164 -12
  258. data/rust/host/src/boundary_fuzz/server.rs +283 -36
  259. data/rust/host/src/boundary_fuzz/web.rs +56 -8
  260. data/rust/host/src/checkout.rs +78 -32
  261. data/rust/host/src/clock.rs +8 -0
  262. data/rust/host/src/commerce_ir.rs +333 -95
  263. data/rust/host/src/crypto.rs +33 -0
  264. data/rust/host/src/dispatch.rs +1037 -331
  265. data/rust/host/src/expr_json.rs +1021 -273
  266. data/rust/host/src/extension.rs +24 -4
  267. data/rust/host/src/field_hints.rs +11 -7
  268. data/rust/host/src/fuzz_support.rs +85 -13
  269. data/rust/host/src/host.rs +100 -0
  270. data/rust/host/src/http.rs +22 -0
  271. data/rust/host/src/ir.rs +326 -16
  272. data/rust/host/src/journal.rs +277 -176
  273. data/rust/host/src/lambda_client.rs +219 -53
  274. data/rust/host/src/lib.rs +16 -4
  275. data/rust/host/src/log.rs +62 -11
  276. data/rust/host/src/mint.rs +1153 -326
  277. data/rust/host/src/needs.rs +185 -44
  278. data/rust/host/src/payments/keystore.rs +197 -45
  279. data/rust/host/src/payments/roles.rs +39 -7
  280. data/rust/host/src/payments/tests.rs +1427 -282
  281. data/rust/host/src/payments.rs +391 -112
  282. data/rust/host/src/platform_signin/attack_tests.rs +5 -5
  283. data/rust/host/src/platform_signin/config.rs +11 -11
  284. data/rust/host/src/platform_signin/cookie.rs +2 -2
  285. data/rust/host/src/platform_signin/extension.rs +2 -1
  286. data/rust/host/src/platform_signin/handler.rs +9 -8
  287. data/rust/host/src/platform_signin/jti.rs +4 -2
  288. data/rust/host/src/platform_signin/jwks.rs +2 -1
  289. data/rust/host/src/platform_signin/jws.rs +2 -37
  290. data/rust/host/src/platform_signin/known_answer_tests.rs +5 -1
  291. data/rust/host/src/platform_signin/mode_tests.rs +5 -3
  292. data/rust/host/src/platform_signin/pages.rs +20 -20
  293. data/rust/host/src/platform_signin/session.rs +8 -1
  294. data/rust/host/src/platform_signin/settings.rs +6 -6
  295. data/rust/host/src/platform_signin/strict_json.rs +12 -9
  296. data/rust/host/src/platform_signin/verify.rs +41 -41
  297. data/rust/host/src/platform_signin/wiring_support.rs +13 -18
  298. data/rust/host/src/presentation.rs +207 -63
  299. data/rust/host/src/presentation_write.rs +502 -187
  300. data/rust/host/src/query_step.rs +69 -18
  301. data/rust/host/src/rate_limit.rs +541 -142
  302. data/rust/host/src/reference_transform.rs +77 -25
  303. data/rust/host/src/reference_validate.rs +443 -104
  304. data/rust/host/src/resend.rs +190 -55
  305. data/rust/host/src/secrets.rs +116 -21
  306. data/rust/host/src/server.rs +555 -104
  307. data/rust/host/src/storage_shape.rs +88 -21
  308. data/rust/host/src/test_pg.rs +17 -7
  309. data/rust/host/src/ui_schema.rs +495 -207
  310. data/rust/host/src/wasm_runner.rs +21 -6
  311. data/rust/host/src/web/commerce.rs +101 -23
  312. data/rust/host/src/web/newsletter/cooldown.rs +10 -3
  313. data/rust/host/src/web/newsletter/template.rs +26 -7
  314. data/rust/host/src/web/newsletter/tests.rs +112 -23
  315. data/rust/host/src/web/newsletter.rs +209 -59
  316. data/rust/host/src/web/newsletter_send.rs +251 -79
  317. data/rust/host/src/web/registration_receipt/tests.rs +16 -4
  318. data/rust/host/src/web/registration_receipt.rs +100 -24
  319. data/rust/host/src/web/registrations.rs +1384 -332
  320. data/rust/host/src/web/resend_webhook.rs +138 -80
  321. data/rust/host/src/web.rs +2416 -508
  322. data/rust/lsp/Cargo.toml +36 -0
  323. data/rust/lsp/clippy.toml +4 -0
  324. data/rust/lsp/src/json.rs +29 -22
  325. data/rust/lsp/src/main.rs +81 -31
  326. data/rust/lsp/src/outline.rs +23 -7
  327. data/rust/lsp/src/rpc.rs +17 -4
  328. data/rust/parser/Cargo.toml +36 -0
  329. data/rust/parser/clippy.toml +4 -0
  330. data/rust/parser/src/build/closed_sets.rs +6 -1
  331. data/rust/parser/src/build/identity.rs +4 -3
  332. data/rust/parser/src/build/mod.rs +1 -1
  333. data/rust/parser/src/build/named_pattern.rs +4 -1
  334. data/rust/parser/src/build/naming.rs +2 -80
  335. data/rust/parser/src/build/pattern_subset.rs +6 -8
  336. data/rust/parser/src/build/query_derive.rs +3 -1
  337. data/rust/parser/src/build/query_inference.rs +14 -5
  338. data/rust/parser/src/build/read_model.rs +4 -1
  339. data/rust/parser/src/build/references.rs +4 -3
  340. data/rust/parser/src/canonical.rs +34 -15
  341. data/rust/parser/src/emit.rs +187 -105
  342. data/rust/parser/src/expr/ast_json.rs +134 -42
  343. data/rust/parser/src/expr/evaluator.rs +27 -9
  344. data/rust/parser/src/expr/mod.rs +54 -13
  345. data/rust/parser/src/expr/resolver.rs +299 -92
  346. data/rust/parser/src/ir.rs +35 -3
  347. data/rust/parser/src/keywords.rs +7168 -516
  348. data/rust/parser/src/lex.rs +11 -25
  349. data/rust/parser/src/lib.rs +1 -0
  350. data/rust/parser/src/main.rs +65 -6
  351. data/rust/parser/src/parse/adapter.rs +157 -0
  352. data/rust/parser/src/parse/aggregate.rs +19 -10
  353. data/rust/parser/src/parse/chapter.rs +16 -17
  354. data/rust/parser/src/parse/command.rs +35 -16
  355. data/rust/parser/src/parse/domain_port.rs +4 -7
  356. data/rust/parser/src/parse/entity.rs +21 -11
  357. data/rust/parser/src/parse/hecksagon.rs +10 -4
  358. data/rust/parser/src/parse/lifecycle.rs +9 -5
  359. data/rust/parser/src/parse/mod.rs +22 -91
  360. data/rust/parser/src/parse/needs.rs +21 -12
  361. data/rust/parser/src/parse/policy.rs +2 -2
  362. data/rust/parser/src/parse/process_manager.rs +9 -11
  363. data/rust/parser/src/parse/query.rs +8 -4
  364. data/rust/parser/src/parse/read_model.rs +57 -16
  365. data/rust/parser/src/parse/value_object.rs +7 -2
  366. data/rust/parser/src/parse/world.rs +322 -0
  367. data/rust/parser/src/ruby_value.rs +7 -6
  368. data/rust/src/exemplar/json.rs +4 -4
  369. data/rust/src/exemplar/mutations.rs +18 -0
  370. data/rust/src/exemplar/read_models.rs +1 -10
  371. data/rust/src/kernel/attribute_shapes/composite.rs +54 -14
  372. data/rust/src/kernel/attribute_shapes/list.rs +2 -2
  373. data/rust/src/kernel/attribute_shapes/mod.rs +8 -8
  374. data/rust/src/kernel/attribute_shapes/scalar.rs +2 -2
  375. data/rust/src/kernel/cli.rs +244 -114
  376. data/rust/src/kernel/dispatch.rs +323 -115
  377. data/rust/src/kernel/dispatch_guard_tests.rs +327 -65
  378. data/rust/src/kernel/expr.rs +203 -86
  379. data/rust/src/kernel/expression_operators/arithmetic.rs +138 -25
  380. data/rust/src/kernel/expression_operators/comparison.rs +87 -2
  381. data/rust/src/kernel/expression_operators/enumeration.rs +106 -22
  382. data/rust/src/kernel/expression_operators/logical.rs +9 -3
  383. data/rust/src/kernel/expression_operators/membership.rs +3 -1
  384. data/rust/src/kernel/expression_operators/mod.rs +34 -21
  385. data/rust/src/kernel/expression_operators/pattern_match.rs +46 -11
  386. data/rust/src/kernel/expression_operators/positional.rs +84 -30
  387. data/rust/src/kernel/expression_operators/presence.rs +52 -13
  388. data/rust/src/kernel/expression_operators/sign_test.rs +3 -1
  389. data/rust/src/kernel/expression_operators/sized.rs +24 -8
  390. data/rust/src/kernel/expression_operators/text.rs +120 -27
  391. data/rust/src/kernel/expression_operators/to_string.rs +10 -4
  392. data/rust/src/kernel/json.rs +244 -148
  393. data/rust/src/kernel/mod.rs +47 -33
  394. data/rust/src/kernel/named_query.rs +63 -15
  395. data/rust/src/kernel/needs.rs +112 -25
  396. data/rust/src/kernel/orchestrate.rs +980 -581
  397. data/rust/src/kernel/orchestrate_guard_tests.rs +538 -140
  398. data/rust/src/kernel/pattern.rs +107 -34
  399. data/rust/src/kernel/present.rs +261 -0
  400. data/rust/src/kernel/query_comparators.rs +118 -41
  401. data/rust/src/kernel/query_ordering.rs +13 -5
  402. data/rust/src/kernel/read_model.rs +479 -181
  403. data/rust/src/kernel/reference_lookup.rs +122 -26
  404. data/rust/src/kernel/repository.rs +293 -66
  405. data/rust/src/kernel/routing.rs +58 -42
  406. data/rust/src/kernel/ruby_float.rs +139 -0
  407. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +57 -57
  408. data/rust/src/kernel/vocab/entity_dispatch_order.rs +54 -54
  409. data/rust/src/kernel/vocab/field_hint.rs +19 -25
  410. data/rust/src/kernel/vocab/mod.rs +6 -6
  411. data/rust/src/kernel/vocab/query_comparator.rs +23 -24
  412. data/rust/src/kernel/vocab/refusal_template.rs +501 -297
  413. data/rust/src/lib.rs +50 -0
  414. data/rust/src/main.rs +4 -1
  415. data/rust/web/Cargo.toml +36 -0
  416. data/rust/web/clippy.toml +4 -0
  417. metadata +85 -15
  418. data/examples/pizzas/bluebook/hecksagon/mock_stripe_payment_adapter.rb +0 -36
  419. data/lib/hecks/adapters/driven/google_authentication.adapter +0 -3
  420. data/lib/hecks/adapters/driven/google_authentication.rb +0 -99
  421. data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +0 -3
  422. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +0 -32
  423. data/lib/hecks/ports/authentication.port +0 -6
  424. data/lib/hecks/ports/authentication.rb +0 -67
  425. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +0 -255
  426. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +0 -22
  427. data/rust/host/src/approval.rs +0 -652
@@ -27,8 +27,10 @@ pub struct InMemoryRepository<T: Clone> {
27
27
  }
28
28
 
29
29
  impl<T: Clone> InMemoryRepository<T> {
30
- pub fn new() -> Self {
31
- Self { records: BTreeMap::new() }
30
+ pub const fn new() -> Self {
31
+ Self {
32
+ records: BTreeMap::new(),
33
+ }
32
34
  }
33
35
 
34
36
  /// `(id, record)` pairs; `Repository::all` drops the id, which the CLI `instances` dump needs.
@@ -46,7 +48,9 @@ impl<T: Clone> InMemoryRepository<T> {
46
48
  &'a self,
47
49
  to_json: impl Fn(&T) -> super::Json + 'a,
48
50
  ) -> impl Iterator<Item = (&'a String, &'a super::Json)> {
49
- self.records.iter().map(move |(id, slot)| (id, slot.json.get_or_init(|| to_json(&slot.record))))
51
+ self.records
52
+ .iter()
53
+ .map(move |(id, slot)| (id, slot.json.get_or_init(|| to_json(&slot.record))))
50
54
  }
51
55
  }
52
56
 
@@ -56,11 +60,20 @@ impl<T: Clone> Repository<T> for InMemoryRepository<T> {
56
60
  }
57
61
 
58
62
  fn save(&mut self, id: &str, record: T) {
59
- self.records.insert(id.to_string(), Slot { record, json: OnceCell::new() });
63
+ self.records.insert(
64
+ id.to_string(),
65
+ Slot {
66
+ record,
67
+ json: OnceCell::new(),
68
+ },
69
+ );
60
70
  }
61
71
 
62
72
  fn all(&self) -> Vec<T> {
63
- self.records.values().map(|slot| slot.record.clone()).collect()
73
+ self.records
74
+ .values()
75
+ .map(|slot| slot.record.clone())
76
+ .collect()
64
77
  }
65
78
 
66
79
  fn count(&self) -> usize {
@@ -83,7 +96,12 @@ pub fn check_reference<T: Clone>(
83
96
  }
84
97
  // Wording goes through `RefusalSite` so it cannot drift from the Ruby table.
85
98
  Err(super::Refusal::NotFound(
86
- super::refusal_wording::NotFoundReferenceTargetMissingArgs { target, heads, key: value }.render_args(),
99
+ super::refusal_wording::NotFoundReferenceTargetMissingArgs {
100
+ target,
101
+ heads,
102
+ key: value,
103
+ }
104
+ .render_args(),
87
105
  ))
88
106
  }
89
107
 
@@ -104,7 +122,9 @@ pub trait AggregateScan {
104
122
  /// `scan`, so a store that overrides only `scan` still answers; generated stores override
105
123
  /// this to read their repositories' cached renderings.
106
124
  fn scan_each(&self, aggregate: &str, visit: &mut dyn FnMut(&str, &super::Json)) -> bool {
107
- let Some(entries) = self.scan(aggregate) else { return false };
125
+ let Some(entries) = self.scan(aggregate) else {
126
+ return false;
127
+ };
108
128
  for (id, record) in &entries {
109
129
  visit(id, record);
110
130
  }
@@ -168,7 +188,9 @@ pub fn entry_matches(
168
188
  ///
169
189
  /// Shared because read models nest this wrapping at every level, not only the top row.
170
190
  pub fn row_json(id: String, record: super::Json) -> super::Json {
171
- let super::Json::Object(mut fields) = record else { return record };
191
+ let super::Json::Object(mut fields) = record else {
192
+ return record;
193
+ };
172
194
  fields.insert(0, ("id".to_string(), super::Json::Str(id)));
173
195
  super::Json::Object(fields)
174
196
  }
@@ -187,10 +209,16 @@ pub fn holds_role_via(
187
209
  let Some(def) = assignments.and_then(|verb| super::named_query::find(queries, verb)) else {
188
210
  return false;
189
211
  };
190
- let args = super::Json::Object(vec![("actor_id".to_string(), super::Json::Str(actor_id.to_string()))]);
191
- let Ok(rows) = super::named_query::run(store, def, &args, None) else { return false };
212
+ let args = super::Json::Object(vec![(
213
+ "actor_id".to_string(),
214
+ super::Json::Str(actor_id.to_string()),
215
+ )]);
216
+ let Ok(rows) = super::named_query::run(store, def, &args, None) else {
217
+ return false;
218
+ };
192
219
  rows.iter().any(|(_, record)| {
193
- let matches_role = record.dig("role_name.value").and_then(super::Json::as_str) == Some(role);
220
+ let matches_role =
221
+ record.dig("role_name.value").and_then(super::Json::as_str) == Some(role);
194
222
  let not_revoked = matches!(record.dig("ends_at"), None | Some(super::Json::Null));
195
223
  matches_role && not_revoked
196
224
  })
@@ -214,7 +242,9 @@ pub fn check_role_via(
214
242
  queries: &[super::QueryDef],
215
243
  assignments: Option<&str>,
216
244
  ) -> Result<(), super::Refusal> {
217
- let attached = assignments.and_then(|verb| super::named_query::find(queries, verb)).is_some();
245
+ let attached = assignments
246
+ .and_then(|verb| super::named_query::find(queries, verb))
247
+ .is_some();
218
248
  // An identified caller with no stated role is checked against what Governance assigned them: the
219
249
  // caller need not claim a role it may not hold. Unchecked without a Governance provider, as ever.
220
250
  if let (None, Some(role), Some(actor_id)) = (caller_role, command_role, caller_actor_id) {
@@ -222,10 +252,17 @@ pub fn check_role_via(
222
252
  return Ok(());
223
253
  }
224
254
  return Err(super::Refusal::Unauthorized(
225
- super::refusal_wording::UnauthorizedRoleMismatchArgs { command: command_name, role, caller_role: "no assigned role" }.render_args(),
255
+ super::refusal_wording::UnauthorizedRoleMismatchArgs {
256
+ command: command_name,
257
+ role,
258
+ caller_role: "no assigned role",
259
+ }
260
+ .render_args(),
226
261
  ));
227
262
  }
228
- let (Some(caller), Some(role)) = (caller_role, command_role) else { return Ok(()) };
263
+ let (Some(caller), Some(role)) = (caller_role, command_role) else {
264
+ return Ok(());
265
+ };
229
266
 
230
267
  let authorized = match caller_actor_id {
231
268
  Some(actor_id) if attached => holds_role_via(store, queries, assignments, actor_id, role),
@@ -237,7 +274,12 @@ pub fn check_role_via(
237
274
  }
238
275
  // The refusal quotes the role the caller typed, not the one `holds_role_via` found (as Ruby).
239
276
  Err(super::Refusal::Unauthorized(
240
- super::refusal_wording::UnauthorizedRoleMismatchArgs { command: command_name, role, caller_role: caller }.render_args(),
277
+ super::refusal_wording::UnauthorizedRoleMismatchArgs {
278
+ command: command_name,
279
+ role,
280
+ caller_role: caller,
281
+ }
282
+ .render_args(),
241
283
  ))
242
284
  }
243
285
 
@@ -257,12 +299,18 @@ mod filter_entries_none_in_state_tests {
257
299
  impl AggregateScan for FakeStore {
258
300
  fn scan(&self, aggregate: &str) -> Option<Vec<(String, Json)>> {
259
301
  let bare = aggregate.strip_prefix(&format!("{}::", self.domain))?;
260
- self.aggregates.iter().find(|(name, _)| *name == bare).map(|(_, entries)| entries.clone())
302
+ self.aggregates
303
+ .iter()
304
+ .find(|(name, _)| *name == bare)
305
+ .map(|(_, entries)| entries.clone())
261
306
  }
262
307
  }
263
308
 
264
309
  fn claim_record(state: &str) -> Json {
265
- Json::obj(vec![("state", Json::obj(vec![("value", Json::str(state))]))])
310
+ Json::obj(vec![(
311
+ "state",
312
+ Json::obj(vec![("value", Json::str(state))]),
313
+ )])
266
314
  }
267
315
 
268
316
  fn assignment_record(claim_id: &str) -> Json {
@@ -298,8 +346,10 @@ mod filter_entries_none_in_state_tests {
298
346
  &cross_domain,
299
347
  );
300
348
 
301
- let claim_ids: Vec<&str> =
302
- matched.iter().map(|(_, record)| record.dig("claim_id").and_then(Json::as_str).unwrap()).collect();
349
+ let claim_ids: Vec<&str> = matched
350
+ .iter()
351
+ .map(|(_, record)| record.dig("claim_id").and_then(Json::as_str).unwrap())
352
+ .collect();
303
353
  assert_eq!(claim_ids, vec!["c2", "nonexistent"]);
304
354
  }
305
355
  }
@@ -321,7 +371,15 @@ mod check_role_actor_id_tests {
321
371
  store: &impl AggregateScan,
322
372
  queries: &[QueryDef],
323
373
  ) -> Result<(), Refusal> {
324
- check_role_via(command_role, command_name, caller_role, caller_actor_id, store, queries, Some(GOVERNANCE_ASSIGNMENTS))
374
+ check_role_via(
375
+ command_role,
376
+ command_name,
377
+ caller_role,
378
+ caller_actor_id,
379
+ store,
380
+ queries,
381
+ Some(GOVERNANCE_ASSIGNMENTS),
382
+ )
325
383
  }
326
384
 
327
385
  /// Mirrors the merged `Store` compiled for a domain using Governance: one `RoleAssignment`
@@ -345,7 +403,11 @@ mod check_role_actor_id_tests {
345
403
  QueryDef {
346
404
  verb: "Governance::RoleAssignment.AssignmentsForActor",
347
405
  aggregate: "Governance::RoleAssignment",
348
- conditions: &[QueryCondition { field: "actor_id", comparator: QueryComparator::Eq, value: QueryConditionValue::Arg("actor_id") }],
406
+ conditions: &[QueryCondition {
407
+ field: "actor_id",
408
+ comparator: QueryComparator::Eq,
409
+ value: QueryConditionValue::Arg("actor_id"),
410
+ }],
349
411
  reference_hop_conditions: &[],
350
412
  order_by: None,
351
413
  offset: None,
@@ -356,65 +418,134 @@ mod check_role_actor_id_tests {
356
418
 
357
419
  fn role_assignment(actor_id: &str, role_name: &str, ends_at: Option<&str>) -> Json {
358
420
  Json::obj(vec![
359
- ("actor_id", Json::obj(vec![("value", Json::str(actor_id.to_string()))])),
360
- ("role_name", Json::obj(vec![("value", Json::str(role_name.to_string()))])),
361
- ("scope", Json::obj(vec![("value", Json::str("kitchen".to_string()))])),
362
- ("starts_at", Json::obj(vec![("value", Json::str("2026-01-01".to_string()))])),
363
- ("ends_at", match ends_at {
364
- Some(ts) => Json::obj(vec![("value", Json::str(ts.to_string()))]),
365
- None => Json::Null,
366
- }),
421
+ (
422
+ "actor_id",
423
+ Json::obj(vec![("value", Json::str(actor_id.to_string()))]),
424
+ ),
425
+ (
426
+ "role_name",
427
+ Json::obj(vec![("value", Json::str(role_name.to_string()))]),
428
+ ),
429
+ (
430
+ "scope",
431
+ Json::obj(vec![("value", Json::str("kitchen".to_string()))]),
432
+ ),
433
+ (
434
+ "starts_at",
435
+ Json::obj(vec![("value", Json::str("2026-01-01".to_string()))]),
436
+ ),
437
+ (
438
+ "ends_at",
439
+ ends_at.map_or(Json::Null, |ts| {
440
+ Json::obj(vec![("value", Json::str(ts.to_string()))])
441
+ }),
442
+ ),
367
443
  ])
368
444
  }
369
445
 
370
446
  // An identified actor with no grant is refused even when the typed role matches.
371
447
  #[test]
372
448
  fn refuses_an_identified_caller_with_no_matching_grant_even_though_the_typed_role_matches() {
373
- let store = FakeStore { role_assignments: vec![] };
449
+ let store = FakeStore {
450
+ role_assignments: vec![],
451
+ };
374
452
  let queries = [assignments_for_actor_query()];
375
453
 
376
- let result = check_role(Some("Chef"), "Prepare", Some("Chef"), Some("attacker"), &store, &queries);
454
+ let result = check_role(
455
+ Some("Chef"),
456
+ "Prepare",
457
+ Some("Chef"),
458
+ Some("attacker"),
459
+ &store,
460
+ &queries,
461
+ );
377
462
 
378
- assert!(result.is_err(), "an actor with no real grant must be refused even though it typed the right role");
463
+ assert!(
464
+ result.is_err(),
465
+ "an actor with no real grant must be refused even though it typed the right role"
466
+ );
379
467
  }
380
468
 
381
469
  // A live (non-revoked) matching grant is accepted.
382
470
  #[test]
383
471
  fn accepts_an_identified_caller_with_a_live_matching_grant() {
384
- let store = FakeStore { role_assignments: vec![("ra1".to_string(), role_assignment("u1", "Chef", None))] };
472
+ let store = FakeStore {
473
+ role_assignments: vec![("ra1".to_string(), role_assignment("u1", "Chef", None))],
474
+ };
385
475
  let queries = [assignments_for_actor_query()];
386
476
 
387
- let result = check_role(Some("Chef"), "Prepare", Some("Chef"), Some("u1"), &store, &queries);
477
+ let result = check_role(
478
+ Some("Chef"),
479
+ "Prepare",
480
+ Some("Chef"),
481
+ Some("u1"),
482
+ &store,
483
+ &queries,
484
+ );
388
485
 
389
- assert!(result.is_ok(), "a live matching grant must be accepted: {result:?}");
486
+ assert!(
487
+ result.is_ok(),
488
+ "a live matching grant must be accepted: {result:?}"
489
+ );
390
490
  }
391
491
 
392
492
  // A revoked grant (`ends_at` set) is refused.
393
493
  #[test]
394
494
  fn refuses_a_revoked_grant() {
395
- let store = FakeStore { role_assignments: vec![("ra1".to_string(), role_assignment("u1", "Chef", Some("2026-06-01")))] };
495
+ let store = FakeStore {
496
+ role_assignments: vec![(
497
+ "ra1".to_string(),
498
+ role_assignment("u1", "Chef", Some("2026-06-01")),
499
+ )],
500
+ };
396
501
  let queries = [assignments_for_actor_query()];
397
502
 
398
- let result = check_role(Some("Chef"), "Prepare", Some("Chef"), Some("u1"), &store, &queries);
503
+ let result = check_role(
504
+ Some("Chef"),
505
+ "Prepare",
506
+ Some("Chef"),
507
+ Some("u1"),
508
+ &store,
509
+ &queries,
510
+ );
399
511
 
400
- assert!(result.is_err(), "a revoked grant must not authorize: {result:?}");
512
+ assert!(
513
+ result.is_err(),
514
+ "a revoked grant must not authorize: {result:?}"
515
+ );
401
516
  }
402
517
 
403
518
  // An identified caller that states no role is held to what Governance assigned them.
404
519
  #[test]
405
520
  fn an_identified_caller_with_no_stated_role_needs_a_live_assignment_of_the_commands_role() {
406
- let store = FakeStore { role_assignments: vec![("ra1".to_string(), role_assignment("u1", "Chef", None))] };
521
+ let store = FakeStore {
522
+ role_assignments: vec![("ra1".to_string(), role_assignment("u1", "Chef", None))],
523
+ };
407
524
  let queries = [assignments_for_actor_query()];
408
525
 
409
- assert!(check_role(Some("Chef"), "Prepare", None, Some("u1"), &store, &queries).is_ok(), "an assigned actor needs no stated role");
410
- assert!(check_role(Some("Waiter"), "Serve", None, Some("u1"), &store, &queries).is_err(), "an actor not assigned the role is refused");
411
- assert!(check_role(Some("Chef"), "Prepare", None, Some("u2"), &store, &queries).is_err(), "an unassigned actor is refused");
526
+ assert!(
527
+ check_role(Some("Chef"), "Prepare", None, Some("u1"), &store, &queries).is_ok(),
528
+ "an assigned actor needs no stated role"
529
+ );
530
+ assert!(
531
+ check_role(Some("Waiter"), "Serve", None, Some("u1"), &store, &queries).is_err(),
532
+ "an actor not assigned the role is refused"
533
+ );
534
+ assert!(
535
+ check_role(Some("Chef"), "Prepare", None, Some("u2"), &store, &queries).is_err(),
536
+ "an unassigned actor is refused"
537
+ );
412
538
  }
413
539
 
414
540
  // Revoked assignments do not count for a caller with no stated role either.
415
541
  #[test]
416
542
  fn an_identified_caller_with_no_stated_role_is_refused_on_a_revoked_assignment() {
417
- let store = FakeStore { role_assignments: vec![("ra1".to_string(), role_assignment("u1", "Chef", Some("2026-06-01")))] };
543
+ let store = FakeStore {
544
+ role_assignments: vec![(
545
+ "ra1".to_string(),
546
+ role_assignment("u1", "Chef", Some("2026-06-01")),
547
+ )],
548
+ };
418
549
  let queries = [assignments_for_actor_query()];
419
550
 
420
551
  assert!(check_role(Some("Chef"), "Prepare", None, Some("u1"), &store, &queries).is_err());
@@ -423,64 +554,149 @@ mod check_role_actor_id_tests {
423
554
  // No Governance provider attached: an actor with no stated role stays unchecked, as before.
424
555
  #[test]
425
556
  fn an_identified_caller_with_no_stated_role_is_unchecked_when_governance_is_not_attached() {
426
- let store = FakeStore { role_assignments: vec![] };
557
+ let store = FakeStore {
558
+ role_assignments: vec![],
559
+ };
427
560
 
428
561
  let result = check_role_via(Some("Chef"), "Prepare", None, Some("u1"), &store, &[], None);
429
562
 
430
- assert!(result.is_ok(), "no provider, nothing to check against: {result:?}");
563
+ assert!(
564
+ result.is_ok(),
565
+ "no provider, nothing to check against: {result:?}"
566
+ );
431
567
  }
432
568
 
433
569
  // A caller with only `role:` keeps the plain string comparison, whatever grants exist.
434
570
  #[test]
435
571
  fn a_bare_role_only_caller_is_unaffected_by_any_real_grant_data() {
436
- let store = FakeStore { role_assignments: vec![("ra1".to_string(), role_assignment("u1", "Customer", None))] };
572
+ let store = FakeStore {
573
+ role_assignments: vec![("ra1".to_string(), role_assignment("u1", "Customer", None))],
574
+ };
437
575
  let queries = [assignments_for_actor_query()];
438
576
 
439
577
  // Matching string, no actor_id: accepted.
440
- let matches = check_role(Some("Chef"), "Prepare", Some("Chef"), None, &store, &queries);
441
- assert!(matches.is_ok(), "a bare matching role string must still be accepted unchanged: {matches:?}");
578
+ let matches = check_role(
579
+ Some("Chef"),
580
+ "Prepare",
581
+ Some("Chef"),
582
+ None,
583
+ &store,
584
+ &queries,
585
+ );
586
+ assert!(
587
+ matches.is_ok(),
588
+ "a bare matching role string must still be accepted unchanged: {matches:?}"
589
+ );
442
590
 
443
591
  // Mismatched string, no actor_id: refused.
444
- let mismatches = check_role(Some("Chef"), "Prepare", Some("Customer"), None, &store, &queries);
445
- assert!(mismatches.is_err(), "a bare mismatched role string must still refuse unchanged");
592
+ let mismatches = check_role(
593
+ Some("Chef"),
594
+ "Prepare",
595
+ Some("Customer"),
596
+ None,
597
+ &store,
598
+ &queries,
599
+ );
600
+ assert!(
601
+ mismatches.is_err(),
602
+ "a bare mismatched role string must still refuse unchanged"
603
+ );
446
604
  }
447
605
 
448
606
  // Without an `AssignmentsForActor` query, a bound `actor_id` falls back to the string check.
449
607
  #[test]
450
608
  fn falls_back_to_the_string_check_when_this_domain_never_attached_governance() {
451
- let store = FakeStore { role_assignments: vec![] };
609
+ let store = FakeStore {
610
+ role_assignments: vec![],
611
+ };
452
612
  let queries: [QueryDef; 0] = [];
453
613
 
454
- let matches = check_role(Some("Chef"), "Prepare", Some("Chef"), Some("whoever"), &store, &queries);
614
+ let matches = check_role(
615
+ Some("Chef"),
616
+ "Prepare",
617
+ Some("Chef"),
618
+ Some("whoever"),
619
+ &store,
620
+ &queries,
621
+ );
455
622
  assert!(matches.is_ok(), "no AssignmentsForActor query compiled in -> string fallback, matching role -> accepted: {matches:?}");
456
623
 
457
- let mismatches = check_role(Some("Chef"), "Prepare", Some("Customer"), Some("whoever"), &store, &queries);
624
+ let mismatches = check_role(
625
+ Some("Chef"),
626
+ "Prepare",
627
+ Some("Customer"),
628
+ Some("whoever"),
629
+ &store,
630
+ &queries,
631
+ );
458
632
  assert!(mismatches.is_err(), "no AssignmentsForActor query compiled in -> string fallback, mismatched role -> refused");
459
633
  }
460
634
 
461
635
  // The declared provider verb is honoured, not the Governance query name...
462
636
  #[test]
463
637
  fn check_role_via_reads_whichever_assignments_query_the_chapter_declared() {
464
- let declared = QueryDef { verb: "Access::Grant.HeldBy", ..assignments_for_actor_query() };
638
+ let declared = QueryDef {
639
+ verb: "Access::Grant.HeldBy",
640
+ ..assignments_for_actor_query()
641
+ };
465
642
  let queries = [declared];
466
643
 
467
- let granted = FakeStore { role_assignments: vec![("ra1".to_string(), role_assignment("u1", "Chef", None))] };
468
- let accepted = check_role_via(Some("Chef"), "Prepare", Some("Chef"), Some("u1"), &granted, &queries, Some("Access::Grant.HeldBy"));
469
- assert!(accepted.is_ok(), "a live grant under the declared query must be accepted: {accepted:?}");
644
+ let granted = FakeStore {
645
+ role_assignments: vec![("ra1".to_string(), role_assignment("u1", "Chef", None))],
646
+ };
647
+ let accepted = check_role_via(
648
+ Some("Chef"),
649
+ "Prepare",
650
+ Some("Chef"),
651
+ Some("u1"),
652
+ &granted,
653
+ &queries,
654
+ Some("Access::Grant.HeldBy"),
655
+ );
656
+ assert!(
657
+ accepted.is_ok(),
658
+ "a live grant under the declared query must be accepted: {accepted:?}"
659
+ );
470
660
 
471
- let ungranted = FakeStore { role_assignments: vec![] };
472
- let refused = check_role_via(Some("Chef"), "Prepare", Some("Chef"), Some("u1"), &ungranted, &queries, Some("Access::Grant.HeldBy"));
473
- assert!(refused.is_err(), "no grant under the declared query must refuse even though the typed role matches");
661
+ let ungranted = FakeStore {
662
+ role_assignments: vec![],
663
+ };
664
+ let refused = check_role_via(
665
+ Some("Chef"),
666
+ "Prepare",
667
+ Some("Chef"),
668
+ Some("u1"),
669
+ &ungranted,
670
+ &queries,
671
+ Some("Access::Grant.HeldBy"),
672
+ );
673
+ assert!(
674
+ refused.is_err(),
675
+ "no grant under the declared query must refuse even though the typed role matches"
676
+ );
474
677
  }
475
678
 
476
679
  // ...and with no declared provider, a Governance-named query is ignored.
477
680
  #[test]
478
681
  fn check_role_via_ignores_a_governance_named_query_nobody_declared() {
479
- let store = FakeStore { role_assignments: vec![] };
682
+ let store = FakeStore {
683
+ role_assignments: vec![],
684
+ };
480
685
  let queries = [assignments_for_actor_query()];
481
686
 
482
- let result = check_role_via(Some("Chef"), "Prepare", Some("Chef"), Some("whoever"), &store, &queries, None);
483
- assert!(result.is_ok(), "no declared provider -> string fallback, matching role -> accepted: {result:?}");
687
+ let result = check_role_via(
688
+ Some("Chef"),
689
+ "Prepare",
690
+ Some("Chef"),
691
+ Some("whoever"),
692
+ &store,
693
+ &queries,
694
+ None,
695
+ );
696
+ assert!(
697
+ result.is_ok(),
698
+ "no declared provider -> string fallback, matching role -> accepted: {result:?}"
699
+ );
484
700
  }
485
701
  }
486
702
 
@@ -489,6 +705,8 @@ mod json_cache_tests {
489
705
  use super::{InMemoryRepository, Repository};
490
706
  use crate::kernel::Json;
491
707
 
708
+ // reason: `json_entries` takes `fn(&T) -> Json`, so the signature is fixed by the caller.
709
+ #[allow(clippy::trivially_copy_pass_by_ref)]
492
710
  fn render(n: &i64) -> Json {
493
711
  Json::obj(vec![("n", Json::int(*n))])
494
712
  }
@@ -499,11 +717,20 @@ mod json_cache_tests {
499
717
  let mut repo: InMemoryRepository<i64> = InMemoryRepository::new();
500
718
  repo.save("b", 2);
501
719
  repo.save("a", 1);
502
- let first: Vec<(String, Json)> = repo.json_entries(render).map(|(id, json)| (id.clone(), json.clone())).collect();
503
- assert_eq!(first, vec![("a".to_string(), render(&1)), ("b".to_string(), render(&2))]);
720
+ let first: Vec<(String, Json)> = repo
721
+ .json_entries(render)
722
+ .map(|(id, json)| (id.clone(), json.clone()))
723
+ .collect();
724
+ assert_eq!(
725
+ first,
726
+ vec![("a".to_string(), render(&1)), ("b".to_string(), render(&2))]
727
+ );
504
728
 
505
729
  repo.save("a", 10);
506
- let second: Vec<Json> = repo.json_entries(render).map(|(_, json)| json.clone()).collect();
730
+ let second: Vec<Json> = repo
731
+ .json_entries(render)
732
+ .map(|(_, json)| json.clone())
733
+ .collect();
507
734
  assert_eq!(second, vec![render(&10), render(&2)]);
508
735
  }
509
736
  }