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,36 @@
1
+ require_relative "../../hecks"
2
+
3
+ module Hecks
4
+ module CLI
5
+ # The command behind `bin/project_diagrams` and `hecks project_diagrams`: boots a
6
+ # domain, finds the chapter it names, and writes its Mermaid diagrams
7
+ # (`Projections::Diagrams`) under `docs/generated/diagrams/<chapter>/` below the
8
+ # root.
9
+ module ProjectDiagrams
10
+ module_function
11
+
12
+ def call(argv, program:, root:)
13
+ usage = "usage: #{program} <domain-path> <ChapterName>"
14
+ domain_path = argv[0] or abort usage
15
+ chapter_name = argv[1] or abort usage
16
+
17
+ runtime = Hecks.boot(File.expand_path(domain_path, Dir.pwd))
18
+ chapter = runtime.registry.bluebook(chapter_name)
19
+ hecksagon = runtime.registry.hecksagon(chapter_name)
20
+ directory = File.expand_path("docs/generated/diagrams/#{Naming.snake(chapter_name)}", root)
21
+
22
+ written = Projector.write(
23
+ Projector.call(:diagrams, bluebook: chapter, options: { hecksagon: hecksagon }),
24
+ directory, as: :files
25
+ )
26
+
27
+ if written.empty?
28
+ puts "#{chapter_name} declares no lifecycle, relationship, or dispatch to project yet"
29
+ else
30
+ puts "wrote #{written.size} diagram(s) to #{directory}:"
31
+ written.each { |path| puts " #{path}" }
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,173 @@
1
+ require "json"
2
+ require_relative "../../hecks"
3
+ # ADR 0033 — a domain wired to PostgresEra needs this plugin loaded
4
+ # explicitly; the era/lineage subsystem does not load with core.
5
+ require_relative "../ports/persistence/plugins/era"
6
+
7
+ module Hecks
8
+ module CLI
9
+ # The command behind `bin/run` and `hecks run`: dispatches one verb from the
10
+ # command line, or executes a JSON step list and reports instances, events,
11
+ # refusals, reactions, sagas and query rows.
12
+ #
13
+ # `run bug.discover reference=BUG#1 severity=high …` needs no JSON; the verb
14
+ # tree and its arguments are projected from the bluebook, so this knows nothing
15
+ # about any domain and its help cannot go stale. The step-list form takes a
16
+ # script path, `-` for stdin, or the JSON itself. The first argument is a
17
+ # domain only when it is a directory; otherwise the nearest enclosing one is
18
+ # used.
19
+ module Run
20
+ module_function
21
+
22
+ # Runs `argv` as a verb invocation or a step list against a domain.
23
+ #
24
+ # @param argv [Array<String>] the arguments: an optional domain directory, then
25
+ # a verb with `name=value` pairs, a script path, `-`, or inline JSON
26
+ # @param program [String] the name usage and help messages call this command by
27
+ # @return [void]
28
+ # @raise [SystemExit] on any refusal, usage error or unmet expectation, and
29
+ # after the command-line form answers
30
+ def call(argv, program:)
31
+ usage = "usage: #{program} [domain] <verb [name=value …] | script.json | - | '{\"steps\":[…]}'>"
32
+ argv = argv.dup
33
+ here = Adapters::Folder.new.domain_root
34
+ domain = argv.first && File.directory?(argv.first) ? argv.shift : here
35
+ abort "no bluebook here — #{Dir.pwd} is not inside a domain. #{usage}" unless domain
36
+
37
+ # The command-line form answers unless the first argument is script-shaped,
38
+ # in which case the step-list form takes over.
39
+ cli_form(domain, argv, program) unless argv.first && script_shaped?(argv.first)
40
+
41
+ script = argv.first or abort usage
42
+ document = parse(read_source(script))
43
+ steps = document["steps"] or abort %(the script has no "steps" — #{usage})
44
+ report = execute(Hecks.boot(domain), steps)
45
+
46
+ # The run is reported before it is judged: an expectation that fails says
47
+ # what was wanted and never what happened, so the evidence goes to stdout
48
+ # first and every unmet expectation is collected rather than aborted on.
49
+ puts JSON.pretty_generate(report)
50
+ unmet = unmet_expectations(document["expectations"] || {}, report)
51
+ abort unmet.join("\n") unless unmet.empty?
52
+ end
53
+
54
+ # No fall-through: reached only when the argument is not script-shaped, so a
55
+ # near miss deserves the runner's "did you mean" rather than the step-list
56
+ # form's "no such script".
57
+ def cli_form(domain, argv, program)
58
+ runtime = Hecks.boot(domain, install_facade: false)
59
+ text, status = Facade::CliRunner.call(runtime: runtime, argv: argv, program: program)
60
+ status.zero? ? puts(text) : abort(text)
61
+ exit 0
62
+ end
63
+
64
+ def script_shaped?(arg) = arg == "-" || arg.match?(/\A\s*\{/) || File.file?(arg)
65
+
66
+ # Decided by looking, because a script beginning with `{` is not a filename
67
+ # anybody meant.
68
+ def read_source(script)
69
+ case script
70
+ when "-" then $stdin.read
71
+ when /\A\s*\{/ then script
72
+ else
73
+ File.exist?(script) ? File.read(script) : abort("no such script: #{script}")
74
+ end
75
+ end
76
+
77
+ # Parsed before anything boots, because booting opens a real store and a typo
78
+ # in the JSON should cost a sentence, not a connection.
79
+ def parse(source)
80
+ JSON.parse(source)
81
+ rescue JSON::ParserError => e
82
+ abort "that is not JSON: #{e.message.lines.first.strip}"
83
+ end
84
+
85
+ def execute(runtime, steps)
86
+ refusals = []
87
+ queries = []
88
+ steps.each { |step| run_step(runtime, step, refusals, queries) }
89
+
90
+ { instances: instances(runtime), events: events(runtime), refusals: refusals,
91
+ reactions: runtime.reactions, sagas: runtime.sagas, queries: queries }
92
+ end
93
+
94
+ # @api private
95
+ def run_step(runtime, step, refusals, queries)
96
+ args = (step["args"] || {}).transform_keys(&:to_sym)
97
+ if (question = step["query"])
98
+ queries << ask(runtime, question, args)
99
+ return
100
+ end
101
+
102
+ verb = step["verb"]
103
+ begin
104
+ runtime.dispatch_flat(verb, args)
105
+ rescue StandardError => e
106
+ refusals << { verb: verb, error: e.message.to_s }
107
+ end
108
+ end
109
+
110
+ # @api private
111
+ def ask(runtime, question, args)
112
+ { query: question, args: args, rows: runtime.query(question, **args) }
113
+ rescue StandardError => e
114
+ { query: question, args: args, error: e.message }
115
+ end
116
+
117
+ # @api private
118
+ def instances(runtime)
119
+ runtime.registry.bluebooks.each_with_object({}) do |(domain_name, bluebook), all|
120
+ bluebook.aggregates.each do |aggregate|
121
+ runtime.registry.repository(domain_name, aggregate).all.each do |record|
122
+ all["#{domain_name}::#{aggregate.name}##{record.id}"] = record.state
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+ # @api private
129
+ def events(runtime)
130
+ runtime.events.map do |event|
131
+ { name: event.name, aggregate: event.aggregate, id: event.id, payload: event.payload }
132
+ end
133
+ end
134
+
135
+ def unmet_expectations(expectations, report)
136
+ missing_events = Array(expectations["event_names"]) - report[:events].map { |event| event[:name] }
137
+ unmet = []
138
+ unmet << "matrix expected events missing: #{missing_events.join(', ')}" unless missing_events.empty?
139
+ unmet.concat(unmet_refusals(Array(expectations["refusals"]), report[:refusals]))
140
+ unmet.concat(unmet_instances(expectations["instances"] || {}, report[:instances]))
141
+ end
142
+
143
+ # @api private
144
+ def unmet_refusals(expected_refusals, refusals)
145
+ expected_refusals.filter_map do |expected|
146
+ verb = expected.fetch("verb")
147
+ matched = refusals.any? do |refusal|
148
+ refusal[:verb] == verb && refusal[:error].include?(expected.fetch("includes"))
149
+ end
150
+ next if matched
151
+
152
+ # A refusal that changed its words is a different story from one that never
153
+ # happened, so the actual errors are printed beside the wanted one.
154
+ said = refusals.select { |refusal| refusal[:verb] == verb }.map { |refusal| refusal[:error] }.uniq
155
+ "matrix expected refusal missing: #{expected}\n " \
156
+ "#{verb} actually refused with: #{said.empty? ? '(nothing — every attempt was accepted)' : said.inspect}"
157
+ end
158
+ end
159
+
160
+ # @api private
161
+ def unmet_instances(expected_instances, instances)
162
+ expected_instances.flat_map do |key, fields|
163
+ actual = instances[key] || {}
164
+ fields.filter_map do |field, value|
165
+ next if actual[field.to_sym] == value
166
+
167
+ "matrix expected #{key}.#{field}=#{value.inspect}, got #{actual[field.to_sym].inspect}"
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,45 @@
1
+ require_relative "../../hecks"
2
+ require_relative "../bluebook/smoke_test"
3
+
4
+ module Hecks
5
+ module CLI
6
+ # The command behind `bin/smoke_test` and `hecks smoke_test`: boots a real domain
7
+ # and dispatches one synthesized call per declared command and report
8
+ # (`Bluebook::SmokeTest`), printing every failure rather than the first.
9
+ #
10
+ # Needs a real `.hecksagon`; a failure may be a domain bug or a value the
11
+ # synthesizer's naive choices cannot satisfy — smoke-testing proves nothing
12
+ # crashes, not that an answer is right.
13
+ module SmokeTest
14
+ module_function
15
+
16
+ def call(argv, root:)
17
+ dir_arg = argv.first
18
+ targets = dir_arg ? [dir_arg] : Dir.glob("#{root}/examples/*/").map { |d| d.chomp("/") }.select { |d| wired?(d) }
19
+ abort "no wired example domains found (none of examples/* has a .hecksagon)" if targets.empty?
20
+
21
+ ok = targets.reduce(true) { |all_ok, dir| clean?(dir) && all_ok }
22
+
23
+ puts
24
+ puts ok ? "Every declared command and report dispatched cleanly." : "SMOKE TEST FOUND PROBLEMS."
25
+ exit(ok ? 0 : 1)
26
+ end
27
+
28
+ def wired?(dir) = !Dir.glob(File.join(dir, "**/*.hecksagon")).empty?
29
+
30
+ # @api private
31
+ def clean?(dir)
32
+ puts "── #{File.basename(dir)}"
33
+ failures = Bluebook::SmokeTest.call(dir)
34
+ if failures.empty?
35
+ puts " clean — every declared command and report dispatched cleanly"
36
+ return true
37
+ end
38
+
39
+ puts " #{failures.size} failure(s)"
40
+ failures.each { |f| puts " #{f}" }
41
+ false
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,53 @@
1
+ require "json"
2
+ require_relative "../../hecks"
3
+
4
+ module Hecks
5
+ module CLI
6
+ # The command behind `bin/stores` and `hecks stores`: every aggregate's current
7
+ # records as JSON (the head, not the journal — see `bin/history` for that).
8
+ module Stores
9
+ module_function
10
+
11
+ # Boots the domain `argv` names and prints its stores as one JSON document.
12
+ #
13
+ # @param argv [Array<String>] the domain directory, first
14
+ # @param program [String] the name the error message calls this command by
15
+ # @return [void]
16
+ # @raise [IndexError] when `argv` is empty
17
+ # @raise [SystemExit] when the domain directory does not exist
18
+ def call(argv, program:)
19
+ domain = argv.fetch(0)
20
+ unless Dir.exist?(domain)
21
+ warn "#{program}: no such domain #{domain.inspect}"
22
+ exit 1
23
+ end
24
+
25
+ registry = Hecks.boot(domain).registry
26
+ stores = registry.bluebooks.each_with_object({}) do |(name, bluebook), all|
27
+ bluebook.aggregates.each do |aggregate|
28
+ all[aggregate.storage_name] = data_for(registry.repository(name, aggregate))
29
+ end
30
+ end
31
+
32
+ puts JSON.generate(stores)
33
+ end
34
+
35
+ # Reads one aggregate's current records as plain JSON-ready hashes, ordered by id.
36
+ #
37
+ # @param repository [Ports::Persistence::AppendOnly] the aggregate's repository
38
+ # @return [Array<Hash{String => Object}>] one hash per stored `Runtime::Instance`,
39
+ # sorted by id; `[]` when the aggregate has no records
40
+ def records(repository)
41
+ repository.all.sort_by(&:id).map { |instance| JSON.parse(JSON.generate(instance.to_h)) }
42
+ end
43
+
44
+ # Shapes one aggregate's dump for the top-level stores document.
45
+ #
46
+ # @param repository [Ports::Persistence::AppendOnly] the aggregate's repository
47
+ # @return [Hash{String => Object}] `"authoritative"` mapped to `records(repository)`
48
+ def data_for(repository)
49
+ { "authoritative" => records(repository) }
50
+ end
51
+ end
52
+ end
53
+ end
data/lib/hecks/cli.rb ADDED
@@ -0,0 +1,160 @@
1
+ module Hecks
2
+ # The `hecks` command an installed gem puts on the path: one router over the
3
+ # domain-operator tools, each also runnable from a checkout as `bin/<name>`.
4
+ #
5
+ # Every subcommand's logic lives under `cli/`, with `bin/<name>` a thin wrapper
6
+ # over the same entry point, so the two can't drift apart.
7
+ #
8
+ # See `docs/decisions/0066-the-gem-ships-a-hecks-executable-and-dev-tooling-stays-in-the-repo.md`.
9
+ module CLI
10
+ # One subcommand's help: a one-line summary and its usage line.
11
+ Command = Struct.new(:summary, :usage)
12
+
13
+ # Every subcommand, in the order the help lists them.
14
+ COMMANDS = {
15
+ "run" => Command.new(
16
+ "Dispatch a verb, or run a JSON step list, against a domain.",
17
+ "hecks run [domain] <verb [name=value …] | script.json | - | '{\"steps\":[…]}'>"
18
+ ),
19
+ "docs" => Command.new(
20
+ "Print a domain's usage document, projected from its bluebook.",
21
+ "hecks docs [domain-path] [aggregate]"
22
+ ),
23
+ "narrate" => Command.new(
24
+ "Print a domain read back in English, projected from its bluebook.",
25
+ "hecks narrate [domain-path] [aggregate]"
26
+ ),
27
+ "ir" => Command.new(
28
+ "Print a booted domain's IR as JSON.",
29
+ "hecks ir <domain> [--translations] | hecks ir --meta"
30
+ ),
31
+ "stores" => Command.new(
32
+ "Print every aggregate's current records as JSON.",
33
+ "hecks stores <domain>"
34
+ ),
35
+ "model_check" => Command.new(
36
+ "Statically check a domain's IR for dead states and unreachable steps.",
37
+ "hecks model_check [--strict] [--profile client] <domain> [<domain> …]"
38
+ ),
39
+ "smoke_test" => Command.new(
40
+ "Boot a domain and dispatch every declared command and report once.",
41
+ "hecks smoke_test [domain]"
42
+ ),
43
+ "project_diagrams" => Command.new(
44
+ "Write a domain's Mermaid diagrams under ./docs/generated/diagrams/.",
45
+ "hecks project_diagrams <domain-path> <ChapterName>"
46
+ ),
47
+ "project_cli" => Command.new(
48
+ "Write a command-line launcher beside each domain, named after its bluebook.",
49
+ "hecks project_cli [domain-path …]"
50
+ ),
51
+ "mcp" => Command.new(
52
+ "Serve the MCP door over stdio (no authentication; stdio only).",
53
+ "hecks mcp [--stdio]"
54
+ )
55
+ }.freeze
56
+
57
+ HELP_FLAGS = ["--help", "-h"].freeze
58
+
59
+ # Printed above the usage line by `overview`, on every path that reaches it
60
+ # (a bare `hecks`, `hecks --help`, and an unknown subcommand alike).
61
+ HERO = <<~BANNER.freeze
62
+ # # ##### #### # # ####
63
+ # # # # # # #
64
+ ##### #### # ## ###
65
+ # # # # # # #
66
+ # # ##### #### # # ####
67
+
68
+ It's all about the specs
69
+ BANNER
70
+
71
+ # Exit status for a missing or unknown subcommand.
72
+ USAGE_STATUS = 2
73
+
74
+ module_function
75
+
76
+ # Routes `argv` to its subcommand and runs it.
77
+ #
78
+ # A subcommand followed only by `--help`/`-h` prints its usage without
79
+ # running it; other arguments still reach the verb itself.
80
+ #
81
+ # @param argv [Array<String>] the command line, subcommand first
82
+ # @param out [#puts] where help goes
83
+ # @param err [#puts] where usage errors go
84
+ # @return [Integer] the process exit status for a subcommand that returns; a
85
+ # subcommand that fails exits the process itself
86
+ def start(argv, out: $stdout, err: $stderr)
87
+ name, *rest = argv
88
+ if name.nil? || !COMMANDS.key?(name)
89
+ asked = HELP_FLAGS.include?(name) || name == "help"
90
+ err.puts "hecks: unknown command #{name.inspect}" unless asked || name.nil?
91
+ overview(asked ? out : err)
92
+ return asked ? 0 : USAGE_STATUS
93
+ end
94
+
95
+ if rest.length == 1 && HELP_FLAGS.include?(rest.first)
96
+ help(COMMANDS.fetch(name), out)
97
+ else
98
+ dispatch(name, rest)
99
+ end
100
+ 0
101
+ end
102
+
103
+ # Runs one known subcommand with the working directory as its root.
104
+ #
105
+ # @param name [String] a key of `COMMANDS`
106
+ # @param argv [Array<String>] the arguments after the subcommand
107
+ # @return [void]
108
+ # @raise [SystemExit] when the subcommand exits the process itself
109
+ def dispatch(name, argv)
110
+ program = "hecks #{name}"
111
+ case name
112
+ when "run"
113
+ require_relative "cli/run"
114
+ Run.call(argv, program: program)
115
+ when "docs", "narrate"
116
+ require_relative "cli/document"
117
+ Document.call(argv, projection: name.to_sym, program: program, root: Dir.pwd)
118
+ when "ir"
119
+ require_relative "cli/ir"
120
+ Ir.call(argv, program: program)
121
+ when "stores"
122
+ require_relative "cli/stores"
123
+ Stores.call(argv, program: program)
124
+ when "model_check"
125
+ require_relative "cli/model_check"
126
+ ModelCheck.call(argv, program: program)
127
+ when "smoke_test"
128
+ require_relative "cli/smoke_test"
129
+ SmokeTest.call(argv, root: Dir.pwd)
130
+ when "project_diagrams"
131
+ require_relative "cli/project_diagrams"
132
+ ProjectDiagrams.call(argv, program: program, root: Dir.pwd)
133
+ when "project_cli"
134
+ require_relative "cli/project_cli"
135
+ ProjectCli.call(argv, program: program, root: Dir.pwd, remove_stale_bin: false)
136
+ when "mcp"
137
+ require_relative "cli/mcp"
138
+ Mcp.call(argv)
139
+ end
140
+ end
141
+
142
+ # @api private
143
+ def overview(io)
144
+ io.puts HERO
145
+ io.puts "usage: hecks <command> [arguments]"
146
+ io.puts ""
147
+ width = COMMANDS.keys.map(&:length).max
148
+ COMMANDS.each { |name, command| io.puts " #{name.ljust(width)} #{command.summary}" }
149
+ io.puts ""
150
+ io.puts "Run `hecks <command> --help` for one command's usage."
151
+ end
152
+
153
+ # @api private
154
+ def help(command, io)
155
+ io.puts "usage: #{command.usage}"
156
+ io.puts ""
157
+ io.puts command.summary
158
+ end
159
+ end
160
+ end
@@ -1,29 +1,12 @@
1
1
  module Hecks
2
- # The invisible field a built construct carries.
2
+ # The invisible identity a built construct carries: an owner chain under `hecks_`-prefixed
3
+ # fields, not an attribute and not a key in `to_h`.
3
4
  #
4
- # ## Identity
5
+ # The fully-qualified name walks owners and matches the id `MetaValidator::Judge#identify` mints.
5
6
  #
6
- # A construct is a record with an owner chain — the chapter (Bluebook)
7
- # owns its aggregates, an aggregate owns everything declared on it — and the
8
- # bluebook identity is carried in its own field, under a `hecks_` prefix that
9
- # no domain attribute can collide with. Invisible means exactly that: not an
10
- # attribute, not a key in `to_h`, not a reader on instances. Framework
11
- # metadata about the construct, not part of the domain it describes.
12
- #
13
- # The identity is computed by walking owners rather than stamped, so nothing
14
- # has to be re-stamped when a chapter is assembled after its aggregates:
15
- #
16
- # Pizzas the chapter — no owner
7
+ # Pizzas the chapter, no owner
17
8
  # Pizzas::Pizza an aggregate joins its chapter with ::
18
9
  # Pizzas::Pizza.Price everything else joins its owner with .
19
- #
20
- # That spelling is not invented here. It is the id `MetaValidator::Judge`
21
- # already mints in `#identify`, so a construct and the meta-domain's
22
- # record of that construct carry the same identity, and there is no
23
- # translation table between them to be quietly wrong in.
24
- #
25
- # ## Usage
26
- #
27
10
  # price = Class.new(ValueObject) # a declaration holder
28
11
  # price.hecks_name = "Price"
29
12
  # price.hecks_owner = pizza_ir # the Aggregate that declares it
@@ -32,15 +15,12 @@ module Hecks
32
15
  # A construct asked for an identity it cannot compute.
33
16
  class Unowned < StandardError; end
34
17
 
35
- # What declares this one — the chapter above an aggregate, the aggregate
36
- # above a value object. nil for a chapter, which is the top.
18
+ # What declares this construct; nil for a chapter, which is the top.
37
19
  attr_accessor :hecks_owner
38
20
 
39
21
  attr_writer :hecks_name
40
22
 
41
- # A chapter is the only construct that legitimately has no owner. Everything
42
- # else is declared in something, so a missing owner is an unstamped construct
43
- # rather than a top — see hecks_fqn.
23
+ # Marks a chapter, the only construct that legitimately has no owner.
44
24
  attr_writer :hecks_root
45
25
 
46
26
  # Whether this construct is the top of its own owner chain.
@@ -53,18 +33,12 @@ module Hecks
53
33
  # @return [String] the construct's own name, without any owner prefix
54
34
  def hecks_name = @hecks_name
55
35
 
56
- # How this construct joins its owner. An aggregate is a member of its
57
- # chapter's namespace (`::`) ; everything else is declared on its owner
58
- # (`.`). Overridden by Aggregate, defaulted here for every other construct.
36
+ # How this construct joins its owner: `::` for an aggregate, `.` for everything else.
59
37
  #
60
38
  # @return [String] `"."`, the separator this construct uses in `hecks_fqn`
61
39
  def hecks_separator = "."
62
40
 
63
- # Refuses rather than guesses. A construct with no owner and no claim to be a
64
- # chapter has simply not been stamped yet — entity commands are in that state
65
- # while entities are still IR objects — and answering the bare name would be a
66
- # plausible half-truth that no test would notice. That shape of falsehood is
67
- # what this repo keeps finding, so it goes red instead.
41
+ # Refuses rather than answering a bare name for an unstamped construct.
68
42
  #
69
43
  # @return [String] the fully-qualified name, joining every owner from the
70
44
  # chapter down to this construct