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
@@ -0,0 +1,142 @@
1
+ require "digest"
2
+ require "json"
3
+ require_relative "live"
4
+ require_relative "toolbox"
5
+ require_relative "behavior_run"
6
+ require_relative "board_edit"
7
+ require_relative "jobs"
8
+
9
+ module Hecks
10
+ module Composer
11
+ # What the editor's lower panels ask of the server, on the hecksagon side: the Console and
12
+ # Records panels (a booted copy of the workspace), the Behaviors panel, Hecks's own commands for
13
+ # the palette and Eras, and the one background job the Fuzz panel polls. Every method takes the
14
+ # request's JSON body as a Hash and answers a Hash, so the router stays a table.
15
+ #
16
+ # The booted copy is kept between requests and rebuilt when the files or unsaved text change, so
17
+ # the records a Console command made survive until the text does.
18
+ class Panels
19
+ # Replaced, in a command's argument values, by the workspace directory.
20
+ WORKSPACE_TOKEN = "$WORKSPACE".freeze
21
+
22
+ # @param workspace [String] the domain directory the editor opens
23
+ # @param session [Session] the editor's in-memory draft bookkeeping, which keeps the timeline
24
+ def initialize(workspace:, session:)
25
+ @workspace = workspace
26
+ @session = session
27
+ @lock = Mutex.new
28
+ at_exit { close }
29
+ end
30
+
31
+ # Removes the booted copy of the workspace, if one is held.
32
+ # @return [void]
33
+ def close
34
+ @live&.close
35
+ @live = @live_key = nil
36
+ end
37
+
38
+ # @param body [Hash] `buffers`
39
+ # @return [Hash] `chapter` and `commands` (see {Live#commands}), or `error`
40
+ def commands(body) = with_live(body) { |live| { chapter: live.chapter_name, commands: live.commands } }
41
+
42
+ # @param body [Hash] `buffers`, `verb`, `to` and `with`
43
+ # @return [Hash] see {Live#dispatch}, or `error` when the text does not boot
44
+ def dispatch(body)
45
+ with_live(body) { |live| live.dispatch(verb: body.fetch("verb"), to: body["to"], with: body.fetch("with", {})) }
46
+ end
47
+
48
+ # @param body [Hash] `buffers`
49
+ # @return [Hash] `records` by aggregate and `history` of events, or `error`
50
+ def records(body) = with_live(body) { |live| { records: live.records, history: live.history } }
51
+
52
+ # @param body [Hash] `buffers`
53
+ # @return [Hash] see {BehaviorRun.call}
54
+ def behaviors(body) = BehaviorRun.call(dir: @workspace, buffers: body.fetch("buffers", {}))
55
+
56
+ # Carries out one board action on the workspace as it is on screen.
57
+ #
58
+ # @param body [Hash] `buffers`, `op` (see {BoardEdit::OPERATIONS}) and `args`
59
+ # @return [Hash] the patched file as `path`, `text` and `line`, or `error` when the action
60
+ # is refused
61
+ def board_edit(body)
62
+ Workspace.stage(dir: @workspace, buffers: body.fetch("buffers", {})) do |stage|
63
+ BoardEdit.call(stage: stage, action: body.fetch("op"), args: body.fetch("args", {}))
64
+ end
65
+ rescue BoardEdit::Refused => e
66
+ { error: e.message }
67
+ end
68
+
69
+ # @param body [Hash] `path`
70
+ # @return [Hash] `revisions`, newest first (see {Session#timeline})
71
+ def timeline(body) = { revisions: @session.timeline(body.fetch("path")) }
72
+
73
+ # @param _body [Hash] unused
74
+ # @return [Hash] `catalog` of Hecks's commands and queries (see {Toolbox#catalog})
75
+ def catalog(_body) = { catalog: toolbox.catalog }
76
+
77
+ # @param body [Hash] `kind` and `verb`
78
+ # @return [Hash] `help` text
79
+ def help(body) = { help: toolbox.help(kind: body.fetch("kind"), verb: body.fetch("verb")) }
80
+
81
+ # @param body [Hash] `kind`, `verb`, `args` and `confirm`
82
+ # @return [Hash] see {Toolbox#run}
83
+ def run(body) = toolbox.run(**request_of(body))
84
+
85
+ # @param _body [Hash] unused
86
+ # @return [Hash] the era queries over the workspace (see {Toolbox#eras})
87
+ def eras(_body) = toolbox.eras(@workspace)
88
+
89
+ # Starts the background job, unless one is running.
90
+ #
91
+ # @param body [Hash] `kind`, `verb`, `args` and `confirm`
92
+ # @return [Hash] `started`, and `error` when a job was already running
93
+ def start_job(body)
94
+ jobs.start(**request_of(body))
95
+ { started: true }
96
+ rescue Jobs::Busy => e
97
+ { started: false, error: e.message }
98
+ end
99
+
100
+ # @param _body [Hash] unused
101
+ # @return [Hash] the job's state (see {Jobs#state})
102
+ def job_state(_body) = jobs.state
103
+
104
+ # @param _body [Hash] unused
105
+ # @return [Hash] `cancelled`, whether a job was running
106
+ def cancel_job(_body) = { cancelled: jobs.cancel }
107
+
108
+ private
109
+
110
+ def with_live(body)
111
+ @lock.synchronize { yield live_for(body.fetch("buffers", {})) }
112
+ rescue StandardError, ScriptError => e
113
+ { error: e.message.lines.first.to_s.chomp }
114
+ end
115
+
116
+ def live_for(buffers)
117
+ key = digest(buffers)
118
+ return @live if @live && key == @live_key
119
+
120
+ close
121
+ @live = Live.open(dir: @workspace, buffers: buffers)
122
+ @live_key = key
123
+ @live
124
+ end
125
+
126
+ def digest(buffers)
127
+ disk = Workspace.files(@workspace).to_h { |path| [path, File.read(File.join(@workspace, path))] }
128
+ Digest::SHA256.hexdigest(JSON.generate([disk.sort, buffers.sort]))
129
+ end
130
+
131
+ def request_of(body)
132
+ args = body.fetch("args", {}).transform_values { |value| value.to_s.gsub(WORKSPACE_TOKEN, @workspace) }
133
+ { kind: body.fetch("kind"), verb: body.fetch("verb"), args: args, confirm: body["confirm"] }
134
+ end
135
+
136
+ def toolbox = @toolbox ||= Toolbox.new
137
+
138
+ # A second toolbox, so a long job does not hold up the palette.
139
+ def jobs = @jobs ||= Jobs.new(runner: ->(**request) { Toolbox.new.run(**request) })
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,83 @@
1
+ require "open3"
2
+
3
+ module Hecks
4
+ module Composer
5
+ # The fast stage of a check: the Rust parser's verdict on the staged bluebooks, which it gives
6
+ # in milliseconds, before anything is loaded. It reports the first refusal only, with the
7
+ # words that would have been accepted there.
8
+ module Parse
9
+ # The repository root, where the parser's build lands under `rust/parser/target/`.
10
+ ROOT = File.expand_path("../../..", __dir__).freeze
11
+
12
+ # Raised when no `hecks-parse` binary can be found.
13
+ class ParserMissing < StandardError; end
14
+
15
+ module_function
16
+
17
+ # Parses every bluebook in a staged workspace, one chapter at a time.
18
+ #
19
+ # @param stage [String] a staged directory (see {Workspace.stage})
20
+ # @return [Hash, nil] nil when everything parses; otherwise `{file:, line:, message:,
21
+ # expected:}` for the first refusal, with `file` relative to the stage
22
+ # @raise [ParserMissing] if `hecks-parse` is not built
23
+ def call(stage)
24
+ bluebooks(stage).each do |chapter, files|
25
+ refusal = refusal_for(stage, chapter, files)
26
+ return refusal if refusal
27
+ end
28
+ nil
29
+ end
30
+
31
+ # @param stage [String] a staged directory
32
+ # @return [Hash{String => Array<String>}] relative bluebook paths by the chapter their
33
+ # header names
34
+ def bluebooks(stage)
35
+ Workspace.files(stage).grep(/\.bluebook\z/).group_by { |relative| chapter_name(File.join(stage, relative)) }
36
+ .reject { |name, _| name.nil? }
37
+ end
38
+
39
+ # @param path [String] a bluebook file
40
+ # @return [String, nil] the name its `Hecks.bluebook "Name"` header declares
41
+ def chapter_name(path)
42
+ File.foreach(path) { |line| return Regexp.last_match(1) if line =~ /\A\s*Hecks\.bluebook\s+"([^"]+)"/ }
43
+ nil
44
+ end
45
+
46
+ # @api private
47
+ def refusal_for(stage, chapter, files)
48
+ paths = files.map { |relative| File.join(stage, relative) }
49
+ _out, err, status = Open3.capture3(binary, "chapter", "--chapter", chapter, *paths)
50
+ return nil if status.success?
51
+
52
+ diagnostic(err.lines.first.to_s.chomp, stage)
53
+ end
54
+
55
+ # Reads `<file>:<line>: <message>` with an optional `(expected one of: a, b, c)` suffix.
56
+ # @api private
57
+ def diagnostic(line, stage)
58
+ rest = line.delete_prefix("#{stage}/")
59
+ file, number, message = rest.split(":", 3)
60
+ return { file: nil, line: nil, message: line, expected: [] } unless number.to_s.strip.match?(/\A\d+\z/)
61
+
62
+ message = message.to_s.strip
63
+ expected = []
64
+ if (found = message.match(/\A(.*) \(expected one of: (.*)\)\z/))
65
+ message = found[1]
66
+ expected = found[2].split(", ")
67
+ end
68
+ { file: file, line: number.to_i, message: message, expected: expected }
69
+ end
70
+
71
+ # @return [String] the path to `hecks-parse`: `HECKS_PARSE_BIN`, then the release or debug
72
+ # build under `rust/parser/target/`
73
+ # @raise [ParserMissing] if none exists
74
+ def binary
75
+ candidates = [ENV.fetch("HECKS_PARSE_BIN", nil)] +
76
+ %w[release debug].map { |profile| File.join(ROOT, "rust/parser/target/#{profile}/hecks-parse") }
77
+ candidates.compact.find { |path| File.executable?(path) } or
78
+ raise ParserMissing, "hecks-parse is not built — run `cargo build --release " \
79
+ "--manifest-path rust/parser/Cargo.toml`"
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,28 @@
1
+ module Hecks
2
+ module Composer
3
+ # Reduces what a booted domain hands back (records, events, value objects) to plain Hashes,
4
+ # Arrays and scalars, so the page can show it as JSON.
5
+ module Plain
6
+ module_function
7
+
8
+ # @param item [Object] a value object, record, event, or a Hash or Array of them
9
+ # @return [Object] the same, with every value object replaced by its bare value: one of a
10
+ # single field is that field's value, and one of several is a Hash of them
11
+ def call(item)
12
+ case item
13
+ when Runtime::Value then bare(item)
14
+ when Runtime::Instance, Runtime::Event then call(item.to_h)
15
+ when Hash then item.transform_values { |inner| call(inner) }
16
+ when Array then item.map { |inner| call(inner) }
17
+ else item
18
+ end
19
+ end
20
+
21
+ # @api private
22
+ def bare(value)
23
+ fields = call(value.to_h)
24
+ fields.size == 1 ? fields.values.first : fields
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,7 @@
1
+ Hecks.port "composer_agent" do
2
+ verb "asked_by"
3
+ signal :reply
4
+ answers :draft
5
+ answers :wire
6
+ answers :classify
7
+ end
@@ -0,0 +1,141 @@
1
+ require "hecks"
2
+ require_relative "../adapters/driving/admission"
3
+ require_relative "plain"
4
+
5
+ module Hecks
6
+ module Composer
7
+ # The editor's own bookkeeping, held in memory: one Composer chapter booted for the life of the
8
+ # server, with a `Draft` per chapter being edited. The files on disk stay the truth; the draft
9
+ # keeps the text last seen, the conversation about it, and what its last check said, so the
10
+ # page can show a history and an agent's turns survive a refresh.
11
+ class Session
12
+ CHAPTER_DIR = File.expand_path("bluebook", __dir__).freeze
13
+ VERB = "Composer::Draft".freeze
14
+
15
+ # @return [Runtime::Dispatcher] the booted Composer chapter
16
+ attr_reader :runtime
17
+
18
+ # Boots the Composer chapter, which admits only the `Web` driving adapter.
19
+ #
20
+ # @raise [Adapters::Driving::Admission::Refused] if the hecksagon does not admit `Web`
21
+ def initialize
22
+ @runtime = Hecks.boot(CHAPTER_DIR, install_driving: false, environment: "memory")
23
+ Adapters::Driving::Admission.admit!(@runtime.registry, "Web")
24
+ @sequence = Hash.new(0)
25
+ end
26
+
27
+ # Opens a draft for a chapter, or leaves an open one as it is.
28
+ #
29
+ # @param slug [String] the draft's name
30
+ # @param chapter_name [String] the chapter's name
31
+ # @param text [String] the text it starts from
32
+ # @return [void]
33
+ def open(slug:, chapter_name:, text:)
34
+ return if draft(slug)
35
+
36
+ dispatch("Begin", slug: wrap(slug), chapter_name: wrap(chapter_name), text: wrap(text))
37
+ end
38
+
39
+ # Records the text now on screen.
40
+ # @param slug [String] the draft's name
41
+ # @param text [String] the text
42
+ # @return [void]
43
+ def revise(slug:, text:)
44
+ dispatch("Revise", to: slug, text: wrap(text))
45
+ end
46
+
47
+ # Keeps the text of one workspace file, one draft per file with the path as its slug: the
48
+ # first record of a path begins its draft, later ones revise it.
49
+ #
50
+ # @param path [String] the file's path relative to the workspace
51
+ # @param text [String] the text now on screen
52
+ # @return [void]
53
+ def record(path:, text:)
54
+ return revise(slug: path, text: text) if draft(path)
55
+
56
+ self.open(slug: path, chapter_name: chapter_name_in(text), text: text)
57
+ end
58
+
59
+ # The text a file has held, from the chapter's own events.
60
+ #
61
+ # @param path [String] the file's path relative to the workspace
62
+ # @return [Array<Hash>] `name`, `at` and `text` per recorded revision, newest first; empty
63
+ # when the path was never recorded (`text` is nil for an event that carries none)
64
+ def timeline(path)
65
+ @runtime.events.filter_map { |event| revision(event.to_h, path) }.reverse
66
+ end
67
+
68
+ # Records a check's verdict: accepted when it found no refusal, rejected with its words
69
+ # when it did.
70
+ #
71
+ # @param slug [String] the draft's name
72
+ # @param diagnostic [Hash, nil] the check's refusal, or nil
73
+ # @return [void]
74
+ def judge(slug:, diagnostic:)
75
+ return dispatch("Accept", to: slug, note: wrap("ok")) unless diagnostic
76
+
77
+ where = diagnostic[:line] ? "#{diagnostic[:file]}:#{diagnostic[:line]} " : ""
78
+ dispatch("Reject", to: slug, note: wrap(where + diagnostic[:message].to_s))
79
+ end
80
+
81
+ # Puts the author's request into the draft's conversation.
82
+ # @param slug [String] the draft's name
83
+ # @param said [String] the request
84
+ # @return [void]
85
+ def ask(slug:, said:)
86
+ dispatch("Ask", to: slug, sequence: wrap(next_sequence(slug)), said: wrap(said))
87
+ end
88
+
89
+ # Puts the agent's reply into the draft's conversation, with the text it left behind.
90
+ #
91
+ # @param slug [String] the draft's name
92
+ # @param said [String] the reply
93
+ # @param text [String] the text the draft holds after the agent's edit
94
+ # @return [void]
95
+ def answer(slug:, said:, text:)
96
+ dispatch("Answer", to: slug, sequence: wrap(next_sequence(slug)), said: wrap(said), text: wrap(text))
97
+ end
98
+
99
+ # @param slug [String] the draft's name
100
+ # @return [Hash, nil] the draft as the chapter holds it, or nil when none is open
101
+ def draft(slug)
102
+ @runtime.registry.repository("Composer", aggregate).find(slug)&.then { |row| plain(row) }
103
+ rescue StandardError
104
+ nil
105
+ end
106
+
107
+ # The draft in plain terms, for the page.
108
+ #
109
+ # @param slug [String] the draft's name
110
+ # @return [Hash, nil] `status`, `note` and `turns` (`sequence`, `speaker`, `said`), or nil
111
+ def summary(slug)
112
+ held = draft(slug) or return nil
113
+ { status: held[:status], note: Plain.call(held[:note]), turns: Plain.call(Array(held[:turns])) }
114
+ end
115
+
116
+ private
117
+
118
+ # @api private
119
+ def chapter_name_in(text) = text[/Hecks\.\w+\s+"([A-Z][A-Za-z0-9]*)"/, 1] || "Workspace"
120
+
121
+ # @api private
122
+ def revision(event, path)
123
+ return unless event[:aggregate] == VERB && event[:id] == path
124
+
125
+ { name: event[:name], at: event[:occurred_at], text: Plain.call(event[:payload])&.dig(:text) }
126
+ end
127
+
128
+ def aggregate = @runtime.registry.bluebook("Composer").aggregate("Draft")
129
+
130
+ def dispatch(command, to: nil, **facts)
131
+ @runtime.dispatch("#{VERB}.#{command}", to: to, with: facts)
132
+ end
133
+
134
+ def wrap(value) = { value: value }
135
+
136
+ def next_sequence(slug) = @sequence[slug] += 1
137
+
138
+ def plain(row) = row.respond_to?(:to_h) ? row.to_h : row
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,166 @@
1
+ require "hecks"
2
+ require "hecks/composer/parse"
3
+
4
+ module Hecks
5
+ module Composer
6
+ # Runs Hecks's own commands and queries in-process for a command palette and an Eras panel.
7
+ # It is on the hecksagon side like the other composer files: it boots the Hecks chapter once,
8
+ # on first use, and sends argv to the CLI runner one call at a time. A command that is not on
9
+ # the safe list is guarded: it runs only when the caller echoes back a confirmation phrase.
10
+ class Toolbox
11
+ SAFE_COMMANDS = %w[
12
+ model_check_run.model_check operation.run operation.run_behaviors operation.smoke_test
13
+ operation.refresh_projections fuzz_run.fuzz fuzz_run.bench build.check_conformance
14
+ build.fuzz_conformance conformance_run.check_engine_agreement
15
+ conformance_run.measure_doc_coverage kernel_run.measure_kernel_coverage
16
+ style_run.check_comments style_run.check_rust_comments style_run.check_comments_unchanged
17
+ ].freeze
18
+
19
+ ERA_QUERIES = {
20
+ shape: "introspection.shape", scaffold_translation: "era.scaffold_translation",
21
+ audit_translation: "era.audit_translation", compaction: "era.compaction"
22
+ }.freeze
23
+
24
+ # @return [Toolbox] a toolbox that boots the Hecks chapter on first use
25
+ def initialize
26
+ @lock = Mutex.new
27
+ @runtime = nil
28
+ end
29
+
30
+ # Everything the palette can offer, commands and queries together.
31
+ #
32
+ # @return [Array<Hash>] `{kind:, verb:, goal:, guarded:}` sorted by verb, without the
33
+ # commands whose role is System
34
+ def catalog
35
+ (rows("command", commands, :goal) + rows("query", queries, :description))
36
+ .sort_by { |row| [row[:verb], row[:kind]] }
37
+ end
38
+
39
+ # The runner's own help text for one verb.
40
+ #
41
+ # @param kind [String] "command" or "query"
42
+ # @param verb [String] a qualified verb such as `model_check_run.model_check`
43
+ # @return [String] the help text
44
+ def help(kind:, verb:)
45
+ invoke(argv_for(kind, verb, [], help: true)).first
46
+ end
47
+
48
+ # Runs one command or query and says what it printed.
49
+ #
50
+ # @param kind [String] "command" or "query"
51
+ # @param verb [String] a qualified verb
52
+ # @param args [Hash{String => #to_s}] argument names and values, such as `domain.value`
53
+ # @param confirm [String, nil] the phrase `RUN <verb>` that a guarded command requires
54
+ # @return [Hash] `ok`, `status`, `output` and, when nothing ran or the run failed, `error`
55
+ def run(kind:, verb:, args: {}, confirm: nil)
56
+ refusal = refusal_for(kind, verb, args, confirm)
57
+ return refusal if refusal
58
+
59
+ output, status = invoke(argv_for(kind, verb, pairs(args)))
60
+ { ok: status.zero?, status: status, output: output }
61
+ end
62
+
63
+ # The four era readings of a workspace.
64
+ #
65
+ # @param dir [String] the workspace directory
66
+ # @return [Hash{Symbol => Hash}] `shape`, `scaffold_translation`, `audit_translation` and
67
+ # `compaction`, each a {#run} result
68
+ def eras(dir)
69
+ ERA_QUERIES.transform_values { |verb| run(kind: "query", verb: verb, args: { "domain.value" => dir }) }
70
+ end
71
+
72
+ private
73
+
74
+ # @api private
75
+ def rows(kind, items, reader)
76
+ items.map { |verb, item| entry(kind, verb, item.public_send(reader)) }
77
+ end
78
+
79
+ # @api private
80
+ def entry(kind, verb, goal)
81
+ guarded = kind == "command" && !SAFE_COMMANDS.include?(verb)
82
+ { kind: kind, verb: verb, goal: goal.to_s, guarded: guarded }
83
+ end
84
+
85
+ # @api private
86
+ def refusal_for(kind, verb, args, confirm)
87
+ return failure(2, "unknown kind #{kind.inspect}") unless %w[command query].include?(kind)
88
+ return failure(2, "unknown #{kind} #{verb.inspect}") unless known(kind).key?(verb.to_s)
89
+ return failure(2, "arguments may not contain a newline") if newline?(args)
90
+ return unless kind == "command" && !SAFE_COMMANDS.include?(verb) && confirm != "RUN #{verb}"
91
+
92
+ failure(2, "#{verb} is guarded: confirm with \"RUN #{verb}\"")
93
+ end
94
+
95
+ # @api private
96
+ def failure(status, message)
97
+ { ok: false, status: status, output: "", error: message }
98
+ end
99
+
100
+ # @api private
101
+ def newline?(args)
102
+ args.any? { |name, value| [name, value].any? { |text| text.to_s.match?(/[\r\n]/) } }
103
+ end
104
+
105
+ # @api private
106
+ def pairs(args)
107
+ args.map { |name, value| "#{name}=#{value}" }
108
+ end
109
+
110
+ # @api private
111
+ def argv_for(kind, verb, pairs, help: false)
112
+ tail = help ? ["--help"] : pairs
113
+ return ["query", verb, *tail] if kind == "query"
114
+
115
+ [verb, *tail, *(help ? [] : ["--wait"])]
116
+ end
117
+
118
+ # @api private
119
+ def known(kind)
120
+ kind == "query" ? queries : commands
121
+ end
122
+
123
+ # @api private
124
+ def commands
125
+ @commands ||= verbs(:commands).reject { |_verb, command| command.role.to_s == "System" }
126
+ end
127
+
128
+ # @api private
129
+ def queries
130
+ @queries ||= verbs(:queries)
131
+ end
132
+
133
+ # @api private
134
+ def verbs(member)
135
+ chapter.aggregates.flat_map do |aggregate|
136
+ aggregate.public_send(member).map do |item|
137
+ ["#{Naming.snake(aggregate.hecks_name)}.#{Naming.snake(item.hecks_name)}", item]
138
+ end
139
+ end.to_h
140
+ end
141
+
142
+ # @api private
143
+ def chapter
144
+ runtime.registry.bluebook("Hecks")
145
+ end
146
+
147
+ # @api private
148
+ def runtime
149
+ @lock.synchronize { @runtime ||= boot }
150
+ end
151
+
152
+ # @api private
153
+ def boot
154
+ Hecks.boot(File.join(Parse::ROOT, "lib/hecks/hecks"), install_driving: false, environment: "memory")
155
+ end
156
+
157
+ # @api private
158
+ def invoke(argv)
159
+ rt = runtime
160
+ @lock.synchronize do
161
+ Hecks::Adapters::Driving::CliRunner.call(runtime: rt, program: "hecks", argv: argv)
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end