hecks 3.10.0 → 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 (481) 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 +18 -2
  116. data/lib/hecks/doc/reference/rendering.rb +6 -3
  117. data/lib/hecks/forms/app/commands.rb +8 -7
  118. data/lib/hecks/forms/app/queries.rb +2 -1
  119. data/lib/hecks/forms/app.rb +1 -1
  120. data/lib/hecks/forms/record_renderer.rb +1 -1
  121. data/lib/hecks/framework/bluebook/privacy.bluebook +11 -0
  122. data/lib/hecks/fuzzing/concurrent_dispatch.rb +4 -1
  123. data/lib/hecks/fuzzing/failure_triage.rb +2 -1
  124. data/lib/hecks/fuzzing/mutation/run.rb +11 -4
  125. data/lib/hecks/fuzzing/properties/catalog.rb +6 -4
  126. data/lib/hecks/fuzzing/properties/guaranteed_by_construction.rb +18 -11
  127. data/lib/hecks/fuzzing/properties/guards.rb +1 -1
  128. data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +1 -1
  129. data/lib/hecks/fuzzing/self_consistency.rb +6 -2
  130. data/lib/hecks/fuzzing/shrinker.rb +3 -1
  131. data/lib/hecks/fuzzing/value_generator.rb +19 -5
  132. data/lib/hecks/gate/stages.yml +4 -10
  133. data/lib/hecks/grammar/evolve.rb +1 -1
  134. data/lib/hecks/grammar/expression_operators.json +372 -0
  135. data/lib/hecks/grammar/operators.rb +3 -1
  136. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +10 -10
  137. data/lib/hecks/hecks/adapters/codebase/inspection/rubycritic.rb +35 -0
  138. data/lib/hecks/hecks/adapters/codebase/inspection.rb +104 -0
  139. data/lib/hecks/hecks/adapters/codebase/publishing.rb +2 -1
  140. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +12 -2
  141. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +2 -1
  142. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +1 -1
  143. data/lib/hecks/hecks/adapters/cost_explorer.rb +8 -5
  144. data/lib/hecks/hecks/adapters/deploy_toolchain/statuses.rb +2 -1
  145. data/lib/hecks/hecks/adapters/finding_github.rb +13 -6
  146. data/lib/hecks/hecks/adapters/first_admin.rb +2 -2
  147. data/lib/hecks/hecks/adapters/github_rulesets.rb +6 -3
  148. data/lib/hecks/hecks/adapters/in_process_boot.rb +1 -1
  149. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +1 -1
  150. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +10 -20
  151. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +28 -5
  152. data/lib/hecks/hecks/adapters/local_files.rb +2 -2
  153. data/lib/hecks/hecks/adapters/rehearsal/audit_report.rb +107 -0
  154. data/lib/hecks/hecks/adapters/rehearsal/census.rb +71 -0
  155. data/lib/hecks/hecks/adapters/rehearsal/declaration.rb +133 -0
  156. data/lib/hecks/hecks/adapters/rehearsal/environment.rb +83 -0
  157. data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +142 -0
  158. data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +96 -0
  159. data/lib/hecks/hecks/adapters/rehearsal/postgres_census.rb +100 -0
  160. data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +69 -0
  161. data/lib/hecks/hecks/adapters/rehearsal/postgres_store.rb +129 -0
  162. data/lib/hecks/hecks/adapters/rehearsal/rows.rb +41 -0
  163. data/lib/hecks/hecks/adapters/rehearsal/rust_audit.rb +83 -0
  164. data/lib/hecks/hecks/adapters/rehearsal/subject.rb +104 -0
  165. data/lib/hecks/hecks/adapters/rehearsal.rb +54 -0
  166. data/lib/hecks/hecks/adapters/rehearsal_store.adapter +7 -0
  167. data/lib/hecks/hecks/adapters/rehearsal_store.rb +81 -0
  168. data/lib/hecks/hecks/adapters/terminal.rb +13 -11
  169. data/lib/hecks/hecks/codebase.behaviors +13 -1
  170. data/lib/hecks/hecks/codebase.bluebook +239 -5
  171. data/lib/hecks/hecks/custodian.behaviors +24 -6
  172. data/lib/hecks/hecks/custodian.bluebook +6 -1227
  173. data/lib/hecks/hecks/deploy.behaviors +1 -1
  174. data/lib/hecks/hecks/deploy.world +1 -3
  175. data/lib/hecks/hecks/hecks.behaviors +1 -1
  176. data/lib/hecks/hecks/hecks.hecksagon +53 -18
  177. data/lib/hecks/hecks/hecks.world +10 -10
  178. data/lib/hecks/hecks/rehearsal.bluebook +477 -0
  179. data/lib/hecks/hecks/site.behaviors +1 -1
  180. data/lib/hecks/hecks/site.world +0 -1
  181. data/lib/hecks/hecks/tooling.bluebook +1238 -0
  182. data/lib/hecks/indifferent_key.rb +20 -0
  183. data/lib/hecks/language/adapter.bluebook +13 -3
  184. data/lib/hecks/language/bluebook/aggregate.bluebook +23 -0
  185. data/lib/hecks/language/bluebook/bluebook.bluebook +15 -0
  186. data/lib/hecks/language/bluebook/entity.bluebook +18 -0
  187. data/lib/hecks/language/bluebook/policy.bluebook +9 -1
  188. data/lib/hecks/language/bluebook/query.bluebook +1 -1
  189. data/lib/hecks/language/bluebook/vocabulary.bluebook +11 -4
  190. data/lib/hecks/language/hecksagon/domain_port.bluebook +1 -1
  191. data/lib/hecks/language/hecksagon/hecksagon.bluebook +22 -0
  192. data/lib/hecks/language/oidc.json +10 -0
  193. data/lib/hecks/language/port.bluebook +4 -4
  194. data/lib/hecks/literal.rb +12 -3
  195. data/lib/hecks/ports/key_vault.rb +7 -2
  196. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +21 -10
  197. data/lib/hecks/ports/persistence/plugins/era/postgres_era/boot.rb +4 -2
  198. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +2 -1
  199. data/lib/hecks/ports/persistence/repository_factory.rb +6 -3
  200. data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +29 -0
  201. data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +1 -1
  202. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  203. data/lib/hecks/projections/deploy/shared.rb +5 -2
  204. data/lib/hecks/projections/model/deviations.rb +2 -2
  205. data/lib/hecks/projections/model.rb +2 -2
  206. data/lib/hecks/projections/oidc.rb +13 -1
  207. data/lib/hecks/projections/shape.rb +5 -2
  208. data/lib/hecks/projections/site/cms_editor/attributes.rb +26 -6
  209. data/lib/hecks/projections/site/cms_editor/blocks.rb +157 -0
  210. data/lib/hecks/projections/site/cms_editor/checks.rb +14 -1
  211. data/lib/hecks/projections/site/cms_editor/clearing.rb +70 -0
  212. data/lib/hecks/projections/site/cms_editor/color.rb +124 -0
  213. data/lib/hecks/projections/site/cms_editor/composition.rb +42 -0
  214. data/lib/hecks/projections/site/cms_editor/destructive.rb +61 -0
  215. data/lib/hecks/projections/site/cms_editor/drafts.rb +102 -0
  216. data/lib/hecks/projections/site/cms_editor/hints.rb +74 -0
  217. data/lib/hecks/projections/site/cms_editor/icons.rb +86 -0
  218. data/lib/hecks/projections/site/cms_editor/key_kinds.rb +31 -0
  219. data/lib/hecks/projections/site/cms_editor/limits.rb +72 -0
  220. data/lib/hecks/projections/site/cms_editor/media.rb +1 -1
  221. data/lib/hecks/projections/site/cms_editor/moment.rb +34 -0
  222. data/lib/hecks/projections/site/cms_editor/palette.rb +115 -0
  223. data/lib/hecks/projections/site/cms_editor/pickers.rb +85 -0
  224. data/lib/hecks/projections/site/cms_editor/preview.rb +91 -0
  225. data/lib/hecks/projections/site/cms_editor/relations.rb +92 -0
  226. data/lib/hecks/projections/site/cms_editor/scale_checks.rb +66 -0
  227. data/lib/hecks/projections/site/cms_editor/scheduling.rb +150 -0
  228. data/lib/hecks/projections/site/cms_editor/schema.rb +96 -19
  229. data/lib/hecks/projections/site/cms_editor/setting.rb +52 -9
  230. data/lib/hecks/projections/site/cms_editor/targets.rb +86 -0
  231. data/lib/hecks/projections/site/cms_editor/templates/package.json.tmpl +18 -1
  232. data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +251 -57
  233. data/lib/hecks/projections/site/cms_editor/templates/src/assets.ts.tmpl +98 -0
  234. data/lib/hecks/projections/site/cms_editor/templates/src/auth/membership.ts.tmpl +32 -24
  235. data/lib/hecks/projections/site/cms_editor/templates/src/browser/app.css.tmpl +412 -0
  236. data/lib/hecks/projections/site/cms_editor/templates/src/browser/autosave.js.tmpl +144 -0
  237. data/lib/hecks/projections/site/cms_editor/templates/src/browser/blocks.js.tmpl +500 -0
  238. data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_doc.js.tmpl +175 -0
  239. data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_editor.js.tmpl +418 -0
  240. data/lib/hecks/projections/site/cms_editor/templates/src/browser/epoch.js.tmpl +61 -0
  241. data/lib/hecks/projections/site/cms_editor/templates/src/browser/forms.js.tmpl +210 -0
  242. data/lib/hecks/projections/site/cms_editor/templates/src/browser/lists.js.tmpl +40 -0
  243. data/lib/hecks/projections/site/cms_editor/templates/src/browser/main.js.tmpl +26 -0
  244. data/lib/hecks/projections/site/cms_editor/templates/src/browser/media_picker.js.tmpl +232 -0
  245. data/lib/hecks/projections/site/cms_editor/templates/src/browser/moments.js.tmpl +73 -0
  246. data/lib/hecks/projections/site/cms_editor/templates/src/browser/pickers.js.tmpl +152 -0
  247. data/lib/hecks/projections/site/cms_editor/templates/src/browser/preview.js.tmpl +98 -0
  248. data/lib/hecks/projections/site/cms_editor/templates/src/browser/shell.js.tmpl +140 -0
  249. data/lib/hecks/projections/site/cms_editor/templates/src/browser/toast.js.tmpl +66 -0
  250. data/lib/hecks/projections/site/cms_editor/templates/src/choices.ts.tmpl +99 -0
  251. data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +78 -24
  252. data/lib/hecks/projections/site/cms_editor/templates/src/config.ts.tmpl +6 -0
  253. data/lib/hecks/projections/site/cms_editor/templates/src/flash.ts.tmpl +52 -0
  254. data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +19 -2
  255. data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +31 -8
  256. data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +36 -0
  257. data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +131 -2
  258. data/lib/hecks/projections/site/cms_editor/templates/src/ui/availability.ts.tmpl +15 -0
  259. data/lib/hecks/projections/site/cms_editor/templates/src/ui/block_list.js.tmpl +213 -0
  260. data/lib/hecks/projections/site/cms_editor/templates/src/ui/blocks.ts.tmpl +275 -0
  261. data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_model.js.tmpl +8 -5
  262. data/lib/hecks/projections/site/cms_editor/templates/src/ui/detail.ts.tmpl +88 -0
  263. data/lib/hecks/projections/site/cms_editor/templates/src/ui/dialogs.ts.tmpl +43 -0
  264. data/lib/hecks/projections/site/cms_editor/templates/src/ui/fields.ts.tmpl +229 -34
  265. data/lib/hecks/projections/site/cms_editor/templates/src/ui/html.ts.tmpl +40 -81
  266. data/lib/hecks/projections/site/cms_editor/templates/src/ui/icons.ts.tmpl +15 -0
  267. data/lib/hecks/projections/site/cms_editor/templates/src/ui/input.ts.tmpl +29 -12
  268. data/lib/hecks/projections/site/cms_editor/templates/src/ui/links.ts.tmpl +100 -0
  269. data/lib/hecks/projections/site/cms_editor/templates/src/ui/moments.ts.tmpl +44 -0
  270. data/lib/hecks/projections/site/cms_editor/templates/src/ui/outline.ts.tmpl +53 -17
  271. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +178 -88
  272. data/lib/hecks/projections/site/cms_editor/templates/src/ui/paging.ts.tmpl +82 -0
  273. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +110 -0
  274. data/lib/hecks/projections/site/cms_editor/templates/src/ui/preview.ts.tmpl +87 -0
  275. data/lib/hecks/projections/site/cms_editor/templates/src/ui/related.ts.tmpl +97 -0
  276. data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +160 -0
  277. data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +211 -0
  278. data/lib/hecks/projections/site/cms_editor/templates/src/ui/table.ts.tmpl +151 -0
  279. data/lib/hecks/projections/site/cms_editor/templates/src/ui/theme.js.tmpl +16 -0
  280. data/lib/hecks/projections/site/cms_editor/templates/src/ui/values.ts.tmpl +69 -0
  281. data/lib/hecks/projections/site/cms_editor/templates/src/ui/words.ts.tmpl +97 -0
  282. data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +35 -0
  283. data/lib/hecks/projections/site/cms_editor/templates/tsconfig.json.tmpl +2 -1
  284. data/lib/hecks/projections/site/cms_editor/theme.rb +29 -0
  285. data/lib/hecks/projections/site/cms_editor/theme_checks.rb +57 -0
  286. data/lib/hecks/projections/site/cms_editor.rb +49 -23
  287. data/lib/hecks/projections/site/role_probe.rb +2 -0
  288. data/lib/hecks/projector/cli_audience.rb +2 -1
  289. data/lib/hecks/projector/cli_projector/listing.rb +1 -1
  290. data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
  291. data/lib/hecks/projector/docs_projector/closing.rb +1 -1
  292. data/lib/hecks/projector/docs_projector.rb +1 -1
  293. data/lib/hecks/projector/exporter/bindings.rb +40 -10
  294. data/lib/hecks/projector/exporter/capability_values.rb +55 -0
  295. data/lib/hecks/projector/narrate_projector/reactions.rb +1 -1
  296. data/lib/hecks/projector/target.rb +11 -0
  297. data/lib/hecks/projector.rb +32 -5
  298. data/lib/hecks/quality_control/adapters/github_checks.rb +12 -8
  299. data/lib/hecks/quality_control/adapters/github_issues.rb +8 -4
  300. data/lib/hecks/quality_control/cli/qa_open_pr.rb +2 -2
  301. data/lib/hecks/quality_control/cli/qa_sweep/checks/shrinking.rb +1 -1
  302. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +1 -1
  303. data/lib/hecks/quality_control/cli/qa_sweep.rb +1 -1
  304. data/lib/hecks/quality_control/quality_control.bluebook +6 -2
  305. data/lib/hecks/release/approval.rb +64 -0
  306. data/lib/hecks/router/{door.rb → aggregate_reader.rb} +5 -4
  307. data/lib/hecks/router/namespace_installer.rb +16 -16
  308. data/lib/hecks/router.rb +1 -1
  309. data/lib/hecks/runtime/best_effort.rb +22 -0
  310. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +12 -2
  311. data/lib/hecks/runtime/command_interpreter.rb +3 -2
  312. data/lib/hecks/runtime/command_rules/emission.rb +1 -1
  313. data/lib/hecks/runtime/dispatcher.rb +4 -4
  314. data/lib/hecks/runtime/entity_element/location.rb +1 -1
  315. data/lib/hecks/runtime/event.rb +5 -0
  316. data/lib/hecks/runtime/identity.rb +7 -6
  317. data/lib/hecks/runtime/interpreting.rb +2 -1
  318. data/lib/hecks/runtime/invocation_builder.rb +1 -1
  319. data/lib/hecks/runtime/loader/boot_steps.rb +8 -5
  320. data/lib/hecks/runtime/loader.rb +10 -10
  321. data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -4
  322. data/lib/hecks/runtime/policy_interpreter/fan_out.rb +3 -3
  323. data/lib/hecks/runtime/policy_interpreter.rb +6 -6
  324. data/lib/hecks/runtime/port_operation_interpreter.rb +1 -1
  325. data/lib/hecks/runtime/reaction_invocation.rb +1 -1
  326. data/lib/hecks/runtime/rebuild_sweep.rb +11 -1
  327. data/lib/hecks/runtime/registry/ask_verification.rb +35 -0
  328. data/lib/hecks/runtime/registry/hecksagon_rules.rb +10 -0
  329. data/lib/hecks/runtime/registry/merging.rb +1 -1
  330. data/lib/hecks/runtime/registry/providers.rb +16 -0
  331. data/lib/hecks/runtime/registry/read_repository.rb +6 -4
  332. data/lib/hecks/runtime/registry/verification.rb +4 -1
  333. data/lib/hecks/runtime/registry/world_defaults.rb +29 -0
  334. data/lib/hecks/runtime/registry.rb +1 -1
  335. data/lib/hecks/runtime/saga_interpreter/compensation.rb +1 -1
  336. data/lib/hecks/runtime/saga_interpreter/delivery.rb +4 -4
  337. data/lib/hecks/runtime/saga_interpreter/transitions.rb +1 -1
  338. data/lib/hecks/runtime/saga_interpreter.rb +3 -3
  339. data/lib/hecks/runtime/tenant_check.rb +5 -4
  340. data/lib/hecks/runtime/value/coercion.rb +2 -2
  341. data/lib/hecks/runtime/value/validation.rb +16 -4
  342. data/lib/hecks/runtime.rb +9 -9
  343. data/lib/hecks/rust_build/project_rust/target_ir.rb +1 -1
  344. data/lib/hecks/rust_build/wasm.rb +1 -1
  345. data/lib/hecks/rust_build/wasm_browser.rb +1 -1
  346. data/lib/hecks/rust_build.rb +11 -0
  347. data/lib/hecks/served_options.rb +53 -0
  348. data/lib/hecks/site/bluebook/site.bluebook +1 -0
  349. data/lib/hecks/storehouse/audit.rb +1 -1
  350. data/lib/hecks/storehouse/dispatching.rb +8 -6
  351. data/lib/hecks/storehouse/introspection.rb +2 -2
  352. data/lib/hecks/storehouse/querying.rb +6 -4
  353. data/lib/hecks/storehouse.rb +4 -4
  354. data/lib/hecks/tools/comment_style/source_file.rb +4 -1
  355. data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +4 -1
  356. data/lib/hecks/tools/lanes/rows.rb +6 -0
  357. data/lib/hecks/tools/oidc_manifests.rb +4 -5
  358. data/lib/hecks/tools/regeneration_run.rb +11 -4
  359. data/lib/hecks/tools/site_routes/editor_files.rb +12 -5
  360. data/lib/hecks/tools/tenant_provisioning.rb +2 -2
  361. data/lib/hecks/tools/tools_doc.rb +4 -4
  362. data/lib/hecks/version.rb +1 -1
  363. data/lib/hecks/vocabulary.rb +4 -4
  364. data/lib/hecks.rb +15 -15
  365. data/lib/rubocop/cop/hecks/env_dump.rb +69 -0
  366. data/lib/rubocop/cop/hecks/monotonic_duration.rb +53 -0
  367. data/lib/rubocop/cop/hecks/no_wall_clock_in_runtime.rb +34 -0
  368. data/lib/rubocop/cop/hecks/raw_shell_interpolation.rb +79 -0
  369. data/lib/rubocop/cop/hecks/sleep_in_spec.rb +34 -0
  370. data/lib/rubocop/cop/hecks/swallowed_rescue.rb +37 -0
  371. data/lib/rubocop/cop/hecks/symbol_string_key_mix.rb +50 -0
  372. data/lib/rubocop/cop/hecks/untyped_raise.rb +37 -0
  373. data/prebuilt/05c358d38d661a3774f813d49c66f5b68939af94432d66257a56f49ee2598994.marshal +0 -0
  374. data/prebuilt/26c40c61f09b09b11651c64668eda3ceb180f83cf2a3802d3033d2fd99ac841b.marshal +0 -0
  375. data/prebuilt/verdicts-230ac18c25abfd3d92ede032bee2661a4c2cd2773ab0cf0e2ae52546ba2f7af9.json +1 -0
  376. data/rust/.gitignore +6 -0
  377. data/rust/Cargo.toml +6 -0
  378. data/rust/build/src/binding_pass.rs +103 -0
  379. data/rust/build/src/cargo_sync.rs +6 -0
  380. data/rust/build/src/lineage_pass.rs +3 -3
  381. data/rust/build/src/main.rs +17 -5
  382. data/rust/build/src/pipeline.rs +48 -5
  383. data/rust/build/src/seams_pass.rs +123 -0
  384. data/rust/build/src/translation_pass.rs +167 -0
  385. data/rust/codegen/src/asks.rs +103 -0
  386. data/rust/codegen/src/commands.rs +13 -8
  387. data/rust/codegen/src/constraints.rs +25 -0
  388. data/rust/codegen/src/domain_generator.rs +2 -0
  389. data/rust/codegen/src/expr_emitter.rs +13 -0
  390. data/rust/codegen/src/json_codec.rs +54 -15
  391. data/rust/codegen/src/main.rs +1 -0
  392. data/rust/codegen/src/mutations.rs +128 -3
  393. data/rust/codegen/src/queries.rs +11 -2
  394. data/rust/codegen/src/reactions.rs +1 -1
  395. data/rust/codegen/src/types.rs +166 -8
  396. data/rust/host/HECKS_RELEASE +1 -1
  397. data/rust/host/src/api.rs +4 -4
  398. data/rust/host/src/bin/validate_harness.rs +43 -0
  399. data/rust/host/src/boundary_fuzz/expr_json.rs +2 -2
  400. data/rust/host/src/boundary_fuzz/ir.rs +1 -0
  401. data/rust/host/src/boundary_fuzz/server.rs +1 -1
  402. data/rust/host/src/checkout.rs +35 -19
  403. data/rust/host/src/commerce_ir.rs +408 -2
  404. data/rust/host/src/expr_json.rs +118 -0
  405. data/rust/host/src/mint.rs +1 -1
  406. data/rust/host/src/payments/tests.rs +27 -1
  407. data/rust/host/src/payments.rs +12 -4
  408. data/rust/host/src/query_step.rs +22 -4
  409. data/rust/host/src/reference_validate.rs +653 -108
  410. data/rust/host/src/resend.rs +2 -0
  411. data/rust/host/src/server.rs +19 -6
  412. data/rust/host/src/ui_schema.rs +132 -5
  413. data/rust/host/src/web/newsletter.rs +7 -8
  414. data/rust/host/src/web/newsletter_send.rs +38 -13
  415. data/rust/host/src/web/registrations.rs +229 -26
  416. data/rust/host/src/web.rs +3 -3
  417. data/rust/parser/src/emit.rs +38 -6
  418. data/rust/parser/src/expr/ast_json.rs +7 -1
  419. data/rust/parser/src/expr/resolver.rs +171 -6
  420. data/rust/parser/src/ir.rs +7 -0
  421. data/rust/parser/src/keywords.rs +24 -0
  422. data/rust/parser/src/main.rs +3 -0
  423. data/rust/parser/src/parse/aggregate.rs +2 -1
  424. data/rust/parser/src/parse/chapter.rs +2 -2
  425. data/rust/parser/src/parse/command.rs +2 -0
  426. data/rust/parser/src/parse/hecksagon.rs +5 -2
  427. data/rust/parser/src/parse/lifecycle.rs +81 -0
  428. data/rust/parser/src/parse/mod.rs +15 -1
  429. data/rust/parser/src/parse/policy.rs +13 -2
  430. data/rust/parser/src/parse/value_object.rs +13 -8
  431. data/rust/parser/src/ruby_value.rs +1 -1
  432. data/rust/src/exemplar/commands.rs +4 -4
  433. data/rust/src/exemplar/constraints.rs +1 -1
  434. data/rust/src/exemplar/json.rs +43 -1
  435. data/rust/src/kernel/cli.rs +7 -5
  436. data/rust/src/kernel/dispatch.rs +2 -2
  437. data/rust/src/kernel/dispatch_guard_tests.rs +1 -1
  438. data/rust/src/kernel/expr.rs +66 -3
  439. data/rust/src/kernel/expression_operators/arithmetic.rs +130 -5
  440. data/rust/src/kernel/expression_operators/text.rs +26 -2
  441. data/rust/src/kernel/json.rs +15 -0
  442. data/rust/src/kernel/mod.rs +1 -1
  443. data/rust/src/kernel/orchestrate.rs +54 -5
  444. data/rust/src/kernel/orchestrate_guard_tests.rs +67 -0
  445. data/rust/src/kernel/pattern.rs +60 -0
  446. data/rust/src/kernel/reference_lookup.rs +90 -7
  447. metadata +140 -38
  448. data/lib/hecks/doors/cli_door/nesting.rb +0 -34
  449. data/lib/hecks/doors/cli_door/shorthand.rb +0 -85
  450. data/lib/hecks/doors/cli_door.rb +0 -124
  451. data/lib/hecks/doors/cli_runner/answers.rb +0 -138
  452. data/lib/hecks/doors/cli_runner/resolution.rb +0 -146
  453. data/lib/hecks/doors/cli_runner/settling.rb +0 -115
  454. data/lib/hecks/doors/cli_runner/streaming.rb +0 -90
  455. data/lib/hecks/doors/cli_runner/suggestions.rb +0 -43
  456. data/lib/hecks/doors/cli_runner.rb +0 -66
  457. data/lib/hecks/doors/command_request.rb +0 -119
  458. data/lib/hecks/doors/handle.rb +0 -208
  459. data/lib/hecks/doors/json_door.rb +0 -132
  460. data/lib/hecks/doors/launcher_options.rb +0 -247
  461. data/lib/hecks/doors/mcp_door/properties.rb +0 -47
  462. data/lib/hecks/doors/mcp_door/tool_calls.rb +0 -113
  463. data/lib/hecks/doors/mcp_door/tool_definitions/auditing.rb +0 -51
  464. data/lib/hecks/doors/mcp_door/tool_definitions/calling.rb +0 -69
  465. data/lib/hecks/doors/mcp_door/tool_definitions/reading.rb +0 -97
  466. data/lib/hecks/doors/mcp_door/tool_definitions.rb +0 -15
  467. data/lib/hecks/doors/mcp_door.rb +0 -156
  468. data/lib/hecks/doors/mcp_door_scope/argument_policy.rb +0 -92
  469. data/lib/hecks/doors/mcp_door_scope/env_problems.rb +0 -55
  470. data/lib/hecks/doors/mcp_door_scope/presenter.rb +0 -78
  471. data/lib/hecks/doors/mcp_door_scope.rb +0 -201
  472. data/lib/hecks/doors/mcp_guide_cache.rb +0 -75
  473. data/lib/hecks/doors/ruby_door/aggregate_door.rb +0 -162
  474. data/lib/hecks/doors/ruby_door/chapter.rb +0 -89
  475. data/lib/hecks/doors/ruby_door.rb +0 -80
  476. data/lib/hecks/doors/usage_cache.rb +0 -139
  477. data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_widget.js.tmpl +0 -282
  478. data/lib/hecks/projections/site/cms_editor/templates/src/ui/media_picker.js.tmpl +0 -109
  479. data/prebuilt/c28d6d9db14eea3e17161e9d9c262efee30b256cf97693a7cfd552bce24b2f7f.marshal +0 -0
  480. data/prebuilt/cb375cba61ca0dbe8ddb9739c306510fe05e5f4b7a9b6955caff8224c63ea0e0.marshal +0 -0
  481. data/prebuilt/verdicts-d6d584ad43b5f3cebf88aafbbdbf024c38574acf18e06adffbc555f7786b1c2f.json +0 -1
@@ -0,0 +1,159 @@
1
+ require "json"
2
+ require_relative "../.."
3
+ require_relative "../../cli/mcp"
4
+ require_relative "mcp/tool_definitions"
5
+ require_relative "mcp/tool_calls"
6
+
7
+ module Hecks
8
+ module Adapters
9
+ module Driving
10
+ # The MCP server onto the storehouse bus: hand-rolled JSON-RPC over stdio, one
11
+ # server per booted domain. All logic and the JSON shape answered with live in
12
+ # `Storehouse`; this is plumbing only. A `role:`/`actor_id:` a caller sends is
13
+ # asserted, not verified, unless Governance is attached.
14
+ #
15
+ # There is no authentication: `domain:` boots real Ruby confined to
16
+ # `Storehouse::BOOT_ROOT`, so this is a code execution surface for anyone who
17
+ # can write to stdin (ADR 0062). Spawning with `HECKS_SERVER_TOOLS=readers` and
18
+ # `HECKS_SERVER_DOMAINS=<dir>[:<dir>...]` narrows a server to read-only tools and
19
+ # named domains (`McpScope`); this limits what one agent can reach and is
20
+ # not itself authentication.
21
+ module Mcp
22
+ PROTOCOL_VERSION = "2024-11-05".freeze
23
+
24
+ TOOLS = ToolDefinitions::TOOLS
25
+
26
+ NOTES = [
27
+ "Identity is self-asserted: role:/actor_id: are claims made by the caller and are not verified.",
28
+ "state, events, history, follow, describe, catalog and query run with no role check at all.",
29
+ "domain: boots real Ruby under #{Storehouse::BOOT_ROOT}; anyone who can write to stdin can run it."
30
+ ].freeze
31
+
32
+ # The method that answers each JSON-RPC method that has a response.
33
+ REPLIES = {
34
+ "initialize" => :initialize_result,
35
+ "tools/list" => :tools_list,
36
+ "tools/call" => :tools_call,
37
+ "ping" => :ping_result
38
+ }.freeze
39
+
40
+ module_function
41
+
42
+ # Read once, so a bad setting refuses to start the server before anything is
43
+ # written to stdout.
44
+ def scope
45
+ @scope ||= McpScope.start!(server: CLI::Mcp::SERVER)
46
+ end
47
+
48
+ # The standing notes, then what this server's scope says about `domain:`.
49
+ def warning_notes
50
+ NOTES[0, 2] + (scope.restricted? ? scope.notes : [NOTES[2]])
51
+ end
52
+
53
+ def serve
54
+ McpStdioGuard.warn!(server: CLI::Mcp::SERVER, notes: warning_notes)
55
+ $stdout.sync = true
56
+
57
+ $stdin.each_line { |line| answer_line(line.strip) }
58
+ end
59
+
60
+ # Answers one request line; a line that is not JSON is a parse error, not a crash.
61
+ def answer_line(line)
62
+ return if line.empty?
63
+
64
+ handle_safely(JSON.parse(line))
65
+ rescue JSON::ParserError => e
66
+ send_error(nil, -32_700, "Parse error: #{e.message}")
67
+ end
68
+
69
+ # Handles a request, answering an internal error rather than ending the server.
70
+ def handle_safely(request)
71
+ handle(request)
72
+ rescue StandardError => e
73
+ send_error(request["id"], -32_603, "Internal error: #{e.class}: #{e.message}")
74
+ end
75
+
76
+ # `domain:` is confined to `Storehouse::BOOT_ROOT`, and further to a restricted
77
+ # server's `HECKS_SERVER_DOMAINS`, before anything boots. An unrestricted server boots the
78
+ # domain afresh on every call; a restricted server lives for one spawner's session and
79
+ # keeps each named domain booted until the files of its directory change.
80
+ def boot(domain)
81
+ path = Storehouse.confine!(scope.admit_domain!(domain), "domain")
82
+ runtime = scope.restricted? ? resident_boot(path) : Hecks.boot(path, install_driving: false)
83
+ Admission.admit!(runtime.registry, "Mcp")
84
+ runtime
85
+ end
86
+
87
+ # The runtime a restricted server keeps for `path`, booted again once its files change.
88
+ def resident_boot(path)
89
+ resident = (@resident ||= {})
90
+ fingerprint = Storehouse.fingerprint(path)
91
+ return resident[path][:runtime] if resident[path] && resident[path][:fingerprint] == fingerprint
92
+
93
+ resident[path] = { fingerprint: fingerprint, runtime: Hecks.boot(path, install_driving: false) }
94
+ resident[path][:runtime]
95
+ end
96
+
97
+ def answer_tool(name, args)
98
+ return scope.refusal(name) unless scope.permits_tool?(name)
99
+
100
+ ToolCalls.call(name, scope.with_default_domain(args))
101
+ end
102
+
103
+ # One line per allowed command: what it takes and the role it declares, read from the booted
104
+ # domain the server serves. Nil when the server serves no single domain or it will not boot,
105
+ # so
106
+ # `tools/list` still answers, with the commands as a bare list.
107
+ def command_guide
108
+ return unless scope.commands_mode? && scope.default_domain
109
+
110
+ McpGuideCache.remember(scope.default_domain, scope.allowed_commands) do
111
+ Storehouse.command_guide(boot(scope.default_domain), scope.allowed_commands)
112
+ end
113
+ rescue StandardError
114
+ nil
115
+ end
116
+
117
+ def tool_result(payload)
118
+ { content: [{ type: "text", text: JSON.pretty_generate(payload) }], isError: payload[:ok] == false }
119
+ end
120
+
121
+ def send_response(id, result)
122
+ puts JSON.generate({ jsonrpc: "2.0", id: id, result: result })
123
+ end
124
+
125
+ def send_error(id, code, message)
126
+ puts JSON.generate({ jsonrpc: "2.0", id: id, error: { code: code, message: message } })
127
+ end
128
+
129
+ # Answers one JSON-RPC request. A notification has no id and gets no response.
130
+ def handle(request)
131
+ id = request["id"]
132
+ method = request["method"]
133
+ reply = REPLIES[method]
134
+ return send_response(id, public_send(reply, request["params"] || {})) if reply
135
+
136
+ send_error(id, -32_601, "Method not found: #{method}") unless id.nil? || method == "notifications/initialized"
137
+ end
138
+
139
+ def initialize_result(_params)
140
+ { protocolVersion: PROTOCOL_VERSION, capabilities: { tools: {} },
141
+ serverInfo: { name: CLI::Mcp::SERVER, version: "1.1.0" } }
142
+ end
143
+
144
+ # The tools this server's scope permits, each presented with the command guide.
145
+ def tools_list(_params)
146
+ served = TOOLS.select { |tool| scope.permits_tool?(tool[:name]) }
147
+ guide = command_guide
148
+ { tools: served.map { |tool| scope.present(tool, guide) } }
149
+ end
150
+
151
+ def tools_call(params)
152
+ tool_result(answer_tool(params["name"], params["arguments"] || {}))
153
+ end
154
+
155
+ def ping_result(_params) = {}
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,78 @@
1
+ require "digest"
2
+ require "fileutils"
3
+ require "json"
4
+ require_relative "../../cache_dir"
5
+ require_relative "../../storehouse"
6
+ require_relative "../../bluebook/meta_validator/verdict_cache"
7
+
8
+ module Hecks
9
+ module Adapters
10
+ module Driving
11
+ # Remembers the command guide a commands server builds for `tools/list`, so a server that
12
+ # starts
13
+ # again does not boot its domain to describe its commands. A client waits only a moment for a
14
+ # server's tools, and a boot at that point costs more than the wait.
15
+ #
16
+ # An entry is keyed by the domain directory's files, a digest of the hecks code, and the
17
+ # allowed commands, so a change to any of them builds a new guide. The file is JSON, read only
18
+ # from a directory and a file that only this user can write (the verdict cache's own check),
19
+ # and only when it is a list of strings: a guide is text an agent reads as instructions.
20
+ module McpGuideCache
21
+ module_function
22
+
23
+ # The guide for a domain and its allowed commands, from the cache or else from the block,
24
+ # which is then remembered. A cache that cannot be written is not an error: the next start
25
+ # builds the guide again.
26
+ #
27
+ # @param domain [String] the domain directory the guide describes
28
+ # @param commands [Array<String>] the allowed command names
29
+ # @yieldreturn [Array<String>, nil] the guide, built when none is remembered
30
+ # @return [Array<String>, nil] the guide
31
+ def remember(domain, commands)
32
+ file = File.join(dir, "#{key(domain, commands)}.json")
33
+ cached = read(file)
34
+ return cached if cached
35
+
36
+ guide = yield
37
+ write(file, guide) if guide.is_a?(Array) && guide.any?
38
+ guide
39
+ end
40
+
41
+ # @return [String] the directory entries live in
42
+ def dir = Hecks::CacheDir.path("mcp_guides")
43
+
44
+ # @param domain [String] the domain directory
45
+ # @param commands [Array<String>] the allowed command names
46
+ # @return [String] a hex SHA-256 over the domain's files, the hecks code and the commands
47
+ def key(domain, commands)
48
+ parts = [Storehouse.fingerprint(domain), Bluebook::MetaValidator::VerdictCache.code_digest,
49
+ commands.sort.join(",")]
50
+ Digest::SHA256.hexdigest(parts.join("\0"))
51
+ end
52
+
53
+ # @param file [String] an entry's path
54
+ # @return [Array<String>, nil] the guide, or nil when absent, untrusted or malformed
55
+ def read(file)
56
+ return unless File.file?(file) && Bluebook::MetaValidator::VerdictCache.trusted?(file)
57
+
58
+ guide = JSON.parse(File.read(file))
59
+ guide if guide.is_a?(Array) && guide.all?(String)
60
+ rescue StandardError
61
+ nil
62
+ end
63
+
64
+ # @param file [String] an entry's path
65
+ # @param guide [Array<String>] the guide to remember
66
+ # @return [void]
67
+ def write(file, guide)
68
+ FileUtils.mkdir_p(dir, mode: 0o700)
69
+ temp = "#{file}.#{Process.pid}.tmp"
70
+ File.write(temp, JSON.generate(guide), perm: 0o600)
71
+ File.rename(temp, file)
72
+ rescue StandardError
73
+ FileUtils.rm_f(temp) if temp
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,97 @@
1
+ require_relative "../../../runtime/errors"
2
+
3
+ module Hecks
4
+ module Adapters
5
+ module Driving
6
+ class McpScope
7
+ # What a restricted server lets an argument say: a denied name is refused, a path must
8
+ # resolve
9
+ # inside the root with symlinks followed, and a git ref must be a plain name.
10
+ module ArgumentPolicy
11
+ module_function
12
+
13
+ # Checks every argument in `args`, nested or not, and refuses the first one the policy
14
+ # forbids.
15
+ #
16
+ # @param args [Hash, Array, nil] the arguments as the caller gave them
17
+ # @param label [String] how the server describes its mode, for the refusal
18
+ # @raise [Runtime::TypeMismatch] on the first argument refused
19
+ def admit!(args, label)
20
+ each_argument(args) do |name, value|
21
+ if DENIED_ARGUMENTS.include?(name)
22
+ refuse!(name, label, "never passes it on: it names a host, a binary, an output or a change of state")
23
+ end
24
+ admit_path!(name, value, label) if PATH_ARGUMENTS.include?(name)
25
+ admit_ref!(name, value, label) if REF_ARGUMENTS.include?(name)
26
+ end
27
+ end
28
+
29
+ # Yields every argument name with its value, descending into nested objects and lists, so
30
+ # `{"file" => {"value" => "x"}}` is checked the way `{"file" => "x"}` is.
31
+ def each_argument(args, &visit)
32
+ case args
33
+ when Hash
34
+ args.each do |name, value|
35
+ yield(name.to_s, value)
36
+ each_argument(value, &visit)
37
+ end
38
+ when Array
39
+ args.each { |item| each_argument(item, &visit) }
40
+ end
41
+ end
42
+
43
+ # A value written as an object of one field (`{"value" => "x"}`) is that field's value.
44
+ def scalar(value)
45
+ return value unless value.is_a?(Hash)
46
+
47
+ value.fetch("value") { value.fetch(:value, value) }
48
+ end
49
+
50
+ def refuse!(name, label, why)
51
+ raise Runtime::TypeMismatch, "argument: #{name.inspect} is refused: this server runs in #{label} and #{why}"
52
+ end
53
+
54
+ def admit_path!(name, value, label)
55
+ Array(scalar(value)).flat_map { |item| item.to_s.split(",") }.reject(&:empty?).each do |path|
56
+ next if !path.include?(":") && inside_root?(path)
57
+
58
+ refuse!(name, label, "passes a path only when it resolves inside #{Storehouse::BOOT_ROOT} " \
59
+ "and holds no colon: #{path.inspect}")
60
+ end
61
+ end
62
+
63
+ def admit_ref!(name, value, label)
64
+ ref = scalar(value).to_s
65
+ return if ref.match?(PLAIN_REF) && !ref.include?("..")
66
+
67
+ refuse!(name, label, "passes only a plain git ref: #{ref.inspect}")
68
+ end
69
+
70
+ def inside_root?(path)
71
+ root = resolve_real(Storehouse::BOOT_ROOT)
72
+ resolved = resolve_real(File.expand_path(path, Storehouse::BOOT_ROOT))
73
+ resolved == root || resolved.start_with?("#{root}#{File::SEPARATOR}")
74
+ end
75
+
76
+ # The real path of `path`, symlinks followed: of the whole path when it exists, else of
77
+ # its
78
+ # deepest existing parent with the missing names put back, so a link above a file that
79
+ # does
80
+ # not exist yet still shows where the file would land.
81
+ def resolve_real(path)
82
+ missing = []
83
+ current = File.expand_path(path)
84
+ until File.exist?(current)
85
+ parent = File.dirname(current)
86
+ break if parent == current
87
+
88
+ missing.unshift(File.basename(current))
89
+ current = parent
90
+ end
91
+ File.join(File.realpath(current), *missing)
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,69 @@
1
+ module Hecks
2
+ module Adapters
3
+ module Driving
4
+ class McpScope
5
+ # What is wrong with the `HECKS_SERVER_*` variables a server was spawned with, as the
6
+ # sentences that refuse to start it.
7
+ module EnvProblems
8
+ module_function
9
+
10
+ # Variables under this prefix are retired spellings of the `HECKS_SERVER_*` ones.
11
+ RETIRED_PREFIX = "HECKS_DOOR_".freeze
12
+
13
+ # Every problem in `env`: a variable the server does not take, then those of the mode.
14
+ def setting_problems(env)
15
+ known = [TOOLS_VARIABLE, DOMAINS_VARIABLE, COMMANDS_VARIABLE]
16
+ problems = retired_problems(env) + (env.keys.select { |name| name.start_with?(ENV_PREFIX) } - known).map do |name|
17
+ "environment #{name} is not accepted; the server takes only #{known.join(", ")}"
18
+ end
19
+ problems + mode_problems(env[TOOLS_VARIABLE], env[DOMAINS_VARIABLE], env[COMMANDS_VARIABLE])
20
+ end
21
+
22
+ # A variable under the retired prefix is refused, never ignored: ignoring
23
+ # `HECKS_DOOR_TOOLS`
24
+ # would start an unrestricted server where the spawner asked for a narrow one.
25
+ def retired_problems(env)
26
+ env.keys.select { |name| name.start_with?(RETIRED_PREFIX) }.map do |name|
27
+ "environment #{name} is retired; the server reads #{name.sub(RETIRED_PREFIX, ENV_PREFIX)}"
28
+ end
29
+ end
30
+
31
+ def mode_problems(tools, domains, commands)
32
+ return stray_problems(domains, commands) if tools.nil?
33
+ unless MODES.include?(tools)
34
+ return ["#{TOOLS_VARIABLE}=#{tools.inspect} is not accepted; the values are #{MODES.map(&:inspect).join(" and ")}"]
35
+ end
36
+
37
+ problems = []
38
+ if split_paths(domains.to_s).empty?
39
+ problems << "#{TOOLS_VARIABLE}=#{tools} needs #{DOMAINS_VARIABLE}, the domains this server may read"
40
+ end
41
+ problems + command_problems(tools, commands)
42
+ end
43
+
44
+ def stray_problems(domains, commands)
45
+ stray = { DOMAINS_VARIABLE => domains, COMMANDS_VARIABLE => commands }.compact.keys
46
+ return [] if stray.empty?
47
+
48
+ ["#{stray.join(" and ")} set without #{TOOLS_VARIABLE}=#{READERS} or #{TOOLS_VARIABLE}=#{COMMANDS}; " \
49
+ "they only narrow a restricted server"]
50
+ end
51
+
52
+ def command_problems(tools, commands)
53
+ if tools == COMMANDS && split_names(commands).empty?
54
+ ["#{TOOLS_VARIABLE}=#{COMMANDS} needs #{COMMANDS_VARIABLE}, the commands this server may dispatch"]
55
+ elsif tools == READERS && !commands.nil?
56
+ ["#{COMMANDS_VARIABLE} is set in reader mode; it only narrows a commands server"]
57
+ else
58
+ []
59
+ end
60
+ end
61
+
62
+ def split_paths(value) = value.to_s.split(File::PATH_SEPARATOR).reject(&:empty?)
63
+
64
+ def split_names(value) = value.to_s.split(",").map(&:strip).reject(&:empty?).uniq
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,83 @@
1
+ module Hecks
2
+ module Adapters
3
+ module Driving
4
+ class McpScope
5
+ # A tool definition as `tools/list` shows it to one server, and the notes the server starts
6
+ # with.
7
+ module Presenter
8
+ module_function
9
+
10
+ # The tool as `tools/list` shows it. A server that serves one domain makes `domain:`
11
+ # optional.
12
+ # On a commands server, `dispatch` also names the commands it serves as an enum, so a
13
+ # caller
14
+ # sees them as typed choices instead of finding out by being refused, and says what each
15
+ # takes and which role it declares.
16
+ #
17
+ # @param scope [McpScope] the server's scope
18
+ # @param tool [Hash] a tool definition from `Mcp::TOOLS`
19
+ # @param guide [Array<String>, nil] one line per allowed command
20
+ # (`Storehouse.command_guide`)
21
+ # @return [Hash] the definition, narrowed for this server
22
+ def present(scope, tool, guide)
23
+ tool = domain_optional(tool, scope.default_domain) if scope.default_domain
24
+ return tool unless scope.commands_mode? && tool[:name] == "dispatch"
25
+
26
+ narrow_dispatch(tool, guide, scope.allowed_commands)
27
+ end
28
+
29
+ # `dispatch`, with the commands the server serves as typed choices.
30
+ def narrow_dispatch(tool, guide, commands)
31
+ properties = tool[:inputSchema][:properties]
32
+ steps = properties[:steps]
33
+ narrowed = properties.merge(command: with_enum(properties[:command], commands),
34
+ steps: steps.merge(items: step_items(steps[:items], commands)))
35
+ tool.merge(description: dispatch_description(tool[:description], guide, commands),
36
+ inputSchema: tool[:inputSchema].merge(properties: narrowed))
37
+ end
38
+
39
+ def domain_optional(tool, default)
40
+ schema = tool[:inputSchema]
41
+ return tool unless schema[:properties].key?(:domain)
42
+
43
+ note = "Optional: this server serves one domain, #{default}, and uses it when you leave this out."
44
+ properties = schema[:properties].merge(domain: schema[:properties][:domain].merge(description: note))
45
+ tool.merge(inputSchema: schema.merge(properties: properties, required: schema[:required] - ["domain"]))
46
+ end
47
+
48
+ def dispatch_description(base, guide, commands)
49
+ intro = "#{base} This server dispatches only the commands below, each with the role it declares: pass " \
50
+ "that role as `role`. A command that takes `run` takes a key you choose, and the call answers " \
51
+ "the record as it stands once its reactions have run; an argument marked * is required."
52
+ entries = Array(guide).empty? ? commands : guide
53
+ ([intro] + entries.map { |entry| "- #{entry}" }).join("\n")
54
+ end
55
+
56
+ def with_enum(property, commands)
57
+ property.merge(enum: commands)
58
+ end
59
+
60
+ def step_items(items, commands)
61
+ items.merge(properties: items[:properties].merge(command: with_enum(items[:properties][:command], commands)))
62
+ end
63
+
64
+ # What a reader server says about itself when it starts.
65
+ def reader_notes(domains)
66
+ ["Reader mode (#{TOOLS_VARIABLE}=#{READERS}): serves #{READER_TOOLS.join(", ")}; " \
67
+ "refuses dispatch (with dry_run and steps), behaviors and every other tool.",
68
+ "domain: boots only #{DOMAINS_VARIABLE}: #{domains.join(", ")}.",
69
+ "Reader mode limits reach and identifies no one; it is not authentication."]
70
+ end
71
+
72
+ # What a commands server says about itself when it starts.
73
+ def commands_notes(domains, commands)
74
+ ["Commands mode (#{TOOLS_VARIABLE}=#{COMMANDS}): serves #{READER_TOOLS.join(", ")} and dispatch of only " \
75
+ "#{COMMANDS_VARIABLE}: #{commands.join(", ")}; refuses behaviors and every other tool.",
76
+ "domain: boots only #{DOMAINS_VARIABLE}: #{domains.join(", ")}.",
77
+ "Commands mode limits reach and identifies no one; role: stays self-asserted and it is not authentication."]
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end