hecks 2.7.0 → 2.9.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 (463) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +11 -0
  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 +18 -99
  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 +4 -30
  23. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +34 -108
  24. data/lib/hecks/adapters/driven/postgres.rb +145 -134
  25. data/lib/hecks/adapters/driven/postgres_era.adapter +4 -15
  26. data/lib/hecks/adapters/driven/prism.rb +7 -30
  27. data/lib/hecks/adapters/driven/sql_query_builder.rb +21 -121
  28. data/lib/hecks/adapters/driven/sqlite/codec.rb +8 -40
  29. data/lib/hecks/adapters/driven/sqlite/projection.rb +26 -82
  30. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +40 -84
  31. data/lib/hecks/adapters/driven/sqlite.rb +128 -65
  32. data/lib/hecks/adapters/driven/tenant_provisioner.rb +15 -37
  33. data/lib/hecks/adapters/driven.rb +5 -36
  34. data/lib/hecks/adapters/driving/github_webhook.rb +8 -76
  35. data/lib/hecks/behaviors/dsl.rb +7 -25
  36. data/lib/hecks/behaviors/expectations.rb +32 -142
  37. data/lib/hecks/behaviors/ir.rb +7 -20
  38. data/lib/hecks/behaviors/rspec.rb +6 -17
  39. data/lib/hecks/behaviors/runner.rb +10 -34
  40. data/lib/hecks/behaviors.rb +4 -8
  41. data/lib/hecks/bluebook/aggregate.rb +9 -75
  42. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +9 -38
  43. data/lib/hecks/bluebook/assembly/build.rb +4 -15
  44. data/lib/hecks/bluebook/assembly/contract.rb +15 -136
  45. data/lib/hecks/bluebook/assembly/contracts.rb +54 -174
  46. data/lib/hecks/bluebook/assembly/marks.rb +31 -223
  47. data/lib/hecks/bluebook/assembly/specializer.rb +6 -52
  48. data/lib/hecks/bluebook/assembly.rb +6 -45
  49. data/lib/hecks/bluebook/attribute.rb +12 -61
  50. data/lib/hecks/bluebook/behaviour/aggregate.rb +10 -39
  51. data/lib/hecks/bluebook/behaviour/attribute.rb +3 -14
  52. data/lib/hecks/bluebook/behaviour/chapter.rb +17 -42
  53. data/lib/hecks/bluebook/behaviour/command.rb +14 -66
  54. data/lib/hecks/bluebook/behaviour/domain_port.rb +4 -20
  55. data/lib/hecks/bluebook/behaviour/entity.rb +7 -27
  56. data/lib/hecks/bluebook/behaviour/hexagon.rb +23 -41
  57. data/lib/hecks/bluebook/behaviour/lifecycle.rb +15 -40
  58. data/lib/hecks/bluebook/behaviour/policy.rb +23 -52
  59. data/lib/hecks/bluebook/behaviour/process_manager.rb +30 -57
  60. data/lib/hecks/bluebook/behaviour/read_model.rb +38 -26
  61. data/lib/hecks/bluebook/behaviour/traits.rb +27 -64
  62. data/lib/hecks/bluebook/behaviour/value_object.rb +5 -24
  63. data/lib/hecks/bluebook/capabilities.rb +17 -49
  64. data/lib/hecks/bluebook/chapter.rb +10 -48
  65. data/lib/hecks/bluebook/command.rb +23 -81
  66. data/lib/hecks/bluebook/domain_port.rb +14 -63
  67. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +50 -229
  68. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +50 -377
  69. data/lib/hecks/bluebook/dsl/attribute_collector.rb +33 -204
  70. data/lib/hecks/bluebook/dsl/binding_proxy.rb +17 -65
  71. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +86 -708
  72. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +56 -170
  73. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
  74. data/lib/hecks/bluebook/dsl/command_builder.rb +74 -546
  75. data/lib/hecks/bluebook/dsl/const_shim.rb +8 -41
  76. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +12 -87
  77. data/lib/hecks/bluebook/dsl/entity_builder.rb +44 -330
  78. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +20 -269
  79. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +26 -189
  80. data/lib/hecks/bluebook/dsl/identity_declaration.rb +13 -88
  81. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +6 -20
  82. data/lib/hecks/bluebook/dsl/policy_builder.rb +16 -88
  83. data/lib/hecks/bluebook/dsl/port_builder.rb +2 -6
  84. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +22 -96
  85. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +31 -184
  86. data/lib/hecks/bluebook/dsl/query_builder.rb +11 -42
  87. data/lib/hecks/bluebook/dsl/read_model_builder.rb +75 -201
  88. data/lib/hecks/bluebook/dsl/rule_reference.rb +22 -163
  89. data/lib/hecks/bluebook/dsl/translation_builder.rb +28 -114
  90. data/lib/hecks/bluebook/dsl/value_object_builder.rb +10 -124
  91. data/lib/hecks/bluebook/dsl/word_gate.rb +16 -162
  92. data/lib/hecks/bluebook/dsl/world_builder.rb +19 -106
  93. data/lib/hecks/bluebook/entity.rb +13 -71
  94. data/lib/hecks/bluebook/expression/ast_json.rb +24 -190
  95. data/lib/hecks/bluebook/expression/ast_reader.rb +12 -54
  96. data/lib/hecks/bluebook/expression/canonical_form.rb +11 -88
  97. data/lib/hecks/bluebook/expression/evaluator.rb +47 -295
  98. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +30 -147
  99. data/lib/hecks/bluebook/expression/resolver.rb +37 -660
  100. data/lib/hecks/bluebook/hexagon.rb +5 -28
  101. data/lib/hecks/bluebook/lifecycle.rb +5 -16
  102. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -7
  103. data/lib/hecks/bluebook/meta_validator/judge.rb +111 -361
  104. data/lib/hecks/bluebook/meta_validator/plan.rb +33 -162
  105. data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -7
  106. data/lib/hecks/bluebook/meta_validator/readings.rb +56 -342
  107. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -176
  108. data/lib/hecks/bluebook/meta_validator/shapes.rb +54 -268
  109. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +47 -308
  110. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +7 -43
  111. data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -18
  112. data/lib/hecks/bluebook/meta_validator.rb +79 -349
  113. data/lib/hecks/bluebook/model_check/client_profile.rb +10 -91
  114. data/lib/hecks/bluebook/model_check.rb +102 -600
  115. data/lib/hecks/bluebook/pattern_subset.rb +11 -69
  116. data/lib/hecks/bluebook/process_manager.rb +13 -83
  117. data/lib/hecks/bluebook/project_loader.rb +3 -39
  118. data/lib/hecks/bluebook/project_register.rb +12 -60
  119. data/lib/hecks/bluebook/query.rb +9 -48
  120. data/lib/hecks/bluebook/read_model.rb +57 -81
  121. data/lib/hecks/bluebook/reference.rb +7 -41
  122. data/lib/hecks/bluebook/smoke_test.rb +8 -98
  123. data/lib/hecks/bluebook/synthesizer.rb +13 -62
  124. data/lib/hecks/bluebook/translation.rb +26 -62
  125. data/lib/hecks/bluebook/value_object.rb +11 -36
  126. data/lib/hecks/bluebook.rb +5 -22
  127. data/lib/hecks/cache_dir.rb +84 -0
  128. data/lib/hecks/cli/document.rb +86 -0
  129. data/lib/hecks/cli/ir.rb +45 -0
  130. data/lib/hecks/cli/mcp.rb +31 -0
  131. data/lib/hecks/cli/mcp_door.rb +366 -0
  132. data/lib/hecks/cli/model_check.rb +203 -0
  133. data/lib/hecks/cli/project_cli.rb +90 -0
  134. data/lib/hecks/cli/project_diagrams.rb +36 -0
  135. data/lib/hecks/cli/run.rb +173 -0
  136. data/lib/hecks/cli/smoke_test.rb +45 -0
  137. data/lib/hecks/cli/stores.rb +53 -0
  138. data/lib/hecks/cli.rb +160 -0
  139. data/lib/hecks/construct.rb +8 -34
  140. data/lib/hecks/deploy/bluebook/deploy.bluebook +33 -142
  141. data/lib/hecks/deploy/bluebook/deploy.hecksagon +2 -9
  142. data/lib/hecks/embryonaut_bluebook/lock.rb +3 -35
  143. data/lib/hecks/embryonaut_bluebook/shape.rb +1 -17
  144. data/lib/hecks/embryonaut_bluebook/vendor.rb +8 -53
  145. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +4 -19
  146. data/lib/hecks/embryonaut_bluebook.rb +4 -71
  147. data/lib/hecks/facade/cli_door.rb +24 -106
  148. data/lib/hecks/facade/cli_runner.rb +28 -172
  149. data/lib/hecks/facade/command_request.rb +13 -33
  150. data/lib/hecks/facade/handle.rb +34 -121
  151. data/lib/hecks/facade/json_door.rb +31 -146
  152. data/lib/hecks/facade/surface/aggregate_door.rb +39 -104
  153. data/lib/hecks/facade/surface/chapter.rb +10 -54
  154. data/lib/hecks/facade/surface.rb +8 -23
  155. data/lib/hecks/facade.rb +10 -25
  156. data/lib/hecks/forms/app.rb +15 -70
  157. data/lib/hecks/forms/command_form_renderer.rb +10 -29
  158. data/lib/hecks/forms/field_renderer.rb +17 -156
  159. data/lib/hecks/forms/field_shape.rb +30 -222
  160. data/lib/hecks/forms/html.rb +12 -47
  161. data/lib/hecks/forms/index_renderer.rb +1 -4
  162. data/lib/hecks/forms/page.rb +3 -9
  163. data/lib/hecks/forms/params.rb +14 -60
  164. data/lib/hecks/forms/port_argument.rb +2 -19
  165. data/lib/hecks/forms/query_form_renderer.rb +4 -79
  166. data/lib/hecks/forms/record_renderer.rb +8 -75
  167. data/lib/hecks/forms/record_table.rb +7 -50
  168. data/lib/hecks/forms/reference_options.rb +3 -30
  169. data/lib/hecks/forms/value_object_shape.rb +8 -35
  170. data/lib/hecks/forms.rb +5 -33
  171. data/lib/hecks/fqn.rb +5 -12
  172. data/lib/hecks/framework/bluebook/compliance.bluebook +16 -65
  173. data/lib/hecks/framework/bluebook/console_settings.bluebook +47 -134
  174. data/lib/hecks/framework/bluebook/framework.hecksagon +5 -14
  175. data/lib/hecks/framework/bluebook/governance.bluebook +13 -34
  176. data/lib/hecks/framework/bluebook/identity.bluebook +11 -26
  177. data/lib/hecks/framework/bluebook/privacy.bluebook +8 -33
  178. data/lib/hecks/framework.rb +11 -80
  179. data/lib/hecks/freezer.rb +5 -39
  180. data/lib/hecks/grammar/expression.bluebook +15 -40
  181. data/lib/hecks/grammar/translation.bluebook +3 -6
  182. data/lib/hecks/grammar.rb +16 -41
  183. data/lib/hecks/ir.rb +14 -83
  184. data/lib/hecks/language/adapter.bluebook +2 -7
  185. data/lib/hecks/language/bluebook/aggregate.bluebook +56 -221
  186. data/lib/hecks/language/bluebook/attaches/paging.bluebook +6 -15
  187. data/lib/hecks/language/bluebook/bluebook.bluebook +15 -83
  188. data/lib/hecks/language/bluebook/bluebook.hecksagon +3 -15
  189. data/lib/hecks/language/bluebook/command.bluebook +27 -144
  190. data/lib/hecks/language/bluebook/entity.bluebook +24 -97
  191. data/lib/hecks/language/bluebook/policy.bluebook +16 -67
  192. data/lib/hecks/language/bluebook/process_manager.bluebook +28 -151
  193. data/lib/hecks/language/bluebook/projection.bluebook +126 -60
  194. data/lib/hecks/language/bluebook/query.bluebook +12 -43
  195. data/lib/hecks/language/bluebook/shape.bluebook +23 -73
  196. data/lib/hecks/language/bluebook/syntax.bluebook +51 -262
  197. data/lib/hecks/language/bluebook/vocabulary.bluebook +98 -197
  198. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +3 -10
  199. data/lib/hecks/language/hecksagon/domain_port.bluebook +4 -19
  200. data/lib/hecks/language/hecksagon/hecksagon.bluebook +6 -25
  201. data/lib/hecks/language/hecksagon/port_operation.bluebook +3 -14
  202. data/lib/hecks/language/oidc.json +35 -0
  203. data/lib/hecks/language/port.bluebook +4 -17
  204. data/lib/hecks/language/translation/translation.bluebook +3 -9
  205. data/lib/hecks/language/translation/translation_aggregate.bluebook +5 -24
  206. data/lib/hecks/language/world/wiring.bluebook +1 -3
  207. data/lib/hecks/language/world/world.bluebook +5 -7
  208. data/lib/hecks/literal.rb +8 -50
  209. data/lib/hecks/mcp_door_scope.rb +95 -0
  210. data/lib/hecks/mcp_stdio_guard.rb +4 -41
  211. data/lib/hecks/naming.rb +29 -137
  212. data/lib/hecks/ports/access_control.rb +7 -23
  213. data/lib/hecks/ports/agent/answers.rb +5 -23
  214. data/lib/hecks/ports/agent.rb +47 -172
  215. data/lib/hecks/ports/authentication.rb +12 -39
  216. data/lib/hecks/ports/authorization.rb +16 -43
  217. data/lib/hecks/ports/clock.rb +5 -45
  218. data/lib/hecks/ports/extraction.rb +7 -12
  219. data/lib/hecks/ports/identity_assignment.rb +8 -29
  220. data/lib/hecks/ports/identity_generation.rb +6 -29
  221. data/lib/hecks/ports/identity_resolution.rb +2 -10
  222. data/lib/hecks/ports/key_vault.rb +9 -45
  223. data/lib/hecks/ports/persistence/append_only.rb +62 -65
  224. data/lib/hecks/ports/persistence/binding_policy.rb +4 -14
  225. data/lib/hecks/ports/persistence/codec_boundary.rb +20 -80
  226. data/lib/hecks/ports/persistence/null_saga_store.rb +4 -20
  227. data/lib/hecks/ports/persistence/plugin.rb +13 -42
  228. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +38 -141
  229. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +15 -115
  230. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +16 -84
  231. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +7 -26
  232. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -22
  233. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +55 -181
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/events.rb +77 -0
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +12 -122
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +28 -128
  237. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +73 -474
  238. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +31 -124
  239. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +45 -219
  240. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +13 -108
  241. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +18 -75
  242. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +9 -49
  243. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +6 -123
  244. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +23 -55
  245. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +14 -72
  246. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +7 -16
  247. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +14 -115
  248. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +13 -45
  249. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +127 -554
  250. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +13 -63
  251. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +2 -17
  252. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +3 -14
  253. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +13 -58
  254. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -17
  255. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -26
  256. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +9 -25
  257. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +22 -122
  258. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +34 -123
  259. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +5 -12
  260. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +3 -9
  261. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -14
  262. data/lib/hecks/ports/persistence/plugins/era.rb +4 -23
  263. data/lib/hecks/ports/persistence/postgres_dump/connection.rb +7 -23
  264. data/lib/hecks/ports/persistence/postgres_dump.rb +4 -35
  265. data/lib/hecks/ports/persistence/remote_runtime.rb +2 -24
  266. data/lib/hecks/ports/persistence/repository_factory.rb +8 -27
  267. data/lib/hecks/ports/persistence/state_codec.rb +23 -103
  268. data/lib/hecks/ports/persistence.rb +7 -14
  269. data/lib/hecks/ports/projection.rb +42 -84
  270. data/lib/hecks/ports/query/in_memory.rb +10 -58
  271. data/lib/hecks/ports/query/ordering.rb +11 -32
  272. data/lib/hecks/ports/query.rb +14 -37
  273. data/lib/hecks/ports.rb +2 -8
  274. data/lib/hecks/projections/bootstrap_table.rb +11 -43
  275. data/lib/hecks/projections/deploy/fargate/assembly.rb +2 -15
  276. data/lib/hecks/projections/deploy/fargate/cdn.rb +1 -48
  277. data/lib/hecks/projections/deploy/fargate/cdn_default.rb +2 -11
  278. data/lib/hecks/projections/deploy/fargate/check.rb +5 -14
  279. data/lib/hecks/projections/deploy/fargate/containers.rb +3 -78
  280. data/lib/hecks/projections/deploy/fargate/extras.rb +3 -44
  281. data/lib/hecks/projections/deploy/fargate/monitoring.rb +0 -44
  282. data/lib/hecks/projections/deploy/fargate/settings.rb +2 -54
  283. data/lib/hecks/projections/deploy/fargate/yaml.rb +13 -58
  284. data/lib/hecks/projections/deploy/fargate.rb +35 -202
  285. data/lib/hecks/projections/deploy/lambda.rb +151 -679
  286. data/lib/hecks/projections/deploy/preview/access_yaml.rb +1 -33
  287. data/lib/hecks/projections/deploy/preview/containers.rb +6 -111
  288. data/lib/hecks/projections/deploy/preview/routing_yaml.rb +4 -56
  289. data/lib/hecks/projections/deploy/preview/script.rb +2 -14
  290. data/lib/hecks/projections/deploy/preview/settings.rb +3 -33
  291. data/lib/hecks/projections/deploy/preview/task_yaml.rb +2 -38
  292. data/lib/hecks/projections/deploy/preview/template.rb +2 -58
  293. data/lib/hecks/projections/deploy/preview.rb +2 -67
  294. data/lib/hecks/projections/deploy/scripts/settings.rb +3 -25
  295. data/lib/hecks/projections/deploy/scripts.rb +4 -57
  296. data/lib/hecks/projections/deploy/shared.rb +47 -146
  297. data/lib/hecks/projections/deploy/smoke.rb +4 -61
  298. data/lib/hecks/projections/deploy/template_diff/comparer.rb +3 -25
  299. data/lib/hecks/projections/deploy/template_diff/loader.rb +2 -11
  300. data/lib/hecks/projections/deploy/template_diff.rb +5 -57
  301. data/lib/hecks/projections/diagrams.rb +20 -582
  302. data/lib/hecks/projections/glossary/html.rb +14 -130
  303. data/lib/hecks/projections/glossary/markdown.rb +8 -69
  304. data/lib/hecks/projections/glossary/mermaid.rb +9 -66
  305. data/lib/hecks/projections/glossary/sensitivity.rb +5 -34
  306. data/lib/hecks/projections/glossary/sentences.rb +14 -159
  307. data/lib/hecks/projections/glossary.rb +30 -222
  308. data/lib/hecks/projections/ir.rb +3 -10
  309. data/lib/hecks/projections/model/deviations.rb +15 -42
  310. data/lib/hecks/projections/model.rb +12 -51
  311. data/lib/hecks/projections/oidc.rb +12 -56
  312. data/lib/hecks/projections/parser_table.rb +9 -28
  313. data/lib/hecks/projections/reference.rb +9 -16
  314. data/lib/hecks/projections/rust_vocabulary.rb +12 -250
  315. data/lib/hecks/projections/shape.rb +3 -29
  316. data/lib/hecks/projections/statements.rb +7 -86
  317. data/lib/hecks/projections/vocabulary.rb +6 -22
  318. data/lib/hecks/projections.rb +2 -16
  319. data/lib/hecks/projector/cli_projector.rb +60 -375
  320. data/lib/hecks/projector/docs_projector.rb +10 -201
  321. data/lib/hecks/projector/exporter.rb +46 -138
  322. data/lib/hecks/projector/ir_projector.rb +2 -9
  323. data/lib/hecks/projector/narrate_projector.rb +15 -189
  324. data/lib/hecks/projector/target.rb +13 -76
  325. data/lib/hecks/projector.rb +30 -121
  326. data/lib/hecks/query_specification/common/comparators.rb +5 -26
  327. data/lib/hecks/query_specification/common/comparison.rb +35 -143
  328. data/lib/hecks/query_specification/common/dsl.rb +8 -24
  329. data/lib/hecks/query_specification/common/null_policy.rb +20 -70
  330. data/lib/hecks/query_specification/common/options.rb +2 -6
  331. data/lib/hecks/query_specification/common/where_clause.rb +2 -10
  332. data/lib/hecks/query_specification/field_path.rb +37 -77
  333. data/lib/hecks/query_specification/hop_path.rb +12 -76
  334. data/lib/hecks/release/gem_pin.rb +2 -26
  335. data/lib/hecks/release/runner/ci_publisher.rb +93 -0
  336. data/lib/hecks/release/runner/commands.rb +66 -0
  337. data/lib/hecks/release/runner/console.rb +52 -0
  338. data/lib/hecks/release/runner/gem_publisher.rb +43 -0
  339. data/lib/hecks/release/runner/git.rb +48 -0
  340. data/lib/hecks/release/runner/npm_publisher.rb +75 -0
  341. data/lib/hecks/release/runner/preflight.rb +104 -0
  342. data/lib/hecks/release/runner/published.rb +55 -0
  343. data/lib/hecks/release/runner/tagger.rb +80 -0
  344. data/lib/hecks/release/runner.rb +215 -0
  345. data/lib/hecks/rendering.rb +4 -26
  346. data/lib/hecks/router/namespace_installer.rb +12 -36
  347. data/lib/hecks/router.rb +4 -8
  348. data/lib/hecks/runtime/aggregate_lock.rb +8 -33
  349. data/lib/hecks/runtime/boot_gates.rb +3 -18
  350. data/lib/hecks/runtime/caller.rb +19 -59
  351. data/lib/hecks/runtime/capability_graph.rb +5 -21
  352. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
  353. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +23 -160
  354. data/lib/hecks/runtime/command_interpreter.rb +125 -307
  355. data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
  356. data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
  357. data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
  358. data/lib/hecks/runtime/command_rules/emission.rb +8 -21
  359. data/lib/hecks/runtime/command_rules/references.rb +23 -202
  360. data/lib/hecks/runtime/dependency_planning.rb +30 -102
  361. data/lib/hecks/runtime/dispatcher.rb +82 -241
  362. data/lib/hecks/runtime/entity_element.rb +40 -462
  363. data/lib/hecks/runtime/entity_interpreter.rb +65 -220
  364. data/lib/hecks/runtime/errors.rb +32 -115
  365. data/lib/hecks/runtime/event.rb +3 -25
  366. data/lib/hecks/runtime/identity.rb +18 -154
  367. data/lib/hecks/runtime/instance.rb +18 -150
  368. data/lib/hecks/runtime/interpreting.rb +11 -70
  369. data/lib/hecks/runtime/invocation.rb +28 -164
  370. data/lib/hecks/runtime/loader.rb +48 -186
  371. data/lib/hecks/runtime/outbox.rb +38 -261
  372. data/lib/hecks/runtime/policy_interpreter.rb +38 -200
  373. data/lib/hecks/runtime/port_operation_interpreter.rb +26 -87
  374. data/lib/hecks/runtime/query_interpreter.rb +45 -187
  375. data/lib/hecks/runtime/reaction_invocation.rb +36 -138
  376. data/lib/hecks/runtime/read_model_interpreter.rb +139 -208
  377. data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
  378. data/lib/hecks/runtime/reference_hop.rb +8 -81
  379. data/lib/hecks/runtime/refusal_wording.rb +8 -89
  380. data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
  381. data/lib/hecks/runtime/registry/verification.rb +52 -225
  382. data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
  383. data/lib/hecks/runtime/registry.rb +103 -244
  384. data/lib/hecks/runtime/remote_dispatcher.rb +37 -152
  385. data/lib/hecks/runtime/routing.rb +4 -15
  386. data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
  387. data/lib/hecks/runtime/saga_interpreter.rb +79 -263
  388. data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
  389. data/lib/hecks/runtime/tenant_check.rb +6 -68
  390. data/lib/hecks/runtime/tenant_scope.rb +3 -28
  391. data/lib/hecks/runtime/value/admission.rb +16 -67
  392. data/lib/hecks/runtime/value/coercion.rb +155 -641
  393. data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
  394. data/lib/hecks/runtime/value.rb +12 -84
  395. data/lib/hecks/runtime.rb +17 -65
  396. data/lib/hecks/storehouse.rb +146 -567
  397. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +8 -32
  398. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
  399. data/lib/hecks/vendoring/git_environment.rb +2 -8
  400. data/lib/hecks/vendoring/git_source.rb +4 -10
  401. data/lib/hecks/vendoring.rb +7 -46
  402. data/lib/hecks/version.rb +3 -14
  403. data/lib/hecks/vocabulary.rb +5 -0
  404. data/lib/hecks.rb +32 -82
  405. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
  406. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
  407. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
  408. metadata +30 -59
  409. data/lib/hecks/bench/cli.rb +0 -74
  410. data/lib/hecks/bench/environment.rb +0 -100
  411. data/lib/hecks/bench/postgres_probe.rb +0 -41
  412. data/lib/hecks/bench/report.rb +0 -116
  413. data/lib/hecks/bench/ruby_runner.rb +0 -151
  414. data/lib/hecks/bench/run.rb +0 -55
  415. data/lib/hecks/bench/rust_runner.rb +0 -165
  416. data/lib/hecks/bench/stats.rb +0 -75
  417. data/lib/hecks/bench/suite.rb +0 -172
  418. data/lib/hecks/bench/workload.rb +0 -137
  419. data/lib/hecks/bench.rb +0 -28
  420. data/lib/hecks/codemod.rb +0 -443
  421. data/lib/hecks/corpus.rb +0 -479
  422. data/lib/hecks/doc/reference.rb +0 -585
  423. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -684
  424. data/lib/hecks/fuzzing/combination_miner.rb +0 -178
  425. data/lib/hecks/fuzzing/concurrent_dispatch.rb +0 -469
  426. data/lib/hecks/fuzzing/coverage_campaign.rb +0 -161
  427. data/lib/hecks/fuzzing/differential.rb +0 -192
  428. data/lib/hecks/fuzzing/domain_generator.rb +0 -871
  429. data/lib/hecks/fuzzing/era_boundary.rb +0 -154
  430. data/lib/hecks/fuzzing/form_census.rb +0 -285
  431. data/lib/hecks/fuzzing/generated_domain_check.rb +0 -171
  432. data/lib/hecks/fuzzing/invalid_value_generator.rb +0 -125
  433. data/lib/hecks/fuzzing/isolated_boot.rb +0 -658
  434. data/lib/hecks/fuzzing/nondeterministic.rb +0 -79
  435. data/lib/hecks/fuzzing/persistence_parity.rb +0 -253
  436. data/lib/hecks/fuzzing/properties/corrections.rb +0 -125
  437. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +0 -688
  438. data/lib/hecks/fuzzing/properties/guards.rb +0 -398
  439. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +0 -399
  440. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +0 -137
  441. data/lib/hecks/fuzzing/properties/outbox.rb +0 -180
  442. data/lib/hecks/fuzzing/properties/querying.rb +0 -260
  443. data/lib/hecks/fuzzing/properties.rb +0 -267
  444. data/lib/hecks/fuzzing/qa_settings.rb +0 -164
  445. data/lib/hecks/fuzzing/replay.rb +0 -930
  446. data/lib/hecks/fuzzing/rotation_priority.rb +0 -114
  447. data/lib/hecks/fuzzing/rust_gap_manifest.rb +0 -139
  448. data/lib/hecks/fuzzing/self_consistency.rb +0 -825
  449. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +0 -532
  450. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +0 -171
  451. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +0 -127
  452. data/lib/hecks/fuzzing/sequence_generator/picker.rb +0 -116
  453. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +0 -138
  454. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +0 -344
  455. data/lib/hecks/fuzzing/sequence_generator.rb +0 -311
  456. data/lib/hecks/fuzzing/shrinker.rb +0 -309
  457. data/lib/hecks/fuzzing/structural_skips.rb +0 -53
  458. data/lib/hecks/fuzzing/sweep_depth.rb +0 -61
  459. data/lib/hecks/fuzzing/target_capabilities.rb +0 -255
  460. data/lib/hecks/fuzzing/value_generator.rb +0 -340
  461. data/lib/hecks/fuzzing.rb +0 -23
  462. data/lib/hecks/grammar/evolve.rb +0 -524
  463. data/lib/hecks/query_ir.rb +0 -482
@@ -0,0 +1,366 @@
1
+ require "json"
2
+ require_relative "../../hecks"
3
+ require_relative "mcp"
4
+
5
+ module Hecks
6
+ module CLI
7
+ # The MCP door onto the storehouse bus: hand-rolled JSON-RPC over stdio, one
8
+ # server per booted domain. All logic and the JSON shape answered with live in
9
+ # `Storehouse`; this is plumbing only. A `role:`/`actor_id:` a caller sends is
10
+ # asserted, not verified, unless Governance is attached.
11
+ #
12
+ # There is no authentication: `domain:` boots real Ruby confined to
13
+ # `Storehouse::BOOT_ROOT`, so this is a code execution surface for anyone who
14
+ # can write to stdin (ADR 0062). Spawning with `HECKS_DOOR_TOOLS=readers` and
15
+ # `HECKS_DOOR_DOMAINS=<dir>[:<dir>...]` narrows a door to read-only tools and
16
+ # named domains (`McpDoorScope`); this limits what one agent can reach and is
17
+ # not itself authentication.
18
+ module McpDoor
19
+ PROTOCOL_VERSION = "2024-11-05".freeze
20
+
21
+ SUMMARY_PROPERTY = {
22
+ type: "string",
23
+ description: "One line: why this call is happening. Required — it is what makes the audit trail legible later."
24
+ }.freeze
25
+
26
+ DOMAIN_PROPERTY = {
27
+ type: "string",
28
+ description: "The domain directory, containing a .hecksagon (e.g. \"examples/banking\"). The `domains` tool " \
29
+ "lists what's available."
30
+ }.freeze
31
+
32
+ ARGS_PROPERTY = {
33
+ type: "object",
34
+ description: "The command/query's declared arguments, nested value-object literals included " \
35
+ "(e.g. {\"reference\":{\"value\":\"BUG#1\"}})."
36
+ }.freeze
37
+
38
+ SOURCE_PROPERTY = {
39
+ type: "string",
40
+ enum: Hecks::Storehouse::SOURCE_TAGS,
41
+ description: "Optional: who is calling, for the audit log `follow` reads back."
42
+ }.freeze
43
+
44
+ ROLE_PROPERTY = {
45
+ type: "string",
46
+ description: "Bind a caller identity for the call's duration, so a role-gated command's authorization is " \
47
+ "checked rather than merely documented. Required by `dispatch` for any command that declares a " \
48
+ "role — omitting it refuses the call rather than running it unchecked. Also required if " \
49
+ "`actor_id` is given."
50
+ }.freeze
51
+
52
+ ACTOR_ID_PROPERTY = {
53
+ type: "string",
54
+ description: "Optional, alongside `role`: WHO holds that role — once the domain attaches Governance, checked " \
55
+ "against a real Governance::RoleAssignment instead of a bare string match. Requires `role`."
56
+ }.freeze
57
+
58
+ TOOLS = [
59
+ {
60
+ name: "dispatch",
61
+ description: "Issue a command against a booted domain — or, with `steps`, several in one call. `command` is " \
62
+ "the short or qualified verb name (the catalog/describe tools show what's available). Set " \
63
+ "`dry_run: true` to check whether it WOULD succeed without committing anything. Answers the " \
64
+ "record's id, its resulting state, and the events it announced — or a structured refusal, " \
65
+ "never a crash.",
66
+ inputSchema: {
67
+ type: "object",
68
+ properties: {
69
+ domain: DOMAIN_PROPERTY,
70
+ command: { type: "string", description: "The command's short or qualified name, e.g. \"open_account\". " \
71
+ "Ignored when `steps` is given." },
72
+ args: ARGS_PROPERTY,
73
+ summary: SUMMARY_PROPERTY,
74
+ source: SOURCE_PROPERTY,
75
+ role: ROLE_PROPERTY,
76
+ actor_id: ACTOR_ID_PROPERTY,
77
+ dry_run: { type: "boolean", description: "Preview only — check the command would succeed, commit nothing. " \
78
+ "Ignored when `steps` is given." },
79
+ steps: {
80
+ type: "array",
81
+ description: "Run several commands in one call, in order. Each item: {command, args}. When given, " \
82
+ "`command`/`args`/`dry_run` above are ignored.",
83
+ items: {
84
+ type: "object",
85
+ properties: { command: { type: "string" }, args: ARGS_PROPERTY },
86
+ required: %w[command]
87
+ }
88
+ }
89
+ },
90
+ required: %w[domain summary]
91
+ }
92
+ },
93
+ {
94
+ name: "query",
95
+ description: "Ask a declared question of a booted domain. `question` is the short or qualified query name. " \
96
+ "Answers the row array the query itself declares.",
97
+ inputSchema: {
98
+ type: "object",
99
+ properties: {
100
+ domain: DOMAIN_PROPERTY,
101
+ question: { type: "string", description: "The query's short or qualified name." },
102
+ args: ARGS_PROPERTY,
103
+ summary: SUMMARY_PROPERTY,
104
+ source: SOURCE_PROPERTY,
105
+ role: ROLE_PROPERTY,
106
+ actor_id: ACTOR_ID_PROPERTY
107
+ },
108
+ required: %w[domain question summary]
109
+ }
110
+ },
111
+ {
112
+ name: "events",
113
+ description: "What actually HAPPENED, with payloads, to one aggregate/record — events THIS DOOR witnessed " \
114
+ "(every real dispatch ever routed through it, sourced from its own audit log — not a full " \
115
+ "domain-wide event-sourcing replay). Distinct from `state` (what's stored now) and `history` " \
116
+ "(append-only operation snapshots, no payload). Narrow with `aggregate` and (requires " \
117
+ "`aggregate`) `id`; `limit` keeps only the most recent.",
118
+ inputSchema: {
119
+ type: "object",
120
+ properties: {
121
+ domain: DOMAIN_PROPERTY,
122
+ aggregate: { type: "string", description: "Narrow to one aggregate, e.g. \"Account\"." },
123
+ id: { type: "string", description: "Narrow to one record's own events. Requires `aggregate`." },
124
+ limit: { type: "integer", description: "Keep only the most recent N events. Omit for all of them." }
125
+ },
126
+ required: %w[domain]
127
+ }
128
+ },
129
+ {
130
+ name: "state",
131
+ description: "Read what an aggregate actually has stored, no verb involved. Pass `id` for one record; " \
132
+ "omit it to list every record the aggregate currently holds.",
133
+ inputSchema: {
134
+ type: "object",
135
+ properties: {
136
+ domain: DOMAIN_PROPERTY,
137
+ aggregate: { type: "string", description: "The aggregate's name, e.g. \"Account\"." },
138
+ id: { type: "string", description: "A specific record's id. Omit to list every record." },
139
+ summary: SUMMARY_PROPERTY
140
+ },
141
+ required: %w[domain aggregate summary]
142
+ }
143
+ },
144
+ {
145
+ name: "catalog",
146
+ description: "Zoom level one: every aggregate a domain declares, and every command/query name each " \
147
+ "answers to. No summary needed — this changes nothing and commits nothing to any log.",
148
+ inputSchema: {
149
+ type: "object",
150
+ properties: { domain: DOMAIN_PROPERTY },
151
+ required: %w[domain]
152
+ }
153
+ },
154
+ {
155
+ name: "describe",
156
+ description: "Zoom level two: one aggregate's full contract — every command's arguments, the states it " \
157
+ "may be issued from, and every way it can refuse. Omit `aggregate` for the whole domain.",
158
+ inputSchema: {
159
+ type: "object",
160
+ properties: {
161
+ domain: DOMAIN_PROPERTY,
162
+ aggregate: { type: "string", description: "Narrow to one aggregate. Omit for the whole domain." }
163
+ },
164
+ required: %w[domain]
165
+ }
166
+ },
167
+ {
168
+ name: "validate",
169
+ description: "Zoom level three: is this domain's wiring sound — every bind names a declared aggregate, " \
170
+ "every adapter satisfies its port, the default adapter is usable. {valid:false, error:…} " \
171
+ "on a real wiring defect, never a bare crash. Set `deep: true` to also model-check the IR " \
172
+ "for dead lifecycle transitions, unreachable saga states, and dispatches to nowhere.",
173
+ inputSchema: {
174
+ type: "object",
175
+ properties: {
176
+ domain: DOMAIN_PROPERTY,
177
+ deep: { type: "boolean", description: "Also run the static model checker (lifecycle/saga/policy IR)." }
178
+ },
179
+ required: %w[domain]
180
+ }
181
+ },
182
+ {
183
+ name: "domains",
184
+ description: "Zoom level zero: every domain directory found under a root (default \"examples\"), so a " \
185
+ "caller who doesn't already know a path can find one.",
186
+ inputSchema: {
187
+ type: "object",
188
+ properties: { under: { type: "string", description: "Root directory to scan. Defaults to \"examples\"." } }
189
+ }
190
+ },
191
+ {
192
+ name: "history",
193
+ description: "The full write history for every aggregate in a domain — every operation an append-only " \
194
+ "adapter ever recorded, not just current state. An aggregate on a non-append-only adapter " \
195
+ "answers an empty history honestly.",
196
+ inputSchema: {
197
+ type: "object",
198
+ properties: { domain: DOMAIN_PROPERTY },
199
+ required: %w[domain]
200
+ }
201
+ },
202
+ {
203
+ name: "behaviors",
204
+ description: "Run a domain's hand-curated `.behaviors` example tests and report pass/fail/error per test. " \
205
+ "`target` is a single `.behaviors` file or a directory to sweep.",
206
+ inputSchema: {
207
+ type: "object",
208
+ properties: { target: { type: "string", description: "A .behaviors file, or a directory to sweep." } },
209
+ required: %w[target]
210
+ }
211
+ },
212
+ {
213
+ name: "follow",
214
+ description: "Tail this door's own audit log for a domain — the last `limit` dispatch/query/state/dry-run " \
215
+ "calls made through it, each carrying its summary, source, and outcome.",
216
+ inputSchema: {
217
+ type: "object",
218
+ properties: {
219
+ domain: DOMAIN_PROPERTY,
220
+ limit: { type: "integer", description: "How many recent entries to return. Defaults to 20." }
221
+ },
222
+ required: %w[domain]
223
+ }
224
+ }
225
+ ].freeze
226
+
227
+ NOTES = [
228
+ "Identity is self-asserted: role:/actor_id: are claims made by the caller and are not verified.",
229
+ "state, events, history, follow, describe, catalog and query run with no role check at all.",
230
+ "domain: boots real Ruby under #{Storehouse::BOOT_ROOT}; anyone who can write to stdin can run it."
231
+ ].freeze
232
+
233
+ module_function
234
+
235
+ # Read once, so a bad setting refuses to start the door before anything is
236
+ # written to stdout.
237
+ def scope
238
+ @scope ||= McpDoorScope.start!(server: Mcp::SERVER)
239
+ end
240
+
241
+ # The standing notes, then what this door's scope says about `domain:`.
242
+ def warning_notes
243
+ NOTES[0, 2] + (scope.restricted? ? scope.notes : [NOTES[2]])
244
+ end
245
+
246
+ def serve
247
+ McpStdioGuard.warn!(server: Mcp::SERVER, notes: warning_notes)
248
+ $stdout.sync = true
249
+
250
+ $stdin.each_line do |line|
251
+ line = line.strip
252
+ next if line.empty?
253
+
254
+ begin
255
+ request = JSON.parse(line)
256
+ rescue JSON::ParserError => e
257
+ send_error(nil, -32_700, "Parse error: #{e.message}")
258
+ next
259
+ end
260
+
261
+ begin
262
+ handle(request)
263
+ rescue StandardError => e
264
+ send_error(request["id"], -32_603, "Internal error: #{e.class}: #{e.message}")
265
+ end
266
+ end
267
+ end
268
+
269
+ # `domain:` is confined to `Storehouse::BOOT_ROOT`, and further to a reader
270
+ # door's `HECKS_DOOR_DOMAINS`, before anything boots.
271
+ def boot(domain)
272
+ Hecks.boot(Storehouse.confine!(scope.admit_domain!(domain), "domain"), install_facade: false)
273
+ end
274
+
275
+ # One `when` per tool, so the shared `rescue` catches every branch's escape
276
+ # once; a domain refusal is already `{ok: false, ...}` from `Storehouse`.
277
+ def call_tool(name, args) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
278
+ case name
279
+ when "dispatch"
280
+ runtime = boot(args["domain"])
281
+ if args["steps"]
282
+ Storehouse.dispatch_batch(runtime: runtime, steps: args["steps"], summary: args["summary"],
283
+ source: args["source"], role: args["role"], actor_id: args["actor_id"])
284
+ else
285
+ Storehouse.dispatch(runtime: runtime, command: args["command"], summary: args["summary"],
286
+ args: args["args"] || {}, source: args["source"],
287
+ dry_run: args["dry_run"] == true, role: args["role"], actor_id: args["actor_id"])
288
+ end
289
+ when "query"
290
+ Storehouse.query(runtime: boot(args["domain"]), question: args["question"],
291
+ summary: args["summary"], args: args["args"] || {}, source: args["source"],
292
+ role: args["role"], actor_id: args["actor_id"])
293
+ when "events"
294
+ Storehouse.events(runtime: boot(args["domain"]), aggregate: args["aggregate"], id: args["id"],
295
+ limit: args["limit"])
296
+ when "state"
297
+ Storehouse.state(runtime: boot(args["domain"]), aggregate: args["aggregate"],
298
+ summary: args["summary"], id: args["id"])
299
+ when "catalog"
300
+ Storehouse.catalog(runtime: boot(args["domain"]))
301
+ when "describe"
302
+ Storehouse.describe(runtime: boot(args["domain"]), aggregate: args["aggregate"])
303
+ when "validate"
304
+ Storehouse.validate(domain: scope.admit_domain!(args["domain"]), deep: args["deep"] == true)
305
+ when "domains"
306
+ Storehouse.domains(under: args["under"] || "examples")
307
+ when "history"
308
+ Storehouse.history(runtime: boot(args["domain"]))
309
+ when "behaviors"
310
+ Storehouse.behaviors(target: args["target"])
311
+ when "follow"
312
+ Storehouse.follow(runtime: boot(args["domain"]), limit: args["limit"] || 20)
313
+ else
314
+ { ok: false, error: "no such tool: #{name.inspect} — known: #{TOOLS.map { |t| t[:name] }.join(', ')}" }
315
+ end
316
+ rescue StandardError => e
317
+ # A defect, not a refusal: reaching here means a bad domain path or an
318
+ # argument shape `Facade::JsonDoor` could not symbolize.
319
+ { ok: false, error: "#{e.class}: #{e.message}" }
320
+ end
321
+
322
+ def answer_tool(name, args)
323
+ return scope.refusal(name) unless scope.permits_tool?(name)
324
+
325
+ call_tool(name, args)
326
+ end
327
+
328
+ def tool_result(payload)
329
+ { content: [{ type: "text", text: JSON.pretty_generate(payload) }], isError: payload[:ok] == false }
330
+ end
331
+
332
+ def send_response(id, result)
333
+ puts JSON.generate({ jsonrpc: "2.0", id: id, result: result })
334
+ end
335
+
336
+ def send_error(id, code, message)
337
+ puts JSON.generate({ jsonrpc: "2.0", id: id, error: { code: code, message: message } })
338
+ end
339
+
340
+ def handle(request)
341
+ id = request["id"]
342
+ method = request["method"]
343
+ params = request["params"] || {}
344
+
345
+ case method
346
+ when "initialize"
347
+ send_response(id, {
348
+ protocolVersion: PROTOCOL_VERSION,
349
+ capabilities: { tools: {} },
350
+ serverInfo: { name: Mcp::SERVER, version: "1.1.0" }
351
+ })
352
+ when "notifications/initialized"
353
+ nil # a notification — no id, no response
354
+ when "tools/list"
355
+ send_response(id, { tools: TOOLS.select { |tool| scope.permits_tool?(tool[:name]) } })
356
+ when "tools/call"
357
+ send_response(id, tool_result(answer_tool(params["name"], params["arguments"] || {})))
358
+ when "ping"
359
+ send_response(id, {})
360
+ else
361
+ send_error(id, -32_601, "Method not found: #{method}") unless id.nil?
362
+ end
363
+ end
364
+ end
365
+ end
366
+ end
@@ -0,0 +1,203 @@
1
+ require_relative "../../hecks"
2
+ require_relative "../bluebook/model_check"
3
+
4
+ module Hecks
5
+ module CLI
6
+ # The command behind `bin/model_check` and `hecks model_check`: static analysis
7
+ # over the IR, printed per domain, exiting non-zero when any error-severity
8
+ # finding is left — unreachable lifecycle states, dead transitions, unreached
9
+ # saga states, dispatches to a nonexistent command, handlers for an event
10
+ # nothing emits. The runtime checks none of these; a dead transition never
11
+ # fires, in silence.
12
+ #
13
+ # Given domain paths it checks just those, cross-domain; given none it sweeps
14
+ # the repository's corpus and the language's own chapters, which needs `root:`.
15
+ module ModelCheck
16
+ # The directory the framework's own `.port` and `.adapter` files live under.
17
+ HECKS_DIR = File.expand_path("..", __dir__)
18
+
19
+ # How one run is configured: `strict` and `profile` from the command line,
20
+ # `rust_dir` from the root (nil without one).
21
+ Options = Struct.new(:strict, :profile, :rust_dir)
22
+
23
+ module_function
24
+
25
+ # Runs the model check `argv` describes and exits with its verdict.
26
+ #
27
+ # @param argv [Array<String>] `--strict`, `--profile NAME` and domain paths
28
+ # @param program [String] the name the usage message calls this command by
29
+ # @param root [String, nil] the repository root, for the corpus sweep and the
30
+ # Rust-target check; nil when there is no checkout
31
+ # @return [void]
32
+ # @raise [SystemExit] always: 0 when clean, 1 with findings or a usage error,
33
+ # 2 for an unknown profile
34
+ def call(argv, program:, root: nil)
35
+ argv = argv.dup
36
+ strict = !argv.delete("--strict").nil?
37
+ profile = take_profile(argv)
38
+ unless profile.nil? || Bluebook::ModelCheck::PROFILES.include?(profile)
39
+ warn "unknown profile #{profile.inspect} (known: #{Bluebook::ModelCheck::PROFILES.join(', ')})"
40
+ exit 2
41
+ end
42
+
43
+ options = Options.new(strict, profile, root && File.join(root, "rust"))
44
+ ok = if argv.any? then check_domains(argv, options)
45
+ elsif root then check_corpus(root, options)
46
+ else abort "usage: #{program} [--strict] [--profile client] <domain> [<domain> …]"
47
+ end
48
+
49
+ puts
50
+ puts ok ? "No dead states, no unreachable protocol steps." : "THE MODEL HAS FINDINGS."
51
+ exit(ok ? 0 : 1)
52
+ end
53
+
54
+ def take_profile(argv)
55
+ inline = argv.find { |arg| arg.start_with?("--profile=") }
56
+ index = argv.index("--profile")
57
+ return unless inline || index
58
+
59
+ name = inline ? inline.delete_prefix("--profile=") : argv[index + 1].to_s
60
+ argv.delete(inline) if inline
61
+ argv.slice!(index, 2) if index
62
+ name.to_sym
63
+ end
64
+
65
+ # Examines every named domain against one shared set of known domains and
66
+ # emitted events, so a cross-domain reaction between two of them is checked.
67
+ def check_domains(domain_args, options)
68
+ booted = domain_args.map do |domain_arg|
69
+ [File.basename(domain_arg.chomp("/")), boot(bluebook_dir(domain_arg.chomp("/")) || domain_arg)]
70
+ end
71
+ examine_all(booted, options)
72
+ end
73
+
74
+ # Two passes over the same boots, because a single boot's own registry cannot
75
+ # answer whether a target domain exists anywhere in the corpus.
76
+ def check_corpus(root, options)
77
+ require_relative "../corpus"
78
+ targets = Corpus.model_check_members(root: root).map { |member| [member.stem, Corpus.source_of(member)] }
79
+ booted = targets.map { |name, source| [name, boot(source)] }
80
+ ok = examine_all(booted, options)
81
+ examine_language(options) && ok
82
+ end
83
+
84
+ # `root:` is the source's parent, so a member declaring
85
+ # `uses_embryonaut_bluebook` can vendor from it.
86
+ def boot(source)
87
+ root = File.directory?(source) ? File.dirname(source) : nil
88
+ registry = Runtime::Registry.new(root: root)
89
+ Hecks.with_registry(registry) { load_source(source) }
90
+ registry
91
+ end
92
+
93
+ # @api private
94
+ def load_source(source)
95
+ Kernel.load(File.join(HECKS_DIR, "ports/persistence.port"))
96
+ Kernel.load(File.join(HECKS_DIR, "ports/extraction.port"))
97
+ Kernel.load(File.join(HECKS_DIR, "adapters/driven/memory.adapter"))
98
+ Kernel.load(File.join(HECKS_DIR, "adapters/driven/prism.adapter"))
99
+ folder = Adapters::Folder.new
100
+ if File.directory?(source)
101
+ folder.load_bluebooks(source)
102
+ else
103
+ folder.load_bluebooks(File.dirname(source), [File.basename(source)])
104
+ end
105
+
106
+ # A port attaches to its aggregate from the hecksagon, not the bluebook, so a
107
+ # deaf-policy check that skipped it would miss a policy reacting to an event
108
+ # nothing emits. Recording a bind builds IR only; no adapter resolves here.
109
+ hecksagons = if File.directory?(source)
110
+ Dir.glob(File.join(source, "*.hecksagon"))
111
+ else
112
+ [source.sub(/\.bluebook\z/, ".hecksagon")]
113
+ end
114
+ hecksagons.each { |hecksagon| Kernel.load(hecksagon) if File.exist?(hecksagon) }
115
+ end
116
+
117
+ # @api private
118
+ def examine_all(booted, options)
119
+ known_domains = booted.flat_map { |_, registry| registry.bluebooks.keys + registry.hecksagons.keys }.to_set
120
+ global_emitted_events = booted.flat_map do |_, registry|
121
+ registry.bluebooks.values.flat_map { |chapter| Bluebook::ModelCheck.emitted_events(chapter) }
122
+ end.to_set
123
+
124
+ booted.reduce(true) do |all_ok, (name, registry)|
125
+ domain_passes?(name, registry, options,
126
+ known_domains: known_domains, global_emitted_events: global_emitted_events) && all_ok
127
+ end
128
+ end
129
+
130
+ def domain_passes?(name, registry, options, known_domains:, global_emitted_events:)
131
+ puts "── #{name}"
132
+
133
+ rust_target = rust_target?(name, options.rust_dir)
134
+ chapters = registry.bluebooks.values
135
+ findings = chapters.flat_map do |chapter|
136
+ Bluebook::ModelCheck.call(chapter, hecksagon: registry.hecksagon(chapter.name), known_domains: known_domains,
137
+ global_emitted_events: global_emitted_events,
138
+ rust_target: rust_target,
139
+ strict: options.strict, profile: options.profile)
140
+ end
141
+
142
+ allowed = Bluebook::ModelCheck::ALLOWED_FINDINGS.fetch(name, [])
143
+ matched, findings = findings.partition { |f| allowed.include?([f.kind, f.subject]) }
144
+ matched.each { |f| puts " ALLOWLISTED #{f}" }
145
+
146
+ stale = allowed - matched.map { |f| [f.kind, f.subject] }
147
+ unless stale.empty?
148
+ puts " STALE ALLOWLIST ENTRIES (no longer found — remove them): #{stale.inspect}"
149
+ return false
150
+ end
151
+
152
+ passes?(findings, " clean — #{chapters.size} chapter(s), no dead states, no unreachable protocol steps")
153
+ end
154
+
155
+ # Checked through `MetaValidator.grammar_registry`, the judged graph its
156
+ # conformance specs use, because any one grammar file booted alone is a
157
+ # fraction of the language.
158
+ def examine_language(options)
159
+ ok = true
160
+ ["Bluebook", "World"].each do |name|
161
+ chapter = Bluebook::MetaValidator.grammar_registry.bluebook(name)
162
+ next unless chapter
163
+
164
+ puts "── #{name} (the language itself)"
165
+ findings = Bluebook::ModelCheck.call(chapter, strict: options.strict, profile: options.profile)
166
+ ok = passes?(findings, " clean — no dead states, no unreachable protocol steps") && ok
167
+ end
168
+ ok
169
+ end
170
+
171
+ # @api private
172
+ def passes?(findings, clean_line)
173
+ if findings.empty?
174
+ puts clean_line
175
+ return true
176
+ end
177
+
178
+ errors, warnings = findings.partition { |f| f.severity == :error }
179
+ puts " #{errors.size} error(s), #{warnings.size} warning(s)"
180
+ findings.each { |f| puts " #{f}" }
181
+ errors.empty?
182
+ end
183
+
184
+ # The repository-only fuzzing tooling is loaded lazily, only when there is a
185
+ # `rust/` to read.
186
+ def rust_target?(name, rust_dir)
187
+ return false unless rust_dir
188
+
189
+ require_relative "../fuzzing/target_capabilities"
190
+ Fuzzing::TargetCapabilities.rust_feature?(name, rust_dir)
191
+ end
192
+
193
+ # The same rule `Corpus.bluebook_dir` applies.
194
+ def bluebook_dir(domain_path)
195
+ [File.join(domain_path, "bluebook"), domain_path].each do |dir|
196
+ files = Dir[File.join(dir, "*.bluebook")]
197
+ return File.dirname(files.first) unless files.empty?
198
+ end
199
+ nil
200
+ end
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,90 @@
1
+ require "fileutils"
2
+ require_relative "../../hecks"
3
+
4
+ module Hecks
5
+ module CLI
6
+ # The command behind `bin/project_cli` and `hecks project_cli`: writes a
7
+ # command-line launcher beside each domain, named after its bluebook, so
8
+ # `Hecks.bluebook "QualityControl"` in `qa/` becomes `qa/quality_control`.
9
+ #
10
+ # Each launcher is a dozen lines pinning one directory and handing over to
11
+ # `Facade::CliRunner`; the surface itself is projected at the moment it runs,
12
+ # so editing a chapter changes behaviour without regenerating anything.
13
+ module ProjectCli
14
+ # Build output and other trees under a root that hold no domain a caller means.
15
+ IGNORED = %r{\A(rust|deploy|tmp|coverage)/}
16
+
17
+ module_function
18
+
19
+ # `remove_stale_bin` deletes `root/bin/<name>`, where a launcher for the same
20
+ # chapter would otherwise linger as a second front door.
21
+ def call(argv, program:, root:, remove_stale_bin: true)
22
+ wanted = argv.empty? ? domains(root) : argv.map { |path| path.delete_prefix("#{root}/").chomp("/") }
23
+
24
+ wanted.each do |path|
25
+ name = bluebook_name(root, path) or next
26
+ snake = Naming.snake(name)
27
+ file = File.join(root, path, snake)
28
+
29
+ File.write(file, launcher(path, name, program))
30
+ FileUtils.chmod("+x", file)
31
+ FileUtils.rm_f(File.join(root, "bin", snake)) if remove_stale_bin
32
+
33
+ puts " #{path}/#{snake} -> #{name}"
34
+ end
35
+ end
36
+
37
+ def domains(root)
38
+ folder = Adapters::Folder.new
39
+ Dir.glob(File.join(root, "**/*.hecksagon"))
40
+ .map { |path| folder.domain_root(File.dirname(path)) }
41
+ .compact
42
+ .map { |path| path.delete_prefix("#{root}/") }
43
+ .grep_v(IGNORED)
44
+ .uniq.sort
45
+ end
46
+
47
+ # Reads the name from `Hecks.bluebook "…"` rather than the directory, so a
48
+ # domain using `formerly_known_as` gets a launcher under its current name.
49
+ def bluebook_name(root, path)
50
+ Hecks.boot(File.join(root, path), install_facade: false).registry.bluebooks.keys.first
51
+ rescue StandardError => e
52
+ warn " #{path}: cannot boot — #{e.message.lines.first.strip}"
53
+ nil
54
+ end
55
+
56
+ def launcher(path, name, program)
57
+ snake = Naming.snake(name)
58
+ up = "../" * path.count("/").succ
59
+
60
+ <<~RUBY
61
+ #!/usr/bin/env ruby
62
+
63
+ # #{name}'s command line — GENERATED by #{program}. Do not edit.
64
+ #
65
+ # A launcher, not a CLI: the verbs, their arguments and their refusals are
66
+ # projected from the bluebook beside it every time this runs, so a change
67
+ # to the chapter shows up here without re-minting anything.
68
+ #
69
+ # #{path}/#{snake} every verb, and every question
70
+ # #{path}/#{snake} <verb> --help what it wants, and how it refuses
71
+
72
+ $LOAD_PATH.unshift File.expand_path("#{up}lib", __dir__)
73
+
74
+ require "hecks"
75
+
76
+ runtime = begin
77
+ Hecks.boot(__dir__, install_facade: false)
78
+ rescue StandardError => e
79
+ abort "cannot open #{name}: \#{e.message.lines.first.strip}"
80
+ end
81
+
82
+ text, status = Hecks::Facade::CliRunner.call(
83
+ runtime: runtime, argv: ARGV, program: "#{path}/#{snake}"
84
+ )
85
+ status.zero? ? puts(text) : abort(text)
86
+ RUBY
87
+ end
88
+ end
89
+ end
90
+ end