hecks 3.10.0 → 3.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (542) hide show
  1. checksums.yaml +4 -4
  2. data/examples/pizzas/bluebook/pizzas.bluebook +29 -0
  3. data/examples/pizzas/bluebook/pizzas.hecksagon +7 -0
  4. data/exe/hecks +4 -4
  5. data/lib/hecks/adapters/driven/d1.rb +2 -10
  6. data/lib/hecks/adapters/driven/heki.adapter +1 -1
  7. data/lib/hecks/adapters/driven/heki.rb +2 -12
  8. data/lib/hecks/adapters/driven/lambda.adapter +1 -1
  9. data/lib/hecks/adapters/driven/lambda.rb +3 -18
  10. data/lib/hecks/adapters/driven/postgres.rb +4 -11
  11. data/lib/hecks/adapters/driven/sqlite.adapter +1 -1
  12. data/lib/hecks/adapters/driven/sqlite.rb +2 -10
  13. data/lib/hecks/adapters/driving/admission.rb +46 -0
  14. data/lib/hecks/adapters/driving/cli/nesting.rb +36 -0
  15. data/lib/hecks/adapters/driving/cli/shorthand.rb +88 -0
  16. data/lib/hecks/adapters/driving/cli.rb +127 -0
  17. data/lib/hecks/adapters/driving/cli_runner/answers.rb +160 -0
  18. data/lib/hecks/adapters/driving/cli_runner/resolution.rb +158 -0
  19. data/lib/hecks/adapters/driving/cli_runner/settling.rb +120 -0
  20. data/lib/hecks/adapters/driving/cli_runner/streaming.rb +93 -0
  21. data/lib/hecks/adapters/driving/cli_runner/suggestions.rb +47 -0
  22. data/lib/hecks/adapters/driving/cli_runner.rb +70 -0
  23. data/lib/hecks/adapters/driving/command_request.rb +121 -0
  24. data/lib/hecks/adapters/driving/handle.rb +214 -0
  25. data/lib/hecks/adapters/driving/json.rb +136 -0
  26. data/lib/hecks/adapters/driving/launcher_options.rb +267 -0
  27. data/lib/hecks/adapters/driving/mcp/properties.rb +49 -0
  28. data/lib/hecks/adapters/driving/mcp/tool_calls.rb +116 -0
  29. data/lib/hecks/adapters/driving/mcp/tool_definitions/auditing.rb +53 -0
  30. data/lib/hecks/adapters/driving/mcp/tool_definitions/calling.rb +71 -0
  31. data/lib/hecks/adapters/driving/mcp/tool_definitions/reading.rb +99 -0
  32. data/lib/hecks/adapters/driving/mcp/tool_definitions.rb +18 -0
  33. data/lib/hecks/adapters/driving/mcp.rb +159 -0
  34. data/lib/hecks/adapters/driving/mcp_guide_cache.rb +78 -0
  35. data/lib/hecks/adapters/driving/mcp_scope/argument_policy.rb +97 -0
  36. data/lib/hecks/adapters/driving/mcp_scope/env_problems.rb +69 -0
  37. data/lib/hecks/adapters/driving/mcp_scope/presenter.rb +83 -0
  38. data/lib/hecks/adapters/driving/mcp_scope.rb +213 -0
  39. data/lib/hecks/adapters/driving/newcomer.rb +50 -0
  40. data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +171 -0
  41. data/lib/hecks/adapters/driving/ruby/chapter.rb +91 -0
  42. data/lib/hecks/adapters/driving/ruby.rb +83 -0
  43. data/lib/hecks/adapters/driving/usage_cache.rb +144 -0
  44. data/lib/hecks/{doors.rb → adapters/driving.rb} +10 -6
  45. data/lib/hecks/behaviors/expectations/boot.rb +1 -1
  46. data/lib/hecks/bench/ruby_runner.rb +1 -1
  47. data/lib/hecks/bluebook/ask_resolution.rb +130 -0
  48. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +8 -2
  49. data/lib/hecks/bluebook/assembly/contracts/read_models.rb +1 -1
  50. data/lib/hecks/bluebook/assembly/contracts/rules.rb +4 -2
  51. data/lib/hecks/bluebook/assembly/contracts/structure.rb +3 -1
  52. data/lib/hecks/bluebook/assembly/marks.rb +4 -15
  53. data/lib/hecks/bluebook/assembly/scalars.rb +39 -0
  54. data/lib/hecks/bluebook/attribute.rb +1 -1
  55. data/lib/hecks/bluebook/behaviour/command.rb +13 -8
  56. data/lib/hecks/bluebook/behaviour/policy.rb +30 -0
  57. data/lib/hecks/bluebook/capabilities/attribute_values.rb +72 -0
  58. data/lib/hecks/bluebook/capabilities.rb +91 -9
  59. data/lib/hecks/bluebook/domain_port.rb +16 -4
  60. data/lib/hecks/bluebook/dsl/adapter_builder.rb +18 -6
  61. data/lib/hecks/bluebook/dsl/binding_proxy.rb +13 -0
  62. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/computed_sources.rb +98 -0
  63. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/fan_out_rows.rb +42 -0
  64. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/projected_fields.rb +14 -2
  65. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/provisions.rb +69 -3
  66. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/with_projections.rb +17 -10
  67. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +5 -0
  68. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
  69. data/lib/hecks/bluebook/dsl/command_builder/mutations.rb +8 -1
  70. data/lib/hecks/bluebook/dsl/command_builder/operands.rb +121 -0
  71. data/lib/hecks/bluebook/dsl/command_builder.rb +2 -0
  72. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +10 -1
  73. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +45 -2
  74. data/lib/hecks/bluebook/dsl/policy_builder.rb +47 -10
  75. data/lib/hecks/bluebook/dsl/rule_reference.rb +7 -4
  76. data/lib/hecks/bluebook/dsl/world_builder.rb +1 -1
  77. data/lib/hecks/bluebook/expression/ast_json/emitters.rb +6 -0
  78. data/lib/hecks/bluebook/expression/ast_json.rb +9 -6
  79. data/lib/hecks/bluebook/expression/ast_reader.rb +14 -9
  80. data/lib/hecks/bluebook/expression/projection.json +36 -0
  81. data/lib/hecks/bluebook/expression/resolver/operations.rb +31 -0
  82. data/lib/hecks/bluebook/expression/resolver/parsing.rb +38 -5
  83. data/lib/hecks/bluebook/expression/resolver/scanning.rb +22 -5
  84. data/lib/hecks/bluebook/expression/resolver/values.rb +10 -0
  85. data/lib/hecks/bluebook/expression/resolver.rb +20 -1
  86. data/lib/hecks/bluebook/hecksagon.rb +23 -3
  87. data/lib/hecks/bluebook/lifecycle.rb +22 -3
  88. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +13 -1
  89. data/lib/hecks/bluebook/meta_validator/modes.rb +4 -1
  90. data/lib/hecks/bluebook/meta_validator/readings.rb +24 -1
  91. data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +2 -1
  92. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +6 -3
  93. data/lib/hecks/bluebook/meta_validator/shapes.rb +5 -0
  94. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +1 -1
  95. data/lib/hecks/bluebook/meta_validator.rb +12 -12
  96. data/lib/hecks/bluebook/model_check/ask_checks.rb +71 -0
  97. data/lib/hecks/bluebook/model_check/policy_checks.rb +2 -1
  98. data/lib/hecks/bluebook/model_check/reference_checks.rb +70 -0
  99. data/lib/hecks/bluebook/model_check.rb +8 -2
  100. data/lib/hecks/bluebook/policy.rb +4 -2
  101. data/lib/hecks/bluebook/smoke_test.rb +1 -1
  102. data/lib/hecks/bluebook/synthesizer.rb +2 -0
  103. data/lib/hecks/bluebook.rb +2 -0
  104. data/lib/hecks/cli/command_table.rb +1 -1
  105. data/lib/hecks/cli/console.rb +2 -1
  106. data/lib/hecks/cli/document.rb +1 -1
  107. data/lib/hecks/cli/launcher_words.rb +4 -4
  108. data/lib/hecks/cli/mcp.rb +4 -4
  109. data/lib/hecks/cli/project_cli/launcher_source.rb +2 -2
  110. data/lib/hecks/cli/project_cli/templates.rb +5 -5
  111. data/lib/hecks/cli/project_cli.rb +9 -6
  112. data/lib/hecks/cli/run.rb +2 -2
  113. data/lib/hecks/cli/smoke_http/checks.rb +4 -1
  114. data/lib/hecks/cli/smoke_http/transport.rb +3 -3
  115. data/lib/hecks/cli/smoke_http.rb +3 -0
  116. data/lib/hecks/codemod.rb +5 -2
  117. data/lib/hecks/corpus/routes.rb +7 -0
  118. data/lib/hecks/corpus/rust.rb +3 -2
  119. data/lib/hecks/deploy/bluebook/deploy.bluebook +34 -4
  120. data/lib/hecks/doc/reference/rendering.rb +6 -3
  121. data/lib/hecks/forms/app/commands.rb +8 -7
  122. data/lib/hecks/forms/app/queries.rb +2 -1
  123. data/lib/hecks/forms/app.rb +1 -1
  124. data/lib/hecks/forms/record_renderer.rb +1 -1
  125. data/lib/hecks/framework/bluebook/privacy.bluebook +11 -0
  126. data/lib/hecks/fuzzing/concurrent_dispatch.rb +4 -1
  127. data/lib/hecks/fuzzing/failure_triage.rb +2 -1
  128. data/lib/hecks/fuzzing/mutation/run.rb +11 -4
  129. data/lib/hecks/fuzzing/properties/catalog.rb +6 -4
  130. data/lib/hecks/fuzzing/properties/guaranteed_by_construction.rb +18 -11
  131. data/lib/hecks/fuzzing/properties/guards.rb +1 -1
  132. data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +1 -1
  133. data/lib/hecks/fuzzing/self_consistency.rb +6 -2
  134. data/lib/hecks/fuzzing/shrinker.rb +3 -1
  135. data/lib/hecks/fuzzing/value_generator.rb +19 -5
  136. data/lib/hecks/gate/stages.yml +33 -7
  137. data/lib/hecks/grammar/evolve.rb +1 -1
  138. data/lib/hecks/grammar/expression_operators.json +372 -0
  139. data/lib/hecks/grammar/operators.rb +3 -1
  140. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +10 -10
  141. data/lib/hecks/hecks/adapters/codebase/inspection/rubycritic.rb +35 -0
  142. data/lib/hecks/hecks/adapters/codebase/inspection.rb +104 -0
  143. data/lib/hecks/hecks/adapters/codebase/publishing.rb +2 -1
  144. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +12 -2
  145. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +2 -1
  146. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +1 -1
  147. data/lib/hecks/hecks/adapters/cost_explorer.rb +8 -5
  148. data/lib/hecks/hecks/adapters/deploy_toolchain/statuses.rb +2 -1
  149. data/lib/hecks/hecks/adapters/finding_github.rb +13 -6
  150. data/lib/hecks/hecks/adapters/first_admin.rb +2 -2
  151. data/lib/hecks/hecks/adapters/github_rulesets.rb +6 -3
  152. data/lib/hecks/hecks/adapters/in_process_boot.rb +1 -1
  153. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +1 -1
  154. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +10 -20
  155. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +28 -5
  156. data/lib/hecks/hecks/adapters/local_files.rb +2 -2
  157. data/lib/hecks/hecks/adapters/rehearsal/audit_report.rb +107 -0
  158. data/lib/hecks/hecks/adapters/rehearsal/census.rb +71 -0
  159. data/lib/hecks/hecks/adapters/rehearsal/declaration.rb +133 -0
  160. data/lib/hecks/hecks/adapters/rehearsal/environment.rb +83 -0
  161. data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +142 -0
  162. data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +96 -0
  163. data/lib/hecks/hecks/adapters/rehearsal/postgres_census.rb +100 -0
  164. data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +69 -0
  165. data/lib/hecks/hecks/adapters/rehearsal/postgres_store.rb +133 -0
  166. data/lib/hecks/hecks/adapters/rehearsal/rows.rb +41 -0
  167. data/lib/hecks/hecks/adapters/rehearsal/rust_audit.rb +83 -0
  168. data/lib/hecks/hecks/adapters/rehearsal/subject.rb +104 -0
  169. data/lib/hecks/hecks/adapters/rehearsal.rb +54 -0
  170. data/lib/hecks/hecks/adapters/rehearsal_store.adapter +7 -0
  171. data/lib/hecks/hecks/adapters/rehearsal_store.rb +81 -0
  172. data/lib/hecks/hecks/adapters/terminal.rb +13 -11
  173. data/lib/hecks/hecks/codebase.behaviors +13 -1
  174. data/lib/hecks/hecks/codebase.bluebook +239 -5
  175. data/lib/hecks/hecks/custodian.behaviors +24 -6
  176. data/lib/hecks/hecks/custodian.bluebook +6 -1227
  177. data/lib/hecks/hecks/deploy.behaviors +1 -1
  178. data/lib/hecks/hecks/deploy.world +1 -3
  179. data/lib/hecks/hecks/hecks.behaviors +1 -1
  180. data/lib/hecks/hecks/hecks.hecksagon +53 -18
  181. data/lib/hecks/hecks/hecks.world +13 -10
  182. data/lib/hecks/hecks/rehearsal.bluebook +477 -0
  183. data/lib/hecks/hecks/site.behaviors +1 -1
  184. data/lib/hecks/hecks/site.world +0 -1
  185. data/lib/hecks/hecks/tooling.bluebook +1238 -0
  186. data/lib/hecks/indifferent_key.rb +20 -0
  187. data/lib/hecks/language/adapter.bluebook +13 -3
  188. data/lib/hecks/language/bluebook/aggregate.bluebook +23 -0
  189. data/lib/hecks/language/bluebook/bluebook.behaviors +23 -0
  190. data/lib/hecks/language/bluebook/bluebook.bluebook +15 -0
  191. data/lib/hecks/language/bluebook/entity.bluebook +18 -0
  192. data/lib/hecks/language/bluebook/policy.behaviors +19 -0
  193. data/lib/hecks/language/bluebook/policy.bluebook +9 -1
  194. data/lib/hecks/language/bluebook/query.bluebook +1 -1
  195. data/lib/hecks/language/bluebook/vocabulary.bluebook +63 -4
  196. data/lib/hecks/language/hecksagon/domain_port.bluebook +1 -1
  197. data/lib/hecks/language/hecksagon/hecksagon.bluebook +22 -0
  198. data/lib/hecks/language/oidc.json +10 -0
  199. data/lib/hecks/language/port.bluebook +4 -4
  200. data/lib/hecks/literal.rb +12 -3
  201. data/lib/hecks/ports/key_vault.rb +7 -2
  202. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +21 -10
  203. data/lib/hecks/ports/persistence/plugins/era/postgres_era/boot.rb +4 -2
  204. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +2 -1
  205. data/lib/hecks/ports/persistence/repository_factory.rb +6 -3
  206. data/lib/hecks/projection_files/expression_tables.rb +7 -2
  207. data/lib/hecks/projections/deploy/box/compose.rb +6 -4
  208. data/lib/hecks/projections/deploy/box/edge.rb +62 -0
  209. data/lib/hecks/projections/deploy/box/logging.rb +99 -0
  210. data/lib/hecks/projections/deploy/box/roll_scripts.rb +16 -8
  211. data/lib/hecks/projections/deploy/box/settings/cdn.rb +40 -0
  212. data/lib/hecks/projections/deploy/box/settings/logs.rb +40 -0
  213. data/lib/hecks/projections/deploy/box/settings/patterns.rb +11 -0
  214. data/lib/hecks/projections/deploy/box/settings/types.rb +16 -1
  215. data/lib/hecks/projections/deploy/box/settings.rb +6 -2
  216. data/lib/hecks/projections/deploy/box/stack_yaml.rb +5 -2
  217. data/lib/hecks/projections/deploy/box/templates/Makefile.tmpl +16 -0
  218. data/lib/hecks/projections/deploy/box/templates/box.yaml.tmpl +8 -4
  219. data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +32 -0
  220. data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +1 -1
  221. data/lib/hecks/projections/deploy/box/templates/edge.mk.tmpl +17 -0
  222. data/lib/hecks/projections/deploy/box/templates/edge.yaml.tmpl +65 -0
  223. data/lib/hecks/projections/deploy/box/templates/log-group.yaml.tmpl +12 -0
  224. data/lib/hecks/projections/deploy/box/templates/log-policy.yaml.tmpl +7 -0
  225. data/lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl +5 -4
  226. data/lib/hecks/projections/deploy/box/templates/render-compose.sh.tmpl +5 -4
  227. data/lib/hecks/projections/deploy/box.rb +15 -3
  228. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  229. data/lib/hecks/projections/deploy/shared.rb +5 -2
  230. data/lib/hecks/projections/model/deviations.rb +2 -2
  231. data/lib/hecks/projections/model.rb +2 -2
  232. data/lib/hecks/projections/oidc.rb +13 -1
  233. data/lib/hecks/projections/shape.rb +5 -2
  234. data/lib/hecks/projections/site/cms_editor/attributes.rb +26 -6
  235. data/lib/hecks/projections/site/cms_editor/blocks.rb +157 -0
  236. data/lib/hecks/projections/site/cms_editor/checks.rb +14 -1
  237. data/lib/hecks/projections/site/cms_editor/clearing.rb +70 -0
  238. data/lib/hecks/projections/site/cms_editor/color.rb +124 -0
  239. data/lib/hecks/projections/site/cms_editor/composition.rb +42 -0
  240. data/lib/hecks/projections/site/cms_editor/destructive.rb +61 -0
  241. data/lib/hecks/projections/site/cms_editor/drafts.rb +102 -0
  242. data/lib/hecks/projections/site/cms_editor/hints.rb +74 -0
  243. data/lib/hecks/projections/site/cms_editor/icons.rb +86 -0
  244. data/lib/hecks/projections/site/cms_editor/key_kinds.rb +31 -0
  245. data/lib/hecks/projections/site/cms_editor/limits.rb +72 -0
  246. data/lib/hecks/projections/site/cms_editor/media.rb +1 -1
  247. data/lib/hecks/projections/site/cms_editor/moment.rb +34 -0
  248. data/lib/hecks/projections/site/cms_editor/palette.rb +115 -0
  249. data/lib/hecks/projections/site/cms_editor/pickers.rb +85 -0
  250. data/lib/hecks/projections/site/cms_editor/preview.rb +91 -0
  251. data/lib/hecks/projections/site/cms_editor/relations.rb +92 -0
  252. data/lib/hecks/projections/site/cms_editor/scale_checks.rb +66 -0
  253. data/lib/hecks/projections/site/cms_editor/scheduling.rb +150 -0
  254. data/lib/hecks/projections/site/cms_editor/schema.rb +96 -19
  255. data/lib/hecks/projections/site/cms_editor/setting.rb +52 -9
  256. data/lib/hecks/projections/site/cms_editor/targets.rb +86 -0
  257. data/lib/hecks/projections/site/cms_editor/templates/package.json.tmpl +18 -1
  258. data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +251 -57
  259. data/lib/hecks/projections/site/cms_editor/templates/src/assets.ts.tmpl +98 -0
  260. data/lib/hecks/projections/site/cms_editor/templates/src/auth/membership.ts.tmpl +32 -24
  261. data/lib/hecks/projections/site/cms_editor/templates/src/browser/app.css.tmpl +412 -0
  262. data/lib/hecks/projections/site/cms_editor/templates/src/browser/autosave.js.tmpl +144 -0
  263. data/lib/hecks/projections/site/cms_editor/templates/src/browser/blocks.js.tmpl +500 -0
  264. data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_doc.js.tmpl +175 -0
  265. data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_editor.js.tmpl +418 -0
  266. data/lib/hecks/projections/site/cms_editor/templates/src/browser/epoch.js.tmpl +61 -0
  267. data/lib/hecks/projections/site/cms_editor/templates/src/browser/forms.js.tmpl +210 -0
  268. data/lib/hecks/projections/site/cms_editor/templates/src/browser/lists.js.tmpl +40 -0
  269. data/lib/hecks/projections/site/cms_editor/templates/src/browser/main.js.tmpl +26 -0
  270. data/lib/hecks/projections/site/cms_editor/templates/src/browser/media_picker.js.tmpl +232 -0
  271. data/lib/hecks/projections/site/cms_editor/templates/src/browser/moments.js.tmpl +73 -0
  272. data/lib/hecks/projections/site/cms_editor/templates/src/browser/pickers.js.tmpl +152 -0
  273. data/lib/hecks/projections/site/cms_editor/templates/src/browser/preview.js.tmpl +98 -0
  274. data/lib/hecks/projections/site/cms_editor/templates/src/browser/shell.js.tmpl +140 -0
  275. data/lib/hecks/projections/site/cms_editor/templates/src/browser/toast.js.tmpl +66 -0
  276. data/lib/hecks/projections/site/cms_editor/templates/src/choices.ts.tmpl +99 -0
  277. data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +78 -24
  278. data/lib/hecks/projections/site/cms_editor/templates/src/config.ts.tmpl +6 -0
  279. data/lib/hecks/projections/site/cms_editor/templates/src/flash.ts.tmpl +52 -0
  280. data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +19 -2
  281. data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +31 -8
  282. data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +36 -0
  283. data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +131 -2
  284. data/lib/hecks/projections/site/cms_editor/templates/src/ui/availability.ts.tmpl +15 -0
  285. data/lib/hecks/projections/site/cms_editor/templates/src/ui/block_list.js.tmpl +213 -0
  286. data/lib/hecks/projections/site/cms_editor/templates/src/ui/blocks.ts.tmpl +275 -0
  287. data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_model.js.tmpl +8 -5
  288. data/lib/hecks/projections/site/cms_editor/templates/src/ui/detail.ts.tmpl +88 -0
  289. data/lib/hecks/projections/site/cms_editor/templates/src/ui/dialogs.ts.tmpl +43 -0
  290. data/lib/hecks/projections/site/cms_editor/templates/src/ui/fields.ts.tmpl +229 -34
  291. data/lib/hecks/projections/site/cms_editor/templates/src/ui/html.ts.tmpl +40 -81
  292. data/lib/hecks/projections/site/cms_editor/templates/src/ui/icons.ts.tmpl +15 -0
  293. data/lib/hecks/projections/site/cms_editor/templates/src/ui/input.ts.tmpl +29 -12
  294. data/lib/hecks/projections/site/cms_editor/templates/src/ui/links.ts.tmpl +100 -0
  295. data/lib/hecks/projections/site/cms_editor/templates/src/ui/moments.ts.tmpl +44 -0
  296. data/lib/hecks/projections/site/cms_editor/templates/src/ui/outline.ts.tmpl +53 -17
  297. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +178 -88
  298. data/lib/hecks/projections/site/cms_editor/templates/src/ui/paging.ts.tmpl +82 -0
  299. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +110 -0
  300. data/lib/hecks/projections/site/cms_editor/templates/src/ui/preview.ts.tmpl +87 -0
  301. data/lib/hecks/projections/site/cms_editor/templates/src/ui/related.ts.tmpl +97 -0
  302. data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +160 -0
  303. data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +211 -0
  304. data/lib/hecks/projections/site/cms_editor/templates/src/ui/table.ts.tmpl +151 -0
  305. data/lib/hecks/projections/site/cms_editor/templates/src/ui/theme.js.tmpl +16 -0
  306. data/lib/hecks/projections/site/cms_editor/templates/src/ui/values.ts.tmpl +69 -0
  307. data/lib/hecks/projections/site/cms_editor/templates/src/ui/words.ts.tmpl +97 -0
  308. data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +35 -0
  309. data/lib/hecks/projections/site/cms_editor/templates/tsconfig.json.tmpl +2 -1
  310. data/lib/hecks/projections/site/cms_editor/theme.rb +29 -0
  311. data/lib/hecks/projections/site/cms_editor/theme_checks.rb +57 -0
  312. data/lib/hecks/projections/site/cms_editor.rb +49 -23
  313. data/lib/hecks/projections/site/role_probe.rb +2 -0
  314. data/lib/hecks/projections/site/site_root/workflow.rb +5 -0
  315. data/lib/hecks/projector/cli_audience.rb +22 -1
  316. data/lib/hecks/projector/cli_projector/listing.rb +1 -1
  317. data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
  318. data/lib/hecks/projector/cli_projector/usage.rb +15 -4
  319. data/lib/hecks/projector/docs_projector/closing.rb +1 -1
  320. data/lib/hecks/projector/docs_projector.rb +1 -1
  321. data/lib/hecks/projector/exporter/bindings.rb +40 -10
  322. data/lib/hecks/projector/exporter/capability_values.rb +55 -0
  323. data/lib/hecks/projector/narrate_projector/reactions.rb +1 -1
  324. data/lib/hecks/projector/target.rb +11 -0
  325. data/lib/hecks/projector.rb +32 -5
  326. data/lib/hecks/quality_control/adapters/github_checks.rb +12 -8
  327. data/lib/hecks/quality_control/adapters/github_issues.rb +8 -4
  328. data/lib/hecks/quality_control/cli/qa_open_pr.rb +2 -2
  329. data/lib/hecks/quality_control/cli/qa_sweep/checks/shrinking.rb +1 -1
  330. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +1 -1
  331. data/lib/hecks/quality_control/cli/qa_sweep.rb +1 -1
  332. data/lib/hecks/quality_control/quality_control.bluebook +6 -2
  333. data/lib/hecks/release/approval.rb +64 -0
  334. data/lib/hecks/router/{door.rb → aggregate_reader.rb} +5 -4
  335. data/lib/hecks/router/namespace_installer.rb +16 -16
  336. data/lib/hecks/router.rb +1 -1
  337. data/lib/hecks/runtime/best_effort.rb +22 -0
  338. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +12 -2
  339. data/lib/hecks/runtime/command_interpreter.rb +3 -2
  340. data/lib/hecks/runtime/command_rules/emission.rb +1 -1
  341. data/lib/hecks/runtime/dispatcher.rb +4 -4
  342. data/lib/hecks/runtime/entity_element/location.rb +1 -1
  343. data/lib/hecks/runtime/event.rb +5 -0
  344. data/lib/hecks/runtime/identity.rb +7 -6
  345. data/lib/hecks/runtime/interpreting.rb +2 -1
  346. data/lib/hecks/runtime/invocation_builder.rb +1 -1
  347. data/lib/hecks/runtime/loader/boot_steps.rb +8 -5
  348. data/lib/hecks/runtime/loader.rb +10 -10
  349. data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -4
  350. data/lib/hecks/runtime/policy_interpreter/fan_out.rb +3 -3
  351. data/lib/hecks/runtime/policy_interpreter.rb +6 -6
  352. data/lib/hecks/runtime/port_operation_interpreter.rb +1 -1
  353. data/lib/hecks/runtime/reaction_invocation.rb +1 -1
  354. data/lib/hecks/runtime/rebuild_sweep.rb +11 -1
  355. data/lib/hecks/runtime/registry/ask_verification.rb +35 -0
  356. data/lib/hecks/runtime/registry/hecksagon_rules.rb +10 -0
  357. data/lib/hecks/runtime/registry/merging.rb +1 -1
  358. data/lib/hecks/runtime/registry/providers.rb +16 -0
  359. data/lib/hecks/runtime/registry/read_repository.rb +6 -4
  360. data/lib/hecks/runtime/registry/verification.rb +4 -1
  361. data/lib/hecks/runtime/registry/world_defaults.rb +29 -0
  362. data/lib/hecks/runtime/registry.rb +1 -1
  363. data/lib/hecks/runtime/saga_interpreter/compensation.rb +1 -1
  364. data/lib/hecks/runtime/saga_interpreter/delivery.rb +4 -4
  365. data/lib/hecks/runtime/saga_interpreter/transitions.rb +1 -1
  366. data/lib/hecks/runtime/saga_interpreter.rb +3 -3
  367. data/lib/hecks/runtime/tenant_check.rb +5 -4
  368. data/lib/hecks/runtime/value/coercion.rb +2 -2
  369. data/lib/hecks/runtime/value/validation.rb +16 -4
  370. data/lib/hecks/runtime.rb +9 -9
  371. data/lib/hecks/rust_build/host.rb +11 -4
  372. data/lib/hecks/rust_build/prebuilt_binary.rb +101 -0
  373. data/lib/hecks/rust_build/project_rust/target_ir.rb +1 -1
  374. data/lib/hecks/rust_build/wasm.rb +1 -1
  375. data/lib/hecks/rust_build/wasm_browser.rb +1 -1
  376. data/lib/hecks/rust_build.rb +11 -0
  377. data/lib/hecks/served_options.rb +53 -0
  378. data/lib/hecks/site/bluebook/site.bluebook +1 -0
  379. data/lib/hecks/storehouse/audit.rb +1 -1
  380. data/lib/hecks/storehouse/dispatching.rb +8 -6
  381. data/lib/hecks/storehouse/introspection.rb +2 -2
  382. data/lib/hecks/storehouse/querying.rb +6 -4
  383. data/lib/hecks/storehouse.rb +4 -4
  384. data/lib/hecks/tools/ci_gate_decision.rb +35 -0
  385. data/lib/hecks/tools/ci_gates.rb +20 -1
  386. data/lib/hecks/tools/comment_style/source_file.rb +4 -1
  387. data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +4 -1
  388. data/lib/hecks/tools/lanes/files.rb +8 -0
  389. data/lib/hecks/tools/lanes/prebuilt_workflow.rb +81 -0
  390. data/lib/hecks/tools/lanes/rows.rb +6 -0
  391. data/lib/hecks/tools/lanes.rb +1 -0
  392. data/lib/hecks/tools/oidc_manifests.rb +4 -5
  393. data/lib/hecks/tools/regeneration_run.rb +11 -4
  394. data/lib/hecks/tools/release_only.rb +111 -0
  395. data/lib/hecks/tools/site_routes/editor_files.rb +12 -5
  396. data/lib/hecks/tools/tenant_provisioning.rb +2 -2
  397. data/lib/hecks/tools/tools_doc.rb +4 -4
  398. data/lib/hecks/version.rb +1 -1
  399. data/lib/hecks/vocabulary.rb +19 -5
  400. data/lib/hecks.rb +15 -15
  401. data/lib/rubocop/cop/hecks/env_dump.rb +69 -0
  402. data/lib/rubocop/cop/hecks/monotonic_duration.rb +53 -0
  403. data/lib/rubocop/cop/hecks/no_wall_clock_in_runtime.rb +34 -0
  404. data/lib/rubocop/cop/hecks/raw_shell_interpolation.rb +79 -0
  405. data/lib/rubocop/cop/hecks/sleep_in_spec.rb +34 -0
  406. data/lib/rubocop/cop/hecks/swallowed_rescue.rb +37 -0
  407. data/lib/rubocop/cop/hecks/symbol_string_key_mix.rb +50 -0
  408. data/lib/rubocop/cop/hecks/untyped_raise.rb +37 -0
  409. data/prebuilt/9ee54e2f0d4d6d3fe046827779b221e02de665fba911a048ae5dd8b8fbfb7d90.marshal +0 -0
  410. data/prebuilt/d74a00e996222c1d8b676d5959092effc46b4dc59e0555f3bf9554ac58e3094a.marshal +0 -0
  411. data/prebuilt/verdicts-df19f35868d3284a70f55e42dcef87ef4e65e6a678e5135f72e1f6cff4c1357b.json +1 -0
  412. data/rust/.gitignore +6 -0
  413. data/rust/Cargo.toml +6 -0
  414. data/rust/build/src/binding_pass.rs +103 -0
  415. data/rust/build/src/cargo_sync.rs +6 -0
  416. data/rust/build/src/lineage_pass.rs +3 -3
  417. data/rust/build/src/main.rs +17 -5
  418. data/rust/build/src/pipeline.rs +48 -5
  419. data/rust/build/src/seams_pass.rs +123 -0
  420. data/rust/build/src/translation_pass.rs +167 -0
  421. data/rust/codegen/src/asks.rs +103 -0
  422. data/rust/codegen/src/commands.rs +13 -8
  423. data/rust/codegen/src/constraints.rs +25 -0
  424. data/rust/codegen/src/domain_generator.rs +2 -0
  425. data/rust/codegen/src/expr_emitter.rs +13 -0
  426. data/rust/codegen/src/json_codec.rs +54 -15
  427. data/rust/codegen/src/main.rs +1 -0
  428. data/rust/codegen/src/mutations.rs +128 -3
  429. data/rust/codegen/src/queries.rs +11 -2
  430. data/rust/codegen/src/reactions.rs +1 -1
  431. data/rust/codegen/src/types.rs +166 -8
  432. data/rust/host/HECKS_RELEASE +1 -1
  433. data/rust/host/src/api.rs +4 -4
  434. data/rust/host/src/bin/validate_harness.rs +43 -0
  435. data/rust/host/src/boundary_fuzz/expr_json.rs +2 -2
  436. data/rust/host/src/boundary_fuzz/ir.rs +1 -0
  437. data/rust/host/src/boundary_fuzz/server.rs +1 -1
  438. data/rust/host/src/checkout.rs +35 -19
  439. data/rust/host/src/commerce_ir.rs +408 -2
  440. data/rust/host/src/expr_json.rs +118 -0
  441. data/rust/host/src/extension.rs +13 -1
  442. data/rust/host/src/lib.rs +1 -0
  443. data/rust/host/src/mint.rs +1 -1
  444. data/rust/host/src/payments/tests.rs +27 -1
  445. data/rust/host/src/payments.rs +12 -4
  446. data/rust/host/src/platform_signin/attack_tests.rs +1100 -0
  447. data/rust/host/src/platform_signin/config.rs +512 -0
  448. data/rust/host/src/platform_signin/cookie.rs +237 -0
  449. data/rust/host/src/platform_signin/extension.rs +190 -0
  450. data/rust/host/src/platform_signin/fetcher.rs +322 -0
  451. data/rust/host/src/platform_signin/handler.rs +251 -0
  452. data/rust/host/src/platform_signin/handler_tests.rs +554 -0
  453. data/rust/host/src/platform_signin/jti.rs +196 -0
  454. data/rust/host/src/platform_signin/jwks.rs +542 -0
  455. data/rust/host/src/platform_signin/jws.rs +249 -0
  456. data/rust/host/src/platform_signin/known_answer_tests.rs +237 -0
  457. data/rust/host/src/platform_signin/mapping_tests.rs +254 -0
  458. data/rust/host/src/platform_signin/mod.rs +75 -0
  459. data/rust/host/src/platform_signin/mode_tests.rs +382 -0
  460. data/rust/host/src/platform_signin/pages.rs +204 -0
  461. data/rust/host/src/platform_signin/query.rs +233 -0
  462. data/rust/host/src/platform_signin/session.rs +151 -0
  463. data/rust/host/src/platform_signin/settings.rs +385 -0
  464. data/rust/host/src/platform_signin/strict_json.rs +351 -0
  465. data/rust/host/src/platform_signin/test_support.rs +235 -0
  466. data/rust/host/src/platform_signin/ttl_tests.rs +94 -0
  467. data/rust/host/src/platform_signin/verify.rs +604 -0
  468. data/rust/host/src/platform_signin/wiring_support.rs +319 -0
  469. data/rust/host/src/query_step.rs +22 -4
  470. data/rust/host/src/reference_validate.rs +653 -108
  471. data/rust/host/src/resend.rs +2 -0
  472. data/rust/host/src/server.rs +19 -6
  473. data/rust/host/src/ui_schema.rs +132 -5
  474. data/rust/host/src/web/newsletter.rs +7 -8
  475. data/rust/host/src/web/newsletter_send.rs +38 -13
  476. data/rust/host/src/web/registrations.rs +229 -26
  477. data/rust/host/src/web.rs +53 -9
  478. data/rust/parser/src/emit.rs +38 -6
  479. data/rust/parser/src/expr/ast_json.rs +7 -1
  480. data/rust/parser/src/expr/resolver.rs +171 -6
  481. data/rust/parser/src/ir.rs +7 -0
  482. data/rust/parser/src/keywords.rs +24 -0
  483. data/rust/parser/src/main.rs +3 -0
  484. data/rust/parser/src/parse/aggregate.rs +2 -1
  485. data/rust/parser/src/parse/chapter.rs +2 -2
  486. data/rust/parser/src/parse/command.rs +2 -0
  487. data/rust/parser/src/parse/hecksagon.rs +5 -2
  488. data/rust/parser/src/parse/lifecycle.rs +81 -0
  489. data/rust/parser/src/parse/mod.rs +15 -1
  490. data/rust/parser/src/parse/policy.rs +13 -2
  491. data/rust/parser/src/parse/value_object.rs +34 -17
  492. data/rust/parser/src/ruby_value.rs +1 -1
  493. data/rust/src/exemplar/commands.rs +4 -4
  494. data/rust/src/exemplar/constraints.rs +1 -1
  495. data/rust/src/exemplar/json.rs +43 -1
  496. data/rust/src/kernel/cli.rs +7 -5
  497. data/rust/src/kernel/dispatch.rs +2 -2
  498. data/rust/src/kernel/dispatch_guard_tests.rs +1 -1
  499. data/rust/src/kernel/expr.rs +66 -3
  500. data/rust/src/kernel/expression_operators/arithmetic.rs +130 -5
  501. data/rust/src/kernel/expression_operators/text.rs +26 -2
  502. data/rust/src/kernel/json.rs +15 -0
  503. data/rust/src/kernel/mod.rs +1 -1
  504. data/rust/src/kernel/orchestrate.rs +54 -5
  505. data/rust/src/kernel/orchestrate_guard_tests.rs +67 -0
  506. data/rust/src/kernel/pattern.rs +60 -0
  507. data/rust/src/kernel/reference_lookup.rs +90 -7
  508. metadata +178 -38
  509. data/lib/hecks/doors/cli_door/nesting.rb +0 -34
  510. data/lib/hecks/doors/cli_door/shorthand.rb +0 -85
  511. data/lib/hecks/doors/cli_door.rb +0 -124
  512. data/lib/hecks/doors/cli_runner/answers.rb +0 -138
  513. data/lib/hecks/doors/cli_runner/resolution.rb +0 -146
  514. data/lib/hecks/doors/cli_runner/settling.rb +0 -115
  515. data/lib/hecks/doors/cli_runner/streaming.rb +0 -90
  516. data/lib/hecks/doors/cli_runner/suggestions.rb +0 -43
  517. data/lib/hecks/doors/cli_runner.rb +0 -66
  518. data/lib/hecks/doors/command_request.rb +0 -119
  519. data/lib/hecks/doors/handle.rb +0 -208
  520. data/lib/hecks/doors/json_door.rb +0 -132
  521. data/lib/hecks/doors/launcher_options.rb +0 -247
  522. data/lib/hecks/doors/mcp_door/properties.rb +0 -47
  523. data/lib/hecks/doors/mcp_door/tool_calls.rb +0 -113
  524. data/lib/hecks/doors/mcp_door/tool_definitions/auditing.rb +0 -51
  525. data/lib/hecks/doors/mcp_door/tool_definitions/calling.rb +0 -69
  526. data/lib/hecks/doors/mcp_door/tool_definitions/reading.rb +0 -97
  527. data/lib/hecks/doors/mcp_door/tool_definitions.rb +0 -15
  528. data/lib/hecks/doors/mcp_door.rb +0 -156
  529. data/lib/hecks/doors/mcp_door_scope/argument_policy.rb +0 -92
  530. data/lib/hecks/doors/mcp_door_scope/env_problems.rb +0 -55
  531. data/lib/hecks/doors/mcp_door_scope/presenter.rb +0 -78
  532. data/lib/hecks/doors/mcp_door_scope.rb +0 -201
  533. data/lib/hecks/doors/mcp_guide_cache.rb +0 -75
  534. data/lib/hecks/doors/ruby_door/aggregate_door.rb +0 -162
  535. data/lib/hecks/doors/ruby_door/chapter.rb +0 -89
  536. data/lib/hecks/doors/ruby_door.rb +0 -80
  537. data/lib/hecks/doors/usage_cache.rb +0 -139
  538. data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_widget.js.tmpl +0 -282
  539. data/lib/hecks/projections/site/cms_editor/templates/src/ui/media_picker.js.tmpl +0 -109
  540. data/prebuilt/c28d6d9db14eea3e17161e9d9c262efee30b256cf97693a7cfd552bce24b2f7f.marshal +0 -0
  541. data/prebuilt/cb375cba61ca0dbe8ddb9739c306510fe05e5f4b7a9b6955caff8224c63ea0e0.marshal +0 -0
  542. data/prebuilt/verdicts-d6d584ad43b5f3cebf88aafbbdbf024c38574acf18e06adffbc555f7786b1c2f.json +0 -1
@@ -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
@@ -0,0 +1,133 @@
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
+ # What the URL names, and nothing it leaves out: `postgres:///postgres` parses with an empty
123
+ # host, which would override a `PGHOST` the environment sets and send the restore to the
124
+ # local socket while the rest of the rehearsal reached the server by TCP.
125
+ def restore_env
126
+ uri = URI.parse(server_url)
127
+ { "PGHOST" => uri.host, "PGPORT" => uri.port&.to_s, "PGUSER" => uri.user, "PGPASSWORD" => uri.password }
128
+ .reject { |_name, value| value.to_s.empty? }
129
+ end
130
+ end
131
+ end
132
+ end
133
+ 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