hecks 3.9.1 → 3.11.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 (504) hide show
  1. checksums.yaml +4 -4
  2. data/examples/pizzas/bluebook/pizzas.bluebook +29 -0
  3. data/examples/pizzas/bluebook/pizzas.hecksagon +7 -0
  4. data/exe/hecks +4 -4
  5. data/lib/hecks/adapters/driven/d1.rb +2 -10
  6. data/lib/hecks/adapters/driven/heki.adapter +1 -1
  7. data/lib/hecks/adapters/driven/heki.rb +2 -12
  8. data/lib/hecks/adapters/driven/lambda.adapter +1 -1
  9. data/lib/hecks/adapters/driven/lambda.rb +3 -18
  10. data/lib/hecks/adapters/driven/postgres.rb +4 -11
  11. data/lib/hecks/adapters/driven/sqlite.adapter +1 -1
  12. data/lib/hecks/adapters/driven/sqlite.rb +2 -10
  13. data/lib/hecks/adapters/driving/admission.rb +46 -0
  14. data/lib/hecks/adapters/driving/cli/nesting.rb +36 -0
  15. data/lib/hecks/adapters/driving/cli/shorthand.rb +88 -0
  16. data/lib/hecks/adapters/driving/cli.rb +127 -0
  17. data/lib/hecks/adapters/driving/cli_runner/answers.rb +160 -0
  18. data/lib/hecks/adapters/driving/cli_runner/resolution.rb +155 -0
  19. data/lib/hecks/adapters/driving/cli_runner/settling.rb +120 -0
  20. data/lib/hecks/adapters/driving/cli_runner/streaming.rb +93 -0
  21. data/lib/hecks/adapters/driving/cli_runner/suggestions.rb +47 -0
  22. data/lib/hecks/adapters/driving/cli_runner.rb +69 -0
  23. data/lib/hecks/adapters/driving/command_request.rb +121 -0
  24. data/lib/hecks/adapters/driving/handle.rb +214 -0
  25. data/lib/hecks/adapters/driving/json.rb +136 -0
  26. data/lib/hecks/adapters/driving/launcher_options.rb +267 -0
  27. data/lib/hecks/adapters/driving/mcp/properties.rb +49 -0
  28. data/lib/hecks/adapters/driving/mcp/tool_calls.rb +116 -0
  29. data/lib/hecks/adapters/driving/mcp/tool_definitions/auditing.rb +53 -0
  30. data/lib/hecks/adapters/driving/mcp/tool_definitions/calling.rb +71 -0
  31. data/lib/hecks/adapters/driving/mcp/tool_definitions/reading.rb +99 -0
  32. data/lib/hecks/adapters/driving/mcp/tool_definitions.rb +18 -0
  33. data/lib/hecks/adapters/driving/mcp.rb +159 -0
  34. data/lib/hecks/adapters/driving/mcp_guide_cache.rb +78 -0
  35. data/lib/hecks/adapters/driving/mcp_scope/argument_policy.rb +97 -0
  36. data/lib/hecks/adapters/driving/mcp_scope/env_problems.rb +69 -0
  37. data/lib/hecks/adapters/driving/mcp_scope/presenter.rb +83 -0
  38. data/lib/hecks/adapters/driving/mcp_scope.rb +213 -0
  39. data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +171 -0
  40. data/lib/hecks/adapters/driving/ruby/chapter.rb +91 -0
  41. data/lib/hecks/adapters/driving/ruby.rb +83 -0
  42. data/lib/hecks/adapters/driving/usage_cache.rb +144 -0
  43. data/lib/hecks/{doors.rb → adapters/driving.rb} +10 -6
  44. data/lib/hecks/behaviors/expectations/boot.rb +1 -1
  45. data/lib/hecks/bench/ruby_runner.rb +1 -1
  46. data/lib/hecks/bluebook/ask_resolution.rb +130 -0
  47. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +8 -2
  48. data/lib/hecks/bluebook/assembly/contracts/rules.rb +4 -2
  49. data/lib/hecks/bluebook/assembly/contracts/structure.rb +3 -1
  50. data/lib/hecks/bluebook/assembly/marks.rb +1 -0
  51. data/lib/hecks/bluebook/attribute.rb +1 -1
  52. data/lib/hecks/bluebook/behaviour/command.rb +13 -8
  53. data/lib/hecks/bluebook/behaviour/policy.rb +30 -0
  54. data/lib/hecks/bluebook/capabilities/attribute_values.rb +72 -0
  55. data/lib/hecks/bluebook/capabilities.rb +91 -9
  56. data/lib/hecks/bluebook/domain_port.rb +16 -4
  57. data/lib/hecks/bluebook/dsl/adapter_builder.rb +18 -6
  58. data/lib/hecks/bluebook/dsl/binding_proxy.rb +13 -0
  59. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/computed_sources.rb +98 -0
  60. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/fan_out_rows.rb +42 -0
  61. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/projected_fields.rb +14 -2
  62. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/provisions.rb +69 -3
  63. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/with_projections.rb +17 -10
  64. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +5 -0
  65. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
  66. data/lib/hecks/bluebook/dsl/command_builder/mutations.rb +8 -1
  67. data/lib/hecks/bluebook/dsl/command_builder/operands.rb +121 -0
  68. data/lib/hecks/bluebook/dsl/command_builder.rb +2 -0
  69. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +10 -1
  70. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +45 -2
  71. data/lib/hecks/bluebook/dsl/policy_builder.rb +47 -10
  72. data/lib/hecks/bluebook/dsl/rule_reference.rb +7 -4
  73. data/lib/hecks/bluebook/dsl/world_builder.rb +1 -1
  74. data/lib/hecks/bluebook/expression/ast_json/emitters.rb +6 -0
  75. data/lib/hecks/bluebook/expression/ast_json.rb +9 -6
  76. data/lib/hecks/bluebook/expression/ast_reader.rb +14 -9
  77. data/lib/hecks/bluebook/expression/projection.json +36 -0
  78. data/lib/hecks/bluebook/expression/resolver/operations.rb +31 -0
  79. data/lib/hecks/bluebook/expression/resolver/parsing.rb +38 -5
  80. data/lib/hecks/bluebook/expression/resolver/scanning.rb +22 -5
  81. data/lib/hecks/bluebook/expression/resolver/values.rb +10 -0
  82. data/lib/hecks/bluebook/expression/resolver.rb +20 -1
  83. data/lib/hecks/bluebook/hecksagon.rb +23 -3
  84. data/lib/hecks/bluebook/lifecycle.rb +22 -3
  85. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +13 -1
  86. data/lib/hecks/bluebook/meta_validator/modes.rb +4 -1
  87. data/lib/hecks/bluebook/meta_validator/readings.rb +18 -1
  88. data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +2 -1
  89. data/lib/hecks/bluebook/meta_validator/shapes.rb +5 -0
  90. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +1 -1
  91. data/lib/hecks/bluebook/meta_validator.rb +12 -12
  92. data/lib/hecks/bluebook/model_check/ask_checks.rb +71 -0
  93. data/lib/hecks/bluebook/model_check/policy_checks.rb +2 -1
  94. data/lib/hecks/bluebook/model_check/reference_checks.rb +70 -0
  95. data/lib/hecks/bluebook/model_check.rb +8 -2
  96. data/lib/hecks/bluebook/policy.rb +4 -2
  97. data/lib/hecks/bluebook/smoke_test.rb +1 -1
  98. data/lib/hecks/bluebook/synthesizer.rb +2 -0
  99. data/lib/hecks/bluebook.rb +1 -0
  100. data/lib/hecks/cli/command_table.rb +1 -1
  101. data/lib/hecks/cli/console.rb +2 -1
  102. data/lib/hecks/cli/document.rb +1 -1
  103. data/lib/hecks/cli/launcher_words.rb +4 -4
  104. data/lib/hecks/cli/mcp.rb +4 -4
  105. data/lib/hecks/cli/project_cli/launcher_source.rb +2 -2
  106. data/lib/hecks/cli/project_cli/templates.rb +5 -5
  107. data/lib/hecks/cli/project_cli.rb +9 -6
  108. data/lib/hecks/cli/run.rb +2 -2
  109. data/lib/hecks/cli/smoke_http/checks.rb +4 -1
  110. data/lib/hecks/cli/smoke_http/transport.rb +3 -3
  111. data/lib/hecks/cli/smoke_http.rb +3 -0
  112. data/lib/hecks/codemod.rb +5 -2
  113. data/lib/hecks/corpus/routes.rb +7 -0
  114. data/lib/hecks/corpus/rust.rb +3 -2
  115. data/lib/hecks/deploy/bluebook/deploy.bluebook +124 -2
  116. data/lib/hecks/deploy/bluebook/deploy.hecksagon +8 -0
  117. data/lib/hecks/deploy/oidc.json +16 -3
  118. data/lib/hecks/doc/reference/rendering.rb +6 -3
  119. data/lib/hecks/forms/app/commands.rb +8 -7
  120. data/lib/hecks/forms/app/queries.rb +2 -1
  121. data/lib/hecks/forms/app.rb +1 -1
  122. data/lib/hecks/forms/record_renderer.rb +1 -1
  123. data/lib/hecks/framework/bluebook/privacy.bluebook +11 -0
  124. data/lib/hecks/fuzzing/concurrent_dispatch.rb +4 -1
  125. data/lib/hecks/fuzzing/failure_triage.rb +2 -1
  126. data/lib/hecks/fuzzing/mutation/run.rb +11 -4
  127. data/lib/hecks/fuzzing/properties/catalog.rb +6 -4
  128. data/lib/hecks/fuzzing/properties/guaranteed_by_construction.rb +18 -11
  129. data/lib/hecks/fuzzing/properties/guards.rb +1 -1
  130. data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +1 -1
  131. data/lib/hecks/fuzzing/self_consistency.rb +6 -2
  132. data/lib/hecks/fuzzing/shrinker.rb +3 -1
  133. data/lib/hecks/fuzzing/value_generator.rb +19 -5
  134. data/lib/hecks/gate/stages.yml +4 -10
  135. data/lib/hecks/grammar/evolve.rb +1 -1
  136. data/lib/hecks/grammar/expression_operators.json +372 -0
  137. data/lib/hecks/grammar/operators.rb +3 -1
  138. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +10 -10
  139. data/lib/hecks/hecks/adapters/codebase/inspection/rubycritic.rb +35 -0
  140. data/lib/hecks/hecks/adapters/codebase/inspection.rb +104 -0
  141. data/lib/hecks/hecks/adapters/codebase/publishing.rb +2 -1
  142. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +12 -2
  143. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +2 -1
  144. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +1 -1
  145. data/lib/hecks/hecks/adapters/cost_explorer.rb +8 -5
  146. data/lib/hecks/hecks/adapters/deploy_toolchain/clearing.rb +51 -0
  147. data/lib/hecks/hecks/adapters/deploy_toolchain/statuses.rb +2 -1
  148. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +2 -0
  149. data/lib/hecks/hecks/adapters/finding_github.rb +13 -6
  150. data/lib/hecks/hecks/adapters/first_admin.rb +2 -2
  151. data/lib/hecks/hecks/adapters/github_rulesets.rb +6 -3
  152. data/lib/hecks/hecks/adapters/in_process_boot.rb +1 -1
  153. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +1 -1
  154. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +10 -20
  155. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +28 -5
  156. data/lib/hecks/hecks/adapters/local_files.rb +2 -2
  157. data/lib/hecks/hecks/adapters/rehearsal/audit_report.rb +107 -0
  158. data/lib/hecks/hecks/adapters/rehearsal/census.rb +71 -0
  159. data/lib/hecks/hecks/adapters/rehearsal/declaration.rb +133 -0
  160. data/lib/hecks/hecks/adapters/rehearsal/environment.rb +83 -0
  161. data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +142 -0
  162. data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +96 -0
  163. data/lib/hecks/hecks/adapters/rehearsal/postgres_census.rb +100 -0
  164. data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +69 -0
  165. data/lib/hecks/hecks/adapters/rehearsal/postgres_store.rb +129 -0
  166. data/lib/hecks/hecks/adapters/rehearsal/rows.rb +41 -0
  167. data/lib/hecks/hecks/adapters/rehearsal/rust_audit.rb +83 -0
  168. data/lib/hecks/hecks/adapters/rehearsal/subject.rb +104 -0
  169. data/lib/hecks/hecks/adapters/rehearsal.rb +54 -0
  170. data/lib/hecks/hecks/adapters/rehearsal_store.adapter +7 -0
  171. data/lib/hecks/hecks/adapters/rehearsal_store.rb +81 -0
  172. data/lib/hecks/hecks/adapters/site_toolchain/role_prober.rb +78 -0
  173. data/lib/hecks/hecks/adapters/site_toolchain.rb +29 -4
  174. data/lib/hecks/hecks/adapters/terminal.rb +13 -11
  175. data/lib/hecks/hecks/adapters/world_clearing.rb +65 -0
  176. data/lib/hecks/hecks/codebase.behaviors +13 -1
  177. data/lib/hecks/hecks/codebase.bluebook +239 -5
  178. data/lib/hecks/hecks/custodian.behaviors +24 -6
  179. data/lib/hecks/hecks/custodian.bluebook +6 -1227
  180. data/lib/hecks/hecks/deploy.behaviors +1 -1
  181. data/lib/hecks/hecks/deploy.world +1 -3
  182. data/lib/hecks/hecks/hecks.behaviors +1 -1
  183. data/lib/hecks/hecks/hecks.hecksagon +61 -18
  184. data/lib/hecks/hecks/hecks.world +11 -10
  185. data/lib/hecks/hecks/rehearsal.bluebook +477 -0
  186. data/lib/hecks/hecks/site.behaviors +1 -1
  187. data/lib/hecks/hecks/site.world +0 -1
  188. data/lib/hecks/hecks/tooling.bluebook +1238 -0
  189. data/lib/hecks/indifferent_key.rb +20 -0
  190. data/lib/hecks/language/adapter.bluebook +13 -3
  191. data/lib/hecks/language/bluebook/aggregate.bluebook +23 -0
  192. data/lib/hecks/language/bluebook/bluebook.bluebook +15 -0
  193. data/lib/hecks/language/bluebook/entity.bluebook +18 -0
  194. data/lib/hecks/language/bluebook/policy.bluebook +9 -1
  195. data/lib/hecks/language/bluebook/query.bluebook +1 -1
  196. data/lib/hecks/language/bluebook/vocabulary.bluebook +11 -4
  197. data/lib/hecks/language/hecksagon/domain_port.bluebook +1 -1
  198. data/lib/hecks/language/hecksagon/hecksagon.bluebook +22 -0
  199. data/lib/hecks/language/oidc.json +10 -0
  200. data/lib/hecks/language/port.bluebook +4 -4
  201. data/lib/hecks/literal.rb +12 -3
  202. data/lib/hecks/ports/key_vault.rb +7 -2
  203. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +21 -10
  204. data/lib/hecks/ports/persistence/plugins/era/postgres_era/boot.rb +4 -2
  205. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +2 -1
  206. data/lib/hecks/ports/persistence/repository_factory.rb +6 -3
  207. data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +29 -0
  208. data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +1 -1
  209. data/lib/hecks/projections/deploy/fargate/settings/tuning.rb +15 -0
  210. data/lib/hecks/projections/deploy/fargate/settings.rb +3 -1
  211. data/lib/hecks/projections/deploy/fargate/stack/readers.rb +1 -0
  212. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  213. data/lib/hecks/projections/deploy/shared.rb +5 -2
  214. data/lib/hecks/projections/deploy/templates/fargate/Makefile.tmpl +7 -4
  215. data/lib/hecks/projections/model/deviations.rb +2 -2
  216. data/lib/hecks/projections/model.rb +2 -2
  217. data/lib/hecks/projections/oidc.rb +13 -1
  218. data/lib/hecks/projections/shape.rb +5 -2
  219. data/lib/hecks/projections/site/cms_editor/attributes.rb +95 -0
  220. data/lib/hecks/projections/site/cms_editor/blocks.rb +157 -0
  221. data/lib/hecks/projections/site/cms_editor/checks.rb +77 -0
  222. data/lib/hecks/projections/site/cms_editor/clearing.rb +70 -0
  223. data/lib/hecks/projections/site/cms_editor/color.rb +124 -0
  224. data/lib/hecks/projections/site/cms_editor/composition.rb +42 -0
  225. data/lib/hecks/projections/site/cms_editor/destructive.rb +61 -0
  226. data/lib/hecks/projections/site/cms_editor/drafts.rb +102 -0
  227. data/lib/hecks/projections/site/cms_editor/hints.rb +74 -0
  228. data/lib/hecks/projections/site/cms_editor/icons.rb +86 -0
  229. data/lib/hecks/projections/site/cms_editor/key_kinds.rb +31 -0
  230. data/lib/hecks/projections/site/cms_editor/limits.rb +72 -0
  231. data/lib/hecks/projections/site/cms_editor/media.rb +85 -0
  232. data/lib/hecks/projections/site/cms_editor/moment.rb +34 -0
  233. data/lib/hecks/projections/site/cms_editor/palette.rb +115 -0
  234. data/lib/hecks/projections/site/cms_editor/pickers.rb +85 -0
  235. data/lib/hecks/projections/site/cms_editor/pretty.rb +40 -0
  236. data/lib/hecks/projections/site/cms_editor/preview.rb +91 -0
  237. data/lib/hecks/projections/site/cms_editor/relations.rb +92 -0
  238. data/lib/hecks/projections/site/cms_editor/scale_checks.rb +66 -0
  239. data/lib/hecks/projections/site/cms_editor/scheduling.rb +150 -0
  240. data/lib/hecks/projections/site/cms_editor/schema.rb +161 -0
  241. data/lib/hecks/projections/site/cms_editor/setting.rb +130 -0
  242. data/lib/hecks/projections/site/cms_editor/targets.rb +86 -0
  243. data/lib/hecks/projections/site/cms_editor/templates/package.json.tmpl +36 -0
  244. data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +370 -0
  245. data/lib/hecks/projections/site/cms_editor/templates/src/assets.ts.tmpl +98 -0
  246. data/lib/hecks/projections/site/cms_editor/templates/src/auth/membership.ts.tmpl +87 -0
  247. data/lib/hecks/projections/site/cms_editor/templates/src/auth/session.ts.tmpl +36 -0
  248. data/lib/hecks/projections/site/cms_editor/templates/src/auth/sso.ts.tmpl +20 -0
  249. data/lib/hecks/projections/site/cms_editor/templates/src/browser/app.css.tmpl +412 -0
  250. data/lib/hecks/projections/site/cms_editor/templates/src/browser/autosave.js.tmpl +144 -0
  251. data/lib/hecks/projections/site/cms_editor/templates/src/browser/blocks.js.tmpl +500 -0
  252. data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_doc.js.tmpl +175 -0
  253. data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_editor.js.tmpl +418 -0
  254. data/lib/hecks/projections/site/cms_editor/templates/src/browser/epoch.js.tmpl +61 -0
  255. data/lib/hecks/projections/site/cms_editor/templates/src/browser/forms.js.tmpl +210 -0
  256. data/lib/hecks/projections/site/cms_editor/templates/src/browser/lists.js.tmpl +40 -0
  257. data/lib/hecks/projections/site/cms_editor/templates/src/browser/main.js.tmpl +26 -0
  258. data/lib/hecks/projections/site/cms_editor/templates/src/browser/media_picker.js.tmpl +232 -0
  259. data/lib/hecks/projections/site/cms_editor/templates/src/browser/moments.js.tmpl +73 -0
  260. data/lib/hecks/projections/site/cms_editor/templates/src/browser/pickers.js.tmpl +152 -0
  261. data/lib/hecks/projections/site/cms_editor/templates/src/browser/preview.js.tmpl +98 -0
  262. data/lib/hecks/projections/site/cms_editor/templates/src/browser/shell.js.tmpl +140 -0
  263. data/lib/hecks/projections/site/cms_editor/templates/src/browser/toast.js.tmpl +66 -0
  264. data/lib/hecks/projections/site/cms_editor/templates/src/choices.ts.tmpl +99 -0
  265. data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +133 -0
  266. data/lib/hecks/projections/site/cms_editor/templates/src/config.ts.tmpl +29 -0
  267. data/lib/hecks/projections/site/cms_editor/templates/src/flash.ts.tmpl +52 -0
  268. data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +43 -0
  269. data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +177 -0
  270. data/lib/hecks/projections/site/cms_editor/templates/src/media/multipart.ts.tmpl +53 -0
  271. data/lib/hecks/projections/site/cms_editor/templates/src/media/sniff.ts.tmpl +94 -0
  272. data/lib/hecks/projections/site/cms_editor/templates/src/media/storage.ts.tmpl +50 -0
  273. data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +36 -0
  274. data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +214 -0
  275. data/lib/hecks/projections/site/cms_editor/templates/src/server.ts.tmpl +65 -0
  276. data/lib/hecks/projections/site/cms_editor/templates/src/ui/availability.ts.tmpl +15 -0
  277. data/lib/hecks/projections/site/cms_editor/templates/src/ui/block_list.js.tmpl +213 -0
  278. data/lib/hecks/projections/site/cms_editor/templates/src/ui/blocks.ts.tmpl +275 -0
  279. data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_model.js.tmpl +123 -0
  280. data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_parse.js.tmpl +320 -0
  281. data/lib/hecks/projections/site/cms_editor/templates/src/ui/detail.ts.tmpl +88 -0
  282. data/lib/hecks/projections/site/cms_editor/templates/src/ui/dialogs.ts.tmpl +43 -0
  283. data/lib/hecks/projections/site/cms_editor/templates/src/ui/fields.ts.tmpl +275 -0
  284. data/lib/hecks/projections/site/cms_editor/templates/src/ui/html.ts.tmpl +61 -0
  285. data/lib/hecks/projections/site/cms_editor/templates/src/ui/icons.ts.tmpl +15 -0
  286. data/lib/hecks/projections/site/cms_editor/templates/src/ui/input.ts.tmpl +92 -0
  287. data/lib/hecks/projections/site/cms_editor/templates/src/ui/links.ts.tmpl +100 -0
  288. data/lib/hecks/projections/site/cms_editor/templates/src/ui/moments.ts.tmpl +44 -0
  289. data/lib/hecks/projections/site/cms_editor/templates/src/ui/outline.ts.tmpl +63 -0
  290. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +213 -0
  291. data/lib/hecks/projections/site/cms_editor/templates/src/ui/paging.ts.tmpl +82 -0
  292. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +110 -0
  293. data/lib/hecks/projections/site/cms_editor/templates/src/ui/preview.ts.tmpl +87 -0
  294. data/lib/hecks/projections/site/cms_editor/templates/src/ui/related.ts.tmpl +97 -0
  295. data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +160 -0
  296. data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +211 -0
  297. data/lib/hecks/projections/site/cms_editor/templates/src/ui/table.ts.tmpl +151 -0
  298. data/lib/hecks/projections/site/cms_editor/templates/src/ui/theme.js.tmpl +16 -0
  299. data/lib/hecks/projections/site/cms_editor/templates/src/ui/values.ts.tmpl +69 -0
  300. data/lib/hecks/projections/site/cms_editor/templates/src/ui/words.ts.tmpl +97 -0
  301. data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +35 -0
  302. data/lib/hecks/projections/site/cms_editor/templates/tsconfig.json.tmpl +17 -0
  303. data/lib/hecks/projections/site/cms_editor/theme.rb +29 -0
  304. data/lib/hecks/projections/site/cms_editor/theme_checks.rb +57 -0
  305. data/lib/hecks/projections/site/cms_editor.rb +133 -0
  306. data/lib/hecks/projections/site/role_probe.rb +74 -0
  307. data/lib/hecks/projector/cli_audience.rb +2 -1
  308. data/lib/hecks/projector/cli_projector/listing.rb +1 -1
  309. data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
  310. data/lib/hecks/projector/docs_projector/closing.rb +1 -1
  311. data/lib/hecks/projector/docs_projector.rb +1 -1
  312. data/lib/hecks/projector/exporter/bindings.rb +40 -10
  313. data/lib/hecks/projector/exporter/capability_values.rb +55 -0
  314. data/lib/hecks/projector/narrate_projector/reactions.rb +1 -1
  315. data/lib/hecks/projector/target.rb +11 -0
  316. data/lib/hecks/projector.rb +32 -5
  317. data/lib/hecks/quality_control/adapters/github_checks.rb +12 -8
  318. data/lib/hecks/quality_control/adapters/github_issues.rb +8 -4
  319. data/lib/hecks/quality_control/cli/qa_open_pr.rb +2 -2
  320. data/lib/hecks/quality_control/cli/qa_sweep/checks/shrinking.rb +1 -1
  321. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +1 -1
  322. data/lib/hecks/quality_control/cli/qa_sweep.rb +1 -1
  323. data/lib/hecks/quality_control/quality_control.bluebook +6 -2
  324. data/lib/hecks/release/approval.rb +64 -0
  325. data/lib/hecks/router/{door.rb → aggregate_reader.rb} +5 -4
  326. data/lib/hecks/router/namespace_installer.rb +16 -16
  327. data/lib/hecks/router.rb +1 -1
  328. data/lib/hecks/runtime/best_effort.rb +22 -0
  329. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +12 -2
  330. data/lib/hecks/runtime/command_interpreter.rb +3 -2
  331. data/lib/hecks/runtime/command_rules/emission.rb +1 -1
  332. data/lib/hecks/runtime/dispatcher.rb +4 -4
  333. data/lib/hecks/runtime/entity_element/location.rb +1 -1
  334. data/lib/hecks/runtime/event.rb +5 -0
  335. data/lib/hecks/runtime/identity.rb +7 -6
  336. data/lib/hecks/runtime/interpreting.rb +2 -1
  337. data/lib/hecks/runtime/invocation_builder.rb +1 -1
  338. data/lib/hecks/runtime/loader/boot_steps.rb +8 -5
  339. data/lib/hecks/runtime/loader.rb +10 -10
  340. data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -4
  341. data/lib/hecks/runtime/policy_interpreter/fan_out.rb +3 -3
  342. data/lib/hecks/runtime/policy_interpreter.rb +6 -6
  343. data/lib/hecks/runtime/port_operation_interpreter.rb +1 -1
  344. data/lib/hecks/runtime/reaction_invocation.rb +1 -1
  345. data/lib/hecks/runtime/rebuild_sweep.rb +11 -1
  346. data/lib/hecks/runtime/registry/ask_verification.rb +35 -0
  347. data/lib/hecks/runtime/registry/hecksagon_rules.rb +10 -0
  348. data/lib/hecks/runtime/registry/merging.rb +1 -1
  349. data/lib/hecks/runtime/registry/providers.rb +16 -0
  350. data/lib/hecks/runtime/registry/read_repository.rb +6 -4
  351. data/lib/hecks/runtime/registry/verification.rb +4 -1
  352. data/lib/hecks/runtime/registry/world_defaults.rb +29 -0
  353. data/lib/hecks/runtime/registry.rb +1 -1
  354. data/lib/hecks/runtime/saga_interpreter/compensation.rb +1 -1
  355. data/lib/hecks/runtime/saga_interpreter/delivery.rb +4 -4
  356. data/lib/hecks/runtime/saga_interpreter/transitions.rb +1 -1
  357. data/lib/hecks/runtime/saga_interpreter.rb +3 -3
  358. data/lib/hecks/runtime/tenant_check.rb +5 -4
  359. data/lib/hecks/runtime/value/coercion.rb +2 -2
  360. data/lib/hecks/runtime/value/validation.rb +16 -4
  361. data/lib/hecks/runtime.rb +9 -9
  362. data/lib/hecks/rust_build/project_rust/target_ir.rb +1 -1
  363. data/lib/hecks/rust_build/wasm.rb +1 -1
  364. data/lib/hecks/rust_build/wasm_browser.rb +1 -1
  365. data/lib/hecks/rust_build.rb +11 -0
  366. data/lib/hecks/served_options.rb +53 -0
  367. data/lib/hecks/site/bluebook/site.bluebook +29 -1
  368. data/lib/hecks/site/bluebook/site.ports.hecksagon +6 -0
  369. data/lib/hecks/storehouse/audit.rb +1 -1
  370. data/lib/hecks/storehouse/dispatching.rb +8 -6
  371. data/lib/hecks/storehouse/introspection.rb +2 -2
  372. data/lib/hecks/storehouse/querying.rb +6 -4
  373. data/lib/hecks/storehouse.rb +4 -4
  374. data/lib/hecks/tools/comment_style/source_file.rb +4 -1
  375. data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +4 -1
  376. data/lib/hecks/tools/lanes/rows.rb +6 -0
  377. data/lib/hecks/tools/oidc_manifests.rb +4 -5
  378. data/lib/hecks/tools/regeneration_run.rb +11 -4
  379. data/lib/hecks/tools/site_routes/cli.rb +1 -1
  380. data/lib/hecks/tools/site_routes/editor_files.rb +45 -0
  381. data/lib/hecks/tools/site_routes.rb +14 -7
  382. data/lib/hecks/tools/tenant_provisioning.rb +2 -2
  383. data/lib/hecks/tools/tools_doc.rb +4 -4
  384. data/lib/hecks/version.rb +1 -1
  385. data/lib/hecks/vocabulary.rb +4 -4
  386. data/lib/hecks.rb +15 -15
  387. data/lib/rubocop/cop/hecks/env_dump.rb +69 -0
  388. data/lib/rubocop/cop/hecks/monotonic_duration.rb +53 -0
  389. data/lib/rubocop/cop/hecks/no_wall_clock_in_runtime.rb +34 -0
  390. data/lib/rubocop/cop/hecks/raw_shell_interpolation.rb +79 -0
  391. data/lib/rubocop/cop/hecks/sleep_in_spec.rb +34 -0
  392. data/lib/rubocop/cop/hecks/swallowed_rescue.rb +37 -0
  393. data/lib/rubocop/cop/hecks/symbol_string_key_mix.rb +50 -0
  394. data/lib/rubocop/cop/hecks/untyped_raise.rb +37 -0
  395. data/prebuilt/05c358d38d661a3774f813d49c66f5b68939af94432d66257a56f49ee2598994.marshal +0 -0
  396. data/prebuilt/26c40c61f09b09b11651c64668eda3ceb180f83cf2a3802d3033d2fd99ac841b.marshal +0 -0
  397. data/prebuilt/verdicts-230ac18c25abfd3d92ede032bee2661a4c2cd2773ab0cf0e2ae52546ba2f7af9.json +1 -0
  398. data/rust/.gitignore +6 -0
  399. data/rust/Cargo.toml +9 -1
  400. data/rust/build/src/binding_pass.rs +103 -0
  401. data/rust/build/src/cargo_sync.rs +6 -0
  402. data/rust/build/src/lineage_pass.rs +3 -3
  403. data/rust/build/src/main.rs +17 -5
  404. data/rust/build/src/pipeline.rs +48 -5
  405. data/rust/build/src/seams_pass.rs +123 -0
  406. data/rust/build/src/translation_pass.rs +167 -0
  407. data/rust/codegen/src/asks.rs +103 -0
  408. data/rust/codegen/src/bridging.rs +1 -1
  409. data/rust/codegen/src/commands.rs +13 -8
  410. data/rust/codegen/src/constraints.rs +25 -0
  411. data/rust/codegen/src/domain_generator.rs +11 -9
  412. data/rust/codegen/src/expr_emitter.rs +13 -0
  413. data/rust/codegen/src/json_codec.rs +54 -15
  414. data/rust/codegen/src/main.rs +2 -1
  415. data/rust/codegen/src/mutations.rs +174 -4
  416. data/rust/codegen/src/naming.rs +30 -0
  417. data/rust/codegen/src/queries.rs +11 -2
  418. data/rust/codegen/src/reactions.rs +1 -1
  419. data/rust/codegen/src/types.rs +166 -8
  420. data/rust/host/HECKS_RELEASE +1 -1
  421. data/rust/host/src/api.rs +4 -4
  422. data/rust/host/src/bin/validate_harness.rs +43 -0
  423. data/rust/host/src/boundary_fuzz/expr_json.rs +2 -2
  424. data/rust/host/src/boundary_fuzz/ir.rs +1 -0
  425. data/rust/host/src/boundary_fuzz/server.rs +1 -1
  426. data/rust/host/src/checkout.rs +35 -19
  427. data/rust/host/src/commerce_ir.rs +408 -2
  428. data/rust/host/src/expr_json.rs +118 -0
  429. data/rust/host/src/mint.rs +1 -1
  430. data/rust/host/src/payments/tests.rs +27 -1
  431. data/rust/host/src/payments.rs +12 -4
  432. data/rust/host/src/query_step.rs +22 -4
  433. data/rust/host/src/reference_validate.rs +653 -108
  434. data/rust/host/src/resend.rs +2 -0
  435. data/rust/host/src/server.rs +19 -6
  436. data/rust/host/src/ui_schema.rs +132 -5
  437. data/rust/host/src/web/newsletter.rs +7 -8
  438. data/rust/host/src/web/newsletter_send.rs +38 -13
  439. data/rust/host/src/web/registrations.rs +229 -26
  440. data/rust/host/src/web.rs +3 -3
  441. data/rust/parser/src/emit.rs +38 -6
  442. data/rust/parser/src/expr/ast_json.rs +7 -1
  443. data/rust/parser/src/expr/resolver.rs +171 -6
  444. data/rust/parser/src/ir.rs +7 -0
  445. data/rust/parser/src/keywords.rs +24 -0
  446. data/rust/parser/src/main.rs +3 -0
  447. data/rust/parser/src/parse/aggregate.rs +2 -1
  448. data/rust/parser/src/parse/chapter.rs +2 -2
  449. data/rust/parser/src/parse/command.rs +2 -0
  450. data/rust/parser/src/parse/hecksagon.rs +5 -2
  451. data/rust/parser/src/parse/lifecycle.rs +81 -0
  452. data/rust/parser/src/parse/mod.rs +15 -1
  453. data/rust/parser/src/parse/policy.rs +13 -2
  454. data/rust/parser/src/parse/value_object.rs +13 -8
  455. data/rust/parser/src/ruby_value.rs +1 -1
  456. data/rust/src/exemplar/commands.rs +4 -4
  457. data/rust/src/exemplar/constraints.rs +1 -1
  458. data/rust/src/exemplar/json.rs +43 -1
  459. data/rust/src/kernel/cli.rs +7 -5
  460. data/rust/src/kernel/dispatch.rs +2 -2
  461. data/rust/src/kernel/dispatch_guard_tests.rs +1 -1
  462. data/rust/src/kernel/expr.rs +73 -4
  463. data/rust/src/kernel/expression_operators/arithmetic.rs +130 -5
  464. data/rust/src/kernel/expression_operators/presence.rs +41 -3
  465. data/rust/src/kernel/expression_operators/text.rs +26 -2
  466. data/rust/src/kernel/json.rs +15 -0
  467. data/rust/src/kernel/mod.rs +1 -1
  468. data/rust/src/kernel/orchestrate.rs +54 -5
  469. data/rust/src/kernel/orchestrate_guard_tests.rs +67 -0
  470. data/rust/src/kernel/pattern.rs +60 -0
  471. data/rust/src/kernel/reference_lookup.rs +90 -7
  472. metadata +174 -36
  473. data/lib/hecks/doors/cli_door/nesting.rb +0 -34
  474. data/lib/hecks/doors/cli_door/shorthand.rb +0 -85
  475. data/lib/hecks/doors/cli_door.rb +0 -124
  476. data/lib/hecks/doors/cli_runner/answers.rb +0 -138
  477. data/lib/hecks/doors/cli_runner/resolution.rb +0 -146
  478. data/lib/hecks/doors/cli_runner/settling.rb +0 -115
  479. data/lib/hecks/doors/cli_runner/streaming.rb +0 -90
  480. data/lib/hecks/doors/cli_runner/suggestions.rb +0 -43
  481. data/lib/hecks/doors/cli_runner.rb +0 -66
  482. data/lib/hecks/doors/command_request.rb +0 -119
  483. data/lib/hecks/doors/handle.rb +0 -208
  484. data/lib/hecks/doors/json_door.rb +0 -132
  485. data/lib/hecks/doors/launcher_options.rb +0 -247
  486. data/lib/hecks/doors/mcp_door/properties.rb +0 -47
  487. data/lib/hecks/doors/mcp_door/tool_calls.rb +0 -113
  488. data/lib/hecks/doors/mcp_door/tool_definitions/auditing.rb +0 -51
  489. data/lib/hecks/doors/mcp_door/tool_definitions/calling.rb +0 -69
  490. data/lib/hecks/doors/mcp_door/tool_definitions/reading.rb +0 -97
  491. data/lib/hecks/doors/mcp_door/tool_definitions.rb +0 -15
  492. data/lib/hecks/doors/mcp_door.rb +0 -156
  493. data/lib/hecks/doors/mcp_door_scope/argument_policy.rb +0 -92
  494. data/lib/hecks/doors/mcp_door_scope/env_problems.rb +0 -55
  495. data/lib/hecks/doors/mcp_door_scope/presenter.rb +0 -78
  496. data/lib/hecks/doors/mcp_door_scope.rb +0 -201
  497. data/lib/hecks/doors/mcp_guide_cache.rb +0 -75
  498. data/lib/hecks/doors/ruby_door/aggregate_door.rb +0 -162
  499. data/lib/hecks/doors/ruby_door/chapter.rb +0 -89
  500. data/lib/hecks/doors/ruby_door.rb +0 -80
  501. data/lib/hecks/doors/usage_cache.rb +0 -139
  502. data/prebuilt/0ee7f83cba59ce7d1c45c65ba166b071779f70143449f584f33ff7989f64eccb.marshal +0 -0
  503. data/prebuilt/6e4a6bcf0aa880e2e6b056e1846b70e5a768090b9dc998bbadeaf5d6bcfd3db8.marshal +0 -0
  504. data/prebuilt/verdicts-91aaf93ce8981bd9a754948f075d95650dc2ec6435015376f5c60992b36a24b1.json +0 -1
@@ -1,3 +1,5 @@
1
+ require_relative "capability_values"
2
+
1
3
  module Hecks
2
4
  module Projector
3
5
  module Exporter
@@ -5,6 +7,8 @@ module Hecks
5
7
  # membership, identity, newsletter, registrations, payments): a per-deployment binding fact,
6
8
  # not part of the declared IR. Each answers `{}` when nothing the domain attaches provides it.
7
9
  module Bindings
10
+ include CapabilityValues
11
+
8
12
  # Which chapter this domain's role checks resolve against
9
13
  # (`Registry#authorization_provider_for`); `{}` if none does.
10
14
  #
@@ -70,16 +74,20 @@ module Hecks
70
74
  # @param registry [Runtime::Registry] the booted registry `domain_name` is loaded in
71
75
  # @param domain_name [String] the domain to export the newsletter binding for
72
76
  # @return [Hash{Symbol => String, nil}] `:provider` (name), `:subscribe`, `:add_name`,
73
- # `:confirm`, `:unsubscribe` (qualified verbs), and `:aggregate` (`:subscribe`'s own
74
- # leading qualified aggregate name); `{}` if nothing this domain attaches provides
75
- # newsletter
77
+ # `:confirm`, `:unsubscribe` (qualified verbs), `:aggregate` (`:subscribe`'s own
78
+ # leading qualified aggregate name) and, when declared, `:awaiting_confirmation`,
79
+ # `:receives_issues` and `:left` (the states of the lifecycle marks they name) and
80
+ # `:confirm_window` and `:unsubscribe_window` (seconds); `{}` if nothing this domain
81
+ # attaches provides newsletter
76
82
  def newsletter(registry, domain_name)
77
83
  provider = registry.newsletter_provider_for(domain_name)
78
84
  return {} unless provider
79
85
 
80
86
  verbs = verbs_of(provider, Bluebook::Capabilities::NEWSLETTER,
81
87
  :subscribe, :add_name, :confirm, :unsubscribe)
82
- { provider: provider.name, **verbs, aggregate: aggregate_of(verbs[:subscribe]) }
88
+ { provider: provider.name, **verbs, aggregate: aggregate_of(verbs[:subscribe]),
89
+ **all_marked_states(provider, Bluebook::Capabilities::NEWSLETTER),
90
+ **all_durations(provider, Bluebook::Capabilities::NEWSLETTER) }
83
91
  end
84
92
 
85
93
  # Which chapter answers sending a newsletter issue
@@ -107,15 +115,18 @@ module Hecks
107
115
  # @param domain_name [String] the domain to export the registrations binding for
108
116
  # @return [Hash{Symbol => String, nil}] `:provider` (name), `:schedule`, `:request`
109
117
  # (qualified verbs), `:event_aggregate` and `:registration_aggregate` (each verb's own
110
- # leading qualified aggregate name); `{}` if nothing this domain attaches provides
111
- # registrations
118
+ # leading qualified aggregate name) and, when declared, `:frees_seat` (the states of the
119
+ # lifecycle mark it names) and `:registered_at` (the attribute that stamps a request);
120
+ # `{}` if nothing this domain attaches provides registrations
112
121
  def registrations(registry, domain_name)
113
122
  provider = registry.registrations_provider_for(domain_name)
114
123
  return {} unless provider
115
124
 
116
125
  verbs = verbs_of(provider, Bluebook::Capabilities::REGISTRATIONS, :schedule, :request)
117
126
  { provider: provider.name, **verbs,
118
- **aggregates_of(verbs, event_aggregate: :schedule, registration_aggregate: :request) }
127
+ **aggregates_of(verbs, event_aggregate: :schedule, registration_aggregate: :request),
128
+ **all_marked_states(provider, Bluebook::Capabilities::REGISTRATIONS),
129
+ **all_words(provider, Bluebook::Capabilities::REGISTRATIONS) }
119
130
  end
120
131
 
121
132
  # Which chapter owns the payment-processor connection
@@ -142,14 +153,33 @@ module Hecks
142
153
  # @param registry [Runtime::Registry] the booted registry `domain_name` is loaded in
143
154
  # @param domain_name [String] the domain to export the payments binding for
144
155
  # @return [Hash{Symbol => String, nil}] `:provider` (name), `:initiate`, `:succeeded`,
145
- # `:failed` (qualified verbs), and `:aggregate` (`:initiate`'s own leading qualified
146
- # aggregate name); `{}` if nothing this domain attaches provides payments
156
+ # `:failed` (qualified verbs), `:aggregate` (`:initiate`'s own leading qualified
157
+ # aggregate name) and, when declared, `:holds_seat` (the states of the lifecycle mark
158
+ # it names) and `:lapse_reason` (the word a lapsed hold
159
+ # records); `{}` if nothing this domain attaches provides payments
147
160
  def payments(registry, domain_name)
148
161
  provider = registry.payments_provider_for(domain_name)
149
162
  return {} unless provider
150
163
 
151
164
  verbs = verbs_of(provider, Bluebook::Capabilities::PAYMENTS, :initiate, :succeeded, :failed)
152
- { provider: provider.name, **verbs, aggregate: aggregate_of(verbs[:initiate]) }
165
+ { provider: provider.name, **verbs, aggregate: aggregate_of(verbs[:initiate]),
166
+ **marked_states(provider, Bluebook::Capabilities::PAYMENTS, :holds_seat),
167
+ **all_words(provider, Bluebook::Capabilities::PAYMENTS) }
168
+ end
169
+
170
+ # Which chapter owns the checkout boundary (`Registry#checkout_provider_for`); `{}` if
171
+ # none does.
172
+ #
173
+ # @param registry [Runtime::Registry] the booted registry `domain_name` is loaded in
174
+ # @param domain_name [String] the domain to export the checkout binding for
175
+ # @return [Hash{Symbol => Object}] `:provider` (name) and, when declared,
176
+ # `:webhook_tolerance` and `:session_hold` (whole seconds); `{}` if nothing this
177
+ # domain attaches provides checkout
178
+ def checkout(registry, domain_name)
179
+ provider = registry.checkout_provider_for(domain_name)
180
+ return {} unless provider
181
+
182
+ { provider: provider.name, **all_durations(provider, Bluebook::Capabilities::CHECKOUT) }
153
183
  end
154
184
 
155
185
  private
@@ -0,0 +1,55 @@
1
+ module Hecks
2
+ module Projector
3
+ module Exporter
4
+ # The values a capability's optional contract entries resolve to in the exported fact: the
5
+ # states of a lifecycle `:mark` (ADR 0097), the whole seconds of a `:duration` (ADR 0098), and
6
+ # the word of a `:text` or `:attribute` entry (ADR 0099).
7
+ module CapabilityValues
8
+ private
9
+
10
+ # `{ key => states }` for each optional `:mark` entry `provider` declares under
11
+ # `capability`.
12
+ def all_marked_states(provider, capability)
13
+ mark_keys = declared_keys(capability, :mark)
14
+ mark_keys.reduce({}) { |marks, key| marks.merge(marked_states(provider, capability, key)) }
15
+ end
16
+
17
+ # `{ key => seconds }` for each optional `:duration` entry `provider` declares under
18
+ # `capability`: the whole-seconds default of the attribute it names.
19
+ def all_durations(provider, capability)
20
+ declared_keys(capability, :duration).each_with_object({}) do |key, durations|
21
+ verb = provider.provision(capability)&.fetch(key, nil)
22
+ durations[key] = Bluebook::Capabilities.duration_of(provider, verb) if verb
23
+ end
24
+ end
25
+
26
+ # `{ key => word }` for each optional `:text` entry and `{ key => attribute_name }` for each
27
+ # optional `:attribute` entry `provider` declares under `capability` (ADR 0099).
28
+ def all_words(provider, capability)
29
+ words = { text: :text_of, attribute: :attribute_of }.flat_map do |kind, resolver|
30
+ declared_keys(capability, kind).filter_map do |key|
31
+ verb = provider.provision(capability)&.fetch(key, nil)
32
+ [key, Bluebook::Capabilities.public_send(resolver, provider, verb)] if verb
33
+ end
34
+ end
35
+ words.to_h
36
+ end
37
+
38
+ # `{ key => states }` for an optional `:mark` entry `provider` declares under `capability`:
39
+ # the states of the named aggregate's lifecycle mark. `{}` when the entry is not declared.
40
+ def marked_states(provider, capability, key)
41
+ verb = provider.provision(capability)&.fetch(key, nil)
42
+ return {} unless verb
43
+
44
+ aggregate_name, mark = verb.split(".", 2)
45
+ { key => provider.aggregate(aggregate_name).lifecycle.marked(mark) }
46
+ end
47
+
48
+ # The contract keys of `capability` whose kind is `kind`.
49
+ def declared_keys(capability, kind)
50
+ Bluebook::Capabilities::CONTRACTS.fetch(capability).select { |_, entry| entry == kind }.keys
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -16,7 +16,7 @@ module Hecks
16
16
  # A policy as the sentence saying it fires on its own.
17
17
  def policy_sentence(policy)
18
18
  elsewhere = policy.target_domain ? " in #{policy.target_domain}" : ""
19
- "Whenever `#{policy.on_event}` happens, `#{policy.trigger_command}` fires on its own#{elsewhere} — " \
19
+ "Whenever `#{policy.on_event}` happens, `#{policy.reaches}` fires on its own#{elsewhere} — " \
20
20
  "nobody has to ask for it."
21
21
  end
22
22
 
@@ -58,6 +58,17 @@ module Hecks
58
58
  # before `projects_as` is called
59
59
  def projection_needs_world? = @projection_needs_world || false
60
60
 
61
+ # Derives what kind of target this is from flags it already declares,
62
+ # so there is no second spelling to drift.
63
+ #
64
+ # @return [Symbol] `:export` if it needs a world, `:meta` if it needs a
65
+ # chapter declaring particular aggregates, otherwise `:projection`
66
+ def projection_category
67
+ return :export if projection_needs_world?
68
+
69
+ projection_declares.empty? ? :projection : :meta
70
+ end
71
+
61
72
  # Names the capability module(s) a construct must satisfy, defaulting
62
73
  # to plain chapter-hood when `projects_as` named none.
63
74
  #
@@ -4,10 +4,13 @@ module Hecks
4
4
  #
5
5
  # Projector.call(:ir, bluebook: chapter)
6
6
  #
7
- # A projection is inert and derived from a chapter's declaration. An export
8
- # (rust crate, WASM, CloudFormation) also needs `.world` bindings and declares
9
- # `needs_world: true`. State projections (`hecks project_expression_tables`) and
10
- # `Ports::Projection` read-model catch-up are different things and not registered.
7
+ # A target's category is derived from the flags it declares (`category`):
8
+ #
9
+ # - `:projection` is inert and derived from any chapter's declaration.
10
+ # - `:meta` also needs a chapter declaring particular aggregates (`declares:`),
11
+ # so it only fits hecks's own grammar chapters.
12
+ # - `:export` (CloudFormation, ...) also needs `.world` bindings and declares
13
+ # `needs_world: true`; that is adapter wiring, not a projection of the domain.
11
14
  module Projector
12
15
  class UnknownProjector < StandardError; end
13
16
  # A chapter-scoped projector was handed something that is not a chapter.
@@ -38,7 +41,9 @@ module Hecks
38
41
  # @raise [WrongConstruct] if `bluebook` lacks what the projector requires
39
42
  def call(name, bluebook:, options: {}, world: nil)
40
43
  projector = registry.fetch(name.to_sym) do
41
- raise UnknownProjector, "no projector registered for #{name.inspect} — registered: #{registered.sort.inspect}"
44
+ raise UnknownProjector,
45
+ "no projector registered for #{name.inspect} — registered: " \
46
+ "#{registered_by_category.map { |kind, keys| "#{kind} #{keys.inspect}" }.join(", ")}"
42
47
  end
43
48
  admits!(name, projector, bluebook, world)
44
49
  projector.call(bluebook: bluebook, options: world ? options.merge(world: world) : options)
@@ -114,6 +119,28 @@ module Hecks
114
119
  # @return [Array<Symbol>] every key currently registered
115
120
  def registered = registry.keys
116
121
 
122
+ # Gives the category of a registered target, derived from its flags.
123
+ # A target that answers no `projection_category` (the built-ins, which do
124
+ # not extend `Target`) is a plain projection.
125
+ #
126
+ # @param name [String, Symbol] the registered projector's key
127
+ # @return [Symbol] `:projection`, `:meta` or `:export` (`:projection` for an
128
+ # unregistered `name`)
129
+ def category(name)
130
+ projector = registry.fetch(name.to_sym) { return :projection }
131
+ projector.respond_to?(:projection_category) ? projector.projection_category : :projection
132
+ end
133
+
134
+ # Lists every registered key under its category.
135
+ #
136
+ # @return [Hash{Symbol => Array<Symbol>}] `:projection`, `:meta` and `:export`
137
+ # (always all three), each with its sorted keys
138
+ def registered_by_category
139
+ grouped = { projection: [], meta: [], export: [] }
140
+ registered.sort.each { |key| grouped[category(key)] << key }
141
+ grouped
142
+ end
143
+
117
144
  # Gives the live registry, initializing it on first use.
118
145
  #
119
146
  # @return [Hash{Symbol => Module, Class, #call}] the live key => projector registry
@@ -2,12 +2,16 @@
2
2
 
3
3
  require "open3"
4
4
  require "json"
5
+ require_relative "../../indifferent_key"
5
6
 
6
7
  module Hecks
7
8
  module Adapters
8
9
  # The `CI` port adapter: answers `Clearance.CI.Run` from GitHub's check runs for a commit.
9
10
  # Raises when no checks exist, any are still running, or any failed.
10
11
  class GithubChecks
12
+ # Raised when check runs cannot be read or have not settled.
13
+ class Error < RuntimeError; end
14
+
11
15
  # All keywords are unused; the adapter takes no per-boot configuration.
12
16
  def initialize(aggregate: nil, settings: {}, root: nil); end
13
17
 
@@ -29,7 +33,7 @@ module Hecks
29
33
  #
30
34
  # @param commit [Hash, String] a materialized value object (`{value: sha}`) or a bare sha
31
35
  # @return [Hash] `{ summary: { value: ... } }` when every check is green
32
- # @raise [RuntimeError] when the sha is malformed, `gh` is missing or fails, no checks
36
+ # @raise [Error] when the sha is malformed, `gh` is missing or fails, no checks
33
37
  # exist, any are incomplete, or any failed
34
38
  def run(commit:, **)
35
39
  sha = valid_sha(commit)
@@ -49,7 +53,7 @@ module Hecks
49
53
  # @param names [Array<String>] the checks to look up
50
54
  # @return [Hash{String => Symbol}] each name to `:passed`, `:failed`, `:pending` (reported but
51
55
  # not completed) or `:missing` (not reported at all)
52
- # @raise [RuntimeError] when the sha is malformed or `gh` is missing or fails
56
+ # @raise [Error] when the sha is malformed or `gh` is missing or fails
53
57
  def states(commit:, names:)
54
58
  reported = check_runs(valid_sha(commit)).select { |run| actions?(run) }.group_by { |run| run["name"] }
55
59
  latest = reported.transform_values { |runs| runs.max_by { |run| run["id"].to_i } }
@@ -62,7 +66,7 @@ module Hecks
62
66
 
63
67
  def valid_sha(commit)
64
68
  sha = sha_of(commit)
65
- raise "not a commit sha: #{sha.inspect}" unless sha.match?(SHA_PATTERN)
69
+ raise Error, "not a commit sha: #{sha.inspect}" unless sha.match?(SHA_PATTERN)
66
70
 
67
71
  sha
68
72
  end
@@ -70,11 +74,11 @@ module Hecks
70
74
  # The check runs against `sha`, once none is still running.
71
75
  def settled_runs(sha)
72
76
  runs = check_runs(sha)
73
- raise "gh reports no checks at all against #{sha}" if runs.empty?
77
+ raise Error, "gh reports no checks at all against #{sha}" if runs.empty?
74
78
 
75
79
  # Refuse rather than answer on an incomplete run; asking again once it settles is safe.
76
80
  incomplete = runs.reject { |run| run["status"] == "completed" }
77
- raise "checks against #{sha} are still running — asked before they settled" if incomplete.any?
81
+ raise Error, "checks against #{sha} are still running — asked before they settled" if incomplete.any?
78
82
 
79
83
  runs
80
84
  end
@@ -95,7 +99,7 @@ module Hecks
95
99
  def sha_of(commit)
96
100
  return commit if commit.is_a?(String)
97
101
 
98
- (commit.key?(:value) ? commit[:value] : commit["value"]).to_s
102
+ IndifferentKey.read(commit, :value).to_s
99
103
  end
100
104
 
101
105
  # Every page of check runs: a red run past the first page must still be seen.
@@ -115,11 +119,11 @@ module Hecks
115
119
  def check_run_page(sha, page)
116
120
  path = "repos/{owner}/{repo}/commits/#{sha}/check-runs?per_page=#{PER_PAGE}&page=#{page}"
117
121
  out, err, status = Open3.capture3("gh", "api", path)
118
- raise "gh api check-runs failed for #{sha}: #{err.strip.empty? ? out.strip : err.strip}" unless status.success?
122
+ raise Error, "gh api check-runs failed for #{sha}: #{err.strip.empty? ? out.strip : err.strip}" unless status.success?
119
123
 
120
124
  JSON.parse(out)
121
125
  rescue Errno::ENOENT
122
- raise "gh is not installed or not on PATH — cannot read check runs for #{sha}"
126
+ raise Error, "gh is not installed or not on PATH — cannot read check runs for #{sha}"
123
127
  end
124
128
  end
125
129
  end
@@ -1,12 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "open3"
4
+ require_relative "../../indifferent_key"
4
5
 
5
6
  module Hecks
6
7
  module Adapters
7
8
  # The `IssueTracker` port's adapter: files a `QualityControl::Ticket` as a GitHub issue.
8
9
  # Raises when `gh` refuses, which the runtime records as the ticket's refusal.
9
10
  class GithubIssues
11
+ # Raised when `gh` cannot file the issue.
12
+ class Error < RuntimeError; end
13
+
10
14
  # @param aggregate [Object, nil] unused
11
15
  # @param settings [Hash] unused
12
16
  # @param root [String, nil] unused
@@ -18,7 +22,7 @@ module Hecks
18
22
  # value objects (`{value: ...}`) or bare strings, and `pull_request` names a proposed fix
19
23
  # @return [Hash{Symbol => Hash}] `number:` and `url:` of the issue, in the shape the
20
24
  # ticket's `Filed` command takes
21
- # @raise [RuntimeError] when `gh` exits non-zero or prints no issue URL
25
+ # @raise [Error] when `gh` exits non-zero or prints no issue URL
22
26
  def file(**held)
23
27
  out = create_issue(plain(held[:repository]), plain(held[:title]).to_s, issue_body(held))
24
28
  url, number = issue_url_and_number(out)
@@ -37,7 +41,7 @@ module Hecks
37
41
  def issue_url_and_number(out)
38
42
  url = out.strip.lines.last.to_s.strip
39
43
  number = url[%r{/issues/(\d+)\z}, 1]
40
- raise "gh issue create printed no issue URL: #{out.strip.inspect}" unless number
44
+ raise Error, "gh issue create printed no issue URL: #{out.strip.inspect}" unless number
41
45
 
42
46
  [url, number]
43
47
  end
@@ -45,7 +49,7 @@ module Hecks
45
49
  def create_issue(repository, title, body)
46
50
  out, err, status = Open3.capture3("gh", "issue", "create", "--repo", repository,
47
51
  "--title", title, "--body", body)
48
- raise "gh issue create failed — #{err.strip.empty? ? out.strip : err.strip}" unless status.success?
52
+ raise Error, "gh issue create failed — #{err.strip.empty? ? out.strip : err.strip}" unless status.success?
49
53
 
50
54
  out
51
55
  end
@@ -54,7 +58,7 @@ module Hecks
54
58
  def plain(field)
55
59
  return field unless field.is_a?(Hash)
56
60
 
57
- field.key?(:value) ? field[:value] : field["value"]
61
+ IndifferentKey.read(field, :value)
58
62
  end
59
63
  end
60
64
  end
@@ -11,8 +11,8 @@ require_relative "qa_open_pr/recording"
11
11
  module Hecks
12
12
  module QualityControlCli
13
13
  # The command behind `hecks quality_control patch.open`: opens a PR and records it in the
14
- # QualityControl ledger in one step. It is the only door into `QualityControl::Patch.Open` and
15
- # `QualityControl::Improvement.Open`.
14
+ # QualityControl ledger in one step. It is the only entry point into
15
+ # `QualityControl::Patch.Open` and `QualityControl::Improvement.Open`.
16
16
  #
17
17
  # It refuses (exit 1, nothing opened or recorded) unless the ledger's own `Open` command would
18
18
  # take the record and the `GitPr` adapter agrees. The command's `given`s are the rules, asked
@@ -10,7 +10,7 @@ module Hecks
10
10
  private
11
11
 
12
12
  # Re-runs only the comparison that surprised; `seat` matters only for `self_consistency`,
13
- # which reads the Rust binary's rehydration door under a differential seat.
13
+ # which reads the Rust binary's rehydration entry point under a differential seat.
14
14
  def candidate_divergences(check_mode, seat, steps)
15
15
  case check_mode
16
16
  when :differential
@@ -37,7 +37,7 @@ module Hecks
37
37
 
38
38
  def print_finding_instructions
39
39
  puts "No Bug was logged (that needs a real failing-test demonstration, which"
40
- puts "needs judgment this script does not have — hecks quality_control log is the door)"
40
+ puts "needs judgment this script does not have — hecks quality_control log is the entry point)"
41
41
  puts "and the sweep was not concluded. The ledger's own SuspendOnSurprise"
42
42
  puts "policy took the target out of the rotation; it stays out until a PERSON"
43
43
  puts "runs: hecks quality_control ask run target=#{@target_reference} " \
@@ -130,7 +130,7 @@ module Hecks
130
130
  @trace_last = now
131
131
  end
132
132
 
133
- # Default `install_doors` so `QualityControl::Target` and friends are top-level constants.
133
+ # Default `install_driving` so `QualityControl::Target` and friends are top-level constants.
134
134
  def boot_ledger
135
135
  Hecks.boot(@domain_dir)
136
136
  rescue StandardError => e
@@ -164,7 +164,7 @@ Hecks.bluebook "QualityControl" do
164
164
  role "QA engineer"
165
165
  goal "Hand a chapter back to the rotation, stamped with when, what this pass was worth, and how the clean streak moves"
166
166
 
167
- # `now` is the argument and `last_swept` the field, so the door can fill the clock.
167
+ # `now` is the argument and `last_swept` the field, so the entry point can fill the clock.
168
168
  #
169
169
  # `next_streak` is the caller's to compute (`clean_streak + 1` if every check held and none
170
170
  # was ever surprised, else 0): `Target` cannot read `Sweep` state and no policy payload can
@@ -530,6 +530,7 @@ Hecks.bluebook "QualityControl" do
530
530
  end
531
531
 
532
532
  lifecycle :status, default: "sweeping" do
533
+ mark :failure, "abandoned"
533
534
  transition "Check" => "sweeping", from: "sweeping"
534
535
  transition "Waive" => "sweeping", from: "sweeping"
535
536
  transition "Conclude" => "concluded", from: "sweeping"
@@ -778,7 +779,7 @@ Hecks.bluebook "QualityControl" do
778
779
  invariant("a bug sequence is positive") { value.positive? }
779
780
  end
780
781
 
781
- # Prefixed on purpose: `Doors::RubyDoor.install` puts every aggregate name on Object, so a
782
+ # Prefixed on purpose: `Adapters::Driving::Ruby.install` puts every aggregate name on Object, so a
782
783
  # value object sharing a word with any aggregate resolves to that class once the other domain
783
784
  # boots.
784
785
  value_object "BugTitle" do
@@ -1456,6 +1457,7 @@ Hecks.bluebook "QualityControl" do
1456
1457
  # filing. `Submit` is non-creating, so `TicketSubmitted` carries `{ id: ... }`, which a
1457
1458
  # reference slot takes.
1458
1459
  lifecycle :status, default: "raised" do
1460
+ mark :failure, "refused", "abandoned"
1459
1461
  transition "Submit" => "submitting", from: ["raised", "refused"]
1460
1462
  transition "Filed" => "filed", from: "submitting"
1461
1463
  transition "Refused" => "refused", from: "submitting"
@@ -1866,6 +1868,7 @@ Hecks.bluebook "QualityControl" do
1866
1868
  # proof-only PR can be closed while still "opened" (`retire_if_settled?` hit
1867
1869
  # `LifecycleRefused`).
1868
1870
  lifecycle :status, default: "opened" do
1871
+ mark :failure, "needs_fix"
1869
1872
  transition "Land" => "landed", from: ["opened", "needs_fix"]
1870
1873
  transition "Regress" => "needs_fix", from: "landed"
1871
1874
  transition "Merge" => "merged", from: ["opened", "landed", "needs_fix"]
@@ -1983,6 +1986,7 @@ Hecks.bluebook "QualityControl" do
1983
1986
  end
1984
1987
 
1985
1988
  lifecycle :status, default: "running" do
1989
+ mark :failure, "red"
1986
1990
  transition "Passed" => "green", from: "running"
1987
1991
  transition "Failed" => "red", from: "running"
1988
1992
  end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hecks/vocabulary"
4
+
5
+ module Hecks
6
+ module Release
7
+ # Whether the commit that set the version carries the owner's approval of the release.
8
+ #
9
+ # Releases are the owner's decision. A bump on `main` reaches `stable` by itself, so the bump
10
+ # commit must say, in its message, that a release is wanted: a trailer line named by the
11
+ # `release_trailer` of the `Lane` row a release is cut from, `Release-Approved-By: <name>`. The
12
+ # decision is read from git history alone, so it can be made again on any later run. It reads
13
+ # only the Vocabulary table, so `release.yml` can run it with the runner's own Ruby.
14
+ module Approval
15
+ module_function
16
+
17
+ # @return [String] the trailer key, read from the `Lane` row that feeds a tag
18
+ # @raise [KeyError] when that row names no trailer
19
+ def trailer
20
+ row = Hecks::Vocabulary.rows("Lane").find { |lane| !lane["feeds"].to_s.empty? }
21
+ key = row && row["release_trailer"].to_s
22
+ raise KeyError, "the Lane row a release is cut from names no release_trailer" if key.nil? || key.empty?
23
+
24
+ key
25
+ end
26
+
27
+ # @param message [String] a commit message
28
+ # @return [String, nil] who approved, from the first trailer line with a name, or nil
29
+ def approver(message)
30
+ line = message.to_s.lines.map(&:strip).find { |text| text.match?(/\A#{Regexp.escape(trailer)}:\s*\S/) }
31
+ line&.sub(/\A[^:]*:\s*/, "")
32
+ end
33
+
34
+ # @param message [String] the message of the commit that set the version
35
+ # @param tag_exists [Boolean] whether the release's tag already stands on origin: a release
36
+ # that was begun was approved when it began, so finishing it asks again for nothing
37
+ # @param explicit [String, nil] a name the owner passed to a by-hand run
38
+ # @return [Array(Boolean, String)] whether the release may go ahead, and the words to print
39
+ def verdict(message:, tag_exists: false, explicit: nil)
40
+ return [true, "approved by #{explicit.strip} (by-hand run)"] unless explicit.to_s.strip.empty?
41
+ return [true, "the tag already stands; finishing a release that was begun"] if tag_exists
42
+
43
+ who = approver(message)
44
+ return [true, "approved by #{who} (#{trailer} trailer)"] if who
45
+
46
+ [false, "the commit that set the version has no '#{trailer}: <name>' line in its message, so it " \
47
+ "is not a release the owner asked for. Nothing was tagged or pushed. To release it, " \
48
+ "re-run by hand with the approval: gh workflow run release.yml -f tag=vX.Y.Z -f approved_by=<name>"]
49
+ end
50
+
51
+ # Reads the decision's inputs from the environment `release.yml` gives it.
52
+ #
53
+ # @param env [#[]] `COMMIT_MESSAGE`, `TAG_EXISTS` (true/false), `APPROVED_BY`
54
+ # @param out [#puts] where the words go
55
+ # @return [Integer] the exit status: 0 to go ahead, 1 to refuse
56
+ def check(env: ENV, out: $stdout)
57
+ ok, words = verdict(message: env["COMMIT_MESSAGE"], tag_exists: env["TAG_EXISTS"] == "true",
58
+ explicit: env["APPROVED_BY"])
59
+ out.puts(ok ? "release approval: #{words}" : "::error::#{words}")
60
+ ok ? 0 : 1
61
+ end
62
+ end
63
+ end
64
+ end
@@ -1,12 +1,13 @@
1
- require_relative "../doors/handle"
1
+ require_relative "../adapters/driving/handle"
2
2
 
3
3
  module Hecks
4
4
  class Router
5
5
  class NamespaceInstaller
6
6
  # One aggregate's repository and event log, read through its dispatcher.
7
- Door = Struct.new(:dispatcher, :domain, :aggregate) do
7
+ AggregateReader = Struct.new(:dispatcher, :domain, :aggregate) do
8
8
  # @param entry [Router::Entry] a router entry
9
- # @return [Door, nil] the door for the entry's aggregate; nil for a query/command-only entry
9
+ # @return [AggregateReader, nil] the reader for the entry's aggregate; nil for a
10
+ # query/command-only entry
10
11
  # whose owner isn't a real aggregate root
11
12
  def self.for(entry)
12
13
  domain = entry.fqn.domain
@@ -29,7 +30,7 @@ module Hecks
29
30
  def all = repository.all.map { |instance| handle(instance) }
30
31
 
31
32
  def handle(instance)
32
- Doors::Handle.new(dispatcher: dispatcher, domain: domain, aggregate: aggregate, instance: instance)
33
+ Adapters::Driving::Handle.new(dispatcher: dispatcher, domain: domain, aggregate: aggregate, instance: instance)
33
34
  end
34
35
  end
35
36
  end
@@ -1,6 +1,6 @@
1
1
  require_relative "../fqn"
2
2
  require_relative "options_proxy"
3
- require_relative "door"
3
+ require_relative "aggregate_reader"
4
4
 
5
5
  module Hecks
6
6
  class Router
@@ -12,11 +12,11 @@ module Hecks
12
12
  end
13
13
 
14
14
  # Installs every current-version route as a namespace constant/method, plus
15
- # the aggregate CRUD door and short `Aggregate.verb` shortcuts.
15
+ # the aggregate CRUD module and short `Aggregate.verb` shortcuts.
16
16
  def install!
17
17
  current_entries.each { |entry| install_namespace_entry(entry) }
18
18
  install_shortcuts!
19
- install_aggregate_doors!
19
+ install_aggregate_modules!
20
20
  self
21
21
  end
22
22
 
@@ -48,26 +48,26 @@ module Hecks
48
48
  end
49
49
 
50
50
  # `.find`/`.all`/`.count`/`.events`/`.repository`, matching `Hecks.boot`'s
51
- # door; grouped by (realm, domain, aggregate) since the five don't vary by verb.
52
- def install_aggregate_doors!
51
+ # module; grouped by (realm, domain, aggregate) since the five don't vary by verb.
52
+ def install_aggregate_modules!
53
53
  current_entries.reject { |entry| entry.fqn.aggregate.nil? }
54
54
  .group_by { |entry| [entry.fqn.realm, entry.fqn.domain, entry.fqn.aggregate] }
55
- .each_value { |entries| install_aggregate_door(entries.first) }
55
+ .each_value { |entries| install_aggregate_module(entries.first) }
56
56
  end
57
57
 
58
- def install_aggregate_door(entry)
59
- door = Door.for(entry)
60
- return unless door
58
+ def install_aggregate_module(entry)
59
+ reader = AggregateReader.for(entry)
60
+ return unless reader
61
61
 
62
- define_door_methods(namespace_for(entry.fqn.realm, entry.fqn.domain, entry.fqn.aggregate), door)
62
+ define_module_methods(namespace_for(entry.fqn.realm, entry.fqn.domain, entry.fqn.aggregate), reader)
63
63
  end
64
64
 
65
- def define_door_methods(target, door)
66
- target.define_singleton_method(:repository) { door.repository }
67
- target.define_singleton_method(:count) { door.repository.count }
68
- target.define_singleton_method(:events) { door.events }
69
- target.define_singleton_method(:find) { |id| door.find(id) }
70
- target.define_singleton_method(:all) { door.all }
65
+ def define_module_methods(target, reader)
66
+ target.define_singleton_method(:repository) { reader.repository }
67
+ target.define_singleton_method(:count) { reader.repository.count }
68
+ target.define_singleton_method(:events) { reader.events }
69
+ target.define_singleton_method(:find) { |id| reader.find(id) }
70
+ target.define_singleton_method(:all) { reader.all }
71
71
  end
72
72
 
73
73
  def install_shortcuts!
data/lib/hecks/router.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Hecks
2
- # The public, project-wide dispatch door. It owns address resolution only;
2
+ # The public, project-wide dispatch entry point. It owns address resolution only;
3
3
  # each discovered Bluebook keeps its own runtime and persistence bindings.
4
4
  class Router
5
5
  class UnknownAddress < StandardError; end
@@ -0,0 +1,22 @@
1
+ module Hecks
2
+ module Runtime
3
+ # The one place the runtime answers a default when a read raises, so a deliberate guard is
4
+ # named at its call site and `Hecks/SwallowedRescue` can refuse every other silent swallow.
5
+ #
6
+ # BestEffort.call(false) { adapter_class.tenant_capable? } # => false when the lookup raises
7
+ module BestEffort
8
+ module_function
9
+
10
+ # Runs the block and answers its value, or `default` when it raises a StandardError.
11
+ #
12
+ # @param default [Object] what to answer when the block raises
13
+ # @yield the read that may raise
14
+ # @return [Object] the block's value, or `default`
15
+ def call(default)
16
+ yield
17
+ rescue StandardError
18
+ default
19
+ end
20
+ end
21
+ end
22
+ end