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
@@ -16,6 +16,9 @@ module Hecks
16
16
  # it, a span with no complete day, or a failed `aws` call is refused with a reason, which the
17
17
  # runtime records on the check.
18
18
  class CostExplorer
19
+ # Raised when the budget is exceeded or Cost Explorer cannot be read.
20
+ class Error < RuntimeError; end
21
+
19
22
  # Days in an average month, for scaling a daily mean to a monthly rate.
20
23
  DAYS_IN_MONTH = 30.4375
21
24
 
@@ -35,17 +38,17 @@ module Hecks
35
38
  # first day to count (`YYYY-MM-DD`), each a value object or a plain value
36
39
  # @return [Hash{Symbol => Hash}] `report:` the rate against the budget, the shape
37
40
  # `CostCheck.Pass` takes
38
- # @raise [RuntimeError] when no complete day has passed since `since`, `aws` fails, or the
41
+ # @raise [Error] when no complete day has passed since `since`, `aws` fails, or the
39
42
  # rate is over the budget
40
43
  def measure(**held)
41
44
  budget = Integer(plain(held[:budget]))
42
45
  first = Date.iso8601(plain(held[:since]).to_s)
43
46
  last = today
44
- raise "no complete day since #{first}; today (#{last}) is still being billed" if first >= last
47
+ raise Error, "no complete day since #{first}; today (#{last}) is still being billed" if first >= last
45
48
 
46
49
  days = daily_costs(first, last)
47
50
  line = report_line(first, last, days, budget)
48
- raise "over budget: #{line}" if monthly_rate(days) > budget
51
+ raise Error, "over budget: #{line}" if monthly_rate(days) > budget
49
52
 
50
53
  { report: { value: line } }
51
54
  end
@@ -62,10 +65,10 @@ module Hecks
62
65
  "--granularity", "DAILY", "--metrics", "UnblendedCost",
63
66
  "--group-by", "Type=DIMENSION,Key=SERVICE", "--output", "json"
64
67
  )
65
- raise "aws ce get-cost-and-usage failed: #{err.strip.empty? ? out.strip : err.strip}" unless status.success?
68
+ raise Error, "aws ce get-cost-and-usage failed: #{err.strip.empty? ? out.strip : err.strip}" unless status.success?
66
69
 
67
70
  rows = JSON.parse(out).fetch("ResultsByTime")
68
- raise "Cost Explorer returned no days for #{first}..#{last}" if rows.empty?
71
+ raise Error, "Cost Explorer returned no days for #{first}..#{last}" if rows.empty?
69
72
 
70
73
  rows.to_h { |row| [row.dig("TimePeriod", "Start"), by_service(row)] }
71
74
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require_relative "../console_capture"
5
+ require_relative "../world_clearing"
6
+
7
+ module Hecks
8
+ module Adapters
9
+ class DeployToolchain
10
+ # The `ClearDeployment` ask: empties the `deployed_to` blocks of a domain's `.world` files, in
11
+ # place or into a directory, and says which files and settings it removed.
12
+ module Clearing
13
+ # Empties every `deployed_to` block under `<domain>/bluebook/**/*.world`, through
14
+ # `hecks deploy handover.clear`.
15
+ #
16
+ # With `out`, the cleared files are written there at their paths below `<domain>/bluebook/`
17
+ # and the originals are left alone; without it they are rewritten in place. A world with
18
+ # nothing left to clear is not rewritten and not listed.
19
+ #
20
+ # @param held [Hash] the `Handover` record: `domain`, and `out` when set
21
+ # @return [Hash{Symbol => Hash}] `output:` one line per file cleared with its settings
22
+ # @raise [ConsoleCapture::Failure] when the domain holds no `.world` file
23
+ def clear_deployment(**held)
24
+ bluebook = File.join(File.expand_path(plain(held[:domain])), "bluebook")
25
+ out = plain(held[:out])
26
+ lines = worlds_of(bluebook).filter_map { |path| clear_world(path, bluebook, out && File.expand_path(out)) }
27
+ { output: { value: lines.empty? ? "no deployed_to settings to clear" : lines.join("\n") } }
28
+ end
29
+
30
+ private
31
+
32
+ def worlds_of(bluebook)
33
+ worlds = Dir.glob(File.join(bluebook, "**", "*.world"))
34
+ raise ConsoleCapture::Failure, "#{bluebook} holds no .world file to clear" if worlds.empty?
35
+
36
+ worlds
37
+ end
38
+
39
+ def clear_world(path, bluebook, out)
40
+ cleared = WorldClearing.call(File.read(path))
41
+ return unless cleared.changed?
42
+
43
+ target = out ? File.join(out, path.delete_prefix("#{bluebook}/")) : path
44
+ FileUtils.mkdir_p(File.dirname(target))
45
+ File.write(target, cleared.text)
46
+ "#{target}: cleared #{cleared.settings.join(", ")}"
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -26,7 +26,8 @@ module Hecks
26
26
 
27
27
  # What each status of `deploy-box.sh` means, for the reason a refusal gives.
28
28
  BOX_STATUS = { 40 => "the box stack has no instance", 41 => "the roll did not succeed on the box",
29
- 42 => "the box is not healthy after the roll" }.freeze
29
+ 42 => "the box is not healthy after the roll",
30
+ 43 => "the database has no usable restore anchor (backups unreadable or kept too few days)" }.freeze
30
31
 
31
32
  # What each status of `deploy-service.sh` means; it ends with the box roll's own statuses.
32
33
  SERVICE_STATUS = { 2 => "unknown service", 30 => "the existing tag is not in ECR",
@@ -10,6 +10,7 @@ require_relative "deploy_plans"
10
10
  require_relative "deploy_toolchain/statuses"
11
11
  require_relative "deploy_toolchain/scripts"
12
12
  require_relative "deploy_toolchain/answers"
13
+ require_relative "deploy_toolchain/clearing"
13
14
 
14
15
  module Hecks
15
16
  module Adapters
@@ -26,6 +27,7 @@ module Hecks
26
27
  include Statuses
27
28
  include Scripts
28
29
  include Answers
30
+ include Clearing
29
31
 
30
32
  # Accepts the arguments every driven adapter is built with and keeps none of them.
31
33
  #
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "open3"
4
+ require_relative "../../indifferent_key"
4
5
 
5
6
  module Hecks
6
7
  module Adapters
@@ -12,6 +13,9 @@ module Hecks
12
13
  # and the runtime records the refusal on the finding. An ask that needs the issue before one
13
14
  # was opened is refused the same way, so a finding never blocks on GitHub.
14
15
  class FindingGithub
16
+ # Raised when `gh` cannot file or find the finding's issue.
17
+ class Error < RuntimeError; end
18
+
15
19
  # The environment variable that names the repository when no setting does.
16
20
  REPOSITORY_VARIABLE = "HECKS_FINDINGS_REPO"
17
21
 
@@ -27,12 +31,12 @@ module Hecks
27
31
  # @param held [Hash] the finding's fields; `title` and `body` are value objects or strings
28
32
  # @return [Hash{Symbol => Hash}] `issue_number:` of the new issue, the shape
29
33
  # `Finding.RecordIssue` takes
30
- # @raise [RuntimeError] when no repository is named, `gh` exits non-zero, or prints no
34
+ # @raise [Error] when no repository is named, `gh` exits non-zero, or prints no
31
35
  # issue URL
32
36
  def open_issue(**held)
33
37
  out = gh("issue", "create", "--title", plain(held[:title]).to_s, "--body", plain(held[:body]).to_s)
34
38
  number = out.lines.last.to_s.strip[%r{/issues/(\d+)\z}, 1]
35
- raise "gh issue create printed no issue URL: #{out.strip.inspect}" unless number
39
+ raise Error, "gh issue create printed no issue URL: #{out.strip.inspect}" unless number
36
40
 
37
41
  { issue_number: { value: number.to_i } }
38
42
  end
@@ -82,7 +86,10 @@ module Hecks
82
86
 
83
87
  def gh(*arguments)
84
88
  out, err, status = Open3.capture3("gh", *arguments, "--repo", repository)
85
- raise "gh #{arguments.first(2).join(" ")} failed — #{err.strip.empty? ? out.strip : err.strip}" unless status.success?
89
+ unless status.success?
90
+ raise Error,
91
+ "gh #{arguments.first(2).join(" ")} failed — #{err.strip.empty? ? out.strip : err.strip}"
92
+ end
86
93
 
87
94
  out
88
95
  end
@@ -91,12 +98,12 @@ module Hecks
91
98
  name = @repository || ENV.fetch(REPOSITORY_VARIABLE, nil)
92
99
  return name unless name.to_s.strip.empty?
93
100
 
94
- raise "no repository named: set the `repository` setting or #{REPOSITORY_VARIABLE} to owner/name"
101
+ raise Error, "no repository named: set the `repository` setting or #{REPOSITORY_VARIABLE} to owner/name"
95
102
  end
96
103
 
97
104
  def issue(held)
98
105
  number = plain(held[:issue_number])
99
- raise "the finding has no GitHub issue yet" if number.to_s.empty?
106
+ raise Error, "the finding has no GitHub issue yet" if number.to_s.empty?
100
107
 
101
108
  number.to_s
102
109
  end
@@ -107,7 +114,7 @@ module Hecks
107
114
  def plain(field)
108
115
  return field unless field.is_a?(Hash)
109
116
 
110
- field.key?(:value) ? field[:value] : field["value"]
117
+ IndifferentKey.read(field, :value)
111
118
  end
112
119
  end
113
120
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../../doors/json_door"
3
+ require_relative "../../adapters/driving/json"
4
4
 
5
5
  module Hecks
6
6
  module Adapters
@@ -87,7 +87,7 @@ module Hecks
87
87
  %i[admit grant people].to_h { |key| [key, provider.provided_verb(Bluebook::Capabilities::MEMBERSHIP, key)] }
88
88
  end
89
89
 
90
- def rows(verb) = @runtime.query(verb).map { |row| Doors::JsonDoor.materialize(row) }
90
+ def rows(verb) = @runtime.query(verb).map { |row| Adapters::Driving::Json.materialize(row) }
91
91
 
92
92
  # The role a verb's command declares, which the dispatch must assert to be let through.
93
93
  def gated_role(provider, verb)
@@ -12,6 +12,9 @@ module Hecks
12
12
  # ruleset says; it only carries a ruleset to and from GitHub. `{owner}/{repo}` is a `gh api`
13
13
  # template resolved from the git remote.
14
14
  class GithubRulesets
15
+ # Raised when `gh` is missing or refuses a ruleset call.
16
+ class Error < RuntimeError; end
17
+
15
18
  # The part of a ruleset a lane projects: everything else GitHub fills in (ids, links,
16
19
  # timestamps, defaults) is left out of a comparison.
17
20
  COMPARED = %w[name target enforcement].freeze
@@ -23,7 +26,7 @@ module Hecks
23
26
  end
24
27
 
25
28
  # @return [Array<Hash>] every ruleset of the repository, in full
26
- # @raise [RuntimeError] when `gh` is missing or refuses
29
+ # @raise [Error] when `gh` is missing or refuses
27
30
  def all
28
31
  listed = json(call("api", "repos/{owner}/{repo}/rulesets?per_page=100"))
29
32
  listed.map { |ruleset| json(call("api", "repos/{owner}/{repo}/rulesets/#{ruleset.fetch("id")}")) }
@@ -37,7 +40,7 @@ module Hecks
37
40
  #
38
41
  # @param projected [Hash] the ruleset as `Tools::Lanes` projects it
39
42
  # @return [Symbol] `:created` or `:updated`
40
- # @raise [RuntimeError] when GitHub refuses it
43
+ # @raise [Error] when GitHub refuses it
41
44
  def apply(projected)
42
45
  live = named(projected.fetch("name"))
43
46
  path = "repos/{owner}/{repo}/rulesets"
@@ -110,7 +113,7 @@ module Hecks
110
113
 
111
114
  def call(*args, stdin: nil)
112
115
  out, err, ok = @runner.call(args, stdin)
113
- raise "gh #{args.first(3).join(" ")} failed: #{err.strip.empty? ? out.strip : err.strip}" unless ok
116
+ raise Error, "gh #{args.first(3).join(" ")} failed: #{err.strip.empty? ? out.strip : err.strip}" unless ok
114
117
 
115
118
  out
116
119
  end
@@ -174,7 +174,7 @@ module Hecks
174
174
  raise Runtime::NotFound, "no domain here, and none named" unless path
175
175
  raise Runtime::NotFound, "no such domain #{path.inspect}" unless Dir.exist?(path)
176
176
 
177
- Hecks.boot(File.expand_path(path), install_doors: false)
177
+ Hecks.boot(File.expand_path(path), install_driving: false)
178
178
  end
179
179
 
180
180
  def chapter_of(domain, chapter)
@@ -23,7 +23,7 @@ module Hecks
23
23
  def initialize(path, aggregates:, kind:)
24
24
  raise Runtime::NotFound, "no such domain #{path.inspect}" unless Dir.exist?(path.to_s)
25
25
 
26
- @registry = Hecks.boot(File.expand_path(path), install_doors: false).registry
26
+ @registry = Hecks.boot(File.expand_path(path), install_driving: false).registry
27
27
  @wanted = aggregates
28
28
  @kind = kind
29
29
  end
@@ -12,17 +12,10 @@ module Hecks
12
12
  # It answers what the audit's three layers found, as the report `hecks audit_translation`
13
13
  # prints, and the edge itself, so approving it audits the very edge that was read.
14
14
  class EdgeAudit
15
- # What one audit found.
16
- #
17
- # @!attribute [r] edge
18
- # @return [Bluebook::Translation] the audited edge
19
- # @!attribute [r] leaving
20
- # @return [Integer] how many edges leave the latest held era
21
- # @!attribute [r] ok
22
- # @return [Boolean] whether no layer refused
23
- # @!attribute [r] report
24
- # @return [String] the report, one block per aggregate
25
- Finding = Struct.new(:edge, :leaving, :ok, :report, keyword_init: true)
15
+ # What one audit found: the audited `edge`, how many edges are `leaving` the latest held
16
+ # era, whether it is `ok`, the `report` text, every refusal (`Aggregate#id: wording`) and
17
+ # the records as translated (`after`), by aggregate name and id.
18
+ Finding = Struct.new(:edge, :leaving, :ok, :report, :refusals, :after, keyword_init: true)
26
19
 
27
20
  # Audits the edge that leaves the latest held era.
28
21
  #
@@ -89,14 +82,11 @@ module Hecks
89
82
  end
90
83
 
91
84
  def finding(chain, era, leaving, edge = chain.last[:translation])
92
- lines = []
93
- ok = true
94
- @held.bluebook.aggregates.each do |aggregate|
95
- verdict, records = verdict_for(aggregate, edge, era, chain)
96
- lines.concat(describe(aggregate, edge, verdict, records))
97
- ok &&= verdict.ok?
98
- end
99
- Finding.new(edge: edge, leaving: leaving, ok: ok, report: lines.join("\n"))
85
+ audits = @held.bluebook.aggregates.map { |aggregate| [aggregate, *verdict_for(aggregate, edge, era, chain)] }
86
+ lines = audits.flat_map { |aggregate, verdict, records, _| describe(aggregate, edge, verdict, records) }
87
+ refusals = audits.flat_map { |_, verdict, _, _| verdict.violations }
88
+ Finding.new(edge: edge, leaving: leaving, ok: refusals.empty?, report: lines.join("\n"),
89
+ refusals: refusals, after: audits.to_h { |aggregate, _, _, after| [aggregate.name, after] })
100
90
  end
101
91
 
102
92
  def verdict_for(aggregate, edge, era, chain)
@@ -108,7 +98,7 @@ module Hecks
108
98
  end
109
99
  verdict = Translation::Audit.check(aggregate: aggregate, declared: edge.for_aggregate(aggregate.name),
110
100
  before: before, after: after)
111
- [verdict, after.size]
101
+ [verdict, after.size, after]
112
102
  end
113
103
 
114
104
  def translated(aggregate, era, chain)
@@ -22,16 +22,18 @@ module Hecks
22
22
  #
23
23
  # @param path [String] the domain directory, or the project holding its `bluebook/`
24
24
  # @param mode [Symbol] one of `MODES`
25
+ # @param environment [String, nil] the overlay under `environments/` to load last;
26
+ # `HECKS_PROJECT_ENVIRONMENT` when not given
25
27
  # @return [HeldDomain] the loaded domain
26
28
  # @raise [Runtime::NotFound] if the directory does not exist, declares no bluebook, or
27
29
  # does not load
28
- def self.open(path, mode: :translated)
30
+ def self.open(path, mode: :translated, environment: ENV.fetch("HECKS_PROJECT_ENVIRONMENT", nil))
29
31
  raise ArgumentError, "unknown mode #{mode.inspect}" unless MODES.include?(mode)
30
32
 
31
33
  loading = Ports::Loading.bootstrap
32
34
  directory = loading.bluebook_directory(path)
33
35
  registry = Runtime::Registry.new(root: File.dirname(directory))
34
- load_into(registry, loading, directory, mode)
36
+ load_into(registry, loading, directory, mode, environment)
35
37
  new(registry, directory)
36
38
  rescue Errno::ENOENT
37
39
  raise Runtime::NotFound, "no such domain #{path.inspect}"
@@ -40,8 +42,7 @@ module Hecks
40
42
  end
41
43
 
42
44
  # @api private
43
- def self.load_into(registry, loading, directory, mode)
44
- environment = ENV.fetch("HECKS_PROJECT_ENVIRONMENT", nil)
45
+ def self.load_into(registry, loading, directory, mode, environment)
45
46
  Hecks.with_registry(registry) do
46
47
  loading.load_library
47
48
  loading.load_project(loading.shared_root(nil, directory))
@@ -91,7 +92,29 @@ module Hecks
91
92
  # @return [Hash] the settings
92
93
  def settings
93
94
  declared = registry.binding_settings(bluebook.name, Ports::Persistence::VERB, adapter_name)
94
- ENV["HECKS_SCHEMA"] ? declared.merge(schema: ENV["HECKS_SCHEMA"]) : declared
95
+ declared = declared.merge(schema: ENV["HECKS_SCHEMA"]) if ENV["HECKS_SCHEMA"]
96
+ declared = declared.merge(schema: @schema) if @schema
97
+ @database ? declared.merge(database: @database) : declared
98
+ end
99
+
100
+ # Names the schema its journal store is read in, over the world's and `HECKS_SCHEMA`, so a
101
+ # rehearsal reads the schema it reports.
102
+ #
103
+ # @param schema [String] a schema name
104
+ # @return [HeldDomain] self
105
+ def read_schema(schema)
106
+ @schema = schema
107
+ self
108
+ end
109
+
110
+ # Points the journal store at another database than its world names, so a rehearsal
111
+ # reads a copy and never the world's own. Every read and write after this reaches it.
112
+ #
113
+ # @param database [String] a `postgres://` URL
114
+ # @return [HeldDomain] self
115
+ def read_from(database)
116
+ @database = database
117
+ self
95
118
  end
96
119
 
97
120
  # The text the era is frozen from: the bluebook source as it stands on disk.
@@ -23,7 +23,7 @@ module Hecks
23
23
  # named ones, through `Hecks::CLI::ProjectCli`. A launcher is a generated file that only
24
24
  # points at its domain, so an existing one is replaced and nothing else is touched.
25
25
  #
26
- # @param held [Hash] the `Door` record: `domains` (comma separated paths under the current
26
+ # @param held [Hash] the `Launch` record: `domains` (comma separated paths under the current
27
27
  # directory; every domain found when absent)
28
28
  # @return [Hash{Symbol => Hash}] `output:` one line per launcher written
29
29
  # @raise [ConsoleCapture::Failure] when no launcher was written
@@ -45,7 +45,7 @@ module Hecks
45
45
  # Nothing is replaced: every file is checked before the first is written, and a directory
46
46
  # that already holds a bluebook is refused.
47
47
  #
48
- # @param held [Hash] the `Door` record: `name`, and optionally `adapter` and `dir`
48
+ # @param held [Hash] the `Launch` record: `name`, and optionally `adapter` and `dir`
49
49
  # @return [Hash{Symbol => Hash}] `output:` the report that was printed
50
50
  # @raise [ConsoleCapture::Failure] when the name or adapter is refused, or a file exists
51
51
  def scaffold(**held)
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hecks
4
+ module Adapters
5
+ module Rehearsal
6
+ # What the mint's audit read and what it concluded, as the facts the report holds: how much
7
+ # was examined by each engine, whether anything was, why not when nothing was, and how the
8
+ # two engines stood against each other.
9
+ class AuditReport
10
+ # The limit a clean verdict carries when no stored row was read.
11
+ UNAUDITED = "the stored rows"
12
+
13
+ # The wording that explains a Rust-only refusal: the Ruby audit's first layer does not hold
14
+ # a stored value to a closed set, a required field or an undeclared value-object key.
15
+ RUST_ONLY = "The Rust audit also holds a stored value to its closed set, a required field and an " \
16
+ "undeclared value-object key, which the Ruby audit's first layer does not yet: a row " \
17
+ "only Rust refuses is that known difference between the engines, not a second finding."
18
+
19
+ # @param minting [String] none, first or next
20
+ # @param edge [Bluebook::Translation, nil] the edge the audit ran through
21
+ # @param states [Hash{String => Hash}] the translated records the audit read, by aggregate
22
+ # @param ruby [Array<String>] the Ruby audit's refusals
23
+ # @param rust [RustAudit::Result] the second engine's answer
24
+ def initialize(minting:, edge:, states:, ruby:, rust:)
25
+ @minting = minting
26
+ @edge = edge
27
+ @states = states
28
+ @ruby = ruby
29
+ @rust = rust
30
+ end
31
+
32
+ # @return [Boolean] whether any stored row was read by the audit
33
+ def audited? = !@edge.nil? && @minting == "next"
34
+
35
+ # @return [Integer] the rows the Ruby audit read
36
+ def rows = @states.values.sum(&:size)
37
+
38
+ # @return [Hash] `audited`, `rows_examined`, `aggregates_examined`, `rust_rows_examined`
39
+ # (zero when the second engine did not run, which `parity` says) and the sentence the
40
+ # verdict rests on
41
+ def facts
42
+ { audited: flag(audited?), rows_examined: count(rows), reason: { value: reason },
43
+ aggregates_examined: count(aggregates),
44
+ rust_rows_examined: count(@rust.rows) }
45
+ end
46
+
47
+ # @return [Hash] the Rust engine's standing against the Ruby one, with who refused what
48
+ def parity
49
+ return { engine: "rust", status: "not_run", note: @rust.note } unless @rust.ran
50
+ return { engine: "rust", status: "agree", note: agreed } if ruby_records == rust_records
51
+
52
+ { engine: "rust", status: "differ", note: differed }
53
+ end
54
+
55
+ # @return [Array<Hash>] the limit a rehearsal that read nothing carries
56
+ def limits = audited? ? [] : [{ what: UNAUDITED, why: reason }]
57
+
58
+ private
59
+
60
+ def flag(value) = { value: value }
61
+
62
+ def count(value) = { value: value }
63
+
64
+ def reason
65
+ case @minting
66
+ when "none" then "no mint is needed at this shape, so no stored row was examined"
67
+ when "first" then "no era is held, so the first mint holds the shape as it stands and no stored row was examined"
68
+ else next_reason
69
+ end
70
+ end
71
+
72
+ def next_reason
73
+ return "no edge leads the held era to this shape, so no stored row was examined" unless @edge
74
+
75
+ "the audit read #{rows} stored row(s) of #{aggregates} aggregate(s) through the edge " \
76
+ "#{@edge.from} -> #{@edge.to}"
77
+ end
78
+
79
+ def aggregates = @states.count { |_, held| held.any? }
80
+
81
+ def ruby_records = records(@ruby)
82
+
83
+ def rust_records = records(@rust.violations)
84
+
85
+ def records(refusals) = refusals.map { |text| text[/\A[^:]+/] }.uniq.sort
86
+
87
+ def agreed = "ruby refused #{ruby_records.size} records, rust #{rust_records.size}; rust read #{@rust.rows} rows"
88
+
89
+ def differed
90
+ rust_only = rust_records - ruby_records
91
+ ruby_only = ruby_records - rust_records
92
+ "the engines refused different records: ruby #{ruby_records.size}, rust #{rust_records.size}. " \
93
+ "#{only("rust", rust_only)}#{only("ruby", ruby_only)}#{known_gap(rust_only)}" \
94
+ "Both are held against the report: the union is refused."
95
+ end
96
+
97
+ def known_gap(rust_only) = rust_only.empty? ? "" : "#{RUST_ONLY} "
98
+
99
+ def only(engine, held)
100
+ return "" if held.empty?
101
+
102
+ "Only #{engine} refused: #{held.first(5).join(", ")}#{", ..." if held.size > 5}. "
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hecks
4
+ module Adapters
5
+ module Rehearsal
6
+ # What a data copy holds for the domain, counted before any audit runs: the eras, the rows
7
+ # each aggregate's head holds, and the entries and live records of the journal.
8
+ #
9
+ # The mint's audit reads the journal (the latest entry of each record), never the head, so
10
+ # the two counts sit side by side and a divergence between them is visible, not refused.
11
+ Census = Struct.new(:schema, :source, :present, :eras, :journal_entries, :counts, :elsewhere,
12
+ keyword_init: true) do
13
+ # One aggregate's rows: what its head holds and what the journal's latest entries hold.
14
+ const_set(:Count, Struct.new(:aggregate, :head, :journal, keyword_init: true))
15
+
16
+ # @return [Array<String>] other schemas of the copy that hold a lineage
17
+ def elsewhere = self[:elsewhere] || []
18
+
19
+ # @return [Integer] the rows the heads hold, over every aggregate
20
+ def head_rows = counts.sum(&:head)
21
+
22
+ # @return [Integer] the live records the journal's latest entries hold
23
+ def journal_rows = counts.sum(&:journal)
24
+
25
+ # @return [Boolean] whether the copy holds no row of the domain at all
26
+ def empty? = head_rows.zero? && journal_rows.zero? && journal_entries.zero?
27
+
28
+ # @param domain [String] the domain's name, for the sentence
29
+ # @return [String] what the copy holds, in words
30
+ def summary(domain)
31
+ "the copy holds #{eras} era(s), #{head_rows} head rows and #{journal_rows} journal records for " \
32
+ "#{domain} in schema #{schema}"
33
+ end
34
+
35
+ # @param domain [String] the domain's name, for the sentence
36
+ # @return [String] why a copy that holds no row cannot be rehearsed, and what to set
37
+ def refusal(domain)
38
+ held = eras.zero? ? "no era and no rows" : "#{eras} era(s) but no rows"
39
+ where = if source == "memory"
40
+ "in the `rows` document"
41
+ else
42
+ "in schema #{schema}#{" (not in the copy)" unless present}"
43
+ end
44
+ "the copy holds #{held} for domain #{domain} #{where}: #{remedy}#{hint}"
45
+ end
46
+
47
+ # @return [Hash] the report facts, as the `RehearsalRun` commands take them
48
+ def facts
49
+ { schema_used: { name: schema, source: source, present: present }, eras_held: { value: eras },
50
+ journal_entries: { value: journal_entries }, head_rows: { value: head_rows }, census: counts.map(&:to_h) }
51
+ end
52
+
53
+ private
54
+
55
+ def where
56
+ return "in the `rows` document" if source == "memory"
57
+
58
+ "in schema #{schema}#{" (not in the copy)" unless present}"
59
+ end
60
+
61
+ def remedy
62
+ return "name the document that holds the stored records in `rows`" if source == "memory"
63
+
64
+ "set the schema in the world file (`rehearsed_by` { schema \"...\" })"
65
+ end
66
+
67
+ def hint = elsewhere.empty? ? "" : "; schemas in this copy that hold a lineage: #{elsewhere.join(", ")}"
68
+ end
69
+ end
70
+ end
71
+ end