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,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+ require "open3"
5
+ require "uri"
6
+ require_relative "declaration"
7
+ require_relative "postgres_reading"
8
+
9
+ module Hecks
10
+ module Adapters
11
+ module Rehearsal
12
+ # The data copy as a throwaway database on the server the world's `connection_env` names.
13
+ #
14
+ # `empty` and `dump` create a database of this run's own (`hecks_rehearsal_<token>`) and
15
+ # restore the dump into it; `readonly_copy` reads a database somebody else made, as it is,
16
+ # in a read-only session, and never creates or drops one. A database is isolated, not a
17
+ # schema, because a dump carries its own schema names. The name derives from the run key, so
18
+ # every ask of one rehearsal reaches the same database and a dropped one is dropped once.
19
+ class PostgresStore
20
+ include PostgresReading
21
+
22
+ PREFIX = "hecks_rehearsal_"
23
+ NAME = /\A#{PREFIX}[0-9a-f]{16}\z/
24
+
25
+ # @param declaration [Declaration] a `postgres` declaration
26
+ # @param run [String] the rehearsal's key
27
+ # @param protected_databases [Array<String>] databases the domain's own world names
28
+ def initialize(declaration, run, protected_databases: [])
29
+ @declaration = declaration
30
+ @run = run
31
+ @protected = protected_databases
32
+ end
33
+
34
+ # @return [String] the database this rehearsal reads
35
+ def database_name
36
+ return URI.parse(server_url).path.delete_prefix("/") if @declaration.mode == "readonly_copy"
37
+
38
+ "#{PREFIX}#{Digest::SHA256.hexdigest(@run)[0, 16]}"
39
+ end
40
+
41
+ # Creates the database and restores the dump, or checks the copy is reachable. A failure
42
+ # leaves nothing behind.
43
+ #
44
+ # @return [void]
45
+ # @raise [Refused] if the server refuses, or the restore does
46
+ def prepare!
47
+ return reach_copy! if @declaration.mode == "readonly_copy"
48
+
49
+ discard!
50
+ admin { |db| db.exec("CREATE DATABASE #{db.quote_ident(database_name)}") }
51
+ restore! if @declaration.mode == "dump"
52
+ rescue Refused, PG::Error => e
53
+ discard!
54
+ raise e.is_a?(Refused) ? e : Refused.new(e.message.strip)
55
+ end
56
+
57
+ # Drops the throwaway database. A copy somebody else made is never dropped, and one the
58
+ # world asked to keep is refused here, so the run records why it stands.
59
+ #
60
+ # @return [void]
61
+ # @raise [Refused] if the world's `drop_after` is false and a database stands
62
+ def drop!
63
+ return if @declaration.mode == "readonly_copy" || !NAME.match?(database_name)
64
+ raise Refused, "kept #{database_name}: the world's drop_after is false" unless @declaration.drop_after
65
+
66
+ discard!
67
+ end
68
+
69
+ # @return [String] a URL for the database this rehearsal reads
70
+ def url
71
+ uri = URI.parse(server_url)
72
+ uri.path = "/#{database_name}"
73
+ uri.to_s
74
+ end
75
+
76
+ private
77
+
78
+ def discard!
79
+ return unless NAME.match?(database_name)
80
+
81
+ admin { |db| db.exec("DROP DATABASE IF EXISTS #{db.quote_ident(database_name)} WITH (FORCE)") }
82
+ rescue Refused, PG::Error
83
+ nil
84
+ end
85
+
86
+ def server_url
87
+ value = @declaration.connection
88
+ return value if value.match?(%r{\Apostgres(ql)?://})
89
+
90
+ raise Refused, "#{@declaration.connection_env} must hold a postgres:// URL"
91
+ end
92
+
93
+ def admin
94
+ require "pg"
95
+ db = PG.connect(server_url)
96
+ yield db
97
+ ensure
98
+ db&.close
99
+ end
100
+
101
+ def reach_copy!
102
+ raise Refused, "readonly_copy names a database the domain's own world persists to" if @protected.include?(url)
103
+
104
+ admin { |db| db.exec("SELECT 1") }
105
+ end
106
+
107
+ # argv arrays and PG* variables: no shell, and no password on a command line.
108
+ def restore!
109
+ file = @declaration.dump
110
+ raise Refused, "no such dump #{file.inspect}" unless File.file?(file)
111
+
112
+ _out, err, status = Open3.capture3(restore_env, *restore_argv(file))
113
+ raise Refused, "restoring the dump failed: #{err.strip.lines.first}" unless status.success?
114
+ end
115
+
116
+ def restore_argv(file)
117
+ return ["psql", "--no-psqlrc", "-v", "ON_ERROR_STOP=1", "-d", database_name, "-f", file] if file.end_with?(".sql")
118
+
119
+ ["pg_restore", "--no-owner", "--no-privileges", "-d", database_name, file]
120
+ end
121
+
122
+ def restore_env
123
+ uri = URI.parse(server_url)
124
+ { "PGHOST" => uri.host, "PGPORT" => uri.port&.to_s, "PGUSER" => uri.user, "PGPASSWORD" => uri.password }.compact
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hecks
4
+ module Adapters
5
+ module Rehearsal
6
+ # The mint audit's refusals as report rows: `Aggregate#id`, the rule that refused, and the
7
+ # audit's own wording, capped, with the total of them all.
8
+ module Rows
9
+ # How many rows a report holds; the total counts the rest.
10
+ CAP = 50
11
+
12
+ ROW = /\A([^#:]+)#([^:]+): (.*)\z/m
13
+
14
+ module_function
15
+
16
+ # @param violations [Array<String>] the audit's refusals, `Aggregate#id: wording`
17
+ # @return [Array(Array<Hash>, Integer)] the first `CAP` rows, and how many there were
18
+ def from(violations)
19
+ [violations.first(CAP).map { |text| row(text) }, violations.size]
20
+ end
21
+
22
+ # @param text [String] one refusal
23
+ # @return [Hash] `aggregate`, `id`, `rule`, `wording`
24
+ def row(text)
25
+ match = ROW.match(text)
26
+ aggregate, id, wording = match ? match.captures : ["(edge)", "-", text]
27
+ { aggregate: aggregate, id: id, rule: rule(wording), wording: wording }
28
+ end
29
+
30
+ # @param wording [String] a refusal's words
31
+ # @return [String] `lifecycle`, `reference` or `value`
32
+ def rule(wording)
33
+ return "lifecycle" if wording.include?("lifecycle never reaches")
34
+ return "reference" if wording.include?("reference transform") || wording.include?("id set changed")
35
+
36
+ "value"
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "open3"
5
+ require "hecks/projector/exporter"
6
+
7
+ module Hecks
8
+ module Adapters
9
+ module Rehearsal
10
+ # The Rust host's mint audit (`reference_validate`), run over the translated rows through the
11
+ # `validate_harness` binary, as the second engine of a rehearsal.
12
+ #
13
+ # The binary is found by `HECKS_VALIDATE_HARNESS`, else under `rust/host/target` of the
14
+ # checkout this gem runs from (`cargo build --bin validate_harness`); a rehearsal never
15
+ # builds it. Without one the engine is reported as not run.
16
+ class RustAudit
17
+ # @!attribute [r] ran
18
+ # @return [Boolean] whether the harness answered
19
+ # @!attribute [r] violations
20
+ # @return [Array<String>] its refusals, `Aggregate#id: wording`
21
+ # @!attribute [r] note
22
+ # @return [String] why it did not run, or what it ran over
23
+ # @!attribute [r] rows
24
+ # @return [Integer] how many rows it was given (zero when it did not run)
25
+ Result = Struct.new(:ran, :violations, :note, :rows, keyword_init: true)
26
+
27
+ # @return [String, nil] the harness binary, when one stands
28
+ def self.binary
29
+ root = File.expand_path("../../../../..", __dir__)
30
+ built = %w[debug release].map { |profile| "#{root}/rust/host/target/#{profile}/validate_harness" }
31
+ [ENV.fetch("HECKS_VALIDATE_HARNESS", nil), *built].compact.find { |path| File.executable?(path) }
32
+ end
33
+
34
+ # @param subject [Subject] the loaded domain, whose IR the audit is held to
35
+ # @param states [Hash{String => Hash{String => Hash}}] aggregate name, then id, then state
36
+ # @return [Result] what the harness refused, or why it did not run
37
+ def self.call(subject, states)
38
+ path = binary
39
+ return new(nil, subject).stood_down("no validate_harness built: cargo build --bin validate_harness") unless path
40
+
41
+ new(path, subject).call(states)
42
+ end
43
+
44
+ # @param path [String, nil] the harness binary
45
+ # @param subject [Subject] the loaded domain
46
+ def initialize(path, subject)
47
+ @path = path
48
+ @subject = subject
49
+ end
50
+
51
+ # @param states [Hash] aggregate name, id, state
52
+ # @return [Result] the harness's answer
53
+ def call(states)
54
+ cases = states.flat_map { |name, rows| rows.map { |id, state| case_for(name, id, state) } }
55
+ out, err, status = Open3.capture3(@path, stdin_data: JSON.generate("ir" => ir, "cases" => cases))
56
+ return stood_down("validate_harness failed: #{err.lines.first&.strip}") unless status.success?
57
+
58
+ answered(out, cases.size)
59
+ end
60
+
61
+ # @param note [String] why the engine did not run
62
+ # @return [Result] an answer that refused nothing
63
+ def stood_down(note) = Result.new(ran: false, violations: [], note: note, rows: 0)
64
+
65
+ private
66
+
67
+ def answered(out, rows)
68
+ Result.new(ran: true, violations: refusals(out), note: "ran over #{rows} rows", rows: rows)
69
+ end
70
+
71
+ def ir = Projector::Exporter.call(@subject.held.registry).fetch(@subject.bluebook.name)
72
+
73
+ def case_for(name, id, state)
74
+ { "aggregate" => name, "id" => id, "state" => JSON.parse(JSON.generate(state)) }
75
+ end
76
+
77
+ def refusals(out)
78
+ JSON.parse(out).fetch("results").flat_map { |result| result["violations"] || [result["error"]].compact }
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "declaration"
4
+ require_relative "environment"
5
+ require_relative "../journal_store/edge_audit"
6
+ require_relative "../journal_store/held_domain"
7
+
8
+ module Hecks
9
+ module Adapters
10
+ module Rehearsal
11
+ # The domain a rehearsal is run on: loaded as the era tools load it, with nothing bound or
12
+ # booted, so no persistence binding of the domain's own is reached.
13
+ class Subject
14
+ # The domain did not load: the answer a rehearsal gives is `cannot_boot`, with the reason.
15
+ class CannotBoot < StandardError; end
16
+
17
+ attr_reader :held, :declaration, :environment
18
+
19
+ # @param path [String] the domain directory
20
+ # @return [Subject] the loaded subject, with its world's declaration checked
21
+ # @raise [CannotBoot] if the directory is absent, declares no bluebook or does not load
22
+ # @raise [Refused] if the world's `rehearsed_by` is absent or wrong
23
+ def self.open(path)
24
+ environment = choose_environment(path)
25
+ held = load(path, environment)
26
+ new(held, Declaration.from_world(held.registry.world(held.bluebook.name), held.directory), environment)
27
+ end
28
+
29
+ # Whatever stops a domain from loading is a reason it cannot boot, a script error included.
30
+ # A missing bind names the environment that was loaded and the file it came from.
31
+ #
32
+ # @api private
33
+ def self.load(path, environment)
34
+ JournalStore::HeldDomain.open(path, environment: environment.overlay)
35
+ rescue Runtime::WiringError => e
36
+ raise CannotBoot, "#{e.message} The rehearsal loaded #{environment}; overlays this domain " \
37
+ "holds: #{environment.available_words}. " \
38
+ "Name one with `environment \"...\"` in rehearsed_by, or HECKS_PROJECT_ENVIRONMENT."
39
+ rescue StandardError, ScriptError => e
40
+ raise CannotBoot, e.message
41
+ end
42
+ private_class_method :load
43
+
44
+ # The overlay the world names, read before the domain loads so the load can use it.
45
+ #
46
+ # @api private
47
+ def self.choose_environment(path)
48
+ loading = Ports::Loading.bootstrap
49
+ directory = loading.bluebook_directory(path)
50
+ Environment.resolve(declared_environment(loading, directory), directory)
51
+ rescue StandardError, ScriptError
52
+ Environment.resolve(nil, path.to_s)
53
+ end
54
+ private_class_method :choose_environment
55
+
56
+ # @api private
57
+ def self.declared_environment(loading, directory)
58
+ registry = Runtime::Registry.new(root: File.dirname(directory))
59
+ Hecks.with_registry(registry) do
60
+ loading.load_library
61
+ loading.load_each(directory, %w[*.world])
62
+ end
63
+ registry.worlds.values.filter_map { |world| world.for_verb("rehearsed_by")[:environment] }.first
64
+ rescue StandardError, ScriptError
65
+ nil
66
+ end
67
+ private_class_method :declared_environment
68
+
69
+ # @param held [JournalStore::HeldDomain] the loaded domain
70
+ # @param declaration [Declaration] what its world says about the data copy
71
+ # @param environment [Environment] the overlay the domain was loaded under
72
+ def initialize(held, declaration, environment)
73
+ @held = held
74
+ @declaration = declaration
75
+ @environment = environment
76
+ end
77
+
78
+ # @return [Bluebook::Chapter] the domain's bluebook as it stands
79
+ def bluebook = held.bluebook
80
+
81
+ # @return [String] the label the shape a mint would hold has
82
+ def label = Runtime::StorageShape.mint_label(bluebook)
83
+
84
+ # @return [String] the domain's bluebook directory
85
+ def directory = held.directory
86
+
87
+ # The edge that leads one era's label to the shape's label.
88
+ #
89
+ # @param from [String] the held era's label
90
+ # @return [Bluebook::Translation, nil] the edge; nil when none is declared
91
+ def edge_from(from)
92
+ held.registry.translations.find { |t| t.domain == bluebook.name && t.from == from && t.to == label }
93
+ end
94
+
95
+ # @return [Array<String>] the persisted databases the domain's own world names, which a
96
+ # rehearsal must never be pointed at
97
+ def declared_databases
98
+ blocks = held.registry.world(bluebook.name)&.settings || {}
99
+ blocks.select { |key, _| key.to_s.start_with?("persisted_by") }.values.filter_map { |block| block[:database] }
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+ require_relative "rehearsal/declaration"
5
+ require_relative "rehearsal/rows"
6
+ require_relative "rehearsal/subject"
7
+ require_relative "rehearsal/memory_store"
8
+ require_relative "rehearsal/postgres_store"
9
+ require_relative "rehearsal/inquiry"
10
+
11
+ module Hecks
12
+ module Adapters
13
+ # Helpers the two rehearsal port adapters (`RehearsalStore`, `RehearsalSubject`) share.
14
+ module Rehearsal
15
+ module_function
16
+
17
+ # The Hecks this process runs, as `release+commit` when a checkout can name its commit.
18
+ #
19
+ # @return [String] `3.9.0`, or `3.9.0+bb8e41d`
20
+ def host_version
21
+ commit = checkout_commit
22
+ commit ? "#{Hecks::VERSION}+#{commit[0, 7]}" : Hecks::VERSION.to_s
23
+ end
24
+
25
+ # @param pin [String, nil] a release or a commit prefix the run must be on
26
+ # @return [void]
27
+ # @raise [Refused] if this process is not on the pin
28
+ def check_pin!(pin)
29
+ return if pin.nil? || pin.empty? || pin == Hecks::VERSION.to_s || checkout_commit.to_s.start_with?(pin)
30
+
31
+ raise Refused, "this is Hecks #{host_version}, not the pin #{pin}: run the rehearsal from the pinned install"
32
+ end
33
+
34
+ # @param subject [Subject] the loaded domain
35
+ # @param run [String] the rehearsal's key
36
+ # @return [MemoryStore, PostgresStore] the store its world declares
37
+ def store_for(subject, run)
38
+ declaration = subject.declaration
39
+ return MemoryStore.new(declaration) if declaration.kind == "memory"
40
+
41
+ PostgresStore.new(declaration, run, protected_databases: subject.declared_databases)
42
+ end
43
+
44
+ # @return [String, nil] the commit of the checkout this gem runs from
45
+ def checkout_commit
46
+ root = File.expand_path("../../../..", __dir__)
47
+ out, status = Open3.capture2("git", "-C", root, "rev-parse", "HEAD", err: File::NULL)
48
+ status.success? ? out.strip : nil
49
+ rescue SystemCallError
50
+ nil
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,7 @@
1
+ # Required by name, not relative to this file: a fuzz or model check boots a copy of this directory,
2
+ # and the implementation must load once, from `lib/`, not from the copy.
3
+ require "hecks/hecks/adapters/rehearsal_store"
4
+
5
+ Hecks.adapter "RehearsalStore" do
6
+ port "RehearsalStore"
7
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "rehearsal"
4
+
5
+ module Hecks
6
+ module Adapters
7
+ # The `RehearsalStore` port's adapter: the copy of a domain's data its own world names, which
8
+ # prepares itself when surveyed, runs the mint's audit over itself when examined, and is
9
+ # dropped when it has answered, whichever way it answers.
10
+ #
11
+ # The strategy (`memory` or `postgres`) is the one the subject's `.world` declares with
12
+ # `rehearsed_by`, never an argument. Nothing is written to the subject's own databases: no era
13
+ # is held or named, no table provisioned, no command dispatched. The copy is dropped here, in
14
+ # the same ask that last needs it, so no reaction that fails to arrive can leave one behind.
15
+ class RehearsalStore
16
+ # Accepts the arguments every driven adapter is built with and keeps none of them.
17
+ #
18
+ # @param aggregate [Object, nil] unused
19
+ # @param settings [Hash] unused
20
+ # @param root [String, nil] unused
21
+ def initialize(aggregate: nil, settings: {}, root: nil); end
22
+
23
+ # Prepares the copy, loads the domain against it, and reads where its shape stands.
24
+ #
25
+ # @param held [Hash] the record: `domain`, `run` and `pin`
26
+ # @return [Hash] the shape facts and what the copy holds, or `booted: false` with why the
27
+ # domain did not load
28
+ # @raise [Rehearsal::Refused] if the pin, the world's declaration or the copy is refused
29
+ def survey(**held)
30
+ Rehearsal.check_pin!(plain(held[:pin]))
31
+ subject = Rehearsal::Subject.open(plain(held[:domain]))
32
+ store = Rehearsal.store_for(subject, plain(held[:run]))
33
+ store.prepare!
34
+ guarded(store) { surveyed(subject, store) }
35
+ rescue Rehearsal::Subject::CannotBoot => e
36
+ { booted: { value: false }, boot_error: { value: e.message } }
37
+ end
38
+
39
+ # Runs the mint's audit over the copy, then drops it.
40
+ #
41
+ # @param held [Hash] the record: `domain` and `run`
42
+ # @return [Hash] the edge facts, every refused row, what was not checked, and whether the
43
+ # copy was dropped
44
+ def examine(**held)
45
+ subject = Rehearsal::Subject.open(plain(held[:domain]))
46
+ store = Rehearsal.store_for(subject, plain(held[:run]))
47
+ facts = guarded(store) { Rehearsal::Inquiry.new(subject, store).examine }
48
+ facts.merge(release(store))
49
+ rescue Rehearsal::Subject::CannotBoot => e
50
+ raise Rehearsal::Refused, e.message
51
+ end
52
+
53
+ private
54
+
55
+ # A copy that holds nothing is answered at once, so it is dropped here: no examination
56
+ # follows to drop it. Any other copy stands until it is examined.
57
+ def surveyed(subject, store)
58
+ facts = Rehearsal::Inquiry.new(subject, store).survey
59
+ standing = { released: { value: false }, kept: { value: "" } }
60
+ facts.merge(facts[:copy_empty][:value] ? release(store) : standing)
61
+ end
62
+
63
+ # Runs the block; when it raises, the copy is dropped before the error goes on.
64
+ def guarded(store)
65
+ yield
66
+ rescue StandardError
67
+ release(store)
68
+ raise
69
+ end
70
+
71
+ def release(store)
72
+ store.drop!
73
+ { released: { value: true }, kept: { value: "" } }
74
+ rescue Rehearsal::Refused, StandardError => e
75
+ { released: { value: false }, kept: { value: e.message } }
76
+ end
77
+
78
+ def plain(argument) = argument.is_a?(Hash) ? argument[:value] : argument
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "net/http"
5
+ require_relative "../../../projections/site/role_probe"
6
+
7
+ module Hecks
8
+ module Adapters
9
+ class SiteToolchain
10
+ # Dispatches each role-declaring command to a host with no role and no actor, and reports
11
+ # whether the host refused the caller.
12
+ #
13
+ # A host that does not enforce roles would run the command, so this talks only to a host on
14
+ # this machine: the one a spec or a CI job started for the purpose.
15
+ class RoleProber
16
+ # The addresses that name this machine.
17
+ LOOPBACK = %w[localhost 127.0.0.1 ::1 [::1]].freeze
18
+
19
+ # Seconds to wait to connect to the host, and again for its answer.
20
+ TIMEOUT = 10
21
+
22
+ # The failures of the network that fail one check rather than the whole run.
23
+ NETWORK = [SystemCallError, Timeout::Error, SocketError, JSON::ParserError].freeze
24
+
25
+ # @param checks [Array<Projections::Site::RoleProbe::Check>] the commands to dispatch
26
+ # @param base [String] the host's scheme, host and port
27
+ # @raise [ConsoleCapture::Failure] when the host is not on this machine
28
+ def initialize(checks, base:)
29
+ @checks = checks
30
+ @uri = URI.parse(base)
31
+ return if LOOPBACK.include?(@uri.host)
32
+
33
+ raise ConsoleCapture::Failure, "roles are checked against a host on this machine, not #{@uri.host}: " \
34
+ "a host that does not enforce roles would run each command"
35
+ end
36
+
37
+ # @return [Array<(String, Integer)>] the report, one line per command ending in the count,
38
+ # and how many failed; a command whose synthesized arguments the host refused is counted
39
+ # as unchecked, not failed
40
+ def call
41
+ lines = @checks.map { |check| judged(check) }
42
+ failed = lines.count { |_, state| state == :failed }
43
+ unchecked = lines.count { |_, state| state == :unchecked }
44
+ summary = "#{lines.size} commands, #{failed} failed, #{unchecked} unchecked"
45
+ [lines.map(&:first).append(summary).join("\n"), failed]
46
+ end
47
+
48
+ private
49
+
50
+ def judged(check)
51
+ answer = answer_to(check)
52
+ return [line(check, "unchecked: the host refused the arguments before the role"), :unchecked] if unchecked?(answer)
53
+
54
+ why = Projections::Site::RoleProbe.verdict(check, answer)
55
+ [line(check, why ? "FAILED: #{why}" : "ok"), why ? :failed : :ok]
56
+ rescue *NETWORK => e
57
+ [line(check, "FAILED: #{e.class}: #{e.message}"), :failed]
58
+ end
59
+
60
+ def unchecked?(answer) = Projections::Site::RoleProbe.unchecked?(answer)
61
+
62
+ def line(check, outcome) = " #{check.verb} (#{check.role}) is refused with no role... #{outcome}"
63
+
64
+ def answer_to(check)
65
+ request = Net::HTTP::Post.new(URI.join(@uri.to_s, "/dispatch"), "Content-Type" => "application/json")
66
+ request.body = JSON.generate(verb: check.verb, with: check.arguments)
67
+ JSON.parse(connection.request(request).body)
68
+ end
69
+
70
+ def connection
71
+ Net::HTTP.new(@uri.host, @uri.port).tap do |http|
72
+ http.open_timeout = http.read_timeout = TIMEOUT
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -9,6 +9,7 @@ require "hecks/tools"
9
9
  require "hecks/tools/site_routes"
10
10
  require "hecks/projections/site/live_edge"
11
11
  require_relative "site_toolchain/prober"
12
+ require_relative "site_toolchain/role_prober"
12
13
 
13
14
  module Hecks
14
15
  module Adapters
@@ -23,7 +24,8 @@ module Hecks
23
24
  SCRIPTS = { project: "project_site" }.freeze
24
25
 
25
26
  # The tool's flag for each `SiteProjection` field that takes a value.
26
- FLAGS = { "out" => :out, "template" => :template, "cms" => :cms, "root" => :root, "extension" => :extension }.freeze
27
+ FLAGS = { "out" => :out, "template" => :template, "cms" => :cms, "root" => :root, "editor" => :editor,
28
+ "extension" => :extension }.freeze
27
29
 
28
30
  # Accepts the arguments every driven adapter is built with and keeps none of them.
29
31
  #
@@ -35,7 +37,7 @@ module Hecks
35
37
  # Writes a project's `routes.ts` from its declared route table, or with `check` only compares.
36
38
  #
37
39
  # @param held [Hash] the `SiteProjection` record: `domain`, and `out`, `template`, `cms`,
38
- # `root`, `extension` and `check` when set
40
+ # `root`, `editor`, `extension` and `check` when set
39
41
  # @return [Hash{Symbol => Hash}] `output:` one line per file written or found current
40
42
  # @raise [ConsoleCapture::Failure] when the route table is refused (an unknown cache class,
41
43
  # a duplicate path), a path or the extension is refused, or under `check` a file is out
@@ -84,6 +86,23 @@ module Hecks
84
86
  { output: { value: report } }
85
87
  end
86
88
 
89
+ # Dispatches every command a project declares a role for to a host with no role and no actor,
90
+ # and checks the host refuses each as `Unauthorized`. The host must be on this machine and
91
+ # enforcing roles (`HECKS_ROLE_ENFORCEMENT=enforce`); one that is not would run the commands.
92
+ #
93
+ # @param held [Hash] the `SiteProjection` record: `domain`, the project that declares the
94
+ # commands, and `url`, the host's base address
95
+ # @return [Hash{Symbol => Hash}] `output:` one line per command, ending in the count
96
+ # @raise [ConsoleCapture::Failure] when the host is not on this machine, or a command is not
97
+ # refused for its role; the message is the report
98
+ def roles(**held)
99
+ checks = Projections::Site::RoleProbe.checks(registry_of(located(:domain, held[:domain])))
100
+ report, failed = RoleProber.new(checks, base: base_url(plain(held[:url]))).call
101
+ raise ConsoleCapture::Failure, report if failed.positive?
102
+
103
+ { output: { value: report } }
104
+ end
105
+
87
106
  private
88
107
 
89
108
  # The scheme, host and port of an http(s) address.
@@ -121,12 +140,18 @@ module Hecks
121
140
  # The project's checked route table.
122
141
  def table_of(root) = read_table(root).last
123
142
 
124
- # The registry, chapter and checked table of the project at `root`.
125
- def read_table(root)
143
+ # The registry the project at `root` boots into.
144
+ def registry_of(root)
126
145
  registry = nil
127
146
  outcome = ConsoleCapture.capture { registry = Tools::SiteRoutes.registry_for(root) }
128
147
  raise ConsoleCapture::Failure, outcome.output.strip unless outcome.ok?
129
148
 
149
+ registry
150
+ end
151
+
152
+ # The registry, chapter and checked table of the project at `root`.
153
+ def read_table(root)
154
+ registry = registry_of(root)
130
155
  chapter = Projections::Site::Table.chapter(registry)
131
156
  [registry, chapter, Projections::Site::Table.read(chapter, registry: registry)]
132
157
  rescue Projections::Site::Table::Invalid => e