hecks 3.14.0 → 4.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 (419) 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/lib/hecks/adapters/driven/prism.rb +0 -2
  9. data/lib/hecks/adapters/driven.rb +0 -2
  10. data/lib/hecks/adapters/driving/json.rb +1 -7
  11. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -2
  12. data/lib/hecks/bluebook/dsl/derivation_words.rb +58 -0
  13. data/lib/hecks/bluebook/dsl/translation_builder.rb +6 -37
  14. data/lib/hecks/bluebook/expression/ast_json.rb +0 -9
  15. data/lib/hecks/bluebook/hecksagon.rb +1 -1
  16. data/lib/hecks/bluebook/meta_validator/translation_judge/rules.rb +7 -10
  17. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +4 -8
  18. data/lib/hecks/bluebook/meta_validator.rb +0 -11
  19. data/lib/hecks/bluebook/translation.rb +18 -10
  20. data/lib/hecks/bluebook/translation_expression/evaluator.rb +63 -0
  21. data/lib/hecks/bluebook/translation_expression/parser.rb +127 -0
  22. data/lib/hecks/bluebook/translation_expression.rb +39 -0
  23. data/lib/hecks/bluebook/translation_plan.rb +70 -0
  24. data/lib/hecks/bluebook.rb +2 -0
  25. data/lib/hecks/chapters.rb +3 -3
  26. data/lib/hecks/composer/adapters/claude_composer.adapter +3 -0
  27. data/lib/hecks/composer/adapters/claude_composer.rb +83 -0
  28. data/lib/hecks/composer/adapters/fake_agent.adapter +3 -0
  29. data/lib/hecks/composer/adapters/fake_agent.rb +55 -0
  30. data/lib/hecks/composer/adapters/jev.adapter +3 -0
  31. data/lib/hecks/composer/adapters/jev.rb +66 -0
  32. data/lib/hecks/composer/agent.rb +113 -0
  33. data/lib/hecks/composer/assistant.rb +110 -0
  34. data/lib/hecks/composer/behavior_run.rb +53 -0
  35. data/lib/hecks/composer/bluebook/composer.bluebook +178 -0
  36. data/lib/hecks/composer/bluebook/composer.hecksagon +16 -0
  37. data/lib/hecks/composer/board.rb +65 -0
  38. data/lib/hecks/composer/board_edit/command_text.rb +39 -0
  39. data/lib/hecks/composer/board_edit/source.rb +84 -0
  40. data/lib/hecks/composer/board_edit.rb +144 -0
  41. data/lib/hecks/composer/check.rb +89 -0
  42. data/lib/hecks/composer/domains.rb +81 -0
  43. data/lib/hecks/composer/jobs.rb +134 -0
  44. data/lib/hecks/composer/layout.rb +37 -0
  45. data/lib/hecks/composer/live.rb +150 -0
  46. data/lib/hecks/composer/outline.rb +48 -0
  47. data/lib/hecks/composer/page.html +989 -0
  48. data/lib/hecks/composer/panels.rb +142 -0
  49. data/lib/hecks/composer/parse.rb +83 -0
  50. data/lib/hecks/composer/plain.rb +28 -0
  51. data/lib/hecks/composer/ports/composer_agent.port +7 -0
  52. data/lib/hecks/composer/session.rb +141 -0
  53. data/lib/hecks/composer/toolbox.rb +166 -0
  54. data/lib/hecks/composer/web.rb +156 -0
  55. data/lib/hecks/composer/workbench.rb +51 -0
  56. data/lib/hecks/composer/workspace.rb +81 -0
  57. data/lib/hecks/composer.rb +17 -0
  58. data/lib/hecks/corpus/adapters.rb +22 -0
  59. data/lib/hecks/corpus/rust.rb +1 -1
  60. data/lib/hecks/corpus/worlds.rb +19 -0
  61. data/lib/hecks/corpus.rb +5 -0
  62. data/lib/hecks/forms/app/commands.rb +2 -3
  63. data/lib/hecks/forms/app/queries.rb +2 -3
  64. data/lib/hecks/forms/app/records.rb +17 -5
  65. data/lib/hecks/forms/command_form_renderer.rb +42 -16
  66. data/lib/hecks/forms/field_controls.rb +2 -2
  67. data/lib/hecks/forms/field_renderer.rb +1 -1
  68. data/lib/hecks/forms/field_shape.rb +36 -17
  69. data/lib/hecks/forms/index_renderer.rb +2 -1
  70. data/lib/hecks/forms/page.rb +1 -1
  71. data/lib/hecks/forms/params.rb +0 -13
  72. data/lib/hecks/forms/query_form_renderer.rb +7 -6
  73. data/lib/hecks/forms/record_renderer.rb +11 -7
  74. data/lib/hecks/forms/rule_words.rb +100 -0
  75. data/lib/hecks/forms/value_object_shape.rb +2 -1
  76. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -3
  77. data/lib/hecks/fuzzing/isolated_boot/worlds.rb +3 -6
  78. data/lib/hecks/fuzzing/replay/filters.rb +2 -6
  79. data/lib/hecks/gate/stages.yml +25 -0
  80. data/lib/hecks/grammar/evolve/seeds.rb +0 -6
  81. data/lib/hecks/grammar/evolve.rb +0 -3
  82. data/lib/hecks/grammar/translation.bluebook +1 -1
  83. data/lib/hecks/hecks/adapters/codebase/composing.rb +56 -0
  84. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +3 -1
  85. data/lib/hecks/hecks/adapters/journal_store/changes.rb +0 -26
  86. data/lib/hecks/hecks/adapters/journal_store/examination.rb +2 -23
  87. data/lib/hecks/hecks/adapters/journal_store/readings.rb +3 -11
  88. data/lib/hecks/hecks/adapters/journal_store.rb +0 -11
  89. data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +4 -11
  90. data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +0 -9
  91. data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +0 -4
  92. data/lib/hecks/hecks/codebase.behaviors +6 -0
  93. data/lib/hecks/hecks/codebase.bluebook +21 -0
  94. data/lib/hecks/hecks/composer.behaviors +68 -0
  95. data/lib/hecks/hecks/context_map.hecksagon +1 -1
  96. data/lib/hecks/hecks/custodian.behaviors +0 -6
  97. data/lib/hecks/hecks/custodian.bluebook +3 -80
  98. data/lib/hecks/hecks/hecks.hecksagon +2 -2
  99. data/lib/hecks/hecks/hecks.world +1 -1
  100. data/lib/hecks/hecks/rehearsal.bluebook +7 -13
  101. data/lib/hecks/kernel_capabilities/templates.rb +4 -4
  102. data/lib/hecks/kernel_capabilities.rb +1 -1
  103. data/lib/hecks/language/bluebook/vocabulary.bluebook +2 -3
  104. data/lib/hecks/language/translation/translation_aggregate.bluebook +21 -24
  105. data/lib/hecks/ports/access_control.rb +2 -2
  106. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +3 -41
  107. data/lib/hecks/ports/persistence/plugins/era/lineage/coverage.rb +7 -7
  108. data/lib/hecks/ports/persistence/plugins/era/lineage/rewrite.rb +25 -2
  109. data/lib/hecks/ports/persistence/plugins/era/lineage/rules.rb +4 -4
  110. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +17 -25
  111. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/base_sql.rb +0 -12
  112. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/domain_rename.rb +1 -1
  113. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +2 -22
  114. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +0 -1
  115. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +9 -3
  116. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +3 -29
  117. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/refusals.rb +2 -26
  118. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -33
  119. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -3
  120. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +2 -4
  121. data/lib/hecks/ports/persistence/plugins/era/translation/expression_sql.rb +52 -0
  122. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +23 -29
  123. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +1 -1
  124. data/lib/hecks/ports/persistence/plugins/era/translation.rb +0 -1
  125. data/lib/hecks/ports/persistence/plugins/era.rb +3 -6
  126. data/lib/hecks/ports.rb +0 -1
  127. data/lib/hecks/projection_files.rb +3 -1
  128. data/lib/hecks/projections/deploy/templates/lambda/nat_comment_oauth.tmpl +1 -1
  129. data/lib/hecks/projections/glossary/entries.rb +10 -4
  130. data/lib/hecks/projections/glossary/groups.rb +42 -0
  131. data/lib/hecks/projections/glossary/html/page.rb +7 -2
  132. data/lib/hecks/projections/glossary/html/parser.rb +1 -1
  133. data/lib/hecks/projections/glossary/html/sections.rb +31 -11
  134. data/lib/hecks/projections/glossary/html.rb +1 -2
  135. data/lib/hecks/projections/glossary/markdown.rb +31 -28
  136. data/lib/hecks/projections/glossary/mermaid.rb +3 -0
  137. data/lib/hecks/projections/glossary/page.css +143 -153
  138. data/lib/hecks/projections/glossary/page.js +63 -3
  139. data/lib/hecks/projections/glossary/sentences/actions.rb +88 -0
  140. data/lib/hecks/projections/glossary/sentences/paths.rb +77 -0
  141. data/lib/hecks/projections/glossary/sentences/queries.rb +90 -0
  142. data/lib/hecks/projections/glossary/sentences/value_objects.rb +32 -5
  143. data/lib/hecks/projections/glossary/sentences.rb +13 -3
  144. data/lib/hecks/projections/glossary/slugs.rb +4 -1
  145. data/lib/hecks/projections/glossary.rb +2 -1
  146. data/lib/hecks/projections/plain_english.rb +97 -0
  147. data/lib/hecks/projections/rust_vocabulary/arguments.rb +2 -2
  148. data/lib/hecks/projections/rust_vocabulary/enum_body.rb +20 -25
  149. data/lib/hecks/projections/rust_vocabulary/tests.rb +2 -2
  150. data/lib/hecks/projections/rust_vocabulary.rb +14 -6
  151. data/lib/hecks/projections/site/cms_editor/color.rb +0 -6
  152. data/lib/hecks/projections/statements/actions.rb +99 -0
  153. data/lib/hecks/projections/statements/relationships.rb +86 -0
  154. data/lib/hecks/projections/statements.rb +15 -24
  155. data/lib/hecks/projector/exporter/adapters.rb +112 -0
  156. data/lib/hecks/projector/exporter/translations.rb +18 -35
  157. data/lib/hecks/projector/exporter/worlds.rb +103 -0
  158. data/lib/hecks/projector/exporter.rb +5 -1
  159. data/lib/hecks/quality_control/adapters/agent.rb +6 -7
  160. data/lib/hecks/quality_control/adapters/agent_profile.rb +16 -114
  161. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +1 -1
  162. data/lib/hecks/runtime/loader/boot_steps.rb +0 -20
  163. data/lib/hecks/runtime/outbox.rb +2 -9
  164. data/lib/hecks/runtime/port_operation_interpreter.rb +1 -9
  165. data/lib/hecks/runtime/remote_dispatcher.rb +2 -2
  166. data/lib/hecks/runtime/value.rb +13 -0
  167. data/lib/hecks/rust_build/project_rust/target_ir.rb +23 -4
  168. data/lib/hecks/rust_build/wasm.rb +0 -12
  169. data/lib/hecks/rust_build/write_if_changed.rb +12 -12
  170. data/lib/hecks/rust_format.rb +77 -0
  171. data/lib/hecks/tools/fuzz_sweep/findings.rb +3 -9
  172. data/lib/hecks/tools/fuzz_sweep.rb +1 -3
  173. data/lib/hecks/tools/translation_audit/verdicts.rb +3 -28
  174. data/lib/hecks/tools/translation_audit.rb +8 -10
  175. data/lib/hecks/tools/translation_scaffold/reporting.rb +1 -1
  176. data/lib/hecks/version.rb +1 -1
  177. data/lib/hecks/vocabulary.rb +2 -1
  178. data/lib/hecks.rb +3 -0
  179. data/prebuilt/{a883a7e568667aa5ac904c82daff356bdf9e996252a36af6def4a4a43efb967e.marshal → 02c09c73b272c1fb5a9ae6d87bb5e5e529208e00f6b4b597dc2c0fd70a9c0964.marshal} +0 -0
  180. data/prebuilt/{1a6f596be124228be93eba1d86dbc9d2933190fa389b284331241cb9f27115a0.marshal → bb84a835cf6a15136bad5ca03aa5bf93a5b92f5f72444618d3ef2412b987fb32.marshal} +0 -0
  181. data/prebuilt/{verdicts-872993b1aa26d73fe48f28251374b8585fed145d02aab42be0f00de386b6ff80.json → verdicts-f1f10da14c8e8f01b4c9b7e3e2d74be8cd2bf4676364ed17fa16975d2a076d75.json} +1 -1
  182. data/rust/Cargo.toml +40 -0
  183. data/rust/build/Cargo.toml +36 -0
  184. data/rust/build/clippy.toml +4 -0
  185. data/rust/build/src/adapters_pass.rs +196 -0
  186. data/rust/build/src/binding_pass.rs +54 -13
  187. data/rust/build/src/build_artifact.rs +23 -5
  188. data/rust/build/src/cargo_sync.rs +69 -26
  189. data/rust/build/src/lineage_pass.rs +101 -87
  190. data/rust/build/src/main.rs +11 -2
  191. data/rust/build/src/pipeline.rs +80 -19
  192. data/rust/build/src/reserved_names.rs +6 -50
  193. data/rust/build/src/resolve.rs +82 -35
  194. data/rust/build/src/rustfmt.rs +100 -0
  195. data/rust/build/src/seams_pass.rs +85 -16
  196. data/rust/build/src/subprocess.rs +30 -6
  197. data/rust/build/src/tmp.rs +5 -2
  198. data/rust/build/src/translation_pass.rs +72 -22
  199. data/rust/build/src/world_pass.rs +109 -0
  200. data/rust/clippy.toml +4 -0
  201. data/rust/codegen/Cargo.toml +36 -0
  202. data/rust/codegen/clippy.toml +4 -0
  203. data/rust/codegen/src/asks.rs +40 -14
  204. data/rust/codegen/src/attr.rs +2 -2
  205. data/rust/codegen/src/bridging.rs +676 -117
  206. data/rust/codegen/src/commands.rs +979 -373
  207. data/rust/codegen/src/constraints.rs +88 -25
  208. data/rust/codegen/src/dependency_planning.rs +128 -34
  209. data/rust/codegen/src/domain_generator.rs +300 -170
  210. data/rust/codegen/src/exemplar.rs +87 -32
  211. data/rust/codegen/src/expr_emitter.rs +54 -6
  212. data/rust/codegen/src/fielded.rs +193 -41
  213. data/rust/codegen/src/hecks_naming.rs +124 -16
  214. data/rust/codegen/src/hecks_naming_tests.rs +81 -0
  215. data/rust/codegen/src/json.rs +80 -44
  216. data/rust/codegen/src/json_codec.rs +585 -149
  217. data/rust/codegen/src/literal.rs +104 -138
  218. data/rust/codegen/src/main.rs +145 -52
  219. data/rust/codegen/src/manifest.rs +76 -14
  220. data/rust/codegen/src/mutations.rs +1212 -239
  221. data/rust/codegen/src/naming.rs +111 -60
  222. data/rust/codegen/src/optional_pass.rs +90 -28
  223. data/rust/codegen/src/ports.rs +5 -4
  224. data/rust/codegen/src/prelude.rs +139 -49
  225. data/rust/codegen/src/queries.rs +601 -226
  226. data/rust/codegen/src/reactions.rs +268 -74
  227. data/rust/codegen/src/read_models.rs +606 -205
  228. data/rust/codegen/src/reference_specs.rs +16 -3
  229. data/rust/codegen/src/registry.rs +128 -79
  230. data/rust/codegen/src/reserved_names.rs +6 -50
  231. data/rust/codegen/src/shared.rs +71 -22
  232. data/rust/codegen/src/sidecars.rs +15 -5
  233. data/rust/codegen/src/skip_reason.rs +8 -2
  234. data/rust/codegen/src/types.rs +306 -108
  235. data/rust/host/Cargo.lock +1 -0
  236. data/rust/host/Cargo.toml +40 -4
  237. data/rust/host/HECKS_RELEASE +1 -1
  238. data/rust/host/clippy.toml +4 -0
  239. data/rust/host/src/api.rs +698 -218
  240. data/rust/host/src/auth.rs +491 -214
  241. data/rust/host/src/b64.rs +91 -0
  242. data/rust/host/src/bin/expr_harness.rs +6 -2
  243. data/rust/host/src/bin/lineage_harness.rs +47 -11
  244. data/rust/host/src/bin/mint_harness.rs +70 -37
  245. data/rust/host/src/bin/validate_harness.rs +16 -8
  246. data/rust/host/src/boot.rs +144 -68
  247. data/rust/host/src/boundary_fuzz/api.rs +130 -32
  248. data/rust/host/src/boundary_fuzz/auth.rs +108 -23
  249. data/rust/host/src/boundary_fuzz/expr_json.rs +85 -11
  250. data/rust/host/src/boundary_fuzz/ir.rs +164 -12
  251. data/rust/host/src/boundary_fuzz/server.rs +283 -36
  252. data/rust/host/src/boundary_fuzz/web.rs +56 -8
  253. data/rust/host/src/checkout.rs +78 -32
  254. data/rust/host/src/clock.rs +8 -0
  255. data/rust/host/src/commerce_ir.rs +333 -95
  256. data/rust/host/src/crypto.rs +33 -0
  257. data/rust/host/src/dispatch.rs +990 -331
  258. data/rust/host/src/expr_json.rs +1021 -273
  259. data/rust/host/src/extension.rs +24 -4
  260. data/rust/host/src/field_hints.rs +11 -7
  261. data/rust/host/src/fuzz_support.rs +85 -13
  262. data/rust/host/src/host.rs +100 -0
  263. data/rust/host/src/http.rs +22 -0
  264. data/rust/host/src/ir.rs +326 -16
  265. data/rust/host/src/journal.rs +277 -176
  266. data/rust/host/src/lambda_client.rs +219 -53
  267. data/rust/host/src/lib.rs +16 -4
  268. data/rust/host/src/log.rs +62 -11
  269. data/rust/host/src/mint.rs +1153 -326
  270. data/rust/host/src/needs.rs +185 -44
  271. data/rust/host/src/payments/keystore.rs +197 -45
  272. data/rust/host/src/payments/roles.rs +39 -7
  273. data/rust/host/src/payments/tests.rs +1427 -282
  274. data/rust/host/src/payments.rs +391 -112
  275. data/rust/host/src/platform_signin/attack_tests.rs +5 -5
  276. data/rust/host/src/platform_signin/config.rs +11 -11
  277. data/rust/host/src/platform_signin/cookie.rs +2 -2
  278. data/rust/host/src/platform_signin/extension.rs +2 -1
  279. data/rust/host/src/platform_signin/handler.rs +9 -8
  280. data/rust/host/src/platform_signin/jti.rs +4 -2
  281. data/rust/host/src/platform_signin/jwks.rs +2 -1
  282. data/rust/host/src/platform_signin/jws.rs +2 -37
  283. data/rust/host/src/platform_signin/known_answer_tests.rs +5 -1
  284. data/rust/host/src/platform_signin/mode_tests.rs +5 -3
  285. data/rust/host/src/platform_signin/pages.rs +20 -20
  286. data/rust/host/src/platform_signin/session.rs +8 -1
  287. data/rust/host/src/platform_signin/settings.rs +6 -6
  288. data/rust/host/src/platform_signin/strict_json.rs +12 -9
  289. data/rust/host/src/platform_signin/verify.rs +41 -41
  290. data/rust/host/src/platform_signin/wiring_support.rs +13 -18
  291. data/rust/host/src/presentation.rs +207 -63
  292. data/rust/host/src/presentation_write.rs +502 -187
  293. data/rust/host/src/query_step.rs +69 -18
  294. data/rust/host/src/rate_limit.rs +541 -142
  295. data/rust/host/src/reference_transform.rs +77 -25
  296. data/rust/host/src/reference_validate.rs +443 -104
  297. data/rust/host/src/resend.rs +190 -55
  298. data/rust/host/src/secrets.rs +116 -21
  299. data/rust/host/src/server.rs +483 -104
  300. data/rust/host/src/storage_shape.rs +88 -21
  301. data/rust/host/src/test_pg.rs +17 -7
  302. data/rust/host/src/ui_schema.rs +495 -207
  303. data/rust/host/src/wasm_runner.rs +21 -6
  304. data/rust/host/src/web/commerce.rs +101 -23
  305. data/rust/host/src/web/newsletter/cooldown.rs +10 -3
  306. data/rust/host/src/web/newsletter/template.rs +26 -7
  307. data/rust/host/src/web/newsletter/tests.rs +112 -23
  308. data/rust/host/src/web/newsletter.rs +209 -59
  309. data/rust/host/src/web/newsletter_send.rs +251 -79
  310. data/rust/host/src/web/registration_receipt/tests.rs +16 -4
  311. data/rust/host/src/web/registration_receipt.rs +100 -24
  312. data/rust/host/src/web/registrations.rs +1384 -332
  313. data/rust/host/src/web/resend_webhook.rs +138 -80
  314. data/rust/host/src/web.rs +2416 -508
  315. data/rust/lsp/Cargo.toml +36 -0
  316. data/rust/lsp/clippy.toml +4 -0
  317. data/rust/lsp/src/json.rs +29 -22
  318. data/rust/lsp/src/main.rs +81 -31
  319. data/rust/lsp/src/outline.rs +23 -7
  320. data/rust/lsp/src/rpc.rs +17 -4
  321. data/rust/parser/Cargo.toml +36 -0
  322. data/rust/parser/clippy.toml +4 -0
  323. data/rust/parser/src/build/closed_sets.rs +6 -1
  324. data/rust/parser/src/build/identity.rs +4 -3
  325. data/rust/parser/src/build/mod.rs +1 -1
  326. data/rust/parser/src/build/named_pattern.rs +4 -1
  327. data/rust/parser/src/build/naming.rs +2 -80
  328. data/rust/parser/src/build/pattern_subset.rs +6 -8
  329. data/rust/parser/src/build/query_derive.rs +3 -1
  330. data/rust/parser/src/build/query_inference.rs +14 -5
  331. data/rust/parser/src/build/read_model.rs +4 -1
  332. data/rust/parser/src/build/references.rs +4 -3
  333. data/rust/parser/src/canonical.rs +34 -15
  334. data/rust/parser/src/emit.rs +187 -105
  335. data/rust/parser/src/expr/ast_json.rs +134 -42
  336. data/rust/parser/src/expr/evaluator.rs +27 -9
  337. data/rust/parser/src/expr/mod.rs +54 -13
  338. data/rust/parser/src/expr/resolver.rs +299 -92
  339. data/rust/parser/src/ir.rs +35 -3
  340. data/rust/parser/src/keywords.rs +7168 -516
  341. data/rust/parser/src/lex.rs +11 -25
  342. data/rust/parser/src/lib.rs +1 -0
  343. data/rust/parser/src/main.rs +65 -6
  344. data/rust/parser/src/parse/adapter.rs +157 -0
  345. data/rust/parser/src/parse/aggregate.rs +19 -10
  346. data/rust/parser/src/parse/chapter.rs +16 -17
  347. data/rust/parser/src/parse/command.rs +35 -16
  348. data/rust/parser/src/parse/domain_port.rs +4 -7
  349. data/rust/parser/src/parse/entity.rs +21 -11
  350. data/rust/parser/src/parse/hecksagon.rs +10 -4
  351. data/rust/parser/src/parse/lifecycle.rs +9 -5
  352. data/rust/parser/src/parse/mod.rs +22 -91
  353. data/rust/parser/src/parse/needs.rs +21 -12
  354. data/rust/parser/src/parse/policy.rs +2 -2
  355. data/rust/parser/src/parse/process_manager.rs +9 -11
  356. data/rust/parser/src/parse/query.rs +8 -4
  357. data/rust/parser/src/parse/read_model.rs +57 -16
  358. data/rust/parser/src/parse/value_object.rs +7 -2
  359. data/rust/parser/src/parse/world.rs +322 -0
  360. data/rust/parser/src/ruby_value.rs +7 -6
  361. data/rust/src/exemplar/json.rs +4 -4
  362. data/rust/src/exemplar/mutations.rs +18 -0
  363. data/rust/src/exemplar/read_models.rs +1 -10
  364. data/rust/src/kernel/attribute_shapes/composite.rs +54 -14
  365. data/rust/src/kernel/attribute_shapes/list.rs +2 -2
  366. data/rust/src/kernel/attribute_shapes/mod.rs +8 -8
  367. data/rust/src/kernel/attribute_shapes/scalar.rs +2 -2
  368. data/rust/src/kernel/cli.rs +229 -112
  369. data/rust/src/kernel/dispatch.rs +323 -115
  370. data/rust/src/kernel/dispatch_guard_tests.rs +327 -65
  371. data/rust/src/kernel/expr.rs +203 -86
  372. data/rust/src/kernel/expression_operators/arithmetic.rs +138 -25
  373. data/rust/src/kernel/expression_operators/comparison.rs +87 -2
  374. data/rust/src/kernel/expression_operators/enumeration.rs +106 -22
  375. data/rust/src/kernel/expression_operators/logical.rs +9 -3
  376. data/rust/src/kernel/expression_operators/membership.rs +3 -1
  377. data/rust/src/kernel/expression_operators/mod.rs +34 -21
  378. data/rust/src/kernel/expression_operators/pattern_match.rs +46 -11
  379. data/rust/src/kernel/expression_operators/positional.rs +84 -30
  380. data/rust/src/kernel/expression_operators/presence.rs +52 -13
  381. data/rust/src/kernel/expression_operators/sign_test.rs +3 -1
  382. data/rust/src/kernel/expression_operators/sized.rs +24 -8
  383. data/rust/src/kernel/expression_operators/text.rs +120 -27
  384. data/rust/src/kernel/expression_operators/to_string.rs +10 -4
  385. data/rust/src/kernel/json.rs +230 -148
  386. data/rust/src/kernel/mod.rs +46 -33
  387. data/rust/src/kernel/named_query.rs +63 -15
  388. data/rust/src/kernel/needs.rs +112 -25
  389. data/rust/src/kernel/orchestrate.rs +980 -581
  390. data/rust/src/kernel/orchestrate_guard_tests.rs +538 -140
  391. data/rust/src/kernel/pattern.rs +107 -34
  392. data/rust/src/kernel/query_comparators.rs +118 -41
  393. data/rust/src/kernel/query_ordering.rs +13 -5
  394. data/rust/src/kernel/read_model.rs +479 -181
  395. data/rust/src/kernel/reference_lookup.rs +122 -26
  396. data/rust/src/kernel/repository.rs +293 -66
  397. data/rust/src/kernel/routing.rs +58 -42
  398. data/rust/src/kernel/ruby_float.rs +139 -0
  399. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +57 -57
  400. data/rust/src/kernel/vocab/entity_dispatch_order.rs +54 -54
  401. data/rust/src/kernel/vocab/field_hint.rs +19 -25
  402. data/rust/src/kernel/vocab/mod.rs +6 -6
  403. data/rust/src/kernel/vocab/query_comparator.rs +23 -24
  404. data/rust/src/kernel/vocab/refusal_template.rs +501 -297
  405. data/rust/src/lib.rs +50 -0
  406. data/rust/src/main.rs +4 -1
  407. data/rust/web/Cargo.toml +36 -0
  408. data/rust/web/clippy.toml +4 -0
  409. metadata +82 -15
  410. data/examples/pizzas/bluebook/hecksagon/mock_stripe_payment_adapter.rb +0 -36
  411. data/lib/hecks/adapters/driven/google_authentication.adapter +0 -3
  412. data/lib/hecks/adapters/driven/google_authentication.rb +0 -99
  413. data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +0 -3
  414. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +0 -32
  415. data/lib/hecks/ports/authentication.port +0 -6
  416. data/lib/hecks/ports/authentication.rb +0 -67
  417. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +0 -255
  418. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +0 -22
  419. data/rust/host/src/approval.rs +0 -652
@@ -19,8 +19,15 @@ use std::collections::HashMap;
19
19
  /// `domain_ir` is the whole `ir.json`: a declared set named by `admits: "Aggregate::Set"` and a
20
20
  /// value object another aggregate of the chapter declares are looked up there.
21
21
  pub fn validate(domain_ir: &Value, aggregate_ir: &Value, id: &str, state: &Value) -> Vec<String> {
22
- let name = aggregate_ir.get("name").and_then(Value::as_str).unwrap_or("");
23
- let attributes = aggregate_ir.get("attributes").and_then(Value::as_array).cloned().unwrap_or_default();
22
+ let name = aggregate_ir
23
+ .get("name")
24
+ .and_then(Value::as_str)
25
+ .unwrap_or("");
26
+ let attributes = aggregate_ir
27
+ .get("attributes")
28
+ .and_then(Value::as_array)
29
+ .cloned()
30
+ .unwrap_or_default();
24
31
  let checker = Checker {
25
32
  domain: domain_ir,
26
33
  aggregate_name: name,
@@ -41,12 +48,19 @@ pub fn validate(domain_ir: &Value, aggregate_ir: &Value, id: &str, state: &Value
41
48
  fn index_by_name<'a>(node: &'a Value, key: &str) -> HashMap<&'a str, &'a Value> {
42
49
  node.get(key)
43
50
  .and_then(Value::as_array)
44
- .map(|list| list.iter().filter_map(|item| item.get("name").and_then(Value::as_str).map(|n| (n, item))).collect())
51
+ .map(|list| {
52
+ list.iter()
53
+ .filter_map(|item| item.get("name").and_then(Value::as_str).map(|n| (n, item)))
54
+ .collect()
55
+ })
45
56
  .unwrap_or_default()
46
57
  }
47
58
 
48
59
  fn declared_attributes(node: &Value) -> &[Value] {
49
- node.get("attributes").and_then(Value::as_array).map(Vec::as_slice).unwrap_or_default()
60
+ node.get("attributes")
61
+ .and_then(Value::as_array)
62
+ .map(Vec::as_slice)
63
+ .unwrap_or_default()
50
64
  }
51
65
 
52
66
  fn flag(node: &Value, key: &str) -> bool {
@@ -75,8 +89,13 @@ struct Checker<'a> {
75
89
  }
76
90
 
77
91
  impl<'a> Checker<'a> {
78
- fn refuse(&self, violations: &mut Vec<String>, message: String) {
79
- violations.push(format!("{}#{}: {message}", self.aggregate_name, self.id));
92
+ fn refuse(&self, violations: &mut Vec<String>, message: impl Into<String>) {
93
+ violations.push(format!(
94
+ "{}#{}: {}",
95
+ self.aggregate_name,
96
+ self.id,
97
+ message.into()
98
+ ));
80
99
  }
81
100
 
82
101
  /// The aggregate's own value object of that name, else the one every aggregate of the chapter
@@ -92,22 +111,40 @@ impl<'a> Checker<'a> {
92
111
  .map(|aggregates| {
93
112
  aggregates
94
113
  .iter()
95
- .filter_map(|aggregate| index_by_name(aggregate, "value_objects").get(type_name).copied())
114
+ .filter_map(|aggregate| {
115
+ index_by_name(aggregate, "value_objects")
116
+ .get(type_name)
117
+ .copied()
118
+ })
96
119
  .collect()
97
120
  })
98
121
  .unwrap_or_default();
99
122
  let first = *declared.first()?;
100
- declared.iter().all(|other| same_shape(other, first)).then_some(first)
123
+ declared
124
+ .iter()
125
+ .all(|other| same_shape(other, first))
126
+ .then_some(first)
101
127
  }
102
128
 
103
- fn check_attributes(&self, owner_name: &str, owner: Owner, state: &Value, attributes: &[Value], violations: &mut Vec<String>) {
104
- let Some(state_obj) = state.as_object() else { return };
129
+ fn check_attributes(
130
+ &self,
131
+ owner_name: &str,
132
+ owner: Owner,
133
+ state: &Value,
134
+ attributes: &[Value],
135
+ violations: &mut Vec<String>,
136
+ ) {
137
+ let Some(state_obj) = state.as_object() else {
138
+ return;
139
+ };
105
140
  for attr in attributes {
106
141
  let attr_name = attribute_name(attr);
107
142
  let type_name = attr.get("type").and_then(Value::as_str).unwrap_or("");
108
143
  let list = flag(attr, "list");
109
144
  let optional = flag(attr, "optional");
110
- let Some(raw) = state_obj.get(attr_name) else { continue };
145
+ let Some(raw) = state_obj.get(attr_name) else {
146
+ continue;
147
+ };
111
148
 
112
149
  if raw.is_null() {
113
150
  // A value object's required fields were judged as a whole; a list is never
@@ -122,10 +159,18 @@ impl<'a> Checker<'a> {
122
159
  match raw.as_array() {
123
160
  Some(items) => {
124
161
  for item in items {
125
- self.check_value(owner_name, attr_name, type_name, item, attr, violations);
162
+ self.check_value(
163
+ owner_name, attr_name, type_name, item, attr, violations,
164
+ );
126
165
  }
127
166
  }
128
- None => self.refuse(violations, format!("{owner_name}.{attr_name} expects list_of({type_name}), got {}", ruby_inspect(raw))),
167
+ None => self.refuse(
168
+ violations,
169
+ format!(
170
+ "{owner_name}.{attr_name} expects list_of({type_name}), got {}",
171
+ ruby_inspect(raw)
172
+ ),
173
+ ),
129
174
  }
130
175
  } else {
131
176
  self.check_value(owner_name, attr_name, type_name, raw, attr, violations);
@@ -137,17 +182,34 @@ impl<'a> Checker<'a> {
137
182
  /// A required slot holding null. A value object reads as built from no fields at all, as
138
183
  /// `Value::Coercion#nil_argument` builds it: refused for the first required field it lacks,
139
184
  /// or accepted when it has none.
140
- fn refuse_null(&self, owner_name: &str, attr_name: &str, type_name: &str, violations: &mut Vec<String>) {
185
+ fn refuse_null(
186
+ &self,
187
+ owner_name: &str,
188
+ attr_name: &str,
189
+ type_name: &str,
190
+ violations: &mut Vec<String>,
191
+ ) {
141
192
  match self.value_object(type_name) {
142
- Some(vo) => self.check_value_object(attr_name, type_name, vo, &Value::Object(serde_json::Map::new()), violations),
143
- None => self.refuse(violations, format!("{owner_name}.{attr_name} expects {type_name}, got nil")),
193
+ Some(vo) => self.check_value_object(
194
+ attr_name,
195
+ type_name,
196
+ vo,
197
+ &Value::Object(serde_json::Map::new()),
198
+ violations,
199
+ ),
200
+ None => self.refuse(
201
+ violations,
202
+ format!("{owner_name}.{attr_name} expects {type_name}, got nil"),
203
+ ),
144
204
  }
145
205
  }
146
206
 
147
207
  /// `admits: "Aggregate::Set"`: the value must be one of the named closed set's first-column
148
208
  /// members, compared as text (`Value::Admission#admit_declared_set`).
149
209
  fn check_admits(&self, attr: &Value, raw: &Value, violations: &mut Vec<String>) {
150
- let Some(admits) = attr.get("admits").and_then(Value::as_str) else { return };
210
+ let Some(admits) = attr.get("admits").and_then(Value::as_str) else {
211
+ return;
212
+ };
151
213
  let name = attribute_name(attr);
152
214
  let Some(admitted) = self.admitted_members(admits) else {
153
215
  self.refuse(
@@ -167,8 +229,18 @@ impl<'a> Checker<'a> {
167
229
  if admitted.contains(&ruby_to_s(offered)) {
168
230
  return;
169
231
  }
170
- let listed = admitted.iter().map(|member| inspect_text(member)).collect::<Vec<_>>().join(", ");
171
- self.refuse(violations, format!("{name} admits {admits} — {listed} — got {}", ruby_inspect(offered)));
232
+ let listed = admitted
233
+ .iter()
234
+ .map(|member| inspect_text(member))
235
+ .collect::<Vec<_>>()
236
+ .join(", ");
237
+ self.refuse(
238
+ violations,
239
+ format!(
240
+ "{name} admits {admits} — {listed} — got {}",
241
+ ruby_inspect(offered)
242
+ ),
243
+ );
172
244
  }
173
245
 
174
246
  /// The first column of each member row of the set `"Aggregate::Set"` names, as text.
@@ -179,19 +251,40 @@ impl<'a> Checker<'a> {
179
251
  .get("aggregates")
180
252
  .and_then(Value::as_array)?
181
253
  .iter()
182
- .find(|aggregate| aggregate.get("name").and_then(Value::as_str) == Some(aggregate_name))?;
254
+ .find(|aggregate| {
255
+ aggregate.get("name").and_then(Value::as_str) == Some(aggregate_name)
256
+ })?;
183
257
  let set = *index_by_name(aggregate, "value_objects").get(set_name)?;
184
258
  let discriminant = attribute_name(declared_attributes(set).first()?);
185
- Some(member_rows(set).iter().map(|row| ruby_to_s(row.get(discriminant).unwrap_or(&Value::Null))).collect())
259
+ Some(
260
+ member_rows(set)
261
+ .iter()
262
+ .map(|row| ruby_to_s(row.get(discriminant).unwrap_or(&Value::Null)))
263
+ .collect(),
264
+ )
186
265
  }
187
266
 
188
- fn check_value(&self, owner_name: &str, attr_name: &str, type_name: &str, value: &Value, attr: &Value, violations: &mut Vec<String>) {
267
+ fn check_value(
268
+ &self,
269
+ owner_name: &str,
270
+ attr_name: &str,
271
+ type_name: &str,
272
+ value: &Value,
273
+ attr: &Value,
274
+ violations: &mut Vec<String>,
275
+ ) {
189
276
  if let Some(vo) = self.value_object(type_name) {
190
277
  self.check_value_object(attr_name, type_name, vo, value, violations);
191
278
  return;
192
279
  }
193
280
  if let Some(entity) = self.entities.get(type_name) {
194
- self.check_attributes(type_name, Owner::Record, value, declared_attributes(entity), violations);
281
+ self.check_attributes(
282
+ type_name,
283
+ Owner::Record,
284
+ value,
285
+ declared_attributes(entity),
286
+ violations,
287
+ );
195
288
  return;
196
289
  }
197
290
 
@@ -201,15 +294,30 @@ impl<'a> Checker<'a> {
201
294
  self.refuse(violations, refusal);
202
295
  return;
203
296
  }
204
- if let (Some(pattern), Some(text)) = (attr.get("pattern").and_then(Value::as_str), value.as_str()) {
297
+ if let (Some(pattern), Some(text)) =
298
+ (attr.get("pattern").and_then(Value::as_str), value.as_str())
299
+ {
205
300
  if matches!(regex::Regex::new(pattern), Ok(re) if !re.is_match(text)) {
206
- self.refuse(violations, format!("{owner_name}.{attr_name} must match {pattern}, got {}", ruby_inspect(value)));
301
+ self.refuse(
302
+ violations,
303
+ format!(
304
+ "{owner_name}.{attr_name} must match {pattern}, got {}",
305
+ ruby_inspect(value)
306
+ ),
307
+ );
207
308
  }
208
309
  }
209
310
  }
210
311
 
211
312
  /// One stored value object, held to what `Value::Validation#validate!` holds an offered one to.
212
- fn check_value_object(&self, attr_name: &str, type_name: &str, vo: &Value, raw: &Value, violations: &mut Vec<String>) {
313
+ fn check_value_object(
314
+ &self,
315
+ attr_name: &str,
316
+ type_name: &str,
317
+ vo: &Value,
318
+ raw: &Value,
319
+ violations: &mut Vec<String>,
320
+ ) {
213
321
  let declared = declared_attributes(vo);
214
322
 
215
323
  // A bare scalar stands in for a single-field value object's one field.
@@ -217,19 +325,43 @@ impl<'a> Checker<'a> {
217
325
  let fields = if raw.is_object() {
218
326
  raw
219
327
  } else if declared.len() == 1 {
220
- wrapped = Value::Object(std::iter::once((attribute_name(&declared[0]).to_string(), raw.clone())).collect());
328
+ wrapped = Value::Object(
329
+ std::iter::once((attribute_name(&declared[0]).to_string(), raw.clone())).collect(),
330
+ );
221
331
  &wrapped
222
332
  } else {
223
- self.refuse(violations, format!("{attr_name} is a {type_name} — pass its fields as an object, not {}", ruby_inspect(raw)));
333
+ self.refuse(
334
+ violations,
335
+ format!(
336
+ "{attr_name} is a {type_name} — pass its fields as an object, not {}",
337
+ ruby_inspect(raw)
338
+ ),
339
+ );
340
+ return;
341
+ };
342
+ let Some(given) = fields.as_object() else {
224
343
  return;
225
344
  };
226
- let Some(given) = fields.as_object() else { return };
227
345
 
228
- let mut unknown: Vec<&str> = given.keys().map(String::as_str).filter(|key| !declared.iter().any(|attr| attribute_name(attr) == *key)).collect();
346
+ let mut unknown: Vec<&str> = given
347
+ .keys()
348
+ .map(String::as_str)
349
+ .filter(|key| !declared.iter().any(|attr| attribute_name(attr) == *key))
350
+ .collect();
229
351
  if !unknown.is_empty() {
230
- unknown.sort();
231
- let takes = declared.iter().map(attribute_name).collect::<Vec<_>>().join(", ");
232
- self.refuse(violations, format!("{type_name} does not declare {} — it takes {takes}", unknown.join(", ")));
352
+ unknown.sort_unstable();
353
+ let takes = declared
354
+ .iter()
355
+ .map(attribute_name)
356
+ .collect::<Vec<_>>()
357
+ .join(", ");
358
+ self.refuse(
359
+ violations,
360
+ format!(
361
+ "{type_name} does not declare {} — it takes {takes}",
362
+ unknown.join(", ")
363
+ ),
364
+ );
233
365
  return;
234
366
  }
235
367
 
@@ -237,12 +369,20 @@ impl<'a> Checker<'a> {
237
369
  let before = violations.len();
238
370
  // The first required field left out, as `check_required_fields` refuses it.
239
371
  let missing = declared.iter().find(|attr| {
240
- let present = filled.get(attribute_name(attr)).is_some_and(|value| !value.is_null());
372
+ let present = filled
373
+ .get(attribute_name(attr))
374
+ .is_some_and(|value| !value.is_null());
241
375
  !present && !flag(attr, "optional") && !flag(attr, "list")
242
376
  });
243
377
  if let Some(attr) = missing {
244
378
  let expected = attr.get("type").and_then(Value::as_str).unwrap_or("");
245
- self.refuse(violations, format!("{type_name}.{} expects {expected}, got nil", attribute_name(attr)));
379
+ self.refuse(
380
+ violations,
381
+ format!(
382
+ "{type_name}.{} expects {expected}, got nil",
383
+ attribute_name(attr)
384
+ ),
385
+ );
246
386
  return;
247
387
  }
248
388
 
@@ -264,31 +404,62 @@ impl<'a> Checker<'a> {
264
404
 
265
405
  /// `Value::Admission#admit_member`: some member row matches on every field it names, compared
266
406
  /// as text. The refusal quotes the first column only.
267
- fn check_member(&self, type_name: &str, vo: &Value, fields: &Value, violations: &mut Vec<String>) {
407
+ fn check_member(
408
+ &self,
409
+ type_name: &str,
410
+ vo: &Value,
411
+ fields: &Value,
412
+ violations: &mut Vec<String>,
413
+ ) {
268
414
  let rows = member_rows(vo);
269
415
  let matches_row = |row: &serde_json::Map<String, Value>| {
270
- row.iter().all(|(field, member)| ruby_to_s(fields.get(field).unwrap_or(&Value::Null)) == ruby_to_s(member))
416
+ row.iter().all(|(field, member)| {
417
+ ruby_to_s(fields.get(field).unwrap_or(&Value::Null)) == ruby_to_s(member)
418
+ })
271
419
  };
272
420
  if rows.iter().any(matches_row) {
273
421
  return;
274
422
  }
275
- let Some(first) = declared_attributes(vo).first().map(attribute_name) else { return };
276
- let admitted = rows.iter().map(|row| ruby_inspect(row.get(first).unwrap_or(&Value::Null))).collect::<Vec<_>>().join(", ");
423
+ let Some(first) = declared_attributes(vo).first().map(attribute_name) else {
424
+ return;
425
+ };
426
+ let admitted = rows
427
+ .iter()
428
+ .map(|row| ruby_inspect(row.get(first).unwrap_or(&Value::Null)))
429
+ .collect::<Vec<_>>()
430
+ .join(", ");
277
431
  let offered = ruby_inspect(fields.get(first).unwrap_or(&Value::Null));
278
- self.refuse(violations, format!("{type_name} admits {admitted} — got {offered}"));
432
+ self.refuse(
433
+ violations,
434
+ format!("{type_name} admits {admitted} — got {offered}"),
435
+ );
279
436
  }
280
437
 
281
438
  /// Checks a value object's `invariants` through `expr_json`. Fails closed: a malformed or
282
439
  /// unsupported `ast` is a violation, since an invariant that cannot be evaluated must not
283
440
  /// be minted past.
284
- fn check_invariants(&self, attr_name: &str, type_name: &str, value: &Value, vo: &Value, violations: &mut Vec<String>) {
285
- let Some(invariants) = vo.get("invariants").and_then(Value::as_array) else { return };
441
+ fn check_invariants(
442
+ &self,
443
+ attr_name: &str,
444
+ type_name: &str,
445
+ value: &Value,
446
+ vo: &Value,
447
+ violations: &mut Vec<String>,
448
+ ) {
449
+ let Some(invariants) = vo.get("invariants").and_then(Value::as_array) else {
450
+ return;
451
+ };
286
452
  let known = with_absent_optionals(vo, value);
287
453
 
288
454
  for invariant in invariants {
289
- let description = invariant.get("description").and_then(Value::as_str).unwrap_or("");
455
+ let description = invariant
456
+ .get("description")
457
+ .and_then(Value::as_str)
458
+ .unwrap_or("");
290
459
  // An older `ir.json` has no `ast`: nothing to check, so not a violation.
291
- let Some(ast) = invariant.get("ast") else { continue };
460
+ let Some(ast) = invariant.get("ast") else {
461
+ continue;
462
+ };
292
463
 
293
464
  let expr = match expr_json::parse(ast) {
294
465
  Ok(expr) => expr,
@@ -300,9 +471,12 @@ impl<'a> Checker<'a> {
300
471
 
301
472
  match expr_json::interpret(&expr, &known) {
302
473
  Ok(result) if result.truthy() => {}
303
- Ok(_) => self.refuse(violations, format!("{attr_name} ({type_name}) violates its own invariant — {description}")),
474
+ Ok(_) => self.refuse(
475
+ violations,
476
+ format!("{attr_name} ({type_name}) violates its own invariant — {description}"),
477
+ ),
304
478
  Err(error) => {
305
- self.refuse(violations, format!("{attr_name} ({type_name})'s own invariant {description:?} could not be checked — {error}"))
479
+ self.refuse(violations, format!("{attr_name} ({type_name})'s own invariant {description:?} could not be checked — {error}"));
306
480
  }
307
481
  }
308
482
  }
@@ -318,7 +492,10 @@ fn same_shape(left: &Value, right: &Value) -> bool {
318
492
  .map(|attr| {
319
493
  (
320
494
  attribute_name(attr).to_string(),
321
- attr.get("type").and_then(Value::as_str).unwrap_or("").to_string(),
495
+ attr.get("type")
496
+ .and_then(Value::as_str)
497
+ .unwrap_or("")
498
+ .to_string(),
322
499
  flag(attr, "list"),
323
500
  flag(attr, "optional"),
324
501
  )
@@ -341,7 +518,12 @@ fn member_rows(vo: &Value) -> Vec<serde_json::Map<String, Value>> {
341
518
  pairs
342
519
  .iter()
343
520
  .filter_map(Value::as_array)
344
- .filter_map(|pair| Some((pair.first()?.as_str()?.to_string(), pair.get(1)?.clone())))
521
+ .filter_map(|pair| {
522
+ Some((
523
+ pair.first()?.as_str()?.to_string(),
524
+ pair.get(1)?.clone(),
525
+ ))
526
+ })
345
527
  .collect()
346
528
  })
347
529
  .unwrap_or_default()
@@ -354,7 +536,10 @@ fn member_rows(vo: &Value) -> Vec<serde_json::Map<String, Value>> {
354
536
  /// The value as the Ruby runtime would hold it once it has defaulted what was left out: every
355
537
  /// declared attribute missing from `given` takes its own `default:`, and every required list
356
538
  /// reads as empty (`Value::Validation#apply_defaults`). An optional slot stays absent.
357
- fn with_defaults(declared: &[Value], given: &serde_json::Map<String, Value>) -> serde_json::Map<String, Value> {
539
+ fn with_defaults(
540
+ declared: &[Value],
541
+ given: &serde_json::Map<String, Value>,
542
+ ) -> serde_json::Map<String, Value> {
358
543
  let mut filled = given.clone();
359
544
  for attr in declared {
360
545
  let name = attribute_name(attr);
@@ -377,12 +562,25 @@ fn with_defaults(declared: &[Value], given: &serde_json::Map<String, Value>) ->
377
562
  /// A scalar that does not fit its declared type, worded as dispatch words it; `None` when it fits
378
563
  /// or the type is not a plain scalar. A leaf is a String, a number or a boolean, never a list or
379
564
  /// an object standing in for one.
380
- fn scalar_refusal(owner_name: &str, attr_name: &str, type_name: &str, value: &Value) -> Option<String> {
381
- let mismatch = || format!("{owner_name}.{attr_name} expects {type_name}, got {}", ruby_inspect(value));
565
+ fn scalar_refusal(
566
+ owner_name: &str,
567
+ attr_name: &str,
568
+ type_name: &str,
569
+ value: &Value,
570
+ ) -> Option<String> {
571
+ let mismatch = || {
572
+ format!(
573
+ "{owner_name}.{attr_name} expects {type_name}, got {}",
574
+ ruby_inspect(value)
575
+ )
576
+ };
382
577
  match type_name {
383
578
  "String" if !value.is_string() => Some(mismatch()),
384
579
  "Integer" if value.is_i64() => None,
385
- "Integer" if value.is_u64() => Some(format!("{owner_name}.{attr_name} must fit in a 64-bit integer, got {}", ruby_inspect(value))),
580
+ "Integer" if value.is_u64() => Some(format!(
581
+ "{owner_name}.{attr_name} must fit in a 64-bit integer, got {}",
582
+ ruby_inspect(value)
583
+ )),
386
584
  "Integer" => Some(mismatch()),
387
585
  "Float" if value.is_number() => None,
388
586
  "Float" => Some(mismatch()),
@@ -405,7 +603,14 @@ fn ruby_inspect(value: &Value) -> String {
405
603
  match value {
406
604
  Value::Null => "nil".to_string(),
407
605
  Value::String(text) => inspect_text(text),
408
- Value::Array(items) => format!("[{}]", items.iter().map(ruby_inspect).collect::<Vec<_>>().join(", ")),
606
+ Value::Array(items) => format!(
607
+ "[{}]",
608
+ items
609
+ .iter()
610
+ .map(ruby_inspect)
611
+ .collect::<Vec<_>>()
612
+ .join(", ")
613
+ ),
409
614
  other => other.to_string(),
410
615
  }
411
616
  }
@@ -420,10 +625,14 @@ fn inspect_text(text: &str) -> String {
420
625
  /// `x.nil?` and a comparison with nil all answer for a slot a writer never sent. A name the value
421
626
  /// object does not declare is not filled, so a lookup of it still refuses.
422
627
  fn with_absent_optionals(vo: &Value, value: &Value) -> Value {
423
- let Some(fields) = value.as_object() else { return value.clone() };
628
+ let Some(fields) = value.as_object() else {
629
+ return value.clone();
630
+ };
424
631
  let mut known = fields.clone();
425
632
  for attribute in declared_attributes(vo) {
426
- let Some(name) = attribute.get("name").and_then(Value::as_str) else { continue };
633
+ let Some(name) = attribute.get("name").and_then(Value::as_str) else {
634
+ continue;
635
+ };
427
636
  if flag(attribute, "optional") && !known.contains_key(name) {
428
637
  known.insert(name.to_string(), Value::Null);
429
638
  }
@@ -431,7 +640,13 @@ fn with_absent_optionals(vo: &Value, value: &Value) -> Value {
431
640
  Value::Object(known)
432
641
  }
433
642
 
434
- fn check_lifecycle(aggregate_name: &str, id: &str, state: &Value, lifecycle: &Value, violations: &mut Vec<String>) {
643
+ fn check_lifecycle(
644
+ aggregate_name: &str,
645
+ id: &str,
646
+ state: &Value,
647
+ lifecycle: &Value,
648
+ violations: &mut Vec<String>,
649
+ ) {
435
650
  let field = lifecycle.get("field").and_then(Value::as_str).unwrap_or("");
436
651
  if field.is_empty() {
437
652
  return;
@@ -444,15 +659,21 @@ fn check_lifecycle(aggregate_name: &str, id: &str, state: &Value, lifecycle: &Va
444
659
  let mut allowed: Vec<&str> = lifecycle
445
660
  .get("transitions")
446
661
  .and_then(Value::as_array)
447
- .map(|ts| ts.iter().filter_map(|t| t.get("to_state").and_then(Value::as_str)).collect())
662
+ .map(|ts| {
663
+ ts.iter()
664
+ .filter_map(|t| t.get("to_state").and_then(Value::as_str))
665
+ .collect()
666
+ })
448
667
  .unwrap_or_default();
449
668
  if let Some(default) = lifecycle.get("default").and_then(Value::as_str) {
450
669
  allowed.push(default);
451
670
  }
452
671
 
453
- let ok = held.as_str().map(|h| allowed.contains(&h)).unwrap_or(false);
672
+ let ok = held.as_str().is_some_and(|h| allowed.contains(&h));
454
673
  if !ok {
455
- violations.push(format!("{aggregate_name}#{id}: {field} is {held}, a state this era's lifecycle never reaches"));
674
+ violations.push(format!(
675
+ "{aggregate_name}#{id}: {field} is {held}, a state this era's lifecycle never reaches"
676
+ ));
456
677
  }
457
678
  }
458
679
 
@@ -463,10 +684,20 @@ mod tests {
463
684
 
464
685
  // The aggregate alone is the whole domain here; `domain_validate` takes the domain apart.
465
686
  fn validate(aggregate_ir: &Value, id: &str, state: &Value) -> Vec<String> {
466
- domain_validate(&json!({"aggregates": [aggregate_ir]}), aggregate_ir, id, state)
687
+ domain_validate(
688
+ &json!({"aggregates": [aggregate_ir]}),
689
+ aggregate_ir,
690
+ id,
691
+ state,
692
+ )
467
693
  }
468
694
 
469
- fn domain_validate(domain_ir: &Value, aggregate_ir: &Value, id: &str, state: &Value) -> Vec<String> {
695
+ fn domain_validate(
696
+ domain_ir: &Value,
697
+ aggregate_ir: &Value,
698
+ id: &str,
699
+ state: &Value,
700
+ ) -> Vec<String> {
470
701
  super::validate(domain_ir, aggregate_ir, id, state)
471
702
  }
472
703
 
@@ -525,7 +756,13 @@ mod tests {
525
756
  fn a_value_outside_the_declared_set_it_admits_is_caught_in_dispatchs_words() {
526
757
  let state = json!({ "pizza": { "cents": 1200, "size": "medium" }, "toppings": [], "status": "available" });
527
758
  let violations = validate(&order_ir(), "p1", &state);
528
- assert_eq!(violations, vec![r#"Order#p1: size admits Order::Size — "small", "large" — got "medium""#.to_string()]);
759
+ assert_eq!(
760
+ violations,
761
+ vec![
762
+ r#"Order#p1: size admits Order::Size — "small", "large" — got "medium""#
763
+ .to_string()
764
+ ]
765
+ );
529
766
  }
530
767
 
531
768
  #[test]
@@ -535,7 +772,11 @@ mod tests {
535
772
  let state = json!({ "pizza": { "cents": 1200, "size": "small" }, "toppings": [], "status": "available" });
536
773
  let violations = validate(&ir, "p1", &state);
537
774
  assert_eq!(violations.len(), 1);
538
- assert!(violations[0].contains("size admits Order::Missing, which this chapter does not declare"), "{violations:?}");
775
+ assert!(
776
+ violations[0]
777
+ .contains("size admits Order::Missing, which this chapter does not declare"),
778
+ "{violations:?}"
779
+ );
539
780
  }
540
781
 
541
782
  #[test]
@@ -571,7 +812,10 @@ mod tests {
571
812
  let state = json!({ "pizza": { "cents": -500, "size": "large" }, "toppings": [], "status": "available" });
572
813
  let violations = validate(&order_ir(), "p1", &state);
573
814
  assert_eq!(violations.len(), 1);
574
- assert!(violations[0].contains("a price is never negative"), "{violations:?}");
815
+ assert!(
816
+ violations[0].contains("a price is never negative"),
817
+ "{violations:?}"
818
+ );
575
819
  }
576
820
 
577
821
  #[test]
@@ -584,7 +828,10 @@ mod tests {
584
828
  fn an_invariant_with_no_ast_key_at_all_is_not_checked_and_not_a_violation() {
585
829
  // An `ir.json` built before `ast:` existed: nothing to evaluate, so nothing claimed.
586
830
  let mut ir = order_ir();
587
- ir["value_objects"][0]["invariants"][0].as_object_mut().unwrap().remove("ast");
831
+ ir["value_objects"][0]["invariants"][0]
832
+ .as_object_mut()
833
+ .unwrap()
834
+ .remove("ast");
588
835
  let state = json!({ "pizza": { "cents": -500, "size": "large" }, "toppings": [], "status": "available" });
589
836
  assert_eq!(validate(&ir, "p1", &state), Vec::<String>::new());
590
837
  }
@@ -602,7 +849,10 @@ mod tests {
602
849
  let bad = json!({ "pizza": { "cents": null, "size": "large" }, "toppings": [], "status": "available" });
603
850
  let violations = validate(&ir, "p1", &bad);
604
851
  assert_eq!(violations.len(), 1);
605
- assert!(violations[0].contains("violates its own invariant"), "{violations:?}");
852
+ assert!(
853
+ violations[0].contains("violates its own invariant"),
854
+ "{violations:?}"
855
+ );
606
856
  }
607
857
 
608
858
  #[test]
@@ -614,7 +864,11 @@ mod tests {
614
864
  let state = json!({ "pizza": { "cents": 1200, "size": "large" }, "toppings": [], "status": "available" });
615
865
  let violations = validate(&ir, "p1", &state);
616
866
  assert_eq!(violations.len(), 1);
617
- assert!(violations[0].contains("could not be checked — empty? expects a list or string, got 1200"), "{violations:?}");
867
+ assert!(
868
+ violations[0]
869
+ .contains("could not be checked — empty? expects a list or string, got 1200"),
870
+ "{violations:?}"
871
+ );
618
872
  }
619
873
 
620
874
  #[test]
@@ -622,22 +876,27 @@ mod tests {
622
876
  let state = json!({ "pizza": null, "toppings": [], "status": "available" });
623
877
  let violations = validate(&order_ir(), "p1", &state);
624
878
  assert_eq!(violations.len(), 1);
625
- assert!(violations[0].contains("Pizza.cents expects Integer, got nil"), "{violations:?}");
879
+ assert!(
880
+ violations[0].contains("Pizza.cents expects Integer, got nil"),
881
+ "{violations:?}"
882
+ );
626
883
  }
627
884
 
628
885
  // A page of panels, each optionally holding a note, which optionally holds a link; a panel
629
886
  // also lists cells that may hold a note. Every rule reads a slot a stored value may leave out.
630
887
  fn layout_ir() -> Value {
631
- let attr = |name: &str, ty: &str, list: bool, optional: bool| {
632
- json!({"name": name, "type": ty, "list": list, "optional": optional})
633
- };
888
+ let attr = |name: &str, ty: &str, list: bool, optional: bool| json!({"name": name, "type": ty, "list": list, "optional": optional});
634
889
  let lookup = |path: &[&str]| json!({"op": "lookup", "path": path});
635
- let unset = |path: &[&str]| json!({"op": "assignment", "receiver": lookup(path), "negated": true});
636
- let set = |path: &[&str]| json!({"op": "assignment", "receiver": lookup(path), "negated": false});
890
+ let unset =
891
+ |path: &[&str]| json!({"op": "assignment", "receiver": lookup(path), "negated": true});
892
+ let set =
893
+ |path: &[&str]| json!({"op": "assignment", "receiver": lookup(path), "negated": false});
637
894
  let not_empty = |path: &[&str]| json!({"op": "not", "expr": {"op": "empty", "receiver": {"op": "to_s", "receiver": lookup(path)}}});
638
895
  let either = |left: Value, right: Value| json!({"op": "or", "left": left, "right": right});
639
- let differs = |path: &[&str], text: &str| json!({"op": "compare", "cmp": {"less_than": false, "equal": true, "negated": true},
640
- "left": lookup(path), "right": {"op": "str", "value": text}});
896
+ let differs = |path: &[&str], text: &str| {
897
+ json!({"op": "compare", "cmp": {"less_than": false, "equal": true, "negated": true},
898
+ "left": lookup(path), "right": {"op": "str", "value": text}})
899
+ };
641
900
  let rule = |description: &str, ast: Value| json!({"description": description, "canonical": "", "ast": ast});
642
901
  json!({
643
902
  "name": "Layout",
@@ -668,19 +927,29 @@ mod tests {
668
927
  })
669
928
  }
670
929
 
930
+ #[allow(clippy::needless_pass_by_value)] // reason: test helper called with inline literals
671
931
  fn layout_violations(panels: Value) -> Vec<String> {
672
- validate(&layout_ir(), "l1", &json!({"key": {"value": "l1"}, "panels": panels}))
932
+ validate(
933
+ &layout_ir(),
934
+ "l1",
935
+ &json!({"key": {"value": "l1"}, "panels": panels}),
936
+ )
673
937
  }
674
938
 
675
939
  #[test]
676
940
  fn a_stored_panel_with_every_optional_slot_absent_is_accepted() {
677
- assert_eq!(layout_violations(json!([{"kind": "plain", "cells": [{"label": "c"}]}])), Vec::<String>::new());
941
+ assert_eq!(
942
+ layout_violations(json!([{"kind": "plain", "cells": [{"label": "c"}]}])),
943
+ Vec::<String>::new()
944
+ );
678
945
  }
679
946
 
680
947
  #[test]
681
948
  fn an_absent_slot_reads_the_same_as_an_explicit_null() {
682
949
  let absent = layout_violations(json!([{"kind": "callout", "cells": []}]));
683
- let null = layout_violations(json!([{"kind": "callout", "note": null, "title": null, "cells": []}]));
950
+ let null = layout_violations(
951
+ json!([{"kind": "callout", "note": null, "title": null, "cells": []}]),
952
+ );
684
953
  assert_eq!(absent.len(), 1, "{absent:?}");
685
954
  assert_eq!(absent, null);
686
955
  }
@@ -689,12 +958,18 @@ mod tests {
689
958
  fn a_rule_that_needs_an_absent_slot_refuses_with_its_description_not_a_lookup_error() {
690
959
  let violations = layout_violations(json!([{"kind": "callout", "cells": []}]));
691
960
  assert_eq!(violations.len(), 1, "{violations:?}");
692
- assert!(violations[0].contains("violates its own invariant — a callout carries a note"), "{violations:?}");
961
+ assert!(
962
+ violations[0].contains("violates its own invariant — a callout carries a note"),
963
+ "{violations:?}"
964
+ );
693
965
  }
694
966
 
695
967
  #[test]
696
968
  fn a_path_through_an_absent_value_object_reads_as_nil() {
697
- assert_eq!(layout_violations(json!([{"kind": "quiet", "cells": []}])), Vec::<String>::new());
969
+ assert_eq!(
970
+ layout_violations(json!([{"kind": "quiet", "cells": []}])),
971
+ Vec::<String>::new()
972
+ );
698
973
  }
699
974
 
700
975
  #[test]
@@ -703,7 +978,10 @@ mod tests {
703
978
  let blank = json!([{"kind": "plain", "cells": [{"label": "c", "note": {"text": "n", "link": {"href": "/a", "label": ""}}}]}]);
704
979
  assert_eq!(layout_violations(absent), Vec::<String>::new());
705
980
  let violations = layout_violations(blank);
706
- assert!(violations.len() == 1 && violations[0].contains("a link label is not blank when given"), "{violations:?}");
981
+ assert!(
982
+ violations.len() == 1 && violations[0].contains("a link label is not blank when given"),
983
+ "{violations:?}"
984
+ );
707
985
  }
708
986
 
709
987
  #[test]
@@ -712,27 +990,38 @@ mod tests {
712
990
  let bad = json!([{"kind": "plain", "cells": [{"label": "a"}, {"label": "b", "note": {"text": ""}}]}]);
713
991
  assert_eq!(layout_violations(members), Vec::<String>::new());
714
992
  let violations = layout_violations(bad);
715
- assert!(violations.iter().any(|v| v.contains("a cell's note has words")), "{violations:?}");
993
+ assert!(
994
+ violations
995
+ .iter()
996
+ .any(|v| v.contains("a cell's note has words")),
997
+ "{violations:?}"
998
+ );
716
999
  }
717
1000
 
718
1001
  #[test]
719
1002
  fn a_name_the_value_object_does_not_declare_still_refuses_to_resolve() {
720
1003
  let mut ir = layout_ir();
721
- ir["value_objects"][1]["invariants"] =
722
- json!([{"description": "a typo", "canonical": "", "ast": {"op": "lookup", "path": ["tittle"]}}]);
1004
+ ir["value_objects"][1]["invariants"] = json!([{"description": "a typo", "canonical": "", "ast": {"op": "lookup", "path": ["tittle"]}}]);
723
1005
  let state = json!({"key": {"value": "l1"}, "panels": [{"kind": "plain", "cells": []}]});
724
1006
  let violations = validate(&ir, "l1", &state);
725
1007
  assert_eq!(violations.len(), 1, "{violations:?}");
726
- assert!(violations[0].contains("could not be checked — cannot resolve \"tittle\" — no such attribute or argument"), "{violations:?}");
1008
+ assert!(
1009
+ violations[0].contains(
1010
+ "could not be checked — cannot resolve \"tittle\" — no such attribute or argument"
1011
+ ),
1012
+ "{violations:?}"
1013
+ );
727
1014
  }
728
1015
 
729
1016
  #[test]
730
1017
  fn a_required_slot_left_out_is_refused_by_name_not_filled_in_for_an_invariant_to_read() {
731
1018
  let mut ir = layout_ir();
732
- ir["value_objects"][1]["invariants"] =
733
- json!([{"description": "a kind", "canonical": "", "ast": {"op": "lookup", "path": ["kind"]}}]);
1019
+ ir["value_objects"][1]["invariants"] = json!([{"description": "a kind", "canonical": "", "ast": {"op": "lookup", "path": ["kind"]}}]);
734
1020
  let state = json!({"key": {"value": "l1"}, "panels": [{"cells": []}]});
735
- assert_eq!(validate(&ir, "l1", &state), vec!["Layout#l1: Panel.kind expects String, got nil".to_string()]);
1021
+ assert_eq!(
1022
+ validate(&ir, "l1", &state),
1023
+ vec!["Layout#l1: Panel.kind expects String, got nil".to_string()]
1024
+ );
736
1025
  }
737
1026
 
738
1027
  // A closed set, a multi-field closed set and a value object that holds both, three deep and in a
@@ -765,10 +1054,16 @@ mod tests {
765
1054
  json!({"band": {"size": "small"}, "count": 1, "slug": "ok", "labels": ["a", "b"]})
766
1055
  }
767
1056
 
1057
+ #[allow(clippy::needless_pass_by_value)] // reason: test helper called with inline literals
768
1058
  fn shelf_violations(panels: Value) -> Vec<String> {
769
- validate(&constrained_ir(), "s1", &json!({"band": {"size": "large"}, "panels": panels}))
1059
+ validate(
1060
+ &constrained_ir(),
1061
+ "s1",
1062
+ &json!({"band": {"size": "large"}, "panels": panels}),
1063
+ )
770
1064
  }
771
1065
 
1066
+ #[allow(clippy::needless_pass_by_value)] // reason: test helper called with inline literals
772
1067
  fn assert_refused_as(violations: Vec<String>, expected: &str) {
773
1068
  assert_eq!(violations, vec![format!("Shelf#s1: {expected}")]);
774
1069
  }
@@ -779,21 +1074,34 @@ mod tests {
779
1074
  inner["pairing"] = json!({"code": "q", "label": "Cue"});
780
1075
  let mut outer = panel();
781
1076
  outer["inner"] = json!({"band": {"size": "large"}, "count": 2, "slug": "in", "labels": [], "inner": inner});
782
- assert_eq!(shelf_violations(json!([outer, panel()])), Vec::<String>::new());
1077
+ assert_eq!(
1078
+ shelf_violations(json!([outer, panel()])),
1079
+ Vec::<String>::new()
1080
+ );
783
1081
  }
784
1082
 
785
1083
  #[test]
786
1084
  fn a_closed_set_refuses_a_non_member_alone_in_a_list_member_and_three_deep() {
787
- let violations = validate(&constrained_ir(), "s1", &json!({"band": {"size": "huge"}, "panels": []}));
1085
+ let violations = validate(
1086
+ &constrained_ir(),
1087
+ "s1",
1088
+ &json!({"band": {"size": "huge"}, "panels": []}),
1089
+ );
788
1090
  assert_refused_as(violations, r#"Band admits "small", "large" — got "huge""#);
789
1091
 
790
1092
  let mut bad = panel();
791
1093
  bad["band"] = json!({"size": "huge"});
792
- assert_refused_as(shelf_violations(json!([panel(), bad.clone()])), r#"Band admits "small", "large" — got "huge""#);
1094
+ assert_refused_as(
1095
+ shelf_violations(json!([panel(), bad.clone()])),
1096
+ r#"Band admits "small", "large" — got "huge""#,
1097
+ );
793
1098
 
794
1099
  let mut deep = panel();
795
1100
  deep["inner"] = json!({"band": {"size": "small"}, "count": 2, "slug": "in", "labels": [], "inner": {"band": {"size": "small"}, "count": 3, "slug": "x", "labels": [], "inner": bad}});
796
- assert_refused_as(shelf_violations(json!([deep])), r#"Band admits "small", "large" — got "huge""#);
1101
+ assert_refused_as(
1102
+ shelf_violations(json!([deep])),
1103
+ r#"Band admits "small", "large" — got "huge""#,
1104
+ );
797
1105
  }
798
1106
 
799
1107
  #[test]
@@ -803,14 +1111,20 @@ mod tests {
803
1111
  assert_eq!(shelf_violations(json!([bare])), Vec::<String>::new());
804
1112
  let mut wrong = panel();
805
1113
  wrong["band"] = json!("huge");
806
- assert_refused_as(shelf_violations(json!([wrong])), r#"Band admits "small", "large" — got "huge""#);
1114
+ assert_refused_as(
1115
+ shelf_violations(json!([wrong])),
1116
+ r#"Band admits "small", "large" — got "huge""#,
1117
+ );
807
1118
  }
808
1119
 
809
1120
  #[test]
810
1121
  fn a_multi_field_member_must_match_on_every_column_and_quotes_the_first() {
811
1122
  let mut mixed = panel();
812
1123
  mixed["pairing"] = json!({"code": "p", "label": "Cue"});
813
- assert_refused_as(shelf_violations(json!([mixed])), r#"Pairing admits "p", "q" — got "p""#);
1124
+ assert_refused_as(
1125
+ shelf_violations(json!([mixed])),
1126
+ r#"Pairing admits "p", "q" — got "p""#,
1127
+ );
814
1128
  }
815
1129
 
816
1130
  #[test]
@@ -819,7 +1133,11 @@ mod tests {
819
1133
  extra["bogus"] = json!(1);
820
1134
  assert_refused_as(shelf_violations(json!([extra])), "Panel does not declare bogus — it takes band, count, slug, labels, pairing, tone, inner");
821
1135
 
822
- let violations = validate(&constrained_ir(), "s1", &json!({"band": {"size": "small", "bogus": 1}, "panels": []}));
1136
+ let violations = validate(
1137
+ &constrained_ir(),
1138
+ "s1",
1139
+ &json!({"band": {"size": "small", "bogus": 1}, "panels": []}),
1140
+ );
823
1141
  assert_refused_as(violations, "Band does not declare bogus — it takes size");
824
1142
  }
825
1143
 
@@ -827,7 +1145,10 @@ mod tests {
827
1145
  fn a_required_field_left_out_is_refused_while_an_optional_one_a_default_and_a_list_are_not() {
828
1146
  let mut absent = panel();
829
1147
  absent.as_object_mut().unwrap().remove("count");
830
- assert_refused_as(shelf_violations(json!([absent])), "Panel.count expects Integer, got nil");
1148
+ assert_refused_as(
1149
+ shelf_violations(json!([absent])),
1150
+ "Panel.count expects Integer, got nil",
1151
+ );
831
1152
 
832
1153
  let mut sparse = panel();
833
1154
  sparse.as_object_mut().unwrap().remove("labels");
@@ -838,22 +1159,34 @@ mod tests {
838
1159
  fn a_pattern_is_held_by_every_member_of_a_list_and_by_the_whole_value() {
839
1160
  let mut bad_label = panel();
840
1161
  bad_label["labels"] = json!(["a", "B"]);
841
- assert_refused_as(shelf_violations(json!([bad_label])), r#"Panel.labels must match ^[a-z]+$, got "B""#);
1162
+ assert_refused_as(
1163
+ shelf_violations(json!([bad_label])),
1164
+ r#"Panel.labels must match ^[a-z]+$, got "B""#,
1165
+ );
842
1166
 
843
1167
  let mut newline = panel();
844
1168
  newline["slug"] = json!("ok\n");
845
- assert_refused_as(shelf_violations(json!([newline])), r#"Panel.slug must match ^[a-z]+$, got "ok\n""#);
1169
+ assert_refused_as(
1170
+ shelf_violations(json!([newline])),
1171
+ r#"Panel.slug must match ^[a-z]+$, got "ok\n""#,
1172
+ );
846
1173
  }
847
1174
 
848
1175
  #[test]
849
1176
  fn a_number_past_signed_64_bits_and_a_whole_float_are_not_integers() {
850
1177
  let mut big = panel();
851
- big["count"] = json!(9223372036854775808u64);
852
- assert_refused_as(shelf_violations(json!([big])), "Panel.count must fit in a 64-bit integer, got 9223372036854775808");
1178
+ big["count"] = json!(9_223_372_036_854_775_808_u64);
1179
+ assert_refused_as(
1180
+ shelf_violations(json!([big])),
1181
+ "Panel.count must fit in a 64-bit integer, got 9223372036854775808",
1182
+ );
853
1183
 
854
1184
  let mut whole = panel();
855
1185
  whole["count"] = json!(3.0);
856
- assert_refused_as(shelf_violations(json!([whole])), "Panel.count expects Integer, got 3.0");
1186
+ assert_refused_as(
1187
+ shelf_violations(json!([whole])),
1188
+ "Panel.count expects Integer, got 3.0",
1189
+ );
857
1190
 
858
1191
  let mut largest = panel();
859
1192
  largest["count"] = json!(i64::MAX);
@@ -862,7 +1195,10 @@ mod tests {
862
1195
 
863
1196
  #[test]
864
1197
  fn a_list_offered_a_lone_value_is_a_shape_error_and_a_null_list_is_not() {
865
- assert_refused_as(shelf_violations(json!("x")), r#"Shelf.panels expects list_of(Panel), got "x""#);
1198
+ assert_refused_as(
1199
+ shelf_violations(json!("x")),
1200
+ r#"Shelf.panels expects list_of(Panel), got "x""#,
1201
+ );
866
1202
  let mut unset = panel();
867
1203
  unset["labels"] = Value::Null;
868
1204
  assert_eq!(shelf_violations(json!([unset])), Vec::<String>::new());
@@ -878,6 +1214,9 @@ mod tests {
878
1214
  let domain = json!({"aggregates": [shelf, vocabulary]});
879
1215
  let refused = domain_validate(&domain, &shelf, "s1", &json!({"band": {"size": "huge"}}));
880
1216
  assert_refused_as(refused, r#"Band admits "small" — got "huge""#);
881
- assert_eq!(domain_validate(&domain, &shelf, "s1", &json!({"band": {"size": "small"}})), Vec::<String>::new());
1217
+ assert_eq!(
1218
+ domain_validate(&domain, &shelf, "s1", &json!({"band": {"size": "small"}})),
1219
+ Vec::<String>::new()
1220
+ );
882
1221
  }
883
1222
  }