hecks 2.7.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (463) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +11 -0
  3. data/lib/hecks/adapters/driven/claude_code.rb +4 -104
  4. data/lib/hecks/adapters/driven/d1.rb +44 -237
  5. data/lib/hecks/adapters/driven/folder.rb +17 -76
  6. data/lib/hecks/adapters/driven/google_authentication.rb +15 -49
  7. data/lib/hecks/adapters/driven/governance_authorization.rb +23 -70
  8. data/lib/hecks/adapters/driven/heki/journal.rb +8 -41
  9. data/lib/hecks/adapters/driven/heki/saga_store.rb +12 -54
  10. data/lib/hecks/adapters/driven/heki/snapshot.rb +4 -12
  11. data/lib/hecks/adapters/driven/heki.rb +13 -42
  12. data/lib/hecks/adapters/driven/identity_registry.rb +3 -14
  13. data/lib/hecks/adapters/driven/in_memory_ordering.rb +9 -26
  14. data/lib/hecks/adapters/driven/in_process_key_vault.rb +2 -14
  15. data/lib/hecks/adapters/driven/lambda/client.rb +10 -51
  16. data/lib/hecks/adapters/driven/lambda.rb +10 -84
  17. data/lib/hecks/adapters/driven/local_storage.rb +5 -40
  18. data/lib/hecks/adapters/driven/memory.rb +18 -99
  19. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +5 -21
  20. data/lib/hecks/adapters/driven/postgres/codec.rb +6 -28
  21. data/lib/hecks/adapters/driven/postgres/outbox.rb +3 -12
  22. data/lib/hecks/adapters/driven/postgres/reconnect.rb +4 -30
  23. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +34 -108
  24. data/lib/hecks/adapters/driven/postgres.rb +145 -134
  25. data/lib/hecks/adapters/driven/postgres_era.adapter +4 -15
  26. data/lib/hecks/adapters/driven/prism.rb +7 -30
  27. data/lib/hecks/adapters/driven/sql_query_builder.rb +21 -121
  28. data/lib/hecks/adapters/driven/sqlite/codec.rb +8 -40
  29. data/lib/hecks/adapters/driven/sqlite/projection.rb +26 -82
  30. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +40 -84
  31. data/lib/hecks/adapters/driven/sqlite.rb +128 -65
  32. data/lib/hecks/adapters/driven/tenant_provisioner.rb +15 -37
  33. data/lib/hecks/adapters/driven.rb +5 -36
  34. data/lib/hecks/adapters/driving/github_webhook.rb +8 -76
  35. data/lib/hecks/behaviors/dsl.rb +7 -25
  36. data/lib/hecks/behaviors/expectations.rb +32 -142
  37. data/lib/hecks/behaviors/ir.rb +7 -20
  38. data/lib/hecks/behaviors/rspec.rb +6 -17
  39. data/lib/hecks/behaviors/runner.rb +10 -34
  40. data/lib/hecks/behaviors.rb +4 -8
  41. data/lib/hecks/bluebook/aggregate.rb +9 -75
  42. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +9 -38
  43. data/lib/hecks/bluebook/assembly/build.rb +4 -15
  44. data/lib/hecks/bluebook/assembly/contract.rb +15 -136
  45. data/lib/hecks/bluebook/assembly/contracts.rb +54 -174
  46. data/lib/hecks/bluebook/assembly/marks.rb +31 -223
  47. data/lib/hecks/bluebook/assembly/specializer.rb +6 -52
  48. data/lib/hecks/bluebook/assembly.rb +6 -45
  49. data/lib/hecks/bluebook/attribute.rb +12 -61
  50. data/lib/hecks/bluebook/behaviour/aggregate.rb +10 -39
  51. data/lib/hecks/bluebook/behaviour/attribute.rb +3 -14
  52. data/lib/hecks/bluebook/behaviour/chapter.rb +17 -42
  53. data/lib/hecks/bluebook/behaviour/command.rb +14 -66
  54. data/lib/hecks/bluebook/behaviour/domain_port.rb +4 -20
  55. data/lib/hecks/bluebook/behaviour/entity.rb +7 -27
  56. data/lib/hecks/bluebook/behaviour/hexagon.rb +23 -41
  57. data/lib/hecks/bluebook/behaviour/lifecycle.rb +15 -40
  58. data/lib/hecks/bluebook/behaviour/policy.rb +23 -52
  59. data/lib/hecks/bluebook/behaviour/process_manager.rb +30 -57
  60. data/lib/hecks/bluebook/behaviour/read_model.rb +38 -26
  61. data/lib/hecks/bluebook/behaviour/traits.rb +27 -64
  62. data/lib/hecks/bluebook/behaviour/value_object.rb +5 -24
  63. data/lib/hecks/bluebook/capabilities.rb +17 -49
  64. data/lib/hecks/bluebook/chapter.rb +10 -48
  65. data/lib/hecks/bluebook/command.rb +23 -81
  66. data/lib/hecks/bluebook/domain_port.rb +14 -63
  67. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +50 -229
  68. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +50 -377
  69. data/lib/hecks/bluebook/dsl/attribute_collector.rb +33 -204
  70. data/lib/hecks/bluebook/dsl/binding_proxy.rb +17 -65
  71. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +86 -708
  72. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +56 -170
  73. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
  74. data/lib/hecks/bluebook/dsl/command_builder.rb +74 -546
  75. data/lib/hecks/bluebook/dsl/const_shim.rb +8 -41
  76. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +12 -87
  77. data/lib/hecks/bluebook/dsl/entity_builder.rb +44 -330
  78. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +20 -269
  79. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +26 -189
  80. data/lib/hecks/bluebook/dsl/identity_declaration.rb +13 -88
  81. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +6 -20
  82. data/lib/hecks/bluebook/dsl/policy_builder.rb +16 -88
  83. data/lib/hecks/bluebook/dsl/port_builder.rb +2 -6
  84. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +22 -96
  85. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +31 -184
  86. data/lib/hecks/bluebook/dsl/query_builder.rb +11 -42
  87. data/lib/hecks/bluebook/dsl/read_model_builder.rb +75 -201
  88. data/lib/hecks/bluebook/dsl/rule_reference.rb +22 -163
  89. data/lib/hecks/bluebook/dsl/translation_builder.rb +28 -114
  90. data/lib/hecks/bluebook/dsl/value_object_builder.rb +10 -124
  91. data/lib/hecks/bluebook/dsl/word_gate.rb +16 -162
  92. data/lib/hecks/bluebook/dsl/world_builder.rb +19 -106
  93. data/lib/hecks/bluebook/entity.rb +13 -71
  94. data/lib/hecks/bluebook/expression/ast_json.rb +24 -190
  95. data/lib/hecks/bluebook/expression/ast_reader.rb +12 -54
  96. data/lib/hecks/bluebook/expression/canonical_form.rb +11 -88
  97. data/lib/hecks/bluebook/expression/evaluator.rb +47 -295
  98. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +30 -147
  99. data/lib/hecks/bluebook/expression/resolver.rb +37 -660
  100. data/lib/hecks/bluebook/hexagon.rb +5 -28
  101. data/lib/hecks/bluebook/lifecycle.rb +5 -16
  102. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -7
  103. data/lib/hecks/bluebook/meta_validator/judge.rb +111 -361
  104. data/lib/hecks/bluebook/meta_validator/plan.rb +33 -162
  105. data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -7
  106. data/lib/hecks/bluebook/meta_validator/readings.rb +56 -342
  107. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -176
  108. data/lib/hecks/bluebook/meta_validator/shapes.rb +54 -268
  109. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +47 -308
  110. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +7 -43
  111. data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -18
  112. data/lib/hecks/bluebook/meta_validator.rb +79 -349
  113. data/lib/hecks/bluebook/model_check/client_profile.rb +10 -91
  114. data/lib/hecks/bluebook/model_check.rb +102 -600
  115. data/lib/hecks/bluebook/pattern_subset.rb +11 -69
  116. data/lib/hecks/bluebook/process_manager.rb +13 -83
  117. data/lib/hecks/bluebook/project_loader.rb +3 -39
  118. data/lib/hecks/bluebook/project_register.rb +12 -60
  119. data/lib/hecks/bluebook/query.rb +9 -48
  120. data/lib/hecks/bluebook/read_model.rb +57 -81
  121. data/lib/hecks/bluebook/reference.rb +7 -41
  122. data/lib/hecks/bluebook/smoke_test.rb +8 -98
  123. data/lib/hecks/bluebook/synthesizer.rb +13 -62
  124. data/lib/hecks/bluebook/translation.rb +26 -62
  125. data/lib/hecks/bluebook/value_object.rb +11 -36
  126. data/lib/hecks/bluebook.rb +5 -22
  127. data/lib/hecks/cache_dir.rb +84 -0
  128. data/lib/hecks/cli/document.rb +86 -0
  129. data/lib/hecks/cli/ir.rb +45 -0
  130. data/lib/hecks/cli/mcp.rb +31 -0
  131. data/lib/hecks/cli/mcp_door.rb +366 -0
  132. data/lib/hecks/cli/model_check.rb +203 -0
  133. data/lib/hecks/cli/project_cli.rb +90 -0
  134. data/lib/hecks/cli/project_diagrams.rb +36 -0
  135. data/lib/hecks/cli/run.rb +173 -0
  136. data/lib/hecks/cli/smoke_test.rb +45 -0
  137. data/lib/hecks/cli/stores.rb +53 -0
  138. data/lib/hecks/cli.rb +160 -0
  139. data/lib/hecks/construct.rb +8 -34
  140. data/lib/hecks/deploy/bluebook/deploy.bluebook +33 -142
  141. data/lib/hecks/deploy/bluebook/deploy.hecksagon +2 -9
  142. data/lib/hecks/embryonaut_bluebook/lock.rb +3 -35
  143. data/lib/hecks/embryonaut_bluebook/shape.rb +1 -17
  144. data/lib/hecks/embryonaut_bluebook/vendor.rb +8 -53
  145. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +4 -19
  146. data/lib/hecks/embryonaut_bluebook.rb +4 -71
  147. data/lib/hecks/facade/cli_door.rb +24 -106
  148. data/lib/hecks/facade/cli_runner.rb +28 -172
  149. data/lib/hecks/facade/command_request.rb +13 -33
  150. data/lib/hecks/facade/handle.rb +34 -121
  151. data/lib/hecks/facade/json_door.rb +31 -146
  152. data/lib/hecks/facade/surface/aggregate_door.rb +39 -104
  153. data/lib/hecks/facade/surface/chapter.rb +10 -54
  154. data/lib/hecks/facade/surface.rb +8 -23
  155. data/lib/hecks/facade.rb +10 -25
  156. data/lib/hecks/forms/app.rb +15 -70
  157. data/lib/hecks/forms/command_form_renderer.rb +10 -29
  158. data/lib/hecks/forms/field_renderer.rb +17 -156
  159. data/lib/hecks/forms/field_shape.rb +30 -222
  160. data/lib/hecks/forms/html.rb +12 -47
  161. data/lib/hecks/forms/index_renderer.rb +1 -4
  162. data/lib/hecks/forms/page.rb +3 -9
  163. data/lib/hecks/forms/params.rb +14 -60
  164. data/lib/hecks/forms/port_argument.rb +2 -19
  165. data/lib/hecks/forms/query_form_renderer.rb +4 -79
  166. data/lib/hecks/forms/record_renderer.rb +8 -75
  167. data/lib/hecks/forms/record_table.rb +7 -50
  168. data/lib/hecks/forms/reference_options.rb +3 -30
  169. data/lib/hecks/forms/value_object_shape.rb +8 -35
  170. data/lib/hecks/forms.rb +5 -33
  171. data/lib/hecks/fqn.rb +5 -12
  172. data/lib/hecks/framework/bluebook/compliance.bluebook +16 -65
  173. data/lib/hecks/framework/bluebook/console_settings.bluebook +47 -134
  174. data/lib/hecks/framework/bluebook/framework.hecksagon +5 -14
  175. data/lib/hecks/framework/bluebook/governance.bluebook +13 -34
  176. data/lib/hecks/framework/bluebook/identity.bluebook +11 -26
  177. data/lib/hecks/framework/bluebook/privacy.bluebook +8 -33
  178. data/lib/hecks/framework.rb +11 -80
  179. data/lib/hecks/freezer.rb +5 -39
  180. data/lib/hecks/grammar/expression.bluebook +15 -40
  181. data/lib/hecks/grammar/translation.bluebook +3 -6
  182. data/lib/hecks/grammar.rb +16 -41
  183. data/lib/hecks/ir.rb +14 -83
  184. data/lib/hecks/language/adapter.bluebook +2 -7
  185. data/lib/hecks/language/bluebook/aggregate.bluebook +56 -221
  186. data/lib/hecks/language/bluebook/attaches/paging.bluebook +6 -15
  187. data/lib/hecks/language/bluebook/bluebook.bluebook +15 -83
  188. data/lib/hecks/language/bluebook/bluebook.hecksagon +3 -15
  189. data/lib/hecks/language/bluebook/command.bluebook +27 -144
  190. data/lib/hecks/language/bluebook/entity.bluebook +24 -97
  191. data/lib/hecks/language/bluebook/policy.bluebook +16 -67
  192. data/lib/hecks/language/bluebook/process_manager.bluebook +28 -151
  193. data/lib/hecks/language/bluebook/projection.bluebook +126 -60
  194. data/lib/hecks/language/bluebook/query.bluebook +12 -43
  195. data/lib/hecks/language/bluebook/shape.bluebook +23 -73
  196. data/lib/hecks/language/bluebook/syntax.bluebook +51 -262
  197. data/lib/hecks/language/bluebook/vocabulary.bluebook +98 -197
  198. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +3 -10
  199. data/lib/hecks/language/hecksagon/domain_port.bluebook +4 -19
  200. data/lib/hecks/language/hecksagon/hecksagon.bluebook +6 -25
  201. data/lib/hecks/language/hecksagon/port_operation.bluebook +3 -14
  202. data/lib/hecks/language/oidc.json +35 -0
  203. data/lib/hecks/language/port.bluebook +4 -17
  204. data/lib/hecks/language/translation/translation.bluebook +3 -9
  205. data/lib/hecks/language/translation/translation_aggregate.bluebook +5 -24
  206. data/lib/hecks/language/world/wiring.bluebook +1 -3
  207. data/lib/hecks/language/world/world.bluebook +5 -7
  208. data/lib/hecks/literal.rb +8 -50
  209. data/lib/hecks/mcp_door_scope.rb +95 -0
  210. data/lib/hecks/mcp_stdio_guard.rb +4 -41
  211. data/lib/hecks/naming.rb +29 -137
  212. data/lib/hecks/ports/access_control.rb +7 -23
  213. data/lib/hecks/ports/agent/answers.rb +5 -23
  214. data/lib/hecks/ports/agent.rb +47 -172
  215. data/lib/hecks/ports/authentication.rb +12 -39
  216. data/lib/hecks/ports/authorization.rb +16 -43
  217. data/lib/hecks/ports/clock.rb +5 -45
  218. data/lib/hecks/ports/extraction.rb +7 -12
  219. data/lib/hecks/ports/identity_assignment.rb +8 -29
  220. data/lib/hecks/ports/identity_generation.rb +6 -29
  221. data/lib/hecks/ports/identity_resolution.rb +2 -10
  222. data/lib/hecks/ports/key_vault.rb +9 -45
  223. data/lib/hecks/ports/persistence/append_only.rb +62 -65
  224. data/lib/hecks/ports/persistence/binding_policy.rb +4 -14
  225. data/lib/hecks/ports/persistence/codec_boundary.rb +20 -80
  226. data/lib/hecks/ports/persistence/null_saga_store.rb +4 -20
  227. data/lib/hecks/ports/persistence/plugin.rb +13 -42
  228. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +38 -141
  229. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +15 -115
  230. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +16 -84
  231. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +7 -26
  232. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -22
  233. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +55 -181
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/events.rb +77 -0
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +12 -122
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +28 -128
  237. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +73 -474
  238. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +31 -124
  239. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +45 -219
  240. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +13 -108
  241. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +18 -75
  242. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +9 -49
  243. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +6 -123
  244. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +23 -55
  245. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +14 -72
  246. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +7 -16
  247. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +14 -115
  248. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +13 -45
  249. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +127 -554
  250. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +13 -63
  251. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +2 -17
  252. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +3 -14
  253. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +13 -58
  254. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -17
  255. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -26
  256. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +9 -25
  257. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +22 -122
  258. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +34 -123
  259. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +5 -12
  260. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +3 -9
  261. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -14
  262. data/lib/hecks/ports/persistence/plugins/era.rb +4 -23
  263. data/lib/hecks/ports/persistence/postgres_dump/connection.rb +7 -23
  264. data/lib/hecks/ports/persistence/postgres_dump.rb +4 -35
  265. data/lib/hecks/ports/persistence/remote_runtime.rb +2 -24
  266. data/lib/hecks/ports/persistence/repository_factory.rb +8 -27
  267. data/lib/hecks/ports/persistence/state_codec.rb +23 -103
  268. data/lib/hecks/ports/persistence.rb +7 -14
  269. data/lib/hecks/ports/projection.rb +42 -84
  270. data/lib/hecks/ports/query/in_memory.rb +10 -58
  271. data/lib/hecks/ports/query/ordering.rb +11 -32
  272. data/lib/hecks/ports/query.rb +14 -37
  273. data/lib/hecks/ports.rb +2 -8
  274. data/lib/hecks/projections/bootstrap_table.rb +11 -43
  275. data/lib/hecks/projections/deploy/fargate/assembly.rb +2 -15
  276. data/lib/hecks/projections/deploy/fargate/cdn.rb +1 -48
  277. data/lib/hecks/projections/deploy/fargate/cdn_default.rb +2 -11
  278. data/lib/hecks/projections/deploy/fargate/check.rb +5 -14
  279. data/lib/hecks/projections/deploy/fargate/containers.rb +3 -78
  280. data/lib/hecks/projections/deploy/fargate/extras.rb +3 -44
  281. data/lib/hecks/projections/deploy/fargate/monitoring.rb +0 -44
  282. data/lib/hecks/projections/deploy/fargate/settings.rb +2 -54
  283. data/lib/hecks/projections/deploy/fargate/yaml.rb +13 -58
  284. data/lib/hecks/projections/deploy/fargate.rb +35 -202
  285. data/lib/hecks/projections/deploy/lambda.rb +151 -679
  286. data/lib/hecks/projections/deploy/preview/access_yaml.rb +1 -33
  287. data/lib/hecks/projections/deploy/preview/containers.rb +6 -111
  288. data/lib/hecks/projections/deploy/preview/routing_yaml.rb +4 -56
  289. data/lib/hecks/projections/deploy/preview/script.rb +2 -14
  290. data/lib/hecks/projections/deploy/preview/settings.rb +3 -33
  291. data/lib/hecks/projections/deploy/preview/task_yaml.rb +2 -38
  292. data/lib/hecks/projections/deploy/preview/template.rb +2 -58
  293. data/lib/hecks/projections/deploy/preview.rb +2 -67
  294. data/lib/hecks/projections/deploy/scripts/settings.rb +3 -25
  295. data/lib/hecks/projections/deploy/scripts.rb +4 -57
  296. data/lib/hecks/projections/deploy/shared.rb +47 -146
  297. data/lib/hecks/projections/deploy/smoke.rb +4 -61
  298. data/lib/hecks/projections/deploy/template_diff/comparer.rb +3 -25
  299. data/lib/hecks/projections/deploy/template_diff/loader.rb +2 -11
  300. data/lib/hecks/projections/deploy/template_diff.rb +5 -57
  301. data/lib/hecks/projections/diagrams.rb +20 -582
  302. data/lib/hecks/projections/glossary/html.rb +14 -130
  303. data/lib/hecks/projections/glossary/markdown.rb +8 -69
  304. data/lib/hecks/projections/glossary/mermaid.rb +9 -66
  305. data/lib/hecks/projections/glossary/sensitivity.rb +5 -34
  306. data/lib/hecks/projections/glossary/sentences.rb +14 -159
  307. data/lib/hecks/projections/glossary.rb +30 -222
  308. data/lib/hecks/projections/ir.rb +3 -10
  309. data/lib/hecks/projections/model/deviations.rb +15 -42
  310. data/lib/hecks/projections/model.rb +12 -51
  311. data/lib/hecks/projections/oidc.rb +12 -56
  312. data/lib/hecks/projections/parser_table.rb +9 -28
  313. data/lib/hecks/projections/reference.rb +9 -16
  314. data/lib/hecks/projections/rust_vocabulary.rb +12 -250
  315. data/lib/hecks/projections/shape.rb +3 -29
  316. data/lib/hecks/projections/statements.rb +7 -86
  317. data/lib/hecks/projections/vocabulary.rb +6 -22
  318. data/lib/hecks/projections.rb +2 -16
  319. data/lib/hecks/projector/cli_projector.rb +60 -375
  320. data/lib/hecks/projector/docs_projector.rb +10 -201
  321. data/lib/hecks/projector/exporter.rb +46 -138
  322. data/lib/hecks/projector/ir_projector.rb +2 -9
  323. data/lib/hecks/projector/narrate_projector.rb +15 -189
  324. data/lib/hecks/projector/target.rb +13 -76
  325. data/lib/hecks/projector.rb +30 -121
  326. data/lib/hecks/query_specification/common/comparators.rb +5 -26
  327. data/lib/hecks/query_specification/common/comparison.rb +35 -143
  328. data/lib/hecks/query_specification/common/dsl.rb +8 -24
  329. data/lib/hecks/query_specification/common/null_policy.rb +20 -70
  330. data/lib/hecks/query_specification/common/options.rb +2 -6
  331. data/lib/hecks/query_specification/common/where_clause.rb +2 -10
  332. data/lib/hecks/query_specification/field_path.rb +37 -77
  333. data/lib/hecks/query_specification/hop_path.rb +12 -76
  334. data/lib/hecks/release/gem_pin.rb +2 -26
  335. data/lib/hecks/release/runner/ci_publisher.rb +93 -0
  336. data/lib/hecks/release/runner/commands.rb +66 -0
  337. data/lib/hecks/release/runner/console.rb +52 -0
  338. data/lib/hecks/release/runner/gem_publisher.rb +43 -0
  339. data/lib/hecks/release/runner/git.rb +48 -0
  340. data/lib/hecks/release/runner/npm_publisher.rb +75 -0
  341. data/lib/hecks/release/runner/preflight.rb +104 -0
  342. data/lib/hecks/release/runner/published.rb +55 -0
  343. data/lib/hecks/release/runner/tagger.rb +80 -0
  344. data/lib/hecks/release/runner.rb +215 -0
  345. data/lib/hecks/rendering.rb +4 -26
  346. data/lib/hecks/router/namespace_installer.rb +12 -36
  347. data/lib/hecks/router.rb +4 -8
  348. data/lib/hecks/runtime/aggregate_lock.rb +8 -33
  349. data/lib/hecks/runtime/boot_gates.rb +3 -18
  350. data/lib/hecks/runtime/caller.rb +19 -59
  351. data/lib/hecks/runtime/capability_graph.rb +5 -21
  352. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
  353. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +23 -160
  354. data/lib/hecks/runtime/command_interpreter.rb +125 -307
  355. data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
  356. data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
  357. data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
  358. data/lib/hecks/runtime/command_rules/emission.rb +8 -21
  359. data/lib/hecks/runtime/command_rules/references.rb +23 -202
  360. data/lib/hecks/runtime/dependency_planning.rb +30 -102
  361. data/lib/hecks/runtime/dispatcher.rb +82 -241
  362. data/lib/hecks/runtime/entity_element.rb +40 -462
  363. data/lib/hecks/runtime/entity_interpreter.rb +65 -220
  364. data/lib/hecks/runtime/errors.rb +32 -115
  365. data/lib/hecks/runtime/event.rb +3 -25
  366. data/lib/hecks/runtime/identity.rb +18 -154
  367. data/lib/hecks/runtime/instance.rb +18 -150
  368. data/lib/hecks/runtime/interpreting.rb +11 -70
  369. data/lib/hecks/runtime/invocation.rb +28 -164
  370. data/lib/hecks/runtime/loader.rb +48 -186
  371. data/lib/hecks/runtime/outbox.rb +38 -261
  372. data/lib/hecks/runtime/policy_interpreter.rb +38 -200
  373. data/lib/hecks/runtime/port_operation_interpreter.rb +26 -87
  374. data/lib/hecks/runtime/query_interpreter.rb +45 -187
  375. data/lib/hecks/runtime/reaction_invocation.rb +36 -138
  376. data/lib/hecks/runtime/read_model_interpreter.rb +139 -208
  377. data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
  378. data/lib/hecks/runtime/reference_hop.rb +8 -81
  379. data/lib/hecks/runtime/refusal_wording.rb +8 -89
  380. data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
  381. data/lib/hecks/runtime/registry/verification.rb +52 -225
  382. data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
  383. data/lib/hecks/runtime/registry.rb +103 -244
  384. data/lib/hecks/runtime/remote_dispatcher.rb +37 -152
  385. data/lib/hecks/runtime/routing.rb +4 -15
  386. data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
  387. data/lib/hecks/runtime/saga_interpreter.rb +79 -263
  388. data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
  389. data/lib/hecks/runtime/tenant_check.rb +6 -68
  390. data/lib/hecks/runtime/tenant_scope.rb +3 -28
  391. data/lib/hecks/runtime/value/admission.rb +16 -67
  392. data/lib/hecks/runtime/value/coercion.rb +155 -641
  393. data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
  394. data/lib/hecks/runtime/value.rb +12 -84
  395. data/lib/hecks/runtime.rb +17 -65
  396. data/lib/hecks/storehouse.rb +146 -567
  397. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +8 -32
  398. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
  399. data/lib/hecks/vendoring/git_environment.rb +2 -8
  400. data/lib/hecks/vendoring/git_source.rb +4 -10
  401. data/lib/hecks/vendoring.rb +7 -46
  402. data/lib/hecks/version.rb +3 -14
  403. data/lib/hecks/vocabulary.rb +5 -0
  404. data/lib/hecks.rb +32 -82
  405. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
  406. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
  407. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
  408. metadata +30 -59
  409. data/lib/hecks/bench/cli.rb +0 -74
  410. data/lib/hecks/bench/environment.rb +0 -100
  411. data/lib/hecks/bench/postgres_probe.rb +0 -41
  412. data/lib/hecks/bench/report.rb +0 -116
  413. data/lib/hecks/bench/ruby_runner.rb +0 -151
  414. data/lib/hecks/bench/run.rb +0 -55
  415. data/lib/hecks/bench/rust_runner.rb +0 -165
  416. data/lib/hecks/bench/stats.rb +0 -75
  417. data/lib/hecks/bench/suite.rb +0 -172
  418. data/lib/hecks/bench/workload.rb +0 -137
  419. data/lib/hecks/bench.rb +0 -28
  420. data/lib/hecks/codemod.rb +0 -443
  421. data/lib/hecks/corpus.rb +0 -479
  422. data/lib/hecks/doc/reference.rb +0 -585
  423. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -684
  424. data/lib/hecks/fuzzing/combination_miner.rb +0 -178
  425. data/lib/hecks/fuzzing/concurrent_dispatch.rb +0 -469
  426. data/lib/hecks/fuzzing/coverage_campaign.rb +0 -161
  427. data/lib/hecks/fuzzing/differential.rb +0 -192
  428. data/lib/hecks/fuzzing/domain_generator.rb +0 -871
  429. data/lib/hecks/fuzzing/era_boundary.rb +0 -154
  430. data/lib/hecks/fuzzing/form_census.rb +0 -285
  431. data/lib/hecks/fuzzing/generated_domain_check.rb +0 -171
  432. data/lib/hecks/fuzzing/invalid_value_generator.rb +0 -125
  433. data/lib/hecks/fuzzing/isolated_boot.rb +0 -658
  434. data/lib/hecks/fuzzing/nondeterministic.rb +0 -79
  435. data/lib/hecks/fuzzing/persistence_parity.rb +0 -253
  436. data/lib/hecks/fuzzing/properties/corrections.rb +0 -125
  437. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +0 -688
  438. data/lib/hecks/fuzzing/properties/guards.rb +0 -398
  439. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +0 -399
  440. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +0 -137
  441. data/lib/hecks/fuzzing/properties/outbox.rb +0 -180
  442. data/lib/hecks/fuzzing/properties/querying.rb +0 -260
  443. data/lib/hecks/fuzzing/properties.rb +0 -267
  444. data/lib/hecks/fuzzing/qa_settings.rb +0 -164
  445. data/lib/hecks/fuzzing/replay.rb +0 -930
  446. data/lib/hecks/fuzzing/rotation_priority.rb +0 -114
  447. data/lib/hecks/fuzzing/rust_gap_manifest.rb +0 -139
  448. data/lib/hecks/fuzzing/self_consistency.rb +0 -825
  449. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +0 -532
  450. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +0 -171
  451. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +0 -127
  452. data/lib/hecks/fuzzing/sequence_generator/picker.rb +0 -116
  453. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +0 -138
  454. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +0 -344
  455. data/lib/hecks/fuzzing/sequence_generator.rb +0 -311
  456. data/lib/hecks/fuzzing/shrinker.rb +0 -309
  457. data/lib/hecks/fuzzing/structural_skips.rb +0 -53
  458. data/lib/hecks/fuzzing/sweep_depth.rb +0 -61
  459. data/lib/hecks/fuzzing/target_capabilities.rb +0 -255
  460. data/lib/hecks/fuzzing/value_generator.rb +0 -340
  461. data/lib/hecks/fuzzing.rb +0 -23
  462. data/lib/hecks/grammar/evolve.rb +0 -524
  463. data/lib/hecks/query_ir.rb +0 -482
@@ -0,0 +1,215 @@
1
+ require_relative "runner/commands"
2
+ require_relative "runner/console"
3
+ require_relative "runner/git"
4
+ require_relative "runner/preflight"
5
+ require_relative "runner/published"
6
+ require_relative "runner/tagger"
7
+ require_relative "runner/gem_publisher"
8
+ require_relative "runner/npm_publisher"
9
+ require_relative "runner/ci_publisher"
10
+
11
+ module Hecks
12
+ module Release
13
+ # Performs the release once the release PR has merged: tags the merge
14
+ # commit, publishes the gem, and gets the JS client published to npm.
15
+ #
16
+ # Idempotent: it asks the registries what is already published and skips
17
+ # it, so a run that died between the gem and the package resumes at the package.
18
+ class Runner
19
+ # Which steps to run and how.
20
+ class Options
21
+ # @return [Boolean] run every check and build, but tag, push and publish nothing
22
+ attr_reader :dry_run
23
+
24
+ # @param dry_run [Boolean] run every check and build, but tag, push and publish nothing
25
+ # @param gem_only [Boolean] publish the gem, not the package
26
+ # @param npm_only [Boolean] do the npm step only, not the gem: wait for CI's publish, or
27
+ # with `npm_local` publish from here
28
+ # @param npm_local [Boolean] publish the package from this machine, not by CI
29
+ # @param no_wait [Boolean] do not wait for CI's publish to reach npm
30
+ # @param yes [Boolean] answer every confirmation yes
31
+ # @raise [ArgumentError] if the flags contradict each other
32
+ def initialize(dry_run: false, gem_only: false, npm_only: false, npm_local: false, no_wait: false, yes: false)
33
+ check_flags(gem_only, npm_only, npm_local, no_wait)
34
+
35
+ @dry_run = dry_run
36
+ @gem_only = gem_only
37
+ @npm_only = npm_only
38
+ @npm_local = npm_local
39
+ @no_wait = no_wait
40
+ @yes = yes
41
+ end
42
+
43
+ # Says whether confirmations are answered automatically.
44
+ #
45
+ # @return [Boolean] true under `--yes`
46
+ def yes?
47
+ @yes
48
+ end
49
+
50
+ # Says whether the gem step is in scope.
51
+ #
52
+ # @return [Boolean] true unless `--npm-only`
53
+ def gem?
54
+ !@npm_only
55
+ end
56
+
57
+ # Says whether the npm step is in scope.
58
+ #
59
+ # @return [Boolean] true unless `--gem-only`
60
+ def npm?
61
+ !@gem_only
62
+ end
63
+
64
+ # Says whether the package is published from this machine.
65
+ #
66
+ # @return [Boolean] true under `--npm-local`
67
+ def npm_local?
68
+ @npm_local
69
+ end
70
+
71
+ # Says whether to wait for CI's publish to reach npm.
72
+ #
73
+ # @return [Boolean] true unless `--no-wait`
74
+ def wait?
75
+ !@no_wait
76
+ end
77
+
78
+ private
79
+
80
+ def check_flags(gem_only, npm_only, npm_local, no_wait)
81
+ raise ArgumentError, "--gem-only and --npm-only cannot be combined" if gem_only && npm_only
82
+ raise ArgumentError, "--gem-only and --npm-local cannot be combined" if gem_only && npm_local
83
+ return unless no_wait && npm_local
84
+
85
+ raise ArgumentError, "--no-wait applies to CI's publish, so it cannot be combined with --npm-local"
86
+ end
87
+ end
88
+
89
+ # @param root [String] the repository root
90
+ # @param options [Options] which steps to run and how
91
+ # @param commands [#capture, #run!] starts every process the release runs
92
+ # @param input [IO] where confirmations are read from
93
+ # @param out [IO] where progress is written
94
+ # @param err [IO] where refusals and failures are written
95
+ # @param pause [#call] sleeps for a number of seconds while waiting for CI
96
+ # @param now [#call] answers the current time in seconds, monotonically, while waiting for CI
97
+ def initialize(root:, options: Options.new, commands: Commands.new, input: $stdin, out: $stdout, err: $stderr,
98
+ pause: ->(seconds) { sleep(seconds) }, now: -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) })
99
+ @root = root
100
+ @options = options
101
+ @commands = commands
102
+ @console = Console.new(input: input, out: out, err: err, assume_yes: options.yes?)
103
+ @git = Git.new(root: root, commands: commands)
104
+ @published = Published.new(root: root, commands: commands)
105
+ @ci = CiPublisher.new(root: root, published: @published, console: @console, pause: pause, now: now)
106
+ end
107
+
108
+ # Runs the release.
109
+ #
110
+ # @return [Integer] 0 when the release finished or had nothing to do, 1 when a check refused,
111
+ # a step failed, CI's publish did not arrive in time or a confirmation was declined
112
+ def call
113
+ facts = Preflight.new(root: @root, commands: @commands, git: @git, tools: tools).check!
114
+ pending = pending_steps(facts.version)
115
+ require_workflow(pending)
116
+ return declined unless Tagger.new(git: @git, console: @console, dry_run: @options.dry_run).ensure!(facts)
117
+
118
+ publish(facts, pending)
119
+ rescue Refusal, CommandFailed => e
120
+ @console.warn(e.message)
121
+ 1
122
+ end
123
+
124
+ private
125
+
126
+ def tools
127
+ list = %w[git curl]
128
+ list << "gem" if @options.gem?
129
+ list << "npm" if @options.npm?
130
+ list << "op" if !@options.dry_run && (@options.gem? || @options.npm_local?)
131
+ list
132
+ end
133
+
134
+ def pending_steps(version)
135
+ [
136
+ (:gem if @options.gem? && !done?("hecks #{version} is already on rubygems.org", @published.gem?(version))),
137
+ (:npm if @options.npm? && !done?("@hecks/client #{version} is already on npm", @published.npm?(version)))
138
+ ].compact
139
+ end
140
+
141
+ def done?(message, published)
142
+ @console.say("#{message}; skipping.") if published
143
+ published
144
+ end
145
+
146
+ def ci_publishes?(pending)
147
+ pending.include?(:npm) && !@options.npm_local?
148
+ end
149
+
150
+ def require_workflow(pending)
151
+ return unless ci_publishes?(pending) && !@ci.workflow?
152
+
153
+ raise Refusal, "#{CiPublisher::WORKFLOW} is not in this checkout, so CI cannot publish @hecks/client; " \
154
+ "publish it from here with --npm-local (the first publish, before trusted publishing is set up)"
155
+ end
156
+
157
+ def publish(facts, pending)
158
+ if pending.empty?
159
+ @console.say("Nothing to publish for #{facts.version}.")
160
+ return 0
161
+ end
162
+ return declined unless @options.dry_run || publish_confirmed?(facts.version, pending)
163
+
164
+ run_steps(facts.version, pending)
165
+ end
166
+
167
+ def publish_confirmed?(version, pending)
168
+ targets = []
169
+ targets << "hecks #{version} to rubygems.org" if pending.include?(:gem)
170
+ targets << "@hecks/client #{version} to npm" if pending.include?(:npm) && @options.npm_local?
171
+ return true if targets.empty?
172
+
173
+ suffix = ci_publishes?(pending) ? " (CI then publishes @hecks/client from the tag)" : ""
174
+ @console.confirm?("Publish #{targets.join(' and ')}#{suffix}? This cannot be undone.")
175
+ end
176
+
177
+ def run_steps(version, pending)
178
+ dry_run = @options.dry_run
179
+ publish_gem(version, dry_run) if pending.include?(:gem)
180
+ return 1 if pending.include?(:npm) && !publish_npm!(version, dry_run, ci_publishes?(pending))
181
+
182
+ @console.say(dry_run ? "Dry run complete; nothing was tagged, pushed or published." : "Released hecks #{version}.")
183
+ 0
184
+ end
185
+
186
+ def publish_gem(version, dry_run)
187
+ GemPublisher.new(root: @root, commands: @commands, console: @console).publish!(version, dry_run: dry_run)
188
+ end
189
+
190
+ def publish_npm!(version, dry_run, via_ci)
191
+ return wait_for_ci!(version, dry_run) if via_ci
192
+
193
+ NpmPublisher.new(root: @root, commands: @commands, console: @console).publish!(version, dry_run: dry_run)
194
+ true
195
+ rescue CommandFailed => e
196
+ @console.warn("npm publish failed: #{e.message}")
197
+ unless dry_run
198
+ @console.warn("Finish the release with: bin/release --npm-only --npm-local (published steps are not repeated)")
199
+ end
200
+ raise
201
+ end
202
+
203
+ def wait_for_ci!(version, dry_run)
204
+ arrived = @ci.wait!(version, dry_run: dry_run, wait: @options.wait?)
205
+ @console.warn(CiPublisher.timeout_hint(version)) unless arrived
206
+ arrived
207
+ end
208
+
209
+ def declined
210
+ @console.warn("Aborted; nothing was published.")
211
+ 1
212
+ end
213
+ end
214
+ end
215
+ end
@@ -1,19 +1,9 @@
1
1
  require "json"
2
2
 
3
3
  module Hecks
4
- # How a value reads inside a refusal, in one place, because a refusal is an
5
- # answer and its wording is contract — pinned byte-for-byte by the corpus.
4
+ # How a value reads inside a refusal; the wording is contract, pinned by the corpus.
6
5
  #
7
- # `inspect` and JSON agree on scalars and disagree on everything composite :
8
- # a hash is `{"cents"=>100}` against `{"cents":100}`, an array is `["a", "b"]`
9
- # against `["a","b"]`. Each such spelling is invisible until a composite
10
- # actually reaches a message, which is why both known cases were found by
11
- # something deliberately passing the wrong shape rather than by review — the
12
- # first by hand in spec/corpus/banking.json, the second by bin/fuzz, at a
13
- # different site, after the first was patched where it was found.
14
- #
15
- # So it lives here rather than in whichever file noticed it, and the house style
16
- # is JSON because that is what every other refusal in the corpus already prints.
6
+ # `inspect` and JSON disagree on composites, so refusals use JSON throughout.
17
7
  module Rendering
18
8
  module_function
19
9
 
@@ -28,20 +18,8 @@ module Hecks
28
18
  when nil then "nil"
29
19
  when Hash, Array then JSON.generate(value)
30
20
  else
31
- # A Hecks::Runtime::Value reaching a refusal message (an
32
- # arithmetic op's own `amount`/`current`, an Admissibility
33
- # `current`, ...) had no case here, falling straight through to
34
- # Ruby's own #inspect and leaking a raw object pointer
35
- # ("#<Hecks::Runtime::Value:0x...>") into an otherwise
36
- # correct domain refusal. Duck-typed on `respond_to?(:to_h)`
37
- # rather than naming `Runtime::Value` directly — `Runtime::Value`
38
- # itself requires this file (`runtime/value.rb`'s own
39
- # `require_relative "../rendering"`), so naming it here would be
40
- # circular. A single-field wrapper (the overwhelming common case
41
- # — an amount, an id, a lifecycle field) unwraps to its bare
42
- # scalar, described recursively; a genuinely multi-field value
43
- # renders as its fields' JSON, same as a bare Hash already does
44
- # two lines up.
21
+ # Duck-typed on `to_h`: naming Runtime::Value here would be circular, since
22
+ # it requires this file. A one-field wrapper unwraps to its bare scalar.
45
23
  if value.respond_to?(:to_h) && !value.is_a?(Hash) && !value.is_a?(Array)
46
24
  fields = value.to_h
47
25
  fields.size == 1 ? describe(fields.values.first) : JSON.generate(fields)
@@ -3,22 +3,13 @@ require_relative "../facade/handle"
3
3
 
4
4
  module Hecks
5
5
  class Router
6
- # Installs optional Ruby syntax over an already-loaded router. FQN
7
- # resolution stays in Router; this object only adapts it to constants and
8
- # method calls.
6
+ # Installs optional Ruby syntax over an already-loaded router; FQN
7
+ # resolution stays in Router, this only adapts it to constants and methods.
9
8
  class NamespaceInstaller
10
- # Returned by the `.options(version:)` method installed on each
11
- # namespace module — forwards any command/query verb called on it
12
- # to the router, pinned to this specific FQN version rather than
13
- # the router's default resolution.
9
+ # Returned by `.options(version:)`; forwards command/query verbs to the
10
+ # router, pinned to this FQN version rather than the router's default.
14
11
  class OptionsProxy
15
- # @param router [Router] router to dispatch resolved calls through
16
- # @param realm [String, nil] realm segment of the FQN this proxy pins
17
- # @param domain [String] domain segment of the FQN this proxy pins
18
- # @param aggregate [String, nil] aggregate segment, or nil for a domain-level query
19
- # @param options [Hash{Symbol => Object}] the `.options(...)` call's keywords; only
20
- # `:version` is accepted
21
- # @raise [ArgumentError] if `options` has a key other than `:version`
12
+ # Only the `:version` keyword is accepted; any other key raises `ArgumentError`.
22
13
  def initialize(router:, realm:, domain:, aggregate:, options:)
23
14
  unknown = options.keys - [:version]
24
15
  raise ArgumentError, "unknown router options: #{unknown.join(', ')}" unless unknown.empty?
@@ -52,16 +43,12 @@ module Hecks
52
43
  end
53
44
  end
54
45
 
55
- # @param router [Router] router whose current routes get Ruby constants and methods
56
46
  def initialize(router)
57
47
  @router = router
58
48
  end
59
49
 
60
- # Installs every current-version route as a namespace constant/method,
61
- # plus the aggregate `find`/`all`/`count`/`events`/`repository` door and
62
- # short `Aggregate.verb` shortcuts.
63
- #
64
- # @return [self]
50
+ # Installs every current-version route as a namespace constant/method, plus
51
+ # the aggregate CRUD door and short `Aggregate.verb` shortcuts.
65
52
  def install!
66
53
  current_entries.each { |entry| install_namespace_entry(entry) }
67
54
  install_shortcuts!
@@ -90,18 +77,8 @@ module Hecks
90
77
  end
91
78
  end
92
79
 
93
- # `.find`/`.all`/`.count`/`.events`/`.repository` — the same read/CRUD
94
- # surface `Facade::Surface::AggregateDoor` gives a plain `Hecks
95
- # .boot`, missing here until now: `install_namespace_entry` above
96
- # installs one method per declared verb, so an aggregate with no
97
- # commands or queries of its own shape (or simply never asked for
98
- # a `find`-shaped query) had no way to look up one record by id
99
- # through the router surface at all — real gap, hit live building
100
- # a `List` aggregate meant to be read this way. Grouped by
101
- # (realm, domain, aggregate) rather than installed per-verb,
102
- # because unlike a command or query this is the same five methods
103
- # regardless of which verb happened to trigger this aggregate's
104
- # own namespace module into existing.
80
+ # `.find`/`.all`/`.count`/`.events`/`.repository`, matching `Hecks.boot`'s
81
+ # door; grouped by (realm, domain, aggregate) since the five don't vary by verb.
105
82
  def install_aggregate_doors!
106
83
  current_entries.reject { |entry| entry.fqn.aggregate.nil? }
107
84
  .group_by { |entry| [entry.fqn.realm, entry.fqn.domain, entry.fqn.aggregate] }
@@ -138,10 +115,9 @@ module Hecks
138
115
  current_entries.reject { |entry| entry.fqn.aggregate.nil? }
139
116
  .group_by { |entry| [entry.fqn.aggregate, entry.fqn.verb] }
140
117
  .each do |(aggregate, verb), candidates|
141
- # Bounded chapters wrap in their own module (`Domain::Aggregate`)
142
- # so two BCs can both declare `Person` without colliding on
143
- # Object::Person. Folder-spread files of the SAME chapter still
144
- # get the shortcut — they are not BCs.
118
+ # Bounded chapters wrap in their own module (`Domain::Aggregate`) so two
119
+ # BCs can both declare `Person` without colliding on `Object::Person`.
120
+ # Folder-spread files of the same chapter aren't BCs, so they still get it.
145
121
  next if bounded_chapter?(candidates)
146
122
 
147
123
  shortcut_target(aggregate).define_singleton_method(verb) do |**args|
data/lib/hecks/router.rb CHANGED
@@ -16,8 +16,7 @@ module Hecks
16
16
  # @return [Router] the built router
17
17
  def self.load(root) = new(Bluebook::ProjectLoader.load(root))
18
18
 
19
- # Install one project router for ordinary application calls. The explicit
20
- # `Router.load` API remains useful for tests and embedded hosts.
19
+ # Installs one project router as `.default` for ordinary application calls.
21
20
  #
22
21
  # @param root [String] project root to discover bluebooks under
23
22
  # @return [Router] the booted router, also stored as `.default`
@@ -75,14 +74,12 @@ module Hecks
75
74
  # Dispatches a command to its resolved aggregate.
76
75
  #
77
76
  # @param address [String] a fully-qualified command address, realm included
78
- # @param args [Hash] command facts, plus the dispatcher's optional `:to`, `:with`,
79
- # and `:saga_correlation` keys
77
+ # @param args [Hash] command facts, plus optional `:to`, `:with`, `:saga_correlation`
80
78
  # @return [Runtime::Dispatcher::Result] the dispatch result
81
79
  # @raise [UnknownAddress] if `address` has no realm, or names no known route
82
80
  # @raise [WrongVerbKind] if `address` names a query
83
81
  # @raise [Runtime::UnknownVerb] if the resolved verb names something undeclared
84
- # @raise [StandardError] any class in `Runtime::DOMAIN_REFUSALS` when the domain refuses
85
- # the call
82
+ # @raise [StandardError] any `Runtime::DOMAIN_REFUSALS` class when the domain refuses
86
83
  # @raise [Runtime::StaleWrite] if concurrent writers beat this one through every retry
87
84
  # @raise [Runtime::WiringError] if the aggregate's repository cannot be resolved
88
85
  def dispatch(address, **args)
@@ -97,13 +94,12 @@ module Hecks
97
94
  # @param address [String] a fully-qualified query address, realm included
98
95
  # @param args [Hash{Symbol => Object}] the query's declared arguments
99
96
  # @return [Array<Hash>] one row Hash per match; see `Runtime::Dispatcher#query`
100
- # for the exact shape per address kind
101
97
  # @raise [UnknownAddress] if `address` has no realm, or names no known route
102
98
  # @raise [WrongVerbKind] if `address` names a command
103
99
  # @raise [Runtime::UnknownVerb] if the resolved verb names something undeclared
104
100
  # @raise [Runtime::NotFound] if a read model's root reference names no record
105
101
  # @raise [Runtime::TypeMismatch] if an argument cannot be coerced to its declared type
106
- # @raise [KeyError] if a rooted read model is asked without its reference argument
102
+ # @raise [KeyError] if a rooted read model lacks its reference argument
107
103
  # @raise [Runtime::WiringError] if the aggregate's repository cannot be resolved
108
104
  def query(address, **args)
109
105
  entry = resolve(address)
@@ -1,29 +1,10 @@
1
1
  module Hecks
2
2
  module Runtime
3
- # **A process-wide, striped mutex registry** — the concurrency-control
4
- # mechanism for every adapter that does not declare
5
- # `:optimistic_concurrency` (Heki, Memory today; see
6
- # `CommandInterpreter#call`/`EntityInterpreter#call`, which choose
7
- # between this and Postgres's CAS+retry purely off
8
- # `repository.capabilities`).
3
+ # A process-wide, striped mutex registry, keyed by `[domain, aggregate, id]`.
4
+ # Serializes hydrate-through-save for adapters without `:optimistic_concurrency`.
9
5
  #
10
- # Why a lock suffices here and CAS is not needed: both adapters hold
11
- # process-local data. `Adapters::Memory.tenant_capable?`'s own comment
12
- # states the confirmed fact this relies on — two `Runtime.boot` calls
13
- # get two entirely separate adapter instances; there is never a second
14
- # process writing the same Heki file or the same Memory Hash, only
15
- # possibly other threads within this one process. A `Mutex` held for
16
- # the full hydrate-through-save critical section closes the identical
17
- # lost-update gap CAS closes for Postgres, with no schema, no version
18
- # column, and no retry loop — the second thread simply doesn't start
19
- # its own hydrate until the first thread's save has landed.
20
- #
21
- # **Striped, not one global lock**: keyed by `[domain, aggregate.hecks_name,
22
- # id]`, so two dispatches against two different records never block
23
- # each other. The registry Hash itself is guarded by its own top-level
24
- # Mutex only for the moment a new per-key Mutex is created — two
25
- # threads locking different keys for the first time never wait on one
26
- # another beyond that brief creation window.
6
+ # A lock suffices because those adapters (Heki, Memory) hold process-local data:
7
+ # only threads of this process can write the same record.
27
8
  module AggregateLock
28
9
  @registry_lock = Mutex.new
29
10
  @locks = {}
@@ -31,19 +12,13 @@ module Hecks
31
12
  class << self
32
13
  # Returns the Mutex striped to one record, creating it on first use.
33
14
  #
34
- # `AggregateLock.for(domain, aggregate, id).synchronize { ... }`
35
- # `id: nil` — identity could not be resolved yet (see
36
- # `Identity.best_effort`) — locks by aggregate type alone, coarser
37
- # (every record of this aggregate serializes against every other)
38
- # but still correct: it can only ever make dispatch more
39
- # conservative than a resolved id would.
15
+ # AggregateLock.for(domain, aggregate, id).synchronize { ... }
40
16
  #
41
17
  # @param domain [String] the domain the aggregate belongs to
42
18
  # @param aggregate [Bluebook::Aggregate] the aggregate being dispatched
43
- # @param id [String, nil] the resolved record id, or nil to stripe by aggregate type
44
- # alone
45
- # @return [Mutex] the mutex for this `[domain, aggregate, id]` key, held for the full
46
- # hydrate-through-save critical section
19
+ # @param id [String, nil] the record id; nil locks by aggregate type alone, which
20
+ # is coarser but still correct
21
+ # @return [Mutex] the mutex for this `[domain, aggregate, id]` key
47
22
  def for(domain, aggregate, id = nil)
48
23
  key = id.nil? ? [domain.to_s, aggregate.hecks_name] : [domain.to_s, aggregate.hecks_name, id.to_s]
49
24
  @registry_lock.synchronize { @locks[key] ||= Mutex.new }
@@ -1,24 +1,9 @@
1
1
  module Hecks
2
2
  module Runtime
3
- # A boot's own small set of phase-tagged, conditionally-present gates —
4
- # ADR 0031. Registration is instance-scoped, one per `Loader.boot`
5
- # call, never a module-level singleton: a process that boots more than
6
- # one registry in its lifetime (every spec suite does) must never let
7
- # one boot's capability profile leak into the next boot's gate list.
3
+ # A boot's own set of phase-tagged, conditionally-present gates (ADR 0031).
4
+ # A gate is anything responding to `.call(registry, directory)`; run in registration order.
8
5
  #
9
- # A gate is anything `.call(registry, directory)`-able — an existing
10
- # module method handed over as a `Method` object (`EraCheck.method
11
- # (:check_lineage!)`) needs no wrapper; a bare block does. Phases run
12
- # in the order `run!` is called, gates within a phase in registration
13
- # order — today exactly one gate per phase, so ordering among
14
- # same-phase gates has never been exercised.
15
- #
16
- # This is deliberately not the same registry `Hecks::Projector` uses
17
- # (ADR 0027) — that one is a process-wide, static IR-in/artifact-out
18
- # registry with no bindings and no live-state mutation; this one is
19
- # per-boot and gates real I/O (a Postgres mint, a saga-store read).
20
- # Sharing a primitive between them is deferred until a third consumer
21
- # actually wants it (0031's own Rejected Alternatives).
6
+ # Instance-scoped, one per `Loader.boot`, so one boot's gates never leak into the next.
22
7
  class BootGates
23
8
  # @return [void]
24
9
  def initialize
@@ -1,38 +1,14 @@
1
1
  module Hecks
2
2
  module Runtime
3
- # Who is dispatching, bound for the duration of a block — the ambient
4
- # counterpart to `Runtime.with_registry`, but `Thread.current`-backed
5
- # rather than a plain module ivar: registry-binding happens at boot
6
- # time (single-threaded by construction), this happens at dispatch
7
- # time, which a future per-request caller (docs/rails-integration.md's
8
- # still-unbuilt `WebDoor`) needs to bind safely under concurrency.
9
- #
10
- # A child `Thread.new` spawned mid-block does not inherit this — plain
11
- # Ruby thread-local semantics — but nothing in this codebase spawns a
12
- # worker thread mid-dispatch today, so this is a fact worth naming
13
- # rather than a gap worth solving.
3
+ # Who is dispatching, bound for the duration of a block.
4
+ # `Thread.current`-backed, so per-request callers bind safely under concurrency;
5
+ # a child thread spawned mid-block does not inherit it.
14
6
  module Caller
15
- # `actor_id` is optional, on top of the role that has always been
16
- # here — a caller that states only a role (every caller before this)
17
- # is checked the way it always was, string equality against the
18
- # command's own `role`. A caller that also names who it is lets
19
- # `CommandRules::Authorization` check a real Governance
20
- # `RoleAssignment` instead, once the command's domain has Governance
21
- # attached — see that module's own header for the full split.
7
+ # The bound caller: a role, plus optional `actor_id`, `as_of` and `scope`.
22
8
  #
23
- # `as_of` and `scope` are both optional too, and both self-asserted
24
- # by the caller rather than derived from the command — deliberately:
25
- # `as_of` is filled at the door from `Ports::Clock.now`, never
26
- # inside the interpreter (see `Ports::Clock`'s own header), so
27
- # there is no other place to source it from. `scope` stays here
28
- # rather than becoming a command-level DSL construct on purpose —
29
- # a scope check that lived in the bluebook would put an
30
- # authorization concern inside the domain declaration itself; this
31
- # keeps it an application-boundary fact instead, the same shape
32
- # `role`/`actor_id` already are. The tradeoff: a caller states what
33
- # scope it is acting in, and `holds_role?` verifies a live grant
34
- # exists for that scope — it does not independently confirm the
35
- # scope matches whatever the command's own target data belongs to.
9
+ # All but `role` are self-asserted by the caller, not derived from the command;
10
+ # `scope` is deliberately not a DSL construct, and `holds_role?` checks only that a grant
11
+ # exists for it, not that it matches the command's target data.
36
12
  Current = Struct.new(:role, :actor_id, :as_of, :scope, keyword_init: true)
37
13
 
38
14
  module_function
@@ -43,27 +19,17 @@ module Hecks
43
19
  # block is on the stack for this thread
44
20
  def current = Thread.current[:hecks_caller]
45
21
 
46
- # Binds the ambient caller for the duration of the block, restoring whatever
47
- # caller (if any) was bound before, even if the block raises.
22
+ # Binds the ambient caller for the duration of the block, restoring the previous
23
+ # one even if the block raises.
48
24
  #
49
- # `actor_id`, `as_of` and `scope` are each optional, self-asserted facts about
50
- # the caller rather than derived from the command — see `Current`'s own header
51
- # above for why each stays here instead of becoming a command-level DSL
52
- # construct.
53
- #
54
- # @param role [String] the role the caller states it holds, checked by string
55
- # equality against the command's declared `role`
56
- # @param actor_id [String, nil] who the caller is; when given, lets
57
- # `CommandRules::Authorization` check a real Governance `RoleAssignment`
58
- # instead of the bare role-string comparison
59
- # @param as_of [Integer, nil] Unix epoch seconds the caller asserts as "now",
60
- # filled at the door from `Ports::Clock.now`; nil leaves a `RoleAssignment`'s
61
- # `starts_at` unchecked
62
- # @param scope [String, nil] the scope the caller states it is acting in,
63
- # checked against the matching `RoleAssignment`'s own `scope`; nil skips
64
- # that check
65
- # @yield the code that should see `role`/`actor_id`/`as_of`/`scope` as the
66
- # ambient caller
25
+ # @param role [String] the role the caller holds, checked against the command's `role`
26
+ # @param actor_id [String, nil] who the caller is; when given, `CommandRules::Authorization`
27
+ # checks a real Governance `RoleAssignment`
28
+ # @param as_of [Integer, nil] Unix epoch seconds taken as "now"; nil leaves a
29
+ # `RoleAssignment`'s `starts_at` unchecked
30
+ # @param scope [String, nil] the scope acted in, checked against the `RoleAssignment`'s
31
+ # `scope`; nil skips that check
32
+ # @yield the code that sees these values as the ambient caller
67
33
  # @return [Object] the block's result
68
34
  def as(role:, actor_id: nil, as_of: nil, scope: nil)
69
35
  previous = Thread.current[:hecks_caller]
@@ -75,14 +41,8 @@ module Hecks
75
41
  Thread.current[:hecks_caller] = previous
76
42
  end
77
43
 
78
- # Clears the ambient caller for the duration of the block, restoring
79
- # whatever caller (if any) was bound before, even if the block raises.
80
- #
81
- # A reaction is the system acting, not the caller who happened to be
82
- # on the stack when the triggering command ran — `Dispatcher#reenter`
83
- # clears the ambient caller around a reaction's own dispatch so a
84
- # triggering caller's role can neither satisfy nor block a reaction
85
- # command it has nothing to do with.
44
+ # Clears the ambient caller for the duration of the block, restoring the previous one.
45
+ # A reaction is the system acting, so `Dispatcher#reenter` clears the caller around it.
86
46
  #
87
47
  # @yield the code that should see no ambient caller bound
88
48
  # @return [Object] the block's result
@@ -1,18 +1,7 @@
1
1
  module Hecks
2
2
  module Runtime
3
- # Which ports a boot can actually fulfill, read off the registry it already
4
- # holds. `registry.ports` names every port a domain declared a dependency
5
- # on ; `registry.adapters` names every adapter wired to implement one — the
6
- # same `adapter.port == port.name` match `Ports::Extraction` and
7
- # `Ports::IdentityGeneration` already make for themselves, one at a time,
8
- # each time they resolve. This is that same question asked once, for
9
- # every port at once, so a gap in the wiring is something a caller can ask
10
- # about rather than something a live dispatch discovers by refusing.
11
- #
12
- # `cycles` answers `[]`, always, and honestly: nothing in this port model
13
- # lets one port depend on another — a port names a verb and a signal, an
14
- # adapter names the port it implements, and neither can point at a third
15
- # port. There is no edge for a cycle to be made of.
3
+ # Which ports a boot can fulfill: each declared port matched to the adapters wired to it,
4
+ # so a wiring gap can be asked about before a dispatch refuses.
16
5
  class CapabilityGraph
17
6
  # @param registry [Runtime::Registry] the booted registry to read ports and adapters from
18
7
  # @return [void]
@@ -20,9 +9,7 @@ module Hecks
20
9
  @registry = registry
21
10
  end
22
11
 
23
- # { port name => [adapter name, ...] }, for every port the registry
24
- # declares — including the ports nothing implements, so a caller can
25
- # tell "declared, unfulfilled" from "never declared at all".
12
+ # Maps each declared port's name to the names of its adapters, unfulfilled ports included.
26
13
  #
27
14
  # @return [Hash{String => Array<String>}] each declared port's name mapped to the names
28
15
  # of every adapter bound to it (empty when none are)
@@ -30,17 +17,14 @@ module Hecks
30
17
  @fulfillments ||= @registry.ports.each_key.to_h { |name| [name, adapters_for(name)] }
31
18
  end
32
19
 
33
- # The ports with zero adapters bound — the gap `Runtime::WiringError`
34
- # would otherwise only surface at the moment something tries to dispatch
35
- # through one.
20
+ # The ports with zero adapters bound.
36
21
  #
37
22
  # @return [Array<String>] the names of every declared port with no adapter bound to it
38
23
  def unfulfilled
39
24
  fulfillments.select { |_, adapters| adapters.empty? }.keys
40
25
  end
41
26
 
42
- # Reports the port-dependency cycles this registry has — always none; see the module
43
- # header for why no port model here can express one.
27
+ # Reports the port-dependency cycles; always none, as a port cannot depend on another port.
44
28
  #
45
29
  # @return [Array] always empty
46
30
  def cycles = []