hecks 3.13.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 (440) 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.bluebook +4 -4
  7. data/examples/pizzas/bluebook/pizzas.hecksagon +5 -3
  8. data/examples/pizzas/config.ru +22 -0
  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 +1 -7
  12. data/lib/hecks/bluebook/dsl/attribute_collector.rb +15 -5
  13. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -2
  14. data/lib/hecks/bluebook/dsl/derivation_words.rb +58 -0
  15. data/lib/hecks/bluebook/dsl/translation_builder.rb +6 -37
  16. data/lib/hecks/bluebook/expression/ast_json.rb +0 -9
  17. data/lib/hecks/bluebook/hecksagon.rb +1 -1
  18. data/lib/hecks/bluebook/meta_validator/translation_judge/rules.rb +7 -10
  19. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +4 -8
  20. data/lib/hecks/bluebook/meta_validator.rb +0 -11
  21. data/lib/hecks/bluebook/named_pattern.rb +33 -0
  22. data/lib/hecks/bluebook/translation.rb +18 -10
  23. data/lib/hecks/bluebook/translation_expression/evaluator.rb +63 -0
  24. data/lib/hecks/bluebook/translation_expression/parser.rb +127 -0
  25. data/lib/hecks/bluebook/translation_expression.rb +39 -0
  26. data/lib/hecks/bluebook/translation_plan.rb +70 -0
  27. data/lib/hecks/bluebook.rb +3 -0
  28. data/lib/hecks/chapters.rb +3 -3
  29. data/lib/hecks/cli/domain_stub.rb +1 -1
  30. data/lib/hecks/cli/interview_aggregate.rb +4 -4
  31. data/lib/hecks/composer/adapters/claude_composer.adapter +3 -0
  32. data/lib/hecks/composer/adapters/claude_composer.rb +83 -0
  33. data/lib/hecks/composer/adapters/fake_agent.adapter +3 -0
  34. data/lib/hecks/composer/adapters/fake_agent.rb +55 -0
  35. data/lib/hecks/composer/adapters/jev.adapter +3 -0
  36. data/lib/hecks/composer/adapters/jev.rb +66 -0
  37. data/lib/hecks/composer/agent.rb +113 -0
  38. data/lib/hecks/composer/assistant.rb +110 -0
  39. data/lib/hecks/composer/behavior_run.rb +53 -0
  40. data/lib/hecks/composer/bluebook/composer.bluebook +178 -0
  41. data/lib/hecks/composer/bluebook/composer.hecksagon +16 -0
  42. data/lib/hecks/composer/board.rb +65 -0
  43. data/lib/hecks/composer/board_edit/command_text.rb +39 -0
  44. data/lib/hecks/composer/board_edit/source.rb +84 -0
  45. data/lib/hecks/composer/board_edit.rb +144 -0
  46. data/lib/hecks/composer/check.rb +89 -0
  47. data/lib/hecks/composer/domains.rb +81 -0
  48. data/lib/hecks/composer/jobs.rb +134 -0
  49. data/lib/hecks/composer/layout.rb +37 -0
  50. data/lib/hecks/composer/live.rb +150 -0
  51. data/lib/hecks/composer/outline.rb +48 -0
  52. data/lib/hecks/composer/page.html +989 -0
  53. data/lib/hecks/composer/panels.rb +142 -0
  54. data/lib/hecks/composer/parse.rb +83 -0
  55. data/lib/hecks/composer/plain.rb +28 -0
  56. data/lib/hecks/composer/ports/composer_agent.port +7 -0
  57. data/lib/hecks/composer/session.rb +141 -0
  58. data/lib/hecks/composer/toolbox.rb +166 -0
  59. data/lib/hecks/composer/web.rb +156 -0
  60. data/lib/hecks/composer/workbench.rb +51 -0
  61. data/lib/hecks/composer/workspace.rb +81 -0
  62. data/lib/hecks/composer.rb +17 -0
  63. data/lib/hecks/corpus/adapters.rb +22 -0
  64. data/lib/hecks/corpus/rust.rb +1 -1
  65. data/lib/hecks/corpus/worlds.rb +19 -0
  66. data/lib/hecks/corpus.rb +5 -0
  67. data/lib/hecks/deploy/bluebook/deploy.bluebook +34 -34
  68. data/lib/hecks/forms/app/commands.rb +2 -3
  69. data/lib/hecks/forms/app/queries.rb +2 -3
  70. data/lib/hecks/forms/app/records.rb +17 -5
  71. data/lib/hecks/forms/command_form_renderer.rb +42 -16
  72. data/lib/hecks/forms/field_controls.rb +2 -2
  73. data/lib/hecks/forms/field_renderer.rb +1 -1
  74. data/lib/hecks/forms/field_shape.rb +36 -17
  75. data/lib/hecks/forms/index_renderer.rb +2 -1
  76. data/lib/hecks/forms/page.rb +1 -1
  77. data/lib/hecks/forms/params.rb +0 -13
  78. data/lib/hecks/forms/query_form_renderer.rb +7 -6
  79. data/lib/hecks/forms/record_renderer.rb +11 -7
  80. data/lib/hecks/forms/rule_words.rb +100 -0
  81. data/lib/hecks/forms/value_object_shape.rb +2 -1
  82. data/lib/hecks/framework/bluebook/change.bluebook +2 -2
  83. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -3
  84. data/lib/hecks/fuzzing/domain_generator/block_rendering.rb +1 -1
  85. data/lib/hecks/fuzzing/isolated_boot/worlds.rb +3 -6
  86. data/lib/hecks/fuzzing/replay/filters.rb +2 -6
  87. data/lib/hecks/gate/stages.yml +25 -0
  88. data/lib/hecks/grammar/evolve/seeds.rb +0 -6
  89. data/lib/hecks/grammar/evolve.rb +0 -3
  90. data/lib/hecks/grammar/translation.bluebook +1 -1
  91. data/lib/hecks/hecks/adapters/codebase/composing.rb +56 -0
  92. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +3 -1
  93. data/lib/hecks/hecks/adapters/journal_store/changes.rb +0 -26
  94. data/lib/hecks/hecks/adapters/journal_store/examination.rb +2 -23
  95. data/lib/hecks/hecks/adapters/journal_store/readings.rb +3 -11
  96. data/lib/hecks/hecks/adapters/journal_store.rb +0 -11
  97. data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +4 -11
  98. data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +0 -9
  99. data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +0 -4
  100. data/lib/hecks/hecks/codebase.behaviors +6 -0
  101. data/lib/hecks/hecks/codebase.bluebook +58 -37
  102. data/lib/hecks/hecks/composer.behaviors +68 -0
  103. data/lib/hecks/hecks/context_map.hecksagon +1 -1
  104. data/lib/hecks/hecks/custodian.behaviors +0 -6
  105. data/lib/hecks/hecks/custodian.bluebook +15 -92
  106. data/lib/hecks/hecks/hecks.bluebook +2 -2
  107. data/lib/hecks/hecks/hecks.hecksagon +2 -2
  108. data/lib/hecks/hecks/hecks.world +1 -1
  109. data/lib/hecks/hecks/rehearsal.bluebook +10 -16
  110. data/lib/hecks/hecks/tooling.bluebook +22 -22
  111. data/lib/hecks/kernel_capabilities/templates.rb +4 -4
  112. data/lib/hecks/kernel_capabilities.rb +1 -1
  113. data/lib/hecks/language/bluebook/aggregate.bluebook +1 -0
  114. data/lib/hecks/language/bluebook/command.bluebook +1 -0
  115. data/lib/hecks/language/bluebook/entity.bluebook +1 -0
  116. data/lib/hecks/language/bluebook/query.bluebook +1 -0
  117. data/lib/hecks/language/bluebook/shape.bluebook +8 -7
  118. data/lib/hecks/language/bluebook/vocabulary.bluebook +2 -3
  119. data/lib/hecks/language/hecksagon/port_operation.bluebook +1 -0
  120. data/lib/hecks/language/translation/translation_aggregate.bluebook +21 -24
  121. data/lib/hecks/ports/access_control.rb +2 -2
  122. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +3 -41
  123. data/lib/hecks/ports/persistence/plugins/era/lineage/coverage.rb +7 -7
  124. data/lib/hecks/ports/persistence/plugins/era/lineage/rewrite.rb +25 -2
  125. data/lib/hecks/ports/persistence/plugins/era/lineage/rules.rb +4 -4
  126. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +17 -25
  127. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/base_sql.rb +0 -12
  128. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/domain_rename.rb +1 -1
  129. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +2 -22
  130. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +0 -1
  131. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +9 -3
  132. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +3 -29
  133. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/refusals.rb +2 -26
  134. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -33
  135. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -3
  136. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +2 -4
  137. data/lib/hecks/ports/persistence/plugins/era/translation/expression_sql.rb +52 -0
  138. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +23 -29
  139. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +1 -1
  140. data/lib/hecks/ports/persistence/plugins/era/translation.rb +0 -1
  141. data/lib/hecks/ports/persistence/plugins/era.rb +3 -6
  142. data/lib/hecks/ports.rb +0 -1
  143. data/lib/hecks/projection_files.rb +3 -1
  144. data/lib/hecks/projections/deploy/templates/lambda/nat_comment_oauth.tmpl +1 -1
  145. data/lib/hecks/projections/glossary/entries.rb +10 -4
  146. data/lib/hecks/projections/glossary/groups.rb +42 -0
  147. data/lib/hecks/projections/glossary/html/page.rb +7 -2
  148. data/lib/hecks/projections/glossary/html/parser.rb +1 -1
  149. data/lib/hecks/projections/glossary/html/sections.rb +31 -11
  150. data/lib/hecks/projections/glossary/html.rb +1 -2
  151. data/lib/hecks/projections/glossary/markdown.rb +31 -28
  152. data/lib/hecks/projections/glossary/mermaid.rb +3 -0
  153. data/lib/hecks/projections/glossary/page.css +143 -153
  154. data/lib/hecks/projections/glossary/page.js +63 -3
  155. data/lib/hecks/projections/glossary/sentences/actions.rb +88 -0
  156. data/lib/hecks/projections/glossary/sentences/paths.rb +77 -0
  157. data/lib/hecks/projections/glossary/sentences/queries.rb +90 -0
  158. data/lib/hecks/projections/glossary/sentences/value_objects.rb +32 -5
  159. data/lib/hecks/projections/glossary/sentences.rb +13 -3
  160. data/lib/hecks/projections/glossary/slugs.rb +4 -1
  161. data/lib/hecks/projections/glossary.rb +2 -1
  162. data/lib/hecks/projections/plain_english.rb +97 -0
  163. data/lib/hecks/projections/rust_vocabulary/arguments.rb +2 -2
  164. data/lib/hecks/projections/rust_vocabulary/enum_body.rb +20 -25
  165. data/lib/hecks/projections/rust_vocabulary/tests.rb +2 -2
  166. data/lib/hecks/projections/rust_vocabulary.rb +14 -6
  167. data/lib/hecks/projections/site/cms_editor/color.rb +0 -6
  168. data/lib/hecks/projections/statements/actions.rb +99 -0
  169. data/lib/hecks/projections/statements/relationships.rb +86 -0
  170. data/lib/hecks/projections/statements.rb +15 -24
  171. data/lib/hecks/projector/exporter/adapters.rb +112 -0
  172. data/lib/hecks/projector/exporter/translations.rb +18 -35
  173. data/lib/hecks/projector/exporter/worlds.rb +103 -0
  174. data/lib/hecks/projector/exporter.rb +5 -1
  175. data/lib/hecks/quality_control/adapters/agent.rb +6 -7
  176. data/lib/hecks/quality_control/adapters/agent_profile.rb +16 -114
  177. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +1 -1
  178. data/lib/hecks/quality_control/quality_control.bluebook +51 -51
  179. data/lib/hecks/runtime/loader/boot_steps.rb +0 -20
  180. data/lib/hecks/runtime/outbox.rb +2 -9
  181. data/lib/hecks/runtime/port_operation_interpreter.rb +1 -9
  182. data/lib/hecks/runtime/remote_dispatcher.rb +2 -2
  183. data/lib/hecks/runtime/value.rb +13 -0
  184. data/lib/hecks/rust_build/project_rust/target_ir.rb +23 -4
  185. data/lib/hecks/rust_build/wasm.rb +0 -12
  186. data/lib/hecks/rust_build/write_if_changed.rb +12 -12
  187. data/lib/hecks/rust_format.rb +77 -0
  188. data/lib/hecks/site/bluebook/site.bluebook +5 -5
  189. data/lib/hecks/sme/bluebook/sme.bluebook +12 -12
  190. data/lib/hecks/tickets/bluebook/tickets.bluebook +3 -3
  191. data/lib/hecks/tools/fuzz_sweep/findings.rb +3 -9
  192. data/lib/hecks/tools/fuzz_sweep.rb +1 -3
  193. data/lib/hecks/tools/translation_audit/verdicts.rb +3 -28
  194. data/lib/hecks/tools/translation_audit.rb +8 -10
  195. data/lib/hecks/tools/translation_scaffold/reporting.rb +1 -1
  196. data/lib/hecks/version.rb +1 -1
  197. data/lib/hecks/vocabulary.rb +2 -1
  198. data/lib/hecks.rb +3 -0
  199. data/prebuilt/{58208399bada10b0db99928c48e38789288fc58f7b71a72b21bfa386680e32ed.marshal → 02c09c73b272c1fb5a9ae6d87bb5e5e529208e00f6b4b597dc2c0fd70a9c0964.marshal} +0 -0
  200. data/prebuilt/{2b59737fbdbd5f44edda0fddce2611f45d76087b3a7c782d1f1a17ea58c77948.marshal → bb84a835cf6a15136bad5ca03aa5bf93a5b92f5f72444618d3ef2412b987fb32.marshal} +0 -0
  201. data/prebuilt/verdicts-f1f10da14c8e8f01b4c9b7e3e2d74be8cd2bf4676364ed17fa16975d2a076d75.json +1 -0
  202. data/rust/Cargo.toml +40 -0
  203. data/rust/build/Cargo.toml +36 -0
  204. data/rust/build/clippy.toml +4 -0
  205. data/rust/build/src/adapters_pass.rs +196 -0
  206. data/rust/build/src/binding_pass.rs +54 -13
  207. data/rust/build/src/build_artifact.rs +23 -5
  208. data/rust/build/src/cargo_sync.rs +69 -26
  209. data/rust/build/src/lineage_pass.rs +101 -87
  210. data/rust/build/src/main.rs +11 -2
  211. data/rust/build/src/pipeline.rs +80 -19
  212. data/rust/build/src/reserved_names.rs +6 -50
  213. data/rust/build/src/resolve.rs +82 -35
  214. data/rust/build/src/rustfmt.rs +100 -0
  215. data/rust/build/src/seams_pass.rs +85 -16
  216. data/rust/build/src/subprocess.rs +30 -6
  217. data/rust/build/src/tmp.rs +5 -2
  218. data/rust/build/src/translation_pass.rs +72 -22
  219. data/rust/build/src/world_pass.rs +109 -0
  220. data/rust/clippy.toml +4 -0
  221. data/rust/codegen/Cargo.toml +36 -0
  222. data/rust/codegen/clippy.toml +4 -0
  223. data/rust/codegen/src/asks.rs +40 -14
  224. data/rust/codegen/src/attr.rs +2 -2
  225. data/rust/codegen/src/bridging.rs +676 -117
  226. data/rust/codegen/src/commands.rs +979 -373
  227. data/rust/codegen/src/constraints.rs +88 -25
  228. data/rust/codegen/src/dependency_planning.rs +128 -34
  229. data/rust/codegen/src/domain_generator.rs +300 -170
  230. data/rust/codegen/src/exemplar.rs +87 -32
  231. data/rust/codegen/src/expr_emitter.rs +54 -6
  232. data/rust/codegen/src/fielded.rs +193 -41
  233. data/rust/codegen/src/hecks_naming.rs +124 -16
  234. data/rust/codegen/src/hecks_naming_tests.rs +81 -0
  235. data/rust/codegen/src/json.rs +80 -44
  236. data/rust/codegen/src/json_codec.rs +585 -149
  237. data/rust/codegen/src/literal.rs +104 -138
  238. data/rust/codegen/src/main.rs +145 -52
  239. data/rust/codegen/src/manifest.rs +76 -14
  240. data/rust/codegen/src/mutations.rs +1212 -239
  241. data/rust/codegen/src/naming.rs +111 -60
  242. data/rust/codegen/src/optional_pass.rs +90 -28
  243. data/rust/codegen/src/ports.rs +5 -4
  244. data/rust/codegen/src/prelude.rs +139 -49
  245. data/rust/codegen/src/queries.rs +601 -226
  246. data/rust/codegen/src/reactions.rs +268 -74
  247. data/rust/codegen/src/read_models.rs +606 -205
  248. data/rust/codegen/src/reference_specs.rs +16 -3
  249. data/rust/codegen/src/registry.rs +128 -79
  250. data/rust/codegen/src/reserved_names.rs +6 -50
  251. data/rust/codegen/src/shared.rs +71 -22
  252. data/rust/codegen/src/sidecars.rs +15 -5
  253. data/rust/codegen/src/skip_reason.rs +8 -2
  254. data/rust/codegen/src/types.rs +306 -108
  255. data/rust/host/Cargo.lock +1 -0
  256. data/rust/host/Cargo.toml +40 -4
  257. data/rust/host/HECKS_RELEASE +1 -1
  258. data/rust/host/clippy.toml +4 -0
  259. data/rust/host/src/api.rs +698 -218
  260. data/rust/host/src/auth.rs +491 -214
  261. data/rust/host/src/b64.rs +91 -0
  262. data/rust/host/src/bin/expr_harness.rs +6 -2
  263. data/rust/host/src/bin/lineage_harness.rs +47 -11
  264. data/rust/host/src/bin/mint_harness.rs +70 -37
  265. data/rust/host/src/bin/validate_harness.rs +16 -8
  266. data/rust/host/src/boot.rs +144 -68
  267. data/rust/host/src/boundary_fuzz/api.rs +130 -32
  268. data/rust/host/src/boundary_fuzz/auth.rs +108 -23
  269. data/rust/host/src/boundary_fuzz/expr_json.rs +85 -11
  270. data/rust/host/src/boundary_fuzz/ir.rs +164 -12
  271. data/rust/host/src/boundary_fuzz/server.rs +283 -36
  272. data/rust/host/src/boundary_fuzz/web.rs +56 -8
  273. data/rust/host/src/checkout.rs +78 -32
  274. data/rust/host/src/clock.rs +8 -0
  275. data/rust/host/src/commerce_ir.rs +333 -95
  276. data/rust/host/src/crypto.rs +33 -0
  277. data/rust/host/src/dispatch.rs +990 -331
  278. data/rust/host/src/expr_json.rs +1021 -273
  279. data/rust/host/src/extension.rs +24 -4
  280. data/rust/host/src/field_hints.rs +11 -7
  281. data/rust/host/src/fuzz_support.rs +85 -13
  282. data/rust/host/src/host.rs +100 -0
  283. data/rust/host/src/http.rs +22 -0
  284. data/rust/host/src/ir.rs +326 -16
  285. data/rust/host/src/journal.rs +277 -176
  286. data/rust/host/src/lambda_client.rs +219 -53
  287. data/rust/host/src/lib.rs +16 -4
  288. data/rust/host/src/log.rs +62 -11
  289. data/rust/host/src/mint.rs +1153 -326
  290. data/rust/host/src/needs.rs +185 -44
  291. data/rust/host/src/payments/keystore.rs +197 -45
  292. data/rust/host/src/payments/roles.rs +191 -0
  293. data/rust/host/src/payments/tests.rs +1445 -275
  294. data/rust/host/src/payments.rs +409 -121
  295. data/rust/host/src/platform_signin/attack_tests.rs +5 -5
  296. data/rust/host/src/platform_signin/config.rs +11 -11
  297. data/rust/host/src/platform_signin/cookie.rs +2 -2
  298. data/rust/host/src/platform_signin/extension.rs +2 -1
  299. data/rust/host/src/platform_signin/handler.rs +9 -8
  300. data/rust/host/src/platform_signin/jti.rs +4 -2
  301. data/rust/host/src/platform_signin/jwks.rs +2 -1
  302. data/rust/host/src/platform_signin/jws.rs +2 -37
  303. data/rust/host/src/platform_signin/known_answer_tests.rs +5 -1
  304. data/rust/host/src/platform_signin/mode_tests.rs +5 -3
  305. data/rust/host/src/platform_signin/pages.rs +20 -20
  306. data/rust/host/src/platform_signin/session.rs +8 -1
  307. data/rust/host/src/platform_signin/settings.rs +6 -6
  308. data/rust/host/src/platform_signin/strict_json.rs +12 -9
  309. data/rust/host/src/platform_signin/verify.rs +41 -41
  310. data/rust/host/src/platform_signin/wiring_support.rs +13 -18
  311. data/rust/host/src/presentation.rs +207 -63
  312. data/rust/host/src/presentation_write.rs +502 -187
  313. data/rust/host/src/query_step.rs +69 -18
  314. data/rust/host/src/rate_limit.rs +541 -142
  315. data/rust/host/src/reference_transform.rs +77 -25
  316. data/rust/host/src/reference_validate.rs +443 -104
  317. data/rust/host/src/resend.rs +190 -55
  318. data/rust/host/src/secrets.rs +116 -21
  319. data/rust/host/src/server.rs +483 -104
  320. data/rust/host/src/storage_shape.rs +88 -21
  321. data/rust/host/src/test_pg.rs +17 -7
  322. data/rust/host/src/ui_schema.rs +495 -207
  323. data/rust/host/src/wasm_runner.rs +21 -6
  324. data/rust/host/src/web/commerce.rs +101 -23
  325. data/rust/host/src/web/newsletter/cooldown.rs +10 -3
  326. data/rust/host/src/web/newsletter/template.rs +26 -7
  327. data/rust/host/src/web/newsletter/tests.rs +112 -23
  328. data/rust/host/src/web/newsletter.rs +209 -59
  329. data/rust/host/src/web/newsletter_send.rs +251 -79
  330. data/rust/host/src/web/registration_receipt/tests.rs +16 -4
  331. data/rust/host/src/web/registration_receipt.rs +100 -24
  332. data/rust/host/src/web/registrations.rs +1384 -332
  333. data/rust/host/src/web/resend_webhook.rs +138 -80
  334. data/rust/host/src/web.rs +2416 -508
  335. data/rust/lsp/Cargo.toml +36 -0
  336. data/rust/lsp/clippy.toml +4 -0
  337. data/rust/lsp/src/json.rs +29 -22
  338. data/rust/lsp/src/main.rs +81 -31
  339. data/rust/lsp/src/outline.rs +23 -7
  340. data/rust/lsp/src/rpc.rs +17 -4
  341. data/rust/parser/Cargo.toml +36 -0
  342. data/rust/parser/clippy.toml +4 -0
  343. data/rust/parser/src/build/closed_sets.rs +6 -1
  344. data/rust/parser/src/build/identity.rs +4 -3
  345. data/rust/parser/src/build/mod.rs +1 -0
  346. data/rust/parser/src/build/named_pattern.rs +26 -0
  347. data/rust/parser/src/build/naming.rs +2 -80
  348. data/rust/parser/src/build/pattern_subset.rs +6 -8
  349. data/rust/parser/src/build/query_derive.rs +3 -1
  350. data/rust/parser/src/build/query_inference.rs +14 -5
  351. data/rust/parser/src/build/read_model.rs +4 -1
  352. data/rust/parser/src/build/references.rs +4 -3
  353. data/rust/parser/src/canonical.rs +34 -15
  354. data/rust/parser/src/emit.rs +187 -105
  355. data/rust/parser/src/expr/ast_json.rs +134 -42
  356. data/rust/parser/src/expr/evaluator.rs +27 -9
  357. data/rust/parser/src/expr/mod.rs +54 -13
  358. data/rust/parser/src/expr/resolver.rs +299 -92
  359. data/rust/parser/src/ir.rs +35 -3
  360. data/rust/parser/src/keywords.rs +7168 -510
  361. data/rust/parser/src/lex.rs +11 -25
  362. data/rust/parser/src/lib.rs +1 -0
  363. data/rust/parser/src/main.rs +65 -6
  364. data/rust/parser/src/parse/adapter.rs +157 -0
  365. data/rust/parser/src/parse/aggregate.rs +19 -10
  366. data/rust/parser/src/parse/chapter.rs +16 -17
  367. data/rust/parser/src/parse/command.rs +35 -16
  368. data/rust/parser/src/parse/domain_port.rs +4 -7
  369. data/rust/parser/src/parse/entity.rs +21 -11
  370. data/rust/parser/src/parse/hecksagon.rs +10 -4
  371. data/rust/parser/src/parse/lifecycle.rs +9 -5
  372. data/rust/parser/src/parse/mod.rs +43 -91
  373. data/rust/parser/src/parse/needs.rs +21 -12
  374. data/rust/parser/src/parse/policy.rs +2 -2
  375. data/rust/parser/src/parse/process_manager.rs +9 -11
  376. data/rust/parser/src/parse/query.rs +8 -4
  377. data/rust/parser/src/parse/read_model.rs +57 -16
  378. data/rust/parser/src/parse/value_object.rs +7 -2
  379. data/rust/parser/src/parse/world.rs +322 -0
  380. data/rust/parser/src/ruby_value.rs +7 -6
  381. data/rust/src/exemplar/json.rs +4 -4
  382. data/rust/src/exemplar/mutations.rs +18 -0
  383. data/rust/src/exemplar/read_models.rs +1 -10
  384. data/rust/src/kernel/attribute_shapes/composite.rs +54 -14
  385. data/rust/src/kernel/attribute_shapes/list.rs +2 -2
  386. data/rust/src/kernel/attribute_shapes/mod.rs +8 -8
  387. data/rust/src/kernel/attribute_shapes/scalar.rs +2 -2
  388. data/rust/src/kernel/cli.rs +229 -112
  389. data/rust/src/kernel/dispatch.rs +323 -115
  390. data/rust/src/kernel/dispatch_guard_tests.rs +327 -65
  391. data/rust/src/kernel/expr.rs +203 -86
  392. data/rust/src/kernel/expression_operators/arithmetic.rs +138 -25
  393. data/rust/src/kernel/expression_operators/comparison.rs +87 -2
  394. data/rust/src/kernel/expression_operators/enumeration.rs +106 -22
  395. data/rust/src/kernel/expression_operators/logical.rs +9 -3
  396. data/rust/src/kernel/expression_operators/membership.rs +3 -1
  397. data/rust/src/kernel/expression_operators/mod.rs +34 -21
  398. data/rust/src/kernel/expression_operators/pattern_match.rs +46 -11
  399. data/rust/src/kernel/expression_operators/positional.rs +84 -30
  400. data/rust/src/kernel/expression_operators/presence.rs +52 -13
  401. data/rust/src/kernel/expression_operators/sign_test.rs +3 -1
  402. data/rust/src/kernel/expression_operators/sized.rs +24 -8
  403. data/rust/src/kernel/expression_operators/text.rs +120 -27
  404. data/rust/src/kernel/expression_operators/to_string.rs +10 -4
  405. data/rust/src/kernel/json.rs +230 -148
  406. data/rust/src/kernel/mod.rs +46 -33
  407. data/rust/src/kernel/named_query.rs +63 -15
  408. data/rust/src/kernel/needs.rs +112 -25
  409. data/rust/src/kernel/orchestrate.rs +980 -581
  410. data/rust/src/kernel/orchestrate_guard_tests.rs +538 -140
  411. data/rust/src/kernel/pattern.rs +107 -34
  412. data/rust/src/kernel/query_comparators.rs +118 -41
  413. data/rust/src/kernel/query_ordering.rs +13 -5
  414. data/rust/src/kernel/read_model.rs +479 -181
  415. data/rust/src/kernel/reference_lookup.rs +122 -26
  416. data/rust/src/kernel/repository.rs +293 -66
  417. data/rust/src/kernel/routing.rs +58 -42
  418. data/rust/src/kernel/ruby_float.rs +139 -0
  419. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +57 -57
  420. data/rust/src/kernel/vocab/entity_dispatch_order.rs +54 -54
  421. data/rust/src/kernel/vocab/field_hint.rs +19 -25
  422. data/rust/src/kernel/vocab/mod.rs +6 -6
  423. data/rust/src/kernel/vocab/query_comparator.rs +23 -24
  424. data/rust/src/kernel/vocab/refusal_template.rs +501 -297
  425. data/rust/src/lib.rs +50 -0
  426. data/rust/src/main.rs +4 -1
  427. data/rust/web/Cargo.toml +36 -0
  428. data/rust/web/clippy.toml +4 -0
  429. metadata +85 -15
  430. data/examples/pizzas/bluebook/hecksagon/mock_stripe_payment_adapter.rb +0 -36
  431. data/lib/hecks/adapters/driven/google_authentication.adapter +0 -3
  432. data/lib/hecks/adapters/driven/google_authentication.rb +0 -99
  433. data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +0 -3
  434. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +0 -32
  435. data/lib/hecks/ports/authentication.port +0 -6
  436. data/lib/hecks/ports/authentication.rb +0 -67
  437. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +0 -255
  438. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +0 -22
  439. data/prebuilt/verdicts-48318ca172edd21443b1ad02801c697fff100b746e828f09db987647641a5df2.json +0 -1
  440. data/rust/host/src/approval.rs +0 -652
@@ -203,17 +203,6 @@ module Hecks
203
203
  registry
204
204
  end
205
205
 
206
- # Whether `grammar_registry`'s memoized singleton has finished its
207
- # fixpoint judge and attached-chapter load.
208
- #
209
- # A cache keyed on "finished" instead of its own inputs already missed
210
- # data for a whole build window once.
211
- #
212
- # @return [Boolean]
213
- def self.grammar_registry_ready?
214
- @grammar_registry && @grammar_ready_for == @grammar_registry.object_id
215
- end
216
-
217
206
  # Loads every `ATTACHED_GRAMMAR_DIR` chapter file into `registry`,
218
207
  # judged the ordinary way since the fixpoint has already run.
219
208
  #
@@ -0,0 +1,33 @@
1
+ module Hecks
2
+ module Bluebook
3
+ # The patterns a bluebook names instead of spelling: `matches: :snake_name` says what
4
+ # `pattern: '^[a-z][a-z0-9_]*$'` does. A name stands for exactly its regex, so the IR, the
5
+ # generated Rust and the runtime check see the pattern and never the name.
6
+ #
7
+ # `rust/parser/src/build/named_pattern.rs` holds the same table; spec/named_pattern_spec.rb
8
+ # holds the two to each other.
9
+ module NamedPattern
10
+ # Name => the regex source it stands for, each the exact source the corpus spelled by hand.
11
+ TABLE = {
12
+ present: '[^ \t\n\r]',
13
+ not_empty: '[^\t\n\r]',
14
+ no_whitespace: '^[^ \t\n\r]+$',
15
+ snake_name: "^[a-z][a-z0-9_]*$",
16
+ git_sha: "^[0-9a-fA-F]{7,40}$",
17
+ digits: "^[0-9]+$"
18
+ }.freeze
19
+
20
+ module_function
21
+
22
+ # @param name [Symbol, String] a name in `TABLE`
23
+ # @return [String] the regex source it stands for
24
+ # @raise [ArgumentError] when the name is not one the language gives
25
+ def source(name)
26
+ TABLE.fetch(name.to_sym) do
27
+ raise ArgumentError, "matches: #{name.inspect} is not a named pattern — " \
28
+ "the names are #{TABLE.keys.map(&:inspect).join(", ")}"
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -16,13 +16,16 @@ module Hecks
16
16
  # The attribute kept its name, so `rename`/`move` cannot express it.
17
17
  TranslationRetype = Struct.new(:from, :to)
18
18
 
19
- # A computed transform whose only implementation is a SQL expression run inside
20
- # the compiled Postgres head; an aggregate carrying one refuses to boot elsewhere.
21
- TranslationCompute = Struct.new(:from, :to, :sql)
19
+ # A new field computed from the record's own text by an expression, read before any other
20
+ # rule runs. The fields the expression reads stay where they are; `drop` removes them.
21
+ # `expression` is the source text and `tree` its parsed plan form.
22
+ TranslationDerive = Struct.new(:to, :expression) do
23
+ def tree = TranslationExpression.parse(expression)
24
+ end
22
25
 
23
- # The aggregate's own key is recomputed by a SQL expression; stored state is untouched.
24
- # Postgres-only like `compute`, and it has no `from:`/`to:` path.
25
- TranslationRekey = Struct.new(:sql)
26
+ # The aggregate's id is the value of the field a `derive` writes, for every record where that
27
+ # derive has a value; a record where it has none keeps its id.
28
+ TranslationRekey = Struct.new(:to)
26
29
 
27
30
  # A newly added, required attribute with no source in old data.
28
31
  # `default` is what an existing record reads until a command writes a real value;
@@ -32,12 +35,12 @@ module Hecks
32
35
  # One aggregate's part of a translation between eras.
33
36
  # `drops` makes data loss a declared decision.
34
37
  class TranslationAggregate
35
- attr_reader :name, :was, :renames, :moves, :converts, :drops, :retypes, :computes, :rekeys, :backfills
38
+ attr_reader :name, :was, :renames, :moves, :converts, :drops, :retypes, :derives, :rekeys, :backfills
36
39
 
37
40
  # Every optional field and what it holds when the declaration omits it.
38
41
  FIELD_DEFAULTS = {
39
42
  was: nil, renames: {}, moves: [], converts: [], drops: [], retypes: [],
40
- computes: [], rekeys: [], backfills: []
43
+ derives: [], rekeys: [], backfills: []
41
44
  }.freeze
42
45
 
43
46
  # @param name [String, Symbol] the aggregate's name in the destination era
@@ -47,8 +50,8 @@ module Hecks
47
50
  # @param converts [Array<Bluebook::TranslationConvert>] fields remapped via a lookup table
48
51
  # @param drops [Array<Symbol>] fields deliberately not carried forward
49
52
  # @param retypes [Array<Bluebook::TranslationRetype>] value object or entity type changes
50
- # @param computes [Array<Bluebook::TranslationCompute>] fields computed by Postgres-only SQL
51
- # @param rekeys [Array<Bluebook::TranslationRekey>] SQL recomputing the aggregate's identity
53
+ # @param derives [Array<Bluebook::TranslationDerive>] fields computed from the record's text
54
+ # @param rekeys [Array<Bluebook::TranslationRekey>] the aggregate's identity, recomputed
52
55
  # @param backfills [Array<Bluebook::TranslationBackfill>] new required fields, no old source
53
56
  def initialize(name:, **given)
54
57
  KeywordFields.assign(self, KeywordFields.fill(given, FIELD_DEFAULTS))
@@ -83,6 +86,11 @@ module Hecks
83
86
  # @return [Bluebook::TranslationAggregate, nil] the aggregate's translation, or
84
87
  # `nil` if `name` carries no translation rules
85
88
  def for_aggregate(name) = @aggregates.find { |aggregate| aggregate.name == name.to_s }
89
+
90
+ # This edge as plain data: each aggregate's rules as an ordered list of ops.
91
+ #
92
+ # @return [Hash] see `Bluebook::TranslationPlan`
93
+ def to_plan = TranslationPlan.build(self)
86
94
  end
87
95
  end
88
96
  end
@@ -0,0 +1,63 @@
1
+ module Hecks
2
+ module Bluebook
3
+ module TranslationExpression
4
+ # Computes a parsed expression for one record's state. The reference semantics the
5
+ # compiled Postgres form is held to.
6
+ module Evaluator
7
+ module_function
8
+
9
+ # @param tree [Array] a tree `TranslationExpression.parse` returned
10
+ # @param state [Hash] the record's stored state
11
+ # @return [String, nil] the computed text; nil when the expression has no value
12
+ def call(tree, state)
13
+ case tree.first
14
+ when "lit" then tree[1]
15
+ when "null" then nil
16
+ when "ref" then reference(tree[1], state)
17
+ else apply(tree.first, tree.drop(1), state)
18
+ end
19
+ end
20
+
21
+ # Reads a dotted path out of a record; only text counts as a value.
22
+ def reference(path, state)
23
+ value = path.split(".").inject(state) { |held, key| held.is_a?(Hash) ? member(held, key) : nil }
24
+ value.is_a?(String) ? value : nil
25
+ end
26
+
27
+ # A record's top-level keys are symbols, a value object's are strings.
28
+ def member(hash, key) = hash.key?(key.to_sym) ? hash[key.to_sym] : hash[key]
29
+
30
+ def apply(name, arguments, state)
31
+ return lookup(arguments, state) if name == "lookup"
32
+ return replace(arguments, state) if name == "replace"
33
+
34
+ values = arguments.map { |argument| call(argument, state) }
35
+ return nil if values.any?(&:nil?)
36
+
37
+ fold(name, values)
38
+ end
39
+
40
+ def fold(name, values)
41
+ case name
42
+ when "lower" then values.first.downcase(:ascii)
43
+ when "upper" then values.first.upcase(:ascii)
44
+ else values.join
45
+ end
46
+ end
47
+
48
+ def replace(arguments, state)
49
+ target, from, to = arguments
50
+ call(target, state)&.gsub(from.last) { to.last }
51
+ end
52
+
53
+ def lookup(arguments, state)
54
+ key, table, fallback = arguments
55
+ value = call(key, state)
56
+ return nil if value.nil?
57
+
58
+ table.fetch(value) { call(fallback, state) }
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,127 @@
1
+ require "json"
2
+ require "strscan"
3
+
4
+ module Hecks
5
+ module Bluebook
6
+ module TranslationExpression
7
+ # Reads the text of an expression into its tree, refusing anything outside the language.
8
+ class Parser
9
+ NAME = /[a-z_][a-z0-9_]*(?:\.[a-z_][a-z0-9_]*)*/
10
+ STRING = /"(?:[^"\\]|\\.)*"/
11
+
12
+ # @param text [String] the expression's source text
13
+ def initialize(text)
14
+ @text = text.to_s
15
+ @scan = StringScanner.new(@text)
16
+ end
17
+
18
+ # @return [Array] the expression tree
19
+ # @raise [TranslationExpression::Refused] if the text is not a well-formed expression
20
+ def call
21
+ tree = expression
22
+ skip
23
+ refuse("unexpected #{@scan.rest[0, 12].inspect} after the expression") unless @scan.eos?
24
+ tree
25
+ end
26
+
27
+ private
28
+
29
+ def expression
30
+ skip
31
+ return ["lit", string] if @scan.check(/"/)
32
+
33
+ name = @scan.scan(NAME) || refuse("expected a field, a string or a function")
34
+ return function(name) if @scan.check(/\s*\(/)
35
+
36
+ name == "null" ? ["null"] : ["ref", name]
37
+ end
38
+
39
+ def function(name)
40
+ refuse("#{name} is not a function (#{FUNCTIONS.keys.join(", ")})") unless FUNCTIONS.key?(name)
41
+ punct("(")
42
+ arguments = list { argument(name, _1) }
43
+ punct(")")
44
+ check_arguments(name, arguments)
45
+ [name, *arguments]
46
+ end
47
+
48
+ def argument(name, index)
49
+ skip
50
+ return table if name == "lookup" && index == 1
51
+
52
+ expression
53
+ end
54
+
55
+ def list
56
+ items = []
57
+ loop do
58
+ items << yield(items.size)
59
+ skip
60
+ break unless @scan.skip(/,/)
61
+ end
62
+ items
63
+ end
64
+
65
+ def table
66
+ punct("{")
67
+ pairs = list { pair }
68
+ punct("}")
69
+ refuse("a lookup table repeats a key") unless pairs.map(&:first).uniq.size == pairs.size
70
+ pairs.to_h
71
+ end
72
+
73
+ def pair
74
+ skip
75
+ key = string
76
+ punct(":")
77
+ skip
78
+ [key, string]
79
+ end
80
+
81
+ def string
82
+ token = @scan.scan(STRING) || refuse("expected a string")
83
+ JSON.parse(token, quirks_mode: true)
84
+ rescue JSON::ParserError
85
+ refuse("#{token} is not a well-formed string")
86
+ end
87
+
88
+ def punct(mark)
89
+ skip
90
+ @scan.skip(Regexp.new(Regexp.escape(mark))) || refuse("expected #{mark.inspect}")
91
+ end
92
+
93
+ def skip = @scan.skip(/\s+/)
94
+
95
+ def check_arguments(name, arguments)
96
+ arity = FUNCTIONS.fetch(name)
97
+ refuse("#{name} takes #{arity} expressions, not #{arguments.size}") if arity && arguments.size != arity
98
+ refuse("concat takes two or more expressions") if arity.nil? && arguments.size < 2
99
+ check_literals(name, arguments)
100
+ end
101
+
102
+ def check_literals(name, arguments)
103
+ case name
104
+ when "replace" then check_replace(arguments)
105
+ when "lookup" then check_lookup(arguments)
106
+ end
107
+ end
108
+
109
+ def check_replace(arguments)
110
+ _target, from, to = arguments
111
+ refuse("replace needs string literals for what to find and what to put") unless [from, to].all? { _1.first == "lit" }
112
+ refuse("replace cannot look for an empty string") if from.last.empty?
113
+ end
114
+
115
+ def check_lookup(arguments)
116
+ _key, table, fallback = arguments
117
+ refuse("lookup needs a table of at least one pair") unless table.is_a?(Hash) && !table.empty?
118
+ refuse("lookup needs a string literal or null as its fallback") unless %w[lit null].include?(fallback.first)
119
+ end
120
+
121
+ def refuse(why)
122
+ raise Refused, "#{why} (in #{@text.inspect} at offset #{@scan.pos})"
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,39 @@
1
+ require_relative "translation_expression/parser"
2
+ require_relative "translation_expression/evaluator"
3
+
4
+ module Hecks
5
+ module Bluebook
6
+ # The expression a `derive` rule computes a new field from: text functions over the record's
7
+ # stored text, with no storage engine in sight. It parses to a tree of plain arrays, which
8
+ # is also its plan form:
9
+ #
10
+ # ["ref", "name.value"] ["lit", "text"] ["null"]
11
+ # ["lower", x] ["upper", x] ["concat", x, y, ...]
12
+ # ["replace", x, ["lit", from], ["lit", to]]
13
+ # ["lookup", x, {"key" => "value"}, ["lit", default] | ["null"]]
14
+ #
15
+ # A reference that is missing or not text has no value, and a function with a valueless
16
+ # argument has none either.
17
+ module TranslationExpression
18
+ # Raised when the text of an expression is not one the language accepts.
19
+ class Refused < StandardError; end
20
+
21
+ # The functions and how many expressions each takes; `nil` means "two or more".
22
+ FUNCTIONS = { "lower" => 1, "upper" => 1, "concat" => nil, "replace" => 3, "lookup" => 3 }.freeze
23
+
24
+ # Parses an expression's text into its tree.
25
+ #
26
+ # @param text [String] the expression, e.g. `lower(name.value)`
27
+ # @return [Array] the expression tree
28
+ # @raise [Refused] if the text is not a well-formed expression
29
+ def self.parse(text) = Parser.new(text).call
30
+
31
+ # Computes an expression's value for one record's state.
32
+ #
33
+ # @param tree [Array] a tree `parse` returned
34
+ # @param state [Hash] the record's stored state
35
+ # @return [String, nil] the text it computes; nil when it has no value
36
+ def self.evaluate(tree, state) = Evaluator.call(tree, state)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,70 @@
1
+ module Hecks
2
+ module Bluebook
3
+ # A translation as data: each aggregate's rules as an ordered list of ops any runtime can
4
+ # interpret, with nothing in them that names a storage engine. In the order they apply to a
5
+ # record, each op has its name under "op" plus:
6
+ #
7
+ # derive "to" "expression" (a tree) rename "from" "to" move "from" "to" (dotted paths)
8
+ # convert "from" "to" "values" ([old, new] pairs) drop "path" retype "from" "to"
9
+ # backfill "path" "default" rekey_to "to"
10
+ module TranslationPlan
11
+ VERSION = 1
12
+
13
+ # The op names, in the order they apply; one per rule list of a `TranslationAggregate`.
14
+ OPS = %w[derive rename move convert drop retype backfill rekey_to].freeze
15
+
16
+ module_function
17
+
18
+ # @param translation [Bluebook::Translation] a declared translation edge
19
+ # @return [Hash] the edge as plain data, ready for `JSON.generate`
20
+ def build(translation)
21
+ {
22
+ "version" => VERSION, "domain" => translation.domain, "from" => translation.from.to_s,
23
+ "to" => translation.to.to_s, "retired" => translation.retired.map(&:to_s),
24
+ "aggregates" => translation.aggregates.map { |aggregate| aggregate_plan(aggregate) }
25
+ }
26
+ end
27
+
28
+ def aggregate_plan(aggregate)
29
+ { "name" => aggregate.name, "was" => aggregate.was, "ops" => ops(aggregate) }
30
+ end
31
+
32
+ def ops(aggregate)
33
+ [*derives(aggregate), *renames(aggregate), *moves(aggregate), *converts(aggregate), *drops(aggregate),
34
+ *retypes(aggregate), *backfills(aggregate), *rekeys(aggregate)]
35
+ end
36
+
37
+ def derives(aggregate)
38
+ aggregate.derives.map { |d| { "op" => "derive", "to" => d.to, "expression" => d.tree } }
39
+ end
40
+
41
+ def renames(aggregate)
42
+ aggregate.renames.map { |from, to| { "op" => "rename", "from" => from.to_s, "to" => to.to_s } }
43
+ end
44
+
45
+ def moves(aggregate)
46
+ aggregate.moves.map { |m| { "op" => "move", "from" => m.from, "to" => m.to } }
47
+ end
48
+
49
+ def converts(aggregate)
50
+ aggregate.converts.map do |c|
51
+ { "op" => "convert", "from" => c.from, "to" => c.to, "values" => c.values.map { |old, new| [old, new] } }
52
+ end
53
+ end
54
+
55
+ def drops(aggregate) = aggregate.drops.map { |path| { "op" => "drop", "path" => path.to_s } }
56
+
57
+ def retypes(aggregate)
58
+ aggregate.retypes.map { |r| { "op" => "retype", "from" => r.from, "to" => r.to } }
59
+ end
60
+
61
+ def backfills(aggregate)
62
+ aggregate.backfills.map { |b| { "op" => "backfill", "path" => b.name.to_s, "default" => b.default } }
63
+ end
64
+
65
+ def rekeys(aggregate)
66
+ aggregate.rekeys.map { |rekey| { "op" => "rekey_to", "to" => rekey.to } }
67
+ end
68
+ end
69
+ end
70
+ end
@@ -29,7 +29,9 @@ require_relative "bluebook/ask_resolution"
29
29
  require_relative "bluebook/process_manager"
30
30
  require_relative "bluebook/aggregate"
31
31
  require_relative "bluebook/hecksagon"
32
+ require_relative "bluebook/translation_expression"
32
33
  require_relative "bluebook/translation"
34
+ require_relative "bluebook/translation_plan"
33
35
 
34
36
  require_relative "bluebook/assembly/contract"
35
37
  require_relative "bluebook/assembly/contracts"
@@ -44,6 +46,7 @@ require_relative "bluebook/project_loader"
44
46
 
45
47
  require_relative "bluebook/dsl"
46
48
  require_relative "bluebook/pattern_subset"
49
+ require_relative "bluebook/named_pattern"
47
50
 
48
51
  require_relative "bluebook/meta_validator"
49
52
  require_relative "bluebook/meta_validator/plan"
@@ -5,8 +5,8 @@ require_relative "bluebook/meta_validator"
5
5
 
6
6
  module Hecks
7
7
  # The chapters the gem carries that a hecksagon can attach by name (ADR 0080): the language
8
- # declared in itself, Expression, Tenancy, Deploy, Site, Tickets and QualityControl. Framework
9
- # members stay in `Framework`; `table` and `attach!` find a name across both.
8
+ # declared in itself, Expression, Tenancy, Deploy, Site, Tickets, Composer and QualityControl.
9
+ # Framework members stay in `Framework`; `table` and `attach!` find a name across both.
10
10
  #
11
11
  # A chapter is named by the `Hecks.bluebook "Name"` header of its files, and may span several.
12
12
  # Beside its bluebook a chapter may carry what every hecksagon attaching it needs, whatever
@@ -16,7 +16,7 @@ module Hecks
16
16
  # Where attachable chapters live, relative to `lib/hecks/`.
17
17
  GLOBS = %w[language/**/*.bluebook grammar/expression.bluebook tenancy/bluebook/*.bluebook
18
18
  deploy/bluebook/*.bluebook site/bluebook/*.bluebook tickets/bluebook/*.bluebook
19
- quality_control/*.bluebook].freeze
19
+ composer/bluebook/*.bluebook quality_control/*.bluebook].freeze
20
20
 
21
21
  # Every attachable chapter, by name, with the files that declare it.
22
22
  #
@@ -96,7 +96,7 @@ module Hecks
96
96
  attribute :name, ExampleName
97
97
 
98
98
  value_object "ExampleName" do
99
- attribute :value, String, pattern: '[^ \\t\\n\\r]'
99
+ attribute :value, String, matches: :present
100
100
  end
101
101
 
102
102
  # TODO: replace Create with the first thing that happens to an Example.
@@ -6,8 +6,8 @@ module Hecks
6
6
  # The bluebook text of one accepted thing: its aggregate, with its value objects, commands and
7
7
  # lifecycle. `InterviewDraft` extends it, and supplies the naming and citing helpers it calls.
8
8
  module InterviewAggregate
9
- # The pattern every generated string field carries: it refuses a blank value.
10
- PATTERN = %q('[^ \t\n\r]').freeze
9
+ # The option every generated string field carries: it refuses a blank value.
10
+ PRESENT = "matches: :present".freeze
11
11
 
12
12
  # What stands in for a creating command nothing in the interview named.
13
13
  STUB_NOTE = " # TODO: no action that creates was accepted; replace Create with the real one.".freeze
@@ -15,7 +15,7 @@ module Hecks
15
15
  # One accepted thing: what it is called and identified by, its fields, its transitions, the
16
16
  # actions that create it and the others.
17
17
  Shape = Struct.new(:name, :identifier, :type, :fields, :steps, :creating, :rest, keyword_init: true)
18
- private_constant :PATTERN, :STUB_NOTE, :Shape
18
+ private_constant :PRESENT, :STUB_NOTE, :Shape
19
19
 
20
20
  # @api private
21
21
  def aggregate(interview, thing, actions)
@@ -66,7 +66,7 @@ module Hecks
66
66
  # @api private
67
67
  def type_lines(shape)
68
68
  [" value_object #{shape.type.inspect} do",
69
- " attribute :value, String, pattern: #{PATTERN}", " end",
69
+ " attribute :value, String, #{PRESENT}", " end",
70
70
  *InterviewShape.value_objects(shape.fields)]
71
71
  end
72
72
 
@@ -0,0 +1,3 @@
1
+ Hecks.adapter "ClaudeComposer" do
2
+ port "composer_agent"
3
+ end
@@ -0,0 +1,83 @@
1
+ require "json"
2
+ require "open3"
3
+ require "timeout"
4
+
5
+ require_relative "../agent"
6
+ require_relative "jev"
7
+
8
+ module Hecks
9
+ module Adapters
10
+ # Real `composer_agent` fulfillment for `draft` and `wire`: the developer's own `claude` run
11
+ # as the `hecks-composer` agent, which edits the workspace's files in place. Transport only;
12
+ # whether the edit is any good is the Composer's own check to judge.
13
+ #
14
+ # The agent's write guard confines it to domain files inside the git checkout, so the
15
+ # workspace must sit inside one.
16
+ module ClaudeComposer
17
+ TIMEOUT_SECONDS = 300
18
+
19
+ AGENT = "hecks-composer".freeze
20
+
21
+ DRAFT = "Edit the domain files in %<dir>s as the request below says. Change only what it asks " \
22
+ "for, write the bluebook first, then any hecksagon wiring it needs. Reply with one line " \
23
+ "saying what you changed.".freeze
24
+
25
+ WIRE = "Change how the chapter in %<dir>s is wired, as the request below says: edit only its " \
26
+ ".hecksagon and .world files, and bind adapters that already exist. Reply with one line " \
27
+ "saying what you changed.".freeze
28
+
29
+ module_function
30
+
31
+ # @param prompt [String] what the author asked for
32
+ # @param dir [String] the workspace directory
33
+ # @return [Hash] `"note"` the agent's account of what it changed
34
+ # @raise [Ports::Agent::Unavailable] see {Composer::Agent::Unavailable}
35
+ def draft(prompt:, dir:) = call(DRAFT, prompt, dir)
36
+
37
+ # @param prompt [String] what the author asked for
38
+ # @param dir [String] the workspace directory
39
+ # @return [Hash] `"note"` the agent's account of what it changed
40
+ def wire(prompt:, dir:) = call(WIRE, prompt, dir)
41
+
42
+ # Naming what a prompt asks for is the `jev` categorizer's work, which this adapter lends.
43
+ #
44
+ # @param prompt [String] what the author typed
45
+ # @return [Hash] `"intent"` one of {Composer::Agent::INTENTS}
46
+ # @raise [Composer::Agent::Unavailable] if `jev` cannot answer
47
+ def classify(prompt:) = Jev.classify(prompt: prompt)
48
+
49
+ # @api private
50
+ def call(task, prompt, dir)
51
+ root = checkout_of(dir)
52
+ message = "#{format(task, dir: dir)}\n\nRequest: #{prompt}"
53
+ stdout, status = run(root, message)
54
+ raise Composer::Agent::Unavailable, "claude exited #{status.exitstatus}: #{stdout}" unless status.success?
55
+
56
+ { "note" => JSON.parse(stdout).fetch("result").to_s.lines.first.to_s.strip }
57
+ rescue JSON::ParserError, KeyError => e
58
+ raise Composer::Agent::ValidationError, "claude's reply was not the expected envelope: #{e.message}"
59
+ end
60
+
61
+ # Argv spawn, never a shell, so a `claude` shell alias cannot apply.
62
+ # @api private
63
+ def run(root, message)
64
+ Timeout.timeout(TIMEOUT_SECONDS) do
65
+ Open3.capture2("claude", "-p", "--agent", AGENT, "--output-format", "json",
66
+ "--permission-mode", "acceptEdits", stdin_data: message, chdir: root)
67
+ end
68
+ rescue Timeout::Error
69
+ raise Composer::Agent::Unavailable, "claude did not answer within #{TIMEOUT_SECONDS}s"
70
+ rescue Errno::ENOENT => e
71
+ raise Composer::Agent::Unavailable, "claude is not on PATH: #{e.message}"
72
+ end
73
+
74
+ # @api private
75
+ def checkout_of(dir)
76
+ top, status = Open3.capture2("git", "-C", dir, "rev-parse", "--show-toplevel")
77
+ raise Composer::Agent::Unavailable, "#{dir} is not inside a git checkout" unless status.success?
78
+
79
+ top.strip
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,3 @@
1
+ Hecks.adapter "FakeAgent" do
2
+ port "composer_agent"
3
+ end
@@ -0,0 +1,55 @@
1
+ require_relative "../agent"
2
+
3
+ module Hecks
4
+ module Adapters
5
+ # A scripted `composer_agent`, for specs and for trying the editor with no model behind it.
6
+ # Each ask is recorded; `draft` and `wire` run a script that may rewrite the workspace's files,
7
+ # so a test sees the same thing the real agent leaves behind.
8
+ module FakeAgent
9
+ class << self
10
+ # @return [Array<Hash>] every ask, as `{ask:, prompt:, dir:}` in the order made
11
+ def calls = @calls ||= []
12
+
13
+ # @return [#call, nil] takes `(prompt, dir)` and edits files under `dir`; may return a note
14
+ attr_accessor :edit
15
+
16
+ # @return [#call, nil] takes `prompt` and answers an intent
17
+ attr_accessor :intent
18
+
19
+ # Forgets the calls and both scripts.
20
+ # @return [void]
21
+ def reset!
22
+ @calls = []
23
+ @edit = nil
24
+ @intent = nil
25
+ end
26
+ end
27
+
28
+ module_function
29
+
30
+ # @param prompt [String] what the author asked for
31
+ # @param dir [String] the workspace directory
32
+ # @return [Hash] `"note"` what the script reported
33
+ def draft(prompt:, dir:) = edited(:draft, prompt, dir)
34
+
35
+ # @param prompt [String] what the author asked for
36
+ # @param dir [String] the workspace directory
37
+ # @return [Hash] `"note"` what the script reported
38
+ def wire(prompt:, dir:) = edited(:wire, prompt, dir)
39
+
40
+ # @param prompt [String] what the author typed
41
+ # @return [Hash] `"intent"` what the script answered, `"edit"` without one
42
+ def classify(prompt:)
43
+ FakeAgent.calls << { ask: :classify, prompt: prompt, dir: nil }
44
+ { "intent" => FakeAgent.intent ? FakeAgent.intent.call(prompt) : "edit" }
45
+ end
46
+
47
+ # @api private
48
+ def edited(ask, prompt, dir)
49
+ FakeAgent.calls << { ask: ask, prompt: prompt, dir: dir }
50
+ note = FakeAgent.edit&.call(prompt, dir)
51
+ { "note" => note.is_a?(String) ? note : "changed nothing" }
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,3 @@
1
+ Hecks.adapter "Jev" do
2
+ port "composer_agent"
3
+ end