hecks 2.8.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 (404) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +4 -6
  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 +17 -98
  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 +5 -48
  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 +31 -141
  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 +14 -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 +25 -31
  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 +16 -69
  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 -194
  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 -111
  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 +53 -267
  109. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +43 -305
  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 +6 -21
  114. data/lib/hecks/bluebook/model_check.rb +100 -597
  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 +4 -23
  128. data/lib/hecks/cli/document.rb +3 -21
  129. data/lib/hecks/cli/ir.rb +3 -5
  130. data/lib/hecks/cli/mcp.rb +2 -3
  131. data/lib/hecks/cli/mcp_door.rb +19 -114
  132. data/lib/hecks/cli/model_check.rb +18 -85
  133. data/lib/hecks/cli/project_cli.rb +7 -40
  134. data/lib/hecks/cli/project_diagrams.rb +0 -7
  135. data/lib/hecks/cli/run.rb +12 -58
  136. data/lib/hecks/cli/smoke_test.rb +2 -16
  137. data/lib/hecks/cli/stores.rb +1 -2
  138. data/lib/hecks/cli.rb +18 -24
  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 +82 -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 +9 -90
  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 +61 -64
  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 +6 -25
  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 +18 -124
  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 +150 -678
  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 +2 -15
  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 +42 -134
  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 +3 -6
  336. data/lib/hecks/release/runner/commands.rb +2 -4
  337. data/lib/hecks/release/runner/gem_publisher.rb +2 -3
  338. data/lib/hecks/release/runner/npm_publisher.rb +6 -20
  339. data/lib/hecks/release/runner/preflight.rb +4 -19
  340. data/lib/hecks/release/runner/tagger.rb +2 -9
  341. data/lib/hecks/release/runner.rb +3 -32
  342. data/lib/hecks/rendering.rb +4 -26
  343. data/lib/hecks/router/namespace_installer.rb +12 -36
  344. data/lib/hecks/router.rb +4 -8
  345. data/lib/hecks/runtime/aggregate_lock.rb +8 -33
  346. data/lib/hecks/runtime/boot_gates.rb +3 -18
  347. data/lib/hecks/runtime/caller.rb +19 -59
  348. data/lib/hecks/runtime/capability_graph.rb +5 -21
  349. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
  350. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +23 -160
  351. data/lib/hecks/runtime/command_interpreter.rb +124 -306
  352. data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
  353. data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
  354. data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
  355. data/lib/hecks/runtime/command_rules/emission.rb +8 -21
  356. data/lib/hecks/runtime/command_rules/references.rb +23 -202
  357. data/lib/hecks/runtime/dependency_planning.rb +30 -102
  358. data/lib/hecks/runtime/dispatcher.rb +82 -241
  359. data/lib/hecks/runtime/entity_element.rb +40 -462
  360. data/lib/hecks/runtime/entity_interpreter.rb +64 -219
  361. data/lib/hecks/runtime/errors.rb +32 -115
  362. data/lib/hecks/runtime/event.rb +3 -25
  363. data/lib/hecks/runtime/identity.rb +18 -154
  364. data/lib/hecks/runtime/instance.rb +18 -150
  365. data/lib/hecks/runtime/interpreting.rb +11 -70
  366. data/lib/hecks/runtime/invocation.rb +28 -164
  367. data/lib/hecks/runtime/loader.rb +48 -186
  368. data/lib/hecks/runtime/outbox.rb +38 -261
  369. data/lib/hecks/runtime/policy_interpreter.rb +38 -200
  370. data/lib/hecks/runtime/port_operation_interpreter.rb +26 -87
  371. data/lib/hecks/runtime/query_interpreter.rb +45 -187
  372. data/lib/hecks/runtime/reaction_invocation.rb +36 -138
  373. data/lib/hecks/runtime/read_model_interpreter.rb +117 -208
  374. data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
  375. data/lib/hecks/runtime/reference_hop.rb +8 -81
  376. data/lib/hecks/runtime/refusal_wording.rb +8 -89
  377. data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
  378. data/lib/hecks/runtime/registry/verification.rb +52 -225
  379. data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
  380. data/lib/hecks/runtime/registry.rb +99 -244
  381. data/lib/hecks/runtime/remote_dispatcher.rb +37 -152
  382. data/lib/hecks/runtime/routing.rb +4 -15
  383. data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
  384. data/lib/hecks/runtime/saga_interpreter.rb +79 -263
  385. data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
  386. data/lib/hecks/runtime/tenant_check.rb +6 -68
  387. data/lib/hecks/runtime/tenant_scope.rb +3 -28
  388. data/lib/hecks/runtime/value/admission.rb +16 -67
  389. data/lib/hecks/runtime/value/coercion.rb +155 -641
  390. data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
  391. data/lib/hecks/runtime/value.rb +12 -84
  392. data/lib/hecks/runtime.rb +17 -65
  393. data/lib/hecks/storehouse.rb +141 -568
  394. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +8 -32
  395. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
  396. data/lib/hecks/vendoring/git_environment.rb +2 -8
  397. data/lib/hecks/vendoring/git_source.rb +4 -10
  398. data/lib/hecks/vendoring.rb +7 -46
  399. data/lib/hecks/version.rb +3 -14
  400. data/lib/hecks.rb +26 -81
  401. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
  402. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
  403. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
  404. metadata +3 -2
@@ -2,65 +2,25 @@ require_relative "../naming"
2
2
 
3
3
  module Hecks
4
4
  module Projector
5
- # A bluebook, projected as its own command-line surface.
5
+ # Projects a bluebook as its own command-line surface: the verb tree, argument spec
6
+ # and usage text. Nothing executes here; the generic runner (`bin/run`) parses against it.
6
7
  #
7
- # Every verb a domain declares is a subcommand; every argument is an
8
- # option whose type, whose admitted values and whose required-ness are
9
- # already stated in the chapter. A hand-written CLI restates all of it and
10
- # then drifts — and the first thing to drift is the help text, which is the
11
- # only part anybody reads.
12
- #
13
- # ## What is projected
14
- #
15
- # This answers the surface only — the verb tree, the argument spec, the
16
- # usage text — and nothing executes here. One small generic runner
17
- # (`bin/run`) boots a domain, asks for this, parses against it and
18
- # dispatches.
19
- #
20
- # ## Why project rather than generate
21
- #
22
- # The alternative was generating an executable per domain, which is what
23
- # `bin/project_rust` does for a whole runtime and would be the more
24
- # spectacular version of this. It was not taken: a generated program is a
25
- # second copy of the dispatch logic, and it needs regenerating on every
26
- # bluebook edit — a second tax beside the era gate, paid for a file nobody
27
- # reads. Projecting the surface keeps one dispatcher and a help text that
28
- # cannot be stale, because it is computed at the moment it is printed.
29
- #
30
- # ## The typing is the point
31
- #
32
- # A CLI hands everything over as a String. `sequence.value=99` has to
33
- # become the Integer 99 or the runtime refuses it, and the only honest
34
- # place to learn that is the value object's own declared field type. A CLI
35
- # that guessed — "it looks like a number" — would send 99 for a version
36
- # string of "99" and be wrong in a way nobody could see.
8
+ # Argument types come from the declared field types, never from guessing at the
9
+ # string: `sequence.value=99` must become 99, and a version "99" must stay a String.
37
10
  module CliProjector
38
11
  module_function
39
12
 
40
- # Two namespaces, not one — `{ verbs:, questions:, usage: }`.
41
- #
42
- # A chapter may legally declare a command and a query of one name: the
43
- # language namespaces them and `Banking::Account.Open` is both, in the
44
- # corpus, today. A single flat list of subcommands has to pick one, and
45
- # picking silently is how `Ticket.Filed` sat undetected in this
46
- # repository for a day.
13
+ # Projects the verb and question tables and the usage text.
47
14
  #
48
- # So a question is asked with `ask`: `bin/run ask account.open`. It is
49
- # this codebase own word — `Query::AskOption`, "the ask" — it is
50
- # shell-safe where a `?` suffix would be eaten by globbing, and it makes
51
- # the collision impossible rather than detected. It also reads as what it
52
- # is: everything under `ask` changes nothing.
15
+ # Commands and queries are separate namespaces because a chapter may declare both
16
+ # under one name; questions are asked with `ask`.
53
17
  #
54
18
  # @param bluebook [Bluebook::Chapter] the booted domain to project
55
- # @param options [Hash{Symbol => Object}] `:program` (String, defaults to
56
- # `"bin/run"`) is echoed into the usage text; `:verb` (String) and `:ask`
57
- # (Boolean) select one verb's `--help` text instead of the full usage
58
- # @return [Hash{Symbol => Object}] `:verbs` and `:questions` map each projected
59
- # name to its spec hash; `:names` maps `:command`/`:question` to an alias table
60
- # (short and full spelling both keying the full name); `:usage` is the
61
- # pre-rendered help text
62
- # @raise [Bluebook::DSL::Malformed] if two verbs project to the same command-line
63
- # name
19
+ # @param options [Hash{Symbol => Object}] `:program` (default `"bin/run"`) for the usage
20
+ # text; `:verb` and `:ask` select one verb's `--help` text
21
+ # @return [Hash{Symbol => Object}] `:verbs`, `:questions`, `:names` (alias tables) and
22
+ # `:usage` (pre-rendered help text)
23
+ # @raise [Bluebook::DSL::Malformed] if two verbs project to the same command-line name
64
24
  def call(bluebook:, options: {})
65
25
  verbs = {}
66
26
  questions = {}
@@ -78,46 +38,19 @@ module Hecks
78
38
  end
79
39
  end
80
40
 
81
- # A port is a verb too, and leaving it off the map was a real gap
82
- # rather than a tasteful omission. The runtime has always dispatched
83
- # a port operation by exactly the same name as a command — the
84
- # projection simply never listed one, so `run_specs` and `file`
85
- # answered "no such verb" while working perfectly through Ruby.
86
- #
87
- # It matters most for the caller with no other door. An agent that
88
- # may not shell out reaches this domain only through the projected
89
- # CLI, and a port it cannot see is a capability it does not have.
41
+ # Port operations dispatch by the same name as a command, so they are verbs too.
90
42
  aggregate.ports.each do |port|
91
43
  port.operations.each { |o| claim(verbs, name_for(aggregate, o), port_spec(bluebook, aggregate, port, o)) }
92
44
  end
93
45
  end
94
46
 
95
- # A report is a question too, and leaving it off was the same gap the
96
- # ports had: `Dispatcher#query` has always answered `Domain.ReportName`,
97
- # the projection simply never listed one — so the composed reads worked
98
- # from Ruby and did not exist for anybody whose only door is the command
99
- # line.
100
- #
101
- # It matters most for exactly what a report is for. Every other question
102
- # here answers with rows and leaves the arithmetic to the reader; a
103
- # `group_by` report is the one that counts. An agent that cannot reach
104
- # it can list bugs all day and never answer "how are we doing".
105
- #
106
- # **One dot, not two** — a report belongs to the chapter rather than to any
107
- # aggregate (that is what rootless means), so it is addressed
108
- # `QualityControl.BugsByStatus` where a query is
109
- # `QualityControl::Bug.Queue`. `Dispatcher#query` splits on precisely
110
- # that difference.
47
+ # A report belongs to the chapter, not an aggregate, so it is addressed
48
+ # `Chapter.Report` (one dot) where a query is `Chapter::Aggregate.Query`.
111
49
  bluebook.read_models.each do |model|
112
50
  claim(questions, Naming.snake(model.hecks_name), report_spec(bluebook, model))
113
51
  end
114
52
 
115
- # The short spelling, where it cannot be ambiguous. `pizzas
116
- # create_pizza` rather than `pizzas order.create_pizza` — the
117
- # aggregate is worth typing only when two of them declare the same
118
- # verb, and in a one-aggregate domain it never is. Both spellings are
119
- # always accepted; `names` maps every accepted one to its canonical
120
- # key, and the display name is the shortest that is unambiguous.
53
+ # The display name is the shortest unambiguous one; both spellings are accepted.
121
54
  shorten(verbs)
122
55
  shorten(questions)
123
56
 
@@ -126,13 +59,10 @@ module Hecks
126
59
  usage: usage(bluebook, verbs, questions, options) }
127
60
  end
128
61
 
129
- # A last segment is claimed only if exactly one verb ends in it. Two
130
- # aggregates declaring `Close` keep `customer.close` and `account.close`,
131
- # which is the honest answer — a CLI that picked one would be choosing
132
- # for the caller.
62
+ # Sets `:short` on each spec: the last segment when exactly one verb ends in it,
63
+ # else the full name (two aggregates both declaring `Close` keep both spellings).
133
64
  #
134
- # @param specs [Hash{String => Hash}] the verb or question map being projected,
135
- # mutated in place: each spec hash gains a `:short` key
65
+ # @param specs [Hash{String => Hash}] the verb or question map, mutated in place
136
66
  # @return [void]
137
67
  def shorten(specs)
138
68
  tails = specs.keys.group_by { |name| name.split(".").last }
@@ -142,12 +72,7 @@ module Hecks
142
72
  end
143
73
  end
144
74
 
145
- # Builds the alias table `CliRunner` resolves a typed name against.
146
- #
147
- # @param specs [Hash{String => Hash}] a verb or question map whose specs already
148
- # carry `:short` (set by `shorten`)
149
- # @return [Hash{String => String}] every accepted spelling — the full name and
150
- # its `:short` form — mapped to the full name
75
+ # Maps every accepted spelling (full name and `:short`) to the full name.
151
76
  def aliases(specs)
152
77
  specs.each_with_object({}) do |(name, spec), map|
153
78
  map[name] = name
@@ -155,17 +80,9 @@ module Hecks
155
80
  end
156
81
  end
157
82
 
158
- # A name is claimed once. A command and a query of one name are legal in
159
- # a chapter — the language namespaces them — and ambiguous as
160
- # subcommands. Refused here rather than silently resolving to whichever
161
- # was walked first, which is how `Ticket.Filed` (a command) and
162
- # `Ticket.Filed` (a query) sat undetected until something flattened them.
83
+ # Stores `spec` under `name`, refusing a second claim rather than silently
84
+ # keeping whichever verb was walked first.
163
85
  #
164
- # @param verbs [Hash{String => Hash}] the verb or question map being built,
165
- # mutated in place
166
- # @param name [String] the command-line name this spec claims
167
- # @param spec [Hash{Symbol => Object}] the spec hash to store under `name`
168
- # @return [void]
169
86
  # @raise [Bluebook::DSL::Malformed] if `name` is already claimed by another verb
170
87
  def claim(verbs, name, spec)
171
88
  if verbs.key?(name)
@@ -177,15 +94,7 @@ module Hecks
177
94
  verbs[name] = spec
178
95
  end
179
96
 
180
- # Builds the dotted command-line name for one verb: `aggregate[.entity].verb`,
181
- # snake-cased.
182
- #
183
- # @param aggregate [Bluebook::Aggregate] the verb's owning aggregate
184
- # @param verb [Bluebook::Command, Bluebook::Query, Bluebook::PortOperation]
185
- # the command, query or port operation being named
186
- # @param entity [Bluebook::Entity, nil] the entity the verb is declared on, or `nil`
187
- # for an aggregate-level verb
188
- # @return [String] the dotted, snake-cased command-line name
97
+ # The dotted command-line name, `aggregate[.entity].verb`, snake-cased.
189
98
  def name_for(aggregate, verb, entity = nil)
190
99
  parts = [Naming.snake(aggregate.hecks_name)]
191
100
  parts << Naming.snake(entity.hecks_name) if entity
@@ -193,39 +102,15 @@ module Hecks
193
102
  parts.join(".")
194
103
  end
195
104
 
196
- # Builds the verb's fully-qualified language name, `Bluebook::Aggregate[.Entity].Verb`,
197
- # for display in help text.
198
- #
199
- # @param bluebook [Bluebook::Chapter] the verb's owning chapter
200
- # @param aggregate [Bluebook::Aggregate] the verb's owning aggregate
201
- # @param verb [Bluebook::Command, Bluebook::Query, Bluebook::PortOperation]
202
- # the command, query or port operation being named
203
- # @param entity [Bluebook::Entity, nil] the entity the verb is declared on, or `nil`
204
- # for an aggregate-level verb
205
- # @return [String] the fully-qualified name, as the language spells it
105
+ # The verb's language name, `Chapter::Aggregate[.Entity].Verb`, for help text.
206
106
  def fqn(bluebook, aggregate, verb, entity = nil)
207
107
  [bluebook.name, "::", aggregate.hecks_name, ".",
208
108
  entity ? "#{entity.hecks_name}." : "", verb.hecks_name].join
209
109
  end
210
110
 
211
- # The arguments a receiver adds, before any verb-specific one. Shared by
212
- # `command_spec` and `port_spec` — a port operation always addresses an
213
- # aggregate record (`port_spec` passes `receiver: :aggregate`, never
214
- # `:entity` or `nil`, because a port is declared on an aggregate, never
215
- # an entity), and this is exactly the same "how do I name the record"
216
- # question a command with a receiver already answers, so it is answered
217
- # once, here, rather than duplicated.
218
- #
219
- # `nil` — a creating command — takes none: there is no existing record
220
- # yet for `to=` to name.
221
- #
222
- # @param receiver [Symbol, nil] `:entity`, `:aggregate`, or `nil` for a creating
223
- # command that names no existing record
224
- # @param aggregate [Bluebook::Aggregate] the aggregate the record belongs to
225
- # @param entity [Bluebook::Entity, nil] the entity to name, required when
226
- # `receiver` is `:entity`
227
- # @return [Array<Hash{Symbol => Object}>] the `to`/`to.aggregate`+`to.entity` option
228
- # specs this receiver needs, or `[]` for `nil`
111
+ # The options that name the record a verb acts on, before its own arguments.
112
+ # `nil` (a creating command) takes none: there is no record yet.
113
+ # Ports always pass `:aggregate`, since a port is declared on an aggregate.
229
114
  def receiver_options(receiver, aggregate, entity)
230
115
  case receiver
231
116
  when :entity
@@ -243,20 +128,6 @@ module Hecks
243
128
  end
244
129
  end
245
130
 
246
- # ── one verb ──────────────────────────────────────────────────────
247
-
248
- # Projects one command into the spec `verb_help` and `CliRunner` both read.
249
- #
250
- # @param bluebook [Bluebook::Chapter] the command's owning chapter
251
- # @param aggregate [Bluebook::Aggregate] the command's owning aggregate
252
- # @param entity [Bluebook::Entity, nil] the entity the command is declared on, or
253
- # `nil` for an aggregate-level command
254
- # @param command [Bluebook::Command] the command being projected
255
- # @return [Hash{Symbol => Object}] `:verb` (fully-qualified name), `:kind` (`:command`),
256
- # `:summary`, `:role` (String, nil if the command declares none), `:role_gated`
257
- # (Boolean), `:creates` (Boolean), `:receiver` (`:entity`, `:aggregate` or `nil`),
258
- # `:legacy_receiver` (`:id` or `nil`), `:legacy_arguments`, `:refusals`
259
- # (see `refusals`) and `:arguments` (see `options_for`)
260
131
  def command_spec(bluebook, aggregate, entity, command)
261
132
  holder = entity || aggregate
262
133
  arguments = command.attributes.flat_map { |a| options_for(a, holder, aggregate) }
@@ -266,16 +137,9 @@ module Hecks
266
137
  (command.creates? ? nil : :aggregate)
267
138
  end
268
139
 
269
- # The receiver is not a command argument. An aggregate command names
270
- # its record through to; an entity command needs both the aggregate
271
- # record and the entity element within it. Keeping those paths in the
272
- # projected option list makes the human-facing request complete while
273
- # CommandRequest can remove them before it builds with: from the
274
- # command's declared facts.
275
- #
276
- # Existing aggregate scripts may still spell the receiver id=... .
277
- # That alias is deliberately hidden from help and recorded separately
278
- # as legacy_arguments; new help and examples teach only to=... .
140
+ # The receiver paths stay in the projected option list so the request is complete;
141
+ # CommandRequest strips them before building `with:`. `id=...` is still accepted
142
+ # for aggregate receivers but hidden from help, which teaches only `to=...`.
279
143
  arguments = receiver_options(receiver, aggregate, entity) + arguments
280
144
  legacy_arguments = receiver == :aggregate ? [{ path: "id", type: "String", required: true }] : []
281
145
 
@@ -287,52 +151,18 @@ module Hecks
287
151
  refusals: refusals(command, holder), arguments: arguments }
288
152
  end
289
153
 
290
- # A port operation reads as a verb but reports as a boundary.
291
- #
292
- # `creates: false` because it makes no record, and `refusals: []`
293
- # because it has none in the sense every other verb means: a command's
294
- # refusals are sentences the chapter will say back to you, and an
295
- # outbound operation's failure is somebody else's sentence, unknowable
296
- # from here.
297
- #
298
- # The summary names both endings, which is the one thing a caller most
299
- # needs and cannot infer. `run_specs` looks like it either works or
300
- # errors; what it actually does is answer `SpecsCompleted` even when the
301
- # suite is red, and refuse only when rspec could not run. Somebody
302
- # reading `--help` should not have to open the hecksagon to find that
303
- # out.
304
- #
305
- # @param bluebook [Bluebook::Chapter] the port's owning chapter
306
- # @param aggregate [Bluebook::Aggregate] the aggregate the port is declared on
307
- # @param port [Bluebook::DomainPort] the port the operation is declared on
308
- # @param operation [Bluebook::PortOperation] the operation being projected
309
- # @return [Hash{Symbol => Object}] `:verb` (the `Aggregate.port.operation` name),
310
- # `:kind` (`:command`), `:creates` (`false`), `:receiver` (`:aggregate`),
311
- # `:refusals` (`[]`), `:role` (String, who calls whom through the port),
312
- # `:role_gated` (`false`), `:summary` (see `port_summary`) and `:arguments`
313
- # (see `options_for`)
154
+ # A port operation reads as a verb but reports as a boundary: it creates no
155
+ # record, and an outbound failure is the other side's sentence, so `refusals` is empty.
314
156
  def port_spec(bluebook, aggregate, port, operation)
315
157
  arguments = receiver_options(:aggregate, aggregate, nil) +
316
158
  operation.attributes.flat_map { |a| options_for(a, aggregate, aggregate) }
317
159
 
318
- # The wire name carries the port, the typed name does not.
319
- #
320
- # `Dispatcher#dispatch` splits a verb into head and sub and looks the
321
- # head up as a port, so a port operation is addressed
322
- # `Aggregate.Port.Operation` — three parts where a command has two.
323
- # But nobody should have to type `sweep.toolchain.run_specs`: which
324
- # port a verb goes out through is wiring, and the caller's business is
325
- # what they want done. So the projection spells the verb in full and
326
- # names it short, which is the same split `shorten` already makes.
160
+ # `Dispatcher#dispatch` looks the head up as a port, so the wire name is
161
+ # `Aggregate.Port.Operation`; the short name (see `shorten`) hides the port.
327
162
  { verb: [fqn(bluebook, aggregate, operation).sub(/\.[^.]+\z/, ""), port.name, operation.hecks_name].join("."),
328
163
  kind: :command, creates: false, receiver: :aggregate, refusals: [],
329
- # `role:` here is descriptive text, not an authorization gate —
330
- # who calls whom through the port, for `--help`/`verb_help`'s
331
- # "issued by" line. A port operation never reaches
332
- # `CommandRules::Authorization#refuse_role_mismatch` (only
333
- # `CommandInterpreter`/`EntityInterpreter` call it, never the port
334
- # dispatch path), so `role_gated: false` always, unlike
335
- # `command_spec` where the same key name means a real one.
164
+ # `role:` is help text only; port dispatch never reaches the role check,
165
+ # so `role_gated` is always false here.
336
166
  role: if operation.outbound?
337
167
  "#{aggregate.hecks_name} asking #{port.name}"
338
168
  else
@@ -342,26 +172,16 @@ module Hecks
342
172
  summary: port_summary(port, operation), arguments: arguments }
343
173
  end
344
174
 
345
- # Names both endings a port operation can have: what an inbound `tells` records,
346
- # or what an outbound `asks` came back with and what it said instead.
347
- #
348
- # @param port [Bluebook::DomainPort] the operation's owning port
349
- # @param operation [Bluebook::PortOperation] the operation being summarized
350
- # @return [String] one line, worded for `--help`
175
+ # Names both endings of a port operation, since `--help` is where a caller
176
+ # learns that e.g. a spec run answers `SpecsCompleted` even when the suite is red.
351
177
  def port_summary(port, operation)
352
178
  return "#{port.name} reports it; emits #{operation.emits.join(', ')}" unless operation.outbound?
353
179
 
354
180
  "Ask #{port.name} — answers #{operation.answers}, refuses #{operation.refuses}"
355
181
  end
356
182
 
357
- # A rootless report takes nothing; a rooted one takes the id of the
358
- # record it is a view of, under the name the model gave that reference.
359
- #
360
- # @param bluebook [Bluebook::Chapter] the report's owning chapter
361
- # @param model [Bluebook::ReadModel] the report being projected
362
- # @return [Hash{Symbol => Object}] `:verb` (`Chapter.ReportName`), `:kind`
363
- # (`:query`), `:summary` and `:arguments` (`[]`, or one required id option
364
- # named after the model's `reference_name` when it declares a `reference_target`)
183
+ # A rootless report takes nothing; a rooted one takes the id of the record it
184
+ # is a view of, under the name the model gave that reference.
365
185
  def report_spec(bluebook, model)
366
186
  arguments =
367
187
  if model.reference_target
@@ -375,15 +195,6 @@ module Hecks
375
195
  summary: model.description, arguments: arguments }
376
196
  end
377
197
 
378
- # Projects one query into the spec `verb_help` and `CliRunner` both read.
379
- #
380
- # @param bluebook [Bluebook::Chapter] the query's owning chapter
381
- # @param aggregate [Bluebook::Aggregate] the query's owning aggregate
382
- # @param entity [Bluebook::Entity, nil] the entity the query is declared on, or
383
- # `nil` for an aggregate-level query
384
- # @param query [Bluebook::Query] the query being projected
385
- # @return [Hash{Symbol => Object}] `:verb` (fully-qualified name), `:kind`
386
- # (`:query`), `:summary` and `:arguments` (see `options_for`)
387
198
  def query_spec(bluebook, aggregate, entity, query)
388
199
  arguments = Array(query.to_h[:attributes]).flat_map do |declared|
389
200
  attribute = query.attributes.find { |a| a.name.to_s == declared[:name].to_s }
@@ -394,35 +205,16 @@ module Hecks
394
205
  summary: query.description, arguments: arguments }
395
206
  end
396
207
 
397
- # ── one argument, flattened ───────────────────────────────────────
398
-
399
- # A value object becomes one option per field, dotted. `commit` typed
400
- # `CommitRef` is `--commit.value`, because that is the shape the runtime
401
- # wants and a flat `--commit` would have to guess which field it meant.
402
- # Single-field value objects — almost all of them — read fine either way,
403
- # and the runner accepts the short form for exactly those.
404
- # Recursive, and it has to be. A value object may hold another one —
405
- # pizzas' `Pizza` holds a `Price` and a `Size` — so stopping after one
406
- # level produced `pizza.price_cents=1500` and sent the string "1500"
407
- # where `{ cents: 1500 }` belonged.
408
- #
409
- # The runtime took it. `qa/FINDINGS.md` #2 is exactly that gap —
410
- # `Value::Coercion.build` does not validate nested value objects — so a
411
- # one-level CLI is not merely inconvenient, it is a machine for writing
412
- # malformed records into a real store, which is what it did on its first
413
- # run against the pizzas database.
208
+ # Flattens an attribute into one option per leaf field. A value object becomes
209
+ # dotted options (`--commit.value`), recursing into nested value objects so
210
+ # `{ cents: 1500 }` is never sent as the string "1500".
414
211
  #
415
212
  # @param attribute [Bluebook::Attribute] the field being projected
416
- # @param holder [Bluebook::Aggregate, Bluebook::Entity, Class] the construct
417
- # `attribute` is declared on; a `Bluebook::ValueObject` subclass on a recursive call
213
+ # @param holder [Bluebook::Aggregate, Bluebook::Entity, Class] what declares `attribute`
418
214
  # @param aggregate [Bluebook::Aggregate] the top-level aggregate, kept across recursion
419
- # so a nested value object can still be found
420
- # @param prefix [String, nil] the dotted path built so far, `nil` at the top level
421
- # @param optional [Boolean, nil] whether an enclosing field already makes this one
422
- # optional; `nil` defers to `attribute.optional?`
423
- # @return [Array<Hash{Symbol => Object}>] one option spec per leaf field, each with
424
- # `:path`, `:type`, `:required`, and optionally `:note`, `:enum`, `:pattern`,
425
- # `:default`, `:list`
215
+ # @param prefix [String, nil] the dotted path built so far
216
+ # @param optional [Boolean, nil] whether an enclosing field already makes this one optional
217
+ # @return [Array<Hash{Symbol => Object}>] one option spec per leaf field
426
218
  def options_for(attribute, holder, aggregate, prefix = nil, optional = nil)
427
219
  path = [prefix, attribute.name].compact.join(".")
428
220
  optional ||= attribute.optional?
@@ -431,19 +223,8 @@ module Hecks
431
223
  value_object = value_object_for(attribute, holder, aggregate)
432
224
  return [scalar_option(path, attribute, optional)] unless value_object
433
225
 
434
- # A list says so, all the way down to its leaves.
435
- #
436
- # Without this a `list_of(Tag)` projected exactly like a single Tag:
437
- # one option, `tags.value`, indistinguishable from a scalar. So the
438
- # help said to pass one, `CliDoor#bury` overwrote the leaf each time,
439
- # and passing two tags stored the second and lost the first without
440
- # saying anything. A missing argument is refused loudly; a forgotten
441
- # one is not, which makes it the more expensive of the two by far.
442
- #
443
- # The flag is carried on the leaf rather than kept beside the
444
- # attribute because the leaf is all `CliDoor` ever sees — it is handed
445
- # a path and a spec, and reuniting them with the attribute that
446
- # produced them would be a lookup that exists only to answer this.
226
+ # The list flag rides on each leaf: without it a repeated flag overwrote the
227
+ # leaf silently, and CliDoor only ever sees a path and a spec.
447
228
  fields = value_object.attributes.flat_map do |field|
448
229
  nested = value_object_for(field, value_object, aggregate)
449
230
  next options_for(field, value_object, aggregate, path, optional) if nested
@@ -457,27 +238,11 @@ module Hecks
457
238
  fields.map { |option| option.merge(list: true, note: [option[:note], "repeatable"].compact.join("; ")) }
458
239
  end
459
240
 
460
- # Projects a `reference_to` attribute as the id of the record it points at.
461
- #
462
- # @param attribute [Bluebook::Attribute] a `reference?` attribute
463
- # @return [Hash{Symbol => Object}] `:path`, `:type` (`"String"`), `:required` and
464
- # `:note`
465
241
  def reference_option(attribute)
466
242
  { path: attribute.name.to_s, type: "String", required: !attribute.optional?,
467
243
  note: "id of a #{attribute.type.target_name}" }
468
244
  end
469
245
 
470
- # Projects one leaf field — a plain attribute, or one field of a flattened
471
- # value object.
472
- #
473
- # @param path [String] the dotted option path this field is reached at
474
- # @param field [Bluebook::Attribute] the field being projected
475
- # @param optional [Boolean] whether this option may be omitted
476
- # @param enum [Array<Object>] the closed set of values this field admits, `[]` for
477
- # an open field
478
- # @return [Hash{Symbol => Object}] `:path`, `:type`, `:required`, plus `:enum` when
479
- # `enum` is non-empty, `:pattern` when `field` declares one, and `:default` when
480
- # `field` declares a non-nil one
481
246
  def scalar_option(path, field, optional, enum: [])
482
247
  option = { path: path, type: field.type.to_s, required: !optional }
483
248
  option[:enum] = enum unless enum.empty?
@@ -486,30 +251,15 @@ module Hecks
486
251
  option
487
252
  end
488
253
 
489
- # The values a `one_of` value object's field is closed to, for the option's
490
- # `:enum`.
491
- #
492
- # @param value_object [Class] a `Bluebook::ValueObject` subclass
493
- # @param field [Bluebook::Attribute] the value object's field being projected
494
- # @return [Array<Object>] the distinct values every declared member gives this
495
- # field, or `[]` when the value object declares no closed set
254
+ # The values a `one_of` value object's field is closed to, or `[]` for an open one.
496
255
  def closed_members(value_object, field)
497
256
  return [] unless value_object.closed_set?
498
257
 
499
258
  value_object.members.filter_map { |member| member[field.name] }.uniq
500
259
  end
501
260
 
502
- # Finds the value object type an attribute declares, if any — the fork between
503
- # `scalar_option` (one option) and recursing into `options_for` (one option per
504
- # field).
505
- #
506
- # @param attribute [Bluebook::Attribute] the field whose type is being resolved
507
- # @param holder [Bluebook::Aggregate, Bluebook::Entity, Class, nil] the construct
508
- # `attribute` is declared on, searched first
509
- # @param aggregate [Bluebook::Aggregate, nil] the top-level aggregate, searched when
510
- # `holder` does not declare the value object itself
511
- # @return [Class, nil] the matching `Bluebook::ValueObject` subclass, or `nil` if
512
- # `attribute`'s type names no value object in scope
261
+ # The `Bluebook::ValueObject` subclass an attribute's type names, searching
262
+ # `holder` then `aggregate`, or `nil` for a plain scalar.
513
263
  def value_object_for(attribute, holder, aggregate)
514
264
  [holder, aggregate].compact.each do |scope|
515
265
  next unless scope.respond_to?(:value_objects)
@@ -520,15 +270,8 @@ module Hecks
520
270
  nil
521
271
  end
522
272
 
523
- # Every way this verb can say no, in the chapter's own words — printed
524
- # by `--help` before the caller spends a dispatch finding out.
525
- #
526
- # @param command [Bluebook::Command] the command whose refusals are being projected
527
- # @param holder [Bluebook::Aggregate, Bluebook::Entity] the aggregate or entity the
528
- # command's lifecycle guard, if any, is checked against
529
- # @return [Array<String>] one sentence per way the command can refuse: a lifecycle
530
- # state mismatch, a missing referenced record per `reference_to` attribute, and
531
- # each declared `given`'s description, in that order
273
+ # Every way the verb can say no, in the chapter's own words: a lifecycle state
274
+ # mismatch, a missing referenced record per `reference_to`, then each `given`.
532
275
  def refusals(command, holder)
533
276
  out = []
534
277
  lifecycle = holder.lifecycle
@@ -540,25 +283,14 @@ module Hecks
540
283
  out + command.givens.map(&:description)
541
284
  end
542
285
 
543
- # ── the help ──────────────────────────────────────────────────────
544
-
545
286
  # Renders the full verb/question table, or one verb's `--help` text when
546
287
  # `options[:verb]` names one.
547
- #
548
- # @param bluebook [Bluebook::Chapter] the projected chapter, for its name and vision
549
- # line
550
- # @param verbs [Hash{String => Hash}] the projected, `shorten`ed command map
551
- # @param questions [Hash{String => Hash}] the projected, `shorten`ed query map
552
- # @param options [Hash{Symbol => Object}] `:program` (String), `:verb` (String, nil)
553
- # and `:ask` (Boolean) — see `call`
554
- # @return [String] the rendered help text
555
288
  def usage(bluebook, verbs, questions, options)
556
289
  program = options[:program] || "bin/run"
557
290
  only = options[:verb]
558
291
 
559
- # Which namespace, when both hold the name. `options[:ask]` says so;
560
- # without it a `--help` for a question would print the command that
561
- # shares its name, which banking has and which is how this was found.
292
+ # `options[:ask]` picks the namespace when both hold the name; without it a
293
+ # question's `--help` would print the command sharing its name.
562
294
  if only
563
295
  pool = options[:ask] ? questions : verbs
564
296
  key = aliases(pool)[only] || (options[:ask] ? nil : aliases(questions)[only])
@@ -582,40 +314,19 @@ module Hecks
582
314
  out.join("\n")
583
315
  end
584
316
 
585
- # Picks one verb whose full spelling differs from its short one, to show the
586
- # caller both forms exist.
587
- #
588
- # @param verbs [Hash{String => Hash}] the projected, `shorten`ed command map
589
- # @return [String] `"<short> is also <full>"`, or `""` if every verb's short form
590
- # is its full name (a one-aggregate domain, where no name ever collides)
317
+ # One verb whose full spelling differs from its short one, or `""` when none does.
591
318
  def example_qualified(verbs)
592
319
  name, spec = verbs.find { |key, value| key != value[:short] } || verbs.first
593
320
  name ? "#{spec[:short]} is also #{name}" : ""
594
321
  end
595
322
 
596
- # A query's `description` is written as a paragraph — it argues for why
597
- # the list is worth reading. A verb table wants the first sentence of
598
- # that argument; `--help` still prints the whole thing.
599
- #
600
- # @param text [String, nil] the full description, or `nil`
601
- # @return [String] the text up to and including its first `.`, or `""` for `nil`
323
+ # The verb table wants a query description's first sentence; `--help` prints all.
602
324
  def first_sentence(text)
603
325
  text.to_s.split(/(?<=\.)\s/).first.to_s
604
326
  end
605
327
 
606
- # Four text blocks, in fixed display order — meta (name/kind/role),
607
- # invocation, arguments, refusals. Each block is independent of the
608
- # others' content (only the output order is fixed, and stays fixed
609
- # below), so each is its own method returning the lines it
610
- # contributes — `[]` when it contributes none — concatenated in the
611
- # same order the original inline version built them in.
612
- #
613
- # @param program [String] how the caller was invoked, echoed in the invocation line
614
- # @param name [String] the verb's short display name
615
- # @param spec [Hash{Symbol => Object}] the verb or question's projected spec (see
616
- # `command_spec`, `port_spec`, `report_spec` or `query_spec`)
617
- # @param ask [Boolean] whether this is a question, reached through `ask`
618
- # @return [String] the verb's full `--help` text
328
+ # Four independent blocks, concatenated in fixed order: meta, invocation,
329
+ # arguments, refusals.
619
330
  def verb_help(program, name, spec, ask: false)
620
331
  out = verb_help_meta_lines(name, spec)
621
332
  out.concat(verb_help_invocation_lines(program, name, spec, ask))
@@ -624,12 +335,6 @@ module Hecks
624
335
  out.join("\n")
625
336
  end
626
337
 
627
- # Renders the name/summary heading, plus the dispatched or read verb and issuing
628
- # role when the spec declares them.
629
- #
630
- # @param name [String] the verb's short display name
631
- # @param spec [Hash{Symbol => Object}] the verb or question's projected spec
632
- # @return [Array<String>] the meta block's lines
633
338
  def verb_help_meta_lines(name, spec)
634
339
  out = ["#{name} — #{spec[:summary]}", ""]
635
340
  out << "dispatches #{spec[:verb]}" if spec[:kind] == :command
@@ -638,26 +343,11 @@ module Hecks
638
343
  out
639
344
  end
640
345
 
641
- # Renders the one-line example invocation, with every argument's path as a
642
- # placeholder.
643
- #
644
- # @param program [String] how the caller was invoked
645
- # @param name [String] the verb's short display name
646
- # @param spec [Hash{Symbol => Object}] the verb or question's projected spec, read
647
- # for `:arguments`
648
- # @param ask [Boolean] whether to show the `ask` form of the invocation
649
- # @return [Array<String>] the invocation block's lines
650
346
  def verb_help_invocation_lines(program, name, spec, ask)
651
347
  invocation = ask ? "#{program} ask #{name}" : "#{program} #{name}"
652
348
  ["", " #{invocation}#{spec[:arguments].map { |a| " #{a[:path]}=…" }.join}", ""]
653
349
  end
654
350
 
655
- # Renders one line per declared argument: its type, admitted values, pattern,
656
- # default and whether it is optional.
657
- #
658
- # @param spec [Hash{Symbol => Object}] the verb or question's projected spec, read
659
- # for `:arguments`
660
- # @return [Array<String>] the argument block's lines, `[]` if the verb takes none
661
351
  def verb_help_argument_lines(spec)
662
352
  return [] if spec[:arguments].empty?
663
353
 
@@ -675,11 +365,6 @@ module Hecks
675
365
  lines << ""
676
366
  end
677
367
 
678
- # Renders the "refused when:" block, one line per way the verb can say no.
679
- #
680
- # @param spec [Hash{Symbol => Object}] the verb or question's projected spec, read
681
- # for `:refusals`
682
- # @return [Array<String>] the refusal block's lines, `[]` if the verb declares none
683
368
  def verb_help_refusal_lines(spec)
684
369
  return [] if Array(spec[:refusals]).empty?
685
370