hecks 2.7.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (463) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +11 -0
  3. data/lib/hecks/adapters/driven/claude_code.rb +4 -104
  4. data/lib/hecks/adapters/driven/d1.rb +44 -237
  5. data/lib/hecks/adapters/driven/folder.rb +17 -76
  6. data/lib/hecks/adapters/driven/google_authentication.rb +15 -49
  7. data/lib/hecks/adapters/driven/governance_authorization.rb +23 -70
  8. data/lib/hecks/adapters/driven/heki/journal.rb +8 -41
  9. data/lib/hecks/adapters/driven/heki/saga_store.rb +12 -54
  10. data/lib/hecks/adapters/driven/heki/snapshot.rb +4 -12
  11. data/lib/hecks/adapters/driven/heki.rb +13 -42
  12. data/lib/hecks/adapters/driven/identity_registry.rb +3 -14
  13. data/lib/hecks/adapters/driven/in_memory_ordering.rb +9 -26
  14. data/lib/hecks/adapters/driven/in_process_key_vault.rb +2 -14
  15. data/lib/hecks/adapters/driven/lambda/client.rb +10 -51
  16. data/lib/hecks/adapters/driven/lambda.rb +10 -84
  17. data/lib/hecks/adapters/driven/local_storage.rb +5 -40
  18. data/lib/hecks/adapters/driven/memory.rb +18 -99
  19. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +5 -21
  20. data/lib/hecks/adapters/driven/postgres/codec.rb +6 -28
  21. data/lib/hecks/adapters/driven/postgres/outbox.rb +3 -12
  22. data/lib/hecks/adapters/driven/postgres/reconnect.rb +4 -30
  23. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +34 -108
  24. data/lib/hecks/adapters/driven/postgres.rb +145 -134
  25. data/lib/hecks/adapters/driven/postgres_era.adapter +4 -15
  26. data/lib/hecks/adapters/driven/prism.rb +7 -30
  27. data/lib/hecks/adapters/driven/sql_query_builder.rb +21 -121
  28. data/lib/hecks/adapters/driven/sqlite/codec.rb +8 -40
  29. data/lib/hecks/adapters/driven/sqlite/projection.rb +26 -82
  30. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +40 -84
  31. data/lib/hecks/adapters/driven/sqlite.rb +128 -65
  32. data/lib/hecks/adapters/driven/tenant_provisioner.rb +15 -37
  33. data/lib/hecks/adapters/driven.rb +5 -36
  34. data/lib/hecks/adapters/driving/github_webhook.rb +8 -76
  35. data/lib/hecks/behaviors/dsl.rb +7 -25
  36. data/lib/hecks/behaviors/expectations.rb +32 -142
  37. data/lib/hecks/behaviors/ir.rb +7 -20
  38. data/lib/hecks/behaviors/rspec.rb +6 -17
  39. data/lib/hecks/behaviors/runner.rb +10 -34
  40. data/lib/hecks/behaviors.rb +4 -8
  41. data/lib/hecks/bluebook/aggregate.rb +9 -75
  42. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +9 -38
  43. data/lib/hecks/bluebook/assembly/build.rb +4 -15
  44. data/lib/hecks/bluebook/assembly/contract.rb +15 -136
  45. data/lib/hecks/bluebook/assembly/contracts.rb +54 -174
  46. data/lib/hecks/bluebook/assembly/marks.rb +31 -223
  47. data/lib/hecks/bluebook/assembly/specializer.rb +6 -52
  48. data/lib/hecks/bluebook/assembly.rb +6 -45
  49. data/lib/hecks/bluebook/attribute.rb +12 -61
  50. data/lib/hecks/bluebook/behaviour/aggregate.rb +10 -39
  51. data/lib/hecks/bluebook/behaviour/attribute.rb +3 -14
  52. data/lib/hecks/bluebook/behaviour/chapter.rb +17 -42
  53. data/lib/hecks/bluebook/behaviour/command.rb +14 -66
  54. data/lib/hecks/bluebook/behaviour/domain_port.rb +4 -20
  55. data/lib/hecks/bluebook/behaviour/entity.rb +7 -27
  56. data/lib/hecks/bluebook/behaviour/hexagon.rb +23 -41
  57. data/lib/hecks/bluebook/behaviour/lifecycle.rb +15 -40
  58. data/lib/hecks/bluebook/behaviour/policy.rb +23 -52
  59. data/lib/hecks/bluebook/behaviour/process_manager.rb +30 -57
  60. data/lib/hecks/bluebook/behaviour/read_model.rb +38 -26
  61. data/lib/hecks/bluebook/behaviour/traits.rb +27 -64
  62. data/lib/hecks/bluebook/behaviour/value_object.rb +5 -24
  63. data/lib/hecks/bluebook/capabilities.rb +17 -49
  64. data/lib/hecks/bluebook/chapter.rb +10 -48
  65. data/lib/hecks/bluebook/command.rb +23 -81
  66. data/lib/hecks/bluebook/domain_port.rb +14 -63
  67. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +50 -229
  68. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +50 -377
  69. data/lib/hecks/bluebook/dsl/attribute_collector.rb +33 -204
  70. data/lib/hecks/bluebook/dsl/binding_proxy.rb +17 -65
  71. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +86 -708
  72. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +56 -170
  73. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
  74. data/lib/hecks/bluebook/dsl/command_builder.rb +74 -546
  75. data/lib/hecks/bluebook/dsl/const_shim.rb +8 -41
  76. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +12 -87
  77. data/lib/hecks/bluebook/dsl/entity_builder.rb +44 -330
  78. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +20 -269
  79. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +26 -189
  80. data/lib/hecks/bluebook/dsl/identity_declaration.rb +13 -88
  81. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +6 -20
  82. data/lib/hecks/bluebook/dsl/policy_builder.rb +16 -88
  83. data/lib/hecks/bluebook/dsl/port_builder.rb +2 -6
  84. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +22 -96
  85. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +31 -184
  86. data/lib/hecks/bluebook/dsl/query_builder.rb +11 -42
  87. data/lib/hecks/bluebook/dsl/read_model_builder.rb +75 -201
  88. data/lib/hecks/bluebook/dsl/rule_reference.rb +22 -163
  89. data/lib/hecks/bluebook/dsl/translation_builder.rb +28 -114
  90. data/lib/hecks/bluebook/dsl/value_object_builder.rb +10 -124
  91. data/lib/hecks/bluebook/dsl/word_gate.rb +16 -162
  92. data/lib/hecks/bluebook/dsl/world_builder.rb +19 -106
  93. data/lib/hecks/bluebook/entity.rb +13 -71
  94. data/lib/hecks/bluebook/expression/ast_json.rb +24 -190
  95. data/lib/hecks/bluebook/expression/ast_reader.rb +12 -54
  96. data/lib/hecks/bluebook/expression/canonical_form.rb +11 -88
  97. data/lib/hecks/bluebook/expression/evaluator.rb +47 -295
  98. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +30 -147
  99. data/lib/hecks/bluebook/expression/resolver.rb +37 -660
  100. data/lib/hecks/bluebook/hexagon.rb +5 -28
  101. data/lib/hecks/bluebook/lifecycle.rb +5 -16
  102. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -7
  103. data/lib/hecks/bluebook/meta_validator/judge.rb +111 -361
  104. data/lib/hecks/bluebook/meta_validator/plan.rb +33 -162
  105. data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -7
  106. data/lib/hecks/bluebook/meta_validator/readings.rb +56 -342
  107. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -176
  108. data/lib/hecks/bluebook/meta_validator/shapes.rb +54 -268
  109. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +47 -308
  110. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +7 -43
  111. data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -18
  112. data/lib/hecks/bluebook/meta_validator.rb +79 -349
  113. data/lib/hecks/bluebook/model_check/client_profile.rb +10 -91
  114. data/lib/hecks/bluebook/model_check.rb +102 -600
  115. data/lib/hecks/bluebook/pattern_subset.rb +11 -69
  116. data/lib/hecks/bluebook/process_manager.rb +13 -83
  117. data/lib/hecks/bluebook/project_loader.rb +3 -39
  118. data/lib/hecks/bluebook/project_register.rb +12 -60
  119. data/lib/hecks/bluebook/query.rb +9 -48
  120. data/lib/hecks/bluebook/read_model.rb +57 -81
  121. data/lib/hecks/bluebook/reference.rb +7 -41
  122. data/lib/hecks/bluebook/smoke_test.rb +8 -98
  123. data/lib/hecks/bluebook/synthesizer.rb +13 -62
  124. data/lib/hecks/bluebook/translation.rb +26 -62
  125. data/lib/hecks/bluebook/value_object.rb +11 -36
  126. data/lib/hecks/bluebook.rb +5 -22
  127. data/lib/hecks/cache_dir.rb +84 -0
  128. data/lib/hecks/cli/document.rb +86 -0
  129. data/lib/hecks/cli/ir.rb +45 -0
  130. data/lib/hecks/cli/mcp.rb +31 -0
  131. data/lib/hecks/cli/mcp_door.rb +366 -0
  132. data/lib/hecks/cli/model_check.rb +203 -0
  133. data/lib/hecks/cli/project_cli.rb +90 -0
  134. data/lib/hecks/cli/project_diagrams.rb +36 -0
  135. data/lib/hecks/cli/run.rb +173 -0
  136. data/lib/hecks/cli/smoke_test.rb +45 -0
  137. data/lib/hecks/cli/stores.rb +53 -0
  138. data/lib/hecks/cli.rb +160 -0
  139. data/lib/hecks/construct.rb +8 -34
  140. data/lib/hecks/deploy/bluebook/deploy.bluebook +33 -142
  141. data/lib/hecks/deploy/bluebook/deploy.hecksagon +2 -9
  142. data/lib/hecks/embryonaut_bluebook/lock.rb +3 -35
  143. data/lib/hecks/embryonaut_bluebook/shape.rb +1 -17
  144. data/lib/hecks/embryonaut_bluebook/vendor.rb +8 -53
  145. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +4 -19
  146. data/lib/hecks/embryonaut_bluebook.rb +4 -71
  147. data/lib/hecks/facade/cli_door.rb +24 -106
  148. data/lib/hecks/facade/cli_runner.rb +28 -172
  149. data/lib/hecks/facade/command_request.rb +13 -33
  150. data/lib/hecks/facade/handle.rb +34 -121
  151. data/lib/hecks/facade/json_door.rb +31 -146
  152. data/lib/hecks/facade/surface/aggregate_door.rb +39 -104
  153. data/lib/hecks/facade/surface/chapter.rb +10 -54
  154. data/lib/hecks/facade/surface.rb +8 -23
  155. data/lib/hecks/facade.rb +10 -25
  156. data/lib/hecks/forms/app.rb +15 -70
  157. data/lib/hecks/forms/command_form_renderer.rb +10 -29
  158. data/lib/hecks/forms/field_renderer.rb +17 -156
  159. data/lib/hecks/forms/field_shape.rb +30 -222
  160. data/lib/hecks/forms/html.rb +12 -47
  161. data/lib/hecks/forms/index_renderer.rb +1 -4
  162. data/lib/hecks/forms/page.rb +3 -9
  163. data/lib/hecks/forms/params.rb +14 -60
  164. data/lib/hecks/forms/port_argument.rb +2 -19
  165. data/lib/hecks/forms/query_form_renderer.rb +4 -79
  166. data/lib/hecks/forms/record_renderer.rb +8 -75
  167. data/lib/hecks/forms/record_table.rb +7 -50
  168. data/lib/hecks/forms/reference_options.rb +3 -30
  169. data/lib/hecks/forms/value_object_shape.rb +8 -35
  170. data/lib/hecks/forms.rb +5 -33
  171. data/lib/hecks/fqn.rb +5 -12
  172. data/lib/hecks/framework/bluebook/compliance.bluebook +16 -65
  173. data/lib/hecks/framework/bluebook/console_settings.bluebook +47 -134
  174. data/lib/hecks/framework/bluebook/framework.hecksagon +5 -14
  175. data/lib/hecks/framework/bluebook/governance.bluebook +13 -34
  176. data/lib/hecks/framework/bluebook/identity.bluebook +11 -26
  177. data/lib/hecks/framework/bluebook/privacy.bluebook +8 -33
  178. data/lib/hecks/framework.rb +11 -80
  179. data/lib/hecks/freezer.rb +5 -39
  180. data/lib/hecks/grammar/expression.bluebook +15 -40
  181. data/lib/hecks/grammar/translation.bluebook +3 -6
  182. data/lib/hecks/grammar.rb +16 -41
  183. data/lib/hecks/ir.rb +14 -83
  184. data/lib/hecks/language/adapter.bluebook +2 -7
  185. data/lib/hecks/language/bluebook/aggregate.bluebook +56 -221
  186. data/lib/hecks/language/bluebook/attaches/paging.bluebook +6 -15
  187. data/lib/hecks/language/bluebook/bluebook.bluebook +15 -83
  188. data/lib/hecks/language/bluebook/bluebook.hecksagon +3 -15
  189. data/lib/hecks/language/bluebook/command.bluebook +27 -144
  190. data/lib/hecks/language/bluebook/entity.bluebook +24 -97
  191. data/lib/hecks/language/bluebook/policy.bluebook +16 -67
  192. data/lib/hecks/language/bluebook/process_manager.bluebook +28 -151
  193. data/lib/hecks/language/bluebook/projection.bluebook +126 -60
  194. data/lib/hecks/language/bluebook/query.bluebook +12 -43
  195. data/lib/hecks/language/bluebook/shape.bluebook +23 -73
  196. data/lib/hecks/language/bluebook/syntax.bluebook +51 -262
  197. data/lib/hecks/language/bluebook/vocabulary.bluebook +98 -197
  198. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +3 -10
  199. data/lib/hecks/language/hecksagon/domain_port.bluebook +4 -19
  200. data/lib/hecks/language/hecksagon/hecksagon.bluebook +6 -25
  201. data/lib/hecks/language/hecksagon/port_operation.bluebook +3 -14
  202. data/lib/hecks/language/oidc.json +35 -0
  203. data/lib/hecks/language/port.bluebook +4 -17
  204. data/lib/hecks/language/translation/translation.bluebook +3 -9
  205. data/lib/hecks/language/translation/translation_aggregate.bluebook +5 -24
  206. data/lib/hecks/language/world/wiring.bluebook +1 -3
  207. data/lib/hecks/language/world/world.bluebook +5 -7
  208. data/lib/hecks/literal.rb +8 -50
  209. data/lib/hecks/mcp_door_scope.rb +95 -0
  210. data/lib/hecks/mcp_stdio_guard.rb +4 -41
  211. data/lib/hecks/naming.rb +29 -137
  212. data/lib/hecks/ports/access_control.rb +7 -23
  213. data/lib/hecks/ports/agent/answers.rb +5 -23
  214. data/lib/hecks/ports/agent.rb +47 -172
  215. data/lib/hecks/ports/authentication.rb +12 -39
  216. data/lib/hecks/ports/authorization.rb +16 -43
  217. data/lib/hecks/ports/clock.rb +5 -45
  218. data/lib/hecks/ports/extraction.rb +7 -12
  219. data/lib/hecks/ports/identity_assignment.rb +8 -29
  220. data/lib/hecks/ports/identity_generation.rb +6 -29
  221. data/lib/hecks/ports/identity_resolution.rb +2 -10
  222. data/lib/hecks/ports/key_vault.rb +9 -45
  223. data/lib/hecks/ports/persistence/append_only.rb +62 -65
  224. data/lib/hecks/ports/persistence/binding_policy.rb +4 -14
  225. data/lib/hecks/ports/persistence/codec_boundary.rb +20 -80
  226. data/lib/hecks/ports/persistence/null_saga_store.rb +4 -20
  227. data/lib/hecks/ports/persistence/plugin.rb +13 -42
  228. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +38 -141
  229. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +15 -115
  230. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +16 -84
  231. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +7 -26
  232. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -22
  233. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +55 -181
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/events.rb +77 -0
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +12 -122
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +28 -128
  237. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +73 -474
  238. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +31 -124
  239. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +45 -219
  240. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +13 -108
  241. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +18 -75
  242. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +9 -49
  243. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +6 -123
  244. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +23 -55
  245. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +14 -72
  246. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +7 -16
  247. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +14 -115
  248. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +13 -45
  249. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +127 -554
  250. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +13 -63
  251. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +2 -17
  252. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +3 -14
  253. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +13 -58
  254. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -17
  255. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -26
  256. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +9 -25
  257. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +22 -122
  258. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +34 -123
  259. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +5 -12
  260. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +3 -9
  261. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -14
  262. data/lib/hecks/ports/persistence/plugins/era.rb +4 -23
  263. data/lib/hecks/ports/persistence/postgres_dump/connection.rb +7 -23
  264. data/lib/hecks/ports/persistence/postgres_dump.rb +4 -35
  265. data/lib/hecks/ports/persistence/remote_runtime.rb +2 -24
  266. data/lib/hecks/ports/persistence/repository_factory.rb +8 -27
  267. data/lib/hecks/ports/persistence/state_codec.rb +23 -103
  268. data/lib/hecks/ports/persistence.rb +7 -14
  269. data/lib/hecks/ports/projection.rb +42 -84
  270. data/lib/hecks/ports/query/in_memory.rb +10 -58
  271. data/lib/hecks/ports/query/ordering.rb +11 -32
  272. data/lib/hecks/ports/query.rb +14 -37
  273. data/lib/hecks/ports.rb +2 -8
  274. data/lib/hecks/projections/bootstrap_table.rb +11 -43
  275. data/lib/hecks/projections/deploy/fargate/assembly.rb +2 -15
  276. data/lib/hecks/projections/deploy/fargate/cdn.rb +1 -48
  277. data/lib/hecks/projections/deploy/fargate/cdn_default.rb +2 -11
  278. data/lib/hecks/projections/deploy/fargate/check.rb +5 -14
  279. data/lib/hecks/projections/deploy/fargate/containers.rb +3 -78
  280. data/lib/hecks/projections/deploy/fargate/extras.rb +3 -44
  281. data/lib/hecks/projections/deploy/fargate/monitoring.rb +0 -44
  282. data/lib/hecks/projections/deploy/fargate/settings.rb +2 -54
  283. data/lib/hecks/projections/deploy/fargate/yaml.rb +13 -58
  284. data/lib/hecks/projections/deploy/fargate.rb +35 -202
  285. data/lib/hecks/projections/deploy/lambda.rb +151 -679
  286. data/lib/hecks/projections/deploy/preview/access_yaml.rb +1 -33
  287. data/lib/hecks/projections/deploy/preview/containers.rb +6 -111
  288. data/lib/hecks/projections/deploy/preview/routing_yaml.rb +4 -56
  289. data/lib/hecks/projections/deploy/preview/script.rb +2 -14
  290. data/lib/hecks/projections/deploy/preview/settings.rb +3 -33
  291. data/lib/hecks/projections/deploy/preview/task_yaml.rb +2 -38
  292. data/lib/hecks/projections/deploy/preview/template.rb +2 -58
  293. data/lib/hecks/projections/deploy/preview.rb +2 -67
  294. data/lib/hecks/projections/deploy/scripts/settings.rb +3 -25
  295. data/lib/hecks/projections/deploy/scripts.rb +4 -57
  296. data/lib/hecks/projections/deploy/shared.rb +47 -146
  297. data/lib/hecks/projections/deploy/smoke.rb +4 -61
  298. data/lib/hecks/projections/deploy/template_diff/comparer.rb +3 -25
  299. data/lib/hecks/projections/deploy/template_diff/loader.rb +2 -11
  300. data/lib/hecks/projections/deploy/template_diff.rb +5 -57
  301. data/lib/hecks/projections/diagrams.rb +20 -582
  302. data/lib/hecks/projections/glossary/html.rb +14 -130
  303. data/lib/hecks/projections/glossary/markdown.rb +8 -69
  304. data/lib/hecks/projections/glossary/mermaid.rb +9 -66
  305. data/lib/hecks/projections/glossary/sensitivity.rb +5 -34
  306. data/lib/hecks/projections/glossary/sentences.rb +14 -159
  307. data/lib/hecks/projections/glossary.rb +30 -222
  308. data/lib/hecks/projections/ir.rb +3 -10
  309. data/lib/hecks/projections/model/deviations.rb +15 -42
  310. data/lib/hecks/projections/model.rb +12 -51
  311. data/lib/hecks/projections/oidc.rb +12 -56
  312. data/lib/hecks/projections/parser_table.rb +9 -28
  313. data/lib/hecks/projections/reference.rb +9 -16
  314. data/lib/hecks/projections/rust_vocabulary.rb +12 -250
  315. data/lib/hecks/projections/shape.rb +3 -29
  316. data/lib/hecks/projections/statements.rb +7 -86
  317. data/lib/hecks/projections/vocabulary.rb +6 -22
  318. data/lib/hecks/projections.rb +2 -16
  319. data/lib/hecks/projector/cli_projector.rb +60 -375
  320. data/lib/hecks/projector/docs_projector.rb +10 -201
  321. data/lib/hecks/projector/exporter.rb +46 -138
  322. data/lib/hecks/projector/ir_projector.rb +2 -9
  323. data/lib/hecks/projector/narrate_projector.rb +15 -189
  324. data/lib/hecks/projector/target.rb +13 -76
  325. data/lib/hecks/projector.rb +30 -121
  326. data/lib/hecks/query_specification/common/comparators.rb +5 -26
  327. data/lib/hecks/query_specification/common/comparison.rb +35 -143
  328. data/lib/hecks/query_specification/common/dsl.rb +8 -24
  329. data/lib/hecks/query_specification/common/null_policy.rb +20 -70
  330. data/lib/hecks/query_specification/common/options.rb +2 -6
  331. data/lib/hecks/query_specification/common/where_clause.rb +2 -10
  332. data/lib/hecks/query_specification/field_path.rb +37 -77
  333. data/lib/hecks/query_specification/hop_path.rb +12 -76
  334. data/lib/hecks/release/gem_pin.rb +2 -26
  335. data/lib/hecks/release/runner/ci_publisher.rb +93 -0
  336. data/lib/hecks/release/runner/commands.rb +66 -0
  337. data/lib/hecks/release/runner/console.rb +52 -0
  338. data/lib/hecks/release/runner/gem_publisher.rb +43 -0
  339. data/lib/hecks/release/runner/git.rb +48 -0
  340. data/lib/hecks/release/runner/npm_publisher.rb +75 -0
  341. data/lib/hecks/release/runner/preflight.rb +104 -0
  342. data/lib/hecks/release/runner/published.rb +55 -0
  343. data/lib/hecks/release/runner/tagger.rb +80 -0
  344. data/lib/hecks/release/runner.rb +215 -0
  345. data/lib/hecks/rendering.rb +4 -26
  346. data/lib/hecks/router/namespace_installer.rb +12 -36
  347. data/lib/hecks/router.rb +4 -8
  348. data/lib/hecks/runtime/aggregate_lock.rb +8 -33
  349. data/lib/hecks/runtime/boot_gates.rb +3 -18
  350. data/lib/hecks/runtime/caller.rb +19 -59
  351. data/lib/hecks/runtime/capability_graph.rb +5 -21
  352. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
  353. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +23 -160
  354. data/lib/hecks/runtime/command_interpreter.rb +125 -307
  355. data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
  356. data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
  357. data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
  358. data/lib/hecks/runtime/command_rules/emission.rb +8 -21
  359. data/lib/hecks/runtime/command_rules/references.rb +23 -202
  360. data/lib/hecks/runtime/dependency_planning.rb +30 -102
  361. data/lib/hecks/runtime/dispatcher.rb +82 -241
  362. data/lib/hecks/runtime/entity_element.rb +40 -462
  363. data/lib/hecks/runtime/entity_interpreter.rb +65 -220
  364. data/lib/hecks/runtime/errors.rb +32 -115
  365. data/lib/hecks/runtime/event.rb +3 -25
  366. data/lib/hecks/runtime/identity.rb +18 -154
  367. data/lib/hecks/runtime/instance.rb +18 -150
  368. data/lib/hecks/runtime/interpreting.rb +11 -70
  369. data/lib/hecks/runtime/invocation.rb +28 -164
  370. data/lib/hecks/runtime/loader.rb +48 -186
  371. data/lib/hecks/runtime/outbox.rb +38 -261
  372. data/lib/hecks/runtime/policy_interpreter.rb +38 -200
  373. data/lib/hecks/runtime/port_operation_interpreter.rb +26 -87
  374. data/lib/hecks/runtime/query_interpreter.rb +45 -187
  375. data/lib/hecks/runtime/reaction_invocation.rb +36 -138
  376. data/lib/hecks/runtime/read_model_interpreter.rb +139 -208
  377. data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
  378. data/lib/hecks/runtime/reference_hop.rb +8 -81
  379. data/lib/hecks/runtime/refusal_wording.rb +8 -89
  380. data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
  381. data/lib/hecks/runtime/registry/verification.rb +52 -225
  382. data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
  383. data/lib/hecks/runtime/registry.rb +103 -244
  384. data/lib/hecks/runtime/remote_dispatcher.rb +37 -152
  385. data/lib/hecks/runtime/routing.rb +4 -15
  386. data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
  387. data/lib/hecks/runtime/saga_interpreter.rb +79 -263
  388. data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
  389. data/lib/hecks/runtime/tenant_check.rb +6 -68
  390. data/lib/hecks/runtime/tenant_scope.rb +3 -28
  391. data/lib/hecks/runtime/value/admission.rb +16 -67
  392. data/lib/hecks/runtime/value/coercion.rb +155 -641
  393. data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
  394. data/lib/hecks/runtime/value.rb +12 -84
  395. data/lib/hecks/runtime.rb +17 -65
  396. data/lib/hecks/storehouse.rb +146 -567
  397. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +8 -32
  398. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
  399. data/lib/hecks/vendoring/git_environment.rb +2 -8
  400. data/lib/hecks/vendoring/git_source.rb +4 -10
  401. data/lib/hecks/vendoring.rb +7 -46
  402. data/lib/hecks/version.rb +3 -14
  403. data/lib/hecks/vocabulary.rb +5 -0
  404. data/lib/hecks.rb +32 -82
  405. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
  406. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
  407. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
  408. metadata +30 -59
  409. data/lib/hecks/bench/cli.rb +0 -74
  410. data/lib/hecks/bench/environment.rb +0 -100
  411. data/lib/hecks/bench/postgres_probe.rb +0 -41
  412. data/lib/hecks/bench/report.rb +0 -116
  413. data/lib/hecks/bench/ruby_runner.rb +0 -151
  414. data/lib/hecks/bench/run.rb +0 -55
  415. data/lib/hecks/bench/rust_runner.rb +0 -165
  416. data/lib/hecks/bench/stats.rb +0 -75
  417. data/lib/hecks/bench/suite.rb +0 -172
  418. data/lib/hecks/bench/workload.rb +0 -137
  419. data/lib/hecks/bench.rb +0 -28
  420. data/lib/hecks/codemod.rb +0 -443
  421. data/lib/hecks/corpus.rb +0 -479
  422. data/lib/hecks/doc/reference.rb +0 -585
  423. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -684
  424. data/lib/hecks/fuzzing/combination_miner.rb +0 -178
  425. data/lib/hecks/fuzzing/concurrent_dispatch.rb +0 -469
  426. data/lib/hecks/fuzzing/coverage_campaign.rb +0 -161
  427. data/lib/hecks/fuzzing/differential.rb +0 -192
  428. data/lib/hecks/fuzzing/domain_generator.rb +0 -871
  429. data/lib/hecks/fuzzing/era_boundary.rb +0 -154
  430. data/lib/hecks/fuzzing/form_census.rb +0 -285
  431. data/lib/hecks/fuzzing/generated_domain_check.rb +0 -171
  432. data/lib/hecks/fuzzing/invalid_value_generator.rb +0 -125
  433. data/lib/hecks/fuzzing/isolated_boot.rb +0 -658
  434. data/lib/hecks/fuzzing/nondeterministic.rb +0 -79
  435. data/lib/hecks/fuzzing/persistence_parity.rb +0 -253
  436. data/lib/hecks/fuzzing/properties/corrections.rb +0 -125
  437. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +0 -688
  438. data/lib/hecks/fuzzing/properties/guards.rb +0 -398
  439. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +0 -399
  440. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +0 -137
  441. data/lib/hecks/fuzzing/properties/outbox.rb +0 -180
  442. data/lib/hecks/fuzzing/properties/querying.rb +0 -260
  443. data/lib/hecks/fuzzing/properties.rb +0 -267
  444. data/lib/hecks/fuzzing/qa_settings.rb +0 -164
  445. data/lib/hecks/fuzzing/replay.rb +0 -930
  446. data/lib/hecks/fuzzing/rotation_priority.rb +0 -114
  447. data/lib/hecks/fuzzing/rust_gap_manifest.rb +0 -139
  448. data/lib/hecks/fuzzing/self_consistency.rb +0 -825
  449. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +0 -532
  450. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +0 -171
  451. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +0 -127
  452. data/lib/hecks/fuzzing/sequence_generator/picker.rb +0 -116
  453. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +0 -138
  454. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +0 -344
  455. data/lib/hecks/fuzzing/sequence_generator.rb +0 -311
  456. data/lib/hecks/fuzzing/shrinker.rb +0 -309
  457. data/lib/hecks/fuzzing/structural_skips.rb +0 -53
  458. data/lib/hecks/fuzzing/sweep_depth.rb +0 -61
  459. data/lib/hecks/fuzzing/target_capabilities.rb +0 -255
  460. data/lib/hecks/fuzzing/value_generator.rb +0 -340
  461. data/lib/hecks/fuzzing.rb +0 -23
  462. data/lib/hecks/grammar/evolve.rb +0 -524
  463. data/lib/hecks/query_ir.rb +0 -482
@@ -4,18 +4,8 @@ require_relative "../ports/authorization"
4
4
 
5
5
  module Hecks
6
6
  module Facade
7
- # One record in hand — the object `Pizza.create_pizza!(...)` and
8
- # `Pizza.find(id)` give back.
9
- #
10
- # One shared class, not one minted per aggregate. Rather than subclassing
11
- # per head and defining a reader per field, this wraps a
12
- # `Runtime::Instance` state hash, answers readers through
13
- # `method_missing` and verbs through per-handle singleton methods, and
14
- # closes over the dispatcher and the aggregate's IR — so a boot mints no
15
- # classes at all, and two boots in one process each hand out handles bound
16
- # to their own dispatcher.
17
- #
18
- # A non-creating verb is a method returning self, so commands chain :
7
+ # A record in hand: what `Pizza.create_pizza!(...)` and `Pizza.find(id)`
8
+ # give back. One shared class per aggregate; verbs are per-handle singleton methods.
19
9
  #
20
10
  # Pizza.create_pizza!(...).add_topping!(...).purchase!(...)
21
11
  class Handle
@@ -45,22 +35,10 @@ module Hecks
45
35
  # or a referenced record's id); `nil` when the field is unset or not in state
46
36
  def [](key) = redacted(key.to_sym)
47
37
 
48
- # Answers the record's state as a plain Hash with the bare identity under `:id`.
49
- #
50
- # `id: @id` last, not first — an aggregate is free to declare its own
51
- # attribute literally named `id` (BurningManPrep's `Item`, `attribute
52
- # :id, ItemId`, is real corpus now: `identified_by :id` reads
53
- # that attribute for identity). When it does, `@state[:id]` holds the
54
- # full wrapped value object, not the bare identity string — merging
55
- # `@state` on top of `{ id: @id }` would let that wrapped VO silently
56
- # clobber the correct bare `@id`, so every caller of `to_h` (the JSON
57
- # door's own `/api/:coll` listing, in particular) would get an object
58
- # where a plain identity string belongs. `@id` merged last always wins,
59
- # so `to_h[:id]` is always the true bare identity, regardless of
60
- # whether the aggregate also happens to declare a same-named field.
38
+ # Answers the record's state as a Hash, with `:id` merged in last so it
39
+ # always wins over a same-named `id` attribute (BurningManPrep::Item).
61
40
  #
62
- # @return [Hash{Symbol => Object}] a new Hash of every state field by attribute
63
- # name, plus `:id` holding the identity String
41
+ # @return [Hash{Symbol => Object}] every state field by attribute name, plus `:id`
64
42
  def to_h = @state.to_h { |key, _| [key, redacted(key)] }.merge(id: @id)
65
43
 
66
44
  # Names the aggregate this record belongs to, in the form every dispatch verb and
@@ -90,14 +68,11 @@ module Hecks
90
68
  self
91
69
  end
92
70
 
93
- # Equality is (which aggregate, which ID) — two handles to the same record
94
- # are the same record, and a Pizza never equals an Account that happens to
95
- # share an id. With one shared class for every aggregate,
96
- # `other.is_a?(self.class)` cannot tell them apart ; the fqn says it in data.
71
+ # Equality is (fqn, id): two handles to the same record are equal, and
72
+ # `other.is_a?(self.class)` can't tell aggregates apart since they share one class.
97
73
  #
98
74
  # @param other [Object] anything; only another `Handle` can be equal
99
- # @return [Boolean] true when `other` is a `Handle` with the same `fqn` and `id`,
100
- # whatever state either one holds
75
+ # @return [Boolean] true when `other` is a `Handle` with the same `fqn` and `id`
101
76
  def ==(other) = other.is_a?(Handle) && other.fqn == fqn && other.id == @id
102
77
  alias eql? ==
103
78
  def hash = [Handle, fqn, @id].hash
@@ -108,18 +83,13 @@ module Hecks
108
83
  end
109
84
  alias to_s inspect
110
85
 
111
- # Answers a field reader: `pizza.name` reads `name` out of state.
112
- #
113
- # A declared field not yet written arrives here too (nil, the way a
114
- # defined reader answers). Verbs are not handled here — see
115
- # `define_verb_methods` for why.
86
+ # Answers a field reader: `pizza.name` reads `name` out of state. A
87
+ # declared field not yet written arrives here too, as `nil`.
116
88
  #
117
89
  # @param name [Symbol] the method called, read as an attribute or lifecycle field name
118
90
  # @param args [Array<Object>] ignored by a reader; passed on to `super` otherwise
119
- # @param kwargs [Hash{Symbol => Object}] ignored by a reader; passed on to `super`
120
- # otherwise
121
- # @return [Object, nil] the field's value; `nil` for a declared field with nothing
122
- # written yet
91
+ # @param kwargs [Hash{Symbol => Object}] ignored by a reader; passed on to `super` otherwise
92
+ # @return [Object, nil] the field's value; `nil` when nothing is written yet
123
93
  # @raise [NoMethodError] if `name` is neither a key in state nor a declared field
124
94
  def method_missing(name, *args, **kwargs, &)
125
95
  return redacted(name) if @state.key?(name) || reader?(name)
@@ -135,28 +105,9 @@ module Hecks
135
105
 
136
106
  def repository = @dispatcher.registry.repository(@domain, @ir)
137
107
 
138
- # One field's value, with any Privacy::Marking-flagged leaf masked
139
- # out unless the ambient caller holds a live Governance grant of
140
- # the marking's own `readable_by` — the read-side half of the
141
- # Privacy framework member (lib/hecks/framework/bluebook/
142
- # privacy.bluebook): a marking's presence is what makes a read
143
- # redacted, not a separate flag this class carries itself.
144
- #
145
- # ALWAYS THE STRONG CHECK, never the weak string-only fallback
146
- # `CommandRules::Authorization#refuse_role_mismatch` allows an
147
- # unidentified caller — a read gate gone wrong is a leak, not a
148
- # refused command, so an ambient caller with no `actor_id` (or no
149
- # caller at all) is masked here, full stop, rather than waved
150
- # through the way a self-asserted `role` string is for a command.
151
- #
152
- # ONE LEVEL OF NESTING ONLY — `attendee.medications` masks inside
153
- # the returned `Runtime::Value` via its own `#with`; a marking two
154
- # levels deep is not supported and is left unmasked rather than
155
- # silently mishandled, since nothing in this corpus needs it yet.
156
- #
157
- # @param field [Symbol] the state key being read
158
- # @return [Object, nil] `@state[field]`, or a copy with the marked leaf replaced by
159
- # the literal String `"[redacted]"` when the caller is not authorized to see it
108
+ # Masks any Privacy::Marking leaf the caller isn't granted to read, and
109
+ # masks an unidentified caller outright (a leak is worse than a refusal
110
+ # commands allow). Nesting is masked one level deep only.
160
111
  def redacted(field)
161
112
  raw = @state[field]
162
113
  rows = marked_paths.select { |row| row[:attribute_path][:value].to_s.split(".", 2).first == field.to_s }
@@ -177,12 +128,8 @@ module Hecks
177
128
  raw
178
129
  end
179
130
 
180
- # Every Privacy::Marking declared for this record's own aggregate — `[]` when the
181
- # Privacy framework member is not attached in this boot at all, checked once and
182
- # cheaply rather than paying for a query dispatch every read on a domain that never
183
- # attached Privacy.
184
- #
185
- # @return [Array<Hash>] `Privacy::Marking.ForDomain`'s own rows for this `fqn`
131
+ # Cached per handle so an unattached-Privacy domain never pays for a query
132
+ # dispatch on every read.
186
133
  def marked_paths
187
134
  return @marked_paths if defined?(@marked_paths)
188
135
  return @marked_paths = [] unless @dispatcher.registry.bluebook("Privacy")
@@ -190,13 +137,8 @@ module Hecks
190
137
  @marked_paths = @dispatcher.query("Privacy::Marking.ForDomain", domain: fqn)
191
138
  end
192
139
 
193
- # Whether the ambient caller holds a live Governance grant of `role`, over this
194
- # record's own domain — `false` outright for an unidentified caller or a domain
195
- # with no authorization provider attached, never the weak fallback a command's own
196
- # role check allows (see `redacted`'s own header for why).
197
- #
198
- # @param role [String] the marking's own `readable_by`
199
- # @return [Boolean] true only for an identified caller holding a live grant
140
+ # False outright for an unidentified caller or an unattached domain, never
141
+ # the weak fallback a command's own role check allows.
200
142
  def authorized_for?(role)
201
143
  caller = Runtime::Caller.current
202
144
  return false unless caller&.actor_id
@@ -210,20 +152,9 @@ module Hecks
210
152
  !@ir.attribute(name).nil? || @ir.lifecycle&.field&.to_sym == name
211
153
  end
212
154
 
213
- # Non-creating verbs are defined, not dispatched through method_missing.
214
- #
215
- # method_missing only runs once Ruby finds no real method already
216
- # answering the name — and every object already answers `freeze` and
217
- # `send` (Kernel/Object), among others. A verb whose snake-cased name
218
- # collided with one of those — `Account::Freeze` -> `freeze`,
219
- # `ExternalTransfer::Send` -> `send` in the banking corpus, both real —
220
- # would silently run the Kernel method instead of dispatching: no
221
- # error, no refusal, the call just does the wrong thing. Defining a
222
- # real singleton method per verb closes that; the `!` suffix (every
223
- # command, door and Handle alike) closes it a second, permanent way —
224
- # `freeze!`/`send!` name nothing Kernel/Object already answers to,
225
- # so this exact class of collision cannot recur no matter what a
226
- # future domain names a command.
155
+ # Real singleton methods, not method_missing: a verb named `freeze` or
156
+ # `send` (both real, e.g. Account::Freeze, ExternalTransfer::Send) would
157
+ # otherwise silently hit the Kernel method instead of dispatching.
227
158
  def define_verb_methods
228
159
  @ir.commands.reject(&:creates?).each do |command|
229
160
  define_singleton_method("#{Naming.snake(command.hecks_name)}!") do |**args|
@@ -232,42 +163,24 @@ module Hecks
232
163
  end
233
164
  end
234
165
 
235
- # One head addresses the same way as several. `@ir.identified_by` is only
236
- # the single-head shorthand — nil the moment an identity is composite
237
- # (`SafeDepositBox`'s `branch_code`/`box_number`) — so building the
238
- # identity payload from `identity_heads` instead reads every head, one
239
- # or many alike, straight out of state that already carries them.
166
+ # `@ir.identified_by` is only the single-head shorthand (nil for a
167
+ # composite identity, e.g. SafeDepositBox's branch_code/box_number);
168
+ # this reads every head through `identity_heads` instead.
240
169
  def run(command, **args)
241
170
  @state = @dispatcher.dispatch("#{fqn}.#{command.hecks_name}", to: @id, with: args).instance.state
242
171
  self
243
172
  end
244
173
 
245
- # **The other half of a cross-reference**. `transfer.source` already reads
246
- # the raw value — a plain reader, same as any other attribute, still
247
- # needed by a `given`. This is the hydrated hop docs/rails-integration.md
248
- # designed and marked "nothing built": `transfer.source_account`
249
- # resolves it to the actual Account record, on demand — nothing loads
250
- # until called, and this hop never triggers the next one. Plain
251
- # chaining composes for free from here : `payment.disputed_by_customer.name`
252
- # is two ordinary calls, each individually lazy, which is exactly why
253
- # this is a named accessor per reference rather than a `through:`
254
- # option — that shape was considered and rejected in the same design
255
- # note for hiding how many lookups actually happened behind one call.
174
+ # Lazy hop: `transfer.source_account` resolves to the actual record only
175
+ # when called; nothing loads until then, so chains like
176
+ # `payment.disputed_by_customer.name` stay lazy at every step.
177
+ #
178
+ # Defined before verb methods so a same-named verb wins the name on the
179
+ # rare collision; `initialize` calls this first for that reason.
256
180
  #
257
- # Defined before verb methods, not after — on the vanishing chance a
258
- # reference's own accessor name collided with a command's, the verb
259
- # should win; `initialize` calls this first so `define_verb_methods`
260
- # defines second and last.
261
- # No derivation left (ADR 0025, "References"): `reference_to`
262
- # itself mints the bare attribute name now — `:account`, never
263
- # `:account_id` — so the accessor is spelled exactly like the
264
- # attribute it reads, with no `_id`-strip or `as:`-suffix rule to
265
- # apply first. `piece.account` (a method, defined here) and
266
- # `piece[:account]` (`Handle#[]`, bracket access reading the raw
267
- # id straight off `@instance`) never collide despite sharing a
268
- # name — Ruby dispatches the two completely differently — so no
269
- # renamed accessor (a "studio_studio"-style double suffix) is needed
270
- # to keep them apart.
181
+ # The accessor is named like the attribute itself, never `<name>_id`
182
+ # (ADR 0025) — `piece.account` and `piece[:account]` (bracket access,
183
+ # the raw id) never collide despite sharing a name.
271
184
  def define_reference_accessors
272
185
  @ir.attributes.select(&:reference?).each do |attribute|
273
186
  target = attribute.type.resolve
@@ -7,73 +7,23 @@ require_relative "../runtime/value"
7
7
 
8
8
  module Hecks
9
9
  module Facade
10
- # **The JSON door** — where Facade meets body-in/body-out callers.
11
- #
12
- # `Handle`/`Surface` are Ruby sugar over the dispatcher for a Ruby caller
13
- # holding real objects — a symbol verb name, a `**kwargs` payload, a
14
- # `Handle` back in hand. A REST-ish JSON API is a caller holding strings
15
- # instead: a URL segment naming a collection, a URL segment naming a
16
- # record or a verb, a parsed request body whose every key arrived as a
17
- # String because that is all JSON ever gives. Every app that wants to put
18
- # a JSON API in front of a booted domain has to do that translation —
19
- # name to door module, string to symbol, nested `Runtime::Value` back to
20
- # plain data. This is that translation written once, generic, reading the
21
- # same IR the rest of the facade already reads rather than each app
22
- # re-deriving "how do I find an aggregate by name" against its own routes.
23
- #
24
- # ## No HTTP lives here
25
- #
26
- # Same discipline `Router` and `Surface` already
27
- # hold: this module never sees a request object, never picks a status
28
- # code, never calls `halt`. Every method here takes plain Ruby values in
29
- # — a raw JSON String is the one exception, see `.parse` below, every
30
- # other input is already-parsed data — and returns plain Ruby values
31
- # out, or raises. Turning a raw request body into that input, and
32
- # turning a raised exception into an HTTP status, both stay the calling
33
- # app's job, exactly the way they already are for `Router#dispatch`.
34
- #
35
- # ## One refusal class for every miss
36
- #
37
- # Every "that doesn't exist" case raises `Runtime::NotFound` — the same
38
- # class every time, not a new one per caller. `Runtime::NotFound` already
39
- # sits in `Runtime::DOMAIN_REFUSALS` (runtime/errors.rb) — the family a
40
- # booted app already has, or trivially can have, one generic `error`
41
- # handler for, mapping the whole family to a status code without a
42
- # special case per refusal. A bespoke `JsonDoor::CollectionNotFound` (or
43
- # three of those, one per flavor of "not found") would just force every
44
- # app that adopts this door to widen its rescue clause to match it — the
45
- # opposite of what a shared refusal family is for. So "no such
46
- # aggregate", "no creating command", "no such command", and "no record
47
- # with that id" all raise the one class, distinguished only by message.
10
+ # The JSON door: translates URL segments and parsed JSON bodies to and from the facade.
11
+ # No HTTP lives here; every miss raises `Runtime::NotFound`, distinguished by message.
48
12
  module JsonDoor
49
13
  module_function
50
14
 
51
15
  # Resolves a domain name and an aggregate name, as two URL segments carry them, to
52
16
  # that aggregate's door.
53
17
  #
54
- # "Banking", "Customer" -> the `Banking::Customer` door module `.find` /
55
- # `.create_...!` / etc already answer for — the same module
56
- # `Facade::Handle`'s own reference accessors reach with
57
- # `Object.const_get` (see handle.rb's `define_reference_accessors`).
18
+ # The name is checked against the current boot's IR before Ruby's constant table, so
19
+ # a stale same-named constant from an earlier boot is never returned.
58
20
  #
59
- # Checked against the current boot's IR first, not against Ruby's
60
- # constant table directly — a name that names nothing in this
61
- # registry should refuse before ever asking Ruby whether some
62
- # same-named constant happens to exist (possibly a stale one, left
63
- # over from an earlier boot in this same process — the exact hazard
64
- # `AggregateDoor#port`'s own comment describes at length). Only once
65
- # the IR confirms the aggregate is real does this read the constant
66
- # the current boot's `Surface.install` actually minted for it.
67
- #
68
- # @param dispatcher [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted
69
- # dispatcher whose registry decides whether the aggregate exists
21
+ # @param dispatcher [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted dispatcher
70
22
  # @param domain [String, Symbol] the chapter name, such as `"Banking"`
71
23
  # @param name [String, Symbol] the aggregate's declared name, such as `"Customer"`
72
24
  # @return [Module] the aggregate door installed at `domain::name`
73
- # @raise [Runtime::NotFound] if the registry holds no such chapter, or the chapter
74
- # declares no aggregate of that name
75
- # @raise [NameError] if the IR has the aggregate but no facade constant is
76
- # installed for it, as after a boot with `install_facade: false`
25
+ # @raise [Runtime::NotFound] if the chapter or its aggregate is not declared
26
+ # @raise [NameError] if the aggregate has no facade constant (`install_facade: false`)
77
27
  def aggregate(dispatcher, domain, name)
78
28
  ir = dispatcher.registry.bluebook(domain)&.aggregate(name)
79
29
  raise Runtime::NotFound, "#{domain} declares no aggregate named #{name.inspect}" unless ir
@@ -81,21 +31,11 @@ module Hecks
81
31
  Object.const_get("#{domain}::#{ir.hecks_name}")
82
32
  end
83
33
 
84
- # Names the door method that creates a record of this aggregate.
85
- #
86
- # The one command a POST to a bare collection URL means — "make one of
87
- # these". A creating command is one that declares no `references`
88
- # (`Command#creates?`), and the first one the aggregate declares is the
89
- # one named here; this just names it the same
90
- # snake_case-plus-bang a Ruby caller would already ask the door for
91
- # (`Naming.snake`, the identical call `AggregateDoor` itself makes
92
- # when it defines that singleton method in the first place — `!`
93
- # because every command carries it, door and Handle alike).
34
+ # Names the door method that creates a record of this aggregate: the first command
35
+ # it declares that `creates?`, spelled as a Ruby caller would (`"create_pizza!"`).
94
36
  #
95
- # @param klass [Module] an aggregate door, as `aggregate` returns; anything
96
- # answering `ir` with a `Bluebook::Aggregate` works
97
- # @return [String] the method name to `public_send` to the door, such as
98
- # `"create_pizza!"`
37
+ # @param klass [Module] an aggregate door, as `aggregate` returns
38
+ # @return [String] the method name to `public_send` to the door
99
39
  # @raise [Runtime::NotFound] if the aggregate declares no creating command
100
40
  def creating_command(klass)
101
41
  creating = klass.ir.commands.find(&:creates?)
@@ -107,28 +47,14 @@ module Hecks
107
47
  # Confirms that a command name arriving as text is one a `Handle` of this aggregate
108
48
  # answers, before a caller `public_send`s it.
109
49
  #
110
- # A URL segment or a JSON body's "command" field, checked against what
111
- # a `Handle` can actually dispatch — not `klass.commands`, which is
112
- # `AggregateDoor`'s own door-level list and includes the one creating
113
- # command too (`aggregate_door.rb`'s `commands` singleton method maps
114
- # every `ir.commands`, full stop). A `Handle` only ever defines
115
- # singleton methods for the non-creating ones
116
- # (`Handle#define_verb_methods`, `@ir.commands.reject(&:creates?)`) —
117
- # the creating command lives on the aggregate class itself, dispatched
118
- # through `.creating_command` above, not through a `Handle` in hand.
119
- # Accepting a creating-command name here would let it past this gate
120
- # clean, only to blow up as a raw `NoMethodError` the moment a caller
121
- # tries `handle.public_send(name, **args)`, instead of the 404 this
122
- # method promises. Filtering `reject(&:creates?)` here, the same filter
123
- # `Handle` itself applies, is what keeps "accepted here" and
124
- # "dispatchable there" the same set.
50
+ # A `Handle` defines only the non-creating commands, so the creating command is
51
+ # rejected here rather than failing later as a raw `NoMethodError`.
125
52
  #
126
53
  # @param klass [Module] an aggregate door, as `aggregate` returns
127
54
  # @param name [String, Symbol] the wanted method name with its bang, such as
128
55
  # `"add_topping!"`
129
56
  # @return [String] `name` as a String, unchanged, when a `Handle` answers it
130
- # @raise [Runtime::NotFound] if no non-creating command has that method name,
131
- # including when `name` is the creating command's or lacks the `!`
57
+ # @raise [Runtime::NotFound] if no non-creating command has that method name
132
58
  def validate_command!(klass, name)
133
59
  wanted = name.to_s
134
60
  dispatchable = klass.ir.commands.reject(&:creates?).map { |command| "#{Naming.snake(command.hecks_name)}!" }
@@ -137,14 +63,7 @@ module Hecks
137
63
  raise Runtime::NotFound, "#{klass.ir.hecks_name} declares no command named #{wanted.inspect}"
138
64
  end
139
65
 
140
- # Fetches one record by id, refusing a miss instead of answering nil.
141
- #
142
- # `klass.find` already answers nil-on-miss — the right shape for a
143
- # Ruby caller that means to check for itself. A JSON caller asking for
144
- # one record by id off a URL means to have it, or answer 404 — this is
145
- # that stricter wrapper, raising the same `Runtime::NotFound` the rest
146
- # of this door raises rather than handing back nil for the caller to
147
- # remember to check.
66
+ # Fetches one record by id, raising instead of answering nil on a miss.
148
67
  #
149
68
  # @param klass [Module] an aggregate door, as `aggregate` returns
150
69
  # @param id [String] the record's identity, as the URL carried it
@@ -156,11 +75,6 @@ module Hecks
156
75
 
157
76
  # Converts every Hash key to a Symbol, at every depth of a parsed JSON body.
158
77
  #
159
- # JSON only ever hands back String keys. A command's args, and every
160
- # nested value-object literal inside them, need symbol keys before
161
- # `Handle`/`Dispatcher` will accept them at all — this is that
162
- # recursive conversion, blind to how deep a body nests.
163
- #
164
78
  # @param value [Hash, Array, Object] parsed JSON: a Hash or Array is walked, any
165
79
  # other value is a leaf
166
80
  # @return [Hash{Symbol => Object}, Array, Object] a new structure of the same shape
@@ -174,72 +88,43 @@ module Hecks
174
88
  end
175
89
 
176
90
  # Unwraps a record, a query row, or any value holding `Runtime::Value`s into plain
177
- # Hashes, Arrays and scalars.
91
+ # Hashes, Arrays and scalars a JSON encoder can walk.
178
92
  #
179
- # The other direction: a `Handle`, or a query row's plain state hash,
180
- # carrying a `Runtime::Value` at every level a value object sits at —
181
- # down to plain Ruby a JSON encoder can walk without knowing what a
182
- # `Runtime::Value` is.
183
- #
184
- # `Runtime::Value.materialize` (runtime/value.rb) already does the
185
- # actual recursion — its `Hash` branch calls itself on every value,
186
- # its `Value` branch unwraps through `#to_h`, which itself calls
187
- # `materialize` on every field, so a value object three levels deep
188
- # unwraps three levels deep for free. This is not a second unwrapper
189
- # sitting next to it: `materialize` covers `Hash`/`Array`/`Value`
190
- # already, and a `Handle` is none of those three, so the one thing
191
- # this adds is `#to_h`'ing a `Handle` first so `materialize`'s own
192
- # `Hash` case can take it from there.
93
+ # Delegates to `Runtime::Value.materialize`; the only addition is `#to_h` on a
94
+ # `Handle`, which `materialize` does not recognize.
193
95
  #
194
96
  # @param value [Facade::Handle, Runtime::Value, Hash, Array, Object] what to unwrap;
195
97
  # a `Handle` is read through its `to_h`, so its `:id` comes along
196
98
  # @return [Hash, Array, Object] the same data with every `Runtime::Value` replaced
197
- # by a Hash of its fields; a value that is none of the listed types is returned
198
- # as it came
99
+ # by a Hash of its fields; any other value is returned as it came
199
100
  def materialize(value)
200
101
  value = value.to_h if value.is_a?(Handle)
201
102
  Runtime::Value.materialize(value)
202
103
  end
203
104
 
204
105
  # Turns a command's JSON body, raw or already parsed, into the `to:`/`with:`
205
- # envelope a dispatcher takes.
206
- #
207
- # Parsed JSON and raw JSON text cross the same receiver/payload boundary
208
- # as CLI and forms. The result is ready to splat into Dispatcher#dispatch
209
- # and contains no loose routing fields.
210
- #
211
- # @param body [String, Hash] raw JSON text, or the Hash it parses to, with String
212
- # or Symbol keys
213
- # @param receiver [Symbol, nil] the kind of receiver the command takes: `:aggregate`,
214
- # `:entity`, or `nil` for none (see `CommandRequest.normalize`)
215
- # @param legacy_receiver [Symbol, String, Hash{Symbol => Symbol, String}, nil] the
216
- # flat key, or pair of keys, a body without `to` may name its receiver under;
217
- # `nil` accepts none
218
- # @return [Hash{Symbol => Object}] `{ with: facts }`, plus `to:` whenever `receiver`
219
- # is not `nil`
106
+ # envelope a dispatcher takes. The result splats into `Dispatcher#dispatch`.
107
+ #
108
+ # @param body [String, Hash] raw JSON text, or the Hash it parses to
109
+ # @param receiver [Symbol, nil] `:aggregate`, `:entity`, or `nil` for none
110
+ # (see `CommandRequest.normalize`)
111
+ # @param legacy_receiver [Symbol, String, Hash{Symbol => Symbol, String}, nil] the flat key,
112
+ # or pair of keys, a body without `to` may name its receiver under; `nil` accepts none
113
+ # @return [Hash{Symbol => Object}] `{ with: facts }`, plus `to:` unless `receiver` is `nil`
220
114
  # @raise [JSON::ParserError] if `body` is a String that is not valid JSON
221
- # @raise [Runtime::TypeMismatch] if the body is not a JSON object, or its routing is
222
- # missing, malformed, or mixed with loose keys beside an explicit `with:`
115
+ # @raise [Runtime::TypeMismatch] if the body is not a JSON object or its routing is malformed
223
116
  # @raise [ArgumentError] if `receiver` is not `nil`, `:aggregate` or `:entity`
224
117
  def command_request(body, receiver:, legacy_receiver: nil)
225
118
  input = body.is_a?(String) ? parse(body) : body
226
119
  CommandRequest.normalize(input, receiver: receiver, legacy_receiver: legacy_receiver)
227
120
  end
228
121
 
229
- # Parses a raw request body, leaving keys as Strings.
230
- #
231
- # The one place a raw JSON string is legitimate input for this door —
232
- # a POST body, still text at the point a generic, HTTP-blind layer can
233
- # see it. `JSON::ParserError` already names "this wasn't JSON" exactly
234
- # right ; wrapping it in a bespoke `JsonDoor`-specific class would
235
- # only be a second name for the same fact, so it propagates exactly as
236
- # `JSON.parse` raises it — a calling app catches it the same standard
237
- # way it would catch any other malformed-input error, no new class to
238
- # learn.
122
+ # Parses a raw request body, leaving keys as Strings. `JSON::ParserError` propagates
123
+ # unwrapped.
239
124
  #
240
125
  # @param raw_json [String] JSON text, such as a POST body
241
126
  # @return [Hash{String => Object}, Array, String, Numeric, Boolean, nil] whatever
242
- # the text encodes; a JSON object becomes a Hash with String keys
127
+ # the text encodes
243
128
  # @raise [JSON::ParserError] if the text is not valid JSON
244
129
  def parse(raw_json) = JSON.parse(raw_json)
245
130
  end