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
@@ -101,15 +101,42 @@ module Hecks
101
101
  )
102
102
  end
103
103
 
104
+ class << self
105
+ # The builder whose block is being evaluated, so an aggregate door a facade already
106
+ # installed (a repeat boot in one process) can record a qualified bind into it.
107
+ #
108
+ # @return [Bluebook::DSL::WorldBuilder, nil] nil outside a `.world` block
109
+ attr_reader :current
110
+ end
111
+
104
112
  # Wraps `instance_eval` in `ConstShim`'s resolver: without it, an aggregate-qualified
105
113
  # verb call raises `NameError`, since the bare constant has nothing to resolve to.
114
+ # A bare name resolves to a namespace whose `::` reaches the proxy; a qualified path
115
+ # (`Pizzas::Order`, handed over by an installed facade chapter's `const_missing`)
116
+ # is already the aggregate, so it resolves to the proxy itself.
117
+ #
118
+ # @param domain [String] the domain the world belongs to
119
+ # @yield the world's DSL block
120
+ # @return [Bluebook::World] the judged world
106
121
  def self.build(domain, &block)
107
122
  builder = new(domain)
108
- resolver = ->(_domain) { WorldConstProxy.namespace(builder) }
109
- ConstShim.with(resolver) { builder.instance_eval(&block) } if block
123
+ resolver = lambda do |name|
124
+ name.to_s.include?("::") ? WorldConstProxy.new(builder) : WorldConstProxy.namespace(builder)
125
+ end
126
+ evaluate(builder, resolver, &block) if block
110
127
  builder.build
111
128
  end
112
129
 
130
+ # @api private
131
+ def self.evaluate(builder, resolver, &block)
132
+ previous = @current
133
+ @current = builder
134
+ ConstShim.with(resolver) { builder.instance_eval(&block) }
135
+ ensure
136
+ @current = previous
137
+ end
138
+ private_class_method :evaluate
139
+
113
140
  private
114
141
 
115
142
  def required(value, _label)
@@ -11,7 +11,7 @@ module Hecks
11
11
 
12
12
  STRATEGIES = Hecks::Vocabulary.fetch("NormalisationStrategy")
13
13
 
14
- # Normalisation rules projected from the grammar chapter by bin/expression_projection.
14
+ # Normalisation rules projected from the grammar chapter by hecks project_expression_tables.
15
15
  RULES = JSON.parse(
16
16
  File.read(File.join(__dir__, "projection.json")), symbolize_names: true
17
17
  ).fetch(:normalisations).map { |row| Rule.new(**row) }.freeze
@@ -201,8 +201,9 @@ module Hecks
201
201
  def blank?(value)
202
202
  return true if value.nil? || value == false
203
203
 
204
- # Duck-typed: reaching across to Runtime::Value would couple the namespaces.
205
- value = value.to_h if value.respond_to?(:to_h) && !value.is_a?(Hash)
204
+ # Duck-typed: reaching across to Runtime::Value would couple the namespaces. A list
205
+ # is judged by its own emptiness, never coerced: `[0].to_h` would raise TypeError.
206
+ value = value.to_h if value.respond_to?(:to_h) && !value.is_a?(Hash) && !value.is_a?(Array)
206
207
  case value
207
208
  when String, Array, Hash then value.empty?
208
209
  else false
@@ -32,32 +32,34 @@ module Hecks
32
32
  subscriptions: -> { subscriptions.map(&:to_s) },
33
33
  framework_members: -> { framework_members.map(&:to_s) },
34
34
  vendored_bluebooks: -> { vendored_bluebooks.map(&:to_s) },
35
- bounded: :bounded,
35
+ attached_chapters: -> { attached_chapters.map(&:to_s) },
36
+ bounded: :bounded?,
36
37
  translates: -> { translates.map(&:to_s) }
37
38
  )
38
39
 
39
40
  attr_reader :domain, :binds, :subscriptions, :framework_members, :vendored_bluebooks,
40
- :translates
41
+ :attached_chapters, :translates
41
42
 
42
43
  # @param domain [String, Symbol] the domain this hecksagon wires
43
44
  # @param binds [Array<Bluebook::Bind>] the declared adapter binds
44
45
  # @param subscriptions [Array<String, Symbol>] the external events this domain
45
46
  # subscribes to
46
- # @param framework_members [Array<String, Symbol>] the framework members
47
- # (`Governance`, `Identity`, ...) this domain attaches
47
+ # @param framework_members [Array<String, Symbol>] attached framework members (`Governance`)
48
48
  # @param vendored_bluebooks [Array<String, Symbol>] the vendored embryonaut
49
49
  # bluebook package names this domain attaches
50
+ # @param attached_chapters [Array<String, Symbol>] chapters the gem carries, attached by name
50
51
  # @param bounded [Boolean] whether this chapter is an explicit bounded context
51
52
  # (consumer-owned; `uses_framework` / `uses_embryonaut_bluebook` mark
52
53
  # attached chapters bounded on the registry instead)
53
54
  # @param translates [Array<String>] names of `translates` ACL blocks declared here
54
55
  def initialize(domain:, binds: [], subscriptions: [], framework_members: [],
55
- vendored_bluebooks: [], bounded: false, translates: [])
56
+ vendored_bluebooks: [], attached_chapters: [], bounded: false, translates: [])
56
57
  @domain = domain.to_s
57
58
  @binds = binds
58
59
  @subscriptions = subscriptions
59
60
  @framework_members = framework_members
60
61
  @vendored_bluebooks = vendored_bluebooks
62
+ @attached_chapters = attached_chapters
61
63
  @bounded = bounded ? true : false
62
64
  @translates = Array(translates).map(&:to_s)
63
65
  end
@@ -66,6 +68,12 @@ module Hecks
66
68
  #
67
69
  # @return [Boolean] whether `bounded` was declared on this block
68
70
  def bounded? = @bounded
71
+
72
+ # Every chapter this hecksagon brings into its domain's registry: framework members and
73
+ # attached chapters alike, which resolve the same way.
74
+ #
75
+ # @return [Array<String>] the chapters' names, framework members first
76
+ def member_chapters = (framework_members + attached_chapters).map(&:to_s)
69
77
  end
70
78
 
71
79
  # The built form of a `.world` file, produced by `DSL::WorldBuilder`.
@@ -24,6 +24,7 @@ module Hecks
24
24
  vision: text(@chapter[:vision]),
25
25
  classification: text(@chapter[:classification]),
26
26
  formerly_known_as: text(@chapter[:formerly_known_as]),
27
+ namespace: text(@chapter[:namespace]),
27
28
  attaches_to: attached_contexts(@chapter),
28
29
  provides: provisions(@chapter),
29
30
  aggregates: declared("Aggregate", chapter_id).map { |row| aggregate(row) },
@@ -85,9 +85,12 @@ module Hecks
85
85
 
86
86
  def disk_cache_key(chapters)
87
87
  names = chapters.map { |name, _chapter| name }
88
- Digest::SHA256.hexdigest("#{names.join(',')}:#{grammar_content_digest}")
88
+ Digest::SHA256.hexdigest("#{names.join(',')}:#{grammar_content_digest}:#{VerdictCache.code_digest}")
89
89
  end
90
90
 
91
+ # The key also carries `VerdictCache.code_digest` (all of `lib/`), so an
92
+ # edit to Ruby that builds or judges the grammar misses too.
93
+ #
91
94
  # Covers every chapter's grammar files, not just the core ones —
92
95
  # coarser than strictly necessary, but under-covering this set would
93
96
  # let a stale table survive a real grammar edit.
@@ -0,0 +1,246 @@
1
+ require "digest"
2
+ require "fileutils"
3
+ require "json"
4
+ require_relative "../../cache_dir"
5
+ require_relative "../../version"
6
+
7
+ module Hecks
8
+ module Bluebook
9
+ module MetaValidator
10
+ # Cross-process store of the meta-domain's chapter verdicts, so a boot
11
+ # judges only the chapters no earlier process has judged.
12
+ #
13
+ # Only chapter verdicts live here; `Assembly.call` still runs every boot
14
+ # and a miss judges as a cold process does. The file name carries a digest
15
+ # of the judging code (every file under `lib/`, the Ruby and Hecks
16
+ # versions, `FORMAT`); each entry is keyed by the chapter IR's SHA-256.
17
+ # The file is tagged JSON, never `Marshal`, read only from a private,
18
+ # user-owned directory: a forged "no refusals" entry would skip validation.
19
+ #
20
+ # VerdictCache.record(key, held)
21
+ # VerdictCache.flush # also runs at process exit
22
+ module VerdictCache
23
+ module_function
24
+
25
+ # Bumped when the encoding or the entry shape changes.
26
+ FORMAT = 1
27
+
28
+ # Files older than this that do not match the current code digest are
29
+ # pruned on write.
30
+ STALE_AFTER = 60 * 60
31
+
32
+ # Whether the disk cache is on; `HECKS_VERDICT_CACHE=off` disables it.
33
+ #
34
+ # @return [Boolean]
35
+ def enabled? = ENV["HECKS_VERDICT_CACHE"] != "off"
36
+
37
+ # The directory the verdict files live in.
38
+ #
39
+ # @return [String] an absolute path under `Hecks::CacheDir`
40
+ def dir = Hecks::CacheDir.path("hecks_verdict_cache")
41
+
42
+ # The file the current code's verdicts live in.
43
+ #
44
+ # @return [String] an absolute path
45
+ def path = File.join(dir, "verdicts-#{code_digest}.json")
46
+
47
+ # A digest of everything that decides a verdict: each file under
48
+ # `lib/` (path and bytes), the Ruby and Hecks versions and `FORMAT`.
49
+ # Computed once per process, at first use, which is when the judging
50
+ # code was loaded.
51
+ #
52
+ # @return [String] a hex SHA-256
53
+ def code_digest = @code_digest ||= digest_of(File.expand_path("../../..", __dir__))
54
+
55
+ # The digest `code_digest` reports, for any tree.
56
+ #
57
+ # @param lib [String] the directory whose files decide a verdict
58
+ # @return [String] a hex SHA-256 over `FORMAT`, the versions and every
59
+ # file's relative path and bytes, in sorted order
60
+ def digest_of(lib)
61
+ state = Digest::SHA256.new
62
+ state << "#{FORMAT}\0#{RUBY_VERSION}\0#{Hecks::VERSION}\0"
63
+ Dir.glob(File.join(lib, "**", "*"), File::FNM_DOTMATCH).sort.each do |file|
64
+ next unless File.file?(file)
65
+
66
+ state << file.delete_prefix(lib) << "\0" << File.binread(file) << "\0"
67
+ end
68
+ state.hexdigest
69
+ end
70
+
71
+ # Loads the stored verdicts, once per process; later calls return an
72
+ # empty hash so a caller that resets its own memo re-judges for real.
73
+ #
74
+ # @return [Hash{String => Hash}] verdicts by chapter key; empty when
75
+ # the cache is off, absent, unreadable, corrupt or not private
76
+ def seed
77
+ return {} if @seeded
78
+
79
+ @seeded = true
80
+ return {} unless enabled?
81
+
82
+ loaded = read
83
+ entries.merge!(loaded)
84
+ loaded.dup
85
+ rescue StandardError
86
+ {}
87
+ end
88
+
89
+ # Remembers a freshly judged chapter verdict for the next `flush`.
90
+ #
91
+ # @param key [String] the chapter's SHA-256 key
92
+ # @param held [Hash] the verdict `MetaValidator.hold` returned
93
+ # @return [void]
94
+ def record(key, held)
95
+ return unless enabled?
96
+
97
+ entries[key] = held
98
+ @dirty = true
99
+ register_exit_flush
100
+ end
101
+
102
+ # Writes every known verdict when any was added since the last write.
103
+ # Failure of any kind leaves the cache as it was.
104
+ #
105
+ # @return [void]
106
+ def flush
107
+ return unless @dirty && enabled?
108
+
109
+ @dirty = false
110
+ encoded = encode(entries)
111
+ return unless decode(encoded) == entries
112
+
113
+ write(JSON.generate({ "format" => FORMAT, "entries" => encoded }, max_nesting: false))
114
+ prune
115
+ rescue StandardError
116
+ nil
117
+ end
118
+
119
+ # Forgets the process's own bookkeeping (not the files).
120
+ #
121
+ # @return [void]
122
+ def reset!
123
+ @seeded = false
124
+ @dirty = false
125
+ @entries = nil
126
+ @code_digest = nil
127
+ end
128
+
129
+ # @return [Hash{String => Hash}] every verdict this process knows
130
+ def entries = @entries ||= {}
131
+
132
+ # Reads and decodes the current code's file.
133
+ #
134
+ # @return [Hash{String => Hash}] entries, or `{}` on any problem
135
+ def read
136
+ file = path
137
+ return {} unless File.file?(file) && trusted?(file)
138
+
139
+ doc = JSON.parse(File.binread(file), max_nesting: false)
140
+ return {} unless doc.is_a?(Hash) && doc["format"] == FORMAT
141
+
142
+ decoded = decode(doc["entries"])
143
+ well_formed?(decoded) ? decoded : {}
144
+ rescue StandardError
145
+ {}
146
+ end
147
+
148
+ # Whether `file` and its directory belong to this user and nobody else
149
+ # can write them.
150
+ #
151
+ # @param file [String] a path
152
+ # @return [Boolean]
153
+ def trusted?(file)
154
+ [File.stat(file), File.stat(File.dirname(file))].all? do |stat|
155
+ stat.owned? && stat.mode.nobits?(0o022)
156
+ end
157
+ end
158
+
159
+ # @param entries [Object] a decoded `entries` value
160
+ # @return [Boolean] whether it is a hash of chapter verdicts
161
+ def well_formed?(entries)
162
+ entries.is_a?(Hash) && entries.all? do |key, held|
163
+ key.is_a?(String) && held.is_a?(Hash) && held[:refusals].is_a?(Array) &&
164
+ (held[:refusals].any? || held[:declaration].is_a?(Hash))
165
+ end
166
+ end
167
+
168
+ # Writes through a PID-suffixed temp file and an atomic rename, so a
169
+ # concurrent reader never sees a torn file.
170
+ #
171
+ # @param text [String] the file body
172
+ # @return [void]
173
+ def write(text)
174
+ file = path
175
+ FileUtils.mkdir_p(File.dirname(file), mode: 0o700)
176
+ tmp = "#{file}.#{Process.pid}.tmp"
177
+ File.binwrite(tmp, text)
178
+ File.chmod(0o600, tmp)
179
+ File.rename(tmp, file)
180
+ end
181
+
182
+ # Removes verdict files for other code digests once they are stale.
183
+ #
184
+ # @return [void]
185
+ def prune
186
+ keep = File.basename(path)
187
+ Dir.glob(File.join(dir, "verdicts-*")).each do |file|
188
+ next if File.basename(file) == keep || Time.now - File.mtime(file) < STALE_AFTER
189
+
190
+ File.delete(file)
191
+ rescue StandardError
192
+ next
193
+ end
194
+ end
195
+
196
+ # Encodes plain data as JSON-safe data, tagging what JSON would lose:
197
+ # a symbol is `{"$s" => name}`, a hash is `{"$h" => [[key, value], ...]}`
198
+ # (pair order is hash order).
199
+ #
200
+ # @param obj [Object] Hash, Array, String, Symbol, Integer, nil, true or false
201
+ # @return [Object] the tagged form
202
+ # @raise [ArgumentError] for any other class
203
+ def encode(obj)
204
+ case obj
205
+ when Symbol then { "$s" => obj.to_s }
206
+ when Hash then { "$h" => obj.map { |key, value| [encode(key), encode(value)] } }
207
+ when Array then obj.map { |item| encode(item) }
208
+ when String
209
+ plain = obj.valid_encoding? && (obj.ascii_only? || obj.encoding == Encoding::UTF_8)
210
+ raise ArgumentError, "unencodable string" unless plain
211
+
212
+ obj
213
+ when Integer, nil, true, false then obj
214
+ else raise ArgumentError, "unencodable #{obj.class}"
215
+ end
216
+ end
217
+
218
+ # The inverse of `encode`.
219
+ #
220
+ # @param obj [Object] the tagged form
221
+ # @return [Object] the original data
222
+ def decode(obj)
223
+ case obj
224
+ when Array then obj.map { |item| decode(item) }
225
+ when Hash
226
+ if obj.size == 1 && obj.key?("$s") then obj["$s"].to_sym
227
+ elsif obj.size == 1 && obj.key?("$h") then obj["$h"].to_h { |key, value| [decode(key), decode(value)] }
228
+ else raise ArgumentError, "untagged hash"
229
+ end
230
+ else obj
231
+ end
232
+ end
233
+
234
+ # Registers the exit hook once per process.
235
+ #
236
+ # @return [void]
237
+ def register_exit_flush
238
+ return if @exit_registered
239
+
240
+ @exit_registered = true
241
+ at_exit { flush }
242
+ end
243
+ end
244
+ end
245
+ end
246
+ end
@@ -1,5 +1,6 @@
1
1
  require "digest"
2
2
  require "json"
3
+ require_relative "meta_validator/verdict_cache"
3
4
 
4
5
  module Hecks
5
6
  module Bluebook
@@ -150,9 +151,13 @@ module Hecks
150
151
  # Process-wide judging cache, keyed on a SHA-256 digest of the judged
151
152
  # artifact so a changed bluebook is always re-judged.
152
153
  #
153
- # @return [Hash{String => Array<String>}] refusal messages per digest
154
- # (`[]` when well formed)
155
- def self.verdicts = @verdicts ||= {}
154
+ # Seeded once per process with the chapter verdicts earlier processes
155
+ # stored (see `VerdictCache`); every other verdict starts empty.
156
+ #
157
+ # @return [Hash{String => Object}] refusal messages per digest for a
158
+ # world/port/adapter/translation (`[]` when well formed); the held
159
+ # verdict Hash for a chapter
160
+ def self.verdicts = @verdicts ||= VerdictCache.seed
156
161
 
157
162
  # Judges `world` through the meta-domain's own `WorldJudge` door.
158
163
  #
@@ -248,7 +253,7 @@ module Hecks
248
253
  end
249
254
 
250
255
  key = Digest::SHA256.hexdigest(JSON.generate(bluebook.to_h))
251
- held = verdicts[key] ||= hold(bluebook)
256
+ held = verdicts[key] || (verdicts[key] = hold(bluebook).tap { |fresh| VerdictCache.record(key, fresh) })
252
257
 
253
258
  unless held[:refusals].empty?
254
259
  raise DSL::Malformed,
@@ -51,10 +51,33 @@ module Hecks
51
51
  findings.concat(rust_reserved_name_findings(domain_name: bluebook.name,
52
52
  aggregate_names: bluebook.aggregates.map(&:hecks_name),
53
53
  rust_target: rust_target, strict: strict))
54
+ findings.concat(external_query_findings(bluebook, rust_target: rust_target))
54
55
  findings.concat(ClientProfile.call(bluebook, hecksagon: hecksagon)) if profile == :client
55
56
  findings
56
57
  end
57
58
 
59
+ # Flags a query the hecksagon binds to a port's adapter. Only the Ruby runtime asks an
60
+ # adapter for an answer; a Rust host would find nothing behind the query.
61
+ #
62
+ # @param bluebook [Bluebook::Chapter] the assembled chapter, its hecksagon's ports attached
63
+ # @param rust_target [Boolean] whether this domain has a real Rust target; a domain without
64
+ # one is not checked, since nothing but the Ruby runtime serves it
65
+ # @return [Array<Finding>] one :external_query error per bound query, none off a Rust target
66
+ def external_query_findings(bluebook, rust_target: false)
67
+ return [] unless rust_target
68
+
69
+ bluebook.aggregates.flat_map do |aggregate|
70
+ aggregate.ports.flat_map do |port|
71
+ port.answered_queries.map do |answer|
72
+ Finding.new(kind: :external_query, severity: :error,
73
+ subject: "#{aggregate.hecks_name}.#{answer.name}",
74
+ message: "the #{port.name} port's adapter answers this query, and only the Ruby " \
75
+ "runtime asks an adapter — the Rust host cannot serve it")
76
+ end
77
+ end
78
+ end
79
+ end
80
+
58
81
  # Flags an aggregate or domain name that collides with a Rust keyword or reserved
59
82
  # Cargo key — both become bare module identifiers with no `r#` escape hatch.
60
83
  #
@@ -101,6 +101,44 @@ module Hecks
101
101
  nil
102
102
  end
103
103
 
104
+ # Rewrites the line anchors of a declared pattern into whole-string anchors.
105
+ #
106
+ # Ruby reads `^` and `$` as line anchors, so `"ok\n../evil"` satisfies `^[a-z]+$`; Rust's
107
+ # `regex` reads them as text anchors. Enforcement matches the rewritten source so both
108
+ # engines refuse the same values. Escaped characters and bracket classes are left alone.
109
+ #
110
+ # @param pattern [String, Symbol, #to_s] the declared `pattern:` regex source
111
+ # @return [String] the source with `^` as `\A` and `$` as `\z` outside classes
112
+ def whole_string(pattern)
113
+ chars = pattern.to_s.chars
114
+ in_class = false
115
+ class_start = nil
116
+ out = +""
117
+ index = 0
118
+ while index < chars.length
119
+ char = chars[index]
120
+ if char == "\\"
121
+ out << char << chars[index + 1].to_s
122
+ index += 2
123
+ next
124
+ end
125
+ if in_class
126
+ in_class = false if char == "]" && index != class_start
127
+ elsif char == "["
128
+ in_class = true
129
+ class_start = index + 1
130
+ class_start += 1 if chars[class_start] == "^"
131
+ elsif char == "^"
132
+ char = "\\A"
133
+ elsif char == "$"
134
+ char = "\\z"
135
+ end
136
+ out << char
137
+ index += 1
138
+ end
139
+ out
140
+ end
141
+
104
142
  # Spelled out, not derived from the key: callers read these strings in refusals.
105
143
  CONSTRUCTS = {
106
144
  backreference: "backreference",
@@ -25,14 +25,14 @@ module Hecks
25
25
  limit: one(:limit)
26
26
  )
27
27
 
28
- attr_reader :name, :description, :attributes
28
+ attr_reader :name, :description, :attributes, :returns
29
29
 
30
30
  # @param null_semantics [QuerySpecification::Common::NullSemantics, nil] defaults to
31
31
  # `NullSemantics.default`
32
32
  def initialize(name:, description: nil, attributes: [], wheres: [],
33
33
  order_by: nil, limit: nil, offset: nil, cursor: nil,
34
34
  authorization: nil, null_semantics: nil,
35
- inspection: nil)
35
+ inspection: nil, returns: nil)
36
36
  null_semantics ||= QuerySpecification::Common::NullSemantics.default
37
37
  super(wheres: wheres, order_by: order_by, limit: limit, offset: offset, cursor: cursor,
38
38
  authorization: authorization,
@@ -41,13 +41,28 @@ module Hecks
41
41
  @hecks_name = @name
42
42
  @description = description
43
43
  @attributes = attributes
44
+ @returns = returns&.to_s
44
45
  end
45
46
 
47
+ # The value object a `returns` names, without the `list_of(...)` wrapper.
48
+ #
49
+ # @return [String, nil] the value object's name, or `nil` when the query returns nothing
50
+ def returns_name = @returns&.sub(/\Alist_of\((.*)\)\z/, '\1')
51
+
52
+ # Says whether the answer is many rows of the returned value object.
53
+ #
54
+ # @return [Boolean] true for `returns list_of(Name)`
55
+ def returns_list? = !@returns.nil? && @returns.start_with?("list_of(")
56
+
46
57
  # `extra_options_to_h` (count, median, group_by, scope_to) stays dynamic; `super` covers the
47
- # declared emission.
58
+ # declared emission. `returns` follows, only when the query declares one, so a query that
59
+ # returns nothing keeps its wire shape.
48
60
  #
49
- # @return [Hash] the declared emission merged with `extra_options_to_h`
50
- def to_h = super.merge(extra_options_to_h)
61
+ # @return [Hash] the declared emission merged with `extra_options_to_h`, then `returns`
62
+ def to_h
63
+ shape = super.merge(extra_options_to_h)
64
+ @returns ? shape.merge(returns: @returns) : shape
65
+ end
51
66
  end
52
67
  end
53
68
  end
@@ -23,7 +23,7 @@ module Hecks
23
23
  def call(dir)
24
24
  Dir.mktmpdir("hecks-smoke-") do |scratch|
25
25
  isolate!(dir, scratch)
26
- dispatcher = Hecks.boot(scratch, install_facade: false)
26
+ dispatcher = Hecks.boot(scratch, install_doors: false)
27
27
  domain = dispatcher.registry.bluebooks.keys.first
28
28
  next [] unless domain
29
29
 
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Hecks
6
+ # A JSON document with every object's keys sorted. Key order is not semantics, so a diff a person
7
+ # reads should not have to notice it moved.
8
+ module CanonicalJson
9
+ module_function
10
+
11
+ # Sorts every Hash's keys within a parsed JSON document, recursively.
12
+ #
13
+ # @param node [Object] a value from `JSON.parse`: a `Hash`, an `Array`, or a JSON scalar
14
+ # @return [Object] the same shape with every nested `Hash`'s keys sorted by their string form
15
+ def sort_deep(node)
16
+ case node
17
+ when Hash then node.sort_by { |key, _| key.to_s }.to_h { |key, value| [key, sort_deep(value)] }
18
+ when Array then node.map { |item| sort_deep(item) }
19
+ else node
20
+ end
21
+ end
22
+
23
+ # @param text [String] a JSON document
24
+ # @return [String] the document pretty-printed with its keys in order
25
+ # @raise [JSON::ParserError] if the text is not JSON
26
+ def pretty(text)
27
+ JSON.pretty_generate(sort_deep(JSON.parse(text)))
28
+ end
29
+ end
30
+ end