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,89 @@
1
+ require "json"
2
+
3
+ module Hecks
4
+ module Bench
5
+ # A fixed command sequence for one example domain, identical for every runtime.
6
+ # Cycles name their records by `n`, so no command is refused and none times a rejection path.
7
+ class Workload
8
+ Step = Struct.new(:verb, :args) do
9
+ def to_json_line
10
+ JSON.generate("verb" => verb, "args" => args)
11
+ end
12
+
13
+ def ruby_args
14
+ args.transform_keys(&:to_sym)
15
+ end
16
+ end
17
+
18
+ # The example domain's directory name, also its Cargo feature.
19
+ attr_reader :name
20
+
21
+ # The absolute path of the example domain directory.
22
+ attr_reader :domain_path
23
+
24
+ # The commands run once, untimed, before any cycle.
25
+ attr_reader :setup
26
+
27
+ def initialize(name:, setup:, cycle:)
28
+ @name = name
29
+ @domain_path = File.expand_path("../../../examples/#{name}", __dir__)
30
+ @setup = setup
31
+ @cycle = cycle
32
+ end
33
+
34
+ def cycle(number) = @cycle.call(number)
35
+
36
+ def commands_per_cycle = cycle(0).size
37
+
38
+ def self.all
39
+ { "pizzas" => pizzas, "banking" => banking }
40
+ end
41
+
42
+ def self.fetch(name)
43
+ all.fetch(name) do
44
+ raise ArgumentError, "unknown domain #{name.inspect} — one of #{all.keys.join(', ')}"
45
+ end
46
+ end
47
+
48
+ def self.pizzas
49
+ new(name: "pizzas", setup: [], cycle: lambda { |n|
50
+ pizza = "pizza-#{n}"
51
+ [
52
+ Step.new("Pizzas::Order.CreatePizza",
53
+ { "name" => { "value" => pizza },
54
+ "pizza" => { "price_cents" => { "cents" => 1200 }, "size" => { "value" => "large" } } }),
55
+ Step.new("Pizzas::Order.AddTopping",
56
+ { "name" => pizza, "topping" => { "value" => "Basil" }, "amount" => { "value" => 3 } }),
57
+ Step.new("Pizzas::Order.AddTopping",
58
+ { "name" => pizza, "topping" => { "value" => "Olive" }, "amount" => { "value" => 2 } }),
59
+ Step.new("Pizzas::Order.Purchase",
60
+ { "name" => pizza, "customer_name" => { "value" => "Chris" },
61
+ "amount" => { "cents" => 1200 } })
62
+ ]
63
+ })
64
+ end
65
+
66
+ def self.banking
67
+ register = Step.new("Banking::Customer.Register",
68
+ { "reference" => { "value" => "CUST-0001" },
69
+ "name" => { "given" => "Ada", "family" => "Lovelace" },
70
+ "email" => { "address" => "ada@example.com" } })
71
+ new(name: "banking", setup: [register], cycle: lambda { |n|
72
+ number = { "value" => "acct-#{n}" }
73
+ money = ->(cents) { { "cents" => cents, "currency" => "USD" } }
74
+ [
75
+ Step.new("Banking::Account.Open",
76
+ { "number" => number, "kind" => { "name" => "current" },
77
+ "daily_limit" => { "cents" => 50_000 }, "customer" => "CUST-0001" }),
78
+ Step.new("Banking::Account.Credit",
79
+ { "number" => number, "amount" => money.call(10_000), "narrative" => { "text" => "Deposit" } }),
80
+ Step.new("Banking::Account.Credit",
81
+ { "number" => number, "amount" => money.call(5000), "narrative" => { "text" => "Deposit" } }),
82
+ Step.new("Banking::Account.Debit",
83
+ { "number" => number, "amount" => money.call(2500), "narrative" => { "text" => "Groceries" } })
84
+ ]
85
+ })
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,17 @@
1
+ module Hecks
2
+ # Throughput and latency harness behind `hecks bench`; see `docs/benchmarks.md`.
3
+ # Not required by `lib/hecks.rb`: a booted domain never needs it.
4
+ module Bench
5
+ end
6
+ end
7
+
8
+ require_relative "bench/stats"
9
+ require_relative "bench/workload"
10
+ require_relative "bench/environment"
11
+ require_relative "bench/postgres_probe"
12
+ require_relative "bench/run"
13
+ require_relative "bench/ruby_runner"
14
+ require_relative "bench/rust_runner"
15
+ require_relative "bench/suite"
16
+ require_relative "bench/report"
17
+ require_relative "bench/cli"
@@ -17,6 +17,7 @@ module Hecks
17
17
  vision: [:vision, :plain],
18
18
  classification: [:classification, :plain],
19
19
  formerly_known_as: [:formerly_known_as, :plain],
20
+ namespace: [:namespace, :plain],
20
21
  attaches_to: [:attaches_to, :plain],
21
22
  provides: [:provides, :plain]
22
23
  },
@@ -97,6 +98,7 @@ module Hecks
97
98
  wheres: [:wheres, [:each, :where_clause]],
98
99
  order_by: [:order_by, :order_by],
99
100
  limit: [:limit, :limit],
101
+ returns: [:returns, :plain],
100
102
  # Held by the language as an open map, so every one of these reads the
101
103
  # same way and a ninth option needs no new field on either side.
102
104
  offset: [:offset, [:option, :offset]],
@@ -49,7 +49,7 @@ module Hecks
49
49
 
50
50
  # A bare constant in a bluebook is a name, even when Ruby has heard of it.
51
51
  #
52
- # `Facade::Surface` installs aggregate names as top-level constants, which would win
52
+ # `Doors::RubyDoor` installs aggregate names as top-level constants, which would win
53
53
  # over the `const_missing` resolver and silently rebind the attribute. Demodulising
54
54
  # spells `:Target` and `QualityControl::Target` the same.
55
55
  def spell(type)
@@ -64,13 +64,42 @@ module Hecks
64
64
  # none is attached under that name
65
65
  def port(named) = @ports_by_name[named.to_s]
66
66
 
67
+ # Finds every port whose adapter answers a query.
68
+ #
69
+ # @param named [String, Symbol] the query's declared name
70
+ # @return [Array<Bluebook::DomainPort>] the ports the hecksagon binds to the query; empty
71
+ # when the query is answered from the aggregate's records
72
+ def query_bindings(named) = @ports.select { |port| port.answer_for(named) }
73
+
74
+ # Finds the port whose adapter answers a query.
75
+ #
76
+ # @param named [String, Symbol] the query's declared name
77
+ # @return [Bluebook::DomainPort, nil] the first port bound to the query, or `nil` if
78
+ # the hecksagon binds none
79
+ def query_binding(named) = query_bindings(named).first
80
+
67
81
  # Attaches a port declared in the hecksagon, after the aggregate exists.
68
82
  # `HecksagonBuilder` stamps each operation's reference attributes with
69
- # `declared_in = self` before calling it.
83
+ # `declared_in = self` before calling it. A port declared again under a name the
84
+ # aggregate already holds merges into it, so an overlay repeating a base
85
+ # declaration adds nothing twice; two different names stay two ports.
70
86
  #
71
87
  # @param port [Bluebook::DomainPort] the aggregate-scoped port to attach
72
88
  # @return [void]
73
89
  def add_port(port)
90
+ held = @ports_by_name[port.name]
91
+ return attach_port(port) unless held
92
+
93
+ operations = held.operations + port.operations.reject { |op| held.operation(op.hecks_name) }
94
+ answers = held.answered_queries + port.answered_queries.reject { |a| held.answer_for(a.name) }
95
+ merged = Bluebook::DomainPort.new(name: held.name, operations: operations, answered_queries: answers)
96
+ @ports[@ports.index(held)] = merged
97
+ @ports_by_name[merged.name] = merged
98
+ end
99
+
100
+ # @param port [Bluebook::DomainPort] a port under a name the aggregate does not hold
101
+ # @return [void]
102
+ def attach_port(port)
74
103
  @ports << port
75
104
  @ports_by_name[port.name] = port
76
105
  end
@@ -47,6 +47,13 @@ module Hecks
47
47
  # @return [Bluebook::PortOperation, nil] the operation, or `nil` if none is
48
48
  # declared by that name
49
49
  def operation(named) = @operations.find { |op| op.hecks_name == named.to_s }
50
+
51
+ # Finds the binding this port gives a query, if it answers that query.
52
+ #
53
+ # @param named [String, Symbol] the query's declared name
54
+ # @return [Bluebook::QueryAnswer, nil] the binding, or `nil` if this port does not
55
+ # answer that query
56
+ def answer_for(named) = @answered_queries.find { |answer| answer.name == named.to_s }
50
57
  end
51
58
  end
52
59
  end
@@ -24,6 +24,9 @@ module Hecks
24
24
  # differing only by their old name can't hash identically at the
25
25
  # meta-validator's cache key (`SHA256(JSON(bluebook.to_h))`).
26
26
  formerly_known_as: :formerly_known_as,
27
+ # The Ruby module the chapter's constants install under, when it is not the chapter's
28
+ # own name (the Hecks domain nests under `Hecks::Domain`); nil for every other chapter.
29
+ namespace: :namespace,
27
30
  aggregates: many(:aggregates),
28
31
  read_models: many(:read_models),
29
32
  policies: many(:policies),
@@ -53,7 +56,7 @@ module Hecks
53
56
  end
54
57
 
55
58
  attr_reader :name, :version, :vision, :aggregates, :policies, :process_managers,
56
- :classification, :read_models, :ports, :formerly_known_as, :attaches_to, :provides
59
+ :classification, :read_models, :ports, :formerly_known_as, :namespace, :attaches_to, :provides
57
60
 
58
61
  # @param policies [Array<Bluebook::Policy>] every reaction declared across the
59
62
  # chapter's own aggregates, hoisted here
@@ -61,7 +64,7 @@ module Hecks
61
64
  # its project's own domain model, or `nil` if undeclared
62
65
  def initialize(name:, version: nil, vision: nil, aggregates: [], policies: [],
63
66
  process_managers: [], classification: nil, read_models: [], formerly_known_as: nil,
64
- attaches_to: [], provides: [])
67
+ namespace: nil, attaches_to: [], provides: [])
65
68
  @policies = policies
66
69
  @process_managers = process_managers
67
70
  @name = name.to_s
@@ -73,6 +76,7 @@ module Hecks
73
76
  @read_models = read_models
74
77
  @classification = classification&.to_s
75
78
  @formerly_known_as = formerly_known_as&.to_s
79
+ @namespace = namespace&.to_s
76
80
  @attaches_to = Array(attaches_to).map(&:to_s)
77
81
  @provides = Array(provides).map { |row| Provision.from(row) }
78
82
  settle
@@ -58,20 +58,51 @@ module Hecks
58
58
  end
59
59
  end
60
60
 
61
- # A named group of operations an aggregate exposes to whatever adapter calls in.
61
+ # A query the hecksagon binds to a port's adapter: the bluebook declares the question and the
62
+ # value object its answer takes (`returns`), and this declares that something outside the
63
+ # domain answers it.
64
+ class QueryAnswer
65
+ include Hecks::IR
66
+
67
+ emits_ir(name: :name)
68
+
69
+ attr_reader :name
70
+
71
+ # @param name [String, Symbol] the bound query's declared name
72
+ def initialize(name:)
73
+ @name = name.to_s
74
+ end
75
+ end
76
+
77
+ # A named group of operations an aggregate exposes to whatever adapter calls in, plus the
78
+ # queries its adapter answers.
62
79
  class DomainPort
63
80
  include Hecks::IR
64
81
  include Behaviour::DomainPort
65
82
 
66
83
  emits_ir(name: :name, operations: many(:operations))
67
84
 
68
- attr_reader :name, :operations
85
+ attr_reader :name, :operations, :answered_queries
69
86
 
70
87
  # @param name [String, Symbol] the port's declared name
71
88
  # @param operations [Array<Bluebook::PortOperation>] the port's declared operations
72
- def initialize(name:, operations: [])
73
- @name = name.to_s
74
- @operations = operations
89
+ # @param answered_queries [Array<Bluebook::QueryAnswer>] the queries this port's adapter
90
+ # answers instead of the aggregate's stored records
91
+ def initialize(name:, operations: [], answered_queries: [])
92
+ @name = name.to_s
93
+ @operations = operations
94
+ @answered_queries = answered_queries
95
+ end
96
+
97
+ # `answered_queries` is merged in only when there are some, so a port that binds no query
98
+ # emits the shape it always did.
99
+ #
100
+ # @return [Hash] the port's IR
101
+ def to_h
102
+ shape = super
103
+ return shape if @answered_queries.empty?
104
+
105
+ shape.merge(answered_queries: @answered_queries.map(&:to_h))
75
106
  end
76
107
  end
77
108
  end
@@ -14,6 +14,7 @@ module Hecks
14
14
  # @raise [Bluebook::DSL::ProcessManagerBuilder::InvalidProcessManager] if a
15
15
  # `correlates_by` resolves to something other than a scalar field
16
16
  def validate_assembled!(bluebook)
17
+ validate_not_the_gem_module!(bluebook)
17
18
  # an attribute type references its Shape, so an undeclared value object
18
19
  # fails resolution
19
20
  validate_reference_value_objects!(bluebook.aggregates)
@@ -289,6 +290,15 @@ module Hecks
289
290
  # chapter level because seeing a cycle needs every end declared (ADR 0025). Catches
290
291
  # any ring length; self-reference stays legal, and a cross-chapter target is a
291
292
  # dangling name, not an edge.
293
+ # A chapter's constants install under its name, or its `namespace`; landing in the
294
+ # gem's own `Hecks` module would shadow the gem's modules (ADR 0080), so it refuses.
295
+ def validate_not_the_gem_module!(bluebook)
296
+ return unless (bluebook.namespace || bluebook.name) == "Hecks"
297
+
298
+ raise Malformed, "#{bluebook.name}'s constants would install in the gem's own Hecks module — " \
299
+ "declare a namespace, such as namespace \"Hecks::Domain\", or rename the chapter"
300
+ end
301
+
292
302
  def validate_no_bidirectional_references!(aggregates)
293
303
  edges = aggregates.to_h do |aggregate|
294
304
  [aggregate.hecks_name, aggregate.reference_targets.uniq.reject { |target| target == aggregate.hecks_name }]
@@ -65,6 +65,13 @@ module Hecks
65
65
  # @return [String] `value`, stringified, as stored
66
66
  def formerly_known_as(value) = @formerly_known_as = value.to_s
67
67
 
68
+ # Names the Ruby module the chapter's constants install under instead of its own name,
69
+ # such as `namespace "Hecks::Domain"` for a chapter that would otherwise collide.
70
+ #
71
+ # @param value [String] a constant path, such as `"Hecks::Domain"`
72
+ # @return [String] `value`, stringified, as stored
73
+ def namespace(value) = @namespace = value.to_s
74
+
68
75
  # Names a core grammar context this chapter's sub-language extends (ADR 0026).
69
76
  # Variadic and accumulates across calls, like `identified_by`/`group_by`.
70
77
  #
@@ -197,6 +204,7 @@ module Hecks
197
204
  process_managers: @process_managers,
198
205
  classification: @classification,
199
206
  formerly_known_as: @formerly_known_as,
207
+ namespace: @namespace,
200
208
  attaches_to: @attaches_to || [],
201
209
  provides: @provides || [])
202
210
 
@@ -3,7 +3,7 @@
3
3
  # KeywordSeed under lib/hecks/language/).
4
4
  #
5
5
  # Do not edit. spec/bootstrap_table_spec.rb re-projects this in memory
6
- # and refuses a diff — run bin/project_bootstrap_table instead.
6
+ # and refuses a diff — run hecks project_bootstrap_table instead.
7
7
  #
8
8
  # Plain data, no requires: this is read while the grammar table it was
9
9
  # projected from is still being built (`MetaValidator.bootstrapping?`).
@@ -88,6 +88,7 @@ module Hecks
88
88
  ["DomainPort", "operation"] => :tells_impl,
89
89
  ["DomainPort", "tells"] => :tells_impl,
90
90
  ["DomainPort", "asks"] => :asks_impl,
91
+ ["DomainPort", "answers_query"] => :answers_query_impl,
91
92
  ["Hecksagon", "port"] => :port_impl,
92
93
  ["PortOperation", "reference_to"] => :reference_to_impl,
93
94
  ["PortOperation", "attribute"] => :attribute_impl,
@@ -23,6 +23,7 @@ module Hecks
23
23
  @operations = []
24
24
  @signal = :reply
25
25
  @answers = []
26
+ @answered_queries = []
26
27
  @legacy_bare_port = legacy_bare_port
27
28
  end
28
29
 
@@ -58,6 +59,30 @@ module Hecks
58
59
  @operations << PortOperationBuilder.build(name, to: to, owner: @owner, direction: :outbound, &)
59
60
  end
60
61
 
62
+ # Declares that this port's adapter answers one of the owning aggregate's queries.
63
+ #
64
+ # The bluebook declares the question and the shape of its answer (`returns`) and never
65
+ # says who answers it; this binds it. The adapter is asked by the query's snake-cased
66
+ # name with its arguments. The aggregate's stored records are never read.
67
+ #
68
+ # @param name [String] the query's name, as the aggregate declares it
69
+ # @param removed [Hash] any keyword at all; the binding takes only the name
70
+ # @return [Array<Bluebook::QueryAnswer>] every binding declared so far, this one last
71
+ # @raise [Bluebook::DSL::Malformed] if a keyword such as `shape:` is written, or the query
72
+ # is already bound on this port
73
+ def answers_query_impl(name, **removed)
74
+ unless removed.empty?
75
+ raise Malformed, "answers_query #{name.inspect} takes only the query's name — the shape " \
76
+ "of its answer is the query's own `returns`, declared in the bluebook " \
77
+ "(#{removed.keys.join(', ')} is not a word here)"
78
+ end
79
+ if @answered_queries.any? { |answer| answer.name == name.to_s }
80
+ raise Malformed, "#{@name} binds #{name} twice — a query has one answer"
81
+ end
82
+
83
+ @answered_queries << QueryAnswer.new(name: name)
84
+ end
85
+
61
86
  # Names the verb aggregates call this port by, making it a driven `Port` rather than a
62
87
  # `DomainPort` of operations. A port is one shape or the other, never both.
63
88
  #
@@ -83,10 +108,11 @@ module Hecks
83
108
  #
84
109
  # @return [Bluebook::Port, Bluebook::DomainPort] a `Port` when the body named a `verb`
85
110
  # (or was empty under `legacy_bare_port:`), otherwise a `DomainPort` of its operations
111
+ # and answered queries
86
112
  # @raise [Bluebook::DSL::Malformed] if the body declares both a verb and operations,
87
113
  # declares neither without `legacy_bare_port:`, or the port language refuses the `Port`
88
114
  def build
89
- if @verb && !@operations.empty?
115
+ if @verb && !(@operations.empty? && @answered_queries.empty?)
90
116
  raise Malformed,
91
117
  "#{@name} declares both a verb and operations — a port is one or the other, not both"
92
118
  end
@@ -96,9 +122,11 @@ module Hecks
96
122
  answers: @answers))
97
123
  end
98
124
 
99
- raise Malformed, "#{@name} declares no verb and no operations" if @operations.empty?
125
+ if @operations.empty? && @answered_queries.empty?
126
+ raise Malformed, "#{@name} declares no verb and no operations, and answers no query"
127
+ end
100
128
 
101
- DomainPort.new(name: @name, operations: @operations)
129
+ DomainPort.new(name: @name, operations: @operations, answered_queries: @answered_queries)
102
130
  end
103
131
 
104
132
  # Evaluates a `port` block against a fresh builder and returns whichever shape it declared.
@@ -10,10 +10,54 @@ module Hecks
10
10
  include WordGate
11
11
 
12
12
  class << self
13
- attr_accessor :collector
13
+ # The bind collector of the build running on this thread; thread-local, so a build
14
+ # on another thread never sees it.
15
+ #
16
+ # @return [Array, nil] the binds the running build collects into
17
+ def collector = Thread.current.thread_variable_get(:hecks_hecksagon_collector)
18
+
19
+ # @param value [Array, nil] the binds the running build collects into
20
+ def collector=(value)
21
+ Thread.current.thread_variable_set(:hecks_hecksagon_collector, value)
22
+ end
23
+
24
+ # @return [String, nil] the name of the chapter whose hecksagon is being built
25
+ def building = Thread.current.thread_variable_get(:hecks_hecksagon_building)
26
+
27
+ # @param value [String, nil] the name of the chapter whose hecksagon is being built
28
+ def building=(value)
29
+ Thread.current.thread_variable_set(:hecks_hecksagon_building, value)
30
+ end
31
+
32
+ # @return [Hash, nil] the modules this thread's build has taken off `Hecks`
33
+ def hidden_modules = Thread.current.thread_variable_get(:hecks_hecksagon_hidden)
34
+
35
+ # @param value [Hash, nil] the modules this thread's build has taken off `Hecks`
36
+ def hidden_modules=(value)
37
+ Thread.current.thread_variable_set(:hecks_hecksagon_hidden, value)
38
+ end
14
39
  end
15
40
 
16
- attr_reader :binds, :subscriptions, :framework_members, :vendored_bluebooks
41
+ # Lets the Hecks chapter's own hecksagon spell its aggregates `Hecks::Operation`.
42
+ #
43
+ # While the chapter named Hecks builds its hecksagon, a missing constant on `Hecks` is
44
+ # one of that chapter's aggregates; at any other time it is the ordinary `NameError`.
45
+ #
46
+ # A real module of the same name (`Hecks::Release`) would answer first, so
47
+ # `HecksagonBuilder.build` takes each off `Hecks` while the block runs (see `shadowed`).
48
+ # `attaches` loads whole chapters inside the block; the shadow lifts around each load
49
+ # (see `with_real_modules`), so loaded code sees the real modules.
50
+ module ChapterConstants
51
+ # @param name [Symbol] the missing constant
52
+ # @return [Bluebook::DSL::BindingProxy] a proxy for that aggregate while building
53
+ def const_missing(name)
54
+ return super unless HecksagonBuilder.building == "Hecks" && HecksagonBuilder.collector
55
+
56
+ BindingProxy.new("Hecks::#{name}", HecksagonBuilder.collector)
57
+ end
58
+ end
59
+
60
+ attr_reader :binds, :subscriptions, :framework_members, :vendored_bluebooks, :attached_chapters
17
61
 
18
62
  def initialize(domain)
19
63
  @domain = domain
@@ -21,6 +65,7 @@ module Hecks
21
65
  @subscriptions = []
22
66
  @framework_members = []
23
67
  @vendored_bluebooks = []
68
+ @attached_chapters = []
24
69
  @bounded = false
25
70
  @translates = []
26
71
  end
@@ -54,6 +99,17 @@ module Hecks
54
99
  Hecks.current_registry&.mark_bounded(Hecks::Naming.pascal(name.to_s))
55
100
  end
56
101
 
102
+ # Attaches a chapter the gem carries (the language, Expression, Tenancy, Deploy,
103
+ # QualityControl) by name and loads its files into the registry, with the ports and
104
+ # adapters it ships. Marked bounded like a framework member, since this hecksagon is its
105
+ # anti-corruption layer.
106
+ def attaches(name)
107
+ require_relative "../../chapters"
108
+ @attached_chapters << name.to_s
109
+ self.class.with_real_modules { Hecks::Chapters.load!(name) }
110
+ Hecks.current_registry&.mark_bounded(name.to_s)
111
+ end
112
+
57
113
  # Declares a port at the hecksagon's root — the chapter as a whole, not one aggregate.
58
114
  def port_impl(name, &block)
59
115
  bluebook_ir = Hecks.current_registry.bluebook(@domain) or
@@ -90,7 +146,7 @@ module Hecks
90
146
  def build
91
147
  Hecksagon.new(domain: @domain, binds: @binds, subscriptions: @subscriptions,
92
148
  framework_members: @framework_members, vendored_bluebooks: @vendored_bluebooks,
93
- bounded: @bounded, translates: @translates)
149
+ attached_chapters: @attached_chapters, bounded: @bounded, translates: @translates)
94
150
  end
95
151
 
96
152
  # Records any verb the grammar doesn't own as a domain-wide default bind, e.g. bare
@@ -117,16 +173,79 @@ module Hecks
117
173
  resolver = ->(name) { BindingProxy.namespace(name, builder.binds) }
118
174
 
119
175
  previous = collector
176
+ previous_name = building
120
177
  self.collector = builder.binds
178
+ self.building = domain.to_s
179
+ previous_hidden = hidden_modules
180
+ hidden = previous_name == "Hecks" ? {} : shadowed(domain)
181
+ self.hidden_modules = hidden.empty? ? previous_hidden : hidden
121
182
  begin
122
183
  ConstShim.with(resolver) { builder.instance_eval(&block) } if block
123
184
  ensure
185
+ restore_shadowed(hidden)
186
+ self.hidden_modules = previous_hidden
124
187
  self.collector = previous
188
+ self.building = previous_name
125
189
  end
126
190
 
127
191
  builder.build
128
192
  end
193
+
194
+ # Takes off `Hecks` each real module a chapter aggregate shares its name with, so the
195
+ # name reaches `ChapterConstants#const_missing`. Only the chapter named Hecks has this
196
+ # collision; a nested build of it leaves the outer build's shadows in place.
197
+ #
198
+ # @param domain [String, Symbol] the chapter whose hecksagon is being built
199
+ # @return [Hash{Symbol => Object}] what was taken off, by name: the module, or the
200
+ # autoload path when the constant had not loaded yet
201
+ def self.shadowed(domain)
202
+ return {} unless domain.to_s == "Hecks"
203
+
204
+ chapter = Hecks.current_registry&.bluebook("Hecks")
205
+ names = chapter ? chapter.aggregates.map { |aggregate| aggregate.hecks_name.to_sym } : []
206
+ names.select { |name| Hecks.const_defined?(name, false) }.to_h do |name|
207
+ pending = Hecks.autoload?(name)
208
+ held = pending ? [:autoload, pending] : [:module, Hecks.const_get(name, false)]
209
+ Hecks.send(:remove_const, name)
210
+ [name, held]
211
+ end
212
+ end
213
+
214
+ # Runs the block with every module the running build shadowed back on `Hecks`, and a
215
+ # missing `Hecks::X` an ordinary `NameError`, then shadows them again. A chapter loaded
216
+ # from inside the hecksagon block is ordinary code, not the hecksagon's own vocabulary.
217
+ # A no-op when nothing is shadowed.
218
+ #
219
+ # @yield the code that must see `Hecks`'s real modules
220
+ # @return [Object] the block's result
221
+ def self.with_real_modules
222
+ hidden = hidden_modules
223
+ return yield if hidden.nil? || hidden.empty?
224
+
225
+ name = building
226
+ restore_shadowed(hidden)
227
+ self.building = nil
228
+ begin
229
+ yield
230
+ ensure
231
+ self.building = name
232
+ hidden.replace(shadowed(name))
233
+ end
234
+ end
235
+
236
+ # Puts back what `shadowed` took off.
237
+ #
238
+ # @param hidden [Hash{Symbol => Array}] `shadowed`'s answer
239
+ # @return [void]
240
+ def self.restore_shadowed(hidden)
241
+ hidden.each do |name, (kind, held)|
242
+ Hecks.send(:remove_const, name) if Hecks.const_defined?(name, false)
243
+ kind == :autoload ? Hecks.autoload(name, held) : Hecks.const_set(name, held)
244
+ end
245
+ end
129
246
  end
130
247
  end
131
248
  end
132
249
  end
250
+
251
+ Hecks.singleton_class.prepend(Hecks::Bluebook::DSL::HecksagonBuilder::ChapterConstants)
@@ -64,7 +64,7 @@ module Hecks
64
64
 
65
65
  # C5.3 (docs/semantics/bluebook-semantics.md): overlapping `from:` sets for one command
66
66
  # are refused. Disjoint sets are legitimate. An undeclared `from:` state is left to
67
- # `bin/model_check`.
67
+ # `hecks model_check`.
68
68
  def refuse_ambiguity!
69
69
  return if MetaValidator.shadow_parsing? # frozen era text is exempt
70
70
 
@@ -27,6 +27,31 @@ module Hecks
27
27
  # @return [String] the description as stored
28
28
  def description(value) = @description = value
29
29
 
30
+ # Names the value object of the owning aggregate that shapes this query's answer.
31
+ #
32
+ # The bluebook owns the meaning of an answer: a query that returns a value object is
33
+ # answered by something outside the domain (the hecksagon binds it), and each row of
34
+ # that answer is built as the value object, so a wrong-shaped answer is refused before
35
+ # it enters the domain. A query that returns nothing answers with the aggregate's own row.
36
+ #
37
+ # @param type [Module, AttributeCollector::ListOf] a bare value-object constant, or
38
+ # `list_of(Constant)` when the answer is many rows
39
+ # @return [String] the return type as stored: `"Name"` or `"list_of(Name)"`
40
+ # @raise [Bluebook::DSL::Malformed] if the query already returns, or the type is quoted text
41
+ def returns(type)
42
+ raise Malformed, "#{@name} declares returns twice — a query answers in one shape" if @returns
43
+
44
+ list = type.is_a?(AttributeCollector::ListOf)
45
+ inner = list ? type.type : type
46
+ if inner.is_a?(::String)
47
+ raise Malformed, "#{@name}'s returns #{inner.inspect} is quoted text — give the bare " \
48
+ "constant (#{inner}) instead"
49
+ end
50
+
51
+ name = Naming.demodulise(inner)
52
+ @returns = list ? "list_of(#{name})" : name.to_s
53
+ end
54
+
30
55
  # Declares a query parameter that names another aggregate's identity.
31
56
  #
32
57
  # A plain attribute typed as a reference; a query has no root of its own to act on.
@@ -61,7 +86,8 @@ module Hecks
61
86
  cursor: @cursor,
62
87
  authorization: @authorization,
63
88
  null_semantics: @null_semantics,
64
- inspection: @inspection
89
+ inspection: @inspection,
90
+ returns: @returns
65
91
  )
66
92
  end
67
93
 
@@ -55,7 +55,7 @@ module Hecks
55
55
  #
56
56
  # Projected ahead of time into bootstrap_table.rb for the bootstrap window before
57
57
  # the grammar table itself is built (MetaValidator.bootstrapping?); kept in sync by
58
- # bin/project_bootstrap_table, pinned by spec/bootstrap_table_spec.rb.
58
+ # hecks project_bootstrap_table, pinned by spec/bootstrap_table_spec.rb.
59
59
  BOOTSTRAP_FALLBACK = BootstrapTable::RESOLVES
60
60
 
61
61
  # Reads how a (word, context) pair resolves rule references, off the self-hosted