hecks 2.9.0 → 3.0.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 (600) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +51 -6
  3. data/lib/hecks/adapters/driven/heki/journal.rb +1 -1
  4. data/lib/hecks/adapters/driven/lambda/client.rb +1 -1
  5. data/lib/hecks/adapters/driven/lambda.rb +1 -1
  6. data/lib/hecks/adapters/driven/memory.rb +16 -3
  7. data/lib/hecks/adapters/driven/postgres/reconnect.rb +6 -2
  8. data/lib/hecks/adapters/driven/postgres/shared_connection.rb +180 -0
  9. data/lib/hecks/adapters/driven/postgres.rb +4 -2
  10. data/lib/hecks/adapters/driven/prism.rb +41 -8
  11. data/lib/hecks/adapters/driven/tenant_provisioner.rb +73 -16
  12. data/lib/hecks/behaviors/expectations.rb +1 -1
  13. data/lib/hecks/behaviors.rb +1 -1
  14. data/lib/hecks/bench/cli.rb +62 -0
  15. data/lib/hecks/bench/environment.rb +71 -0
  16. data/lib/hecks/bench/postgres_probe.rb +30 -0
  17. data/lib/hecks/bench/report.rb +76 -0
  18. data/lib/hecks/bench/ruby_runner.rb +93 -0
  19. data/lib/hecks/bench/run.rb +55 -0
  20. data/lib/hecks/bench/rust_runner.rb +101 -0
  21. data/lib/hecks/bench/stats.rb +46 -0
  22. data/lib/hecks/bench/suite.rb +98 -0
  23. data/lib/hecks/bench/workload.rb +89 -0
  24. data/lib/hecks/bench.rb +17 -0
  25. data/lib/hecks/bluebook/assembly/contracts.rb +2 -0
  26. data/lib/hecks/bluebook/attribute.rb +1 -1
  27. data/lib/hecks/bluebook/behaviour/aggregate.rb +30 -1
  28. data/lib/hecks/bluebook/behaviour/domain_port.rb +7 -0
  29. data/lib/hecks/bluebook/chapter.rb +6 -2
  30. data/lib/hecks/bluebook/domain_port.rb +36 -5
  31. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +10 -0
  32. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +8 -0
  33. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -1
  34. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +31 -3
  35. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +122 -3
  36. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +1 -1
  37. data/lib/hecks/bluebook/dsl/query_builder.rb +27 -1
  38. data/lib/hecks/bluebook/dsl/rule_reference.rb +1 -1
  39. data/lib/hecks/bluebook/dsl/world_builder.rb +29 -2
  40. data/lib/hecks/bluebook/expression/canonical_form.rb +1 -1
  41. data/lib/hecks/bluebook/expression/resolver.rb +3 -2
  42. data/lib/hecks/bluebook/hexagon.rb +13 -5
  43. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +1 -0
  44. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +4 -1
  45. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +246 -0
  46. data/lib/hecks/bluebook/meta_validator.rb +9 -4
  47. data/lib/hecks/bluebook/model_check.rb +23 -0
  48. data/lib/hecks/bluebook/pattern_subset.rb +38 -0
  49. data/lib/hecks/bluebook/query.rb +20 -5
  50. data/lib/hecks/bluebook/smoke_test.rb +1 -1
  51. data/lib/hecks/canonical_json.rb +30 -0
  52. data/lib/hecks/chapters.rb +102 -0
  53. data/lib/hecks/cli/behaviors.rb +61 -0
  54. data/lib/hecks/cli/canonicalise.rb +25 -0
  55. data/lib/hecks/cli/check_engine_agreement.rb +32 -0
  56. data/lib/hecks/cli/check_era.rb +88 -0
  57. data/lib/hecks/cli/console.rb +66 -0
  58. data/lib/hecks/cli/deploy_template_diff.rb +40 -0
  59. data/lib/hecks/cli/doc_coverage.rb +32 -0
  60. data/lib/hecks/cli/document.rb +2 -2
  61. data/lib/hecks/cli/expression_projection.rb +41 -0
  62. data/lib/hecks/cli/follow.rb +88 -0
  63. data/lib/hecks/cli/history.rb +44 -0
  64. data/lib/hecks/cli/ir.rb +1 -1
  65. data/lib/hecks/cli/mcp.rb +3 -3
  66. data/lib/hecks/cli/model_check.rb +7 -4
  67. data/lib/hecks/cli/pattern_cases.rb +47 -0
  68. data/lib/hecks/cli/present.rb +39 -0
  69. data/lib/hecks/cli/project_cli.rb +224 -32
  70. data/lib/hecks/cli/project_diagrams.rb +4 -3
  71. data/lib/hecks/cli/project_glossary.rb +44 -0
  72. data/lib/hecks/cli/reference.rb +46 -0
  73. data/lib/hecks/cli/refresh_projections.rb +44 -0
  74. data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +150 -0
  75. data/lib/hecks/cli/release.rb +70 -0
  76. data/lib/hecks/cli/release_gem.rb +59 -0
  77. data/lib/hecks/cli/rspec_io_parallel_files.rb +137 -0
  78. data/lib/hecks/cli/rspec_shard_files.rb +66 -0
  79. data/lib/hecks/cli/run.rb +3 -3
  80. data/lib/hecks/cli/seed_semantics_corpus.rb +71 -0
  81. data/lib/hecks/cli/serve_query_ir_mcp.rb +30 -0
  82. data/lib/hecks/cli/shape.rb +70 -0
  83. data/lib/hecks/cli/smoke_http.rb +230 -0
  84. data/lib/hecks/cli/smoke_test.rb +5 -3
  85. data/lib/hecks/cli/spec_example.rb +27 -0
  86. data/lib/hecks/cli/statements.rb +32 -0
  87. data/lib/hecks/cli/stores.rb +4 -5
  88. data/lib/hecks/cli/stress_concurrency_specs.rb +177 -0
  89. data/lib/hecks/cli.rb +201 -52
  90. data/lib/hecks/codemod.rb +324 -0
  91. data/lib/hecks/corpus.rb +423 -0
  92. data/lib/hecks/deploy/bluebook/deploy.bluebook +591 -23
  93. data/lib/hecks/deploy/bluebook/deploy.hecksagon +41 -4
  94. data/lib/hecks/deploy/oidc.json +82 -2
  95. data/lib/hecks/doc/reference.rb +351 -0
  96. data/lib/hecks/doors/cli_door.rb +196 -0
  97. data/lib/hecks/doors/cli_runner.rb +297 -0
  98. data/lib/hecks/{facade → doors}/command_request.rb +1 -1
  99. data/lib/hecks/{facade → doors}/handle.rb +2 -2
  100. data/lib/hecks/{facade → doors}/json_door.rb +4 -4
  101. data/lib/hecks/doors/launcher_options.rb +129 -0
  102. data/lib/hecks/{cli → doors}/mcp_door.rb +7 -7
  103. data/lib/hecks/doors/mcp_door_scope.rb +97 -0
  104. data/lib/hecks/{facade/surface → doors/ruby_door}/aggregate_door.rb +8 -3
  105. data/lib/hecks/{facade/surface → doors/ruby_door}/chapter.rb +3 -3
  106. data/lib/hecks/{facade/surface.rb → doors/ruby_door.rb} +36 -5
  107. data/lib/hecks/doors.rb +61 -0
  108. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +1 -1
  109. data/lib/hecks/embryonaut_bluebook.rb +1 -1
  110. data/lib/hecks/engine_agreement.rb +156 -0
  111. data/lib/hecks/facade.rb +24 -37
  112. data/lib/hecks/forms/app.rb +6 -6
  113. data/lib/hecks/forms/banking_presentation.rb +50 -0
  114. data/lib/hecks/forms/field_shape.rb +2 -1
  115. data/lib/hecks/forms/port_argument.rb +1 -1
  116. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +393 -0
  117. data/lib/hecks/fuzzing/combination_miner.rb +86 -0
  118. data/lib/hecks/fuzzing/concurrent_dispatch.rb +214 -0
  119. data/lib/hecks/fuzzing/coverage_campaign.rb +143 -0
  120. data/lib/hecks/fuzzing/differential.rb +156 -0
  121. data/lib/hecks/fuzzing/domain_generator.rb +615 -0
  122. data/lib/hecks/fuzzing/era_boundary.rb +71 -0
  123. data/lib/hecks/fuzzing/form_census.rb +136 -0
  124. data/lib/hecks/fuzzing/generated_domain_check.rb +93 -0
  125. data/lib/hecks/fuzzing/invalid_value_generator.rb +64 -0
  126. data/lib/hecks/fuzzing/isolated_boot.rb +301 -0
  127. data/lib/hecks/fuzzing/nondeterministic.rb +51 -0
  128. data/lib/hecks/fuzzing/outside_world.rb +45 -0
  129. data/lib/hecks/fuzzing/persistence_parity.rb +102 -0
  130. data/lib/hecks/fuzzing/properties/corrections.rb +88 -0
  131. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +314 -0
  132. data/lib/hecks/fuzzing/properties/guards.rb +190 -0
  133. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +300 -0
  134. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +93 -0
  135. data/lib/hecks/fuzzing/properties/outbox.rb +89 -0
  136. data/lib/hecks/fuzzing/properties/querying.rb +175 -0
  137. data/lib/hecks/fuzzing/properties.rb +162 -0
  138. data/lib/hecks/fuzzing/qa_settings.rb +123 -0
  139. data/lib/hecks/fuzzing/replay.rb +505 -0
  140. data/lib/hecks/fuzzing/rotation_priority.rb +48 -0
  141. data/lib/hecks/fuzzing/rust_gap_manifest.rb +108 -0
  142. data/lib/hecks/fuzzing/self_consistency.rb +339 -0
  143. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +385 -0
  144. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +132 -0
  145. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +95 -0
  146. data/lib/hecks/fuzzing/sequence_generator/picker.rb +77 -0
  147. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +109 -0
  148. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +237 -0
  149. data/lib/hecks/fuzzing/sequence_generator.rb +189 -0
  150. data/lib/hecks/fuzzing/shrinker.rb +166 -0
  151. data/lib/hecks/fuzzing/structural_skips.rb +33 -0
  152. data/lib/hecks/fuzzing/sweep_depth.rb +43 -0
  153. data/lib/hecks/fuzzing/target_capabilities.rb +163 -0
  154. data/lib/hecks/fuzzing/value_generator.rb +216 -0
  155. data/lib/hecks/fuzzing.rb +23 -0
  156. data/lib/hecks/grammar/evolve.rb +336 -0
  157. data/lib/hecks/hecks/adapters/codebase/codemods.rb +45 -0
  158. data/lib/hecks/hecks/adapters/codebase/conformance.rb +71 -0
  159. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +202 -0
  160. data/lib/hecks/hecks/adapters/codebase/gem_registry.rb +80 -0
  161. data/lib/hecks/hecks/adapters/codebase/kernel_tables.rb +54 -0
  162. data/lib/hecks/hecks/adapters/codebase/language.rb +240 -0
  163. data/lib/hecks/hecks/adapters/codebase/npm_registry.rb +102 -0
  164. data/lib/hecks/hecks/adapters/codebase/publishing.rb +142 -0
  165. data/lib/hecks/hecks/adapters/codebase/regeneration.rb +67 -0
  166. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +90 -0
  167. data/lib/hecks/hecks/adapters/codebase/ruby_child.rb +83 -0
  168. data/lib/hecks/hecks/adapters/codebase/secret_vault.rb +36 -0
  169. data/lib/hecks/hecks/adapters/codebase/source_tree.adapter +7 -0
  170. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +189 -0
  171. data/lib/hecks/hecks/adapters/codebase/sqlite_fixture.rb +71 -0
  172. data/lib/hecks/hecks/adapters/codebase/style.rb +171 -0
  173. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +55 -0
  174. data/lib/hecks/hecks/adapters/codebase/test_suite.rb +165 -0
  175. data/lib/hecks/hecks/adapters/codebase/tree.rb +111 -0
  176. data/lib/hecks/hecks/adapters/console_capture.rb +79 -0
  177. data/lib/hecks/hecks/adapters/deploy_toolchain.adapter +7 -0
  178. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +108 -0
  179. data/lib/hecks/hecks/adapters/git.adapter +7 -0
  180. data/lib/hecks/hecks/adapters/git.rb +102 -0
  181. data/lib/hecks/hecks/adapters/host_http.adapter +7 -0
  182. data/lib/hecks/hecks/adapters/host_http.rb +65 -0
  183. data/lib/hecks/hecks/adapters/in_process_boot.adapter +7 -0
  184. data/lib/hecks/hecks/adapters/in_process_boot.rb +197 -0
  185. data/lib/hecks/hecks/adapters/in_process_operations.rb +202 -0
  186. data/lib/hecks/hecks/adapters/journal_store/changes.rb +143 -0
  187. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +145 -0
  188. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +132 -0
  189. data/lib/hecks/hecks/adapters/journal_store/examination.rb +135 -0
  190. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +163 -0
  191. data/lib/hecks/hecks/adapters/journal_store/readings.rb +187 -0
  192. data/lib/hecks/hecks/adapters/journal_store.adapter +7 -0
  193. data/lib/hecks/hecks/adapters/journal_store.rb +64 -0
  194. data/lib/hecks/hecks/adapters/local_files.adapter +7 -0
  195. data/lib/hecks/hecks/adapters/local_files.rb +46 -0
  196. data/lib/hecks/hecks/adapters/pg_admin.adapter +7 -0
  197. data/lib/hecks/hecks/adapters/pg_admin.rb +245 -0
  198. data/lib/hecks/hecks/adapters/process_pool.adapter +7 -0
  199. data/lib/hecks/hecks/adapters/process_pool.rb +119 -0
  200. data/lib/hecks/hecks/adapters/rust_toolchain.adapter +7 -0
  201. data/lib/hecks/hecks/adapters/rust_toolchain.rb +179 -0
  202. data/lib/hecks/hecks/adapters/rust_workspace.rb +150 -0
  203. data/lib/hecks/hecks/adapters/shell.adapter +7 -0
  204. data/lib/hecks/hecks/adapters/shell.rb +49 -0
  205. data/lib/hecks/hecks/adapters/terminal.adapter +7 -0
  206. data/lib/hecks/hecks/adapters/terminal.rb +74 -0
  207. data/lib/hecks/hecks/codebase.behaviors +172 -0
  208. data/lib/hecks/hecks/codebase.bluebook +2453 -0
  209. data/lib/hecks/hecks/context_map.hecksagon +13 -0
  210. data/lib/hecks/hecks/custodian.behaviors +179 -0
  211. data/lib/hecks/hecks/custodian.bluebook +1847 -0
  212. data/lib/hecks/hecks/deploy.behaviors +74 -0
  213. data/lib/hecks/hecks/deploy.world +12 -0
  214. data/lib/hecks/hecks/environments/memory.world +10 -0
  215. data/lib/hecks/hecks/governance.world +7 -0
  216. data/lib/hecks/hecks/hecks.behaviors +62 -0
  217. data/lib/hecks/hecks/hecks.bluebook +124 -0
  218. data/lib/hecks/hecks/hecks.hecksagon +390 -0
  219. data/lib/hecks/hecks/hecks.world +42 -0
  220. data/lib/hecks/kernel_capabilities.rb +194 -0
  221. data/lib/hecks/language/bluebook/bluebook.bluebook +11 -0
  222. data/lib/hecks/language/bluebook/query.bluebook +7 -0
  223. data/lib/hecks/language/bluebook/vocabulary.bluebook +4 -4
  224. data/lib/hecks/language/hecksagon/domain_port.bluebook +10 -0
  225. data/lib/hecks/language/hecksagon/hecksagon.bluebook +20 -0
  226. data/lib/hecks/mcp_stdio_guard.rb +2 -2
  227. data/lib/hecks/persistence_legacy_fixture/regenerate.rb +161 -0
  228. data/lib/hecks/persistence_legacy_fixture.rb +113 -0
  229. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +8 -5
  230. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +4 -1
  231. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +15 -2
  232. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +1 -1
  233. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +1 -1
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +1 -1
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +32 -15
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +6 -3
  237. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +227 -0
  238. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +1 -1
  239. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +43 -24
  240. data/lib/hecks/ports/persistence/plugins/era/translation.rb +1 -0
  241. data/lib/hecks/ports/persistence/state_codec.rb +24 -5
  242. data/lib/hecks/projection_files.rb +285 -0
  243. data/lib/hecks/projections/bootstrap_table.rb +1 -1
  244. data/lib/hecks/projections/deploy/fargate.rb +5 -5
  245. data/lib/hecks/projections/deploy/lambda.rb +43 -32
  246. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  247. data/lib/hecks/projections/deploy/preview/template.rb +1 -1
  248. data/lib/hecks/projections/deploy/scripts/deploy-service.sh.tmpl +1 -1
  249. data/lib/hecks/projections/deploy/scripts/expected-era.header +1 -1
  250. data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +3 -3
  251. data/lib/hecks/projections/deploy/scripts/smoke-after-deploy.sh.tmpl +1 -1
  252. data/lib/hecks/projections/deploy/shared.rb +1 -1
  253. data/lib/hecks/projections/diagrams.rb +3 -3
  254. data/lib/hecks/projections/model/deviations.rb +1 -1
  255. data/lib/hecks/projections/parser_table.rb +4 -4
  256. data/lib/hecks/projections/reference.rb +2 -3
  257. data/lib/hecks/projections/rust_vocabulary.rb +1 -1
  258. data/lib/hecks/projector/cli_projector.rb +106 -37
  259. data/lib/hecks/projector.rb +1 -1
  260. data/lib/hecks/quality_control/adapters/agent.adapter +7 -0
  261. data/lib/hecks/quality_control/adapters/agent.rb +58 -0
  262. data/lib/hecks/quality_control/adapters/angle_tools.adapter +7 -0
  263. data/lib/hecks/quality_control/adapters/angle_tools.rb +15 -0
  264. data/lib/hecks/quality_control/adapters/clearance_recorder.rb +36 -0
  265. data/lib/hecks/quality_control/adapters/clearance_tools.adapter +7 -0
  266. data/lib/hecks/quality_control/adapters/clearance_tools.rb +17 -0
  267. data/lib/hecks/quality_control/adapters/git_pr.adapter +7 -0
  268. data/lib/hecks/quality_control/adapters/git_pr.rb +226 -0
  269. data/lib/hecks/quality_control/adapters/github_checks.adapter +9 -0
  270. data/lib/hecks/quality_control/adapters/github_checks.rb +83 -0
  271. data/lib/hecks/quality_control/adapters/github_ci_webhook.rb +56 -0
  272. data/lib/hecks/quality_control/adapters/github_issues.adapter +7 -0
  273. data/lib/hecks/quality_control/adapters/github_issues.rb +49 -0
  274. data/lib/hecks/quality_control/adapters/qa_tool.rb +59 -0
  275. data/lib/hecks/quality_control/adapters/sweep_tools.adapter +7 -0
  276. data/lib/hecks/quality_control/adapters/sweep_tools.rb +51 -0
  277. data/lib/hecks/quality_control/adapters/target_tools.adapter +7 -0
  278. data/lib/hecks/quality_control/adapters/target_tools.rb +43 -0
  279. data/lib/hecks/quality_control/cli/child.rb +45 -0
  280. data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +45 -0
  281. data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +248 -0
  282. data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +163 -0
  283. data/lib/hecks/quality_control/cli/qa_generated_domains.rb +450 -0
  284. data/lib/hecks/quality_control/cli/qa_log_bug.rb +195 -0
  285. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +220 -0
  286. data/lib/hecks/quality_control/cli/qa_open_pr.rb +305 -0
  287. data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +154 -0
  288. data/lib/hecks/quality_control/cli/qa_postgres_role.rb +159 -0
  289. data/lib/hecks/quality_control/cli/qa_pr_check.rb +232 -0
  290. data/lib/hecks/quality_control/cli/qa_seed_angles.rb +142 -0
  291. data/lib/hecks/quality_control/cli/qa_seed_targets.rb +69 -0
  292. data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +280 -0
  293. data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +329 -0
  294. data/lib/hecks/quality_control/cli/qa_sweep/dials.rb +92 -0
  295. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +117 -0
  296. data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +175 -0
  297. data/lib/hecks/quality_control/cli/qa_sweep/release_mode.rb +60 -0
  298. data/lib/hecks/quality_control/cli/qa_sweep.rb +611 -0
  299. data/lib/hecks/quality_control/cli/qa_tick.rb +274 -0
  300. data/lib/hecks/quality_control/quality_control.bluebook +2224 -0
  301. data/lib/hecks/quality_control/quality_control.hecksagon +13 -0
  302. data/lib/hecks/quality_control/quality_control.ports.hecksagon +54 -0
  303. data/lib/hecks/query_ir.rb +346 -0
  304. data/lib/hecks/query_ir_mcp.rb +198 -0
  305. data/lib/hecks/release/runner/ci_publisher.rb +2 -2
  306. data/lib/hecks/release/runner/clean_tree.rb +42 -0
  307. data/lib/hecks/release/runner/console.rb +6 -0
  308. data/lib/hecks/release/runner/gem_publisher.rb +14 -7
  309. data/lib/hecks/release/runner/npm_publisher.rb +6 -36
  310. data/lib/hecks/release/runner/preflight.rb +26 -2
  311. data/lib/hecks/release/runner/published.rb +8 -26
  312. data/lib/hecks/release/runner/tagger.rb +30 -15
  313. data/lib/hecks/release/runner.rb +57 -6
  314. data/lib/hecks/router/namespace_installer.rb +3 -3
  315. data/lib/hecks/runtime/adapter_lookup.rb +124 -0
  316. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +4 -0
  317. data/lib/hecks/runtime/dependency_planning.rb +20 -2
  318. data/lib/hecks/runtime/dispatcher.rb +53 -3
  319. data/lib/hecks/runtime/interpreting.rb +3 -0
  320. data/lib/hecks/runtime/loader.rb +65 -12
  321. data/lib/hecks/runtime/outbox.rb +1 -1
  322. data/lib/hecks/runtime/policy_interpreter.rb +5 -2
  323. data/lib/hecks/runtime/port_operation_interpreter.rb +2 -10
  324. data/lib/hecks/runtime/query_interpreter.rb +77 -0
  325. data/lib/hecks/runtime/reaction_outcome.rb +89 -0
  326. data/lib/hecks/runtime/refusal_wording.rb +23 -1
  327. data/lib/hecks/runtime/registry/verification.rb +163 -4
  328. data/lib/hecks/runtime/registry.rb +57 -3
  329. data/lib/hecks/runtime/remote_dispatcher.rb +31 -2
  330. data/lib/hecks/runtime/value/coercion.rb +23 -135
  331. data/lib/hecks/runtime/value/field_checks.rb +172 -0
  332. data/lib/hecks/runtime/value.rb +4 -1
  333. data/lib/hecks/runtime.rb +20 -6
  334. data/lib/hecks/rust_build/conformance.rb +158 -0
  335. data/lib/hecks/rust_build/conformance_fuzz.rb +71 -0
  336. data/lib/hecks/rust_build/coverage.rb +218 -0
  337. data/lib/hecks/rust_build/native_build.rb +124 -0
  338. data/lib/hecks/rust_build/project_rust/root_mod.rb +120 -0
  339. data/lib/hecks/rust_build/project_rust.rb +332 -0
  340. data/lib/hecks/rust_build/wasm.rb +105 -0
  341. data/lib/hecks/rust_build/wasm_browser.rb +113 -0
  342. data/lib/hecks/rust_build.rb +144 -0
  343. data/lib/hecks/storehouse.rb +16 -16
  344. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +2 -2
  345. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +1 -1
  346. data/lib/hecks/three_zero/forms.yml +127 -0
  347. data/lib/hecks/three_zero.rb +12 -0
  348. data/lib/hecks/tools/argument_gate_matrix.rb +316 -0
  349. data/lib/hecks/tools/comment_style.rb +1070 -0
  350. data/lib/hecks/tools/corpus_report.rb +87 -0
  351. data/lib/hecks/tools/deploy_recipe.rb +255 -0
  352. data/lib/hecks/tools/deploy_recipe_lint.rb +282 -0
  353. data/lib/hecks/tools/drop_implicit_append_fields.rb +95 -0
  354. data/lib/hecks/tools/era_projection_backfill.rb +94 -0
  355. data/lib/hecks/tools/era_reattest.rb +146 -0
  356. data/lib/hecks/tools/evolve_run.rb +189 -0
  357. data/lib/hecks/tools/fuzz_sweep.rb +373 -0
  358. data/lib/hecks/tools/heki_compaction.rb +100 -0
  359. data/lib/hecks/tools/hoist_local_givens.rb +246 -0
  360. data/lib/hecks/tools/journal_compaction.rb +89 -0
  361. data/lib/hecks/tools/oidc_manifests.rb +70 -0
  362. data/lib/hecks/tools/query_ir_run.rb +81 -0
  363. data/lib/hecks/tools/regeneration_run.rb +166 -0
  364. data/lib/hecks/tools/rust_comment_style.rb +642 -0
  365. data/lib/hecks/tools/sequence_script.rb +62 -0
  366. data/lib/hecks/tools/tail_merge.rb +117 -0
  367. data/lib/hecks/tools/tenant_provisioning.rb +142 -0
  368. data/lib/hecks/tools/translation_audit.rb +208 -0
  369. data/lib/hecks/tools/translation_scaffold.rb +131 -0
  370. data/lib/hecks/tools.rb +86 -0
  371. data/lib/hecks/version.rb +1 -1
  372. data/lib/hecks.rb +32 -15
  373. data/qa/settings.yml +66 -0
  374. data/rust/.cargo/config.toml +6 -0
  375. data/rust/.gitignore +2 -0
  376. data/rust/Cargo.lock +54 -0
  377. data/rust/Cargo.toml +61 -0
  378. data/rust/build/.cargo/config.toml +3 -0
  379. data/rust/build/.gitignore +1 -0
  380. data/rust/build/Cargo.lock +7 -0
  381. data/rust/build/Cargo.toml +19 -0
  382. data/rust/build/src/build_artifact.rs +47 -0
  383. data/rust/build/src/cargo_sync.rs +248 -0
  384. data/rust/build/src/json.rs +421 -0
  385. data/rust/build/src/lineage_pass.rs +285 -0
  386. data/rust/build/src/main.rs +51 -0
  387. data/rust/build/src/optional_pass.rs +150 -0
  388. data/rust/build/src/pipeline.rs +264 -0
  389. data/rust/build/src/reserved_names.rs +76 -0
  390. data/rust/build/src/resolve.rs +124 -0
  391. data/rust/build/src/root.rs +25 -0
  392. data/rust/build/src/sidecars.rs +73 -0
  393. data/rust/build/src/subprocess.rs +51 -0
  394. data/rust/build/src/tmp.rs +31 -0
  395. data/rust/codegen/.cargo/config.toml +3 -0
  396. data/rust/codegen/.gitignore +1 -0
  397. data/rust/codegen/Cargo.lock +7 -0
  398. data/rust/codegen/Cargo.toml +20 -0
  399. data/rust/codegen/src/attr.rs +32 -0
  400. data/rust/codegen/src/bridging.rs +481 -0
  401. data/rust/codegen/src/commands.rs +1226 -0
  402. data/rust/codegen/src/constraints.rs +109 -0
  403. data/rust/codegen/src/dependency_planning.rs +209 -0
  404. data/rust/codegen/src/domain_generator.rs +1288 -0
  405. data/rust/codegen/src/exemplar.rs +230 -0
  406. data/rust/codegen/src/expr_emitter.rs +87 -0
  407. data/rust/codegen/src/fielded.rs +192 -0
  408. data/rust/codegen/src/hecks_naming.rs +33 -0
  409. data/rust/codegen/src/json.rs +335 -0
  410. data/rust/codegen/src/json_codec.rs +697 -0
  411. data/rust/codegen/src/literal.rs +207 -0
  412. data/rust/codegen/src/main.rs +290 -0
  413. data/rust/codegen/src/manifest.rs +227 -0
  414. data/rust/codegen/src/mutations.rs +784 -0
  415. data/rust/codegen/src/naming.rs +286 -0
  416. data/rust/codegen/src/ports.rs +208 -0
  417. data/rust/codegen/src/prelude.rs +116 -0
  418. data/rust/codegen/src/queries.rs +786 -0
  419. data/rust/codegen/src/reactions.rs +532 -0
  420. data/rust/codegen/src/read_models.rs +697 -0
  421. data/rust/codegen/src/reference_specs.rs +40 -0
  422. data/rust/codegen/src/registry.rs +966 -0
  423. data/rust/codegen/src/reserved_names.rs +76 -0
  424. data/rust/codegen/src/shared.rs +93 -0
  425. data/rust/codegen/src/skip_reason.rs +25 -0
  426. data/rust/codegen/src/types.rs +362 -0
  427. data/rust/host/.cargo/config.toml +3 -0
  428. data/rust/host/.gitignore +1 -0
  429. data/rust/host/Cargo.lock +4297 -0
  430. data/rust/host/Cargo.toml +61 -0
  431. data/rust/host/HECKS_RELEASE +1 -0
  432. data/rust/host/rds-ca-bundle.pem +2736 -0
  433. data/rust/host/src/api.rs +1596 -0
  434. data/rust/host/src/approval.rs +652 -0
  435. data/rust/host/src/auth.rs +1287 -0
  436. data/rust/host/src/bin/expr_harness.rs +46 -0
  437. data/rust/host/src/bin/lineage_harness.rs +119 -0
  438. data/rust/host/src/bin/mint_harness.rs +135 -0
  439. data/rust/host/src/checkout.rs +265 -0
  440. data/rust/host/src/dispatch.rs +1443 -0
  441. data/rust/host/src/expr_json.rs +1117 -0
  442. data/rust/host/src/field_hints.rs +52 -0
  443. data/rust/host/src/ir.rs +733 -0
  444. data/rust/host/src/journal.rs +874 -0
  445. data/rust/host/src/lambda_client.rs +378 -0
  446. data/rust/host/src/log.rs +147 -0
  447. data/rust/host/src/main.rs +474 -0
  448. data/rust/host/src/mint.rs +1865 -0
  449. data/rust/host/src/payments/keystore.rs +330 -0
  450. data/rust/host/src/payments/tests.rs +1400 -0
  451. data/rust/host/src/payments.rs +767 -0
  452. data/rust/host/src/presentation.rs +698 -0
  453. data/rust/host/src/presentation_write.rs +1431 -0
  454. data/rust/host/src/query_step.rs +149 -0
  455. data/rust/host/src/rate_limit.rs +904 -0
  456. data/rust/host/src/reference_transform.rs +241 -0
  457. data/rust/host/src/reference_validate.rs +338 -0
  458. data/rust/host/src/resend.rs +296 -0
  459. data/rust/host/src/secrets.rs +106 -0
  460. data/rust/host/src/server.rs +663 -0
  461. data/rust/host/src/storage_shape.rs +175 -0
  462. data/rust/host/src/test_pg.rs +40 -0
  463. data/rust/host/src/ui_schema.rs +1351 -0
  464. data/rust/host/src/wasm_runner.rs +191 -0
  465. data/rust/host/src/web/newsletter/cooldown.rs +126 -0
  466. data/rust/host/src/web/newsletter/tests.rs +104 -0
  467. data/rust/host/src/web/newsletter.rs +310 -0
  468. data/rust/host/src/web/newsletter_send.rs +357 -0
  469. data/rust/host/src/web/registrations.rs +1505 -0
  470. data/rust/host/src/web.rs +2937 -0
  471. data/rust/lsp/.gitignore +1 -0
  472. data/rust/lsp/Cargo.lock +7 -0
  473. data/rust/lsp/Cargo.toml +16 -0
  474. data/rust/lsp/README.md +133 -0
  475. data/rust/lsp/src/diagnostics.rs +165 -0
  476. data/rust/lsp/src/json.rs +385 -0
  477. data/rust/lsp/src/main.rs +399 -0
  478. data/rust/lsp/src/outline.rs +177 -0
  479. data/rust/lsp/src/rpc.rs +67 -0
  480. data/rust/parser/.cargo/config.toml +5 -0
  481. data/rust/parser/.gitignore +1 -0
  482. data/rust/parser/Cargo.lock +7 -0
  483. data/rust/parser/Cargo.toml +21 -0
  484. data/rust/parser/src/build/closed_sets.rs +27 -0
  485. data/rust/parser/src/build/identity.rs +276 -0
  486. data/rust/parser/src/build/mod.rs +12 -0
  487. data/rust/parser/src/build/naming.rs +141 -0
  488. data/rust/parser/src/build/pattern_subset.rs +161 -0
  489. data/rust/parser/src/build/query_derive.rs +72 -0
  490. data/rust/parser/src/build/query_inference.rs +269 -0
  491. data/rust/parser/src/build/query_options.rs +100 -0
  492. data/rust/parser/src/build/read_model.rs +94 -0
  493. data/rust/parser/src/build/references.rs +32 -0
  494. data/rust/parser/src/canonical.rs +114 -0
  495. data/rust/parser/src/diag.rs +55 -0
  496. data/rust/parser/src/emit.rs +953 -0
  497. data/rust/parser/src/expr/ast_json.rs +147 -0
  498. data/rust/parser/src/expr/evaluator.rs +81 -0
  499. data/rust/parser/src/expr/mod.rs +84 -0
  500. data/rust/parser/src/expr/resolver.rs +497 -0
  501. data/rust/parser/src/ir.rs +387 -0
  502. data/rust/parser/src/keywords.rs +585 -0
  503. data/rust/parser/src/lex.rs +662 -0
  504. data/rust/parser/src/main.rs +252 -0
  505. data/rust/parser/src/parse/aggregate.rs +612 -0
  506. data/rust/parser/src/parse/chapter.rs +517 -0
  507. data/rust/parser/src/parse/command.rs +637 -0
  508. data/rust/parser/src/parse/domain_port.rs +127 -0
  509. data/rust/parser/src/parse/entity.rs +527 -0
  510. data/rust/parser/src/parse/file.rs +67 -0
  511. data/rust/parser/src/parse/hecksagon.rs +231 -0
  512. data/rust/parser/src/parse/lifecycle.rs +162 -0
  513. data/rust/parser/src/parse/mod.rs +1262 -0
  514. data/rust/parser/src/parse/policy.rs +87 -0
  515. data/rust/parser/src/parse/process_manager.rs +305 -0
  516. data/rust/parser/src/parse/query.rs +100 -0
  517. data/rust/parser/src/parse/read_model.rs +214 -0
  518. data/rust/parser/src/parse/value_object.rs +250 -0
  519. data/rust/parser/src/ruby_value.rs +422 -0
  520. data/rust/project/bridging.rb +257 -0
  521. data/rust/project/commands.rb +877 -0
  522. data/rust/project/constraints.rb +86 -0
  523. data/rust/project/dependency_planning.rb +178 -0
  524. data/rust/project/domain_generator.rb +980 -0
  525. data/rust/project/exemplar.rb +148 -0
  526. data/rust/project/expr_emitter.rb +72 -0
  527. data/rust/project/fielded.rb +141 -0
  528. data/rust/project/json_codec.rb +641 -0
  529. data/rust/project/mutations.rb +721 -0
  530. data/rust/project/naming.rb +205 -0
  531. data/rust/project/ports.rb +65 -0
  532. data/rust/project/queries.rb +493 -0
  533. data/rust/project/reactions.rb +317 -0
  534. data/rust/project/read_models.rb +690 -0
  535. data/rust/project/reference_specs.rb +27 -0
  536. data/rust/project/registry.rb +396 -0
  537. data/rust/project/skip_reason.rb +29 -0
  538. data/rust/project/types.rb +225 -0
  539. data/rust/project/write_if_changed.rb +83 -0
  540. data/rust/project.rb +30 -0
  541. data/rust/project_rust_pipeline.rb +383 -0
  542. data/rust/src/exemplar/commands.rs +279 -0
  543. data/rust/src/exemplar/constraints.rs +18 -0
  544. data/rust/src/exemplar/fielded.rs +280 -0
  545. data/rust/src/exemplar/json.rs +277 -0
  546. data/rust/src/exemplar/mod.rs +17 -0
  547. data/rust/src/exemplar/mutations.rs +80 -0
  548. data/rust/src/exemplar/queries.rs +33 -0
  549. data/rust/src/exemplar/reactions.rs +88 -0
  550. data/rust/src/exemplar/read_models.rs +48 -0
  551. data/rust/src/exemplar/registry.rs +166 -0
  552. data/rust/src/exemplar/types.rs +44 -0
  553. data/rust/src/kernel/attribute_shapes/composite.rs +88 -0
  554. data/rust/src/kernel/attribute_shapes/list.rs +18 -0
  555. data/rust/src/kernel/attribute_shapes/mod.rs +44 -0
  556. data/rust/src/kernel/attribute_shapes/optional.rs +9 -0
  557. data/rust/src/kernel/attribute_shapes/scalar.rs +39 -0
  558. data/rust/src/kernel/cli.rs +585 -0
  559. data/rust/src/kernel/dispatch.rs +1090 -0
  560. data/rust/src/kernel/expr.rs +458 -0
  561. data/rust/src/kernel/expression_operators/arithmetic.rs +128 -0
  562. data/rust/src/kernel/expression_operators/comparison.rs +48 -0
  563. data/rust/src/kernel/expression_operators/enumeration.rs +232 -0
  564. data/rust/src/kernel/expression_operators/logical.rs +13 -0
  565. data/rust/src/kernel/expression_operators/membership.rs +18 -0
  566. data/rust/src/kernel/expression_operators/mod.rs +68 -0
  567. data/rust/src/kernel/expression_operators/pattern_match.rs +73 -0
  568. data/rust/src/kernel/expression_operators/positional.rs +126 -0
  569. data/rust/src/kernel/expression_operators/presence.rs +101 -0
  570. data/rust/src/kernel/expression_operators/sign_test.rs +16 -0
  571. data/rust/src/kernel/expression_operators/sized.rs +35 -0
  572. data/rust/src/kernel/expression_operators/text.rs +79 -0
  573. data/rust/src/kernel/expression_operators/to_string.rs +18 -0
  574. data/rust/src/kernel/json.rs +779 -0
  575. data/rust/src/kernel/mod.rs +133 -0
  576. data/rust/src/kernel/named_query.rs +219 -0
  577. data/rust/src/kernel/orchestrate.rs +1810 -0
  578. data/rust/src/kernel/pattern.rs +307 -0
  579. data/rust/src/kernel/query_comparators.rs +206 -0
  580. data/rust/src/kernel/query_ordering.rs +103 -0
  581. data/rust/src/kernel/read_model.rs +914 -0
  582. data/rust/src/kernel/reference_lookup.rs +149 -0
  583. data/rust/src/kernel/repository.rs +468 -0
  584. data/rust/src/kernel/routing.rs +612 -0
  585. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +118 -0
  586. data/rust/src/kernel/vocab/entity_dispatch_order.rs +114 -0
  587. data/rust/src/kernel/vocab/field_hint.rs +70 -0
  588. data/rust/src/kernel/vocab/mod.rs +20 -0
  589. data/rust/src/kernel/vocab/query_comparator.rs +65 -0
  590. data/rust/src/kernel/vocab/refusal_template.rs +1592 -0
  591. data/rust/src/lib.rs +8 -0
  592. data/rust/src/main.rs +16 -0
  593. data/rust/web/.gitignore +1 -0
  594. data/rust/web/Cargo.lock +119 -0
  595. data/rust/web/Cargo.toml +18 -0
  596. data/rust/web/src/lib.rs +8 -0
  597. metadata +477 -12
  598. data/lib/hecks/facade/cli_door.rb +0 -96
  599. data/lib/hecks/facade/cli_runner.rb +0 -127
  600. data/lib/hecks/mcp_door_scope.rb +0 -95
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pg"
4
+
5
+ module Hecks
6
+ module QualityControlCli
7
+ # The command behind `hecks quality_control create_ledger_role`: creates the ordinary Postgres
8
+ # role the QA ledger connects as and makes it own the database. It is idempotent, and refuses a
9
+ # superuser or `BYPASSRLS` role, which the era write-fence cannot bind.
10
+ #
11
+ # hecks quality_control create_ledger_role <database> [--role <role>] # role defaults to
12
+ # hecks_qa
13
+ class QaPostgresRole
14
+ USAGE = "usage: hecks quality_control create_ledger_role <database> [--role <role>]"
15
+
16
+ # `relkind` of each relation to the word `ALTER` takes for it. Partitions are their own
17
+ # rows; `ALTER TABLE ... OWNER` on the parent does not recurse.
18
+ KINDS = { "r" => "TABLE", "p" => "TABLE", "S" => "SEQUENCE", "v" => "VIEW", "m" => "MATERIALIZED VIEW" }.freeze
19
+
20
+ # Creates the role and hands it the database.
21
+ #
22
+ # @param argv [Array<String>] the database, and optionally `--role NAME`
23
+ # @param out [IO] where the report goes
24
+ # @param err [IO] where a usage error goes
25
+ # @return [Integer] 0 once the database is the role's, 1 for a usage error
26
+ # @raise [SystemExit] when the role is a superuser or `BYPASSRLS`, or the database is missing
27
+ def self.call(argv, out: $stdout, err: $stderr)
28
+ new(out: out, err: err).call(argv)
29
+ end
30
+
31
+ # @param out [IO] where the report goes
32
+ # @param err [IO] where a usage error goes
33
+ def initialize(out: $stdout, err: $stderr)
34
+ @out = out
35
+ @err = err
36
+ end
37
+
38
+ # @param argv [Array<String>] the database, and optionally `--role NAME`
39
+ # @return [Integer] the exit status
40
+ # @raise [SystemExit] when the role is a superuser or `BYPASSRLS`, or the database is missing
41
+ def call(argv)
42
+ argv = argv.dup
43
+ role = "hecks_qa"
44
+ if (index = argv.index("--role"))
45
+ role = argv[index + 1] or return usage("--role needs a name")
46
+ argv.slice!(index, 2)
47
+ end
48
+ database = argv.shift
49
+ return usage("no database named") if database.to_s.empty?
50
+ return usage("unexpected argument #{argv.first.inspect}") unless argv.empty?
51
+
52
+ done = []
53
+ skipped = []
54
+ admin = PG.connect(dbname: "postgres")
55
+ create_role(admin, role, done, skipped)
56
+ take_database(admin, database, role, done, skipped)
57
+ admin.close
58
+ take_contents(database, role, done)
59
+ report(database, role, done, skipped)
60
+ 0
61
+ end
62
+
63
+ private
64
+
65
+ def usage(message)
66
+ @err.puts "hecks quality_control create_ledger_role: #{message}"
67
+ @err.puts USAGE
68
+ 1
69
+ end
70
+
71
+ def create_role(admin, role, done, skipped)
72
+ attrs = admin.exec_params("SELECT rolsuper, rolbypassrls FROM pg_roles WHERE rolname = $1", [role])
73
+ if attrs.ntuples.zero?
74
+ # CREATE ROLE has no IF NOT EXISTS. Concurrent creates can lose on the catalog index
75
+ # (unique_violation) rather than duplicate_object, so the block rescues both.
76
+ admin.exec(<<~SQL)
77
+ DO $$ BEGIN
78
+ CREATE ROLE #{admin.quote_ident(role)} LOGIN NOSUPERUSER NOBYPASSRLS NOCREATEDB NOCREATEROLE;
79
+ EXCEPTION WHEN duplicate_object OR unique_violation THEN NULL;
80
+ END $$
81
+ SQL
82
+ done << "created role #{role} (LOGIN, no SUPERUSER, no BYPASSRLS)"
83
+ elsif attrs[0]["rolsuper"] == "t" || attrs[0]["rolbypassrls"] == "t"
84
+ admin.close
85
+ abort "hecks quality_control create_ledger_role: role #{role} already exists as " \
86
+ "#{attrs[0]['rolsuper'] == 't' ? 'a superuser' : 'a BYPASSRLS role'} — the era write-fence " \
87
+ "cannot bite it, which is the exact state this script exists to end. Pick another role, or " \
88
+ "ALTER ROLE #{role} NOSUPERUSER NOBYPASSRLS first."
89
+ else
90
+ skipped << "role #{role} exists, ordinary"
91
+ end
92
+ end
93
+
94
+ def take_database(admin, database, role, done, skipped)
95
+ owner = admin.exec_params(
96
+ "SELECT pg_get_userbyid(datdba) AS owner FROM pg_database WHERE datname = $1", [database]
97
+ )
98
+ if owner.ntuples.zero?
99
+ admin.close
100
+ abort "hecks quality_control create_ledger_role: no database #{database} — createdb it first (PostgresEra provisions " \
101
+ "every table it needs on first connect, never the database itself)"
102
+ end
103
+ if owner[0]["owner"] == role
104
+ skipped << "database #{database} already owned by #{role}"
105
+ else
106
+ admin.exec("ALTER DATABASE #{admin.quote_ident(database)} OWNER TO #{admin.quote_ident(role)}")
107
+ done << "database #{database}: owner #{owner[0]['owner']} -> #{role}"
108
+ end
109
+ end
110
+
111
+ def take_contents(database, role, done)
112
+ db = PG.connect(dbname: database)
113
+ quoted_role = db.quote_ident(role)
114
+ schema_owner = db.exec("SELECT pg_get_userbyid(nspowner) AS owner FROM pg_namespace WHERE nspname = 'public'")
115
+ if schema_owner.ntuples.positive? && schema_owner[0]["owner"] != role
116
+ db.exec("ALTER SCHEMA public OWNER TO #{quoted_role}")
117
+ done << "schema public: owner #{schema_owner[0]['owner']} -> #{role}"
118
+ end
119
+ take_relations(db, role, quoted_role, done)
120
+ take_functions(db, role, quoted_role, done)
121
+ db.close
122
+ end
123
+
124
+ # relkind: r table, p partitioned table, S sequence, v view, m matview.
125
+ def take_relations(db, role, quoted_role, done)
126
+ relations = db.exec_params(<<~SQL, [role])
127
+ SELECT c.relname, c.relkind, pg_get_userbyid(c.relowner) AS owner
128
+ FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
129
+ WHERE n.nspname = 'public' AND c.relkind IN ('r', 'p', 'S', 'v', 'm') AND pg_get_userbyid(c.relowner) <> $1
130
+ ORDER BY c.relkind, c.relname
131
+ SQL
132
+ relations.each do |row|
133
+ db.exec("ALTER #{KINDS.fetch(row['relkind'])} #{db.quote_ident(row['relname'])} OWNER TO #{quoted_role}")
134
+ end
135
+ done << "#{relations.ntuples} relation(s) in public -> #{role}" if relations.ntuples.positive?
136
+ end
137
+
138
+ def take_functions(db, role, quoted_role, done)
139
+ functions = db.exec_params(<<~SQL, [role])
140
+ SELECT p.proname, pg_get_function_identity_arguments(p.oid) AS args
141
+ FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace
142
+ WHERE n.nspname = 'public' AND pg_get_userbyid(p.proowner) <> $1
143
+ ORDER BY p.proname
144
+ SQL
145
+ functions.each do |row|
146
+ db.exec("ALTER FUNCTION #{db.quote_ident(row['proname'])}(#{row['args']}) OWNER TO #{quoted_role}")
147
+ end
148
+ done << "#{functions.ntuples} function(s) in public -> #{role}" if functions.ntuples.positive?
149
+ end
150
+
151
+ def report(database, role, done, skipped)
152
+ @out.puts "hecks quality_control create_ledger_role: #{database} is #{role}'s"
153
+ done.each { |line| @out.puts " did: #{line}" }
154
+ skipped.each { |line| @out.puts " already: #{line}" }
155
+ @out.puts " bind it: database \"postgres://#{role}@localhost/#{database}\"" if done.any?
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,232 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../hecks"
4
+ # The ledger's `persisted_by "PostgresEra"` binding needs the era plugin, which core does not load.
5
+ require_relative "../../ports/persistence/plugins/era"
6
+ require_relative "../adapters/git_pr"
7
+
8
+ module Hecks
9
+ module QualityControlCli
10
+ # The command behind `hecks quality_control check_pull_requests`: checks CI for every PR the
11
+ # QualityControl ledger tracks as open (`Patch` and `Improvement`). It starts a `Clearance` per
12
+ # unsettled head commit and lets the CI port's policies record the verdict.
13
+ #
14
+ # Exit codes: 0 nothing to act on; 1 operational error (`gh`, bad sha, ledger boot);
15
+ # 2 a newly red PR, with the details on stdout. A commit's `Clearance` is minted once and never
16
+ # re-asked; pending checks are skipped, since the CI port has no way to say "ask again later".
17
+ class QaPrCheck
18
+ EXIT_OK = 0
19
+ EXIT_ERROR = 1
20
+ EXIT_NEWLY_RED = 2
21
+
22
+ USAGE = "usage: hecks quality_control check_pull_requests"
23
+
24
+ SHA_PATTERN = /\A[0-9a-fA-F]{7,40}\z/
25
+
26
+ # Two worklists, checked the same way. The citation is what a row references: a `Bug` for a
27
+ # `Patch`, an optional `Angle` for an `Improvement`.
28
+ WORKLISTS = [
29
+ { aggregate: "Patch", citation_field: :bug, citation_label: "bug" },
30
+ { aggregate: "Improvement", citation_field: :angle, citation_label: "angle" }
31
+ ].freeze
32
+
33
+ # Checks every tracked PR.
34
+ #
35
+ # @param argv [Array<String>] no arguments; `--help` prints the usage
36
+ # @param root [String] the repository root, where the ledger's boot directory is found
37
+ # @param env [Hash{String => String}] `QA_SWEEP_DOMAIN_DIR` points at another ledger
38
+ # @return [Integer] 0 clean, 1 on an operational error, 2 for a newly red PR
39
+ # @raise [SystemExit] when given arguments, or the ledger does not boot
40
+ def self.call(argv, root:, env: ENV)
41
+ new(root: root, env: env).call(argv)
42
+ end
43
+
44
+ # @param root [String] the repository root
45
+ # @param env [Hash{String => String}] `QA_SWEEP_DOMAIN_DIR` points at another ledger
46
+ def initialize(root:, env: ENV)
47
+ @domain_dir = env.fetch("QA_SWEEP_DOMAIN_DIR", File.join(root, "qa/bluebook"))
48
+ end
49
+
50
+ # @param argv [Array<String>] no arguments; `--help` prints the usage
51
+ # @return [Integer] the exit status
52
+ # @raise [SystemExit] when given arguments, or the ledger does not boot
53
+ def call(argv)
54
+ unless argv.empty?
55
+ if %w[-h --help].include?(argv.first)
56
+ puts USAGE
57
+ return EXIT_OK
58
+ end
59
+ abort "#{USAGE}\nthis script takes no arguments — it checks every PR QualityControl::Patch or " \
60
+ "QualityControl::Improvement tracks as open, every time"
61
+ end
62
+
63
+ # Every `gh` call goes through the `GitPr` adapter. With `--json`, gh exits 0 whatever the
64
+ # checks say, so a failure means only that the call did not succeed.
65
+ @git_pr = Hecks::Adapters::GitPr.new(repo_dir: Dir.pwd)
66
+ @runtime = boot_ledger
67
+ worklists = WORKLISTS.to_h { |w| [w, query("#{w[:aggregate]}.Open")] }
68
+ if worklists.values.all?(&:empty?)
69
+ puts "no PRs tracked as open by QualityControl::Patch or QualityControl::Improvement — nothing to check"
70
+ return EXIT_OK
71
+ end
72
+
73
+ check_all(worklists)
74
+ end
75
+
76
+ private
77
+
78
+ def boot_ledger
79
+ Hecks.boot(@domain_dir)
80
+ rescue StandardError => e
81
+ abort "the QualityControl ledger did not boot — fix the ledger itself before checking PRs against it " \
82
+ "(#{e.class}: #{e.message})"
83
+ end
84
+
85
+ def check_all(worklists)
86
+ newly_red = []
87
+ operational_errors = []
88
+ worklists.each do |worklist, rows|
89
+ rows.each { |row| check_one_pr(row, newly_red, operational_errors, **worklist) }
90
+ end
91
+ report_errors(operational_errors)
92
+ return report_newly_red(newly_red) if newly_red.any?
93
+ return EXIT_ERROR if operational_errors.any?
94
+
95
+ puts
96
+ puts "clean — every PR QualityControl::Patch or QualityControl::Improvement tracks as open is " \
97
+ "either already cleared, still running, or was retired this run."
98
+ EXIT_OK
99
+ end
100
+
101
+ def query(name, **args) = @runtime.query("QualityControl::#{name}", **args)
102
+
103
+ # Reads nested row fields by `[]`, because `Hecks::Runtime::Value` does not answer `Hash#dig`.
104
+ def field(row, *path)
105
+ path.reduce(row) { |held, key| held.nil? ? nil : held[key] }
106
+ end
107
+
108
+ def clearance_for_commit(sha)
109
+ query("Clearance.All").find { |row| field(row, :commit, :value) == sha }
110
+ end
111
+
112
+ # Retires a Patch/Improvement whose PR GitHub reports merged or closed; nothing else tells
113
+ # the ledger. Returns true if it retired one.
114
+ def retire_if_settled?(view, number, label, aggregate)
115
+ case view[:state]
116
+ when "MERGED"
117
+ @runtime.dispatch_flat("QualityControl::#{aggregate}.Merge", { id: number })
118
+ puts "#{label}: merged — retired from the worklist"
119
+ true
120
+ when "CLOSED"
121
+ @runtime.dispatch_flat("QualityControl::#{aggregate}.Close", { id: number })
122
+ puts "#{label}: closed without merging — retired from the worklist"
123
+ true
124
+ else
125
+ false
126
+ end
127
+ end
128
+
129
+ # Runs a read against `gh`; a failure is appended to `operational_errors` and answers nil.
130
+ def reading_gh(label, operational_errors)
131
+ yield
132
+ rescue Hecks::Adapters::GitPr::CommandFailed => e
133
+ operational_errors << "#{label}: #{e.message}"
134
+ nil
135
+ end
136
+
137
+ # True when every check has finished, so a verdict is worth asking for. Pending stays out
138
+ # here because the CI port cannot express "ask again later".
139
+ def checks_settled?(number, label, citation, sha, operational_errors)
140
+ checks = reading_gh(label, operational_errors) { @git_pr.checks(number) }
141
+ return false unless checks
142
+
143
+ if checks.empty?
144
+ puts "#{label}, #{citation}, #{sha[0, 7]}: gh reports no checks at all yet — skipping this run"
145
+ return false
146
+ end
147
+
148
+ if checks.any? { |c| c[:bucket] == "pending" }
149
+ puts "#{label}, #{citation}, #{sha[0, 7]}: checks still running — skipping this run"
150
+ return false
151
+ end
152
+
153
+ true
154
+ end
155
+
156
+ # Checks one tracked PR, appending to `newly_red` or `operational_errors` in place. The live
157
+ # head sha is read from gh each run, since a push mints a new commit to clear.
158
+ def check_one_pr(row, newly_red, operational_errors, aggregate:, citation_field:, citation_label:)
159
+ number = field(row, :number, :value)
160
+ cited = field(row, citation_field)
161
+ citation = cited ? "#{citation_label} #{cited}" : "no #{citation_label} cited"
162
+ label = "#{aggregate} PR ##{number} (#{field(row, :branch, :value)})"
163
+
164
+ view = reading_gh(label, operational_errors) { @git_pr.pull_request(number) }
165
+ return unless view
166
+ return if retire_if_settled?(view, number, label, aggregate)
167
+
168
+ sha = view[:headRefOid].to_s
169
+ unless sha.match?(SHA_PATTERN)
170
+ operational_errors << "#{label}: head commit #{sha.inspect} does not look like a sha — skipping"
171
+ return
172
+ end
173
+
174
+ if (existing = clearance_for_commit(sha))
175
+ puts "#{label}, #{citation}, #{sha[0, 7]}: already cleared (#{existing[:status]}) — " \
176
+ "settled once, not re-checked"
177
+ return
178
+ end
179
+ return unless checks_settled?(number, label, citation, sha, operational_errors)
180
+
181
+ ask_the_ledger(sha, label, citation, number, newly_red, aggregate: aggregate)
182
+ end
183
+
184
+ # Starts a Clearance and asks the CI port for a verdict. ClearOnPass/RefuseOnFail settle the
185
+ # record synchronously, so `find` sees it straight away; a red one is appended to `newly_red`.
186
+ def ask_the_ledger(sha, label, citation, number, newly_red, aggregate:)
187
+ ::QualityControl::Clearance.start!(commit: { value: sha })
188
+ @runtime.dispatch_flat("QualityControl::Clearance.CI.Run", { commit: sha })
189
+
190
+ cleared = ::QualityControl::Clearance.find(sha)
191
+ summary = cleared.summary.to_h[:value]
192
+
193
+ if cleared.status == "green"
194
+ puts "#{label}, #{citation}, #{sha[0, 7]}: CLEARED — #{summary}"
195
+ return
196
+ end
197
+
198
+ puts "#{label}, #{citation}, #{sha[0, 7]}: RED — #{summary}"
199
+ newly_red << { aggregate: aggregate, pr: number, citation: citation, commit: sha, refusal: summary }
200
+ end
201
+
202
+ def report_errors(operational_errors)
203
+ return if operational_errors.empty?
204
+
205
+ puts
206
+ puts "operational error(s):"
207
+ operational_errors.each { |message| puts " #{message}" }
208
+ end
209
+
210
+ def report_newly_red(newly_red)
211
+ puts
212
+ puts "=" * 72
213
+ puts "FOUND #{newly_red.length} NEWLY-RED PR(S) — BugCiWatch/ImprovementCiWatch already dispatched " \
214
+ "the matching Regress for each, by aggregate."
215
+ puts "An agent picks up the investigation from here: the same judgment"
216
+ puts "SKILL.md's own \"on a surprising check\" section describes for a sweep's"
217
+ puts "own find — self-contained fix with a regression test and a draft PR,"
218
+ puts "or leave the record for something bigger. Never re-verify from this script."
219
+ puts "=" * 72
220
+ newly_red.each do |finding|
221
+ puts
222
+ puts "aggregate: #{finding[:aggregate]}"
223
+ puts "citation: #{finding[:citation]}"
224
+ puts "pr: ##{finding[:pr]}"
225
+ puts "commit: #{finding[:commit]}"
226
+ puts "refusal: #{finding[:refusal]}"
227
+ end
228
+ EXIT_NEWLY_RED
229
+ end
230
+ end
231
+ end
232
+ end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../hecks"
4
+ # The era/lineage plugin has to be loaded before `Hecks.boot` for a real (non-Memory) adapter
5
+ # (ADR 0033).
6
+ require_relative "../../ports/persistence/plugins/era"
7
+
8
+ module Hecks
9
+ module QualityControlCli
10
+ # The command behind `hecks quality_control angle.seed`: seeds the QualityControl ledger's
11
+ # `Angle` backlog with the practice's starting leads. It is idempotent: only references not
12
+ # already on file are proposed.
13
+ class QaSeedAngles
14
+ # A `resolution:` present means the lead was already chased down: it is proposed,
15
+ # investigated and built in one pass. Its absence means the lever is still open.
16
+ SEED = [
17
+ {
18
+ reference: "ANGLE-1",
19
+ premise: "None of BUG#1-5 touched saga/process_manager machinery at all, despite this " \
20
+ "session having just built a real one (BugCiWatch) — a stress domain specifically " \
21
+ "exercising process-manager correlation, multi-step handler chains, and compensation " \
22
+ "legs was a natural, and the highest-priority, next target.",
23
+ citation: "BugCiWatch (lib/hecks/quality_control/quality_control.bluebook, \"noticing when a fix " \
24
+ "stops holding\"); BUG#1-5, none of which touched saga machinery",
25
+ proposer: "Claude QA",
26
+ resolution: "BUG#6 — SagaInterpreter#qualified cannot tell a same-domain nested-entity " \
27
+ "command reference (dispatch Manifest::Slot::Fill) apart from a genuine " \
28
+ "cross-domain qualifier, so the leg is silently read as targeting a nonexistent " \
29
+ "domain and refuses every time; found by qa/stress_domains/waybill on its first " \
30
+ "real run, before fuzzing was even needed. PR #530 (merged)."
31
+ },
32
+ {
33
+ reference: "ANGLE-2",
34
+ premise: "nested_pieces (qa/stress_domains/nested_pieces) proves two-level ENTITY nesting " \
35
+ "fuzzes and dispatches correctly, but nothing in the corpus exercises deep " \
36
+ "reference_to HOPS — a chain of reference-typed lookups across aggregates — or a " \
37
+ "governance/authorization-shaped construct combined with existence-checking. Entity " \
38
+ "nesting and reference hopping are different DSL mechanisms entirely (entity " \
39
+ "addressing vs CommandRules::References#resolve_references / " \
40
+ "resolve_state_references), and ADR 0037's own Finding 5 already shows reference " \
41
+ "resolution is exactly the kind of place Ruby and Rust structurally diverge — a " \
42
+ "stress domain built around multi-hop references, or a scope/authorization check " \
43
+ "combined with existence-checking, is a genuinely different construct combination " \
44
+ "from anything the six bugs found this session actually touched.",
45
+ citation: "qa/stress_domains/nested_pieces (entity nesting, not reference hops); " \
46
+ "docs/decisions/0037-generated-sequence-fuzz-bridge-found-real-gaps-two-fixed-" \
47
+ "three-catalogued.md, Finding 5 (resolve_state_references never ported to Rust)",
48
+ proposer: "Claude QA"
49
+ },
50
+ {
51
+ reference: "ANGLE-3",
52
+ premise: "BUG#1/#3/#4 were specifically ENGINE-DIVERGENCE bugs — Ruby and Rust disagreeing " \
53
+ "on the same dispatch — a class Ruby-only property/exception fuzzing structurally " \
54
+ "cannot see, because there is no second engine to disagree with. " \
55
+ "spec/rust_conformance_fuzz_spec.rb's own DOMAINS list wires up only pizzas and " \
56
+ "banking for CI-gated differential fuzzing. rust/Cargo.toml already declares " \
57
+ "compiled-binary features for compliance and roster (plus embryonaut and meta) that " \
58
+ "sit unused for this purpose; examples/chess and examples/directory have no Cargo " \
59
+ "feature at all — `hecks project_rust` has never regenerated a binary for either. " \
60
+ "Widening either list would let differential fuzzing catch a whole bug class " \
61
+ "Ruby-only fuzzing cannot, on domains the practice already owns.",
62
+ citation: "BUG#1/#3/#4 (engine-divergence bugs); spec/rust_conformance_fuzz_spec.rb DOMAINS " \
63
+ "(pizzas/banking only); rust/Cargo.toml [features] (compliance/roster/embryonaut/" \
64
+ "meta have binaries and are not wired in; chess/directory have no feature at all)",
65
+ proposer: "Claude QA"
66
+ },
67
+ {
68
+ reference: "ANGLE-4",
69
+ premise: "lib/hecks/bluebook/model_check.rb's ALLOWED_FINDINGS, " \
70
+ "spec/fuzzing/meta_domain_coverage_spec.rb's META_DOMAIN_GUARANTEED_BY_CONSTRUCTION " \
71
+ "and META_DOMAIN_KNOWN_GAPS, and ADR 0037's own catalogued Findings 3-5 are all " \
72
+ "places where something once labeled 'known, not a bug' could be re-examined — a " \
73
+ "gap accepted a year ago is not guaranteed to still be the right call once the " \
74
+ "surrounding code has moved. This session's own waybill finding (BUG#6) confirmed a " \
75
+ "SECOND instance of exactly the family ALLOWED_FINDINGS[\"quality_control\"] already " \
76
+ "named once (Naming.command_ref's bare-constant rewrite conflating a genuine " \
77
+ "cross-domain qualifier with mere nesting) — proof the allowlists are themselves a " \
78
+ "live source of leads, not just a static ledger of settled questions.",
79
+ citation: "lib/hecks/bluebook/model_check.rb ALLOWED_FINDINGS; " \
80
+ "spec/fuzzing/meta_domain_coverage_spec.rb META_DOMAIN_GUARANTEED_BY_CONSTRUCTION / " \
81
+ "META_DOMAIN_KNOWN_GAPS; docs/decisions/0037-generated-sequence-fuzz-bridge-found-" \
82
+ "real-gaps-two-fixed-three-catalogued.md Findings 3-5",
83
+ proposer: "Claude QA"
84
+ }
85
+ ].freeze
86
+
87
+ # Proposes the leads the ledger lacks.
88
+ #
89
+ # @param root [String] the repository root, where `qa/bluebook` is the ledger
90
+ # @param out [IO] where each lead's outcome goes
91
+ # @return [Integer] 0 once seeded
92
+ # @raise [SystemExit] when the ledger does not boot
93
+ def self.call(root:, out: $stdout)
94
+ new(root: root, out: out).call
95
+ end
96
+
97
+ # @param root [String] the repository root
98
+ # @param out [IO] where each lead's outcome goes
99
+ def initialize(root:, out: $stdout)
100
+ @domain_dir = File.join(root, "qa/bluebook")
101
+ @out = out
102
+ end
103
+
104
+ # @return [Integer] 0 once seeded
105
+ # @raise [SystemExit] when the ledger does not boot
106
+ def call
107
+ runtime = begin
108
+ Hecks.boot(@domain_dir)
109
+ rescue StandardError => e
110
+ abort "the QualityControl ledger did not boot — fix the ledger itself before seeding it " \
111
+ "(#{e.class}: #{e.message})"
112
+ end
113
+ existing = runtime.query("QualityControl::Angle.All").map { |row| row[:reference][:value] }
114
+ SEED.each do |angle|
115
+ if existing.include?(angle[:reference])
116
+ @out.puts "already on file: #{angle[:reference]}"
117
+ else
118
+ propose(angle)
119
+ end
120
+ end
121
+ 0
122
+ end
123
+
124
+ private
125
+
126
+ def propose(angle)
127
+ record = ::QualityControl::Angle.propose!(
128
+ reference: { value: angle[:reference] }, premise: { value: angle[:premise] },
129
+ citation: { value: angle[:citation] }, proposer: { value: angle[:proposer] },
130
+ now: { value: Time.now.to_i }
131
+ )
132
+ if angle[:resolution]
133
+ record = record.investigate!
134
+ record.build!(resolution: { value: angle[:resolution] })
135
+ @out.puts "proposed, investigated, and built: #{angle[:reference]}"
136
+ else
137
+ @out.puts "proposed: #{angle[:reference]}"
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../hecks"
4
+ # Load the era plugin before `Hecks.boot` (see hecks run).
5
+ require_relative "../../ports/persistence/plugins/era"
6
+ require_relative "../../fuzzing"
7
+ require_relative "../../corpus"
8
+
9
+ module Hecks
10
+ module QualityControlCli
11
+ # The command behind `hecks quality_control target.seed`: identifies every corpus domain as a
12
+ # rotation `Target`, printing each one's inferred capabilities. It is idempotent: references
13
+ # already on file are skipped whatever their status (re-`Identify` refuses).
14
+ #
15
+ # It skips `examples/embryonaut` (an external product) and `lib/hecks/framework` (no standalone
16
+ # Rust binary) on purpose.
17
+ class QaSeedTargets
18
+ # Identifies the targets the ledger lacks.
19
+ #
20
+ # @param root [String] the repository root
21
+ # @param env [Hash{String => String}] `QA_SWEEP_DOMAIN_DIR` points at another ledger
22
+ # @param out [IO] where each target's outcome goes
23
+ # @return [Integer] 0 once seeded
24
+ # @raise [SystemExit] when the ledger does not boot or a corpus path is missing
25
+ def self.call(root:, env: ENV, out: $stdout)
26
+ new(root: root, env: env, out: out).call
27
+ end
28
+
29
+ # @param root [String] the repository root
30
+ # @param env [Hash{String => String}] `QA_SWEEP_DOMAIN_DIR` points at another ledger
31
+ # @param out [IO] where each target's outcome goes
32
+ def initialize(root:, env: ENV, out: $stdout)
33
+ @root = root
34
+ @domain_dir = env.fetch("QA_SWEEP_DOMAIN_DIR", File.join(root, "qa/bluebook"))
35
+ @out = out
36
+ end
37
+
38
+ # @return [Integer] 0 once seeded
39
+ # @raise [SystemExit] when the ledger does not boot or a corpus path is missing
40
+ def call
41
+ # reference => path relative to the repository root, discovered from the corpus.
42
+ seed = Hecks::Corpus.rotation_targets(root: @root)
43
+ runtime = begin
44
+ Hecks.boot(@domain_dir)
45
+ rescue StandardError => e
46
+ abort "the QualityControl ledger did not boot — fix the ledger itself before seeding it " \
47
+ "(#{e.class}: #{e.message})"
48
+ end
49
+ existing = runtime.query("QualityControl::Target.All").map { |row| row[:reference][:value] }
50
+ seed.each { |reference, path| identify(reference, path, existing) }
51
+ 0
52
+ end
53
+
54
+ private
55
+
56
+ def identify(reference, path, existing)
57
+ capabilities = Hecks::Fuzzing::TargetCapabilities.infer(File.join(@root, path)).join(",")
58
+ if existing.include?(reference)
59
+ @out.puts "already on file: #{reference} (#{path}; capabilities #{capabilities})"
60
+ return
61
+ end
62
+ abort "#{reference} names a path that does not exist on disk: #{path}" unless File.directory?(File.join(@root, path))
63
+
64
+ ::QualityControl::Target.identify!(reference: { value: reference }, path: { value: path })
65
+ @out.puts "identified: #{reference} (#{path}; capabilities #{capabilities})"
66
+ end
67
+ end
68
+ end
69
+ end