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,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module Hecks
6
+ module Adapters
7
+ # Everything a rehearsal does on a client's domain: where its data copy comes from, how the
8
+ # mint's audit is run over it, and what is reported. The two port adapters (`RehearsalStore`
9
+ # and `RehearsalSubject`) are thin; the work lives here.
10
+ module Rehearsal
11
+ # A rehearsal that was not admitted or could not be carried on: the port answers it as a
12
+ # refusal, which the run keeps as `refusal`.
13
+ class Refused < StandardError; end
14
+
15
+ # What a subject's own `.world` says about the copy of its data, read from its
16
+ # `rehearsed_by` block and checked before anything is touched.
17
+ #
18
+ # A connection is only ever named by an environment variable (`connection_env`); a literal
19
+ # connection string, user or password in the world is refused, so the file holds no secret.
20
+ class Declaration
21
+ KINDS = %w[memory postgres].freeze
22
+ MODES = %w[empty dump readonly_copy].freeze
23
+ LITERALS = %i[database url connection password user].freeze
24
+ LOCAL_HOSTS = [nil, "", "localhost", "127.0.0.1", "::1", "[::1]"].freeze
25
+
26
+ NAME = /\A[A-Za-z0-9_-]+\z/
27
+
28
+ attr_reader :kind, :mode, :connection_env, :dump, :rows, :drop_after, :allow_remote, :schema, :environment
29
+
30
+ # @param world [#for_verb, nil] the subject's world, as the registry holds it
31
+ # @param directory [String] the domain's bluebook directory, which a relative file is under
32
+ # @return [Declaration] the checked declaration
33
+ # @raise [Refused] if the world declares no `rehearsed_by`, or declares it wrongly
34
+ def self.from_world(world, directory)
35
+ settings = world&.for_verb("rehearsed_by") || {}
36
+ raise Refused, "the domain's world declares no `rehearsed_by`: nothing says where its data copy is" if settings.empty?
37
+
38
+ new(settings, directory).tap(&:check!)
39
+ end
40
+
41
+ # @param settings [Hash] the block's settings, `adapter:` among them
42
+ # @param directory [String] where a relative `dump` or `rows` is found
43
+ def initialize(settings, directory)
44
+ @kind = settings[:adapter].to_s.downcase
45
+ @connection_env = settings[:connection_env]&.to_s
46
+ @dump = expand(settings[:dump], directory)
47
+ @rows = expand(settings[:rows], directory)
48
+ @mode = settings.fetch(:mode) { default_mode }.to_s
49
+ read_switches(settings)
50
+ end
51
+
52
+ # @return [void]
53
+ # @raise [Refused] naming what is wrong with the declaration
54
+ def check!
55
+ check_names!
56
+ raise Refused, literal_wording unless @literals.empty?
57
+
58
+ kind == "postgres" ? check_postgres! : check_memory!
59
+ end
60
+
61
+ # @return [String] the connection the environment holds
62
+ # @raise [Refused] if the variable the world names is unset or empty, or names a server
63
+ # that is not this machine and the world did not say `allow_remote true`
64
+ def connection
65
+ value = ENV.fetch(connection_env, "")
66
+ raise Refused, "the environment variable #{connection_env} is not set" if value.strip.empty?
67
+
68
+ refuse_remote!(value)
69
+ value
70
+ end
71
+
72
+ private
73
+
74
+ def refuse_remote!(value)
75
+ host = URI.parse(value).host
76
+ return if allow_remote || LOCAL_HOSTS.include?(host)
77
+
78
+ raise Refused, "#{connection_env} names the server #{host.inspect}, which is not this machine: " \
79
+ "a rehearsal reads a local copy; say `allow_remote true` in rehearsed_by to point it elsewhere"
80
+ rescue URI::InvalidURIError
81
+ nil
82
+ end
83
+
84
+ def read_switches(settings)
85
+ @drop_after = settings.fetch(:drop_after, true) != false
86
+ @allow_remote = settings.fetch(:allow_remote, false) == true
87
+ @literals = LITERALS & settings.keys.map(&:to_sym)
88
+ @schema = settings[:schema]&.to_s
89
+ @environment = settings[:environment]&.to_s
90
+ end
91
+
92
+ def default_mode = dump || rows ? "dump" : "empty"
93
+
94
+ def check_names!
95
+ raise Refused, "rehearsed_by names #{kind.inspect}; the stores are #{KINDS.join(" and ")}" unless KINDS.include?(kind)
96
+
97
+ raise Refused, "rehearsed_by mode #{mode.inspect} is not one of #{MODES.join(", ")}" unless MODES.include?(mode)
98
+
99
+ check_label!("schema", schema)
100
+ check_label!("environment", environment)
101
+ end
102
+
103
+ def check_label!(word, value)
104
+ return if value.nil? || NAME.match?(value)
105
+
106
+ raise Refused, "rehearsed_by #{word} #{value.inspect} is not a name (letters, digits, `_`, `-`)"
107
+ end
108
+
109
+ def expand(path, directory)
110
+ path && File.expand_path(path.to_s, directory)
111
+ end
112
+
113
+ def literal_wording
114
+ "rehearsed_by holds #{@literals.join(", ")}: name the environment variable in `connection_env`, " \
115
+ "never the value, so the world file holds no secret"
116
+ end
117
+
118
+ def check_postgres!
119
+ unless connection_env
120
+ raise Refused,
121
+ "a postgres rehearsal needs `connection_env`, the name of the variable holding the server"
122
+ end
123
+ raise Refused, "mode dump needs `dump`, the file to restore" if mode == "dump" && !dump
124
+ end
125
+
126
+ def check_memory!
127
+ raise Refused, "mode readonly_copy is a postgres mode: a memory store has no copy to read" if mode == "readonly_copy"
128
+ raise Refused, "mode dump needs `rows`, the JSON file the memory store holds" if mode == "dump" && !rows
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hecks
4
+ module Adapters
5
+ module Rehearsal
6
+ # Which environment overlay (`environments/<name>.hecksagon` and `.world`) a rehearsal loads
7
+ # the subject under, and where that was decided.
8
+ #
9
+ # A rehearsal is about the era the production binds will mint, so it loads `production` when
10
+ # the domain has `environments/production.hecksagon`, the same default the Rust host build
11
+ # takes (`ProjectRust::DomainLoader#load_environment`). Most domains bind their aggregates
12
+ # to a database only in that overlay, so the base files alone have no `persisted_by` bind.
13
+ # The choice is a reported fact (`environment_loaded`), never a silent one.
14
+ class Environment
15
+ # Where the name came from, in the order they are tried.
16
+ SOURCES = %w[world HECKS_PROJECT_ENVIRONMENT default none].freeze
17
+
18
+ NONE_WORDS = "no environment overlay (none named in rehearsed_by or HECKS_PROJECT_ENVIRONMENT, " \
19
+ "and no environments/production.hecksagon)"
20
+
21
+ # @!attribute [r] name
22
+ # @return [String] the overlay's name, or `""` when none was loaded
23
+ # @!attribute [r] source
24
+ # @return [String] one of `SOURCES`
25
+ # @!attribute [r] directory
26
+ # @return [String] the bluebook directory the overlay is looked for under
27
+ attr_reader :name, :source, :directory
28
+
29
+ # @param declared [String, nil] what the world's `rehearsed_by` names (`environment "..."`)
30
+ # @param directory [String] the domain's bluebook directory
31
+ # @return [Environment] the choice: the world's, else `HECKS_PROJECT_ENVIRONMENT`, else
32
+ # `production` when its overlay exists, else none
33
+ def self.resolve(declared, directory)
34
+ env = ENV.fetch("HECKS_PROJECT_ENVIRONMENT", "")
35
+ return new(declared, "world", directory) if declared && !declared.empty?
36
+ return new(env, "HECKS_PROJECT_ENVIRONMENT", directory) unless env.empty?
37
+ return new("production", "default", directory) if File.exist?(File.join(directory, "environments",
38
+ "production.hecksagon"))
39
+
40
+ new("", "none", directory)
41
+ end
42
+
43
+ # @param name [String] the overlay's name
44
+ # @param source [String] one of `SOURCES`
45
+ # @param directory [String] the bluebook directory
46
+ def initialize(name, source, directory)
47
+ @name = name
48
+ @source = source
49
+ @directory = directory
50
+ end
51
+
52
+ # @return [String, nil] the name to load, nil when no overlay is wanted
53
+ def overlay = name.empty? ? nil : name
54
+
55
+ # @return [Boolean] whether the hecksagon file of this overlay exists
56
+ def overlay_file? = File.exist?(file)
57
+
58
+ # @return [String] the hecksagon file the overlay is looked for in
59
+ def file = File.join(directory, "environments", "#{name}.hecksagon")
60
+
61
+ # @return [Hash] the report fact, as the `Environment` value object holds it
62
+ def facts = { name: name, source: source, file: name.empty? ? "" : file }
63
+
64
+ # @return [String] what the overlay choice was, in words
65
+ def to_s
66
+ return NONE_WORDS if name.empty?
67
+
68
+ "environment #{name.inspect} (#{source}), file #{file} #{overlay_file? ? "found" : "NOT FOUND"}"
69
+ end
70
+
71
+ # The overlays the directory holds, so a refusal can say what could be chosen.
72
+ #
73
+ # @return [Array<String>] the names of `environments/*.hecksagon`
74
+ def available
75
+ Dir.glob(File.join(directory, "environments", "*.hecksagon")).map { |path| File.basename(path, ".hecksagon") }.sort
76
+ end
77
+
78
+ # @return [String] the overlays the directory holds, comma separated, or `none`
79
+ def available_words = available.empty? ? "none" : available.join(", ")
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "subject"
4
+ require_relative "memory_store"
5
+ require_relative "postgres_store"
6
+ require_relative "rust_audit"
7
+ require_relative "audit_report"
8
+
9
+ module Hecks
10
+ module Adapters
11
+ module Rehearsal
12
+ # The two questions a rehearsal asks of a subject and its data copy: where the shape stands
13
+ # (`survey`) and what the mint's audit would refuse (`examine`). Each answer is the facts, as
14
+ # the `RehearsalRun` commands take them.
15
+ class Inquiry
16
+ # What every rehearsal says it did not check.
17
+ ENGINE_LIMIT = { what: "the Rust host's mint audit",
18
+ why: "no validate_harness was built, so the Ruby audit alone ran over the rows" }.freeze
19
+
20
+ ALWAYS = [
21
+ { what: "the mint itself",
22
+ why: "no era was written: the edge, its approval and the audit were evaluated, nothing minted" },
23
+ { what: "an edge's coverage of every change in the shape", why: "the mint checks it; the rehearsal does not yet" },
24
+ { what: "commands, policies and requests", why: "no traffic was run: only the stored data and the shape were read" },
25
+ { what: "data written after the copy was made", why: "a copy is a moment; the mint sees the data of its own" }
26
+ ].freeze
27
+
28
+ # @param subject [Subject] the loaded domain
29
+ # @param store [MemoryStore, PostgresStore] its prepared data copy
30
+ def initialize(subject, store)
31
+ @subject = subject
32
+ @store = store
33
+ end
34
+
35
+ # @return [Hash] `booted`, `host_version`, `store`, `mode`, `before`, `after`, `minting`,
36
+ # the schema and environment that were read, and what the copy holds
37
+ def survey
38
+ shape.merge(environment_loaded: @subject.environment.facts, copy_note: plain(copy_note)).merge(copy_facts)
39
+ end
40
+
41
+ # @return [Hash] the edge facts, every refused row and its total, what was read, the parity
42
+ # of the two engines, and what was not checked
43
+ def examine
44
+ edge = minting == "next" ? @subject.edge_from(before) : nil
45
+ refusals, after = edge ? @store.audit(@subject, edge) : [[], {}]
46
+ rust = RustAudit.call(@subject, after)
47
+ report = AuditReport.new(minting: minting, edge: edge, states: after, ruby: refusals, rust: rust)
48
+ answer(edge, report, (refusals + rust.violations).uniq, limits(edge, rust, report))
49
+ end
50
+
51
+ private
52
+
53
+ def plain(value) = { value: value }
54
+
55
+ def shape
56
+ { booted: flag(true), host_version: plain(Rehearsal.host_version), store: plain(@subject.declaration.kind),
57
+ mode: plain(@subject.declaration.mode), before: plain(before), after: plain(@subject.label),
58
+ minting: plain(minting) }
59
+ end
60
+
61
+ def answer(edge, report, refused, unchecked)
62
+ rows, total = Rows.from(refused)
63
+ edge_facts(edge).merge(refused_rows: rows, refusal_total: plain(total), parity: report.parity,
64
+ unchecked: unchecked).merge(report.facts)
65
+ end
66
+
67
+ def flag(value) = plain(value)
68
+
69
+ def census = @census ||= @store.census(@subject)
70
+
71
+ def copy_note
72
+ domain = @subject.bluebook.name
73
+ copy_empty? ? census.refusal(domain) : census.summary(domain)
74
+ end
75
+
76
+ def copy_empty?
77
+ census.empty? && @subject.declaration.mode != "empty" && @subject.bluebook.aggregates.any?
78
+ end
79
+
80
+ # The copy is empty when a dump or a readonly copy was asked for and it holds no row of a
81
+ # domain that declares aggregates; a mode of `empty` is asked to hold nothing.
82
+ def copy_facts = census.facts.merge(copy_empty: flag(copy_empty?))
83
+
84
+ def edge_facts(edge)
85
+ { minting: plain(minting), edge_present: flag(!edge.nil?), edge_approved: flag(approved?(edge)) }
86
+ end
87
+
88
+ def eras = @eras ||= @store.eras(@subject)
89
+
90
+ def latest = @latest ||= eras.last && @subject.held.named(eras.last)
91
+
92
+ def before = latest ? latest[:label] : "none"
93
+
94
+ def minting
95
+ return "first" unless latest
96
+
97
+ before == @subject.label ? "none" : "next"
98
+ end
99
+
100
+ def approved?(edge)
101
+ return false unless edge
102
+
103
+ !Translation::ApprovalFile.needs_rehearsal?(edge) ||
104
+ !Translation::ApprovalFile.applicable(@subject.directory, edge).nil?
105
+ end
106
+
107
+ def limits(edge, rust, report)
108
+ engine = rust.ran ? [] : [ENGINE_LIMIT]
109
+ engine + report.limits + ALWAYS + @store.limits(edge) + [journal_limit] + attached_limits +
110
+ (@subject.held.capable? ? [] : [binding_note])
111
+ end
112
+
113
+ # The audit reads the journal's latest entry of each record, never the head tables.
114
+ def journal_limit
115
+ { what: "the head tables",
116
+ why: "the audit reads each record's latest journal entry (hecks_journal_<domain>_era_N), not the head " \
117
+ "tables, so an edit to a head table is not seen. The copy holds #{census.head_rows} head rows " \
118
+ "and #{census.journal_rows} journal records#{divergence}" }
119
+ end
120
+
121
+ def divergence
122
+ census.head_rows == census.journal_rows ? "." : ": they differ, and only the journal's are audited."
123
+ end
124
+
125
+ # The chapters loaded beside the domain's own (a vendored package): the host holds eras
126
+ # for the domain's own aggregates only, so theirs can neither mint nor be refused.
127
+ def attached_limits
128
+ names = @subject.held.registry.bluebooks.values.map(&:name) - [@subject.bluebook.name]
129
+ return [] if names.empty?
130
+
131
+ [{ what: "the shape of the attached chapters (#{names.sort.join(", ")})",
132
+ why: "their aggregates have no era row of their own, so a change to them is not a mint" }]
133
+ end
134
+
135
+ def binding_note
136
+ { what: "that a boot would mint", why: "the domain is bound to #{@subject.held.adapter_name}, which holds no eras " \
137
+ "in this environment" }
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "rows"
5
+ require_relative "census"
6
+
7
+ module Hecks
8
+ module Adapters
9
+ module Rehearsal
10
+ # The data copy as a JSON document the world names (`rows`): the eras held and the records
11
+ # stored under the latest, in that era's shape. Nothing is created or dropped, so specs and
12
+ # fixtures rehearse without a database.
13
+ #
14
+ # The document is `{"eras": [{"ordinal": 1, "held_text": "<bluebook text>"}], "records":
15
+ # {"Aggregate": {"id": {"field": ...}}}}`.
16
+ class MemoryStore
17
+ # @param declaration [Declaration] a `memory` declaration
18
+ def initialize(declaration)
19
+ path = declaration.rows
20
+ @document = path ? JSON.parse(File.read(path)) : {}
21
+ end
22
+
23
+ # @return [void]
24
+ def prepare! = nil
25
+
26
+ # @return [void] there is nothing to drop
27
+ def drop! = nil
28
+
29
+ # @param _subject [Subject] unused: the document holds its own eras
30
+ # @return [Array<Hash>] the eras, `ordinal` and `held_text`
31
+ def eras(_subject)
32
+ Array(@document["eras"]).map { |era| { ordinal: era["ordinal"], held_text: era["held_text"] } }
33
+ end
34
+
35
+ # What the document holds: it keeps the latest state of each record only, so the head and
36
+ # the journal counts are the same number.
37
+ #
38
+ # @param subject [Subject] the loaded domain
39
+ # @return [Census] the counts
40
+ def census(subject)
41
+ counts = subject.bluebook.aggregates.map do |aggregate|
42
+ held = records(aggregate.name).size
43
+ Census::Count.new(aggregate: aggregate.name, head: held, journal: held)
44
+ end
45
+ Census.new(schema: "(memory document)", source: "memory", present: true, eras: eras(subject).size,
46
+ journal_entries: counts.sum(&:journal), counts: counts)
47
+ end
48
+
49
+ # Runs the mint's audit over the stored records, translated through the edge in process.
50
+ #
51
+ # @param subject [Subject] the loaded domain
52
+ # @param edge [Bluebook::Translation] the edge leading to the shape
53
+ # @return [Array(Array<String>, Hash)] the audit's refusals, and the translated records
54
+ # by aggregate name and id
55
+ def audit(subject, edge)
56
+ refusals = []
57
+ after = subject.bluebook.aggregates.to_h do |aggregate|
58
+ declared = edge.for_aggregate(aggregate.name)
59
+ before = records(declared&.was || aggregate.name)
60
+ translated = translate(aggregate, declared, before)
61
+ refusals.concat(Translation::Audit.check(aggregate: aggregate, declared: declared, before: before,
62
+ after: translated).violations)
63
+ [aggregate.name, translated]
64
+ end
65
+ [refusals, after]
66
+ end
67
+
68
+ # @param edge [Bluebook::Translation] the edge
69
+ # @return [Array<Hash>] what memory cannot apply, as report limits
70
+ def limits(edge)
71
+ return [] unless edge && Translation::ApprovalFile.needs_rehearsal?(edge)
72
+
73
+ [{ what: "the edge's compute and rekey rules",
74
+ why: "they are SQL, which only a postgres copy runs; memory leaves those fields as stored" }]
75
+ end
76
+
77
+ private
78
+
79
+ def records(name) = @document.dig("records", name.to_s) || {}
80
+
81
+ def translate(aggregate, declared, before)
82
+ lineage = Ports::Persistence::Lineage.from_declared(declared, aggregate.name)
83
+ return before unless lineage
84
+
85
+ before.to_h { |id, state| [id, lineage.translate(entry(id, state)).state] }
86
+ end
87
+
88
+ def entry(id, state)
89
+ Ports::Persistence::Entry.new(operation: "save", id: id, state: symbolise(state), mirrors: {})
90
+ end
91
+
92
+ def symbolise(state) = JSON.parse(JSON.generate(state), symbolize_names: true)
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "census"
4
+
5
+ module Hecks
6
+ module Adapters
7
+ module Rehearsal
8
+ # Counts what a Postgres copy holds for the domain without creating anything: a plain
9
+ # connection, the schemas looked up before one is chosen, and counts over the relations that
10
+ # exist. A schema that is not in the copy is reported, never created.
11
+ class PostgresCensus
12
+ JOURNAL_LATEST = <<~SQL
13
+ SELECT aggregate, count(*) AS live FROM (
14
+ SELECT DISTINCT ON (aggregate, aggregate_id) aggregate, operation
15
+ FROM %<journal>s ORDER BY aggregate, aggregate_id, ordinal DESC
16
+ ) latest WHERE operation = 'save' GROUP BY aggregate
17
+ SQL
18
+
19
+ # @param url [String] the copy's database URL
20
+ # @param subject [Subject] the loaded domain
21
+ # @param candidates [Array<Array(String, String)>] schema and source, in the order tried
22
+ def initialize(url, subject, candidates)
23
+ @url = url
24
+ @subject = subject
25
+ @candidates = candidates
26
+ end
27
+
28
+ # @return [Census] the counts, in the first candidate schema that holds a lineage, else the
29
+ # first that exists, else the first named
30
+ def read
31
+ db = PG.connect(@url)
32
+ present = schemas(db)
33
+ lineage = holders(db)
34
+ name, source = choose(present, lineage)
35
+ return absent(name, source, lineage) unless present.include?(name)
36
+
37
+ db.exec("SET search_path TO #{db.quote_ident(name)}")
38
+ counted(db, name, source, lineage - [name])
39
+ ensure
40
+ db&.close
41
+ end
42
+
43
+ private
44
+
45
+ def schemas(db) = db.exec("SELECT schema_name FROM information_schema.schemata").map { |row| row["schema_name"] }
46
+
47
+ def holders(db)
48
+ sql = "SELECT table_schema FROM information_schema.tables WHERE table_name = 'hecks_eras'"
49
+ db.exec(sql).map { |row| row["table_schema"] }.sort
50
+ end
51
+
52
+ def choose(present, lineage)
53
+ @candidates.find { |name, _| lineage.include?(name) } || @candidates.find { |name, _| present.include?(name) } ||
54
+ @candidates.first
55
+ end
56
+
57
+ def absent(name, source, lineage)
58
+ Census.new(schema: name, source: source, present: false, eras: 0, journal_entries: 0,
59
+ counts: empty_counts, elsewhere: lineage)
60
+ end
61
+
62
+ def empty_counts = @subject.bluebook.aggregates.map { |a| Census::Count.new(aggregate: a.name, head: 0, journal: 0) }
63
+
64
+ def counted(db, name, source, elsewhere)
65
+ lineage = PostgresEra::Lineage.new(db, @subject.bluebook.name)
66
+ Census.new(schema: name, source: source, present: true, eras: eras(db), counts: counts(db, lineage),
67
+ journal_entries: rows(db, lineage.journal), elsewhere: elsewhere)
68
+ end
69
+
70
+ def counts(db, lineage)
71
+ live = latest(db, lineage)
72
+ @subject.bluebook.aggregates.map do |aggregate|
73
+ Census::Count.new(aggregate: aggregate.name, head: rows(db, lineage.head_view(aggregate.storage_name)),
74
+ journal: live.fetch(aggregate.storage_name, 0))
75
+ end
76
+ end
77
+
78
+ def eras(db)
79
+ return 0 unless relation?(db, "hecks_eras")
80
+
81
+ db.exec_params("SELECT count(*) FROM hecks_eras WHERE domain = $1", [@subject.bluebook.name])[0]["count"].to_i
82
+ end
83
+
84
+ def latest(db, lineage)
85
+ return {} unless relation?(db, lineage.journal)
86
+
87
+ db.exec(format(JOURNAL_LATEST, journal: db.quote_ident(lineage.journal))).to_h { |r| [r["aggregate"], r["live"].to_i] }
88
+ end
89
+
90
+ def rows(db, name)
91
+ relation?(db, name) ? db.exec("SELECT count(*) FROM #{db.quote_ident(name)}")[0]["count"].to_i : 0
92
+ rescue PG::Error
93
+ 0
94
+ end
95
+
96
+ def relation?(db, name) = !db.exec_params("SELECT to_regclass($1) AS found", [db.quote_ident(name)])[0]["found"].nil?
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "postgres_census"
4
+
5
+ module Hecks
6
+ module Adapters
7
+ module Rehearsal
8
+ # What a `PostgresStore` reads from its database: the eras it holds and the mint's audit over
9
+ # the records under them. No table is provisioned and no era written; a copy somebody else
10
+ # made is read in a read-only session, and the database a run made is its own to touch.
11
+ #
12
+ # The schema is the world's `schema`, else `HECKS_SCHEMA`, else the one the domain's name
13
+ # gives (`Shop` is `shop`), else `public` when that holds the lineage. It is looked up
14
+ # before it is used, so a schema the copy does not have is reported and never created.
15
+ module PostgresReading
16
+ # @param subject [Subject] the loaded domain
17
+ # @return [Census] what the copy holds for the domain, counted once
18
+ def census(subject)
19
+ @census ||= PostgresCensus.new(url, subject, schema_candidates(subject)).read.tap do |found|
20
+ subject.held.read_schema(found.schema)
21
+ end
22
+ end
23
+
24
+ # @param subject [Subject] the loaded domain
25
+ # @return [Array<Hash>] the eras the database holds, none when it holds no lineage
26
+ def eras(subject)
27
+ return [] unless census(subject).present
28
+
29
+ reading(subject) { |lineage| subject.held.held_eras(lineage) }
30
+ end
31
+
32
+ # @param subject [Subject] the loaded domain
33
+ # @param _edge [Bluebook::Translation] the edge leading to the shape
34
+ # @return [Array(Array<String>, Hash)] what the audit's layers refuse over the stored
35
+ # records, and the records as translated, by aggregate name and id
36
+ def audit(subject, _edge)
37
+ return [[], {}] unless census(subject).present
38
+
39
+ reading(subject) do |lineage|
40
+ finding = JournalStore::EdgeAudit.call(subject.held, lineage, subject.held.held_eras(lineage))
41
+ finding ? [finding.refusals, finding.after] : [[], {}]
42
+ end
43
+ end
44
+
45
+ # @param _edge [Bluebook::Translation] unused
46
+ # @return [Array<Hash>] none: a postgres copy runs the compiled rules itself
47
+ def limits(_edge) = []
48
+
49
+ private
50
+
51
+ def schema_candidates(subject)
52
+ return [[@declaration.schema, "world"]] if @declaration.schema
53
+ return [[ENV.fetch("HECKS_SCHEMA"), "HECKS_SCHEMA"]] if ENV.fetch("HECKS_SCHEMA", "") != ""
54
+
55
+ [[Naming.snake(subject.bluebook.name), "derived"], %w[public default]]
56
+ end
57
+
58
+ def reading(subject)
59
+ subject.held.read_from(url)
60
+ db = PostgresEra.connect_for(subject.bluebook.name, subject.held.settings)
61
+ db.exec("SET default_transaction_read_only = on") if @declaration.mode == "readonly_copy"
62
+ yield PostgresEra::Lineage.new(db, subject.bluebook.name)
63
+ ensure
64
+ db&.close
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end