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
@@ -0,0 +1,160 @@
1
+ require "json"
2
+
3
+ module Hecks
4
+ module Adapters
5
+ module Driving
6
+ module CliRunner
7
+ # Runs one resolved command or question and shapes what it answers. Extended into
8
+ # `CliRunner`.
9
+ module Answers
10
+ # What a dispatch needs beyond the spec: the parsed arguments, whether `--wait` was given,
11
+ # and the chapter and launcher setting the command belongs to.
12
+ Invocation = Struct.new(:args, :wait, :bluebook, :launcher)
13
+
14
+ # Parses one resolved command's arguments, runs it as a query or command, and turns the
15
+ # outcome, or the domain's refusal, into `[json_or_message, status]`. With `--wait`, a
16
+ # question whose report names a gap (`LauncherOptions.gap_reported?`) answers status 1.
17
+ #
18
+ # @param bluebook [Bluebook::Chapter] the chapter the command belongs to
19
+ # @param launcher [Hash, nil] the chapter's `launcher` world setting; nil when not opted
20
+ # in
21
+ def dispatch(runtime, spec, name, rest, program, asking, bluebook: nil, launcher: nil) # rubocop:disable Metrics/ParameterLists -- the signature is what `call` passes
22
+ args, wait = command_line(spec, rest, launcher)
23
+ return answer_query(runtime, spec, args, wait, launcher) if spec[:kind] == :query
24
+
25
+ run_command(runtime, spec, Invocation.new(args, wait, bluebook, launcher))
26
+ rescue Runtime::NotFound, Runtime::TypeMismatch => e
27
+ # A bad argument and a missing record both need the same next step: read the help.
28
+ ["#{e.message}\n\n #{program} #{"query " if asking}#{name} --help", 1]
29
+ rescue *Runtime::DOMAIN_REFUSALS => e
30
+ # The refusal is the chapter's own sentence, verbatim.
31
+ [e.message, 1]
32
+ rescue StandardError => e
33
+ # So is a wrapped tool's, when a question's adapter refuses to answer.
34
+ raise unless tool_refusal?(e)
35
+
36
+ [e.message, 1]
37
+ end
38
+
39
+ # The parsed arguments of a command line and whether to wait for the reactions.
40
+ def command_line(spec, rest, launcher)
41
+ rest, wait = LauncherOptions.take_wait(spec, rest) if launcher
42
+ [Cli.arguments(spec, rest), wait || LauncherOptions.settled?(launcher, spec)]
43
+ end
44
+
45
+ # Whether the error is a wrapped tool's refusal. Matched by name: the adapters belong to
46
+ # the
47
+ # Hecks chapter, which a client's runtime never loads.
48
+ def tool_refusal?(error)
49
+ error.class.ancestors.map(&:name).include?(CliRunner::TOOL_REFUSAL)
50
+ end
51
+
52
+ # Runs a command and answers its outcome, settled when `--wait` asks for it.
53
+ def run_command(runtime, spec, invocation)
54
+ args, minted = LauncherOptions.run_key(runtime, spec, invocation.args, invocation.launcher)
55
+ handle = runtime.dispatch_flat(spec[:command], normalized_request(spec, args))
56
+ extra = refusals_and_run(handle, minted)
57
+ return settled(runtime, spec, handle, invocation.bluebook, invocation.launcher, extra) if invocation.wait
58
+
59
+ [JSON.pretty_generate(outcome(handle, extra)), 0]
60
+ end
61
+
62
+ # The request a command takes from its parsed arguments.
63
+ def normalized_request(spec, args)
64
+ CommandRequest.normalize(args, receiver: spec[:receiver], legacy_receiver: spec[:legacy_receiver])
65
+ end
66
+
67
+ # The refused reactions of the dispatch, led by the minted run key when one was.
68
+ def refusals_and_run(handle, minted)
69
+ extra = refused_answer(handle)
70
+ minted ? { run: minted }.merge(extra) : extra
71
+ end
72
+
73
+ # Answers with only this command's outcome; a full store dump is every record there is.
74
+ def outcome(handle, extra)
75
+ return answered(handle).merge(extra) if handle.state.nil?
76
+
77
+ { id: handle.id, state: Json.materialize(handle.state), events: handle.events.map(&:name) }.merge(extra)
78
+ end
79
+
80
+ # Runs a question and answers its rows, or its text when an adapter answered in text.
81
+ #
82
+ # @param wait [Boolean, nil] whether `--wait` was given: a report naming a gap then fails
83
+ # @param launcher [Hash, nil] the chapter's `launcher` setting, which may name the
84
+ # questions that refuse a run they do not hold
85
+ # @return [Array(String, Integer)] the answer and the status
86
+ def answer_query(runtime, spec, args, wait, launcher = nil)
87
+ rows = runtime.query(spec[:command], **args)
88
+ return [unknown_run(args), 1] if rows.empty? && LauncherOptions.unknown_run?(launcher, spec)
89
+
90
+ text = text_answer(spec, rows)
91
+ [text || JSON.pretty_generate(rows.map { |row| Json.materialize(row) }),
92
+ wait && LauncherOptions.gap_reported?(text) ? 1 : 0]
93
+ end
94
+
95
+ # What a question about one run answers when the process holds no such run, for a
96
+ # question the launcher setting lists under `unknown_runs`: a run lives in the process
97
+ # that made it, so a later call has nothing to read, and saying so beats an empty list
98
+ # that reads as "no outcome yet".
99
+ #
100
+ # @param args [Hash] the question's parsed arguments, `run` among them
101
+ # @return [String] the message
102
+ def unknown_run(args)
103
+ key = args[:run].is_a?(Hash) ? args[:run][:value] : args[:run]
104
+ "no run #{key.to_s.inspect} is held by this process: a run is kept in memory by the process " \
105
+ "that made it, so read its outcome from the call that requested it (`--wait` prints it whole)"
106
+ end
107
+
108
+ # The text a query answered by a port gave, when that is the whole answer.
109
+ #
110
+ # A query declared `returns Document` (one value object of the single String attribute
111
+ # `text`) answers one document; printing it raw keeps the document (JSON, Markdown,
112
+ # sentences) readable and pipeable instead of quoted inside another JSON document. What a
113
+ # query declares decides it, not how many rows came back: any other query prints JSON,
114
+ # so a script reading it sees an array whether it held one row or two.
115
+ #
116
+ # @param spec [Hash] the question's projected spec
117
+ # @param rows [Array<Hash>] the query's rows
118
+ # @return [String, nil] the text, or nil when the query does not return a Document
119
+ def text_answer(spec, rows)
120
+ return unless spec[:returns] == "Document" && rows.length == 1 && rows.first.keys == [:text]
121
+ return unless rows.first[:text].is_a?(String)
122
+
123
+ rows.first[:text]
124
+ end
125
+
126
+ # The reactions one dispatch caused that the domain refused, as `refused_reactions:`.
127
+ #
128
+ # A policy's trigger that a `given` refuses is not the command's own refusal: the command
129
+ # has already persisted. The dispatch result carries them (`Result#refused_reactions`);
130
+ # without this the answer would say nothing of it. A defect (a crash) is shown apart, as
131
+ # `reaction_defects:`, and fails `--wait`.
132
+ #
133
+ # @param handle [Runtime::Dispatcher::Result, Runtime::RemoteDispatcher::Result] the
134
+ # outcome
135
+ # @return [Hash] `refused_reactions:` each with the `policy`, its `trigger`, the `reason`;
136
+ # empty when every reaction was delivered (or a remote host sent no per-step log), plus
137
+ # `reaction_defects:` when one crashed
138
+ def refused_answer(handle)
139
+ refused = handle.respond_to?(:refused_reactions) ? handle.refused_reactions : []
140
+ defects = handle.respond_to?(:reaction_defects) ? handle.reaction_defects : []
141
+ answer = refused.empty? ? {} : { refused_reactions: refused }
142
+ defects.empty? ? answer : answer.merge(reaction_defects: defects)
143
+ end
144
+
145
+ # Shapes a port operation's outcome, which has no state: each event with its full payload.
146
+ #
147
+ # The answer lives only in the payloads, so names alone would drop it. Exit status stays 0
148
+ # for refusal events too, since a healthy no is an answer, not a misuse.
149
+ def answered(handle)
150
+ # A port operation hydrates no instance, so the handle's `id` is nil by design.
151
+ { id: handle.id || handle.events.first&.id,
152
+ events: handle.events.map do |event|
153
+ { name: event.name, payload: Json.materialize(event.payload) }
154
+ end }
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,155 @@
1
+ module Hecks
2
+ module Adapters
3
+ module Driving
4
+ module CliRunner
5
+ # Parses a command line against the projection. Extended into `CliRunner`.
6
+ module Resolution
7
+ # The words that ask for the usage.
8
+ HELP_WORDS = ["--help", "-h", "help"].freeze
9
+
10
+ # Parses a command line against the projection: either the answer it gives without
11
+ # running anything, or the command it would run.
12
+ #
13
+ # @return [Hash] `{answer: [text, status]}`, or `{spec:, name:, rest:, asking:, program:,
14
+ # bluebook:, launcher:}` for a command to dispatch
15
+ def resolve(runtime, argv, program)
16
+ refused = Admission.refusal(runtime.registry, "Cli")
17
+ return { answer: [refused, 1] } if refused
18
+
19
+ bluebook, argv, program = chapter_for(runtime, argv, program)
20
+ launcher = LauncherOptions.settings(runtime, bluebook.name)
21
+ options = LauncherOptions.projection(launcher, program)
22
+ cli = Projector.call(:cli, bluebook: bluebook, options: options.merge(audience(runtime, launcher)))
23
+ scope = { runtime: runtime, bluebook: bluebook, launcher: launcher, options: options,
24
+ cli: cli, program: program }
25
+ usage_answer(scope, argv.first) || command_plan(scope, argv)
26
+ end
27
+
28
+ # The answer to a line that is only a request for usage, or nil.
29
+ def usage_answer(scope, name)
30
+ return { answer: [scope[:cli][:usage], 0] } if name.nil? || HELP_WORDS.include?(name)
31
+ return { answer: [all_usage(*scope.values_at(:runtime, :bluebook, :launcher, :options)), 0] } if name == "--all"
32
+ return unless name == "--maintainer"
33
+
34
+ { answer: [maintainer_usage(*scope.values_at(:runtime, :bluebook, :launcher, :options)), 0] }
35
+ end
36
+
37
+ # The command a line names, or the answer when it names none or asks for its help.
38
+ def command_plan(scope, argv)
39
+ cli = scope[:cli]
40
+ asking, name, argv = entry_word(cli, argv)
41
+ return { answer: [cli[:usage], 1] } if name.nil?
42
+
43
+ spec = find_spec(cli, asking, name)
44
+ return { answer: [unknown(cli, name, asking, scope[:program]), 1] } unless spec
45
+
46
+ rest = argv[1..]
47
+ return { answer: [command_help(scope, name, asking), 0] } if rest.include?("--help")
48
+
49
+ { spec: spec, name: name, rest: rest, asking: asking, program: scope[:program],
50
+ bluebook: scope[:bluebook], launcher: scope[:launcher] }
51
+ end
52
+
53
+ # The spec a name reaches; the alias map lets `create_pizza` and `order.create_pizza`
54
+ # reach the same command.
55
+ def find_spec(cli, asking, name)
56
+ pool = asking ? cli[:questions] : cli[:commands]
57
+ pool[cli[:names][asking ? :question : :command][name]]
58
+ end
59
+
60
+ # The usage of one command or question, which its `--help` asks for.
61
+ def command_help(scope, name, asking)
62
+ options = scope[:options].merge(command: name, ask: asking)
63
+ Projector.call(:cli, bluebook: scope[:bluebook], options: options)[:usage]
64
+ end
65
+
66
+ # The usage with the internal commands and queries listed too, which `--all` asks for, and
67
+ # with the maintainer's too.
68
+ def all_usage(runtime, bluebook, launcher, options)
69
+ shown = options.merge(audience(runtime, launcher, maintainer: true))
70
+ Projector.call(:cli, bluebook: bluebook, options: shown.merge(all: true))[:usage]
71
+ end
72
+
73
+ # The usage a maintainer of the hecks checkout sees, which `--maintainer` asks for
74
+ # anywhere.
75
+ def maintainer_usage(runtime, bluebook, launcher, options)
76
+ shown = options.merge(audience(runtime, launcher, maintainer: true))
77
+ Projector.call(:cli, bluebook: bluebook, options: shown)[:usage]
78
+ end
79
+
80
+ # What the help leaves out and points at for whoever typed the line: the maintainer's
81
+ # aggregates only in a hecks checkout, and the chapters the chapter's launcher names, each
82
+ # with the first sentence of what it is for.
83
+ #
84
+ # @return [Hash{Symbol => Object}] `:hide` and `:chapters` (pairs of word and summary)
85
+ def audience(runtime, launcher, maintainer: LauncherOptions.maintainer?)
86
+ shown = LauncherOptions.audience(launcher, maintainer)
87
+ return shown unless shown[:chapters]
88
+
89
+ shown.merge(chapters: chapter_summaries(runtime, shown[:chapters]))
90
+ end
91
+
92
+ # Each named chapter the domain attaches, as its word and the first sentence of its
93
+ # vision.
94
+ def chapter_summaries(runtime, names)
95
+ known = attached_chapters(runtime)
96
+ names.filter_map do |name|
97
+ next unless known.include?(name)
98
+
99
+ [Naming.snake(name), Projector::CliProjector.first_sentence(runtime.registry.bluebook(name).vision)]
100
+ end
101
+ end
102
+
103
+ # Names who the help is for, so the help remembered for one is not given to the other.
104
+ def audience_key
105
+ LauncherOptions.maintainer? ? "maintainer" : "project"
106
+ end
107
+
108
+ # Reads the entry words of a line: whether it asks a query, the bare name, and the words
109
+ # from the name on.
110
+ #
111
+ # `query` gives queries their own namespace (`ask` is its older spelling); a chapter may
112
+ # declare a command and a query of one name. A trailing `!` says the word is a command and
113
+ # is not part of the name. A query answers to its bare name too; `query` is needed only
114
+ # when
115
+ # a command shares it.
116
+ #
117
+ # @return [Array(Boolean, String, Array<String>)] `asking`, `name` (nil when absent),
118
+ # `argv`
119
+ def entry_word(cli, argv)
120
+ asking = CliRunner::QUERY_WORDS.include?(argv.first)
121
+ argv = argv[1..] if asking
122
+ name = argv.first
123
+ return [asking, nil, argv] if name.nil?
124
+
125
+ bang = name.end_with?("!")
126
+ name = name.chomp("!")
127
+ [asking || (!bang && query_only?(cli, name)), name, argv]
128
+ end
129
+
130
+ # Whether the name belongs to a query and to no command.
131
+ def query_only?(cli, name)
132
+ !cli[:names][:command].key?(name) && cli[:names][:question].key?(name)
133
+ end
134
+
135
+ # The chapter a command line speaks to: the booted domain's own, or, when the first word
136
+ # names a chapter its hecksagon attaches or uses as a framework member (`deploy`,
137
+ # `governance`), that chapter, with the word dropped and added to the program name.
138
+ def chapter_for(runtime, argv, program)
139
+ own = runtime.registry.bluebooks.values.first
140
+ target = attached_chapters(runtime).find { |name| Naming.snake(name) == argv.first }
141
+ return [own, argv, program] unless target
142
+
143
+ [runtime.registry.bluebook(target), argv[1..], "#{program} #{argv.first}"]
144
+ end
145
+
146
+ # The chapters the booted domain's hecksagon attaches or uses as framework members.
147
+ def attached_chapters(runtime)
148
+ own = runtime.registry.bluebooks.values.first
149
+ Array(runtime.registry.hecksagon(own.name)&.member_chapters)
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,120 @@
1
+ require "json"
2
+
3
+ module Hecks
4
+ module Adapters
5
+ module Driving
6
+ module CliRunner
7
+ # The answer `--wait` gives once every reaction has run. Extended into `CliRunner`.
8
+ module Settling
9
+ # The answer `--wait` gives: the record re-read after every reaction has run, with all its
10
+ # events, and a status of 1 when its lifecycle ended in a failure state, a refused
11
+ # reaction
12
+ # blocks the run or a reaction crashed.
13
+ #
14
+ # A refusal blocks unless a sibling reaction delivered another command on the same
15
+ # aggregate (`Runtime::ReactionOutcome`). The settled record is always the text, so a
16
+ # failed
17
+ # run pipes like a passing one; the reason a human reads is a third element.
18
+ #
19
+ # @return [Array(String, Integer)] the JSON and the status, plus (Array(String, Integer,
20
+ # String)) why the run failed when it did
21
+ def settled(runtime, spec, handle, bluebook, launcher, extra) # rubocop:disable Metrics/ParameterLists -- pinned by the callers and specs
22
+ why = reactions_failed(handle)
23
+ return finish(stateless_json(handle, extra), why) if handle.state.nil?
24
+
25
+ aggregate = aggregate_of(bluebook, spec)
26
+ state = reread(runtime, bluebook, aggregate, handle) || handle.state
27
+ answer = settled_answer(handle, state, settled_event_names(runtime, bluebook, aggregate, handle), extra)
28
+ why += failure_reasons(aggregate, state, launcher)
29
+ settled_result(answer, LauncherOptions.report?(launcher, spec), why)
30
+ end
31
+
32
+ # The settled answer as the launcher's report when it prints one, else as JSON.
33
+ def settled_result(answer, report, why)
34
+ report ? report_of(answer[:state], why) : finish(JSON.pretty_generate(answer), why)
35
+ end
36
+
37
+ # The JSON of a port operation's outcome, which has no record to re-read.
38
+ def stateless_json(handle, extra)
39
+ JSON.pretty_generate(answered(handle).merge(extra))
40
+ end
41
+
42
+ # The record, as the JSON answer holds it, with the names of its events.
43
+ def settled_answer(handle, state, events, extra)
44
+ { id: handle.id, state: Json.materialize(state), events: events }.merge(extra)
45
+ end
46
+
47
+ # The names of the events the record's run emitted, as the runtime now holds them.
48
+ def settled_event_names(runtime, bluebook, aggregate, handle)
49
+ fqn = "#{bluebook.name}::#{aggregate&.hecks_name}"
50
+ events = runtime.events.select { |event| event.aggregate == fqn && event.id == handle.id }
51
+ (events.empty? ? handle.events : events).map(&:name)
52
+ end
53
+
54
+ # The sentence for a record that ended in a failure state, as a list: empty otherwise.
55
+ def failure_reasons(aggregate, state, launcher)
56
+ return [] unless LauncherOptions.failed?(aggregate, state, launcher)
57
+
58
+ [failure_sentence(aggregate, state, aggregate.lifecycle.field)]
59
+ end
60
+
61
+ # The answer of a command the launcher prints as its report: the text it recorded, with
62
+ # the status 1 when the run failed, and nothing else.
63
+ def report_of(state, why)
64
+ text = state[why.empty? ? :output : :refusal]
65
+ text = text[:value] if text.is_a?(Hash)
66
+ text = why.join("\n") if text.to_s.strip.empty?
67
+ [text.to_s, why.empty? ? 0 : 1]
68
+ end
69
+
70
+ # The sentence for a record that ended in a failure state: the state, then the record's
71
+ # own
72
+ # `refusal` when it keeps one, without the class name an adapter's failure carries.
73
+ def failure_sentence(aggregate, state, field)
74
+ sentence = "#{aggregate.hecks_name} ended in the failure state #{state[field.to_sym].to_s.inspect}"
75
+ refusal = state[:refusal]
76
+ refusal = refusal.value if refusal.respond_to?(:value)
77
+ refusal = refusal[:value] if refusal.is_a?(Hash)
78
+ return sentence if refusal.to_s.strip.empty?
79
+
80
+ "#{sentence}: #{refusal.to_s.strip.sub(/\A(\w+::)+\w+: /, "")}"
81
+ end
82
+
83
+ # The answer and its status: 0 when nothing failed, else 1 with the reasons joined.
84
+ def finish(text, reasons)
85
+ reasons.empty? ? [text, 0] : [text, 1, reasons.join("\n")]
86
+ end
87
+
88
+ # What the reactions of the run the handle reports did wrong: each refusal that blocks it
89
+ # and each crash, as one sentence apiece.
90
+ def reactions_failed(handle)
91
+ blocking = handle.respond_to?(:blocking_reactions) ? handle.blocking_reactions : []
92
+ crashed = handle.respond_to?(:reaction_defects) ? handle.reaction_defects : []
93
+ blocking.map { |row| "reaction #{row[:policy]} was refused (#{row[:trigger]}): #{row[:reason]}" } +
94
+ crashed.map { |row| "reaction #{row[:policy]} crashed (#{row[:error_class]}): #{row[:reason]}" }
95
+ end
96
+
97
+ # Whether a reaction the domain refused blocks the run the handle reports.
98
+ def blocked?(handle)
99
+ handle.respond_to?(:blocking_reactions) && !handle.blocking_reactions.empty?
100
+ end
101
+
102
+ # The aggregate a top-level command belongs to; nil for an entity command or a port.
103
+ def aggregate_of(bluebook, spec)
104
+ head = spec[:command].split("::", 2).last
105
+ return if head.count(".") != 1
106
+
107
+ bluebook.aggregates.find { |aggregate| aggregate.hecks_name == head.split(".").first }
108
+ end
109
+
110
+ # The record as its repository holds it now, or nil when it cannot be read.
111
+ def reread(runtime, bluebook, aggregate, handle)
112
+ return unless aggregate && runtime.respond_to?(:registry)
113
+
114
+ runtime.registry.repository(bluebook.name, aggregate)&.find(handle.id)&.to_h
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,93 @@
1
+ require "json"
2
+
3
+ module Hecks
4
+ module Adapters
5
+ module Driving
6
+ module CliRunner
7
+ # Tails a question: asks again from the cursor each answer gave, printing new entries as
8
+ # JSON lines. Extended into `CliRunner`, so its methods are the runner's own.
9
+ module Streaming
10
+ # Tails a question: asks, prints each new entry as one JSON line, and asks again from the
11
+ # cursor the answer gave, until the reader interrupts. Only for a question the `launcher`
12
+ # setting lists under `streams`, given `--stream`; `from_now` applies to the first ask
13
+ # only.
14
+ #
15
+ # @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted domain
16
+ # @param argv [Array<String>] as for `call`, with `--stream`
17
+ # @param program [String] how the caller was invoked
18
+ # @param out [IO] where each entry's line goes
19
+ # @param err [IO] where a refusal goes
20
+ # @param max_polls [Integer, nil] stop after this many asks; unbounded when nil
21
+ # @return [Integer, nil] 0 or 1 for a stream, nil when `call` should run the line
22
+ def stream(runtime:, argv:, program: "hecks run", out: $stdout, err: $stderr, max_polls: nil) # rubocop:disable Metrics/ParameterLists -- the keyword API is what callers pass
23
+ plan = resolve(runtime, argv, program)
24
+ words = stream_words(plan)
25
+ return unless words
26
+
27
+ run_stream(runtime, plan[:spec], words, out, max_polls)
28
+ rescue Interrupt, Errno::EPIPE
29
+ 0
30
+ rescue Runtime::NotFound, Runtime::TypeMismatch => e
31
+ refuse(err, "#{e.message}\n\n #{program} #{plan[:name]} --help")
32
+ rescue *Runtime::DOMAIN_REFUSALS => e
33
+ refuse(err, e.message)
34
+ end
35
+
36
+ # Tails the question the line names and answers 0, the status of a stream that ended.
37
+ def run_stream(runtime, spec, words, out, max_polls)
38
+ tail(runtime, spec, Cli.arguments(spec, words), out, max_polls)
39
+ 0
40
+ end
41
+
42
+ # Prints a refusal and answers status 1.
43
+ def refuse(err, text)
44
+ err.puts(text)
45
+ 1
46
+ end
47
+
48
+ # One ask after another, each from the cursor the last gave.
49
+ def tail(runtime, spec, args, out, max_polls)
50
+ args = args.merge(wait: { value: 30 }) unless args.key?(:wait)
51
+ polls = 0
52
+ cursor = nil
53
+ loop do
54
+ row = runtime.query(spec[:command], **next_ask(args, cursor)).first || {}
55
+ print_entries(out, row[:events])
56
+ cursor = row[:cursor]
57
+ polls += 1
58
+ break if cursor.nil? || (max_polls && polls >= max_polls)
59
+ end
60
+ end
61
+
62
+ # An entry as a line: its payload, which the answer holds as JSON text, back as an object.
63
+ def entry_line(event)
64
+ line = JSON.parse(JSON.generate(event))
65
+ line["payload"] = JSON.parse(line["payload"]) if line["payload"].is_a?(String)
66
+ line
67
+ rescue JSON::ParserError
68
+ line
69
+ end
70
+
71
+ # The words to ask with when the line is a stream of a streamable question; nil otherwise.
72
+ def stream_words(plan)
73
+ return if plan[:answer] || !LauncherOptions.streams?(plan[:launcher], plan[:spec])
74
+
75
+ words, streaming = LauncherOptions.take_stream(plan[:rest])
76
+ words if streaming
77
+ end
78
+
79
+ # The arguments of the next ask: the first as given, later ones from the cursor.
80
+ def next_ask(args, cursor)
81
+ cursor ? args.except(:from_now).merge(since: { value: cursor }) : args
82
+ end
83
+
84
+ # Prints each event of an answer as one JSON line and flushes.
85
+ def print_entries(out, events)
86
+ Array(events).each { |event| out.puts(JSON.generate(entry_line(Json.materialize(event)))) }
87
+ out.flush
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,47 @@
1
+ module Hecks
2
+ module Adapters
3
+ module Driving
4
+ module CliRunner
5
+ # Words the answer for a command or question the chapter does not declare. Extended into
6
+ # `CliRunner`.
7
+ module Suggestions
8
+ # Words the answer for an unknown command or question, suggesting up to five near names.
9
+ # Ranked by shared prefix, which survives a dropped letter where a substring would not.
10
+ def unknown(cli, name, asking, program)
11
+ near = near_names(cli, name, asking)
12
+ lines = ["no such #{asking ? "query" : "command"}: #{name}"]
13
+ lines += ["", "did you mean:", *near.first(5).map { |candidate| " #{candidate}" }] unless near.empty?
14
+ lines += ["", " #{program}#{" query" if asking} for the full list"]
15
+ lines.join("\n")
16
+ end
17
+
18
+ # The names worth suggesting: the bare name's homes first, then the names sharing a
19
+ # prefix.
20
+ def near_names(cli, name, asking)
21
+ # Both spellings are candidates: `order.create_piza` shares no prefix with
22
+ # `create_pizza`.
23
+ pool = cli[:names][asking ? :question : :command].keys
24
+ # A bare name is the commonest slip now that the aggregate is required: its homes first.
25
+ homes = (pool + cli[:names][asking ? :command : :question].keys)
26
+ .select { |candidate| candidate.split(".").last == name }.uniq
27
+ homes + (similar_names(pool, name) - homes)
28
+ end
29
+
30
+ # The pool's names that share at least half of `name` (and three letters) as a prefix.
31
+ def similar_names(pool, name)
32
+ pool.map { |candidate| [shared_prefix(candidate, name), candidate] }
33
+ .select { |shared, _| shared >= [name.length / 2, 3].max }
34
+ .sort_by { |shared, candidate| [-shared, candidate] }
35
+ .map(&:last)
36
+ end
37
+
38
+ # The length of the prefix two words share.
39
+ def shared_prefix(one, other)
40
+ length = [one.length, other.length].min
41
+ (0...length).find { |index| one[index] != other[index] } || length
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,69 @@
1
+ require "json"
2
+ require_relative "cli"
3
+ require_relative "command_request"
4
+ require_relative "json"
5
+ require_relative "launcher_options"
6
+ require_relative "../../projector"
7
+ require_relative "../../ports/clock"
8
+ require_relative "cli_runner/answers"
9
+ require_relative "cli_runner/resolution"
10
+ require_relative "cli_runner/settling"
11
+ require_relative "cli_runner/streaming"
12
+ require_relative "cli_runner/suggestions"
13
+
14
+ module Hecks
15
+ module Adapters
16
+ module Driving
17
+ # Parses and dispatches a command line against a `Projector::CliProjector` projection.
18
+ # Does no IO: it answers `[text, status]` and leaves printing and exiting to its callers.
19
+ module CliRunner
20
+ # The class an adapter raises when the tool it wraps refuses.
21
+ TOOL_REFUSAL = "Hecks::Adapters::ConsoleCapture::Failure".freeze
22
+ # The words that open the query namespace; `ask` is the older spelling.
23
+ QUERY_WORDS = %w[query ask].freeze
24
+
25
+ extend Resolution
26
+ extend Suggestions
27
+ extend Answers
28
+ extend Settling
29
+ extend Streaming
30
+
31
+ module_function
32
+
33
+ # Runs one command line against a booted domain and answers the text to print.
34
+ # The booted domain's own chapter is projected, or an attached one its first word names.
35
+ #
36
+ # @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted domain
37
+ # @param argv [Array<String>] an optional `query` (or `ask`), the command or query name,
38
+ # then
39
+ # `path=value` pairs; `--help`, `-h`, `help` or nothing answers usage
40
+ # @param program [String] how the caller was invoked, echoed in usage and hints
41
+ # @return [Array(String, Integer)] the text and the status: 0 answered, 1 refused or misused
42
+ # @raise [Runtime::WiringError] if a repository or the clock adapter cannot be resolved
43
+ # @raise [Runtime::StaleWrite] if concurrent writers beat the command through every retry
44
+ def call(runtime:, argv:, program: "hecks run")
45
+ plan = resolve(runtime, argv, program)
46
+ return plan[:answer] if plan[:answer]
47
+
48
+ dispatch(runtime, plan[:spec], plan[:name], plan[:rest], plan[:program], plan[:asking],
49
+ bluebook: plan[:bluebook], launcher: plan[:launcher])
50
+ end
51
+
52
+ # Answers a command line that asks only for usage, from the projection alone.
53
+ #
54
+ # Needs a registry and no bound adapter, so a `Runtime::Loader::Described` serves: the
55
+ # help text, the no-argument usage, a command's `--help` and an unknown one's hint all come
56
+ # out byte-identical to `call`'s.
57
+ #
58
+ # @param runtime [#registry] a booted domain, or what `Hecks.describe` answers
59
+ # @param argv [Array<String>] as for `call`
60
+ # @param program [String] how the caller was invoked
61
+ # @return [Array(String, Integer), nil] the text and status, or nil when the line would
62
+ # run a command or question and so needs a booted domain
63
+ def usage(runtime:, argv:, program: "hecks run")
64
+ UsageCache.fetch(runtime, argv, program, audience: audience_key) { resolve(runtime, argv, program)[:answer] }
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end