hecks 2.8.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 (886) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +50 -7
  3. data/lib/hecks/adapters/driven/claude_code.rb +4 -104
  4. data/lib/hecks/adapters/driven/d1.rb +44 -237
  5. data/lib/hecks/adapters/driven/folder.rb +17 -76
  6. data/lib/hecks/adapters/driven/google_authentication.rb +15 -49
  7. data/lib/hecks/adapters/driven/governance_authorization.rb +23 -70
  8. data/lib/hecks/adapters/driven/heki/journal.rb +8 -41
  9. data/lib/hecks/adapters/driven/heki/saga_store.rb +12 -54
  10. data/lib/hecks/adapters/driven/heki/snapshot.rb +4 -12
  11. data/lib/hecks/adapters/driven/heki.rb +13 -42
  12. data/lib/hecks/adapters/driven/identity_registry.rb +3 -14
  13. data/lib/hecks/adapters/driven/in_memory_ordering.rb +9 -26
  14. data/lib/hecks/adapters/driven/in_process_key_vault.rb +2 -14
  15. data/lib/hecks/adapters/driven/lambda/client.rb +10 -51
  16. data/lib/hecks/adapters/driven/lambda.rb +10 -84
  17. data/lib/hecks/adapters/driven/local_storage.rb +5 -40
  18. data/lib/hecks/adapters/driven/memory.rb +32 -100
  19. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +5 -21
  20. data/lib/hecks/adapters/driven/postgres/codec.rb +6 -28
  21. data/lib/hecks/adapters/driven/postgres/outbox.rb +3 -12
  22. data/lib/hecks/adapters/driven/postgres/reconnect.rb +10 -32
  23. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +34 -108
  24. data/lib/hecks/adapters/driven/postgres/shared_connection.rb +180 -0
  25. data/lib/hecks/adapters/driven/postgres.rb +149 -136
  26. data/lib/hecks/adapters/driven/postgres_era.adapter +4 -15
  27. data/lib/hecks/adapters/driven/prism.rb +48 -38
  28. data/lib/hecks/adapters/driven/sql_query_builder.rb +21 -121
  29. data/lib/hecks/adapters/driven/sqlite/codec.rb +8 -40
  30. data/lib/hecks/adapters/driven/sqlite/projection.rb +26 -82
  31. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +40 -84
  32. data/lib/hecks/adapters/driven/sqlite.rb +128 -65
  33. data/lib/hecks/adapters/driven/tenant_provisioner.rb +76 -62
  34. data/lib/hecks/adapters/driven.rb +5 -36
  35. data/lib/hecks/adapters/driving/github_webhook.rb +8 -76
  36. data/lib/hecks/behaviors/dsl.rb +7 -25
  37. data/lib/hecks/behaviors/expectations.rb +32 -142
  38. data/lib/hecks/behaviors/ir.rb +7 -20
  39. data/lib/hecks/behaviors/rspec.rb +6 -17
  40. data/lib/hecks/behaviors/runner.rb +10 -34
  41. data/lib/hecks/behaviors.rb +4 -8
  42. data/lib/hecks/bench/cli.rb +62 -0
  43. data/lib/hecks/bench/environment.rb +71 -0
  44. data/lib/hecks/bench/postgres_probe.rb +30 -0
  45. data/lib/hecks/bench/report.rb +76 -0
  46. data/lib/hecks/bench/ruby_runner.rb +93 -0
  47. data/lib/hecks/bench/run.rb +55 -0
  48. data/lib/hecks/bench/rust_runner.rb +101 -0
  49. data/lib/hecks/bench/stats.rb +46 -0
  50. data/lib/hecks/bench/suite.rb +98 -0
  51. data/lib/hecks/bench/workload.rb +89 -0
  52. data/lib/hecks/bench.rb +17 -0
  53. data/lib/hecks/bluebook/aggregate.rb +9 -75
  54. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +9 -38
  55. data/lib/hecks/bluebook/assembly/build.rb +4 -15
  56. data/lib/hecks/bluebook/assembly/contract.rb +15 -136
  57. data/lib/hecks/bluebook/assembly/contracts.rb +56 -174
  58. data/lib/hecks/bluebook/assembly/marks.rb +31 -223
  59. data/lib/hecks/bluebook/assembly/specializer.rb +6 -52
  60. data/lib/hecks/bluebook/assembly.rb +6 -45
  61. data/lib/hecks/bluebook/attribute.rb +12 -61
  62. data/lib/hecks/bluebook/behaviour/aggregate.rb +39 -39
  63. data/lib/hecks/bluebook/behaviour/attribute.rb +3 -14
  64. data/lib/hecks/bluebook/behaviour/chapter.rb +14 -42
  65. data/lib/hecks/bluebook/behaviour/command.rb +14 -66
  66. data/lib/hecks/bluebook/behaviour/domain_port.rb +11 -20
  67. data/lib/hecks/bluebook/behaviour/entity.rb +7 -27
  68. data/lib/hecks/bluebook/behaviour/hexagon.rb +23 -41
  69. data/lib/hecks/bluebook/behaviour/lifecycle.rb +15 -40
  70. data/lib/hecks/bluebook/behaviour/policy.rb +23 -52
  71. data/lib/hecks/bluebook/behaviour/process_manager.rb +30 -57
  72. data/lib/hecks/bluebook/behaviour/read_model.rb +25 -31
  73. data/lib/hecks/bluebook/behaviour/traits.rb +27 -64
  74. data/lib/hecks/bluebook/behaviour/value_object.rb +5 -24
  75. data/lib/hecks/bluebook/capabilities.rb +17 -49
  76. data/lib/hecks/bluebook/chapter.rb +16 -50
  77. data/lib/hecks/bluebook/command.rb +23 -81
  78. data/lib/hecks/bluebook/domain_port.rb +49 -67
  79. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +50 -229
  80. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +50 -377
  81. data/lib/hecks/bluebook/dsl/attribute_collector.rb +33 -204
  82. data/lib/hecks/bluebook/dsl/binding_proxy.rb +16 -69
  83. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +96 -708
  84. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +63 -169
  85. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +3 -1
  86. data/lib/hecks/bluebook/dsl/command_builder.rb +74 -546
  87. data/lib/hecks/bluebook/dsl/const_shim.rb +8 -41
  88. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +42 -89
  89. data/lib/hecks/bluebook/dsl/entity_builder.rb +44 -330
  90. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +20 -269
  91. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +148 -197
  92. data/lib/hecks/bluebook/dsl/identity_declaration.rb +13 -88
  93. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +6 -20
  94. data/lib/hecks/bluebook/dsl/policy_builder.rb +16 -88
  95. data/lib/hecks/bluebook/dsl/port_builder.rb +2 -6
  96. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +22 -96
  97. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +31 -184
  98. data/lib/hecks/bluebook/dsl/query_builder.rb +38 -43
  99. data/lib/hecks/bluebook/dsl/read_model_builder.rb +75 -201
  100. data/lib/hecks/bluebook/dsl/rule_reference.rb +22 -163
  101. data/lib/hecks/bluebook/dsl/translation_builder.rb +28 -114
  102. data/lib/hecks/bluebook/dsl/value_object_builder.rb +10 -124
  103. data/lib/hecks/bluebook/dsl/word_gate.rb +16 -162
  104. data/lib/hecks/bluebook/dsl/world_builder.rb +46 -111
  105. data/lib/hecks/bluebook/entity.rb +13 -71
  106. data/lib/hecks/bluebook/expression/ast_json.rb +24 -190
  107. data/lib/hecks/bluebook/expression/ast_reader.rb +12 -54
  108. data/lib/hecks/bluebook/expression/canonical_form.rb +11 -88
  109. data/lib/hecks/bluebook/expression/evaluator.rb +47 -295
  110. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +30 -147
  111. data/lib/hecks/bluebook/expression/resolver.rb +39 -661
  112. data/lib/hecks/bluebook/hexagon.rb +18 -33
  113. data/lib/hecks/bluebook/lifecycle.rb +5 -16
  114. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -7
  115. data/lib/hecks/bluebook/meta_validator/judge.rb +111 -361
  116. data/lib/hecks/bluebook/meta_validator/plan.rb +33 -162
  117. data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -7
  118. data/lib/hecks/bluebook/meta_validator/readings.rb +56 -342
  119. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +25 -176
  120. data/lib/hecks/bluebook/meta_validator/shapes.rb +53 -267
  121. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +47 -306
  122. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +7 -43
  123. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +246 -0
  124. data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -18
  125. data/lib/hecks/bluebook/meta_validator.rb +87 -352
  126. data/lib/hecks/bluebook/model_check/client_profile.rb +6 -21
  127. data/lib/hecks/bluebook/model_check.rb +122 -596
  128. data/lib/hecks/bluebook/pattern_subset.rb +49 -69
  129. data/lib/hecks/bluebook/process_manager.rb +13 -83
  130. data/lib/hecks/bluebook/project_loader.rb +3 -39
  131. data/lib/hecks/bluebook/project_register.rb +12 -60
  132. data/lib/hecks/bluebook/query.rb +27 -51
  133. data/lib/hecks/bluebook/read_model.rb +57 -81
  134. data/lib/hecks/bluebook/reference.rb +7 -41
  135. data/lib/hecks/bluebook/smoke_test.rb +9 -99
  136. data/lib/hecks/bluebook/synthesizer.rb +13 -62
  137. data/lib/hecks/bluebook/translation.rb +26 -62
  138. data/lib/hecks/bluebook/value_object.rb +11 -36
  139. data/lib/hecks/bluebook.rb +5 -22
  140. data/lib/hecks/cache_dir.rb +4 -23
  141. data/lib/hecks/canonical_json.rb +30 -0
  142. data/lib/hecks/chapters.rb +102 -0
  143. data/lib/hecks/cli/behaviors.rb +61 -0
  144. data/lib/hecks/cli/canonicalise.rb +25 -0
  145. data/lib/hecks/cli/check_engine_agreement.rb +32 -0
  146. data/lib/hecks/cli/check_era.rb +88 -0
  147. data/lib/hecks/cli/console.rb +66 -0
  148. data/lib/hecks/cli/deploy_template_diff.rb +40 -0
  149. data/lib/hecks/cli/doc_coverage.rb +32 -0
  150. data/lib/hecks/cli/document.rb +5 -23
  151. data/lib/hecks/cli/expression_projection.rb +41 -0
  152. data/lib/hecks/cli/follow.rb +88 -0
  153. data/lib/hecks/cli/history.rb +44 -0
  154. data/lib/hecks/cli/ir.rb +4 -6
  155. data/lib/hecks/cli/mcp.rb +5 -6
  156. data/lib/hecks/cli/model_check.rb +24 -88
  157. data/lib/hecks/cli/pattern_cases.rb +47 -0
  158. data/lib/hecks/cli/present.rb +39 -0
  159. data/lib/hecks/cli/project_cli.rb +223 -64
  160. data/lib/hecks/cli/project_diagrams.rb +4 -10
  161. data/lib/hecks/cli/project_glossary.rb +44 -0
  162. data/lib/hecks/cli/reference.rb +46 -0
  163. data/lib/hecks/cli/refresh_projections.rb +44 -0
  164. data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +150 -0
  165. data/lib/hecks/cli/release.rb +70 -0
  166. data/lib/hecks/cli/release_gem.rb +59 -0
  167. data/lib/hecks/cli/rspec_io_parallel_files.rb +137 -0
  168. data/lib/hecks/cli/rspec_shard_files.rb +66 -0
  169. data/lib/hecks/cli/run.rb +15 -61
  170. data/lib/hecks/cli/seed_semantics_corpus.rb +71 -0
  171. data/lib/hecks/cli/serve_query_ir_mcp.rb +30 -0
  172. data/lib/hecks/cli/shape.rb +70 -0
  173. data/lib/hecks/cli/smoke_http.rb +230 -0
  174. data/lib/hecks/cli/smoke_test.rb +7 -19
  175. data/lib/hecks/cli/spec_example.rb +27 -0
  176. data/lib/hecks/cli/statements.rb +32 -0
  177. data/lib/hecks/cli/stores.rb +4 -6
  178. data/lib/hecks/cli/stress_concurrency_specs.rb +177 -0
  179. data/lib/hecks/cli.rb +216 -73
  180. data/lib/hecks/codemod.rb +324 -0
  181. data/lib/hecks/construct.rb +8 -34
  182. data/lib/hecks/corpus.rb +423 -0
  183. data/lib/hecks/deploy/bluebook/deploy.bluebook +617 -158
  184. data/lib/hecks/deploy/bluebook/deploy.hecksagon +41 -11
  185. data/lib/hecks/deploy/oidc.json +82 -2
  186. data/lib/hecks/doc/reference.rb +351 -0
  187. data/lib/hecks/doors/cli_door.rb +196 -0
  188. data/lib/hecks/doors/cli_runner.rb +297 -0
  189. data/lib/hecks/{facade → doors}/command_request.rb +14 -34
  190. data/lib/hecks/doors/handle.rb +206 -0
  191. data/lib/hecks/doors/json_door.rb +132 -0
  192. data/lib/hecks/doors/launcher_options.rb +129 -0
  193. data/lib/hecks/{cli → doors}/mcp_door.rb +25 -120
  194. data/lib/hecks/doors/mcp_door_scope.rb +97 -0
  195. data/lib/hecks/doors/ruby_door/aggregate_door.rb +162 -0
  196. data/lib/hecks/doors/ruby_door/chapter.rb +72 -0
  197. data/lib/hecks/doors/ruby_door.rb +77 -0
  198. data/lib/hecks/doors.rb +61 -0
  199. data/lib/hecks/embryonaut_bluebook/lock.rb +3 -35
  200. data/lib/hecks/embryonaut_bluebook/shape.rb +1 -17
  201. data/lib/hecks/embryonaut_bluebook/vendor.rb +8 -53
  202. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +4 -19
  203. data/lib/hecks/embryonaut_bluebook.rb +5 -72
  204. data/lib/hecks/engine_agreement.rb +156 -0
  205. data/lib/hecks/facade.rb +23 -51
  206. data/lib/hecks/forms/app.rb +21 -76
  207. data/lib/hecks/forms/banking_presentation.rb +50 -0
  208. data/lib/hecks/forms/command_form_renderer.rb +10 -29
  209. data/lib/hecks/forms/field_renderer.rb +17 -156
  210. data/lib/hecks/forms/field_shape.rb +31 -222
  211. data/lib/hecks/forms/html.rb +12 -47
  212. data/lib/hecks/forms/index_renderer.rb +1 -4
  213. data/lib/hecks/forms/page.rb +3 -9
  214. data/lib/hecks/forms/params.rb +14 -60
  215. data/lib/hecks/forms/port_argument.rb +2 -19
  216. data/lib/hecks/forms/query_form_renderer.rb +4 -79
  217. data/lib/hecks/forms/record_renderer.rb +8 -75
  218. data/lib/hecks/forms/record_table.rb +7 -50
  219. data/lib/hecks/forms/reference_options.rb +3 -30
  220. data/lib/hecks/forms/value_object_shape.rb +8 -35
  221. data/lib/hecks/forms.rb +5 -33
  222. data/lib/hecks/fqn.rb +5 -12
  223. data/lib/hecks/framework/bluebook/compliance.bluebook +16 -65
  224. data/lib/hecks/framework/bluebook/console_settings.bluebook +47 -134
  225. data/lib/hecks/framework/bluebook/framework.hecksagon +5 -14
  226. data/lib/hecks/framework/bluebook/governance.bluebook +13 -34
  227. data/lib/hecks/framework/bluebook/identity.bluebook +11 -26
  228. data/lib/hecks/framework/bluebook/privacy.bluebook +8 -33
  229. data/lib/hecks/framework.rb +11 -80
  230. data/lib/hecks/freezer.rb +5 -39
  231. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +393 -0
  232. data/lib/hecks/fuzzing/combination_miner.rb +86 -0
  233. data/lib/hecks/fuzzing/concurrent_dispatch.rb +214 -0
  234. data/lib/hecks/fuzzing/coverage_campaign.rb +143 -0
  235. data/lib/hecks/fuzzing/differential.rb +156 -0
  236. data/lib/hecks/fuzzing/domain_generator.rb +615 -0
  237. data/lib/hecks/fuzzing/era_boundary.rb +71 -0
  238. data/lib/hecks/fuzzing/form_census.rb +136 -0
  239. data/lib/hecks/fuzzing/generated_domain_check.rb +93 -0
  240. data/lib/hecks/fuzzing/invalid_value_generator.rb +64 -0
  241. data/lib/hecks/fuzzing/isolated_boot.rb +301 -0
  242. data/lib/hecks/fuzzing/nondeterministic.rb +51 -0
  243. data/lib/hecks/fuzzing/outside_world.rb +45 -0
  244. data/lib/hecks/fuzzing/persistence_parity.rb +102 -0
  245. data/lib/hecks/fuzzing/properties/corrections.rb +88 -0
  246. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +314 -0
  247. data/lib/hecks/fuzzing/properties/guards.rb +190 -0
  248. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +300 -0
  249. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +93 -0
  250. data/lib/hecks/fuzzing/properties/outbox.rb +89 -0
  251. data/lib/hecks/fuzzing/properties/querying.rb +175 -0
  252. data/lib/hecks/fuzzing/properties.rb +162 -0
  253. data/lib/hecks/fuzzing/qa_settings.rb +123 -0
  254. data/lib/hecks/fuzzing/replay.rb +505 -0
  255. data/lib/hecks/fuzzing/rotation_priority.rb +48 -0
  256. data/lib/hecks/fuzzing/rust_gap_manifest.rb +108 -0
  257. data/lib/hecks/fuzzing/self_consistency.rb +339 -0
  258. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +385 -0
  259. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +132 -0
  260. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +95 -0
  261. data/lib/hecks/fuzzing/sequence_generator/picker.rb +77 -0
  262. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +109 -0
  263. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +237 -0
  264. data/lib/hecks/fuzzing/sequence_generator.rb +189 -0
  265. data/lib/hecks/fuzzing/shrinker.rb +166 -0
  266. data/lib/hecks/fuzzing/structural_skips.rb +33 -0
  267. data/lib/hecks/fuzzing/sweep_depth.rb +43 -0
  268. data/lib/hecks/fuzzing/target_capabilities.rb +163 -0
  269. data/lib/hecks/fuzzing/value_generator.rb +216 -0
  270. data/lib/hecks/fuzzing.rb +23 -0
  271. data/lib/hecks/grammar/evolve.rb +336 -0
  272. data/lib/hecks/grammar/expression.bluebook +15 -40
  273. data/lib/hecks/grammar/translation.bluebook +3 -6
  274. data/lib/hecks/grammar.rb +16 -41
  275. data/lib/hecks/hecks/adapters/codebase/codemods.rb +45 -0
  276. data/lib/hecks/hecks/adapters/codebase/conformance.rb +71 -0
  277. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +202 -0
  278. data/lib/hecks/hecks/adapters/codebase/gem_registry.rb +80 -0
  279. data/lib/hecks/hecks/adapters/codebase/kernel_tables.rb +54 -0
  280. data/lib/hecks/hecks/adapters/codebase/language.rb +240 -0
  281. data/lib/hecks/hecks/adapters/codebase/npm_registry.rb +102 -0
  282. data/lib/hecks/hecks/adapters/codebase/publishing.rb +142 -0
  283. data/lib/hecks/hecks/adapters/codebase/regeneration.rb +67 -0
  284. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +90 -0
  285. data/lib/hecks/hecks/adapters/codebase/ruby_child.rb +83 -0
  286. data/lib/hecks/hecks/adapters/codebase/secret_vault.rb +36 -0
  287. data/lib/hecks/hecks/adapters/codebase/source_tree.adapter +7 -0
  288. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +189 -0
  289. data/lib/hecks/hecks/adapters/codebase/sqlite_fixture.rb +71 -0
  290. data/lib/hecks/hecks/adapters/codebase/style.rb +171 -0
  291. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +55 -0
  292. data/lib/hecks/hecks/adapters/codebase/test_suite.rb +165 -0
  293. data/lib/hecks/hecks/adapters/codebase/tree.rb +111 -0
  294. data/lib/hecks/hecks/adapters/console_capture.rb +79 -0
  295. data/lib/hecks/hecks/adapters/deploy_toolchain.adapter +7 -0
  296. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +108 -0
  297. data/lib/hecks/hecks/adapters/git.adapter +7 -0
  298. data/lib/hecks/hecks/adapters/git.rb +102 -0
  299. data/lib/hecks/hecks/adapters/host_http.adapter +7 -0
  300. data/lib/hecks/hecks/adapters/host_http.rb +65 -0
  301. data/lib/hecks/hecks/adapters/in_process_boot.adapter +7 -0
  302. data/lib/hecks/hecks/adapters/in_process_boot.rb +197 -0
  303. data/lib/hecks/hecks/adapters/in_process_operations.rb +202 -0
  304. data/lib/hecks/hecks/adapters/journal_store/changes.rb +143 -0
  305. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +145 -0
  306. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +132 -0
  307. data/lib/hecks/hecks/adapters/journal_store/examination.rb +135 -0
  308. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +163 -0
  309. data/lib/hecks/hecks/adapters/journal_store/readings.rb +187 -0
  310. data/lib/hecks/hecks/adapters/journal_store.adapter +7 -0
  311. data/lib/hecks/hecks/adapters/journal_store.rb +64 -0
  312. data/lib/hecks/hecks/adapters/local_files.adapter +7 -0
  313. data/lib/hecks/hecks/adapters/local_files.rb +46 -0
  314. data/lib/hecks/hecks/adapters/pg_admin.adapter +7 -0
  315. data/lib/hecks/hecks/adapters/pg_admin.rb +245 -0
  316. data/lib/hecks/hecks/adapters/process_pool.adapter +7 -0
  317. data/lib/hecks/hecks/adapters/process_pool.rb +119 -0
  318. data/lib/hecks/hecks/adapters/rust_toolchain.adapter +7 -0
  319. data/lib/hecks/hecks/adapters/rust_toolchain.rb +179 -0
  320. data/lib/hecks/hecks/adapters/rust_workspace.rb +150 -0
  321. data/lib/hecks/hecks/adapters/shell.adapter +7 -0
  322. data/lib/hecks/hecks/adapters/shell.rb +49 -0
  323. data/lib/hecks/hecks/adapters/terminal.adapter +7 -0
  324. data/lib/hecks/hecks/adapters/terminal.rb +74 -0
  325. data/lib/hecks/hecks/codebase.behaviors +172 -0
  326. data/lib/hecks/hecks/codebase.bluebook +2453 -0
  327. data/lib/hecks/hecks/context_map.hecksagon +13 -0
  328. data/lib/hecks/hecks/custodian.behaviors +179 -0
  329. data/lib/hecks/hecks/custodian.bluebook +1847 -0
  330. data/lib/hecks/hecks/deploy.behaviors +74 -0
  331. data/lib/hecks/hecks/deploy.world +12 -0
  332. data/lib/hecks/hecks/environments/memory.world +10 -0
  333. data/lib/hecks/hecks/governance.world +7 -0
  334. data/lib/hecks/hecks/hecks.behaviors +62 -0
  335. data/lib/hecks/hecks/hecks.bluebook +124 -0
  336. data/lib/hecks/hecks/hecks.hecksagon +390 -0
  337. data/lib/hecks/hecks/hecks.world +42 -0
  338. data/lib/hecks/ir.rb +14 -83
  339. data/lib/hecks/kernel_capabilities.rb +194 -0
  340. data/lib/hecks/language/adapter.bluebook +2 -7
  341. data/lib/hecks/language/bluebook/aggregate.bluebook +56 -221
  342. data/lib/hecks/language/bluebook/attaches/paging.bluebook +6 -15
  343. data/lib/hecks/language/bluebook/bluebook.bluebook +26 -83
  344. data/lib/hecks/language/bluebook/bluebook.hecksagon +3 -15
  345. data/lib/hecks/language/bluebook/command.bluebook +27 -144
  346. data/lib/hecks/language/bluebook/entity.bluebook +24 -97
  347. data/lib/hecks/language/bluebook/policy.bluebook +16 -67
  348. data/lib/hecks/language/bluebook/process_manager.bluebook +28 -151
  349. data/lib/hecks/language/bluebook/projection.bluebook +126 -60
  350. data/lib/hecks/language/bluebook/query.bluebook +19 -43
  351. data/lib/hecks/language/bluebook/shape.bluebook +23 -73
  352. data/lib/hecks/language/bluebook/syntax.bluebook +51 -262
  353. data/lib/hecks/language/bluebook/vocabulary.bluebook +83 -198
  354. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +3 -10
  355. data/lib/hecks/language/hecksagon/domain_port.bluebook +14 -19
  356. data/lib/hecks/language/hecksagon/hecksagon.bluebook +26 -25
  357. data/lib/hecks/language/hecksagon/port_operation.bluebook +3 -14
  358. data/lib/hecks/language/oidc.json +35 -0
  359. data/lib/hecks/language/port.bluebook +4 -17
  360. data/lib/hecks/language/translation/translation.bluebook +3 -9
  361. data/lib/hecks/language/translation/translation_aggregate.bluebook +5 -24
  362. data/lib/hecks/language/world/wiring.bluebook +1 -3
  363. data/lib/hecks/language/world/world.bluebook +5 -7
  364. data/lib/hecks/literal.rb +8 -50
  365. data/lib/hecks/mcp_stdio_guard.rb +4 -41
  366. data/lib/hecks/naming.rb +29 -137
  367. data/lib/hecks/persistence_legacy_fixture/regenerate.rb +161 -0
  368. data/lib/hecks/persistence_legacy_fixture.rb +113 -0
  369. data/lib/hecks/ports/access_control.rb +7 -23
  370. data/lib/hecks/ports/agent/answers.rb +5 -23
  371. data/lib/hecks/ports/agent.rb +47 -172
  372. data/lib/hecks/ports/authentication.rb +12 -39
  373. data/lib/hecks/ports/authorization.rb +16 -43
  374. data/lib/hecks/ports/clock.rb +5 -45
  375. data/lib/hecks/ports/extraction.rb +7 -12
  376. data/lib/hecks/ports/identity_assignment.rb +8 -29
  377. data/lib/hecks/ports/identity_generation.rb +6 -29
  378. data/lib/hecks/ports/identity_resolution.rb +2 -10
  379. data/lib/hecks/ports/key_vault.rb +9 -45
  380. data/lib/hecks/ports/persistence/append_only.rb +61 -64
  381. data/lib/hecks/ports/persistence/binding_policy.rb +4 -14
  382. data/lib/hecks/ports/persistence/codec_boundary.rb +20 -80
  383. data/lib/hecks/ports/persistence/null_saga_store.rb +4 -20
  384. data/lib/hecks/ports/persistence/plugin.rb +13 -42
  385. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +44 -144
  386. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +15 -115
  387. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +20 -85
  388. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +6 -25
  389. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +18 -24
  390. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +55 -181
  391. data/lib/hecks/ports/persistence/plugins/era/postgres_era/events.rb +77 -0
  392. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +12 -122
  393. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +28 -128
  394. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +73 -474
  395. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +31 -124
  396. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +45 -219
  397. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +13 -108
  398. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +19 -76
  399. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +9 -49
  400. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +6 -123
  401. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +23 -55
  402. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +14 -72
  403. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +7 -16
  404. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +43 -127
  405. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +13 -45
  406. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +131 -555
  407. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +13 -63
  408. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +227 -0
  409. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +2 -17
  410. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +3 -14
  411. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +13 -58
  412. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -17
  413. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -26
  414. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +50 -47
  415. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -124
  416. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +34 -123
  417. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +5 -12
  418. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +3 -9
  419. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -14
  420. data/lib/hecks/ports/persistence/plugins/era/translation.rb +1 -0
  421. data/lib/hecks/ports/persistence/plugins/era.rb +4 -23
  422. data/lib/hecks/ports/persistence/postgres_dump/connection.rb +7 -23
  423. data/lib/hecks/ports/persistence/postgres_dump.rb +4 -35
  424. data/lib/hecks/ports/persistence/remote_runtime.rb +2 -24
  425. data/lib/hecks/ports/persistence/repository_factory.rb +8 -27
  426. data/lib/hecks/ports/persistence/state_codec.rb +47 -108
  427. data/lib/hecks/ports/persistence.rb +7 -14
  428. data/lib/hecks/ports/projection.rb +42 -84
  429. data/lib/hecks/ports/query/in_memory.rb +10 -58
  430. data/lib/hecks/ports/query/ordering.rb +11 -32
  431. data/lib/hecks/ports/query.rb +14 -37
  432. data/lib/hecks/ports.rb +2 -8
  433. data/lib/hecks/projection_files.rb +285 -0
  434. data/lib/hecks/projections/bootstrap_table.rb +12 -44
  435. data/lib/hecks/projections/deploy/fargate/assembly.rb +2 -15
  436. data/lib/hecks/projections/deploy/fargate/cdn.rb +1 -48
  437. data/lib/hecks/projections/deploy/fargate/cdn_default.rb +2 -11
  438. data/lib/hecks/projections/deploy/fargate/check.rb +5 -14
  439. data/lib/hecks/projections/deploy/fargate/containers.rb +3 -78
  440. data/lib/hecks/projections/deploy/fargate/extras.rb +3 -44
  441. data/lib/hecks/projections/deploy/fargate/monitoring.rb +0 -44
  442. data/lib/hecks/projections/deploy/fargate/settings.rb +2 -54
  443. data/lib/hecks/projections/deploy/fargate/yaml.rb +13 -58
  444. data/lib/hecks/projections/deploy/fargate.rb +40 -207
  445. data/lib/hecks/projections/deploy/lambda.rb +191 -708
  446. data/lib/hecks/projections/deploy/preview/access_yaml.rb +1 -33
  447. data/lib/hecks/projections/deploy/preview/containers.rb +6 -111
  448. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  449. data/lib/hecks/projections/deploy/preview/routing_yaml.rb +4 -56
  450. data/lib/hecks/projections/deploy/preview/script.rb +2 -14
  451. data/lib/hecks/projections/deploy/preview/settings.rb +3 -33
  452. data/lib/hecks/projections/deploy/preview/task_yaml.rb +2 -38
  453. data/lib/hecks/projections/deploy/preview/template.rb +3 -59
  454. data/lib/hecks/projections/deploy/preview.rb +2 -67
  455. data/lib/hecks/projections/deploy/scripts/deploy-service.sh.tmpl +1 -1
  456. data/lib/hecks/projections/deploy/scripts/expected-era.header +1 -1
  457. data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +3 -3
  458. data/lib/hecks/projections/deploy/scripts/settings.rb +3 -25
  459. data/lib/hecks/projections/deploy/scripts/smoke-after-deploy.sh.tmpl +1 -1
  460. data/lib/hecks/projections/deploy/scripts.rb +4 -57
  461. data/lib/hecks/projections/deploy/shared.rb +48 -147
  462. data/lib/hecks/projections/deploy/smoke.rb +4 -61
  463. data/lib/hecks/projections/deploy/template_diff/comparer.rb +3 -25
  464. data/lib/hecks/projections/deploy/template_diff/loader.rb +2 -11
  465. data/lib/hecks/projections/deploy/template_diff.rb +5 -57
  466. data/lib/hecks/projections/diagrams.rb +22 -584
  467. data/lib/hecks/projections/glossary/html.rb +14 -130
  468. data/lib/hecks/projections/glossary/markdown.rb +8 -69
  469. data/lib/hecks/projections/glossary/mermaid.rb +9 -66
  470. data/lib/hecks/projections/glossary/sensitivity.rb +5 -34
  471. data/lib/hecks/projections/glossary/sentences.rb +14 -159
  472. data/lib/hecks/projections/glossary.rb +30 -222
  473. data/lib/hecks/projections/ir.rb +3 -10
  474. data/lib/hecks/projections/model/deviations.rb +16 -43
  475. data/lib/hecks/projections/model.rb +12 -51
  476. data/lib/hecks/projections/oidc.rb +12 -56
  477. data/lib/hecks/projections/parser_table.rb +13 -32
  478. data/lib/hecks/projections/reference.rb +4 -18
  479. data/lib/hecks/projections/rust_vocabulary.rb +13 -251
  480. data/lib/hecks/projections/shape.rb +3 -29
  481. data/lib/hecks/projections/statements.rb +7 -86
  482. data/lib/hecks/projections/vocabulary.rb +6 -22
  483. data/lib/hecks/projections.rb +2 -16
  484. data/lib/hecks/projector/cli_projector.rb +154 -400
  485. data/lib/hecks/projector/docs_projector.rb +10 -201
  486. data/lib/hecks/projector/exporter.rb +42 -134
  487. data/lib/hecks/projector/ir_projector.rb +2 -9
  488. data/lib/hecks/projector/narrate_projector.rb +15 -189
  489. data/lib/hecks/projector/target.rb +13 -76
  490. data/lib/hecks/projector.rb +30 -121
  491. data/lib/hecks/quality_control/adapters/agent.adapter +7 -0
  492. data/lib/hecks/quality_control/adapters/agent.rb +58 -0
  493. data/lib/hecks/quality_control/adapters/angle_tools.adapter +7 -0
  494. data/lib/hecks/quality_control/adapters/angle_tools.rb +15 -0
  495. data/lib/hecks/quality_control/adapters/clearance_recorder.rb +36 -0
  496. data/lib/hecks/quality_control/adapters/clearance_tools.adapter +7 -0
  497. data/lib/hecks/quality_control/adapters/clearance_tools.rb +17 -0
  498. data/lib/hecks/quality_control/adapters/git_pr.adapter +7 -0
  499. data/lib/hecks/quality_control/adapters/git_pr.rb +226 -0
  500. data/lib/hecks/quality_control/adapters/github_checks.adapter +9 -0
  501. data/lib/hecks/quality_control/adapters/github_checks.rb +83 -0
  502. data/lib/hecks/quality_control/adapters/github_ci_webhook.rb +56 -0
  503. data/lib/hecks/quality_control/adapters/github_issues.adapter +7 -0
  504. data/lib/hecks/quality_control/adapters/github_issues.rb +49 -0
  505. data/lib/hecks/quality_control/adapters/qa_tool.rb +59 -0
  506. data/lib/hecks/quality_control/adapters/sweep_tools.adapter +7 -0
  507. data/lib/hecks/quality_control/adapters/sweep_tools.rb +51 -0
  508. data/lib/hecks/quality_control/adapters/target_tools.adapter +7 -0
  509. data/lib/hecks/quality_control/adapters/target_tools.rb +43 -0
  510. data/lib/hecks/quality_control/cli/child.rb +45 -0
  511. data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +45 -0
  512. data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +248 -0
  513. data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +163 -0
  514. data/lib/hecks/quality_control/cli/qa_generated_domains.rb +450 -0
  515. data/lib/hecks/quality_control/cli/qa_log_bug.rb +195 -0
  516. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +220 -0
  517. data/lib/hecks/quality_control/cli/qa_open_pr.rb +305 -0
  518. data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +154 -0
  519. data/lib/hecks/quality_control/cli/qa_postgres_role.rb +159 -0
  520. data/lib/hecks/quality_control/cli/qa_pr_check.rb +232 -0
  521. data/lib/hecks/quality_control/cli/qa_seed_angles.rb +142 -0
  522. data/lib/hecks/quality_control/cli/qa_seed_targets.rb +69 -0
  523. data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +280 -0
  524. data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +329 -0
  525. data/lib/hecks/quality_control/cli/qa_sweep/dials.rb +92 -0
  526. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +117 -0
  527. data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +175 -0
  528. data/lib/hecks/quality_control/cli/qa_sweep/release_mode.rb +60 -0
  529. data/lib/hecks/quality_control/cli/qa_sweep.rb +611 -0
  530. data/lib/hecks/quality_control/cli/qa_tick.rb +274 -0
  531. data/lib/hecks/quality_control/quality_control.bluebook +2224 -0
  532. data/lib/hecks/quality_control/quality_control.hecksagon +13 -0
  533. data/lib/hecks/quality_control/quality_control.ports.hecksagon +54 -0
  534. data/lib/hecks/query_ir.rb +346 -0
  535. data/lib/hecks/query_ir_mcp.rb +198 -0
  536. data/lib/hecks/query_specification/common/comparators.rb +5 -26
  537. data/lib/hecks/query_specification/common/comparison.rb +35 -143
  538. data/lib/hecks/query_specification/common/dsl.rb +8 -24
  539. data/lib/hecks/query_specification/common/null_policy.rb +20 -70
  540. data/lib/hecks/query_specification/common/options.rb +2 -6
  541. data/lib/hecks/query_specification/common/where_clause.rb +2 -10
  542. data/lib/hecks/query_specification/field_path.rb +37 -77
  543. data/lib/hecks/query_specification/hop_path.rb +12 -76
  544. data/lib/hecks/release/gem_pin.rb +2 -26
  545. data/lib/hecks/release/runner/ci_publisher.rb +5 -8
  546. data/lib/hecks/release/runner/clean_tree.rb +42 -0
  547. data/lib/hecks/release/runner/commands.rb +2 -4
  548. data/lib/hecks/release/runner/console.rb +6 -0
  549. data/lib/hecks/release/runner/gem_publisher.rb +15 -9
  550. data/lib/hecks/release/runner/npm_publisher.rb +9 -53
  551. data/lib/hecks/release/runner/preflight.rb +29 -20
  552. data/lib/hecks/release/runner/published.rb +8 -26
  553. data/lib/hecks/release/runner/tagger.rb +31 -23
  554. data/lib/hecks/release/runner.rb +60 -38
  555. data/lib/hecks/rendering.rb +4 -26
  556. data/lib/hecks/router/namespace_installer.rb +15 -39
  557. data/lib/hecks/router.rb +4 -8
  558. data/lib/hecks/runtime/adapter_lookup.rb +124 -0
  559. data/lib/hecks/runtime/aggregate_lock.rb +8 -33
  560. data/lib/hecks/runtime/boot_gates.rb +3 -18
  561. data/lib/hecks/runtime/caller.rb +19 -59
  562. data/lib/hecks/runtime/capability_graph.rb +5 -21
  563. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
  564. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +27 -160
  565. data/lib/hecks/runtime/command_interpreter.rb +124 -306
  566. data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
  567. data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
  568. data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
  569. data/lib/hecks/runtime/command_rules/emission.rb +8 -21
  570. data/lib/hecks/runtime/command_rules/references.rb +23 -202
  571. data/lib/hecks/runtime/dependency_planning.rb +50 -104
  572. data/lib/hecks/runtime/dispatcher.rb +134 -243
  573. data/lib/hecks/runtime/entity_element.rb +40 -462
  574. data/lib/hecks/runtime/entity_interpreter.rb +64 -219
  575. data/lib/hecks/runtime/errors.rb +32 -115
  576. data/lib/hecks/runtime/event.rb +3 -25
  577. data/lib/hecks/runtime/identity.rb +18 -154
  578. data/lib/hecks/runtime/instance.rb +18 -150
  579. data/lib/hecks/runtime/interpreting.rb +14 -70
  580. data/lib/hecks/runtime/invocation.rb +28 -164
  581. data/lib/hecks/runtime/loader.rb +109 -194
  582. data/lib/hecks/runtime/outbox.rb +39 -262
  583. data/lib/hecks/runtime/policy_interpreter.rb +43 -202
  584. data/lib/hecks/runtime/port_operation_interpreter.rb +28 -97
  585. data/lib/hecks/runtime/query_interpreter.rb +122 -187
  586. data/lib/hecks/runtime/reaction_invocation.rb +36 -138
  587. data/lib/hecks/runtime/reaction_outcome.rb +89 -0
  588. data/lib/hecks/runtime/read_model_interpreter.rb +117 -208
  589. data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
  590. data/lib/hecks/runtime/reference_hop.rb +8 -81
  591. data/lib/hecks/runtime/refusal_wording.rb +28 -87
  592. data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
  593. data/lib/hecks/runtime/registry/verification.rb +213 -227
  594. data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
  595. data/lib/hecks/runtime/registry.rb +156 -247
  596. data/lib/hecks/runtime/remote_dispatcher.rb +68 -154
  597. data/lib/hecks/runtime/routing.rb +4 -15
  598. data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
  599. data/lib/hecks/runtime/saga_interpreter.rb +79 -263
  600. data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
  601. data/lib/hecks/runtime/tenant_check.rb +6 -68
  602. data/lib/hecks/runtime/tenant_scope.rb +3 -28
  603. data/lib/hecks/runtime/value/admission.rb +16 -67
  604. data/lib/hecks/runtime/value/coercion.rb +162 -760
  605. data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
  606. data/lib/hecks/runtime/value/field_checks.rb +172 -0
  607. data/lib/hecks/runtime/value.rb +15 -84
  608. data/lib/hecks/runtime.rb +35 -69
  609. data/lib/hecks/rust_build/conformance.rb +158 -0
  610. data/lib/hecks/rust_build/conformance_fuzz.rb +71 -0
  611. data/lib/hecks/rust_build/coverage.rb +218 -0
  612. data/lib/hecks/rust_build/native_build.rb +124 -0
  613. data/lib/hecks/rust_build/project_rust/root_mod.rb +120 -0
  614. data/lib/hecks/rust_build/project_rust.rb +332 -0
  615. data/lib/hecks/rust_build/wasm.rb +105 -0
  616. data/lib/hecks/rust_build/wasm_browser.rb +113 -0
  617. data/lib/hecks/rust_build.rb +144 -0
  618. data/lib/hecks/storehouse.rb +155 -582
  619. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +10 -34
  620. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
  621. data/lib/hecks/three_zero/forms.yml +127 -0
  622. data/lib/hecks/three_zero.rb +12 -0
  623. data/lib/hecks/tools/argument_gate_matrix.rb +316 -0
  624. data/lib/hecks/tools/comment_style.rb +1070 -0
  625. data/lib/hecks/tools/corpus_report.rb +87 -0
  626. data/lib/hecks/tools/deploy_recipe.rb +255 -0
  627. data/lib/hecks/tools/deploy_recipe_lint.rb +282 -0
  628. data/lib/hecks/tools/drop_implicit_append_fields.rb +95 -0
  629. data/lib/hecks/tools/era_projection_backfill.rb +94 -0
  630. data/lib/hecks/tools/era_reattest.rb +146 -0
  631. data/lib/hecks/tools/evolve_run.rb +189 -0
  632. data/lib/hecks/tools/fuzz_sweep.rb +373 -0
  633. data/lib/hecks/tools/heki_compaction.rb +100 -0
  634. data/lib/hecks/tools/hoist_local_givens.rb +246 -0
  635. data/lib/hecks/tools/journal_compaction.rb +89 -0
  636. data/lib/hecks/tools/oidc_manifests.rb +70 -0
  637. data/lib/hecks/tools/query_ir_run.rb +81 -0
  638. data/lib/hecks/tools/regeneration_run.rb +166 -0
  639. data/lib/hecks/tools/rust_comment_style.rb +642 -0
  640. data/lib/hecks/tools/sequence_script.rb +62 -0
  641. data/lib/hecks/tools/tail_merge.rb +117 -0
  642. data/lib/hecks/tools/tenant_provisioning.rb +142 -0
  643. data/lib/hecks/tools/translation_audit.rb +208 -0
  644. data/lib/hecks/tools/translation_scaffold.rb +131 -0
  645. data/lib/hecks/tools.rb +86 -0
  646. data/lib/hecks/vendoring/git_environment.rb +2 -8
  647. data/lib/hecks/vendoring/git_source.rb +4 -10
  648. data/lib/hecks/vendoring.rb +7 -46
  649. data/lib/hecks/version.rb +3 -14
  650. data/lib/hecks.rb +52 -90
  651. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
  652. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
  653. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
  654. data/qa/settings.yml +66 -0
  655. data/rust/.cargo/config.toml +6 -0
  656. data/rust/.gitignore +2 -0
  657. data/rust/Cargo.lock +54 -0
  658. data/rust/Cargo.toml +61 -0
  659. data/rust/build/.cargo/config.toml +3 -0
  660. data/rust/build/.gitignore +1 -0
  661. data/rust/build/Cargo.lock +7 -0
  662. data/rust/build/Cargo.toml +19 -0
  663. data/rust/build/src/build_artifact.rs +47 -0
  664. data/rust/build/src/cargo_sync.rs +248 -0
  665. data/rust/build/src/json.rs +421 -0
  666. data/rust/build/src/lineage_pass.rs +285 -0
  667. data/rust/build/src/main.rs +51 -0
  668. data/rust/build/src/optional_pass.rs +150 -0
  669. data/rust/build/src/pipeline.rs +264 -0
  670. data/rust/build/src/reserved_names.rs +76 -0
  671. data/rust/build/src/resolve.rs +124 -0
  672. data/rust/build/src/root.rs +25 -0
  673. data/rust/build/src/sidecars.rs +73 -0
  674. data/rust/build/src/subprocess.rs +51 -0
  675. data/rust/build/src/tmp.rs +31 -0
  676. data/rust/codegen/.cargo/config.toml +3 -0
  677. data/rust/codegen/.gitignore +1 -0
  678. data/rust/codegen/Cargo.lock +7 -0
  679. data/rust/codegen/Cargo.toml +20 -0
  680. data/rust/codegen/src/attr.rs +32 -0
  681. data/rust/codegen/src/bridging.rs +481 -0
  682. data/rust/codegen/src/commands.rs +1226 -0
  683. data/rust/codegen/src/constraints.rs +109 -0
  684. data/rust/codegen/src/dependency_planning.rs +209 -0
  685. data/rust/codegen/src/domain_generator.rs +1288 -0
  686. data/rust/codegen/src/exemplar.rs +230 -0
  687. data/rust/codegen/src/expr_emitter.rs +87 -0
  688. data/rust/codegen/src/fielded.rs +192 -0
  689. data/rust/codegen/src/hecks_naming.rs +33 -0
  690. data/rust/codegen/src/json.rs +335 -0
  691. data/rust/codegen/src/json_codec.rs +697 -0
  692. data/rust/codegen/src/literal.rs +207 -0
  693. data/rust/codegen/src/main.rs +290 -0
  694. data/rust/codegen/src/manifest.rs +227 -0
  695. data/rust/codegen/src/mutations.rs +784 -0
  696. data/rust/codegen/src/naming.rs +286 -0
  697. data/rust/codegen/src/ports.rs +208 -0
  698. data/rust/codegen/src/prelude.rs +116 -0
  699. data/rust/codegen/src/queries.rs +786 -0
  700. data/rust/codegen/src/reactions.rs +532 -0
  701. data/rust/codegen/src/read_models.rs +697 -0
  702. data/rust/codegen/src/reference_specs.rs +40 -0
  703. data/rust/codegen/src/registry.rs +966 -0
  704. data/rust/codegen/src/reserved_names.rs +76 -0
  705. data/rust/codegen/src/shared.rs +93 -0
  706. data/rust/codegen/src/skip_reason.rs +25 -0
  707. data/rust/codegen/src/types.rs +362 -0
  708. data/rust/host/.cargo/config.toml +3 -0
  709. data/rust/host/.gitignore +1 -0
  710. data/rust/host/Cargo.lock +4297 -0
  711. data/rust/host/Cargo.toml +61 -0
  712. data/rust/host/HECKS_RELEASE +1 -0
  713. data/rust/host/rds-ca-bundle.pem +2736 -0
  714. data/rust/host/src/api.rs +1596 -0
  715. data/rust/host/src/approval.rs +652 -0
  716. data/rust/host/src/auth.rs +1287 -0
  717. data/rust/host/src/bin/expr_harness.rs +46 -0
  718. data/rust/host/src/bin/lineage_harness.rs +119 -0
  719. data/rust/host/src/bin/mint_harness.rs +135 -0
  720. data/rust/host/src/checkout.rs +265 -0
  721. data/rust/host/src/dispatch.rs +1443 -0
  722. data/rust/host/src/expr_json.rs +1117 -0
  723. data/rust/host/src/field_hints.rs +52 -0
  724. data/rust/host/src/ir.rs +733 -0
  725. data/rust/host/src/journal.rs +874 -0
  726. data/rust/host/src/lambda_client.rs +378 -0
  727. data/rust/host/src/log.rs +147 -0
  728. data/rust/host/src/main.rs +474 -0
  729. data/rust/host/src/mint.rs +1865 -0
  730. data/rust/host/src/payments/keystore.rs +330 -0
  731. data/rust/host/src/payments/tests.rs +1400 -0
  732. data/rust/host/src/payments.rs +767 -0
  733. data/rust/host/src/presentation.rs +698 -0
  734. data/rust/host/src/presentation_write.rs +1431 -0
  735. data/rust/host/src/query_step.rs +149 -0
  736. data/rust/host/src/rate_limit.rs +904 -0
  737. data/rust/host/src/reference_transform.rs +241 -0
  738. data/rust/host/src/reference_validate.rs +338 -0
  739. data/rust/host/src/resend.rs +296 -0
  740. data/rust/host/src/secrets.rs +106 -0
  741. data/rust/host/src/server.rs +663 -0
  742. data/rust/host/src/storage_shape.rs +175 -0
  743. data/rust/host/src/test_pg.rs +40 -0
  744. data/rust/host/src/ui_schema.rs +1351 -0
  745. data/rust/host/src/wasm_runner.rs +191 -0
  746. data/rust/host/src/web/newsletter/cooldown.rs +126 -0
  747. data/rust/host/src/web/newsletter/tests.rs +104 -0
  748. data/rust/host/src/web/newsletter.rs +310 -0
  749. data/rust/host/src/web/newsletter_send.rs +357 -0
  750. data/rust/host/src/web/registrations.rs +1505 -0
  751. data/rust/host/src/web.rs +2937 -0
  752. data/rust/lsp/.gitignore +1 -0
  753. data/rust/lsp/Cargo.lock +7 -0
  754. data/rust/lsp/Cargo.toml +16 -0
  755. data/rust/lsp/README.md +133 -0
  756. data/rust/lsp/src/diagnostics.rs +165 -0
  757. data/rust/lsp/src/json.rs +385 -0
  758. data/rust/lsp/src/main.rs +399 -0
  759. data/rust/lsp/src/outline.rs +177 -0
  760. data/rust/lsp/src/rpc.rs +67 -0
  761. data/rust/parser/.cargo/config.toml +5 -0
  762. data/rust/parser/.gitignore +1 -0
  763. data/rust/parser/Cargo.lock +7 -0
  764. data/rust/parser/Cargo.toml +21 -0
  765. data/rust/parser/src/build/closed_sets.rs +27 -0
  766. data/rust/parser/src/build/identity.rs +276 -0
  767. data/rust/parser/src/build/mod.rs +12 -0
  768. data/rust/parser/src/build/naming.rs +141 -0
  769. data/rust/parser/src/build/pattern_subset.rs +161 -0
  770. data/rust/parser/src/build/query_derive.rs +72 -0
  771. data/rust/parser/src/build/query_inference.rs +269 -0
  772. data/rust/parser/src/build/query_options.rs +100 -0
  773. data/rust/parser/src/build/read_model.rs +94 -0
  774. data/rust/parser/src/build/references.rs +32 -0
  775. data/rust/parser/src/canonical.rs +114 -0
  776. data/rust/parser/src/diag.rs +55 -0
  777. data/rust/parser/src/emit.rs +953 -0
  778. data/rust/parser/src/expr/ast_json.rs +147 -0
  779. data/rust/parser/src/expr/evaluator.rs +81 -0
  780. data/rust/parser/src/expr/mod.rs +84 -0
  781. data/rust/parser/src/expr/resolver.rs +497 -0
  782. data/rust/parser/src/ir.rs +387 -0
  783. data/rust/parser/src/keywords.rs +585 -0
  784. data/rust/parser/src/lex.rs +662 -0
  785. data/rust/parser/src/main.rs +252 -0
  786. data/rust/parser/src/parse/aggregate.rs +612 -0
  787. data/rust/parser/src/parse/chapter.rs +517 -0
  788. data/rust/parser/src/parse/command.rs +637 -0
  789. data/rust/parser/src/parse/domain_port.rs +127 -0
  790. data/rust/parser/src/parse/entity.rs +527 -0
  791. data/rust/parser/src/parse/file.rs +67 -0
  792. data/rust/parser/src/parse/hecksagon.rs +231 -0
  793. data/rust/parser/src/parse/lifecycle.rs +162 -0
  794. data/rust/parser/src/parse/mod.rs +1262 -0
  795. data/rust/parser/src/parse/policy.rs +87 -0
  796. data/rust/parser/src/parse/process_manager.rs +305 -0
  797. data/rust/parser/src/parse/query.rs +100 -0
  798. data/rust/parser/src/parse/read_model.rs +214 -0
  799. data/rust/parser/src/parse/value_object.rs +250 -0
  800. data/rust/parser/src/ruby_value.rs +422 -0
  801. data/rust/project/bridging.rb +257 -0
  802. data/rust/project/commands.rb +877 -0
  803. data/rust/project/constraints.rb +86 -0
  804. data/rust/project/dependency_planning.rb +178 -0
  805. data/rust/project/domain_generator.rb +980 -0
  806. data/rust/project/exemplar.rb +148 -0
  807. data/rust/project/expr_emitter.rb +72 -0
  808. data/rust/project/fielded.rb +141 -0
  809. data/rust/project/json_codec.rb +641 -0
  810. data/rust/project/mutations.rb +721 -0
  811. data/rust/project/naming.rb +205 -0
  812. data/rust/project/ports.rb +65 -0
  813. data/rust/project/queries.rb +493 -0
  814. data/rust/project/reactions.rb +317 -0
  815. data/rust/project/read_models.rb +690 -0
  816. data/rust/project/reference_specs.rb +27 -0
  817. data/rust/project/registry.rb +396 -0
  818. data/rust/project/skip_reason.rb +29 -0
  819. data/rust/project/types.rb +225 -0
  820. data/rust/project/write_if_changed.rb +83 -0
  821. data/rust/project.rb +30 -0
  822. data/rust/project_rust_pipeline.rb +383 -0
  823. data/rust/src/exemplar/commands.rs +279 -0
  824. data/rust/src/exemplar/constraints.rs +18 -0
  825. data/rust/src/exemplar/fielded.rs +280 -0
  826. data/rust/src/exemplar/json.rs +277 -0
  827. data/rust/src/exemplar/mod.rs +17 -0
  828. data/rust/src/exemplar/mutations.rs +80 -0
  829. data/rust/src/exemplar/queries.rs +33 -0
  830. data/rust/src/exemplar/reactions.rs +88 -0
  831. data/rust/src/exemplar/read_models.rs +48 -0
  832. data/rust/src/exemplar/registry.rs +166 -0
  833. data/rust/src/exemplar/types.rs +44 -0
  834. data/rust/src/kernel/attribute_shapes/composite.rs +88 -0
  835. data/rust/src/kernel/attribute_shapes/list.rs +18 -0
  836. data/rust/src/kernel/attribute_shapes/mod.rs +44 -0
  837. data/rust/src/kernel/attribute_shapes/optional.rs +9 -0
  838. data/rust/src/kernel/attribute_shapes/scalar.rs +39 -0
  839. data/rust/src/kernel/cli.rs +585 -0
  840. data/rust/src/kernel/dispatch.rs +1090 -0
  841. data/rust/src/kernel/expr.rs +458 -0
  842. data/rust/src/kernel/expression_operators/arithmetic.rs +128 -0
  843. data/rust/src/kernel/expression_operators/comparison.rs +48 -0
  844. data/rust/src/kernel/expression_operators/enumeration.rs +232 -0
  845. data/rust/src/kernel/expression_operators/logical.rs +13 -0
  846. data/rust/src/kernel/expression_operators/membership.rs +18 -0
  847. data/rust/src/kernel/expression_operators/mod.rs +68 -0
  848. data/rust/src/kernel/expression_operators/pattern_match.rs +73 -0
  849. data/rust/src/kernel/expression_operators/positional.rs +126 -0
  850. data/rust/src/kernel/expression_operators/presence.rs +101 -0
  851. data/rust/src/kernel/expression_operators/sign_test.rs +16 -0
  852. data/rust/src/kernel/expression_operators/sized.rs +35 -0
  853. data/rust/src/kernel/expression_operators/text.rs +79 -0
  854. data/rust/src/kernel/expression_operators/to_string.rs +18 -0
  855. data/rust/src/kernel/json.rs +779 -0
  856. data/rust/src/kernel/mod.rs +133 -0
  857. data/rust/src/kernel/named_query.rs +219 -0
  858. data/rust/src/kernel/orchestrate.rs +1810 -0
  859. data/rust/src/kernel/pattern.rs +307 -0
  860. data/rust/src/kernel/query_comparators.rs +206 -0
  861. data/rust/src/kernel/query_ordering.rs +103 -0
  862. data/rust/src/kernel/read_model.rs +914 -0
  863. data/rust/src/kernel/reference_lookup.rs +149 -0
  864. data/rust/src/kernel/repository.rs +468 -0
  865. data/rust/src/kernel/routing.rs +612 -0
  866. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +118 -0
  867. data/rust/src/kernel/vocab/entity_dispatch_order.rs +114 -0
  868. data/rust/src/kernel/vocab/field_hint.rs +70 -0
  869. data/rust/src/kernel/vocab/mod.rs +20 -0
  870. data/rust/src/kernel/vocab/query_comparator.rs +65 -0
  871. data/rust/src/kernel/vocab/refusal_template.rs +1592 -0
  872. data/rust/src/lib.rs +8 -0
  873. data/rust/src/main.rs +16 -0
  874. data/rust/web/.gitignore +1 -0
  875. data/rust/web/Cargo.lock +119 -0
  876. data/rust/web/Cargo.toml +18 -0
  877. data/rust/web/src/lib.rs +8 -0
  878. metadata +478 -12
  879. data/lib/hecks/facade/cli_door.rb +0 -178
  880. data/lib/hecks/facade/cli_runner.rb +0 -271
  881. data/lib/hecks/facade/handle.rb +0 -293
  882. data/lib/hecks/facade/json_door.rb +0 -247
  883. data/lib/hecks/facade/surface/aggregate_door.rb +0 -222
  884. data/lib/hecks/facade/surface/chapter.rb +0 -116
  885. data/lib/hecks/facade/surface.rb +0 -61
  886. data/lib/hecks/mcp_door_scope.rb +0 -176
@@ -2,139 +2,35 @@ require "json"
2
2
  require "fileutils"
3
3
  require "time"
4
4
  require_relative "cache_dir"
5
- require_relative "facade/command_request"
6
- require_relative "facade/json_door"
5
+ require_relative "doors/command_request"
6
+ require_relative "doors/json_door"
7
7
  require_relative "naming"
8
8
  require_relative "projector"
9
9
  require_relative "runtime/errors"
10
10
 
11
11
  module Hecks
12
- # **The bus, not a door** — `docs/archive/hecks-survey-what-we-wish-we-had.md` and
13
- # `docs/future-features.md` both name the sibling project's own
14
- # "Storehouse" the single highest-priority gap this repo had: "no
15
- # per-command tool... the bluebook is the contract, the [door] just
16
- # projects it." This module is that bus, borrowing its name too — the
17
- # dispatch/query/state/... surface below is a pure function of a
18
- # `Runtime::Dispatcher` plus plain Ruby arguments, no IO, no protocol
19
- # awareness. `bin/hecks_mcp_door` is one projection of it — MCP over
20
- # stdio — not the whole thing; a plain CLI door, an HTTP door, a second
21
- # transport of any shape, would sit beside it on the exact same bus,
22
- # sharing the same audit log and caller-identity handling, without
23
- # ever needing to speak MCP. (This bus is deliberately not
24
- # `Facade::McpDoor` — folding it into `Facade` under that name would
25
- # conflate the bus with its one built transport.)
26
- #
27
- # ## The surface
28
- #
29
- # catalog — what aggregates a domain declares, and what each can do
30
- # describe — one aggregate's full command/query/refusal contract
31
- # validate — is this domain's wiring sound (deep: also model-checks it)
32
- # domains — every domain directory under a root, discovered not typed
33
- # dispatch — issue a command (dry_run: preview, steps: batch)
34
- # query — ask a question
35
- # state — read what is actually stored, no verb involved
36
- # events — what happened, with payloads, to one record — not just its
37
- # current state; events this bus witnessed, sourced from
38
- # its own audit log, not a full event-sourcing replay
39
- # history — the full append-only journal, not just current state
40
- # behaviors — run a domain's hand-curated `.behaviors` examples
41
- # follow — tail this bus's own dispatch/query/state audit log
42
- #
43
- # ## Auditing and caller identity
44
- #
45
- # `dispatch`/`query`/`state` each take a `summary` — the survey's "every
46
- # audit row carries human intent for free" — and `dispatch`/`query`
47
- # additionally take an optional `source:` (`SOURCE_TAGS`, the survey's
48
- # `SourceTag`: who is calling) and an optional `role:`/`actor_id:` —
49
- # a real caller identity, bound for the call's duration via `Hecks.
50
- # as_caller`, checked against a `role`-gated command's own declared
51
- # role (`CommandRules::Authorization`) rather than merely documented by
52
- # `describe`. `dispatch` requires it for any command that declares a
53
- # role — `require_caller_for_role_gated!` refuses an unbound dispatch
54
- # against one rather than silently running it unchecked; `query`'s own
55
- # authorization runs on a separate mechanism (`Runtime::TenantScope`)
56
- # that `role:` does not gate. This is self-asserted identity, not
57
- # authentication: the caller states its own `role`/`actor_id`, and this
58
- # bus checks it consistently once stated — see `bin/hecks_mcp_door`'s
59
- # header for what that does and does not guarantee. Every call through
60
- # those three, plus a dry run, is appended to a per-domain JSONL audit
61
- # log (`record!`) `follow` tails back — a record of what the caller
62
- # said it was, not independently verified identity. `catalog`/
63
- # `describe`/`validate`/`domains`/`history`/`behaviors`/`events` need
64
- # neither — they change nothing and commit nothing to any log.
65
- #
66
- # ## Booting is the caller's job
67
- #
68
- # A caller hands in an already-booted `runtime`, the same division of
69
- # labor `Facade::CliRunner` already keeps against `bin/run`: booting a
70
- # domain from a path is IO the calling `bin/` script owns, this stays a
71
- # pure function of a `Runtime::Dispatcher` plus plain Ruby arguments —
72
- # no different from `CliRunner.call(runtime:, argv:)` itself.
73
- # `validate` and `domains` are the two exceptions: `validate`'s whole
74
- # job is to attempt the boot and report whether it survived, so it
75
- # takes the domain path instead and boots it; `domains` has no
76
- # domain to be handed one of yet, that's what it's answering.
77
- #
78
- # ## Built from existing doors
79
- #
80
- # No new vocabulary otherwise. Every method here composes doors that
81
- # already exist — `Projector.call(:cli, ...)` for verb/question alias
82
- # resolution (the identical table `CliRunner` itself resolves against),
83
- # `Projector.call(:docs, ...)` for `describe`, `Facade::JsonDoor` for
84
- # the JSON↔Runtime::Value boundary, `Registry#repository` for `state`/
85
- # `history`, `Registry#verify!` (run by every boot) for `validate`,
86
- # `Bluebook::ModelCheck` for `validate(deep: true)`, `Hecks::Behaviors`
87
- # for `behaviors`, `Adapters::Folder#domain?` for `domains`,
88
- # `Dispatcher#dry_run?` for `dispatch(dry_run: true)`. A second copy of
89
- # any of these here would be the exact duplication `Facade::JsonDoor`'s
90
- # own header already warns against.
12
+ # Dispatch/query/state and introspection over one booted domain, pure
13
+ # beyond its own JSONL audit log — booting stays the caller's job.
91
14
  module Storehouse
92
15
  module_function
93
16
 
94
- # The same closed set `docs/archive/hecks-survey-what-we-wish-we-had.md`'s
95
- # `SourceTag` names — who dispatched, not what. Optional: a caller
96
- # that omits it gets `source: nil` recorded, honestly, rather than a
97
- # guessed default.
17
+ # Optional on dispatch/query; omitted, source is recorded as nil rather
18
+ # than guessed.
98
19
  SOURCE_TAGS = %w[process-manager operator hook sidequest-agent cascade daemon].freeze
99
20
 
100
- # Names the directory holding the bus's own audit trail.
101
- #
102
- # The trail is a JSONL file per domain, one line per
103
- # `dispatch`/`query`/`state`/dry-run call, and `follow` tails it back.
104
- # It is kept under `Hecks::CacheDir`, not the domain's own directory: this is
105
- # the bus's record of what was asked of it, not part of the domain's own
106
- # persisted state, and not the gem's own directory either, which is
107
- # read-only on an installed gem.
108
- #
109
- # @return [String] the absolute path of the `storehouse` directory under
110
- # the cache root
21
+ # Kept under Hecks::CacheDir, not the domain directory (part of the domain's
22
+ # own state) or the gem directory (read-only once installed).
23
+ # :nodoc:
111
24
  def log_root = Hecks::CacheDir.path("storehouse")
112
25
 
113
- # The root every `domain:`/`under:` must resolve under — the project
114
- # directory by default, `HECKS_STOREHOUSE_ROOT` to widen or move it.
115
- # `Hecks.boot` `Kernel.load`s the `.hecksagon`/`.bluebook`/`.world`
116
- # files a domain path resolves to, and those are Ruby, not a data
117
- # format — a caller-supplied path with no confinement at all is an
118
- # unmarked door out of "a narrower, checked surface" (the README's
119
- # own pitch for this bus) and into arbitrary code execution from any
120
- # path on disk. This is a boundary a local convention was standing
121
- # in for, not new behavior for a caller already confined to the
122
- # project tree.
26
+ # The root every domain path resolves under; HECKS_STOREHOUSE_ROOT widens it.
27
+ # Hecks.boot Kernel.loads Ruby files here, so an unconfined path is
28
+ # arbitrary code execution, not just a wrong-directory mistake.
123
29
  BOOT_ROOT = File.expand_path(ENV["HECKS_STOREHOUSE_ROOT"] || File.expand_path("../..", __dir__))
124
30
 
125
- # Resolves `path` against `BOOT_ROOT` and refuses one that escapes it.
126
- #
127
- # **Refused, not silently clamped** — a path outside `BOOT_ROOT` is either
128
- # an honest mistake (a relative path typed against the wrong cwd) or
129
- # the exact thing this check exists to catch, and both deserve the
130
- # same clear refusal rather than a silent rewrite to something the
131
- # caller didn't ask for.
132
- #
133
- # @param path [String, #to_s] the caller-supplied path to confine
134
- # @param label [String] the argument's own name, for the refusal message
135
- # (e.g. `"domain"`, `"under"`)
136
- # @return [String] `path` resolved to an absolute path under `BOOT_ROOT`
137
- # @raise [Runtime::TypeMismatch] if the resolved path is outside `BOOT_ROOT`
31
+ # Refuses a path outside BOOT_ROOT rather than silently clamping it — a
32
+ # stray relative path and a deliberate escape both deserve a clear refusal.
33
+ # :nodoc:
138
34
  def confine!(path, label)
139
35
  resolved = File.expand_path(path.to_s, BOOT_ROOT)
140
36
  return resolved if resolved == BOOT_ROOT || resolved.start_with?("#{BOOT_ROOT}#{File::SEPARATOR}")
@@ -144,45 +40,24 @@ module Hecks
144
40
  "own root (HECKS_STOREHOUSE_ROOT to widen it)"
145
41
  end
146
42
 
147
- # ── shared resolution helpers ────────────────────────────────────
148
-
149
- # The one bluebook a domain directory boots. Every `bin/*` script that
150
- # projects a whole-domain CLI or doc set makes this same assumption
151
- # (`Facade::CliRunner#call`'s own `bluebook = runtime.registry.
152
- # bluebooks.values.first`) — one `.hecksagon` names one chapter.
153
- # The one bluebook this runtime booted.
154
- #
155
- # @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted runtime
156
- # @return [Bluebook::Chapter] the runtime's one loaded bluebook
157
- # @raise [Runtime::NotFound] if the runtime's boot loaded no bluebook
43
+ # The one bluebook this runtime booted; every whole-domain door assumes
44
+ # the same one-bluebook-per-boot shape.
45
+ # :nodoc:
158
46
  def bluebook_for(runtime)
159
47
  runtime.registry.bluebooks.values.first or
160
48
  raise Runtime::NotFound, "this boot loaded no bluebook"
161
49
  end
162
50
 
163
- # Finds one aggregate by name on a bluebook, or refuses.
164
- #
165
- # @param bluebook [Bluebook::Chapter] the chapter to search
166
- # @param name [String, Symbol, #to_s] the aggregate's declared name
167
- # @return [Bluebook::Aggregate] the matching aggregate
168
- # @raise [Runtime::NotFound] if `bluebook` declares no aggregate named `name`
51
+ # :nodoc:
169
52
  def aggregate_ir!(bluebook, name)
170
53
  bluebook.aggregate(name) or
171
54
  raise Runtime::NotFound, "#{bluebook.name} declares no aggregate named #{name.inspect} — " \
172
55
  "known: #{bluebook.aggregates.map(&:hecks_name).sort.join(', ')}"
173
56
  end
174
57
 
175
- # The same alias table `CliRunner` resolves a typed word against — a
176
- # short name when it's unambiguous, the qualified `Aggregate.Verb`
177
- # form always. Shared here so `dispatch` and `query` (and their error
178
- # messages) never drift from what a human typing `bin/run` sees.
179
- #
180
- # @param cli [Hash{Symbol => Object}] a `Projector.call(:cli, ...)` result
181
- # @param name [String, Symbol, #to_s] the command or query name, as given by the caller
182
- # @param asking [Boolean] true to resolve a query (against `cli[:questions]`),
183
- # false to resolve a command (against `cli[:verbs]`)
184
- # @return [Hash{Symbol => Object}] the resolved verb/question spec
185
- # @raise [Runtime::NotFound] if `name` matches no known command or query
58
+ # The same alias table CliRunner resolves against, kept here so dispatch
59
+ # and query never drift from what a human typing hecks run sees.
60
+ # :nodoc:
186
61
  def resolve!(cli, name, asking:)
187
62
  pool = asking ? cli[:questions] : cli[:verbs]
188
63
  key = cli[:names][asking ? :question : :command][name]
@@ -193,11 +68,8 @@ module Hecks
193
68
  raise Runtime::NotFound, "no such #{asking ? 'query' : 'command'}: #{name.inspect} — known: #{known}"
194
69
  end
195
70
 
196
- # Refuses a call with no summary.
197
- #
198
- # @param summary [String, nil] the caller-supplied one-line summary
199
- # @return [void]
200
- # @raise [Runtime::TypeMismatch] if `summary` is nil or blank
71
+ # Required on dispatch/query/state: what makes an audit row legible later.
72
+ # :nodoc:
201
73
  def require_summary!(summary)
202
74
  return unless summary.nil? || summary.to_s.strip.empty?
203
75
 
@@ -205,80 +77,34 @@ module Hecks
205
77
  "a one-line summary: is required on dispatch/query/state — it is what makes an audit row legible later"
206
78
  end
207
79
 
208
- # Refuses a `source:` that is not one of `SOURCE_TAGS`.
209
- #
210
- # @param source [String, Symbol, nil] the caller-supplied source tag
211
- # @return [void]
212
- # @raise [Runtime::TypeMismatch] if `source` is present and not in `SOURCE_TAGS`
80
+ # :nodoc:
213
81
  def valid_source!(source)
214
82
  return if source.nil? || SOURCE_TAGS.include?(source.to_s)
215
83
 
216
84
  raise Runtime::TypeMismatch, "source: #{source.inspect} is not one of #{SOURCE_TAGS.join(', ')}"
217
85
  end
218
86
 
219
- # `actor_id` names who, `role` names what they hold — `Hecks.
220
- # as_caller` requires the latter always, the former is additive
221
- # (`Runtime::Caller::Current`'s own shape). An `actor_id` with no
222
- # `role` would silently do nothing rather than bind a real caller,
223
- # which is worse than refusing: a caller who thinks they've
224
- # identified themselves and haven't deserves to be told.
225
- #
226
- # @param role [String, Symbol, nil] the caller's self-asserted role
227
- # @param actor_id [String, nil] the caller's self-asserted identity
228
- # @return [void]
229
- # @raise [Runtime::TypeMismatch] if `actor_id` is given without `role`
87
+ # actor_id without role would silently bind nothing rather than a real
88
+ # caller — refusing here beats a caller thinking it identified itself.
89
+ # :nodoc:
230
90
  def valid_caller!(role, actor_id)
231
91
  return unless actor_id && role.nil?
232
92
 
233
93
  raise Runtime::TypeMismatch, "actor_id: requires role: too — a caller names WHO through WHICH role they hold"
234
94
  end
235
95
 
236
- # Bound for the duration of one call, then gone — `Hecks.as_caller`
237
- # is itself a `Thread.current`-scoped `ensure`-guarded block, so
238
- # nothing here needs its own cleanup. `role: nil` yields unbound —
239
- # for `query`, exactly as before: `CommandRules::Authorization#
240
- # refuse_role_mismatch` is opt-in on the domain side (`return unless
241
- # caller`), and query authorization runs on a wholly separate
242
- # mechanism (`authorize policy, tenant: :field`, checked against an
243
- # explicit `tenant:` argument — see `Runtime::TenantScope`), so
244
- # binding a caller around a query has no effect on it today; it is
245
- # still accepted here, for symmetry and for the audit log, against
246
- # the day a read model does check `Caller.current`. For `dispatch`,
247
- # `require_caller_for_role_gated!` (below) now refuses before this
248
- # is ever reached when the command declares a role and no caller is
249
- # bound — so an unbound `dispatch` here means either the command
250
- # declares no role at all, or a caller-side check let it through.
251
- #
252
- # @param role [String, Symbol, nil] the caller's self-asserted role; nil runs
253
- # `block` unbound
254
- # @param actor_id [String, nil] the caller's self-asserted identity
255
- # @yield the dispatch/query to run, bound to the caller for its duration
256
- # @return [Object] the block's own return value
96
+ # Binds role/actor_id for the block's duration via Hecks.as_caller; role: nil
97
+ # runs the block unbound (query's own authorization does not depend on it).
98
+ # :nodoc:
257
99
  def with_caller(role, actor_id, &block)
258
100
  return block.call if role.nil?
259
101
 
260
102
  Hecks.as_caller(role: role, actor_id: actor_id, &block)
261
103
  end
262
104
 
263
- # The fail-open half `with_caller` itself cannot close — ADR 0025's
264
- # Governance RBAC work fixed what a *bound* role is checked against
265
- # (a live `Governance::RoleAssignment` lookup instead of a bare
266
- # string match), but changed nothing about a caller who binds no
267
- # role at all: `refuse_role_mismatch` `return`s immediately when
268
- # `Caller.current` is nil, so a bus caller who simply omits `role:`
269
- # sails past a role-gated command unchecked, not denied. That is a
270
- # property of this bus choosing to dispatch unbound, not of the
271
- # domain rule — `bin/run`, the human CLI, has no such gap because a
272
- # human always dispatches through a real `Hecks.as_caller` binding
273
- # upstream of it. Refusing here, before `with_caller`/`dispatch` are
274
- # ever reached, makes the bus keep the same promise: a command whose
275
- # bluebook declares a role is not run through this bus without one.
276
- #
277
- # @param spec [Hash{Symbol => Object}] a resolved command spec (from `resolve!`),
278
- # read for `:role_gated`, `:role` and `:verb`
279
- # @param role [String, Symbol, nil] the caller's self-asserted role
280
- # @return [void]
281
- # @raise [Runtime::Unauthorized] if `spec` is role-gated and `role` is nil
105
+ # A caller who omits role: would otherwise reach a role-gated command
106
+ # unchecked (refuse_role_mismatch no-ops with no bound caller) — refuse here instead.
107
+ # :nodoc:
282
108
  def require_caller_for_role_gated!(spec, role)
283
109
  return unless spec[:role_gated] && role.nil?
284
110
 
@@ -287,26 +113,9 @@ module Hecks
287
113
  "(role:/actor_id:) is bound; dispatching it unbound is refused, not silently unchecked"
288
114
  end
289
115
 
290
- # `dry_run?` (Runtime::Dispatcher) understands only the old flat
291
- # legacy args shape — no to:/with: envelope, `route:` never passed
292
- # (its own header explains why: built directly against
293
- # CommandInterpreter/EntityInterpreter's pre-envelope contract,
294
- # never updated because nothing else needed it to be — a real
295
- # record of history, not a defect this bus should paper over
296
- # silently). `Facade::CommandRequest`/`spec[:legacy_receiver]`
297
- # already know how to name that same flat shape for every receiver
298
- # kind (a bare id under one string key for :aggregate, a
299
- # {aggregate:, entity:} pair of keys for :entity) — this is the one
300
- # door back into it.
301
- #
302
- # @param envelope [Hash{Symbol => Object}] a normalized command request, with an
303
- # optional `:to` route and `:with` facts
304
- # @param receiver [Symbol, nil] `:aggregate`, `:entity`, or nil for anything else
305
- # @param legacy_receiver [Symbol, String, Hash{Symbol => Symbol, String}, nil] where
306
- # the route belongs in the flat shape: a single key for `:aggregate`, an
307
- # `{aggregate:, entity:}` pair of keys for `:entity`
308
- # @return [Hash{Symbol => Object}] the facts, with the route merged in under its
309
- # legacy key(s) when `envelope` carries one
116
+ # dry_run? (Runtime::Dispatcher) only understands the pre-envelope flat
117
+ # args shape; this is the one door back into it from dispatch's own envelope.
118
+ # :nodoc:
310
119
  def flatten_legacy(envelope, receiver, legacy_receiver)
311
120
  facts = envelope[:with] || {}
312
121
  return facts unless envelope.key?(:to)
@@ -321,36 +130,14 @@ module Hecks
321
130
  end
322
131
  end
323
132
 
324
- # ── the audit log `follow` reads back ───────────────────────────────
325
-
326
- # The audit log file one domain's calls are appended to.
327
- #
328
- # @param domain_name [String, Symbol, #to_s] the domain name
329
- # @return [String] the absolute path to that domain's JSONL log file, under
330
- # `log_root`
133
+ # :nodoc:
331
134
  def log_path(domain_name)
332
135
  File.join(log_root, "#{domain_name.to_s.gsub(/[^A-Za-z0-9_-]/, '_')}.jsonl")
333
136
  end
334
137
 
335
- # Appends one call's outcome to its domain's audit log.
336
- #
337
- # Never fails a real call because its own audit log couldn't be
338
- # written — a full disk or a permissions problem is a `follow`
339
- # feature going dark, not a reason to refuse the dispatch/query/
340
- # state call that was actually asked for.
341
- #
342
- # @param domain_name [String, Symbol, nil] the domain to log against; nil skips
343
- # logging entirely (a call that never resolved a bluebook)
344
- # @param tool [String] which tool logged this: `"dispatch"`, `"dry_run"`,
345
- # `"query"`, or `"state"`
346
- # @param summary [String, nil] the caller's one-line summary
347
- # @param source [String, Symbol, nil] the caller's source tag
348
- # @param outcome [Hash{Symbol => Object}] the call's own result, read for `:ok`,
349
- # `:id`, `:error` and `:events`
350
- # @param verb [String, nil] the resolved verb dispatched or queried, when known
351
- # @param role [String, Symbol, nil] the caller's self-asserted role
352
- # @param actor_id [String, nil] the caller's self-asserted identity
353
- # @return [void]
138
+ # Never fails a real call because its own audit log couldn't be written —
139
+ # a full disk silences follow, not the dispatch/query/state call itself.
140
+ # :nodoc:
354
141
  def record!(domain_name, tool:, summary:, source:, outcome:, verb: nil, role: nil, actor_id: nil)
355
142
  return unless domain_name
356
143
 
@@ -363,27 +150,18 @@ module Hecks
363
150
  nil
364
151
  end
365
152
 
366
- # ── the three that drive it ────────────────────────────────────────
367
-
368
- # `dry_run: true` answers a different question than a real dispatch
369
- # does — "would this succeed", not "here is what happened" — so a
370
- # domain refusal is the legitimate, complete answer (`ok: true,
371
- # would_succeed: false`), not a failed call. A malformed request
372
- # (unknown command, a bad args shape) is still a failed call
373
- # (`ok: false`) either way — it never reached the domain to be asked.
153
+ # Issues a command, or previews it with dry_run: true.
374
154
  #
375
- # @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted runtime
155
+ # @param runtime [Runtime::Registry] the booted domain to dispatch against
376
156
  # @param command [String, Symbol] the command name, bare or qualified
377
- # @param summary [String] a one-line human summary of the call, for the audit log
378
- # @param args [Hash] the command's arguments
379
- # @param source [String, Symbol, nil] a `SOURCE_TAGS` tag naming who is calling
380
- # @param dry_run [Boolean] true to check whether the command would succeed,
381
- # without actually dispatching it
382
- # @param role [String, Symbol, nil] the caller's self-asserted role
383
- # @param actor_id [String, nil] the caller's self-asserted identity
384
- # @return [Hash{Symbol => Object}] `:ok` plus, on success, `:id`/`:state`/`:events`
385
- # (a real dispatch) or `:would_succeed`/`:error` (a dry run); on refusal, the
386
- # shape `refused` returns
157
+ # @param summary [String] a one-line audit summary; required
158
+ # @param args [Hash] the command's arguments, JSON-shaped
159
+ # @param source [String, Symbol, nil] a SOURCE_TAGS tag naming who is calling
160
+ # @param dry_run [Boolean] true previews; a false would_succeed is not a failure
161
+ # @param role [String, Symbol, nil] the caller's bound role, or nil to run unbound
162
+ # @param actor_id [String, nil] the caller's identity; requires role:
163
+ # @return [Hash] :ok plus :id/:state/:events (real) or :would_succeed/:error
164
+ # (dry run); or the refused shape
387
165
  def dispatch(runtime:, command:, summary:, args: {}, source: nil, dry_run: false, role: nil, actor_id: nil)
388
166
  bluebook = bluebook_for(runtime)
389
167
  tool = dry_run ? "dry_run" : "dispatch"
@@ -399,24 +177,7 @@ module Hecks
399
177
  outcome
400
178
  end
401
179
 
402
- # Validates and dispatches one command, real or dry-run.
403
- #
404
- # @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted runtime
405
- # @param bluebook [Bluebook::Chapter] the domain's one loaded chapter
406
- # @param command [String, Symbol] the command name, bare or qualified
407
- # @param summary [String, nil] a one-line human summary of the call
408
- # @param args [Hash] the command's arguments, JSON-shaped
409
- # @param source [String, Symbol, nil] a `SOURCE_TAGS` tag naming who is calling
410
- # @param dry_run [Boolean] true to check whether the command would succeed,
411
- # without actually dispatching it
412
- # @param role [String, Symbol, nil] the caller's self-asserted role
413
- # @param actor_id [String, nil] the caller's self-asserted identity
414
- # @return [Hash{Symbol => Object}] the outcome from `real_dispatch`/`dry_run_outcome`,
415
- # merged with `:verb`
416
- # @raise [Runtime::TypeMismatch] if `summary`, `source`, or `actor_id` without
417
- # `role` is invalid
418
- # @raise [Runtime::NotFound] if `command` names no known command
419
- # @raise [Runtime::Unauthorized] if `command` is role-gated and `role` is nil
180
+ # :nodoc:
420
181
  def perform_dispatch(runtime, bluebook, command, summary, args, source, dry_run, role, actor_id)
421
182
  require_summary!(summary)
422
183
  valid_source!(source)
@@ -424,9 +185,9 @@ module Hecks
424
185
  cli = Projector.call(:cli, bluebook: bluebook, options: { program: "mcp" })
425
186
  spec = resolve!(cli, command, asking: false)
426
187
  require_caller_for_role_gated!(spec, role)
427
- envelope = Facade::CommandRequest.normalize(Facade::JsonDoor.deep_symbolize(args),
428
- receiver: spec[:receiver],
429
- legacy_receiver: spec[:legacy_receiver])
188
+ envelope = Doors::CommandRequest.normalize(Doors::JsonDoor.deep_symbolize(args),
189
+ receiver: spec[:receiver],
190
+ legacy_receiver: spec[:legacy_receiver])
430
191
 
431
192
  result = with_caller(role, actor_id) do
432
193
  dry_run ? dry_run_outcome(runtime, spec, envelope, summary: summary) : real_dispatch(runtime, spec, envelope, summary)
@@ -434,30 +195,16 @@ module Hecks
434
195
  result.merge(verb: spec[:verb])
435
196
  end
436
197
 
437
- # Dispatches one command for real and shapes its result for the bus.
438
- #
439
- # @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted runtime
440
- # @param spec [Hash{Symbol => Object}] the resolved command spec (from `resolve!`)
441
- # @param envelope [Hash{Symbol => Object}] the normalized `to:`/`with:` request
442
- # @param summary [String, nil] a one-line human summary of the call
443
- # @return [Hash{Symbol => Object}] `:ok`, `:summary`, `:id`, `:state` (JSON-safe,
444
- # nil for a port operation) and `:events` (name/payload pairs)
198
+ # :nodoc:
445
199
  def real_dispatch(runtime, spec, envelope, summary)
446
200
  result = runtime.dispatch_flat(spec[:verb], envelope)
447
201
  ok(summary: summary,
448
202
  id: result.id,
449
- state: result.state.nil? ? nil : Facade::JsonDoor.materialize(result.state),
450
- events: result.events.map { |event| { name: event.name, payload: Facade::JsonDoor.materialize(event.payload) } })
203
+ state: result.state.nil? ? nil : Doors::JsonDoor.materialize(result.state),
204
+ events: result.events.map { |event| { name: event.name, payload: Doors::JsonDoor.materialize(event.payload) } })
451
205
  end
452
206
 
453
- # Checks whether one command would succeed, without dispatching it for real.
454
- #
455
- # @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted runtime
456
- # @param spec [Hash{Symbol => Object}] the resolved command spec (from `resolve!`)
457
- # @param envelope [Hash{Symbol => Object}] the normalized `to:`/`with:` request
458
- # @param summary [String, nil] a one-line human summary of the call
459
- # @return [Hash{Symbol => Object}] `:ok`, `:summary`, `:would_succeed`, and
460
- # `:error` (the refusal message) when it would not succeed
207
+ # :nodoc:
461
208
  def dry_run_outcome(runtime, spec, envelope, summary:)
462
209
  flat = flatten_legacy(envelope, spec[:receiver], spec[:legacy_receiver])
463
210
  runtime.dry_run?(spec[:verb], **flat)
@@ -466,32 +213,22 @@ module Hecks
466
213
  ok(summary: summary, would_succeed: false, error: e.message)
467
214
  end
468
215
 
469
- # Dispatches a whole sequence of commands as one call.
470
- #
471
- # **One call, many steps** — the survey's own `bin/run <domain> script`
472
- # shape, so an agent issuing a known sequence of commands (open an
473
- # account, then fund it) pays one round trip instead of N. Every step
474
- # goes through `dispatch` itself — same resolution, same audit log
475
- # line per step, same summary/source stamped on all of them since
476
- # the batch is what the caller is declaring intent about, not each
477
- # individual step. Runs every step regardless of an earlier one
478
- # refusing — a later step naming a record an earlier step never
479
- # created will refuse honestly on its own account, which is more
480
- # informative than silently dropping the rest of the batch.
216
+ # Dispatches a sequence of commands as one call, through dispatch itself —
217
+ # same audit log line per step. Runs every step even after an earlier one
218
+ # refuses, since a later step naming a since-refused record will refuse
219
+ # honestly on its own account.
481
220
  #
482
- # @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted runtime
483
- # @param steps [Array<Hash>] each step's `command`/`args`, JSON-shaped
484
- # @param summary [String] a one-line human summary of the whole batch
485
- # @param source [String, Symbol, nil] a `SOURCE_TAGS` tag naming who is calling
486
- # @param role [String, Symbol, nil] the caller's self-asserted role
487
- # @param actor_id [String, nil] the caller's self-asserted identity
488
- # @return [Hash{Symbol => Object}] `:ok` (true only if every step's own `:ok` was
489
- # true), `:summary` and `:results` (each step's own `dispatch` outcome); on a
490
- # refusal outside any one step, the shape `refused` returns
221
+ # @param runtime [Runtime::Registry] the booted domain to dispatch against
222
+ # @param steps [Array<Hash>] each step's command/args, JSON-shaped
223
+ # @param summary [String] a one-line audit summary; required
224
+ # @param source [String, Symbol, nil] a SOURCE_TAGS tag naming who is calling
225
+ # @param role [String, Symbol, nil] the caller's bound role, or nil to run unbound
226
+ # @param actor_id [String, nil] the caller's identity; requires role:
227
+ # @return [Hash] :ok (true only if every step's own :ok was true), :results
491
228
  def dispatch_batch(runtime:, steps:, summary:, source: nil, role: nil, actor_id: nil)
492
229
  require_summary!(summary)
493
230
  results = Array(steps).map do |raw|
494
- step = Facade::JsonDoor.deep_symbolize(raw)
231
+ step = Doors::JsonDoor.deep_symbolize(raw)
495
232
  dispatch(runtime: runtime, command: step[:command], args: step[:args] || {},
496
233
  summary: summary, source: source, role: role, actor_id: actor_id)
497
234
  end
@@ -502,15 +239,14 @@ module Hecks
502
239
 
503
240
  # Answers one declared query.
504
241
  #
505
- # @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted runtime
242
+ # @param runtime [Runtime::Registry] the booted domain to query
506
243
  # @param question [String, Symbol] the query name, bare or qualified
507
- # @param summary [String] a one-line human summary of the call, for the audit log
508
- # @param args [Hash] the query's arguments
509
- # @param source [String, Symbol, nil] a `SOURCE_TAGS` tag naming who is calling
510
- # @param role [String, Symbol, nil] the caller's self-asserted role
511
- # @param actor_id [String, nil] the caller's self-asserted identity
512
- # @return [Hash{Symbol => Object}] `:ok` and `:rows` (each JSON-safe) on success,
513
- # or the shape `refused` returns
244
+ # @param summary [String] a one-line audit summary; required
245
+ # @param args [Hash] the query's arguments, JSON-shaped
246
+ # @param source [String, Symbol, nil] a SOURCE_TAGS tag naming who is calling
247
+ # @param role [String, Symbol, nil] the caller's bound role, or nil to run unbound
248
+ # @param actor_id [String, nil] the caller's identity; requires role:
249
+ # @return [Hash] :ok and :rows (each JSON-safe); or the refused shape
514
250
  def query(runtime:, question:, summary:, args: {}, source: nil, role: nil, actor_id: nil)
515
251
  bluebook = bluebook_for(runtime)
516
252
  outcome = perform_query(bluebook, runtime, question, summary, args, source, role, actor_id)
@@ -525,47 +261,27 @@ module Hecks
525
261
  outcome
526
262
  end
527
263
 
528
- # Validates and answers one query.
529
- #
530
- # @param bluebook [Bluebook::Chapter] the domain's one loaded chapter
531
- # @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted runtime
532
- # @param question [String, Symbol] the query name, bare or qualified
533
- # @param summary [String, nil] a one-line human summary of the call
534
- # @param args [Hash] the query's arguments, JSON-shaped
535
- # @param source [String, Symbol, nil] a `SOURCE_TAGS` tag naming who is calling
536
- # @param role [String, Symbol, nil] the caller's self-asserted role
537
- # @param actor_id [String, nil] the caller's self-asserted identity
538
- # @return [Hash{Symbol => Object}] `:ok`, `:rows` (each JSON-safe) and `:verb`
539
- # @raise [Runtime::TypeMismatch] if `summary`, `source`, or `actor_id` without
540
- # `role` is invalid
541
- # @raise [Runtime::NotFound] if `question` names no known query
264
+ # :nodoc:
542
265
  def perform_query(bluebook, runtime, question, summary, args, source, role, actor_id)
543
266
  require_summary!(summary)
544
267
  valid_source!(source)
545
268
  valid_caller!(role, actor_id)
546
269
  cli = Projector.call(:cli, bluebook: bluebook, options: { program: "mcp" })
547
270
  spec = resolve!(cli, question, asking: true)
548
- rows = with_caller(role, actor_id) { runtime.query(spec[:verb], **Facade::JsonDoor.deep_symbolize(args)) }
271
+ rows = with_caller(role, actor_id) { runtime.query(spec[:verb], **Doors::JsonDoor.deep_symbolize(args)) }
549
272
 
550
- ok(summary: summary, rows: rows.map { |row| Facade::JsonDoor.materialize(row) }).merge(verb: spec[:verb])
273
+ ok(summary: summary, rows: rows.map { |row| Doors::JsonDoor.materialize(row) }).merge(verb: spec[:verb])
551
274
  end
552
275
 
553
- # Reads one aggregate's stored records directly, bypassing any declared query.
276
+ # Reads one aggregate's stored records directly, bypassing any declared
277
+ # query — id: answers one record, omitted answers every record.
554
278
  #
555
- # **What is actually stored** — no verb, no interpretation, the repository
556
- # itself. `id:` given answers one record (`NotFound` when it names
557
- # nothing); omitted answers every record the aggregate currently
558
- # holds. This is the difference `query` can't cover: a query answers a
559
- # declared question, and an aggregate that never declared "list
560
- # everything" has no query this could reuse.
561
- #
562
- # @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted runtime
279
+ # @param runtime [Runtime::Registry] the booted domain to read
563
280
  # @param aggregate [String, Symbol] the aggregate's declared name
564
- # @param summary [String] a one-line human summary of the call, for the audit log
281
+ # @param summary [String] a one-line audit summary; required
565
282
  # @param id [String, Object, nil] one record's identity, or nil for every record
566
- # @param source [String, Symbol, nil] a `SOURCE_TAGS` tag naming who is calling
567
- # @return [Hash{Symbol => Object}] `:ok` and, with `id:`, `:record`; without it,
568
- # `:count` and `:records` (each JSON-safe); or the shape `refused` returns
283
+ # @param source [String, Symbol, nil] a SOURCE_TAGS tag naming who is calling
284
+ # @return [Hash] :ok and, with id:, :record; without it, :count/:records; or refused
569
285
  def state(runtime:, aggregate:, summary:, id: nil, source: nil)
570
286
  bluebook = bluebook_for(runtime)
571
287
  outcome = perform_state(runtime, bluebook, aggregate, summary, id)
@@ -578,18 +294,7 @@ module Hecks
578
294
  outcome
579
295
  end
580
296
 
581
- # Validates and reads one aggregate's stored record(s).
582
- #
583
- # @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted runtime
584
- # @param bluebook [Bluebook::Chapter] the domain's one loaded chapter
585
- # @param aggregate [String, Symbol] the aggregate's declared name
586
- # @param summary [String, nil] a one-line human summary of the call
587
- # @param id [String, Object, nil] one record's identity, or nil for every record
588
- # @return [Hash{Symbol => Object}] `:ok` and, with `id`, `:record`; without it,
589
- # `:count` and `:records` (each JSON-safe)
590
- # @raise [Runtime::TypeMismatch] if `summary` is invalid
591
- # @raise [Runtime::NotFound] if `aggregate` names no known aggregate, or `id`
592
- # names no record of it
297
+ # :nodoc:
593
298
  def perform_state(runtime, bluebook, aggregate, summary, id)
594
299
  require_summary!(summary)
595
300
  ir = aggregate_ir!(bluebook, aggregate)
@@ -598,30 +303,19 @@ module Hecks
598
303
  if id
599
304
  instance = repository.find(id) or
600
305
  raise Runtime::NotFound, "no #{ir.hecks_name} found for id #{id.inspect}"
601
- ok(summary: summary, record: Facade::JsonDoor.materialize(instance.to_h))
306
+ ok(summary: summary, record: Doors::JsonDoor.materialize(instance.to_h))
602
307
  else
603
- records = repository.all.map { |instance| Facade::JsonDoor.materialize(instance.to_h) }
308
+ records = repository.all.map { |instance| Doors::JsonDoor.materialize(instance.to_h) }
604
309
  ok(summary: summary, count: records.length, records: records)
605
310
  end
606
311
  end
607
312
 
608
- # ── the four zoom levels ─────────────────────────────────────────
609
-
610
- # Lists every domain directory under a root.
611
- #
612
- # **Zoom level zero** — every domain directory a root actually holds,
613
- # discovered rather than typed from memory. Every other tool takes
614
- # `domain:` as a directory it assumes the caller already knows; this
615
- # is how a caller who doesn't finds out. `Adapters::Folder#domain?`
616
- # is the same predicate `domain_root`/`nearest_domain` already walk
617
- # up directories checking — a bare `.hecksagon` or one under
618
- # `bluebook/`, the two real shapes this corpus uses.
313
+ # Lists every domain directory found under a root (discovered, not typed).
619
314
  #
620
- # @param under [String] the directory to search, relative to `BOOT_ROOT`
621
- # @return [Hash{Symbol => Object}] `:ok`, `:under`, and `:domains` (each found
622
- # domain's path, relative to `BOOT_ROOT`, sorted); `:domains` is `[]` when
623
- # `under` does not exist
624
- # @raise [Runtime::TypeMismatch] if `under` resolves outside `BOOT_ROOT`
315
+ # @param under [String] the directory to search, relative to BOOT_ROOT
316
+ # @return [Hash] :ok, :under, :domains (each path relative to BOOT_ROOT,
317
+ # sorted); :domains is [] when under does not exist
318
+ # @raise [Runtime::TypeMismatch] if under resolves outside BOOT_ROOT
625
319
  def domains(under: "examples")
626
320
  root = confine!(under, "under")
627
321
  return ok(under: under, domains: []) unless Dir.exist?(root)
@@ -632,17 +326,12 @@ module Hecks
632
326
  ok(under: under, domains: found.map { |name| File.join(under, name) })
633
327
  end
634
328
 
635
- # Lists a domain's aggregates and their command/query names.
329
+ # Lists a domain's aggregates and their command/query names, snake_cased
330
+ # exactly as dispatch/query want them.
636
331
  #
637
- # **Zoom level one** — every aggregate this domain declares, and every
638
- # command/query name each answers to, snake_cased exactly as
639
- # `dispatch`/`query` want it. Enough to pick a target; `describe` is
640
- # the next level down for what one of them actually takes.
641
- #
642
- # @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted runtime
643
- # @return [Hash{Symbol => Object}] `:ok`, `:domain`, and `:aggregates` — an Array
644
- # of `{name:, commands:, queries:}` Hashes, commands and queries sorted,
645
- # command names suffixed `!`; or the shape `refused` returns
332
+ # @param runtime [Runtime::Registry] the booted domain to describe
333
+ # @return [Hash] :ok, :domain, :aggregates (each {name:, commands:, queries:},
334
+ # command names suffixed !); or the refused shape
646
335
  def catalog(runtime:)
647
336
  bluebook = bluebook_for(runtime)
648
337
 
@@ -656,21 +345,14 @@ module Hecks
656
345
  refused(e)
657
346
  end
658
347
 
659
- # Answers one aggregate's (or the whole chapter's) full usage documentation.
660
- #
661
- # **Zoom level two** — the exact same usage document a human gets from
662
- # `bin/docs <domain> [aggregate]` (`Projector::DocsProjector`, the
663
- # identical projection `Surface::AggregateDoor#docs` calls one door
664
- # over): every command's arguments, the states it may be issued
665
- # from, and every way it can refuse. `aggregate:` omitted answers the
666
- # whole chapter.
348
+ # One aggregate's (or the whole chapter's) full usage documentation — the
349
+ # same document hecks docs renders for a human.
667
350
  #
668
- # @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted runtime
351
+ # @param runtime [Runtime::Registry] the booted domain to describe
669
352
  # @param aggregate [String, Symbol, nil] one aggregate's name, or nil for the
670
353
  # whole chapter
671
- # @return [Hash{Symbol => Object}] `:ok`, `:domain` and `:docs` (the rendered
672
- # documentation); or the shape `refused` returns
673
- # @raise [Runtime::NotFound] if `aggregate` names no known aggregate
354
+ # @return [Hash] :ok, :domain, :docs; or the refused shape
355
+ # @raise [Runtime::NotFound] if aggregate names no known aggregate
674
356
  def describe(runtime:, aggregate: nil)
675
357
  bluebook = bluebook_for(runtime)
676
358
  options = aggregate ? { aggregate: aggregate_ir!(bluebook, aggregate).hecks_name } : {}
@@ -680,41 +362,18 @@ module Hecks
680
362
  refused(e)
681
363
  end
682
364
 
683
- # Boots a domain and reports whether its wiring (and, optionally, its logic) is sound.
684
- #
685
- # **Zoom level three** — is the wiring sound at all: every bind names a
686
- # declared aggregate, every adapter satisfies the port it claims, the
687
- # default adapter is usable. `Registry#verify!` (`runtime/registry/
688
- # verification.rb`) is the one place this repo already answers that
689
- # question, and `Runtime::Loader.boot` already calls it as the last
690
- # step of every boot — so this is the one method here that boots for
691
- # itself rather than taking a `runtime:` already in hand, because a
692
- # runtime that successfully reached this line already answered the
693
- # question. Given a domain path, not a booted runtime, deliberately:
694
- # asking "is this valid" about a domain that failed to boot at all
695
- # has to be askable without a runtime to hand it.
365
+ # Boots a domain and reports whether its wiring is sound; deep: true also
366
+ # runs Bluebook::ModelCheck past wiring, into logic. Boots for itself,
367
+ # unlike every other tool here, since a domain that failed to boot has
368
+ # no runtime to hand in — any boot failure answers the question, not just
369
+ # WiringError.
696
370
  #
697
- # `deep: true` goes past wiring into logic — `Bluebook::ModelCheck`,
698
- # the lightweight-formal-methods leg (dead lifecycle transitions, a
699
- # saga state no handler chain reaches, a dispatch to nowhere). Opt-in
700
- # and separate from the base check on purpose: a wiring defect is
701
- # "this cannot run at all", a model-check finding is "this runs, but
702
- # part of it can never fire" — two different questions, and the
703
- # first is far cheaper to ask on every boot.
704
- #
705
- # Any boot failure answers the question, not only `WiringError` — a
706
- # domain path with no `.hecksagon`, a malformed bluebook, is just as
707
- # much "not valid" as a real wiring mismatch, and this tool exists
708
- # precisely so none of those ever cross a projection of this bus as
709
- # a crash.
710
- #
711
- # @param domain [String] the domain's directory path, relative to `BOOT_ROOT`
712
- # @param deep [Boolean] also run `Bluebook::ModelCheck` past wiring, into logic
713
- # @return [Hash{Symbol => Object}] `{ok: true, domain:, valid: true}`, plus
714
- # `:findings` (each `{kind:, severity:, subject:, message:}`) when `deep`; or
715
- # `{ok: false, domain:, valid: false, error:}` if the boot itself failed
371
+ # @param domain [String] the domain's directory path, relative to BOOT_ROOT
372
+ # @param deep [Boolean] also run Bluebook::ModelCheck past wiring, into logic
373
+ # @return [Hash] {ok: true, domain:, valid: true} plus :findings when deep;
374
+ # {ok: false, domain:, valid: false, error:} if the boot itself failed
716
375
  def validate(domain:, deep: false)
717
- runtime = Hecks.boot(confine!(domain, "domain"), install_facade: false)
376
+ runtime = Hecks.boot(confine!(domain, "domain"), install_doors: false)
718
377
  result = { ok: true, domain: domain, valid: true }
719
378
 
720
379
  if deep
@@ -728,22 +387,12 @@ module Hecks
728
387
  { ok: false, domain: domain, valid: false, error: "#{e.class}: #{e.message}" }
729
388
  end
730
389
 
731
- # ── beyond the zoom levels ──────────────────────────────────────────
732
-
733
- # The full write history, not just the current head — `bin/history`'s
734
- # own logic, unchanged: every operation that ever touched the
735
- # aggregate, read off its repository's own `entries`. `Registry#repository`
736
- # always hands back a `Ports::Persistence::AppendOnly`-wrapped adapter,
737
- # never the bare adapter, so this never needs to guard against one —
738
- # what varies by adapter is only how much the wrapped `entries` itself
739
- # actually holds: a fresh Memory boot honestly has none yet, and a
740
- # durable adapter (Heki, Postgres, SQLite, D1) has whatever it
741
- # journaled.
390
+ # The full write history, not just the current head — every operation that
391
+ # ever touched each aggregate, off its repository's own append-only entries.
742
392
  #
743
- # @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted runtime
744
- # @return [Hash{Symbol => Object}] `:ok`, `:domain` and `:history` (each
745
- # aggregate's storage name mapped to its `journal_entries`); or the shape
746
- # `refused` returns
393
+ # @param runtime [Runtime::Registry] the booted domain to read
394
+ # @return [Hash] :ok, :domain, :history (each aggregate's storage name
395
+ # mapped to its journal_entries); or the refused shape
747
396
  def history(runtime:)
748
397
  bluebook = bluebook_for(runtime)
749
398
  entries = bluebook.aggregates.each_with_object({}) do |aggregate, all|
@@ -756,30 +405,17 @@ module Hecks
756
405
  refused(e)
757
406
  end
758
407
 
759
- # One aggregate's full append-only write history, JSON-safe.
760
- #
761
- # @param repository [Ports::Persistence::AppendOnly] the aggregate's repository, as
762
- # `Registry#repository` always returns it
763
- # @return [Array<Hash>] each journal entry's `:operation`, `:id` and `:state`
764
- # (materialized); `[]` when the adapter has journaled nothing yet
408
+ # :nodoc:
765
409
  def journal_entries(repository)
766
- repository.entries.map { |entry| { operation: entry.operation, id: entry.id, state: Facade::JsonDoor.materialize(entry.state) } }
410
+ repository.entries.map { |entry| { operation: entry.operation, id: entry.id, state: Doors::JsonDoor.materialize(entry.state) } }
767
411
  end
768
412
 
769
- # `.behaviors` files, run and reported — hand-curated examples of how
770
- # to use a domain, in domain vocabulary (`docs/guides/behaviors.md`),
771
- # the survey's own "honest-refusal", generated-example-suite items.
772
- # `Hecks::Behaviors` boots each test fresh through `Hecks.boot_files`
773
- # itself — `target:` names a `.behaviors` file or a directory to
774
- # sweep, never a `runtime:`, the one other method here besides
775
- # `validate` that takes a path instead.
413
+ # Runs a domain's hand-curated .behaviors examples and reports the results.
776
414
  #
777
- # @param target [String] a `.behaviors` file's path, or a directory to sweep
778
- # for every `.behaviors` file under it
779
- # @return [Hash{Symbol => Object}] `:ok`, `:target`, `:files` (each file's own
780
- # `behaviors_file` shape) and `:counts` (`Behaviors.summarize`'s tally); or
781
- # the shape `refused` returns
782
- # @raise [Runtime::NotFound] if `target` is nil or names no real file or directory
415
+ # @param target [String] a .behaviors file's path, or a directory to sweep
416
+ # @return [Hash] :ok, :target, :files (each file's behaviors_file shape),
417
+ # :counts; or the refused shape
418
+ # @raise [Runtime::NotFound] if target is nil or names no real file or directory
783
419
  def behaviors(target:)
784
420
  require_relative "behaviors"
785
421
  raise Runtime::NotFound, "no such file or directory: #{target.inspect}" unless target && File.exist?(target)
@@ -795,31 +431,20 @@ module Hecks
795
431
  refused(e)
796
432
  end
797
433
 
798
- # Shapes one `.behaviors` file's own result for the bus.
799
- #
800
- # @param result [Behaviors::FileResult] one file's run result
801
- # @return [Hash{Symbol => Object}] `:path`, `:parse_error`, and `:runs` (each
802
- # test's `description`/`status`/`message`)
434
+ # :nodoc:
803
435
  def behaviors_file(result)
804
436
  { path: result.path,
805
437
  parse_error: result.parse_error,
806
438
  runs: Array(result.runs).map { |run| { description: run.description, status: run.status, message: run.message } } }
807
439
  end
808
440
 
809
- # A live tail without a live process — `bin/hecks_mcp_door` (its
810
- # transport of MCP-over-stdio) answers one request at a time, no push
811
- # channel to a client that only ever asks. This is the honest version
812
- # of the survey's `storehouse follow` for that shape: not a
813
- # subscription, a durable JSONL log every `dispatch`/`query`/`state`/
814
- # dry-run call appends to (`record!`), tailed back here. Still real,
815
- # still cross-process — the log outlives any one door's own process —
816
- # just pull instead of push.
441
+ # Tails this bus's own dispatch/query/state audit log — a pull-based
442
+ # substitute for a push subscription, since a stdio door answers one
443
+ # request at a time with no channel to push through.
817
444
  #
818
- # @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted runtime
819
- # @param limit [Integer, #to_i] how many of the most recent log entries to return;
820
- # clamped to at least 1
821
- # @return [Hash{Symbol => Object}] `:ok`, `:domain` and `:entries` (each logged
822
- # call's own JSON entry); or the shape `refused` returns
445
+ # @param runtime [Runtime::Registry] the booted domain to tail
446
+ # @param limit [Integer, #to_i] how many recent log entries to return
447
+ # @return [Hash] :ok, :domain, :entries; or the refused shape
823
448
  def follow(runtime:, limit: 20)
824
449
  bluebook = bluebook_for(runtime)
825
450
  entries = log_lines(bluebook.name).last([limit.to_i, 1].max)
@@ -829,11 +454,7 @@ module Hecks
829
454
  refused(e)
830
455
  end
831
456
 
832
- # Reads back one domain's whole audit log.
833
- #
834
- # @param domain_name [String, Symbol, #to_s] the domain name
835
- # @return [Array<Hash>] every logged entry, parsed with Symbol keys, in log
836
- # order; `[]` if the domain has no log file yet
457
+ # :nodoc:
837
458
  def log_lines(domain_name)
838
459
  path = log_path(domain_name)
839
460
  return [] unless File.exist?(path)
@@ -841,32 +462,18 @@ module Hecks
841
462
  File.readlines(path).map { |line| JSON.parse(line, symbolize_names: true) }
842
463
  end
843
464
 
844
- # What actually happened, with payloads — distinct from `state`
845
- # (what's stored now) and `history` (append-only operation
846
- # snapshots, no payload). Not a domain-wide event-sourcing replay:
847
- # "one boot per call" means `runtime.events` is always empty except
848
- # during the very call that populated it, discarded the moment that
849
- # call returns — there is no cross-call in-memory log to read here.
850
- # So this reads the same durable audit log `follow` already tails
851
- # (`record!` now stamps a successful dispatch's own announced
852
- # events onto its log line), reshaped: `follow` answers "what was
853
- # called, in order, across every tool"; this answers "what happened
854
- # to one aggregate/record" — events this bus witnessed, which is
855
- # every real dispatch ever routed through it, but no more than that.
856
- # `aggregate:` narrows to one aggregate; `id:` (requires
857
- # `aggregate:` — an id alone is not unique across aggregates)
858
- # narrows to one record's own events.
465
+ # What actually happened, with payloads, to one aggregate or record — read
466
+ # off this bus's own audit log (a dispatch's announced events), not a full
467
+ # event-sourcing replay. aggregate: narrows the search; id: (requires
468
+ # aggregate:) narrows further to one record.
859
469
  #
860
- # @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted runtime
861
- # @param aggregate [String, Symbol, nil] narrows to one aggregate's own events
862
- # @param id [String, Object, nil] narrows to one record's own events; requires
863
- # `aggregate`
864
- # @param limit [Integer, #to_i, nil] how many of the most recent matching events
865
- # to return; nil returns every one found
866
- # @return [Hash{Symbol => Object}] `:ok`, `:domain` and `:events` (each with
867
- # `:name`, `:payload`, `:time`, `:verb` and `:id`); or the shape `refused` returns
868
- # @raise [Runtime::TypeMismatch] if `id` is given without `aggregate`
869
- # @raise [Runtime::NotFound] if `aggregate` names no known aggregate
470
+ # @param runtime [Runtime::Registry] the booted domain to search
471
+ # @param aggregate [String, Symbol, nil] narrows the search to one aggregate
472
+ # @param id [String, Object, nil] narrows further to one record; requires aggregate
473
+ # @param limit [Integer, #to_i, nil] how many recent matching events to
474
+ # return; nil returns every one found
475
+ # @return [Hash] :ok, :domain, :events; or the refused shape
476
+ # @raise [Runtime::TypeMismatch] if id is given without aggregate
870
477
  def events(runtime:, aggregate: nil, id: nil, limit: nil)
871
478
  raise Runtime::TypeMismatch, "id: requires aggregate: too — an id alone is not unique across aggregates" if id && !aggregate
872
479
 
@@ -881,20 +488,7 @@ module Hecks
881
488
  refused(e)
882
489
  end
883
490
 
884
- # One log line's own contribution to `events`, above — nil unless it
885
- # was a successful dispatch carrying events, matching `fqn` (when an
886
- # aggregate narrowed the search) and `id` (when a record did). A
887
- # self-contained per-entry check with nothing to share with its
888
- # neighbors, extracted only to keep `events` itself to the query's
889
- # own shape: build the filter, apply the limit, wrap the result.
890
- #
891
- # @param entry [Hash{Symbol => Object}] one logged audit entry
892
- # @param fqn [String, nil] narrows to a dispatch whose verb starts with this
893
- # aggregate FQN; nil matches any aggregate
894
- # @param id [String, Object, nil] narrows to a dispatch whose settled record has
895
- # this id; nil matches any record
896
- # @return [Array<Hash>, nil] `entry`'s own events (each with `:time`, `:verb` and
897
- # `:id` merged in), or nil if `entry` isn't a matching successful dispatch
491
+ # :nodoc:
898
492
  def entry_events(entry, fqn, id)
899
493
  return unless entry[:tool] == "dispatch" && entry[:ok] && entry[:events]
900
494
  return if fqn && !entry[:verb].to_s.start_with?("#{fqn}.")
@@ -903,39 +497,18 @@ module Hecks
903
497
  entry[:events].map { |event| event.merge(time: entry[:time], verb: entry[:verb], id: entry[:id]) }
904
498
  end
905
499
 
906
- # ── shared shape ────────────────────────────────────────────────────
907
-
908
- # `Runtime::WiringError` belongs here too, alongside the true domain
909
- # refusals — not because it is one (it's a structural defect, not a
910
- # rule the caller broke), but because "this domain isn't wired to
911
- # answer what you're asking" (a `role:`+`actor_id:` caller reaching
912
- # an authorization port nothing implements, a dry run against a
913
- # port verb) is exactly the shape this bus promises never crashes
914
- # through it. `dry_run_outcome` already treats it this way locally;
915
- # this makes every other caller of `refusal_classes` do the same.
916
- #
917
- # @return [Array<Class>] every exception class this bus catches and reshapes
918
- # into an honest refusal rather than letting crash through
500
+ # Runtime::WiringError is included alongside the true domain refusals: a
501
+ # bus caller sees an honest refusal here too, not a crash, even though a
502
+ # wiring defect isn't a rule the caller broke.
503
+ # :nodoc:
919
504
  def refusal_classes = [Runtime::NotFound, Runtime::TypeMismatch, Runtime::WiringError, *Runtime::DOMAIN_REFUSALS]
920
505
 
921
- # Builds a successful outcome Hash.
922
- #
923
- # @param fields [Hash{Symbol => Object}] fields to merge in alongside `ok: true`
924
- # @return [Hash{Symbol => Object}] `{ok: true}` merged with `fields`
506
+ # :nodoc:
925
507
  def ok(**fields) = { ok: true }.merge(fields)
926
508
 
927
- # Builds a refused outcome Hash from a caught exception.
928
- #
929
- # **An honest refusal, not a crash** — the survey's own item #9: "an
930
- # explicit, structured refusal a caller can act on" rather than a
931
- # stack trace an agent has to parse to find the one line that
932
- # mattered. The domain's own refusal text travels verbatim
933
- # (`RefusalWording` already renders every one of these to be read),
934
- # this only wraps it consistently.
935
- #
936
- # @param error [StandardError] the caught exception, a member of `refusal_classes`
937
- # @param summary [String, nil] the caller's one-line summary
938
- # @return [Hash{Symbol => Object}] `{ok: false, summary:, error:}`
509
+ # The domain's own refusal text travels verbatim; this only wraps it
510
+ # consistently, rather than letting a stack trace reach the caller.
511
+ # :nodoc:
939
512
  def refused(error, summary: nil) = { ok: false, summary: summary, error: error.message }
940
513
  end
941
514
  end