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
@@ -1,93 +1,47 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "tmpdir"
4
-
5
3
  module Hecks
6
4
  module Adapters
7
- # What one `Agent` run may do: which tools it holds, where it may write, what it may reach on
8
- # the network, which environment variables it sees and how long it may run.
5
+ # What one `Agent` run may do: which tools it holds, where it may write, which environment
6
+ # variables it sees and how long it may run.
9
7
  #
10
- # Confinement uses the macOS sandbox. Where there is none, a confined run refuses to start
11
- # rather than run unconfined.
8
+ # Confinement is the permission rules of the `claude` command itself, so a run keeps the
9
+ # user's own login. Only the default `claude` command understands them; `Agent` refuses to
10
+ # run any other command under a profile.
12
11
  class AgentProfile
13
- # What the network rule lets through: nothing, outbound HTTPS and name lookups, or anything.
14
- NETWORKS = %i[none https any].freeze
15
-
16
- # How a run is confined: under the operating system's sandbox, or by the permission rules
17
- # of the `claude` command itself. Only the second leaves the user's own login usable, since
18
- # the sandbox refuses the keychain and `~/.claude`.
19
- CONFINEMENTS = %i[sandbox permissions].freeze
20
-
21
12
  # The variables every confined agent sees besides the ones its profile names.
22
13
  BASE_ENV = %w[PATH HOME USER LOGNAME SHELL LANG LC_ALL TERM TMPDIR].freeze
23
14
 
24
- # The tools that write files; a permissions-confined run holds them only through path rules.
15
+ # The tools that write files; a confined run holds them only through path rules.
25
16
  WRITING_TOOLS = %w[Write Edit MultiEdit NotebookEdit].freeze
26
17
 
27
- # Home-relative places an agent may not read: credentials and the harness's own state.
28
- CREDENTIAL_PATHS = %w[
29
- .ssh .aws .gnupg .claude .config/gh .config/op .gem/credentials .netrc Library/Keychains
30
- ].freeze
31
-
32
- SANDBOX = "/usr/bin/sandbox-exec"
33
-
34
- # The keywords `new` takes besides the four lists, with what each defaults to.
35
- LIMIT_DEFAULTS = { timeout: nil, budget: nil, confinement: :sandbox }.freeze
36
-
37
18
  # @return [Array<String>] the tools the agent is given
38
19
  attr_reader :tools
39
- # @return [Array<String>] the directories the agent may write, besides the temp directory
20
+ # @return [Array<String>] the directories the agent may write
40
21
  attr_reader :writable
41
- # @return [Symbol] one of `NETWORKS`
42
- attr_reader :network
43
22
  # @return [Array<String>] the environment variable names the agent may see besides `BASE_ENV`
44
23
  attr_reader :env
45
24
  # @return [Integer, nil] seconds the run may take; nil for no limit
46
25
  attr_reader :timeout
47
26
  # @return [Float, nil] the most the run may spend, in dollars, for an agent that takes one
48
27
  attr_reader :budget
49
- # @return [Symbol] one of `CONFINEMENTS`
50
- attr_reader :confinement
51
28
 
52
29
  # @param tools [Array<String>] the tools the agent is given
53
30
  # @param writable [Array<String>] directories the agent may write
54
- # @param network [Symbol] one of `NETWORKS`
55
31
  # @param env [Array<String>] environment variable names to pass through
56
- # @param limits [Hash] `timeout:` (Integer, nil) seconds the run may take, `budget:`
57
- # (Float, nil) dollars it may spend, `confinement:` one of `CONFINEMENTS` (`network`
58
- # applies to the sandbox only)
59
- # @raise [ArgumentError] when `network` or `confinement` is not one it knows
60
- def initialize(tools: [], writable: [], network: :none, env: [], **limits)
61
- limits = LIMIT_DEFAULTS.merge(reject_unknown(limits))
62
- ensure_known(:network, NETWORKS, network)
63
- ensure_known(:confinement, CONFINEMENTS, limits[:confinement])
32
+ # @param timeout [Integer, nil] seconds the run may take
33
+ # @param budget [Float, nil] dollars it may spend
34
+ def initialize(tools: [], writable: [], env: [], timeout: nil, budget: nil)
64
35
  @tools = tools.dup.freeze
65
36
  @writable = writable.dup.freeze
66
- @network = network
67
37
  @env = env.dup.freeze
68
- @timeout, @budget, @confinement = limits.values_at(:timeout, :budget, :confinement)
69
- end
70
-
71
- # @return [Boolean] whether this profile is enforced by the operating system's sandbox
72
- def sandboxed?
73
- confinement == :sandbox
74
- end
75
-
76
- # @return [Boolean] whether this machine can apply this profile's confinement
77
- def available?
78
- !sandboxed? || File.executable?(SANDBOX)
79
- end
80
-
81
- # @param words [Array<String>] the agent's command
82
- # @return [Array<String>] `words`, run under the sandbox with this profile's policy; the
83
- # words unchanged when the confinement is the command's own permission rules
84
- def confine(words)
85
- sandboxed? ? [SANDBOX, "-p", policy, *words] : words
38
+ @timeout = timeout
39
+ @budget = budget
86
40
  end
87
41
 
88
42
  # @return [String] the `claude` permission mode this profile runs under
89
43
  def permission_mode
90
- sandboxed? ? "acceptEdits" : "dontAsk"
44
+ "dontAsk"
91
45
  end
92
46
 
93
47
  # @param source [Hash{String => String}] the environment to take values from
@@ -96,79 +50,27 @@ module Hecks
96
50
  source.to_h.slice(*BASE_ENV, *env)
97
51
  end
98
52
 
99
- # The `claude` flags this profile's tools and budget become.
53
+ # The `claude` flags this profile's tools and budget become. The writing tools are held only
54
+ # through an `Edit` rule per writable directory, which every file-writing tool answers to.
100
55
  #
101
56
  # @return [Array<String>]
102
57
  def tool_flags
103
58
  flags = tools.empty? ? [] : ["--tools", tools.join(","), "--allowedTools", *allowed_tools]
104
- flags += ["--strict-mcp-config"] unless sandboxed?
59
+ flags += ["--strict-mcp-config"]
105
60
  flags + (budget ? ["--max-budget-usd", budget.to_s] : [])
106
61
  end
107
62
 
108
- # @return [String] the sandbox policy: reads open but for credentials, writes where allowed
109
- def policy
110
- [
111
- "(version 1)", "(allow default)",
112
- "(deny file-write*)", write_rule, "(deny file-read*#{paths_clause(credential_paths)})",
113
- *network_rules
114
- ].join("\n")
115
- end
116
-
117
63
  private
118
64
 
119
- # The tools allowed without asking. A permissions-confined run holds the writing tools only
120
- # through an `Edit` rule per writable directory, which every file-writing tool answers to.
121
65
  def allowed_tools
122
- return [tools.join(",")] if sandboxed?
123
-
124
66
  tools - WRITING_TOOLS + writable.map { |path| "Edit(/#{real(path)}/**)" }
125
67
  end
126
68
 
127
- def write_rule
128
- allowed = [real(Dir.tmpdir), *writable.map { |path| real(path) }]
129
- devices = '(literal "/dev/null") (literal "/dev/tty") (regex #"^/dev/fd/")'
130
- "(allow file-write*#{paths_clause(allowed)} #{devices})"
131
- end
132
-
133
- def credential_paths
134
- CREDENTIAL_PATHS.map { |path| File.join(Dir.home, path) }
135
- end
136
-
137
- def paths_clause(paths)
138
- paths.map { |path| " (subpath #{quote(path)})" }.join
139
- end
140
-
141
- def network_rules
142
- case network
143
- when :any then []
144
- when :https then ["(deny network*)", '(allow network-outbound (remote tcp "*:443") (remote udp "*:53"))',
145
- '(allow network-outbound (remote unix-socket (path-literal "/private/var/run/mDNSResponder")))']
146
- else ["(deny network*)"]
147
- end
148
- end
149
-
150
- def reject_unknown(limits)
151
- unknown = limits.keys - LIMIT_DEFAULTS.keys
152
- return limits if unknown.empty?
153
-
154
- raise ArgumentError, "unknown keyword#{"s" if unknown.size > 1}: #{unknown.map(&:inspect).join(", ")}"
155
- end
156
-
157
- def ensure_known(name, allowed, value)
158
- return if allowed.include?(value)
159
-
160
- raise ArgumentError, "#{name} must be one of #{allowed.inspect}, not #{value.inspect}"
161
- end
162
-
163
69
  def real(path)
164
70
  File.realpath(path)
165
71
  rescue SystemCallError
166
72
  File.expand_path(path)
167
73
  end
168
-
169
- def quote(path)
170
- %("#{path.gsub("\\", "\\\\\\\\").gsub('"', '\\"')}")
171
- end
172
74
  end
173
75
  end
174
76
  end
@@ -116,7 +116,7 @@ module Hecks
116
116
  # The profile `--confine` runs the agent under: it writes only the candidates directory.
117
117
  def confinement_profile
118
118
  FileUtils.mkdir_p(@out_dir)
119
- Hecks::Adapters::AgentProfile.new(confinement: :permissions, tools: CONFINED_TOOLS, writable: [@out_dir],
119
+ Hecks::Adapters::AgentProfile.new(tools: CONFINED_TOOLS, writable: [@out_dir],
120
120
  timeout: CONFINED_TIMEOUT, budget: CONFINED_BUDGET)
121
121
  end
122
122
 
@@ -54,13 +54,13 @@ Hecks.bluebook "QualityControl" do
54
54
  attribute :reason, TargetReason
55
55
 
56
56
  value_object "TargetReference" do
57
- attribute :value, String, pattern: '[^ \t\n\r]'
57
+ attribute :value, String, matches: :present
58
58
  invariant("a target is referenced") { !value.to_s.empty? }
59
59
  end
60
60
 
61
61
  # Required: a name can be disputed, a path can be opened.
62
62
  value_object "TargetPath" do
63
- attribute :value, String, pattern: '[^ \t\n\r]'
63
+ attribute :value, String, matches: :present
64
64
  invariant("a target says where it is") { !value.to_s.empty? }
65
65
  end
66
66
 
@@ -87,7 +87,7 @@ Hecks.bluebook "QualityControl" do
87
87
  end
88
88
 
89
89
  value_object "TargetReason" do
90
- attribute :value, String, pattern: '[^ \t\n\r]'
90
+ attribute :value, String, matches: :present
91
91
  invariant("shelving a target says why") { !value.to_s.empty? }
92
92
  end
93
93
 
@@ -99,7 +99,7 @@ Hecks.bluebook "QualityControl" do
99
99
 
100
100
  # One mode name, as `EligibleFor`'s argument; a query attribute, never stored.
101
101
  value_object "Capability" do
102
- attribute :value, String, pattern: '[^ \t\n\r]'
102
+ attribute :value, String, matches: :present
103
103
  invariant("a mode is named") { !value.to_s.empty? }
104
104
  end
105
105
 
@@ -300,7 +300,7 @@ Hecks.bluebook "QualityControl" do
300
300
  end
301
301
 
302
302
  value_object "DomainPath" do
303
- attribute :value, String, pattern: '[^\t\n\r]'
303
+ attribute :value, String, matches: :not_empty
304
304
 
305
305
  invariant("a domain is named by its path") { !value.to_s.empty? }
306
306
  end
@@ -361,12 +361,12 @@ Hecks.bluebook "QualityControl" do
361
361
  attribute :checks, list_of(Check)
362
362
 
363
363
  value_object "SweepReference" do
364
- attribute :value, String, pattern: '[^ \t\n\r]'
364
+ attribute :value, String, matches: :present
365
365
  invariant("a sweep is referenced") { !value.to_s.empty? }
366
366
  end
367
367
 
368
368
  value_object "Engineer" do
369
- attribute :value, String, pattern: '[^ \t\n\r]'
369
+ attribute :value, String, matches: :present
370
370
  invariant("a sweep says who ran it") { !value.to_s.empty? }
371
371
  end
372
372
 
@@ -381,13 +381,13 @@ Hecks.bluebook "QualityControl" do
381
381
  end
382
382
 
383
383
  value_object "SweepNotes" do
384
- attribute :value, String, pattern: '[^ \t\n\r]'
384
+ attribute :value, String, matches: :present
385
385
  # The number is in the sentence because the sentence is the refusal message.
386
386
  invariant("a sweep says what it learned, in at least 40 characters") { value.to_s.size >= 40 }
387
387
  end
388
388
 
389
389
  value_object "WaiverReason" do
390
- attribute :value, String, pattern: '[^ \t\n\r]'
390
+ attribute :value, String, matches: :present
391
391
  invariant("a waived gate says why") { !value.to_s.empty? }
392
392
  end
393
393
 
@@ -395,7 +395,7 @@ Hecks.bluebook "QualityControl" do
395
395
  # literals, so `spec/quality_control_spec.rb` pins the two equal. "nobody" is refused: unsigned
396
396
  # is anonymous.
397
397
  value_object "WaivedBy" do
398
- attribute :value, String, pattern: '[^ \t\n\r]'
398
+ attribute :value, String, matches: :present
399
399
  invariant("a gate is waived by a person, never by the loop") { value != "qa_sweep" }
400
400
  invariant("a waiver is signed") { value != "nobody" }
401
401
  end
@@ -418,13 +418,13 @@ Hecks.bluebook "QualityControl" do
418
418
  end
419
419
 
420
420
  value_object "Subject" do
421
- attribute :value, String, pattern: '[^ \t\n\r]'
421
+ attribute :value, String, matches: :present
422
422
  invariant("a check names what it put to the system") { !value.to_s.empty? }
423
423
  end
424
424
 
425
425
  # Written before the answer exists: an expectation fitted to the answer cannot be surprised.
426
426
  value_object "Expectation" do
427
- attribute :value, String, pattern: '[^ \t\n\r]'
427
+ attribute :value, String, matches: :present
428
428
  invariant("a check says what it expected") { !value.to_s.empty? }
429
429
  end
430
430
 
@@ -637,37 +637,37 @@ Hecks.bluebook "QualityControl" do
637
637
  end
638
638
 
639
639
  value_object "DomainPath" do
640
- attribute :value, String, pattern: '[^\t\n\r]'
640
+ attribute :value, String, matches: :not_empty
641
641
 
642
642
  invariant("a domain is named by its path") { !value.to_s.empty? }
643
643
  end
644
644
 
645
645
  value_object "DataPath" do
646
- attribute :value, String, pattern: '[^\t\n\r]'
646
+ attribute :value, String, matches: :not_empty
647
647
 
648
648
  invariant("a data directory is named by its path") { !value.to_s.empty? }
649
649
  end
650
650
 
651
651
  value_object "DatabaseName" do
652
- attribute :value, String, pattern: '[^ \t\n\r]'
652
+ attribute :value, String, matches: :present
653
653
 
654
654
  invariant("a database is named") { !value.to_s.empty? }
655
655
  end
656
656
 
657
657
  value_object "RoleName" do
658
- attribute :value, String, pattern: '[^ \t\n\r]'
658
+ attribute :value, String, matches: :present
659
659
 
660
660
  invariant("a role is named") { !value.to_s.empty? }
661
661
  end
662
662
 
663
663
  value_object "SchemaName" do
664
- attribute :value, String, pattern: '[^ \t\n\r]'
664
+ attribute :value, String, matches: :present
665
665
 
666
666
  invariant("a schema is named") { !value.to_s.empty? }
667
667
  end
668
668
 
669
669
  value_object "StepVerb" do
670
- attribute :value, String, pattern: '[^ \t\n\r]'
670
+ attribute :value, String, matches: :present
671
671
 
672
672
  invariant("a step names its verb") { !value.to_s.empty? }
673
673
  end
@@ -770,7 +770,7 @@ Hecks.bluebook "QualityControl" do
770
770
  attribute :window, StaleAfter
771
771
 
772
772
  value_object "BugReference" do
773
- attribute :value, String, pattern: '[^ \t\n\r]'
773
+ attribute :value, String, matches: :present
774
774
  invariant("a bug is referenced") { !value.to_s.empty? }
775
775
  end
776
776
 
@@ -783,56 +783,56 @@ Hecks.bluebook "QualityControl" do
783
783
  # value object sharing a word with any aggregate resolves to that class once the other domain
784
784
  # boots.
785
785
  value_object "BugTitle" do
786
- attribute :value, String, pattern: '[^ \t\n\r]'
786
+ attribute :value, String, matches: :present
787
787
  invariant("a bug is titled") { !value.to_s.empty? }
788
788
  end
789
789
 
790
790
  # Required at `Log`, which is what makes the one absolute rule absolute.
791
791
  value_object "Demonstration" do
792
- attribute :value, String, pattern: '[^ \t\n\r]'
792
+ attribute :value, String, matches: :present
793
793
  invariant("a bug is proven by a test somebody can re-run") { !value.to_s.empty? }
794
794
  end
795
795
 
796
796
  value_object "Symptom" do
797
- attribute :value, String, pattern: '[^ \t\n\r]'
797
+ attribute :value, String, matches: :present
798
798
  invariant("a bug says what actually happened") { !value.to_s.empty? }
799
799
  end
800
800
 
801
801
  value_object "Expectation" do
802
- attribute :value, String, pattern: '[^ \t\n\r]'
802
+ attribute :value, String, matches: :present
803
803
  invariant("a bug says what should have happened") { !value.to_s.empty? }
804
804
  end
805
805
 
806
806
  value_object "BugSite" do
807
- attribute :value, String, pattern: '[^ \t\n\r]'
807
+ attribute :value, String, matches: :present
808
808
  invariant("an investigation names where the bug lives") { !value.to_s.empty? }
809
809
  end
810
810
 
811
811
  value_object "BugCause" do
812
- attribute :value, String, pattern: '[^ \t\n\r]'
812
+ attribute :value, String, matches: :present
813
813
  invariant("an investigation says why the code is wrong") { !value.to_s.empty? }
814
814
  end
815
815
 
816
816
  # 7 to 40 hex: a fix that lives only in a working tree is not a fix.
817
817
  value_object "CommitRef" do
818
- attribute :value, String, pattern: '^[0-9a-fA-F]{7,40}$'
818
+ attribute :value, String, matches: :git_sha
819
819
  end
820
820
 
821
821
  # What was actually run, e.g. "rspec --order random, 1335 examples, 0 failures, seed 12345":
822
822
  # a count and a seed are checkable, "tests pass" is not.
823
823
  value_object "Verification" do
824
- attribute :value, String, pattern: '[^ \t\n\r]'
824
+ attribute :value, String, matches: :present
825
825
  invariant("a verification says what was actually run") { !value.to_s.empty? }
826
826
  end
827
827
 
828
828
  value_object "BugReason" do
829
- attribute :value, String, pattern: '[^ \t\n\r]'
829
+ attribute :value, String, matches: :present
830
830
  invariant("stopping on a bug says why") { !value.to_s.empty? }
831
831
  end
832
832
 
833
833
  # Who decides and what would unblock it, so "deferred" does not become "forgotten".
834
834
  value_object "NextStep" do
835
- attribute :value, String, pattern: '[^ \t\n\r]'
835
+ attribute :value, String, matches: :present
836
836
  invariant("stopping on a bug says what would move it") { !value.to_s.empty? }
837
837
  end
838
838
 
@@ -842,14 +842,14 @@ Hecks.bluebook "QualityControl" do
842
842
  end
843
843
 
844
844
  value_object "WaiverReason" do
845
- attribute :value, String, pattern: '[^ \t\n\r]'
845
+ attribute :value, String, matches: :present
846
846
  invariant("a waived gate says why") { !value.to_s.empty? }
847
847
  end
848
848
 
849
849
  # Redeclared, as elsewhere in this file; the "qa_sweep" literal is pinned to AUTOMATED_ENGINEER
850
850
  # by spec.
851
851
  value_object "WaivedBy" do
852
- attribute :value, String, pattern: '[^ \t\n\r]'
852
+ attribute :value, String, matches: :present
853
853
  invariant("a gate is waived by a person, never by the loop") { value != "qa_sweep" }
854
854
  invariant("a waiver is signed") { value != "nobody" }
855
855
  end
@@ -881,7 +881,7 @@ Hecks.bluebook "QualityControl" do
881
881
  # Not an `Engineer`: that type defaults to "nobody" (an unclaimed bug is held by nobody), which
882
882
  # would let a report omit its submitter. This type has no default and refuses "nobody".
883
883
  value_object "Submitter" do
884
- attribute :value, String, pattern: '[^ \t\n\r]'
884
+ attribute :value, String, matches: :present
885
885
 
886
886
  invariant("a bug is reported by somebody") { value != "nobody" }
887
887
  end
@@ -1237,27 +1237,27 @@ Hecks.bluebook "QualityControl" do
1237
1237
  attribute :reason, AngleReason
1238
1238
 
1239
1239
  value_object "AngleReference" do
1240
- attribute :value, String, pattern: '[^ \t\n\r]'
1240
+ attribute :value, String, matches: :present
1241
1241
  invariant("an angle is referenced") { !value.to_s.empty? }
1242
1242
  end
1243
1243
 
1244
1244
  # The number is in the sentence because the sentence is the refusal; 60 characters trips a
1245
1245
  # stub.
1246
1246
  value_object "AnglePremise" do
1247
- attribute :value, String, pattern: '[^ \t\n\r]'
1247
+ attribute :value, String, matches: :present
1248
1248
  invariant("an angle says enough to be acted on without its proposer in the room, in at least 60 characters") { value.to_s.size >= 60 }
1249
1249
  end
1250
1250
 
1251
1251
  # A Bug reference, ADR number or named gap that a reader can check for themselves.
1252
1252
  value_object "AngleCitation" do
1253
- attribute :value, String, pattern: '[^ \t\n\r]'
1253
+ attribute :value, String, matches: :present
1254
1254
  invariant("an angle cites something concrete, not a mood") { !value.to_s.empty? }
1255
1255
  end
1256
1256
 
1257
1257
  # Not an `Engineer`, for the reason `Submitter` is not: that default would let authorship go
1258
1258
  # unrecorded.
1259
1259
  value_object "Proposer" do
1260
- attribute :value, String, pattern: '[^ \t\n\r]'
1260
+ attribute :value, String, matches: :present
1261
1261
  invariant("an angle is proposed by somebody") { value != "nobody" }
1262
1262
  end
1263
1263
 
@@ -1268,13 +1268,13 @@ Hecks.bluebook "QualityControl" do
1268
1268
 
1269
1269
  # What came of it (a Target, a PR number, a domain path); set once, at `Build`.
1270
1270
  value_object "AngleResolution" do
1271
- attribute :value, String, pattern: '[^ \t\n\r]'
1271
+ attribute :value, String, matches: :present
1272
1272
  invariant("a built angle says what actually got built") { !value.to_s.empty? }
1273
1273
  end
1274
1274
 
1275
1275
  # Redeclared as `Target::Shelve` does: a discard with no reason looks like a lead nobody read.
1276
1276
  value_object "AngleReason" do
1277
- attribute :value, String, pattern: '[^ \t\n\r]'
1277
+ attribute :value, String, matches: :present
1278
1278
  invariant("discarding an angle says why") { !value.to_s.empty? }
1279
1279
  end
1280
1280
 
@@ -1401,7 +1401,7 @@ Hecks.bluebook "QualityControl" do
1401
1401
  attribute :max_attempts, MaxAttempts
1402
1402
 
1403
1403
  value_object "TicketReference" do
1404
- attribute :value, String, pattern: '[^ \t\n\r]'
1404
+ attribute :value, String, matches: :present
1405
1405
  invariant("a ticket is referenced") { !value.to_s.empty? }
1406
1406
  end
1407
1407
 
@@ -1410,7 +1410,7 @@ Hecks.bluebook "QualityControl" do
1410
1410
  end
1411
1411
 
1412
1412
  value_object "TicketTitle" do
1413
- attribute :value, String, pattern: '[^ \t\n\r]'
1413
+ attribute :value, String, matches: :present
1414
1414
  invariant("a ticket is titled") { !value.to_s.empty? }
1415
1415
  end
1416
1416
 
@@ -1421,7 +1421,7 @@ Hecks.bluebook "QualityControl" do
1421
1421
  end
1422
1422
 
1423
1423
  value_object "TicketBody" do
1424
- attribute :value, String, pattern: '[^ \t\n\r]'
1424
+ attribute :value, String, matches: :present
1425
1425
  invariant("a ticket says something") { !value.to_s.empty? }
1426
1426
  end
1427
1427
 
@@ -1647,22 +1647,22 @@ Hecks.bluebook "QualityControl" do
1647
1647
  end
1648
1648
 
1649
1649
  value_object "PatchUrl" do
1650
- attribute :value, String, pattern: '[^ \t\n\r]'
1650
+ attribute :value, String, matches: :present
1651
1651
  invariant("a patch says where it lives") { !value.to_s.empty? }
1652
1652
  end
1653
1653
 
1654
1654
  value_object "PatchBranch" do
1655
- attribute :value, String, pattern: '[^ \t\n\r]'
1655
+ attribute :value, String, matches: :present
1656
1656
  invariant("a patch says which branch it is on") { !value.to_s.empty? }
1657
1657
  end
1658
1658
 
1659
1659
  # Recorded at `Open` so the commit is in hand when the PR needs checking, as `Bug.commit` is.
1660
1660
  value_object "CommitRef" do
1661
- attribute :value, String, pattern: '^[0-9a-fA-F]{7,40}$'
1661
+ attribute :value, String, matches: :git_sha
1662
1662
  end
1663
1663
 
1664
1664
  value_object "PatchTitle" do
1665
- attribute :value, String, pattern: '[^ \t\n\r]'
1665
+ attribute :value, String, matches: :present
1666
1666
  invariant("a patch is titled") { !value.to_s.empty? }
1667
1667
  end
1668
1668
 
@@ -1840,23 +1840,23 @@ Hecks.bluebook "QualityControl" do
1840
1840
  end
1841
1841
 
1842
1842
  value_object "ImprovementUrl" do
1843
- attribute :value, String, pattern: '[^ \t\n\r]'
1843
+ attribute :value, String, matches: :present
1844
1844
  invariant("an improvement says where it lives") { !value.to_s.empty? }
1845
1845
  end
1846
1846
 
1847
1847
  value_object "ImprovementBranch" do
1848
- attribute :value, String, pattern: '[^ \t\n\r]'
1848
+ attribute :value, String, matches: :present
1849
1849
  invariant("an improvement says which branch it is on") { !value.to_s.empty? }
1850
1850
  end
1851
1851
 
1852
1852
  # Redeclared per aggregate. Absent until `Land`: there is nothing to watch until a commit
1853
1853
  # exists.
1854
1854
  value_object "CommitRef" do
1855
- attribute :value, String, pattern: '^[0-9a-fA-F]{7,40}$'
1855
+ attribute :value, String, matches: :git_sha
1856
1856
  end
1857
1857
 
1858
1858
  value_object "ImprovementTitle" do
1859
- attribute :value, String, pattern: '[^ \t\n\r]'
1859
+ attribute :value, String, matches: :present
1860
1860
  invariant("an improvement is titled") { !value.to_s.empty? }
1861
1861
  end
1862
1862
 
@@ -1977,11 +1977,11 @@ Hecks.bluebook "QualityControl" do
1977
1977
  attribute :summary, RunSummary
1978
1978
 
1979
1979
  value_object "CommitRef" do
1980
- attribute :value, String, pattern: '^[0-9a-fA-F]{7,40}$'
1980
+ attribute :value, String, matches: :git_sha
1981
1981
  end
1982
1982
 
1983
1983
  value_object "RunSummary" do
1984
- attribute :value, String, pattern: '[^ \t\n\r]'
1984
+ attribute :value, String, matches: :present
1985
1985
  invariant("a run says what it ran and what came back") { !value.to_s.empty? }
1986
1986
  end
1987
1987
 
@@ -43,7 +43,6 @@ module Hecks
43
43
  gates = BootGates.new
44
44
  load_bound_adapters!(registry)
45
45
  Ports::Persistence.each_plugin { |plugin| plugin.contribute_boot_gates(registry, gates) }
46
- check_compute_rules_backstop!(registry)
47
46
 
48
47
  gates.run!(:pre_verify, registry, directory)
49
48
  registry.verify!
@@ -67,25 +66,6 @@ module Hecks
67
66
  (bound + default).each { |name| load_adapter(registry, name) }
68
67
  end
69
68
 
70
- # A backstop only: fires when a translation declares a `computes`/
71
- # `rekeys` rule but no persistence plugin is loaded to interpret it.
72
- # Any loaded plugin's own compute-rules gate refuses earlier, by name,
73
- # whenever one is loaded — this only runs when nothing was.
74
- def check_compute_rules_backstop!(registry)
75
- return if Ports::Persistence.plugins_loaded?
76
-
77
- registry.translations.each do |translation|
78
- translation.aggregates.each do |aggregate|
79
- next if aggregate.computes.empty? && aggregate.rekeys.empty?
80
-
81
- raise WiringError,
82
- "cannot boot #{translation.domain}::#{aggregate.name}: a compute/rekey rule is declared, but no " \
83
- "persistence plugin that can interpret it is loaded (e.g. require " \
84
- "\"hecks/ports/persistence/plugins/era\")"
85
- end
86
- end
87
- end
88
-
89
69
  private
90
70
 
91
71
  def load_adapter(registry, name)
@@ -2,6 +2,7 @@ require "json"
2
2
  require "securerandom"
3
3
  require "time"
4
4
  require_relative "event"
5
+ require_relative "value"
5
6
  require_relative "../naming"
6
7
 
7
8
  module Hecks
@@ -48,19 +49,11 @@ module Hecks
48
49
  name: hash[:name],
49
50
  aggregate: hash[:aggregate],
50
51
  id: hash[:id],
51
- payload: deep_symbolize(hash[:payload] || {}),
52
+ payload: Value.deep_symbolize(hash[:payload] || {}),
52
53
  occurred_at: hash[:occurred_at],
53
54
  correlation: hash[:correlation]
54
55
  ).emit!
55
56
  end
56
-
57
- def deep_symbolize(value)
58
- case value
59
- when Hash then value.to_h { |k, v| [k.to_sym, deep_symbolize(v)] }
60
- when Array then value.map { |element| deep_symbolize(element) }
61
- else value
62
- end
63
- end
64
57
  end
65
58
  end
66
59
  end
@@ -101,15 +101,7 @@ module Hecks
101
101
  def spread(answer)
102
102
  return { answered: answer } unless answer.is_a?(Hash)
103
103
 
104
- answer.to_h { |key, value| [key.to_sym, deep_symbolize(value)] }
105
- end
106
-
107
- def deep_symbolize(value)
108
- case value
109
- when Hash then value.to_h { |k, v| [k.to_sym, deep_symbolize(v)] }
110
- when Array then value.map { |element| deep_symbolize(element) }
111
- else value
112
- end
104
+ Value.deep_symbolize(answer)
113
105
  end
114
106
 
115
107
  # The record, if there is one. A missing record is not an error here: the ask still