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,57 +2,31 @@ require_relative "../runtime/errors"
2
2
 
3
3
  module Hecks
4
4
  module Facade
5
- # **The CLI door** — where Facade meets a caller holding flat strings.
6
- #
7
- # `JsonDoor` beside this one translates for a caller holding parsed JSON:
8
- # String keys, already-nested objects, real Integers. A command line has
9
- # neither of those. It has `sequence.value=99` — one flat string, with the
10
- # nesting spelled as a path and the type not spelled at all.
11
- #
12
- # So this does the two things that turns into: rebuild the nesting, and
13
- # give every leaf the type the chapter declared for it.
14
- #
15
- # The type comes from the projection, never from the value. A door that
16
- # guessed — "99 looks like a number" — would send the Integer 99 for a
17
- # version string of "99", and be wrong in a way nothing downstream could
18
- # detect, because both are perfectly good arguments. `Projector::CliProjector`
19
- # already read the declared field type out of the value object; this only
20
- # applies it.
5
+ # Turns `path=value` command-line words into a nested, typed argument Hash.
6
+ # Types come from the projection, never from guessing at the value ("99" may be a String).
21
7
  module CliDoor
22
8
  module_function
23
9
 
24
- # Builds a verb's nested, typed argument Hash out of the `name=value` words on a
25
- # command line.
10
+ # Builds a verb's nested, typed argument Hash from its `name=value` words.
26
11
  #
27
- # `["reference.value=BUG#1", "sequence.value=99"]` against a projected
28
- # verb spec -> `{ reference: { value: "BUG#1" }, sequence: { value: 99 } }`
12
+ # arguments(spec, ["reference.value=A-1", "sequence.value=99"])
13
+ # # => { reference: { value: "A-1" }, sequence: { value: 99 } }
29
14
  #
30
- # @param spec [Hash{Symbol => Object}] one verb's entry from `Projector::CliProjector`;
31
- # read for `:arguments` and the optional `:legacy_arguments`, each an Array of
32
- # option Hashes with `:path`, `:type` and, for a list, `:list`
33
- # @param pairs [Array<String>] the words after the verb, each `path=value`; a path
34
- # may be the short form of a single-field value object (`reference` for
35
- # `reference.value`)
36
- # @return [Hash{Symbol => Object}] the arguments nested by path, each leaf cast to
37
- # its declared type and each list option collected into an Array; `{}` for no pairs
38
- # @raise [Runtime::NotFound] if a pair has no `=`, or names a path the verb does
39
- # not take
40
- # @raise [Runtime::TypeMismatch] if a value cannot be read as the declared Integer
41
- # or Float
15
+ # @param spec [Hash{Symbol => Object}] one verb's entry from `Projector::CliProjector`
16
+ # @param pairs [Array<String>] the words after the verb; a path may be the short form
17
+ # of a single-field value object (`reference` for `reference.value`)
18
+ # @return [Hash{Symbol => Object}] the arguments nested by path, leaves cast to type
19
+ # @raise [Runtime::NotFound] if a pair has no `=` or names a path the verb lacks
20
+ # @raise [Runtime::TypeMismatch] if a value does not parse as its Integer or Float
42
21
  def arguments(spec, pairs)
43
- # Legacy options are accepted but not printed in help. This lets an
44
- # existing id=... aggregate invocation cross the new receiver boundary
45
- # while the projected surface teaches to=... exclusively.
22
+ # Extra accepted arguments stay out of help, which teaches only to=....
46
23
  options = (spec[:arguments] + Array(spec[:legacy_arguments])).to_h do |argument|
47
24
  [argument[:path], argument]
48
25
  end
49
26
 
50
27
  pairs.each_with_object({}) do |pair, args|
51
28
  path, value = split(pair)
52
- # key? first, never `||` — full names whichever spelling (the
53
- # bare path, or its one-argument expansion) actually declares
54
- # this option, and the lookup below must hold to that same
55
- # decision rather than re-guessing which one exists.
29
+ # key? rather than `||`, so the lookup below honors the same spelling `full` chose.
56
30
  full = options.key?(path) ? path : expand(path, options)
57
31
  argument = options.key?(full) ? options[full] : raise(Runtime::NotFound, unknown(path, options.keys))
58
32
 
@@ -62,12 +36,7 @@ module Hecks
62
36
  end
63
37
  end
64
38
 
65
- # Cuts one command-line word at its first `=`, so a value may itself contain `=`.
66
- #
67
- # @param pair [String] one word, such as `"reference.value=BUG#1"`
68
- # @return [Array(String, String)] the path and the value; the value is `""` for
69
- # `"name="`
70
- # @raise [Runtime::NotFound] if the word contains no `=`
39
+ # Cuts one word at its first `=`, so a value may itself contain `=`.
71
40
  def split(pair)
72
41
  name, value = pair.split("=", 2)
73
42
  raise Runtime::NotFound, "#{pair.inspect} is not name=value" if value.nil?
@@ -75,32 +44,15 @@ module Hecks
75
44
  [name, value]
76
45
  end
77
46
 
78
- # Expands a bare value-object name into the full path of its only field.
79
- #
80
- # The short form, for the common case. Almost every value object in this
81
- # corpus has exactly one field, so `reference=BUG#1` is unambiguous and
82
- # is what anybody types. Expanded only when precisely one option starts
83
- # with that prefix — two would be a guess, and a guess about which field
84
- # a caller meant is worse than asking them to say.
85
- #
86
- # @param path [String] the path as typed, such as `"reference"`
87
- # @param options [Hash{String => Hash}] the verb's options keyed by full path
88
- # @return [String] the one full path beginning `"#{path}."`, or `path` unchanged when
89
- # none or several do
47
+ # Expands a bare value-object name into the path of its only field.
48
+ # Left unchanged when several fields match, since guessing which was meant is worse.
90
49
  def expand(path, options)
91
50
  candidates = options.keys.select { |key| key.start_with?("#{path}.") }
92
51
  candidates.length == 1 ? candidates.first : path
93
52
  end
94
53
 
95
- # Converts one command-line value to the type the chapter declares for its field.
96
- #
97
- # @param value [String] the text after the `=`
98
- # @param type [String] the declared type name; `"Integer"`, `"Float"` and
99
- # `"Boolean"` are converted, anything else leaves the value a String
100
- # @return [Integer, Float, Boolean, String] the typed value; a Boolean is true only
101
- # for `true`, `yes` or `1` in any letter case, and false for every other text
102
- # @raise [Runtime::TypeMismatch] if the text does not parse as the declared Integer
103
- # or Float
54
+ # Converts one value to its declared type; only Integer, Float and Boolean convert.
55
+ # A Boolean is true only for `true`, `yes` or `1` in any letter case.
104
56
  def cast(value, type)
105
57
  case type
106
58
  when "Integer" then Integer(value)
@@ -112,36 +64,12 @@ module Hecks
112
64
  raise Runtime::TypeMismatch, "#{value.inspect} is not #{type} — the chapter declares this field as #{type}"
113
65
  end
114
66
 
115
- # Adds one element to a list argument, creating the list on first use.
116
- #
117
- # A list grows rather than overwrites, and getting this wrong is silent.
67
+ # Appends one element to a list argument, creating the list on first use.
118
68
  #
119
- # `tags.value=framework tags.value=model-checker` sent through `bury`
120
- # twice would store the second one alone — no refusal, no warning, one tag
121
- # simply gone. That is the failure the interview named first: not the
122
- # loud kind, the kind where a value is forgotten and the caller has no
123
- # way to notice.
124
- #
125
- # A list of one is still a list. `tags.value=flaky` produces
126
- # `[{ value: "flaky" }]`, not `{ value: "flaky" }`, because the chapter
127
- # declared a collection and a caller who sent one element did not
128
- # thereby declare a different shape. A bare object must never reach a
129
- # `list_of` attribute: everything downstream that walks
130
- # it — a query's `contains`, a projection, the Postgres adapter's own
131
- # array handling — is entitled to assume it can iterate.
132
- #
133
- # Multi-field elements are not supported here, deliberately. A flat
134
- # command line has no way to say which `a.x=` goes with which `a.y=`,
135
- # and inventing an index syntax would be a language nobody asked for.
136
- # Every list in this corpus is a list of single-field value objects; a
137
- # richer one is a job for `JsonDoor`, which has real nesting.
138
- #
139
- # @param hash [Hash{Symbol => Object}] the arguments built so far; mutated in place
140
- # @param path [Array<String>] the option's path segments, at least two: the last
141
- # names the element's field, the one before it names the list
142
- # @param value [Integer, Float, Boolean, String] the cast value for the new element
143
- # @return [Hash{Symbol => Object}] `hash`, with `{ leaf => value }` appended to the
144
- # Array at the list's key
69
+ # Repeats must grow the list (`bury` would silently keep only the last), and a single
70
+ # element is still a one-item list, since a `list_of` attribute must never see a bare
71
+ # object. Multi-field elements are left to `JsonDoor`: a flat command line cannot say
72
+ # which `a.x=` pairs with which `a.y=`.
145
73
  def append(hash, path, value)
146
74
  *branches, leaf = path.map(&:to_sym)
147
75
  holder = branches[0..-2].reduce(hash) { |node, key| node[key] ||= {} }
@@ -151,13 +79,7 @@ module Hecks
151
79
  hash
152
80
  end
153
81
 
154
- # Sets one value at a nested path, creating each intermediate Hash on the way and
155
- # overwriting whatever the leaf already held.
156
- #
157
- # @param hash [Hash{Symbol => Object}] the arguments built so far; mutated in place
158
- # @param path [Array<String>] the option's path segments, outermost first
159
- # @param value [Integer, Float, Boolean, String] the cast value to store at the leaf
160
- # @return [Hash{Symbol => Object}] `hash`, with the value set under Symbol keys
82
+ # Sets one value at a nested path, creating intermediate Hashes and overwriting the leaf.
161
83
  def bury(hash, path, value)
162
84
  *branches, leaf = path.map(&:to_sym)
163
85
  target = branches.reduce(hash) { |node, key| node[key] ||= {} }
@@ -166,10 +88,6 @@ module Hecks
166
88
  end
167
89
 
168
90
  # Words the refusal for an argument the verb does not take, listing what it does.
169
- #
170
- # @param path [String] the path as the caller typed it
171
- # @param known [Array<String>] every path the verb accepts, in any order
172
- # @return [String] a one-line message naming `path` and the sorted accepted paths
173
91
  def unknown(path, known)
174
92
  "no argument #{path.inspect} — this verb takes #{known.sort.join(', ')}"
175
93
  end
@@ -7,44 +7,21 @@ require_relative "../ports/clock"
7
7
 
8
8
  module Hecks
9
9
  module Facade
10
- # The runner behind a projected CLI.
11
- #
12
- # `Projector::CliProjector` answers what a domain's command line looks
13
- # like; this is the twenty lines that parse against it and dispatch. It
14
- # lives in `lib/` rather than in a `bin/` because more than one front door
15
- # wants it — `bin/run` for whichever domain you are standing in, `bin/qc`
16
- # pinned to the QA ledger — and a second copy of the parse-and-dispatch
17
- # would be the exact duplication the projection exists to avoid.
18
- #
19
- # No IO. It answers `[text, status]` and never prints or exits, so a spec
20
- # can call it without capturing streams or trapping SystemExit. The `bin/`
21
- # scripts do the printing, the same division `Router` and `JsonDoor`
22
- # already keep against HTTP.
10
+ # Parses and dispatches a command line against a `Projector::CliProjector` projection.
11
+ # Does no IO: it answers `[text, status]` and leaves printing and exiting to `bin/` scripts.
23
12
  module CliRunner
24
13
  module_function
25
14
 
26
- # Runs one command line against a booted domain: resolves the verb or question,
27
- # parses its `name=value` arguments, dispatches or queries, and answers with the text
28
- # to print.
15
+ # Runs one command line against a booted domain and answers the text to print.
16
+ # Only the first bluebook in the registry, the booted domain's own, is projected.
29
17
  #
30
- # `[text, status]` — status 0 answered, 1 refused or misused.
31
- #
32
- # Only the first bluebook in the registry (the booted domain's own) is projected
33
- # into verbs. `--help`, `-h`, `help` or an empty `argv` answers the usage text; a
34
- # verb followed by `--help` answers that verb's help.
35
- #
36
- # @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted domain,
37
- # as `Hecks.boot` returns it
38
- # @param argv [Array<String>] the words after the program name: an optional `ask`,
39
- # the verb or question name (bare or aggregate-qualified), then `path=value` pairs
18
+ # @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted domain
19
+ # @param argv [Array<String>] an optional `ask`, the verb or question name, then
20
+ # `path=value` pairs; `--help`, `-h`, `help` or nothing answers usage
40
21
  # @param program [String] how the caller was invoked, echoed in usage and hints
41
- # @return [Array(String, Integer)] the text to print and the exit status: 0 with
42
- # usage, help, or pretty-printed JSON of the outcome; 1 with a message when the
43
- # verb is unknown, an argument is wrong, a record is missing, or the domain refuses
44
- # @raise [Runtime::WiringError] if a repository, or the clock adapter a `now`
45
- # argument needs, cannot be resolved
46
- # @raise [Runtime::StaleWrite] if concurrent writers beat the command through every
47
- # retry
22
+ # @return [Array(String, Integer)] the text and the status: 0 answered, 1 refused or misused
23
+ # @raise [Runtime::WiringError] if a repository or the clock adapter cannot be resolved
24
+ # @raise [Runtime::StaleWrite] if concurrent writers beat the command through every retry
48
25
  def call(runtime:, argv:, program: "bin/run")
49
26
  bluebook = runtime.registry.bluebooks.values.first
50
27
  cli = Projector.call(:cli, bluebook: bluebook, options: { program: program })
@@ -52,16 +29,14 @@ module Hecks
52
29
  name = argv.first
53
30
  return [cli[:usage], 0] if name.nil? || %w[--help -h help].include?(name)
54
31
 
55
- # `ask` puts a question in its own namespace — a chapter may declare a
56
- # command and a query of one name, and banking does.
32
+ # `ask` gives questions their own namespace; a chapter may declare a command and query
33
+ # of one name.
57
34
  asking = name == "ask"
58
35
  argv = argv[1..] if asking
59
36
  name = argv.first
60
37
  return [cli[:usage], 1] if name.nil?
61
38
 
62
- # Resolved through the alias map, so `pizzas create_pizza` and
63
- # `pizzas order.create_pizza` reach the same verb — the aggregate is
64
- # worth typing only when two of them declare the same word.
39
+ # The alias map lets `create_pizza` and `order.create_pizza` reach the same verb.
65
40
  pool = asking ? cli[:questions] : cli[:verbs]
66
41
  key = cli[:names][asking ? :question : :command][name]
67
42
  spec = pool[key]
@@ -77,26 +52,8 @@ module Hecks
77
52
  dispatch(runtime, spec, name, rest, program, asking)
78
53
  end
79
54
 
80
- # Parses one resolved verb's arguments, runs it as a query or a command, and turns
81
- # the outcome, or the domain's refusal, into printable text.
82
- #
83
- # @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted domain
84
- # @param spec [Hash{Symbol => Object}] the verb's entry from `Projector::CliProjector`;
85
- # read for `:kind`, `:verb`, `:arguments`, `:receiver` and `:legacy_receiver`
86
- # @param name [String] the verb as the caller typed it, echoed in the `--help` hint
87
- # @param rest [Array<String>] the `path=value` words after the verb
88
- # @param program [String] how the caller was invoked, echoed in the `--help` hint
89
- # @param asking [Boolean] whether the verb was reached through `ask`, which only
90
- # changes the wording of the hint
91
- # @return [Array(String, Integer)] status 0 with pretty-printed JSON: the materialized
92
- # rows for a query; `id`, `state` and event names for a command; `id` and events
93
- # with payloads for a port operation. Status 1 with the message of a
94
- # `Runtime::DOMAIN_REFUSALS` error, followed by a `--help` hint when it is a
95
- # `Runtime::NotFound` or `Runtime::TypeMismatch`
96
- # @raise [Runtime::WiringError] if a repository, or the clock adapter a `now`
97
- # argument needs, cannot be resolved
98
- # @raise [Runtime::StaleWrite] if concurrent writers beat the command through every
99
- # retry
55
+ # Parses one resolved verb's arguments, runs it as a query or command, and turns the
56
+ # outcome, or the domain's refusal, into `[json_or_message, status]`.
100
57
  def dispatch(runtime, spec, name, rest, program, asking)
101
58
  args = stamp_time(runtime, spec, CliDoor.arguments(spec, rest))
102
59
 
@@ -105,10 +62,7 @@ module Hecks
105
62
  return [JSON.pretty_generate(rows.map { |row| JsonDoor.materialize(row) }), 0]
106
63
  end
107
64
 
108
- # The answer is scoped to what was asked. `bin/run`'s step-list form
109
- # reports the whole store because a corpus run is judged on all of it;
110
- # somebody who issued one verb wants that verb's outcome, and against a
111
- # Postgres-backed domain the full dump is every record there has been.
65
+ # Answers with only this verb's outcome; a full store dump is every record there is.
112
66
  request = CommandRequest.normalize(args, receiver: spec[:receiver],
113
67
  legacy_receiver: spec[:legacy_receiver])
114
68
  handle = runtime.dispatch_flat(spec[:verb], request)
@@ -118,62 +72,17 @@ module Hecks
118
72
  state: JsonDoor.materialize(handle.state),
119
73
  events: handle.events.map(&:name)), 0]
120
74
  rescue Runtime::NotFound, Runtime::TypeMismatch => e
121
- # A bad argument and a missing record both land here, and both want the
122
- # same next step: read what the verb actually takes.
75
+ # A bad argument and a missing record both need the same next step: read the help.
123
76
  ["#{e.message}\n\n #{program} #{'ask ' if asking}#{name} --help", 1]
124
77
  rescue *Runtime::DOMAIN_REFUSALS => e
125
- # **The refusal is the product** — the chapter's own sentence, verbatim.
78
+ # The refusal is the chapter's own sentence, verbatim.
126
79
  [e.message, 1]
127
80
  end
128
81
 
129
- # Supplies the current time for a verb that takes a `now` argument the caller left out.
130
- #
131
- # The clock, filled in at the door.
132
- #
133
- # A staleness rule needs the time, and the sublanguage cannot ask for it
134
- # — a `given` that read the clock would judge the same record differently
135
- # on two runs, and every replay, audit and fuzz oracle here assumes it
136
- # does not. So `now` stays an argument the predicate merely reads, and
137
- # the question becomes who types it. Left to the caller, that is:
138
- #
139
- # qa/quality_control target.claim id=QC held_by.value=me \
140
- # now.value=$(date +%s) window.value=900
141
- #
142
- # which is a shell incantation in front of every claim, and one an agent
143
- # gets wrong by pasting a stale number.
144
- #
145
- # At the door, not in the runtime, and the distinction is load-bearing.
146
- # `Ports::IdentityGeneration` reasons the same question through for a
147
- # minted uuid and lands on "the value is baked into the caller's args at
148
- # the first live dispatch". A clock consulted inside the interpreter
149
- # would not have that property — a recorded corpus step replayed
150
- # tomorrow would quietly get tomorrow's time, and the fuzzer's oracle and
151
- # the adapter-agreement gate both compare runs of exactly that shape. Here
152
- # it fills only what a person or an agent is typing, and `runtime.dispatch`
153
- # is left alone.
82
+ # Fills in a `now` argument the caller left out, from the clock port; an explicit one wins.
154
83
  #
155
- # An explicit value always wins, so a spec or a caller reproducing a
156
- # moment says so and is believed. This only supplies what was omitted.
157
- #
158
- # By name, which is the one uncomfortable part. `now` is a plausible
159
- # domain word and nothing declares that it means the clock. It is
160
- # tolerable because this is a convenience layer rather than semantics —
161
- # the verb's own help says the argument exists, dispatch is unchanged,
162
- # and passing it explicitly is always available. The honest version is a
163
- # declaration in the chapter (`attribute :now, Instant, from: :clock`),
164
- # which is a language change: DSL, IR, the self-hosted grammar and its
165
- # goldens. Worth doing; not worth smuggling in here.
166
- #
167
- # @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted domain,
168
- # whose registry names the clock adapter
169
- # @param spec [Hash{Symbol => Object}] the verb's projected entry; only an option whose
170
- # `:path` starts `"now."` makes this method act
171
- # @param args [Hash{Symbol => Object}] the parsed arguments; not mutated
172
- # @return [Hash{Symbol => Object}] `args` itself when the verb takes no `now` or the
173
- # caller gave one; otherwise a copy with `now: { value: seconds }`, the clock port's
174
- # current time in whole seconds
175
- # @raise [Runtime::WiringError] if the time is needed and zero or several adapters
176
- # implement the clock port
84
+ # Done at the door, not in the runtime: a clock read inside the interpreter would give a
85
+ # replayed corpus step the replay day's time. Matched by the argument's name alone.
177
86
  def stamp_time(runtime, spec, args)
178
87
  return args unless spec[:arguments].any? { |argument| argument[:path].start_with?("now.") }
179
88
  return args if args.key?(:now)
@@ -181,69 +90,22 @@ module Hecks
181
90
  args.merge(now: { value: Ports::Clock.now(runtime.registry) })
182
91
  end
183
92
 
184
- # Shapes a port operation's outcome for printing: the record asked about, and each
185
- # event with its full payload.
186
- #
187
- # A port operation has no state, and its payload is the entire point.
188
- #
189
- # A command answers with the record it changed, so naming the events is
190
- # enough — the interesting part is in `state`. A port operation changes
191
- # no record: it asked something outside and came back with what was
192
- # said, and that lives only in the event payload. Reporting names alone
193
- # would print `SpecsCompleted` and drop the spec output on the floor.
194
- #
195
- # This is what makes a projected CLI usable as somebody's only door. An
196
- # agent that may not shell out cannot run `rspec` and read the terminal;
197
- # it asks the port and reads the answer, and if the answer is a bare
198
- # event name then the door leads nowhere and it needs a shell after all.
93
+ # Shapes a port operation's outcome, which has no state: each event with its full payload.
199
94
  #
200
- # Both endings come back the same way, and the status stays 0 for both.
201
- # A refusal here is not a misuse — `IssueStillOpen` and `SuiteFailed`
202
- # are answers the caller asked for, correctly delivered. Exit 1 is for
203
- # "you typed something wrong", and conflating the two would have a
204
- # scripted agent treat a healthy no as a broken call.
205
- #
206
- # @param handle [Runtime::Dispatcher::Result, Runtime::RemoteDispatcher::Result] the
207
- # dispatch result, which for a port operation has a nil `state`
208
- # @return [Hash{Symbol => Object}] `:id`, the result's id or else the first event's
209
- # (nil when there is neither), and `:events`, an Array of `{ name:, payload: }`
210
- # Hashes with each payload materialized to plain data
95
+ # The answer lives only in the payloads, so names alone would drop it. Exit status stays 0
96
+ # for refusal events too, since a healthy no is an answer, not a misuse.
211
97
  def answered(handle)
212
- # The ID comes off the event, because a port operation hydrates no
213
- # instance and the handle's own `id` is nil by design. The event knows
214
- # which record was asked about — it was stamped with it — and printing
215
- # `null` beside a payload that plainly says `SW-TOOL` would read as a
216
- # bug in something.
98
+ # A port operation hydrates no instance, so the handle's `id` is nil by design.
217
99
  { id: handle.id || handle.events.first&.id,
218
100
  events: handle.events.map do |event|
219
101
  { name: event.name, payload: JsonDoor.materialize(event.payload) }
220
102
  end }
221
103
  end
222
104
 
223
- # Words the answer to a verb or question that does not exist, suggesting up to five
224
- # names that start the same way.
225
- #
226
- # A near miss is worth more than a list. Somebody who typed
227
- # `bug.discovr` wants one line, not eighty-seven of them.
228
- #
229
- # Ranked by shared prefix, not by substring. Substring matching finds
230
- # nothing for the commonest typo of all — a dropped
231
- # letter, `order.create_piza`, which is a substring of nothing. Prefix
232
- # length survives an error anywhere after it, which is where errors are.
233
- #
234
- # @param cli [Hash{Symbol => Object}] the `Projector::CliProjector` projection; read
235
- # for `cli[:names]`, the alias maps under `:command` and `:question`
236
- # @param name [String] what the caller typed
237
- # @param asking [Boolean] whether it was typed after `ask`, which picks the question
238
- # names and the wording
239
- # @param program [String] how the caller was invoked, echoed in the closing hint
240
- # @return [String] a multi-line message; candidates must share a prefix of at least
241
- # half of `name`'s length, and never fewer than three characters
105
+ # Words the answer for an unknown verb or question, suggesting up to five near names.
106
+ # Ranked by shared prefix, which survives a dropped letter where a substring match would not.
242
107
  def unknown(cli, name, asking, program)
243
- # **Both spellings are candidates**. A caller who typed the qualified
244
- # form with a typo — `order.create_piza` — shares no prefix with the
245
- # short name `create_pizza`, so pooling only one of them suggests
246
- # nothing for half the mistakes anybody makes.
108
+ # Both spellings are candidates: `order.create_piza` shares no prefix with `create_pizza`.
247
109
  pool = cli[:names][asking ? :question : :command].keys
248
110
  near = pool.map { |candidate| [shared_prefix(candidate, name), candidate] }
249
111
  .select { |shared, _| shared >= [name.length / 2, 3].max }
@@ -256,12 +118,6 @@ module Hecks
256
118
  lines.join("\n")
257
119
  end
258
120
 
259
- # Counts how many leading characters two names have in common.
260
- #
261
- # @param one [String] a candidate name
262
- # @param other [String] the name to compare it with
263
- # @return [Integer] the length of the common prefix, from 0 up to the shorter
264
- # name's length
265
121
  def shared_prefix(one, other)
266
122
  length = [one.length, other.length].min
267
123
  (0...length).find { |index| one[index] != other[index] } || length
@@ -2,42 +2,24 @@ require_relative "../runtime/errors"
2
2
 
3
3
  module Hecks
4
4
  module Facade
5
- # Turns an external command request into the dispatcher's receiver/payload
6
- # envelope. Human-facing doors may accept their old flat id spelling at
7
- # the edge, but every call leaving this boundary has one shape:
5
+ # Turns an external command request into the dispatcher's `to:`/`with:` envelope.
8
6
  #
9
7
  # aggregate command: { to: "record-id", with: { declared: "facts" } }
10
- # entity command: { to: { aggregate: "...", entity: "..." },
11
- # with: { declared: "facts" } }
12
- #
13
- # The command interpreter remains the authority on which facts are
14
- # declared. This helper only prevents routing fields from leaking into the
15
- # fact payload and gives every external door the same wire contract.
8
+ # entity command: { to: { aggregate: "...", entity: "..." }, with: { ... } }
16
9
  module CommandRequest
17
10
  module_function
18
11
 
19
- # Splits one external request into routing (`to:`) and facts (`with:`), accepting
20
- # either the explicit envelope or a flat Hash, and refuses a receiver that does
21
- # not fit the kind of command being called.
22
- #
23
- # Keys are symbolized at every depth first, so a parsed JSON body and a Ruby Hash
24
- # are treated alike. In a flat Hash everything but `to` (or the legacy receiver
25
- # keys) is a fact.
12
+ # Splits a request, explicit envelope or flat Hash, into routing (`to:`) and facts (`with:`).
13
+ # Keys are symbolized at every depth; in a flat Hash everything but the receiver is a fact.
26
14
  #
27
- # @param input [Hash, Object] the request, with String or Symbol keys; anything
28
- # that is not a Hash is refused
29
- # @param receiver [Symbol, nil] the kind of receiver the command takes:
30
- # `:aggregate` (an identity), `:entity` (a Hash of `aggregate:` and `entity:`
31
- # identities), or `nil` for a command that takes none, such as a creating command
32
- # @param legacy_receiver [Symbol, String, Hash{Symbol => Symbol, String}, nil] where
33
- # a flat request without `to` may carry its receiver instead: one key name (such
34
- # as `:id`) for an `:aggregate` receiver, or `{ aggregate: key, entity: key }` for
35
- # an `:entity` receiver; `nil` accepts no legacy spelling
36
- # @return [Hash{Symbol => Object}] `{ with: facts }`, plus `to:` holding the route
37
- # whenever `receiver` is not `nil`; ready to pass to `Dispatcher#dispatch_flat`
38
- # @raise [Runtime::TypeMismatch] if `input` or its `with:` is not a Hash, if an
39
- # explicit envelope carries keys other than `to:` and `with:`, or if the route
40
- # is missing, blank, malformed, or given to a command that takes no receiver
15
+ # @param input [Hash] the request, with String or Symbol keys
16
+ # @param receiver [Symbol, nil] `:aggregate`, `:entity`, or `nil` for a command with none
17
+ # @param legacy_receiver [Symbol, String, Hash, nil] the flat key that may carry the
18
+ # receiver instead of `to:`: a name for `:aggregate`, `{ aggregate:, entity: }` for
19
+ # `:entity`
20
+ # @return [Hash{Symbol => Object}] `{ with: facts }`, plus `to:` when `receiver` is set
21
+ # @raise [Runtime::TypeMismatch] if the request is malformed or the route does not fit
22
+ # the receiver kind
41
23
  # @raise [ArgumentError] if `receiver` is not `nil`, `:aggregate` or `:entity`
42
24
  def normalize(input, receiver:, legacy_receiver: nil)
43
25
  request = symbolize(input)
@@ -87,9 +69,7 @@ module Hecks
87
69
  end
88
70
  private_class_method :take_legacy_route
89
71
 
90
- # A case dispatch over the three closed receiver kinds (nil,
91
- # :aggregate, :entity) plus the impossible-kind backstop — each
92
- # branch is its own self-contained validation for that one kind.
72
+ # One self-contained check per closed receiver kind, plus a backstop.
93
73
  # rubocop:disable-next Metrics/CyclomaticComplexity
94
74
  def validate_route!(route, receiver)
95
75
  case receiver