hecks 3.10.0 → 3.12.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 (542) 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 +158 -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 +70 -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/newcomer.rb +50 -0
  40. data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +171 -0
  41. data/lib/hecks/adapters/driving/ruby/chapter.rb +91 -0
  42. data/lib/hecks/adapters/driving/ruby.rb +83 -0
  43. data/lib/hecks/adapters/driving/usage_cache.rb +144 -0
  44. data/lib/hecks/{doors.rb → adapters/driving.rb} +10 -6
  45. data/lib/hecks/behaviors/expectations/boot.rb +1 -1
  46. data/lib/hecks/bench/ruby_runner.rb +1 -1
  47. data/lib/hecks/bluebook/ask_resolution.rb +130 -0
  48. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +8 -2
  49. data/lib/hecks/bluebook/assembly/contracts/read_models.rb +1 -1
  50. data/lib/hecks/bluebook/assembly/contracts/rules.rb +4 -2
  51. data/lib/hecks/bluebook/assembly/contracts/structure.rb +3 -1
  52. data/lib/hecks/bluebook/assembly/marks.rb +4 -15
  53. data/lib/hecks/bluebook/assembly/scalars.rb +39 -0
  54. data/lib/hecks/bluebook/attribute.rb +1 -1
  55. data/lib/hecks/bluebook/behaviour/command.rb +13 -8
  56. data/lib/hecks/bluebook/behaviour/policy.rb +30 -0
  57. data/lib/hecks/bluebook/capabilities/attribute_values.rb +72 -0
  58. data/lib/hecks/bluebook/capabilities.rb +91 -9
  59. data/lib/hecks/bluebook/domain_port.rb +16 -4
  60. data/lib/hecks/bluebook/dsl/adapter_builder.rb +18 -6
  61. data/lib/hecks/bluebook/dsl/binding_proxy.rb +13 -0
  62. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/computed_sources.rb +98 -0
  63. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/fan_out_rows.rb +42 -0
  64. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/projected_fields.rb +14 -2
  65. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/provisions.rb +69 -3
  66. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/with_projections.rb +17 -10
  67. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +5 -0
  68. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
  69. data/lib/hecks/bluebook/dsl/command_builder/mutations.rb +8 -1
  70. data/lib/hecks/bluebook/dsl/command_builder/operands.rb +121 -0
  71. data/lib/hecks/bluebook/dsl/command_builder.rb +2 -0
  72. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +10 -1
  73. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +45 -2
  74. data/lib/hecks/bluebook/dsl/policy_builder.rb +47 -10
  75. data/lib/hecks/bluebook/dsl/rule_reference.rb +7 -4
  76. data/lib/hecks/bluebook/dsl/world_builder.rb +1 -1
  77. data/lib/hecks/bluebook/expression/ast_json/emitters.rb +6 -0
  78. data/lib/hecks/bluebook/expression/ast_json.rb +9 -6
  79. data/lib/hecks/bluebook/expression/ast_reader.rb +14 -9
  80. data/lib/hecks/bluebook/expression/projection.json +36 -0
  81. data/lib/hecks/bluebook/expression/resolver/operations.rb +31 -0
  82. data/lib/hecks/bluebook/expression/resolver/parsing.rb +38 -5
  83. data/lib/hecks/bluebook/expression/resolver/scanning.rb +22 -5
  84. data/lib/hecks/bluebook/expression/resolver/values.rb +10 -0
  85. data/lib/hecks/bluebook/expression/resolver.rb +20 -1
  86. data/lib/hecks/bluebook/hecksagon.rb +23 -3
  87. data/lib/hecks/bluebook/lifecycle.rb +22 -3
  88. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +13 -1
  89. data/lib/hecks/bluebook/meta_validator/modes.rb +4 -1
  90. data/lib/hecks/bluebook/meta_validator/readings.rb +24 -1
  91. data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +2 -1
  92. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +6 -3
  93. data/lib/hecks/bluebook/meta_validator/shapes.rb +5 -0
  94. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +1 -1
  95. data/lib/hecks/bluebook/meta_validator.rb +12 -12
  96. data/lib/hecks/bluebook/model_check/ask_checks.rb +71 -0
  97. data/lib/hecks/bluebook/model_check/policy_checks.rb +2 -1
  98. data/lib/hecks/bluebook/model_check/reference_checks.rb +70 -0
  99. data/lib/hecks/bluebook/model_check.rb +8 -2
  100. data/lib/hecks/bluebook/policy.rb +4 -2
  101. data/lib/hecks/bluebook/smoke_test.rb +1 -1
  102. data/lib/hecks/bluebook/synthesizer.rb +2 -0
  103. data/lib/hecks/bluebook.rb +2 -0
  104. data/lib/hecks/cli/command_table.rb +1 -1
  105. data/lib/hecks/cli/console.rb +2 -1
  106. data/lib/hecks/cli/document.rb +1 -1
  107. data/lib/hecks/cli/launcher_words.rb +4 -4
  108. data/lib/hecks/cli/mcp.rb +4 -4
  109. data/lib/hecks/cli/project_cli/launcher_source.rb +2 -2
  110. data/lib/hecks/cli/project_cli/templates.rb +5 -5
  111. data/lib/hecks/cli/project_cli.rb +9 -6
  112. data/lib/hecks/cli/run.rb +2 -2
  113. data/lib/hecks/cli/smoke_http/checks.rb +4 -1
  114. data/lib/hecks/cli/smoke_http/transport.rb +3 -3
  115. data/lib/hecks/cli/smoke_http.rb +3 -0
  116. data/lib/hecks/codemod.rb +5 -2
  117. data/lib/hecks/corpus/routes.rb +7 -0
  118. data/lib/hecks/corpus/rust.rb +3 -2
  119. data/lib/hecks/deploy/bluebook/deploy.bluebook +34 -4
  120. data/lib/hecks/doc/reference/rendering.rb +6 -3
  121. data/lib/hecks/forms/app/commands.rb +8 -7
  122. data/lib/hecks/forms/app/queries.rb +2 -1
  123. data/lib/hecks/forms/app.rb +1 -1
  124. data/lib/hecks/forms/record_renderer.rb +1 -1
  125. data/lib/hecks/framework/bluebook/privacy.bluebook +11 -0
  126. data/lib/hecks/fuzzing/concurrent_dispatch.rb +4 -1
  127. data/lib/hecks/fuzzing/failure_triage.rb +2 -1
  128. data/lib/hecks/fuzzing/mutation/run.rb +11 -4
  129. data/lib/hecks/fuzzing/properties/catalog.rb +6 -4
  130. data/lib/hecks/fuzzing/properties/guaranteed_by_construction.rb +18 -11
  131. data/lib/hecks/fuzzing/properties/guards.rb +1 -1
  132. data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +1 -1
  133. data/lib/hecks/fuzzing/self_consistency.rb +6 -2
  134. data/lib/hecks/fuzzing/shrinker.rb +3 -1
  135. data/lib/hecks/fuzzing/value_generator.rb +19 -5
  136. data/lib/hecks/gate/stages.yml +33 -7
  137. data/lib/hecks/grammar/evolve.rb +1 -1
  138. data/lib/hecks/grammar/expression_operators.json +372 -0
  139. data/lib/hecks/grammar/operators.rb +3 -1
  140. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +10 -10
  141. data/lib/hecks/hecks/adapters/codebase/inspection/rubycritic.rb +35 -0
  142. data/lib/hecks/hecks/adapters/codebase/inspection.rb +104 -0
  143. data/lib/hecks/hecks/adapters/codebase/publishing.rb +2 -1
  144. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +12 -2
  145. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +2 -1
  146. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +1 -1
  147. data/lib/hecks/hecks/adapters/cost_explorer.rb +8 -5
  148. data/lib/hecks/hecks/adapters/deploy_toolchain/statuses.rb +2 -1
  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 +133 -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/terminal.rb +13 -11
  173. data/lib/hecks/hecks/codebase.behaviors +13 -1
  174. data/lib/hecks/hecks/codebase.bluebook +239 -5
  175. data/lib/hecks/hecks/custodian.behaviors +24 -6
  176. data/lib/hecks/hecks/custodian.bluebook +6 -1227
  177. data/lib/hecks/hecks/deploy.behaviors +1 -1
  178. data/lib/hecks/hecks/deploy.world +1 -3
  179. data/lib/hecks/hecks/hecks.behaviors +1 -1
  180. data/lib/hecks/hecks/hecks.hecksagon +53 -18
  181. data/lib/hecks/hecks/hecks.world +13 -10
  182. data/lib/hecks/hecks/rehearsal.bluebook +477 -0
  183. data/lib/hecks/hecks/site.behaviors +1 -1
  184. data/lib/hecks/hecks/site.world +0 -1
  185. data/lib/hecks/hecks/tooling.bluebook +1238 -0
  186. data/lib/hecks/indifferent_key.rb +20 -0
  187. data/lib/hecks/language/adapter.bluebook +13 -3
  188. data/lib/hecks/language/bluebook/aggregate.bluebook +23 -0
  189. data/lib/hecks/language/bluebook/bluebook.behaviors +23 -0
  190. data/lib/hecks/language/bluebook/bluebook.bluebook +15 -0
  191. data/lib/hecks/language/bluebook/entity.bluebook +18 -0
  192. data/lib/hecks/language/bluebook/policy.behaviors +19 -0
  193. data/lib/hecks/language/bluebook/policy.bluebook +9 -1
  194. data/lib/hecks/language/bluebook/query.bluebook +1 -1
  195. data/lib/hecks/language/bluebook/vocabulary.bluebook +63 -4
  196. data/lib/hecks/language/hecksagon/domain_port.bluebook +1 -1
  197. data/lib/hecks/language/hecksagon/hecksagon.bluebook +22 -0
  198. data/lib/hecks/language/oidc.json +10 -0
  199. data/lib/hecks/language/port.bluebook +4 -4
  200. data/lib/hecks/literal.rb +12 -3
  201. data/lib/hecks/ports/key_vault.rb +7 -2
  202. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +21 -10
  203. data/lib/hecks/ports/persistence/plugins/era/postgres_era/boot.rb +4 -2
  204. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +2 -1
  205. data/lib/hecks/ports/persistence/repository_factory.rb +6 -3
  206. data/lib/hecks/projection_files/expression_tables.rb +7 -2
  207. data/lib/hecks/projections/deploy/box/compose.rb +6 -4
  208. data/lib/hecks/projections/deploy/box/edge.rb +62 -0
  209. data/lib/hecks/projections/deploy/box/logging.rb +99 -0
  210. data/lib/hecks/projections/deploy/box/roll_scripts.rb +16 -8
  211. data/lib/hecks/projections/deploy/box/settings/cdn.rb +40 -0
  212. data/lib/hecks/projections/deploy/box/settings/logs.rb +40 -0
  213. data/lib/hecks/projections/deploy/box/settings/patterns.rb +11 -0
  214. data/lib/hecks/projections/deploy/box/settings/types.rb +16 -1
  215. data/lib/hecks/projections/deploy/box/settings.rb +6 -2
  216. data/lib/hecks/projections/deploy/box/stack_yaml.rb +5 -2
  217. data/lib/hecks/projections/deploy/box/templates/Makefile.tmpl +16 -0
  218. data/lib/hecks/projections/deploy/box/templates/box.yaml.tmpl +8 -4
  219. data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +32 -0
  220. data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +1 -1
  221. data/lib/hecks/projections/deploy/box/templates/edge.mk.tmpl +17 -0
  222. data/lib/hecks/projections/deploy/box/templates/edge.yaml.tmpl +65 -0
  223. data/lib/hecks/projections/deploy/box/templates/log-group.yaml.tmpl +12 -0
  224. data/lib/hecks/projections/deploy/box/templates/log-policy.yaml.tmpl +7 -0
  225. data/lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl +5 -4
  226. data/lib/hecks/projections/deploy/box/templates/render-compose.sh.tmpl +5 -4
  227. data/lib/hecks/projections/deploy/box.rb +15 -3
  228. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  229. data/lib/hecks/projections/deploy/shared.rb +5 -2
  230. data/lib/hecks/projections/model/deviations.rb +2 -2
  231. data/lib/hecks/projections/model.rb +2 -2
  232. data/lib/hecks/projections/oidc.rb +13 -1
  233. data/lib/hecks/projections/shape.rb +5 -2
  234. data/lib/hecks/projections/site/cms_editor/attributes.rb +26 -6
  235. data/lib/hecks/projections/site/cms_editor/blocks.rb +157 -0
  236. data/lib/hecks/projections/site/cms_editor/checks.rb +14 -1
  237. data/lib/hecks/projections/site/cms_editor/clearing.rb +70 -0
  238. data/lib/hecks/projections/site/cms_editor/color.rb +124 -0
  239. data/lib/hecks/projections/site/cms_editor/composition.rb +42 -0
  240. data/lib/hecks/projections/site/cms_editor/destructive.rb +61 -0
  241. data/lib/hecks/projections/site/cms_editor/drafts.rb +102 -0
  242. data/lib/hecks/projections/site/cms_editor/hints.rb +74 -0
  243. data/lib/hecks/projections/site/cms_editor/icons.rb +86 -0
  244. data/lib/hecks/projections/site/cms_editor/key_kinds.rb +31 -0
  245. data/lib/hecks/projections/site/cms_editor/limits.rb +72 -0
  246. data/lib/hecks/projections/site/cms_editor/media.rb +1 -1
  247. data/lib/hecks/projections/site/cms_editor/moment.rb +34 -0
  248. data/lib/hecks/projections/site/cms_editor/palette.rb +115 -0
  249. data/lib/hecks/projections/site/cms_editor/pickers.rb +85 -0
  250. data/lib/hecks/projections/site/cms_editor/preview.rb +91 -0
  251. data/lib/hecks/projections/site/cms_editor/relations.rb +92 -0
  252. data/lib/hecks/projections/site/cms_editor/scale_checks.rb +66 -0
  253. data/lib/hecks/projections/site/cms_editor/scheduling.rb +150 -0
  254. data/lib/hecks/projections/site/cms_editor/schema.rb +96 -19
  255. data/lib/hecks/projections/site/cms_editor/setting.rb +52 -9
  256. data/lib/hecks/projections/site/cms_editor/targets.rb +86 -0
  257. data/lib/hecks/projections/site/cms_editor/templates/package.json.tmpl +18 -1
  258. data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +251 -57
  259. data/lib/hecks/projections/site/cms_editor/templates/src/assets.ts.tmpl +98 -0
  260. data/lib/hecks/projections/site/cms_editor/templates/src/auth/membership.ts.tmpl +32 -24
  261. data/lib/hecks/projections/site/cms_editor/templates/src/browser/app.css.tmpl +412 -0
  262. data/lib/hecks/projections/site/cms_editor/templates/src/browser/autosave.js.tmpl +144 -0
  263. data/lib/hecks/projections/site/cms_editor/templates/src/browser/blocks.js.tmpl +500 -0
  264. data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_doc.js.tmpl +175 -0
  265. data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_editor.js.tmpl +418 -0
  266. data/lib/hecks/projections/site/cms_editor/templates/src/browser/epoch.js.tmpl +61 -0
  267. data/lib/hecks/projections/site/cms_editor/templates/src/browser/forms.js.tmpl +210 -0
  268. data/lib/hecks/projections/site/cms_editor/templates/src/browser/lists.js.tmpl +40 -0
  269. data/lib/hecks/projections/site/cms_editor/templates/src/browser/main.js.tmpl +26 -0
  270. data/lib/hecks/projections/site/cms_editor/templates/src/browser/media_picker.js.tmpl +232 -0
  271. data/lib/hecks/projections/site/cms_editor/templates/src/browser/moments.js.tmpl +73 -0
  272. data/lib/hecks/projections/site/cms_editor/templates/src/browser/pickers.js.tmpl +152 -0
  273. data/lib/hecks/projections/site/cms_editor/templates/src/browser/preview.js.tmpl +98 -0
  274. data/lib/hecks/projections/site/cms_editor/templates/src/browser/shell.js.tmpl +140 -0
  275. data/lib/hecks/projections/site/cms_editor/templates/src/browser/toast.js.tmpl +66 -0
  276. data/lib/hecks/projections/site/cms_editor/templates/src/choices.ts.tmpl +99 -0
  277. data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +78 -24
  278. data/lib/hecks/projections/site/cms_editor/templates/src/config.ts.tmpl +6 -0
  279. data/lib/hecks/projections/site/cms_editor/templates/src/flash.ts.tmpl +52 -0
  280. data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +19 -2
  281. data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +31 -8
  282. data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +36 -0
  283. data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +131 -2
  284. data/lib/hecks/projections/site/cms_editor/templates/src/ui/availability.ts.tmpl +15 -0
  285. data/lib/hecks/projections/site/cms_editor/templates/src/ui/block_list.js.tmpl +213 -0
  286. data/lib/hecks/projections/site/cms_editor/templates/src/ui/blocks.ts.tmpl +275 -0
  287. data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_model.js.tmpl +8 -5
  288. data/lib/hecks/projections/site/cms_editor/templates/src/ui/detail.ts.tmpl +88 -0
  289. data/lib/hecks/projections/site/cms_editor/templates/src/ui/dialogs.ts.tmpl +43 -0
  290. data/lib/hecks/projections/site/cms_editor/templates/src/ui/fields.ts.tmpl +229 -34
  291. data/lib/hecks/projections/site/cms_editor/templates/src/ui/html.ts.tmpl +40 -81
  292. data/lib/hecks/projections/site/cms_editor/templates/src/ui/icons.ts.tmpl +15 -0
  293. data/lib/hecks/projections/site/cms_editor/templates/src/ui/input.ts.tmpl +29 -12
  294. data/lib/hecks/projections/site/cms_editor/templates/src/ui/links.ts.tmpl +100 -0
  295. data/lib/hecks/projections/site/cms_editor/templates/src/ui/moments.ts.tmpl +44 -0
  296. data/lib/hecks/projections/site/cms_editor/templates/src/ui/outline.ts.tmpl +53 -17
  297. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +178 -88
  298. data/lib/hecks/projections/site/cms_editor/templates/src/ui/paging.ts.tmpl +82 -0
  299. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +110 -0
  300. data/lib/hecks/projections/site/cms_editor/templates/src/ui/preview.ts.tmpl +87 -0
  301. data/lib/hecks/projections/site/cms_editor/templates/src/ui/related.ts.tmpl +97 -0
  302. data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +160 -0
  303. data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +211 -0
  304. data/lib/hecks/projections/site/cms_editor/templates/src/ui/table.ts.tmpl +151 -0
  305. data/lib/hecks/projections/site/cms_editor/templates/src/ui/theme.js.tmpl +16 -0
  306. data/lib/hecks/projections/site/cms_editor/templates/src/ui/values.ts.tmpl +69 -0
  307. data/lib/hecks/projections/site/cms_editor/templates/src/ui/words.ts.tmpl +97 -0
  308. data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +35 -0
  309. data/lib/hecks/projections/site/cms_editor/templates/tsconfig.json.tmpl +2 -1
  310. data/lib/hecks/projections/site/cms_editor/theme.rb +29 -0
  311. data/lib/hecks/projections/site/cms_editor/theme_checks.rb +57 -0
  312. data/lib/hecks/projections/site/cms_editor.rb +49 -23
  313. data/lib/hecks/projections/site/role_probe.rb +2 -0
  314. data/lib/hecks/projections/site/site_root/workflow.rb +5 -0
  315. data/lib/hecks/projector/cli_audience.rb +22 -1
  316. data/lib/hecks/projector/cli_projector/listing.rb +1 -1
  317. data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
  318. data/lib/hecks/projector/cli_projector/usage.rb +15 -4
  319. data/lib/hecks/projector/docs_projector/closing.rb +1 -1
  320. data/lib/hecks/projector/docs_projector.rb +1 -1
  321. data/lib/hecks/projector/exporter/bindings.rb +40 -10
  322. data/lib/hecks/projector/exporter/capability_values.rb +55 -0
  323. data/lib/hecks/projector/narrate_projector/reactions.rb +1 -1
  324. data/lib/hecks/projector/target.rb +11 -0
  325. data/lib/hecks/projector.rb +32 -5
  326. data/lib/hecks/quality_control/adapters/github_checks.rb +12 -8
  327. data/lib/hecks/quality_control/adapters/github_issues.rb +8 -4
  328. data/lib/hecks/quality_control/cli/qa_open_pr.rb +2 -2
  329. data/lib/hecks/quality_control/cli/qa_sweep/checks/shrinking.rb +1 -1
  330. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +1 -1
  331. data/lib/hecks/quality_control/cli/qa_sweep.rb +1 -1
  332. data/lib/hecks/quality_control/quality_control.bluebook +6 -2
  333. data/lib/hecks/release/approval.rb +64 -0
  334. data/lib/hecks/router/{door.rb → aggregate_reader.rb} +5 -4
  335. data/lib/hecks/router/namespace_installer.rb +16 -16
  336. data/lib/hecks/router.rb +1 -1
  337. data/lib/hecks/runtime/best_effort.rb +22 -0
  338. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +12 -2
  339. data/lib/hecks/runtime/command_interpreter.rb +3 -2
  340. data/lib/hecks/runtime/command_rules/emission.rb +1 -1
  341. data/lib/hecks/runtime/dispatcher.rb +4 -4
  342. data/lib/hecks/runtime/entity_element/location.rb +1 -1
  343. data/lib/hecks/runtime/event.rb +5 -0
  344. data/lib/hecks/runtime/identity.rb +7 -6
  345. data/lib/hecks/runtime/interpreting.rb +2 -1
  346. data/lib/hecks/runtime/invocation_builder.rb +1 -1
  347. data/lib/hecks/runtime/loader/boot_steps.rb +8 -5
  348. data/lib/hecks/runtime/loader.rb +10 -10
  349. data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -4
  350. data/lib/hecks/runtime/policy_interpreter/fan_out.rb +3 -3
  351. data/lib/hecks/runtime/policy_interpreter.rb +6 -6
  352. data/lib/hecks/runtime/port_operation_interpreter.rb +1 -1
  353. data/lib/hecks/runtime/reaction_invocation.rb +1 -1
  354. data/lib/hecks/runtime/rebuild_sweep.rb +11 -1
  355. data/lib/hecks/runtime/registry/ask_verification.rb +35 -0
  356. data/lib/hecks/runtime/registry/hecksagon_rules.rb +10 -0
  357. data/lib/hecks/runtime/registry/merging.rb +1 -1
  358. data/lib/hecks/runtime/registry/providers.rb +16 -0
  359. data/lib/hecks/runtime/registry/read_repository.rb +6 -4
  360. data/lib/hecks/runtime/registry/verification.rb +4 -1
  361. data/lib/hecks/runtime/registry/world_defaults.rb +29 -0
  362. data/lib/hecks/runtime/registry.rb +1 -1
  363. data/lib/hecks/runtime/saga_interpreter/compensation.rb +1 -1
  364. data/lib/hecks/runtime/saga_interpreter/delivery.rb +4 -4
  365. data/lib/hecks/runtime/saga_interpreter/transitions.rb +1 -1
  366. data/lib/hecks/runtime/saga_interpreter.rb +3 -3
  367. data/lib/hecks/runtime/tenant_check.rb +5 -4
  368. data/lib/hecks/runtime/value/coercion.rb +2 -2
  369. data/lib/hecks/runtime/value/validation.rb +16 -4
  370. data/lib/hecks/runtime.rb +9 -9
  371. data/lib/hecks/rust_build/host.rb +11 -4
  372. data/lib/hecks/rust_build/prebuilt_binary.rb +101 -0
  373. data/lib/hecks/rust_build/project_rust/target_ir.rb +1 -1
  374. data/lib/hecks/rust_build/wasm.rb +1 -1
  375. data/lib/hecks/rust_build/wasm_browser.rb +1 -1
  376. data/lib/hecks/rust_build.rb +11 -0
  377. data/lib/hecks/served_options.rb +53 -0
  378. data/lib/hecks/site/bluebook/site.bluebook +1 -0
  379. data/lib/hecks/storehouse/audit.rb +1 -1
  380. data/lib/hecks/storehouse/dispatching.rb +8 -6
  381. data/lib/hecks/storehouse/introspection.rb +2 -2
  382. data/lib/hecks/storehouse/querying.rb +6 -4
  383. data/lib/hecks/storehouse.rb +4 -4
  384. data/lib/hecks/tools/ci_gate_decision.rb +35 -0
  385. data/lib/hecks/tools/ci_gates.rb +20 -1
  386. data/lib/hecks/tools/comment_style/source_file.rb +4 -1
  387. data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +4 -1
  388. data/lib/hecks/tools/lanes/files.rb +8 -0
  389. data/lib/hecks/tools/lanes/prebuilt_workflow.rb +81 -0
  390. data/lib/hecks/tools/lanes/rows.rb +6 -0
  391. data/lib/hecks/tools/lanes.rb +1 -0
  392. data/lib/hecks/tools/oidc_manifests.rb +4 -5
  393. data/lib/hecks/tools/regeneration_run.rb +11 -4
  394. data/lib/hecks/tools/release_only.rb +111 -0
  395. data/lib/hecks/tools/site_routes/editor_files.rb +12 -5
  396. data/lib/hecks/tools/tenant_provisioning.rb +2 -2
  397. data/lib/hecks/tools/tools_doc.rb +4 -4
  398. data/lib/hecks/version.rb +1 -1
  399. data/lib/hecks/vocabulary.rb +19 -5
  400. data/lib/hecks.rb +15 -15
  401. data/lib/rubocop/cop/hecks/env_dump.rb +69 -0
  402. data/lib/rubocop/cop/hecks/monotonic_duration.rb +53 -0
  403. data/lib/rubocop/cop/hecks/no_wall_clock_in_runtime.rb +34 -0
  404. data/lib/rubocop/cop/hecks/raw_shell_interpolation.rb +79 -0
  405. data/lib/rubocop/cop/hecks/sleep_in_spec.rb +34 -0
  406. data/lib/rubocop/cop/hecks/swallowed_rescue.rb +37 -0
  407. data/lib/rubocop/cop/hecks/symbol_string_key_mix.rb +50 -0
  408. data/lib/rubocop/cop/hecks/untyped_raise.rb +37 -0
  409. data/prebuilt/9ee54e2f0d4d6d3fe046827779b221e02de665fba911a048ae5dd8b8fbfb7d90.marshal +0 -0
  410. data/prebuilt/d74a00e996222c1d8b676d5959092effc46b4dc59e0555f3bf9554ac58e3094a.marshal +0 -0
  411. data/prebuilt/verdicts-df19f35868d3284a70f55e42dcef87ef4e65e6a678e5135f72e1f6cff4c1357b.json +1 -0
  412. data/rust/.gitignore +6 -0
  413. data/rust/Cargo.toml +6 -0
  414. data/rust/build/src/binding_pass.rs +103 -0
  415. data/rust/build/src/cargo_sync.rs +6 -0
  416. data/rust/build/src/lineage_pass.rs +3 -3
  417. data/rust/build/src/main.rs +17 -5
  418. data/rust/build/src/pipeline.rs +48 -5
  419. data/rust/build/src/seams_pass.rs +123 -0
  420. data/rust/build/src/translation_pass.rs +167 -0
  421. data/rust/codegen/src/asks.rs +103 -0
  422. data/rust/codegen/src/commands.rs +13 -8
  423. data/rust/codegen/src/constraints.rs +25 -0
  424. data/rust/codegen/src/domain_generator.rs +2 -0
  425. data/rust/codegen/src/expr_emitter.rs +13 -0
  426. data/rust/codegen/src/json_codec.rs +54 -15
  427. data/rust/codegen/src/main.rs +1 -0
  428. data/rust/codegen/src/mutations.rs +128 -3
  429. data/rust/codegen/src/queries.rs +11 -2
  430. data/rust/codegen/src/reactions.rs +1 -1
  431. data/rust/codegen/src/types.rs +166 -8
  432. data/rust/host/HECKS_RELEASE +1 -1
  433. data/rust/host/src/api.rs +4 -4
  434. data/rust/host/src/bin/validate_harness.rs +43 -0
  435. data/rust/host/src/boundary_fuzz/expr_json.rs +2 -2
  436. data/rust/host/src/boundary_fuzz/ir.rs +1 -0
  437. data/rust/host/src/boundary_fuzz/server.rs +1 -1
  438. data/rust/host/src/checkout.rs +35 -19
  439. data/rust/host/src/commerce_ir.rs +408 -2
  440. data/rust/host/src/expr_json.rs +118 -0
  441. data/rust/host/src/extension.rs +13 -1
  442. data/rust/host/src/lib.rs +1 -0
  443. data/rust/host/src/mint.rs +1 -1
  444. data/rust/host/src/payments/tests.rs +27 -1
  445. data/rust/host/src/payments.rs +12 -4
  446. data/rust/host/src/platform_signin/attack_tests.rs +1100 -0
  447. data/rust/host/src/platform_signin/config.rs +512 -0
  448. data/rust/host/src/platform_signin/cookie.rs +237 -0
  449. data/rust/host/src/platform_signin/extension.rs +190 -0
  450. data/rust/host/src/platform_signin/fetcher.rs +322 -0
  451. data/rust/host/src/platform_signin/handler.rs +251 -0
  452. data/rust/host/src/platform_signin/handler_tests.rs +554 -0
  453. data/rust/host/src/platform_signin/jti.rs +196 -0
  454. data/rust/host/src/platform_signin/jwks.rs +542 -0
  455. data/rust/host/src/platform_signin/jws.rs +249 -0
  456. data/rust/host/src/platform_signin/known_answer_tests.rs +237 -0
  457. data/rust/host/src/platform_signin/mapping_tests.rs +254 -0
  458. data/rust/host/src/platform_signin/mod.rs +75 -0
  459. data/rust/host/src/platform_signin/mode_tests.rs +382 -0
  460. data/rust/host/src/platform_signin/pages.rs +204 -0
  461. data/rust/host/src/platform_signin/query.rs +233 -0
  462. data/rust/host/src/platform_signin/session.rs +151 -0
  463. data/rust/host/src/platform_signin/settings.rs +385 -0
  464. data/rust/host/src/platform_signin/strict_json.rs +351 -0
  465. data/rust/host/src/platform_signin/test_support.rs +235 -0
  466. data/rust/host/src/platform_signin/ttl_tests.rs +94 -0
  467. data/rust/host/src/platform_signin/verify.rs +604 -0
  468. data/rust/host/src/platform_signin/wiring_support.rs +319 -0
  469. data/rust/host/src/query_step.rs +22 -4
  470. data/rust/host/src/reference_validate.rs +653 -108
  471. data/rust/host/src/resend.rs +2 -0
  472. data/rust/host/src/server.rs +19 -6
  473. data/rust/host/src/ui_schema.rs +132 -5
  474. data/rust/host/src/web/newsletter.rs +7 -8
  475. data/rust/host/src/web/newsletter_send.rs +38 -13
  476. data/rust/host/src/web/registrations.rs +229 -26
  477. data/rust/host/src/web.rs +53 -9
  478. data/rust/parser/src/emit.rs +38 -6
  479. data/rust/parser/src/expr/ast_json.rs +7 -1
  480. data/rust/parser/src/expr/resolver.rs +171 -6
  481. data/rust/parser/src/ir.rs +7 -0
  482. data/rust/parser/src/keywords.rs +24 -0
  483. data/rust/parser/src/main.rs +3 -0
  484. data/rust/parser/src/parse/aggregate.rs +2 -1
  485. data/rust/parser/src/parse/chapter.rs +2 -2
  486. data/rust/parser/src/parse/command.rs +2 -0
  487. data/rust/parser/src/parse/hecksagon.rs +5 -2
  488. data/rust/parser/src/parse/lifecycle.rs +81 -0
  489. data/rust/parser/src/parse/mod.rs +15 -1
  490. data/rust/parser/src/parse/policy.rs +13 -2
  491. data/rust/parser/src/parse/value_object.rs +34 -17
  492. data/rust/parser/src/ruby_value.rs +1 -1
  493. data/rust/src/exemplar/commands.rs +4 -4
  494. data/rust/src/exemplar/constraints.rs +1 -1
  495. data/rust/src/exemplar/json.rs +43 -1
  496. data/rust/src/kernel/cli.rs +7 -5
  497. data/rust/src/kernel/dispatch.rs +2 -2
  498. data/rust/src/kernel/dispatch_guard_tests.rs +1 -1
  499. data/rust/src/kernel/expr.rs +66 -3
  500. data/rust/src/kernel/expression_operators/arithmetic.rs +130 -5
  501. data/rust/src/kernel/expression_operators/text.rs +26 -2
  502. data/rust/src/kernel/json.rs +15 -0
  503. data/rust/src/kernel/mod.rs +1 -1
  504. data/rust/src/kernel/orchestrate.rs +54 -5
  505. data/rust/src/kernel/orchestrate_guard_tests.rs +67 -0
  506. data/rust/src/kernel/pattern.rs +60 -0
  507. data/rust/src/kernel/reference_lookup.rs +90 -7
  508. metadata +178 -38
  509. data/lib/hecks/doors/cli_door/nesting.rb +0 -34
  510. data/lib/hecks/doors/cli_door/shorthand.rb +0 -85
  511. data/lib/hecks/doors/cli_door.rb +0 -124
  512. data/lib/hecks/doors/cli_runner/answers.rb +0 -138
  513. data/lib/hecks/doors/cli_runner/resolution.rb +0 -146
  514. data/lib/hecks/doors/cli_runner/settling.rb +0 -115
  515. data/lib/hecks/doors/cli_runner/streaming.rb +0 -90
  516. data/lib/hecks/doors/cli_runner/suggestions.rb +0 -43
  517. data/lib/hecks/doors/cli_runner.rb +0 -66
  518. data/lib/hecks/doors/command_request.rb +0 -119
  519. data/lib/hecks/doors/handle.rb +0 -208
  520. data/lib/hecks/doors/json_door.rb +0 -132
  521. data/lib/hecks/doors/launcher_options.rb +0 -247
  522. data/lib/hecks/doors/mcp_door/properties.rb +0 -47
  523. data/lib/hecks/doors/mcp_door/tool_calls.rb +0 -113
  524. data/lib/hecks/doors/mcp_door/tool_definitions/auditing.rb +0 -51
  525. data/lib/hecks/doors/mcp_door/tool_definitions/calling.rb +0 -69
  526. data/lib/hecks/doors/mcp_door/tool_definitions/reading.rb +0 -97
  527. data/lib/hecks/doors/mcp_door/tool_definitions.rb +0 -15
  528. data/lib/hecks/doors/mcp_door.rb +0 -156
  529. data/lib/hecks/doors/mcp_door_scope/argument_policy.rb +0 -92
  530. data/lib/hecks/doors/mcp_door_scope/env_problems.rb +0 -55
  531. data/lib/hecks/doors/mcp_door_scope/presenter.rb +0 -78
  532. data/lib/hecks/doors/mcp_door_scope.rb +0 -201
  533. data/lib/hecks/doors/mcp_guide_cache.rb +0 -75
  534. data/lib/hecks/doors/ruby_door/aggregate_door.rb +0 -162
  535. data/lib/hecks/doors/ruby_door/chapter.rb +0 -89
  536. data/lib/hecks/doors/ruby_door.rb +0 -80
  537. data/lib/hecks/doors/usage_cache.rb +0 -139
  538. data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_widget.js.tmpl +0 -282
  539. data/lib/hecks/projections/site/cms_editor/templates/src/ui/media_picker.js.tmpl +0 -109
  540. data/prebuilt/c28d6d9db14eea3e17161e9d9c262efee30b256cf97693a7cfd552bce24b2f7f.marshal +0 -0
  541. data/prebuilt/cb375cba61ca0dbe8ddb9739c306510fe05e5f4b7a9b6955caff8224c63ea0e0.marshal +0 -0
  542. data/prebuilt/verdicts-d6d584ad43b5f3cebf88aafbbdbf024c38574acf18e06adffbc555f7786b1c2f.json +0 -1
@@ -16,6 +16,11 @@ module Hecks
16
16
  [/\A(.+)\.set\?\z/, Assignment, { negated: false }], [/\A(.+)\.unset\?\z/, Assignment, { negated: true }]
17
17
  ].freeze
18
18
 
19
+ # The strip family, each matched as `<receiver>.<call>`, naming which ends it trims.
20
+ STRIP_SUFFIXES = [
21
+ [/\A(.+)\.strip\z/, :both], [/\A(.+)\.lstrip\z/, :left], [/\A(.+)\.rstrip\z/, :right]
22
+ ].freeze
23
+
19
24
  # The suffix calls that carry one quoted argument, as `<receiver>.<call>("<argument>")`.
20
25
  TEXT_SUFFIXES = [
21
26
  [/\A(.+)\.split\("([^"]*)"\)\z/, Split, :separator],
@@ -23,10 +28,15 @@ module Hecks
23
28
  [/\A(.+)\.end_with\?\("([^"]*)"\)\z/, EndsWith, :substring]
24
29
  ].freeze
25
30
 
31
+ # The node each infix operator builds, by precedence level.
32
+ ADDITIVE = { "+" => Addition, "-" => Subtraction }.freeze
33
+ MULTIPLICATIVE = { "*" => Multiplication, "/" => Division }.freeze
34
+
26
35
  # Every step `parse` tries, in precedence order (`.length` first, sign tests before the
27
36
  # rest); each answers a node, or `nil` when the expression is not its form.
28
- PARSE_STEPS = [:parse_length, :parse_literal, :parse_array, :parse_addition, :parse_sign_test,
29
- :parse_receiver_suffix, :parse_modulo, :parse_matches_regex, :parse_text_suffix,
37
+ PARSE_STEPS = [:parse_length, :parse_literal, :parse_array, :parse_additive, :parse_multiplicative,
38
+ :parse_group, :parse_sign_test,
39
+ :parse_receiver_suffix, :parse_strip, :parse_modulo, :parse_matches_regex, :parse_text_suffix,
30
40
  :parse_block_opener].freeze
31
41
 
32
42
  # @param expr [String] the leaf expression text
@@ -62,9 +72,24 @@ module Hecks
62
72
  ArrayLiteral.new(elements: elements.map { |element| parse(element) }) if elements
63
73
  end
64
74
 
65
- def parse_addition(expr)
66
- arithmetic = split_addition(expr)
67
- Addition.new(left: parse(arithmetic[0]), right: parse(arithmetic[1])) if arithmetic
75
+ # The additive level: the last top-level `+` or binary `-`, so `a - b + c` is `(a - b) + c`.
76
+ def parse_additive(expr)
77
+ parse_binary(expr, ADDITIVE)
78
+ end
79
+
80
+ # The multiplicative level, binding tighter than the additive one.
81
+ def parse_multiplicative(expr)
82
+ parse_binary(expr, MULTIPLICATIVE)
83
+ end
84
+
85
+ # A parenthesised group reads as what it wraps.
86
+ def parse_group(expr)
87
+ parse(expr[1..-2]) if grouped?(expr)
88
+ end
89
+
90
+ def parse_binary(expr, table)
91
+ operator, left, right = split_last_binary(expr, table.keys)
92
+ table.fetch(operator).new(left: parse(left), right: parse(right)) if operator
68
93
  end
69
94
 
70
95
  def parse_sign_test(expr)
@@ -80,6 +105,14 @@ module Hecks
80
105
  nil
81
106
  end
82
107
 
108
+ def parse_strip(expr)
109
+ STRIP_SUFFIXES.each do |pattern, side|
110
+ match = expr.match(pattern)
111
+ return Strip.new(receiver: parse(match[1]), side: side) if match
112
+ end
113
+ nil
114
+ end
115
+
83
116
  def parse_modulo(expr)
84
117
  modulo = match_call(expr, ".modulo(")
85
118
  Modulo.new(receiver: parse(modulo[0]), divisor: parse(modulo[1])) if modulo
@@ -66,17 +66,34 @@ module Hecks
66
66
  elements.map(&:strip)
67
67
  end
68
68
 
69
- # Braces and brackets count toward depth like parens, so a `+` inside a block body
70
- # or an array element is not this expression's own addition.
71
- def split_addition(expr)
69
+ # The text before and after the last top-level binary operator in `operators`, so a chain
70
+ # splits left-associatively. A `-` is binary only after an operand (`a - b`, not `a - -b`
71
+ # or a leading `-5`).
72
+ #
73
+ # @param operators [Array<String>] the single-character operators to split on
74
+ # @return [Array<String>, nil] the operator and the texts either side, or `nil`
75
+ def split_last_binary(expr, operators)
72
76
  depth = 0
77
+ found = nil
73
78
  scan_text(expr) do |char, index, plain|
74
79
  next unless plain
75
80
 
76
81
  depth += DEPTH_DELTA.fetch(char, 0)
77
- return [expr[0...index].strip, expr[(index + 1)..].strip] if char == "+" && depth.zero?
82
+ found = index if depth.zero? && operators.include?(char) && binary_position?(expr, index, char)
78
83
  end
79
- nil
84
+ found && [expr[found], expr[0...found].strip, expr[(found + 1)..].strip]
85
+ end
86
+
87
+ # Whether the operator at `index` has an operand on its left; only `-` can also be a sign.
88
+ def binary_position?(expr, index, char)
89
+ return true unless char == "-"
90
+
91
+ expr[0...index].rstrip.match?(/[[:alnum:]_)\]"']\z/)
92
+ end
93
+
94
+ # Whether `expr` is one parenthesised group: its first `(` closes at its last character.
95
+ def grouped?(expr)
96
+ expr.start_with?("(") && matching_paren(expr, 1) == expr.length - 1
80
97
  end
81
98
 
82
99
  # Whether `expr` is wrapped in a matching pair of quotes.
@@ -76,6 +76,16 @@ module Hecks
76
76
  value.split(separator)
77
77
  end
78
78
 
79
+ def strip_value(value, side)
80
+ raise EvaluationError, "strip expects a string, got #{describe(value)}" unless value.is_a?(String)
81
+
82
+ case side
83
+ when :left then value.lstrip
84
+ when :right then value.rstrip
85
+ else value.strip
86
+ end
87
+ end
88
+
79
89
  def last_of(value)
80
90
  return value.last if value.respond_to?(:last)
81
91
 
@@ -27,6 +27,11 @@ module Hecks
27
27
  # A plain class: `Struct.new(keyword_init: true)` with no members raises on Ruby 3.2.
28
28
  NilLiteral = Class.new
29
29
  Addition = Struct.new(:left, :right, keyword_init: true)
30
+ Subtraction = Struct.new(:left, :right, keyword_init: true)
31
+ Multiplication = Struct.new(:left, :right, keyword_init: true)
32
+ # Integer operands divide with floor semantics (toward negative infinity); a zero divisor
33
+ # is an evaluation fault, never a crash or an infinity.
34
+ Division = Struct.new(:left, :right, keyword_init: true)
30
35
  SignTest = Struct.new(:operator, :test, :receiver, keyword_init: true)
31
36
  Empty = Struct.new(:receiver, keyword_init: true)
32
37
  ToS = Struct.new(:receiver, keyword_init: true)
@@ -43,6 +48,11 @@ module Hecks
43
48
 
44
49
  Split = Struct.new(:receiver, :separator, keyword_init: true)
45
50
 
51
+ # `.strip`/`.lstrip`/`.rstrip`: `side` is `:both`, `:left` or `:right`. Strips Ruby's own
52
+ # whitespace set (null, tab, line feed, vertical tab, form feed, carriage return, space),
53
+ # never Unicode spaces; a receiver that is not a String is an evaluation fault.
54
+ Strip = Struct.new(:receiver, :side, keyword_init: true)
55
+
46
56
  Last = Struct.new(:receiver, keyword_init: true)
47
57
 
48
58
  First = Struct.new(:receiver, keyword_init: true)
@@ -72,12 +82,21 @@ module Hecks
72
82
  when IntegerLiteral, FloatLiteral, StringLiteral, BoolLiteral then node.value
73
83
  when ArrayLiteral then node.elements.map { |element| interpret(element, state, attrs) }
74
84
  when NilLiteral then nil
75
- when Addition then add(interpret(node.left, state, attrs), interpret(node.right, state, attrs))
85
+ when Addition, Subtraction, Multiplication, Division then interpret_binary(node, state, attrs)
76
86
  when Lookup then lookup(node.path, state, attrs)
77
87
  else interpret_on_receiver(node, state, attrs)
78
88
  end
79
89
  end
80
90
 
91
+ # Each arithmetic node and the operation that applies it to its two resolved operands.
92
+ BINARY_OPERATIONS = { Addition => :add, Subtraction => :subtract, Multiplication => :multiply,
93
+ Division => :divide }.freeze
94
+
95
+ def interpret_binary(node, state, attrs)
96
+ operation = BINARY_OPERATIONS.find { |klass, _| node.is_a?(klass) }.last
97
+ public_send(operation, interpret(node.left, state, attrs), interpret(node.right, state, attrs))
98
+ end
99
+
81
100
  # Resolves the node's receiver, then applies the operation `RECEIVER_OPERATIONS` names.
82
101
  def interpret_on_receiver(node, state, attrs)
83
102
  _, operation = RECEIVER_OPERATIONS.find { |klass, _| node.is_a?(klass) }
@@ -10,10 +10,17 @@ module Hecks
10
10
  def effect? = signal == :effect
11
11
  end
12
12
 
13
- Adapter = Struct.new(:name, :port, :fields, :secrets, keyword_init: true) do
13
+ # One `.adapter`: the port it implements, the settings a world may give it, and the secrets.
14
+ # `defaults` maps a field's name to the value used when the world gives none; a string may hold
15
+ # a `{name}` or `{storage_name}` placeholder, filled from the aggregate the adapter is built
16
+ # for.
17
+ Adapter = Struct.new(:name, :port, :fields, :secrets, :defaults, keyword_init: true) do
14
18
  def declares?(field) = all_fields.include?(field.to_sym)
15
19
 
16
20
  def all_fields = (fields || []) + (secrets || [])
21
+
22
+ # @return [Hash{Symbol => Object}] the declared defaults, empty when none
23
+ def declared_defaults = defaults || {}
17
24
  end
18
25
 
19
26
  Bind = Struct.new(:aggregate, :verb, :adapter, :role, keyword_init: true) do
@@ -49,15 +56,19 @@ module Hecks
49
56
  binds: many(:binds),
50
57
  subscriptions: -> { subscriptions.map(&:to_s) },
51
58
  attachments: -> { attachments.map(&:to_h) },
59
+ driving: -> { driving.map(&:to_s) },
52
60
  bounded: :bounded?,
53
61
  translates: -> { translates.map(&:to_s) }
54
62
  )
55
63
 
56
- attr_reader :domain, :binds, :subscriptions, :attachments, :translates
64
+ attr_reader :domain, :binds, :subscriptions, :attachments, :translates, :driving
65
+
66
+ # The driving adapters `driven_by` can name.
67
+ DRIVING_ADAPTERS = %w[Ruby Cli Json Mcp].freeze
57
68
 
58
69
  # Every optional keyword and what it holds when the declaration omits it.
59
70
  FIELD_DEFAULTS = {
60
- binds: [], subscriptions: [], attachments: [], bounded: false, translates: []
71
+ binds: [], subscriptions: [], attachments: [], bounded: false, translates: [], driving: []
61
72
  }.freeze
62
73
 
63
74
  # @param domain [String, Symbol] the domain this hecksagon wires
@@ -69,11 +80,13 @@ module Hecks
69
80
  # @param bounded [Boolean] whether this chapter is an explicit bounded context
70
81
  # (consumer-owned; `attaches` marks attached chapters bounded on the registry instead)
71
82
  # @param translates [Array<String>] names of `translates` ACL blocks declared here
83
+ # @param driving [Array<String>] the driving adapters `driven_by` admits; empty admits all
72
84
  def initialize(domain:, **given)
73
85
  KeywordFields.assign(self, KeywordFields.fill(given, FIELD_DEFAULTS))
74
86
  @domain = domain.to_s
75
87
  @bounded = @bounded ? true : false
76
88
  @translates = Array(@translates).map(&:to_s)
89
+ @driving = Array(@driving).map(&:to_s)
77
90
  end
78
91
 
79
92
  # Says whether this hecksagon marked its own chapter `bounded`.
@@ -93,6 +106,13 @@ module Hecks
93
106
  # @return [Boolean] whether that chapter is attached here
94
107
  def attaches?(chapter) = member_chapters.include?(chapter.to_s)
95
108
 
109
+ # Says whether a driving adapter may reach this hecksagon's domain: every adapter may when
110
+ # none is declared, and only the declared ones may once any is.
111
+ #
112
+ # @param adapter [String, Symbol] a driving adapter's name, such as `"Mcp"`
113
+ # @return [Boolean] whether `driven_by` admits that adapter
114
+ def driven_by?(adapter) = driving.empty? || driving.include?(adapter.to_s)
115
+
96
116
  # The vendored packages this hecksagon attaches.
97
117
  #
98
118
  # @return [Array<String>] their directory names, in the order they were attached
@@ -32,19 +32,38 @@ module Hecks
32
32
  field: -> { field.to_s },
33
33
  default: :default,
34
34
  # `expand` is private; a Proc runs in the construct's own context, so it can reach it.
35
- transitions: -> { transitions.flat_map { |command, t| expand(command, t) } }
35
+ transitions: -> { transitions.flat_map { |command, t| expand(command, t) } },
36
+ # Dropped from `to_h` when empty (see below), so an unmarked lifecycle's IR is unchanged.
37
+ marks: :marks
36
38
  )
37
39
 
38
- attr_reader :field, :default, :transitions
40
+ attr_reader :field, :default, :transitions, :marks
39
41
 
40
42
  # @param field [Symbol, String] the attribute this state machine lives on
41
43
  # @param default [String, Symbol] the state a new record starts in
42
44
  # @param transitions [Array<Array(String, Bluebook::StateTransition)>] each declared
43
45
  # `command name, StateTransition` pair, in declaration order
44
- def initialize(field:, default:, transitions: [])
46
+ # @param marks [Hash{String, Symbol => Array<String, Symbol>}] each named meaning and the
47
+ # states that carry it, such as `holds_seat => ["pending", "succeeded"]`
48
+ def initialize(field:, default:, transitions: [], marks: {})
45
49
  @field = field.to_sym
46
50
  @default = default.to_s
47
51
  @transitions = transitions
52
+ @marks = marks.to_h { |name, states| [name.to_s, Array(states).map(&:to_s)] }
53
+ end
54
+
55
+ # The states a named meaning marks.
56
+ #
57
+ # @param name [String, Symbol] the mark, such as `:holds_seat`
58
+ # @return [Array<String>, nil] the states in declared order, or nil when no such mark
59
+ def marked(name) = marks[name.to_s]
60
+
61
+ # `marks` is left out when the lifecycle declares none, so IR written before the word
62
+ # existed stays byte-identical.
63
+ def to_h
64
+ row = super
65
+ row.delete(:marks) if marks.empty?
66
+ row
48
67
  end
49
68
  end
50
69
  end
@@ -3,6 +3,9 @@ module Hecks
3
3
  module MetaValidator
4
4
  # Offers a built .adapter to the self-hosted adapter language (adapter.bluebook).
5
5
  class AdapterJudge
6
+ # Marks a field with no declared default; `nil` and `false` are defaults too.
7
+ NONE = Object.new.freeze
8
+
6
9
  attr_reader :refusals
7
10
 
8
11
  # @param adapter [Bluebook::Adapter] the built adapter to judge
@@ -34,10 +37,19 @@ module Hecks
34
37
 
35
38
  def judge!
36
39
  send_to("Adapter::Adapter.Declare", @adapter.name, name: v(@adapter.name), port: v(@adapter.port))
37
- add_all("AddField", @adapter.fields)
40
+ add_fields
38
41
  add_all("AddSecret", @adapter.secrets)
39
42
  end
40
43
 
44
+ # Offers one `AddField` per field, each with its declared default when it has one.
45
+ def add_fields
46
+ Array(@adapter.fields).each do |field|
47
+ default = @adapter.declared_defaults.fetch(field.to_sym, NONE)
48
+ send_to("Adapter::Adapter.AddField", @adapter.name, name: @adapter.name, value: v(field),
49
+ default: default.equal?(NONE) ? nil : v(default.inspect))
50
+ end
51
+ end
52
+
41
53
  # Offers one `verb` per value, each addressed to the adapter by name.
42
54
  def add_all(verb, values)
43
55
  Array(values).each do |value|
@@ -4,6 +4,9 @@ module Hecks
4
4
  # The mode switches of judging: bootstrapping, deferring, disabled, shadow-parsing and the
5
5
  # forced fixpoint. `MetaValidator` extends this, so each reads and writes its own state.
6
6
  module Modes
7
+ # Raised when a mode is asked for a builder that was never opened.
8
+ class Error < RuntimeError; end
9
+
7
10
  # Whether the language's own grammar is still loading raw, unjudged.
8
11
  # Judging it while it loads would recurse, so the bootstrap sets this
9
12
  # and the fixpoint clears it once every grammar file is merged.
@@ -66,7 +69,7 @@ module Hecks
66
69
  # Bare chapter-level givens must resolve before anything below
67
70
  # reads a `Given`'s fields. The `raise` block never runs — this
68
71
  # chapter's builder is always already open by the time `chapter` exists.
69
- builder = registry.bluebook_builder(name) { raise "internal: no open builder for #{name}" }
72
+ builder = registry.bluebook_builder(name) { raise Error, "internal: no open builder for #{name}" }
70
73
  builder.resolve_pending_chapter_givens!
71
74
  # Same, one level down: entity-scoped pending givens.
72
75
  builder.resolve_pending_chapter_entity_givens!
@@ -57,11 +57,25 @@ module Hecks
57
57
  end
58
58
  end
59
59
 
60
+ # A mark names several states, so it expands into one row per state, in declared order.
61
+ def mark_rows(node)
62
+ lifecycle = node.respond_to?(:lifecycle) ? node.lifecycle : nil
63
+ return [] unless lifecycle
64
+
65
+ lifecycle.marks.flat_map { |name, states| states.map { |state| { name: name, state: state } } }
66
+ end
67
+
60
68
  # An open map has no value object to hold it, so each entry is its own row.
61
69
  def pair_rows(map)
62
70
  Array(map&.to_h).map { |key, value| { key: key, value: value } }
63
71
  end
64
72
 
73
+ # A member's cells, each spelled so a quoted `"true"` stays a String and a bare `true` a
74
+ # boolean.
75
+ def member_pair_rows(map)
76
+ Array(map&.to_h).map { |key, value| { key: key, value: Assembly::Scalars.mark(value) } }
77
+ end
78
+
65
79
  # Flattens options via `extra_options_to_h` — a new option needs no
66
80
  # change here. `filters: true` reads wheres/order_by/limit live off
67
81
  # the node, not `to_h`, so wire-format changes there don't affect it.
@@ -116,7 +130,16 @@ module Hecks
116
130
 
117
131
  [{ target: mutation.target, op: mutation.op, field: mutation.target,
118
132
  kind: classified[:kind],
119
- source: classified[:name] || encode_literal(classified[:value]) }]
133
+ source: scalar_source_text(classified) }]
134
+ end
135
+
136
+ # The one text a scalar mutation's source is stored as: an argument or state name, an
137
+ # expression's text, or an encoded literal.
138
+ def scalar_source_text(classified)
139
+ return classified[:name] if classified.key?(:name)
140
+ return classified[:text] if classified.key?(:text)
141
+
142
+ encode_literal(classified[:value])
120
143
  end
121
144
 
122
145
  # The columns of one canonical-form table entry, as the language declares them.
@@ -79,7 +79,8 @@ module Hecks
79
79
  {
80
80
  field: field,
81
81
  default: text(row[:state_start]),
82
- transitions: Array(row[:transitions]).map { |move| transition(move) }
82
+ transitions: Array(row[:transitions]).map { |move| transition(move) },
83
+ marks: Array(row[:marks]).map { |held| mark(held) }
83
84
  }
84
85
  end
85
86
  end
@@ -41,11 +41,14 @@ module Hecks
41
41
  # dispatched state, one member at a time, not behind a `DeclaredIn` query.
42
42
  def members_row(row) = members_of(row)
43
43
 
44
- # The key is stringified, never the value: a pair's value keeps whatever
45
- # native Ruby type the source line actually wrote.
44
+ # The key is stringified, never the value: a pair's value is read back from its text by
45
+ # `Assembly::Scalars`, so a quoted cell returns a String and `true`, `false` and numbers
46
+ # return as the builder holds them.
46
47
  def members_of(value_object_row)
47
48
  Array(value_object_row[:members]).map do |member|
48
- Array(member[:pairs]).map { |pair| [text(pair[:key]).to_s, text(pair[:value])] }
49
+ Array(member[:pairs]).map do |pair|
50
+ [text(pair[:key]).to_s, Assembly::Scalars.unmark(text(pair[:value]))]
51
+ end
49
52
  end
50
53
  end
51
54
 
@@ -86,6 +86,10 @@ module Hecks
86
86
  }
87
87
  end
88
88
 
89
+ def mark(row)
90
+ { name: text(row[:name]), state: text(row[:state]) }
91
+ end
92
+
89
93
  def head(row)
90
94
  {
91
95
  aggregate: text(row[:aggregate]),
@@ -140,6 +144,7 @@ module Hecks
140
144
  value = text(binding[:source])
141
145
 
142
146
  return { kind: kind, name: value } if %w[argument state].include?(kind)
147
+ return { kind: kind, text: value } if kind == "expression"
143
148
 
144
149
  { kind: "literal", value: decode_literal(value) }
145
150
  end
@@ -34,7 +34,7 @@ module Hecks
34
34
  # project, serve or deploy from a chapter, and nothing the judge runs
35
35
  # calls into them. An edit there leaves every verdict as it was, so
36
36
  # their files stay out of the key. Paths are relative to `lib/hecks`.
37
- CONSUMER_TREES = %r{\A/hecks/(?:bench|cli|codemod|deploy|doc|doors|fuzzing|projections|
37
+ CONSUMER_TREES = %r{\A/hecks/(?:bench|cli|codemod|deploy|doc|adapters/driving|fuzzing|projections|
38
38
  quality_control|release)(?:/|\.rb\z)}x
39
39
 
40
40
  # Files older than this that do not match the current code digest are
@@ -15,7 +15,7 @@ module Hecks
15
15
  GRAMMAR_FILES = Dir.glob(File.join(GRAMMAR_DIR, "*.bluebook")).freeze
16
16
  WORLD_GRAMMAR = Dir.glob(File.expand_path("../language/world/*.bluebook", __dir__)).freeze
17
17
  HECKSAGON_GRAMMAR = Dir.glob(File.expand_path("../language/hecksagon/*.bluebook", __dir__)).freeze
18
- # Backs PortJudge, the same door shape as WorldJudge.
18
+ # Backs PortJudge, the same entry point shape as WorldJudge.
19
19
  PORT_GRAMMAR = File.expand_path("../language/port.bluebook", __dir__).freeze
20
20
  # Backs AdapterJudge, one file over.
21
21
  ADAPTER_GRAMMAR = File.expand_path("../language/adapter.bluebook", __dir__).freeze
@@ -41,42 +41,42 @@ module Hecks
41
41
  # verdict Hash for a chapter
42
42
  def self.verdicts = @verdicts ||= VerdictCache.seed
43
43
 
44
- # Judges `world` through the meta-domain's own `WorldJudge` door.
44
+ # Judges `world` through the meta-domain's own `WorldJudge` entry point.
45
45
  #
46
46
  # @param world [Bluebook::World] the world to judge
47
47
  # @return [Bluebook::World] `world` unchanged, if well formed or
48
48
  # while disabled/bootstrapping/shadow-parsing
49
49
  # @raise [DSL::Malformed] if `WorldJudge` finds `world` malformed
50
50
  def self.call_world(world)
51
- judge_door(world, WorldJudge, "#{world.domain}'s world") do
51
+ judge_artifact(world, WorldJudge, "#{world.domain}'s world") do
52
52
  JSON.generate([world.domain, world.realm, world.latest, world.settings])
53
53
  end
54
54
  end
55
55
 
56
- # Judges `port` through the meta-domain's own `PortJudge` door.
56
+ # Judges `port` through the meta-domain's own `PortJudge` entry point.
57
57
  #
58
58
  # @param port [Bluebook::Port] the port to judge
59
59
  # @return [Bluebook::Port] `port` unchanged, if well formed or while
60
60
  # disabled/bootstrapping/shadow-parsing
61
61
  # @raise [DSL::Malformed] if `PortJudge` finds `port` malformed
62
62
  def self.call_port(port)
63
- judge_door(port, PortJudge, "#{port.name}'s port") { JSON.generate([port.name, port.verb, port.signal]) }
63
+ judge_artifact(port, PortJudge, "#{port.name}'s port") { JSON.generate([port.name, port.verb, port.signal]) }
64
64
  end
65
65
 
66
- # Judges `adapter` through the meta-domain's own `AdapterJudge` door.
66
+ # Judges `adapter` through the meta-domain's own `AdapterJudge` entry point.
67
67
  #
68
68
  # @param adapter [Bluebook::Adapter] the adapter to judge
69
69
  # @return [Bluebook::Adapter] `adapter` unchanged, if well formed or
70
70
  # while disabled/bootstrapping/shadow-parsing
71
71
  # @raise [DSL::Malformed] if `AdapterJudge` finds `adapter` malformed
72
72
  def self.call_adapter(adapter)
73
- judge_door(adapter, AdapterJudge, "#{adapter.name}'s adapter") do
74
- JSON.generate([adapter.name, adapter.port, adapter.fields, adapter.secrets])
73
+ judge_artifact(adapter, AdapterJudge, "#{adapter.name}'s adapter") do
74
+ JSON.generate([adapter.name, adapter.port, adapter.fields, adapter.secrets, adapter.declared_defaults])
75
75
  end
76
76
  end
77
77
 
78
78
  # Judges `translation` through the meta-domain's own `TranslationJudge`
79
- # door, walking every nested aggregate's own rule table in one pass.
79
+ # entry point, walking every nested aggregate's own rule table in one pass.
80
80
  #
81
81
  # `Translation` has no `.to_h`; `.inspect` embeds an object-id, so
82
82
  # structurally-identical translations never cache-hit here (a lost
@@ -88,10 +88,10 @@ module Hecks
88
88
  # @raise [DSL::Malformed] if `TranslationJudge` finds `translation`
89
89
  # malformed
90
90
  def self.call_translation(translation)
91
- judge_door(translation, TranslationJudge, "#{translation.domain}'s translation") { translation.inspect }
91
+ judge_artifact(translation, TranslationJudge, "#{translation.domain}'s translation") { translation.inspect }
92
92
  end
93
93
 
94
- # The one door every artifact but a chapter goes through: skip while judging is off,
94
+ # The one entry point every artifact but a chapter goes through: skip while judging is off,
95
95
  # else judge once per distinct `key_text` and refuse a malformed artifact.
96
96
  #
97
97
  # @param subject [Object] the artifact to judge
@@ -100,7 +100,7 @@ module Hecks
100
100
  # @yieldreturn [String] the text whose SHA-256 digest keys the verdict
101
101
  # @return [Object] `subject` unchanged, if well formed or while judging is off
102
102
  # @raise [DSL::Malformed] if `judge` finds `subject` malformed
103
- def self.judge_door(subject, judge, label)
103
+ def self.judge_artifact(subject, judge, label)
104
104
  return subject if disabled? || bootstrapping? || shadow_parsing?
105
105
 
106
106
  key = Digest::SHA256.hexdigest(yield)
@@ -0,0 +1,71 @@
1
+ require_relative "../ask_resolution"
2
+
3
+ module Hecks
4
+ module Bluebook
5
+ module ModelCheck
6
+ # The checks over `ask`: one that resolves to no declared port operation, a port trigger
7
+ # that should be an ask, and a declared ask no policy reaches.
8
+ module AskChecks
9
+ # @param bluebook [Bluebook::Chapter] the chapter the policy is declared in
10
+ # @param policy [Bluebook::Policy] the policy to check
11
+ # @return [Finding, nil] an error for an `ask` that names no declared ask (or an
12
+ # ambiguous one), a warning for a `trigger` that names a port operation, else `nil`
13
+ def ask_finding(bluebook, policy)
14
+ return port_trigger_finding(bluebook, policy) unless policy.asks?
15
+
16
+ outcome = AskResolution.call(bluebook, policy)
17
+ return if outcome.resolved?
18
+
19
+ Finding.new(kind: :unresolved_ask, severity: :error, subject: policy.name, message: outcome.detail)
20
+ end
21
+
22
+ # A port operation no policy reaches, by `ask` or by a trigger that names it.
23
+ #
24
+ # @param bluebook [Bluebook::Chapter] the chapter whose aggregates declare the ports
25
+ # @return [Array<Finding>] one warning per outbound operation nothing asks
26
+ def unused_ask_findings(bluebook)
27
+ reached = reached_port_verbs(bluebook)
28
+ bluebook.aggregates.flat_map do |aggregate|
29
+ outbound_operations(aggregate).filter_map do |port, operation|
30
+ verb = "#{aggregate.hecks_name}.#{port.name}.#{operation.hecks_name}"
31
+ next if reached.include?(Naming.split_verb("#{bluebook.name}::#{verb}"))
32
+
33
+ Finding.new(kind: :unused_ask, severity: :warning, subject: verb,
34
+ message: "the hecksagon declares this ask but no policy asks it")
35
+ end
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def port_trigger_finding(bluebook, policy)
42
+ return unless port_operation_verb?(bluebook, policy.trigger_command)
43
+
44
+ Finding.new(kind: :port_trigger, severity: :warning, subject: policy.name,
45
+ message: "trigger #{policy.trigger_command.inspect} names a port operation — " \
46
+ "write `ask :#{Naming.snake(policy.trigger_command.to_s.split(".").last)}` and " \
47
+ "let the hecksagon map the port")
48
+ end
49
+
50
+ def port_operation_verb?(bluebook, verb)
51
+ qualified = Naming.split_verb("#{bluebook.name}::#{verb}")
52
+ !qualified.nil? && port_verbs_of(bluebook).any? { |held| Naming.split_verb(held) == qualified }
53
+ end
54
+
55
+ def reached_port_verbs(bluebook)
56
+ from_policies = bluebook.policies.map { |policy| policy.trigger_command.to_s }
57
+ from_sagas = bluebook.process_managers.flat_map do |manager|
58
+ manager.handlers.flat_map { |handler| handler.dispatches.map(&:command_name) }
59
+ end
60
+ (from_policies + from_sagas).to_set { |verb| Naming.split_verb("#{bluebook.name}::#{verb}") }
61
+ end
62
+
63
+ def outbound_operations(aggregate)
64
+ aggregate.ports.flat_map do |port|
65
+ port.operations.select(&:outbound?).map { |operation| [port, operation] }
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -11,7 +11,8 @@ module Hecks
11
11
 
12
12
  [
13
13
  deaf_policy_finding(policy, emitted_events(bluebook), global_emitted_events),
14
- unknown_trigger_finding(bluebook, policy)
14
+ policy.asks? ? nil : unknown_trigger_finding(bluebook, policy),
15
+ ask_finding(bluebook, policy)
15
16
  ].compact
16
17
  end
17
18