hecks 2.8.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (404) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +4 -6
  3. data/lib/hecks/adapters/driven/claude_code.rb +4 -104
  4. data/lib/hecks/adapters/driven/d1.rb +44 -237
  5. data/lib/hecks/adapters/driven/folder.rb +17 -76
  6. data/lib/hecks/adapters/driven/google_authentication.rb +15 -49
  7. data/lib/hecks/adapters/driven/governance_authorization.rb +23 -70
  8. data/lib/hecks/adapters/driven/heki/journal.rb +8 -41
  9. data/lib/hecks/adapters/driven/heki/saga_store.rb +12 -54
  10. data/lib/hecks/adapters/driven/heki/snapshot.rb +4 -12
  11. data/lib/hecks/adapters/driven/heki.rb +13 -42
  12. data/lib/hecks/adapters/driven/identity_registry.rb +3 -14
  13. data/lib/hecks/adapters/driven/in_memory_ordering.rb +9 -26
  14. data/lib/hecks/adapters/driven/in_process_key_vault.rb +2 -14
  15. data/lib/hecks/adapters/driven/lambda/client.rb +10 -51
  16. data/lib/hecks/adapters/driven/lambda.rb +10 -84
  17. data/lib/hecks/adapters/driven/local_storage.rb +5 -40
  18. data/lib/hecks/adapters/driven/memory.rb +17 -98
  19. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +5 -21
  20. data/lib/hecks/adapters/driven/postgres/codec.rb +6 -28
  21. data/lib/hecks/adapters/driven/postgres/outbox.rb +3 -12
  22. data/lib/hecks/adapters/driven/postgres/reconnect.rb +4 -30
  23. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +34 -108
  24. data/lib/hecks/adapters/driven/postgres.rb +145 -134
  25. data/lib/hecks/adapters/driven/postgres_era.adapter +4 -15
  26. data/lib/hecks/adapters/driven/prism.rb +7 -30
  27. data/lib/hecks/adapters/driven/sql_query_builder.rb +21 -121
  28. data/lib/hecks/adapters/driven/sqlite/codec.rb +8 -40
  29. data/lib/hecks/adapters/driven/sqlite/projection.rb +26 -82
  30. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +40 -84
  31. data/lib/hecks/adapters/driven/sqlite.rb +128 -65
  32. data/lib/hecks/adapters/driven/tenant_provisioner.rb +5 -48
  33. data/lib/hecks/adapters/driven.rb +5 -36
  34. data/lib/hecks/adapters/driving/github_webhook.rb +8 -76
  35. data/lib/hecks/behaviors/dsl.rb +7 -25
  36. data/lib/hecks/behaviors/expectations.rb +31 -141
  37. data/lib/hecks/behaviors/ir.rb +7 -20
  38. data/lib/hecks/behaviors/rspec.rb +6 -17
  39. data/lib/hecks/behaviors/runner.rb +10 -34
  40. data/lib/hecks/behaviors.rb +4 -8
  41. data/lib/hecks/bluebook/aggregate.rb +9 -75
  42. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +9 -38
  43. data/lib/hecks/bluebook/assembly/build.rb +4 -15
  44. data/lib/hecks/bluebook/assembly/contract.rb +15 -136
  45. data/lib/hecks/bluebook/assembly/contracts.rb +54 -174
  46. data/lib/hecks/bluebook/assembly/marks.rb +31 -223
  47. data/lib/hecks/bluebook/assembly/specializer.rb +6 -52
  48. data/lib/hecks/bluebook/assembly.rb +6 -45
  49. data/lib/hecks/bluebook/attribute.rb +12 -61
  50. data/lib/hecks/bluebook/behaviour/aggregate.rb +10 -39
  51. data/lib/hecks/bluebook/behaviour/attribute.rb +3 -14
  52. data/lib/hecks/bluebook/behaviour/chapter.rb +14 -42
  53. data/lib/hecks/bluebook/behaviour/command.rb +14 -66
  54. data/lib/hecks/bluebook/behaviour/domain_port.rb +4 -20
  55. data/lib/hecks/bluebook/behaviour/entity.rb +7 -27
  56. data/lib/hecks/bluebook/behaviour/hexagon.rb +23 -41
  57. data/lib/hecks/bluebook/behaviour/lifecycle.rb +15 -40
  58. data/lib/hecks/bluebook/behaviour/policy.rb +23 -52
  59. data/lib/hecks/bluebook/behaviour/process_manager.rb +30 -57
  60. data/lib/hecks/bluebook/behaviour/read_model.rb +25 -31
  61. data/lib/hecks/bluebook/behaviour/traits.rb +27 -64
  62. data/lib/hecks/bluebook/behaviour/value_object.rb +5 -24
  63. data/lib/hecks/bluebook/capabilities.rb +17 -49
  64. data/lib/hecks/bluebook/chapter.rb +10 -48
  65. data/lib/hecks/bluebook/command.rb +23 -81
  66. data/lib/hecks/bluebook/domain_port.rb +14 -63
  67. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +50 -229
  68. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +50 -377
  69. data/lib/hecks/bluebook/dsl/attribute_collector.rb +33 -204
  70. data/lib/hecks/bluebook/dsl/binding_proxy.rb +16 -69
  71. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +86 -708
  72. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +56 -170
  73. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
  74. data/lib/hecks/bluebook/dsl/command_builder.rb +74 -546
  75. data/lib/hecks/bluebook/dsl/const_shim.rb +8 -41
  76. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +12 -87
  77. data/lib/hecks/bluebook/dsl/entity_builder.rb +44 -330
  78. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +20 -269
  79. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +26 -194
  80. data/lib/hecks/bluebook/dsl/identity_declaration.rb +13 -88
  81. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +6 -20
  82. data/lib/hecks/bluebook/dsl/policy_builder.rb +16 -88
  83. data/lib/hecks/bluebook/dsl/port_builder.rb +2 -6
  84. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +22 -96
  85. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +31 -184
  86. data/lib/hecks/bluebook/dsl/query_builder.rb +11 -42
  87. data/lib/hecks/bluebook/dsl/read_model_builder.rb +75 -201
  88. data/lib/hecks/bluebook/dsl/rule_reference.rb +22 -163
  89. data/lib/hecks/bluebook/dsl/translation_builder.rb +28 -114
  90. data/lib/hecks/bluebook/dsl/value_object_builder.rb +10 -124
  91. data/lib/hecks/bluebook/dsl/word_gate.rb +16 -162
  92. data/lib/hecks/bluebook/dsl/world_builder.rb +19 -111
  93. data/lib/hecks/bluebook/entity.rb +13 -71
  94. data/lib/hecks/bluebook/expression/ast_json.rb +24 -190
  95. data/lib/hecks/bluebook/expression/ast_reader.rb +12 -54
  96. data/lib/hecks/bluebook/expression/canonical_form.rb +11 -88
  97. data/lib/hecks/bluebook/expression/evaluator.rb +47 -295
  98. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +30 -147
  99. data/lib/hecks/bluebook/expression/resolver.rb +37 -660
  100. data/lib/hecks/bluebook/hexagon.rb +5 -28
  101. data/lib/hecks/bluebook/lifecycle.rb +5 -16
  102. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -7
  103. data/lib/hecks/bluebook/meta_validator/judge.rb +111 -361
  104. data/lib/hecks/bluebook/meta_validator/plan.rb +33 -162
  105. data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -7
  106. data/lib/hecks/bluebook/meta_validator/readings.rb +56 -342
  107. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -176
  108. data/lib/hecks/bluebook/meta_validator/shapes.rb +53 -267
  109. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +43 -305
  110. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +7 -43
  111. data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -18
  112. data/lib/hecks/bluebook/meta_validator.rb +79 -349
  113. data/lib/hecks/bluebook/model_check/client_profile.rb +6 -21
  114. data/lib/hecks/bluebook/model_check.rb +100 -597
  115. data/lib/hecks/bluebook/pattern_subset.rb +11 -69
  116. data/lib/hecks/bluebook/process_manager.rb +13 -83
  117. data/lib/hecks/bluebook/project_loader.rb +3 -39
  118. data/lib/hecks/bluebook/project_register.rb +12 -60
  119. data/lib/hecks/bluebook/query.rb +9 -48
  120. data/lib/hecks/bluebook/read_model.rb +57 -81
  121. data/lib/hecks/bluebook/reference.rb +7 -41
  122. data/lib/hecks/bluebook/smoke_test.rb +8 -98
  123. data/lib/hecks/bluebook/synthesizer.rb +13 -62
  124. data/lib/hecks/bluebook/translation.rb +26 -62
  125. data/lib/hecks/bluebook/value_object.rb +11 -36
  126. data/lib/hecks/bluebook.rb +5 -22
  127. data/lib/hecks/cache_dir.rb +4 -23
  128. data/lib/hecks/cli/document.rb +3 -21
  129. data/lib/hecks/cli/ir.rb +3 -5
  130. data/lib/hecks/cli/mcp.rb +2 -3
  131. data/lib/hecks/cli/mcp_door.rb +19 -114
  132. data/lib/hecks/cli/model_check.rb +18 -85
  133. data/lib/hecks/cli/project_cli.rb +7 -40
  134. data/lib/hecks/cli/project_diagrams.rb +0 -7
  135. data/lib/hecks/cli/run.rb +12 -58
  136. data/lib/hecks/cli/smoke_test.rb +2 -16
  137. data/lib/hecks/cli/stores.rb +1 -2
  138. data/lib/hecks/cli.rb +18 -24
  139. data/lib/hecks/construct.rb +8 -34
  140. data/lib/hecks/deploy/bluebook/deploy.bluebook +33 -142
  141. data/lib/hecks/deploy/bluebook/deploy.hecksagon +2 -9
  142. data/lib/hecks/embryonaut_bluebook/lock.rb +3 -35
  143. data/lib/hecks/embryonaut_bluebook/shape.rb +1 -17
  144. data/lib/hecks/embryonaut_bluebook/vendor.rb +8 -53
  145. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +4 -19
  146. data/lib/hecks/embryonaut_bluebook.rb +4 -71
  147. data/lib/hecks/facade/cli_door.rb +24 -106
  148. data/lib/hecks/facade/cli_runner.rb +28 -172
  149. data/lib/hecks/facade/command_request.rb +13 -33
  150. data/lib/hecks/facade/handle.rb +34 -121
  151. data/lib/hecks/facade/json_door.rb +31 -146
  152. data/lib/hecks/facade/surface/aggregate_door.rb +39 -104
  153. data/lib/hecks/facade/surface/chapter.rb +10 -54
  154. data/lib/hecks/facade/surface.rb +8 -23
  155. data/lib/hecks/facade.rb +10 -25
  156. data/lib/hecks/forms/app.rb +15 -70
  157. data/lib/hecks/forms/command_form_renderer.rb +10 -29
  158. data/lib/hecks/forms/field_renderer.rb +17 -156
  159. data/lib/hecks/forms/field_shape.rb +30 -222
  160. data/lib/hecks/forms/html.rb +12 -47
  161. data/lib/hecks/forms/index_renderer.rb +1 -4
  162. data/lib/hecks/forms/page.rb +3 -9
  163. data/lib/hecks/forms/params.rb +14 -60
  164. data/lib/hecks/forms/port_argument.rb +2 -19
  165. data/lib/hecks/forms/query_form_renderer.rb +4 -79
  166. data/lib/hecks/forms/record_renderer.rb +8 -75
  167. data/lib/hecks/forms/record_table.rb +7 -50
  168. data/lib/hecks/forms/reference_options.rb +3 -30
  169. data/lib/hecks/forms/value_object_shape.rb +8 -35
  170. data/lib/hecks/forms.rb +5 -33
  171. data/lib/hecks/fqn.rb +5 -12
  172. data/lib/hecks/framework/bluebook/compliance.bluebook +16 -65
  173. data/lib/hecks/framework/bluebook/console_settings.bluebook +47 -134
  174. data/lib/hecks/framework/bluebook/framework.hecksagon +5 -14
  175. data/lib/hecks/framework/bluebook/governance.bluebook +13 -34
  176. data/lib/hecks/framework/bluebook/identity.bluebook +11 -26
  177. data/lib/hecks/framework/bluebook/privacy.bluebook +8 -33
  178. data/lib/hecks/framework.rb +11 -80
  179. data/lib/hecks/freezer.rb +5 -39
  180. data/lib/hecks/grammar/expression.bluebook +15 -40
  181. data/lib/hecks/grammar/translation.bluebook +3 -6
  182. data/lib/hecks/grammar.rb +16 -41
  183. data/lib/hecks/ir.rb +14 -83
  184. data/lib/hecks/language/adapter.bluebook +2 -7
  185. data/lib/hecks/language/bluebook/aggregate.bluebook +56 -221
  186. data/lib/hecks/language/bluebook/attaches/paging.bluebook +6 -15
  187. data/lib/hecks/language/bluebook/bluebook.bluebook +15 -83
  188. data/lib/hecks/language/bluebook/bluebook.hecksagon +3 -15
  189. data/lib/hecks/language/bluebook/command.bluebook +27 -144
  190. data/lib/hecks/language/bluebook/entity.bluebook +24 -97
  191. data/lib/hecks/language/bluebook/policy.bluebook +16 -67
  192. data/lib/hecks/language/bluebook/process_manager.bluebook +28 -151
  193. data/lib/hecks/language/bluebook/projection.bluebook +126 -60
  194. data/lib/hecks/language/bluebook/query.bluebook +12 -43
  195. data/lib/hecks/language/bluebook/shape.bluebook +23 -73
  196. data/lib/hecks/language/bluebook/syntax.bluebook +51 -262
  197. data/lib/hecks/language/bluebook/vocabulary.bluebook +82 -197
  198. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +3 -10
  199. data/lib/hecks/language/hecksagon/domain_port.bluebook +4 -19
  200. data/lib/hecks/language/hecksagon/hecksagon.bluebook +6 -25
  201. data/lib/hecks/language/hecksagon/port_operation.bluebook +3 -14
  202. data/lib/hecks/language/oidc.json +35 -0
  203. data/lib/hecks/language/port.bluebook +4 -17
  204. data/lib/hecks/language/translation/translation.bluebook +3 -9
  205. data/lib/hecks/language/translation/translation_aggregate.bluebook +5 -24
  206. data/lib/hecks/language/world/wiring.bluebook +1 -3
  207. data/lib/hecks/language/world/world.bluebook +5 -7
  208. data/lib/hecks/literal.rb +8 -50
  209. data/lib/hecks/mcp_door_scope.rb +9 -90
  210. data/lib/hecks/mcp_stdio_guard.rb +4 -41
  211. data/lib/hecks/naming.rb +29 -137
  212. data/lib/hecks/ports/access_control.rb +7 -23
  213. data/lib/hecks/ports/agent/answers.rb +5 -23
  214. data/lib/hecks/ports/agent.rb +47 -172
  215. data/lib/hecks/ports/authentication.rb +12 -39
  216. data/lib/hecks/ports/authorization.rb +16 -43
  217. data/lib/hecks/ports/clock.rb +5 -45
  218. data/lib/hecks/ports/extraction.rb +7 -12
  219. data/lib/hecks/ports/identity_assignment.rb +8 -29
  220. data/lib/hecks/ports/identity_generation.rb +6 -29
  221. data/lib/hecks/ports/identity_resolution.rb +2 -10
  222. data/lib/hecks/ports/key_vault.rb +9 -45
  223. data/lib/hecks/ports/persistence/append_only.rb +61 -64
  224. data/lib/hecks/ports/persistence/binding_policy.rb +4 -14
  225. data/lib/hecks/ports/persistence/codec_boundary.rb +20 -80
  226. data/lib/hecks/ports/persistence/null_saga_store.rb +4 -20
  227. data/lib/hecks/ports/persistence/plugin.rb +13 -42
  228. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +38 -141
  229. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +15 -115
  230. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +16 -84
  231. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +6 -25
  232. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -22
  233. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +55 -181
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/events.rb +77 -0
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +12 -122
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +28 -128
  237. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +73 -474
  238. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +31 -124
  239. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +45 -219
  240. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +13 -108
  241. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +18 -75
  242. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +9 -49
  243. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +6 -123
  244. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +23 -55
  245. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +14 -72
  246. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +7 -16
  247. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +14 -115
  248. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +13 -45
  249. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +127 -554
  250. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +13 -63
  251. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +2 -17
  252. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +3 -14
  253. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +13 -58
  254. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -17
  255. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -26
  256. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +9 -25
  257. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -124
  258. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +34 -123
  259. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +5 -12
  260. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +3 -9
  261. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -14
  262. data/lib/hecks/ports/persistence/plugins/era.rb +4 -23
  263. data/lib/hecks/ports/persistence/postgres_dump/connection.rb +7 -23
  264. data/lib/hecks/ports/persistence/postgres_dump.rb +4 -35
  265. data/lib/hecks/ports/persistence/remote_runtime.rb +2 -24
  266. data/lib/hecks/ports/persistence/repository_factory.rb +8 -27
  267. data/lib/hecks/ports/persistence/state_codec.rb +23 -103
  268. data/lib/hecks/ports/persistence.rb +7 -14
  269. data/lib/hecks/ports/projection.rb +42 -84
  270. data/lib/hecks/ports/query/in_memory.rb +10 -58
  271. data/lib/hecks/ports/query/ordering.rb +11 -32
  272. data/lib/hecks/ports/query.rb +14 -37
  273. data/lib/hecks/ports.rb +2 -8
  274. data/lib/hecks/projections/bootstrap_table.rb +11 -43
  275. data/lib/hecks/projections/deploy/fargate/assembly.rb +2 -15
  276. data/lib/hecks/projections/deploy/fargate/cdn.rb +1 -48
  277. data/lib/hecks/projections/deploy/fargate/cdn_default.rb +2 -11
  278. data/lib/hecks/projections/deploy/fargate/check.rb +5 -14
  279. data/lib/hecks/projections/deploy/fargate/containers.rb +3 -78
  280. data/lib/hecks/projections/deploy/fargate/extras.rb +3 -44
  281. data/lib/hecks/projections/deploy/fargate/monitoring.rb +0 -44
  282. data/lib/hecks/projections/deploy/fargate/settings.rb +2 -54
  283. data/lib/hecks/projections/deploy/fargate/yaml.rb +13 -58
  284. data/lib/hecks/projections/deploy/fargate.rb +35 -202
  285. data/lib/hecks/projections/deploy/lambda.rb +150 -678
  286. data/lib/hecks/projections/deploy/preview/access_yaml.rb +1 -33
  287. data/lib/hecks/projections/deploy/preview/containers.rb +6 -111
  288. data/lib/hecks/projections/deploy/preview/routing_yaml.rb +4 -56
  289. data/lib/hecks/projections/deploy/preview/script.rb +2 -14
  290. data/lib/hecks/projections/deploy/preview/settings.rb +3 -33
  291. data/lib/hecks/projections/deploy/preview/task_yaml.rb +2 -38
  292. data/lib/hecks/projections/deploy/preview/template.rb +2 -58
  293. data/lib/hecks/projections/deploy/preview.rb +2 -67
  294. data/lib/hecks/projections/deploy/scripts/settings.rb +3 -25
  295. data/lib/hecks/projections/deploy/scripts.rb +4 -57
  296. data/lib/hecks/projections/deploy/shared.rb +47 -146
  297. data/lib/hecks/projections/deploy/smoke.rb +4 -61
  298. data/lib/hecks/projections/deploy/template_diff/comparer.rb +3 -25
  299. data/lib/hecks/projections/deploy/template_diff/loader.rb +2 -11
  300. data/lib/hecks/projections/deploy/template_diff.rb +5 -57
  301. data/lib/hecks/projections/diagrams.rb +20 -582
  302. data/lib/hecks/projections/glossary/html.rb +14 -130
  303. data/lib/hecks/projections/glossary/markdown.rb +8 -69
  304. data/lib/hecks/projections/glossary/mermaid.rb +9 -66
  305. data/lib/hecks/projections/glossary/sensitivity.rb +5 -34
  306. data/lib/hecks/projections/glossary/sentences.rb +14 -159
  307. data/lib/hecks/projections/glossary.rb +30 -222
  308. data/lib/hecks/projections/ir.rb +3 -10
  309. data/lib/hecks/projections/model/deviations.rb +15 -42
  310. data/lib/hecks/projections/model.rb +12 -51
  311. data/lib/hecks/projections/oidc.rb +12 -56
  312. data/lib/hecks/projections/parser_table.rb +9 -28
  313. data/lib/hecks/projections/reference.rb +2 -15
  314. data/lib/hecks/projections/rust_vocabulary.rb +12 -250
  315. data/lib/hecks/projections/shape.rb +3 -29
  316. data/lib/hecks/projections/statements.rb +7 -86
  317. data/lib/hecks/projections/vocabulary.rb +6 -22
  318. data/lib/hecks/projections.rb +2 -16
  319. data/lib/hecks/projector/cli_projector.rb +60 -375
  320. data/lib/hecks/projector/docs_projector.rb +10 -201
  321. data/lib/hecks/projector/exporter.rb +42 -134
  322. data/lib/hecks/projector/ir_projector.rb +2 -9
  323. data/lib/hecks/projector/narrate_projector.rb +15 -189
  324. data/lib/hecks/projector/target.rb +13 -76
  325. data/lib/hecks/projector.rb +30 -121
  326. data/lib/hecks/query_specification/common/comparators.rb +5 -26
  327. data/lib/hecks/query_specification/common/comparison.rb +35 -143
  328. data/lib/hecks/query_specification/common/dsl.rb +8 -24
  329. data/lib/hecks/query_specification/common/null_policy.rb +20 -70
  330. data/lib/hecks/query_specification/common/options.rb +2 -6
  331. data/lib/hecks/query_specification/common/where_clause.rb +2 -10
  332. data/lib/hecks/query_specification/field_path.rb +37 -77
  333. data/lib/hecks/query_specification/hop_path.rb +12 -76
  334. data/lib/hecks/release/gem_pin.rb +2 -26
  335. data/lib/hecks/release/runner/ci_publisher.rb +3 -6
  336. data/lib/hecks/release/runner/commands.rb +2 -4
  337. data/lib/hecks/release/runner/gem_publisher.rb +2 -3
  338. data/lib/hecks/release/runner/npm_publisher.rb +6 -20
  339. data/lib/hecks/release/runner/preflight.rb +4 -19
  340. data/lib/hecks/release/runner/tagger.rb +2 -9
  341. data/lib/hecks/release/runner.rb +3 -32
  342. data/lib/hecks/rendering.rb +4 -26
  343. data/lib/hecks/router/namespace_installer.rb +12 -36
  344. data/lib/hecks/router.rb +4 -8
  345. data/lib/hecks/runtime/aggregate_lock.rb +8 -33
  346. data/lib/hecks/runtime/boot_gates.rb +3 -18
  347. data/lib/hecks/runtime/caller.rb +19 -59
  348. data/lib/hecks/runtime/capability_graph.rb +5 -21
  349. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
  350. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +23 -160
  351. data/lib/hecks/runtime/command_interpreter.rb +124 -306
  352. data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
  353. data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
  354. data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
  355. data/lib/hecks/runtime/command_rules/emission.rb +8 -21
  356. data/lib/hecks/runtime/command_rules/references.rb +23 -202
  357. data/lib/hecks/runtime/dependency_planning.rb +30 -102
  358. data/lib/hecks/runtime/dispatcher.rb +82 -241
  359. data/lib/hecks/runtime/entity_element.rb +40 -462
  360. data/lib/hecks/runtime/entity_interpreter.rb +64 -219
  361. data/lib/hecks/runtime/errors.rb +32 -115
  362. data/lib/hecks/runtime/event.rb +3 -25
  363. data/lib/hecks/runtime/identity.rb +18 -154
  364. data/lib/hecks/runtime/instance.rb +18 -150
  365. data/lib/hecks/runtime/interpreting.rb +11 -70
  366. data/lib/hecks/runtime/invocation.rb +28 -164
  367. data/lib/hecks/runtime/loader.rb +48 -186
  368. data/lib/hecks/runtime/outbox.rb +38 -261
  369. data/lib/hecks/runtime/policy_interpreter.rb +38 -200
  370. data/lib/hecks/runtime/port_operation_interpreter.rb +26 -87
  371. data/lib/hecks/runtime/query_interpreter.rb +45 -187
  372. data/lib/hecks/runtime/reaction_invocation.rb +36 -138
  373. data/lib/hecks/runtime/read_model_interpreter.rb +117 -208
  374. data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
  375. data/lib/hecks/runtime/reference_hop.rb +8 -81
  376. data/lib/hecks/runtime/refusal_wording.rb +8 -89
  377. data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
  378. data/lib/hecks/runtime/registry/verification.rb +52 -225
  379. data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
  380. data/lib/hecks/runtime/registry.rb +99 -244
  381. data/lib/hecks/runtime/remote_dispatcher.rb +37 -152
  382. data/lib/hecks/runtime/routing.rb +4 -15
  383. data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
  384. data/lib/hecks/runtime/saga_interpreter.rb +79 -263
  385. data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
  386. data/lib/hecks/runtime/tenant_check.rb +6 -68
  387. data/lib/hecks/runtime/tenant_scope.rb +3 -28
  388. data/lib/hecks/runtime/value/admission.rb +16 -67
  389. data/lib/hecks/runtime/value/coercion.rb +155 -641
  390. data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
  391. data/lib/hecks/runtime/value.rb +12 -84
  392. data/lib/hecks/runtime.rb +17 -65
  393. data/lib/hecks/storehouse.rb +141 -568
  394. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +8 -32
  395. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
  396. data/lib/hecks/vendoring/git_environment.rb +2 -8
  397. data/lib/hecks/vendoring/git_source.rb +4 -10
  398. data/lib/hecks/vendoring.rb +7 -46
  399. data/lib/hecks/version.rb +3 -14
  400. data/lib/hecks.rb +26 -81
  401. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
  402. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
  403. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
  404. metadata +3 -2
@@ -5,18 +5,10 @@ require_relative "git"
5
5
  module Hecks
6
6
  module Release
7
7
  class Runner
8
- # The checks that run before a release does anything: the tools are
9
- # installed, the checkout is a clean, current `main`, the gem and the
10
- # JavaScript client agree on a version, and the changelog names it.
11
- #
12
- # Every refusal says what to do about it.
8
+ # The checks that run before a release does anything: tools installed, a
9
+ # clean current `main`, matching versions, and a changelog entry.
13
10
  class Preflight
14
- # What the checks learned about the release.
15
- #
16
- # @!attribute [r] version
17
- # @return [String] the version `lib/hecks/version.rb` declares
18
- # @!attribute [r] sha
19
- # @return [String] the commit `HEAD` points at, which is also `origin/main`
11
+ # What the checks learned; `sha` is also `origin/main`, enforced by check_current.
20
12
  Facts = Struct.new(:version, :sha, keyword_init: true)
21
13
 
22
14
  INSTALL_HINTS = {
@@ -29,10 +21,6 @@ module Hecks
29
21
  VERSION_LINE = /^\s*VERSION\s*=\s*"(?<version>[^"]+)"/
30
22
  CLIENT_PACKAGE = "packages/hecks-client/package.json".freeze
31
23
 
32
- # @param root [String] the repository root
33
- # @param commands [#capture] runs the tool probes
34
- # @param git [Git] the repository the release is cut from
35
- # @param tools [Array<String>] the executables the release needs on PATH
36
24
  def initialize(root:, commands:, git:, tools:)
37
25
  @root = root
38
26
  @commands = commands
@@ -40,10 +28,7 @@ module Hecks
40
28
  @tools = tools
41
29
  end
42
30
 
43
- # Runs every check.
44
- #
45
- # @return [Facts] the version and commit the release is cut from
46
- # @raise [Refusal] on the first check that fails, naming the fix
31
+ # Runs every check; the first refusal stops it.
47
32
  def check!
48
33
  check_tools
49
34
  check_branch
@@ -7,21 +7,14 @@ module Hecks
7
7
  # Makes sure the release tag `vX.Y.Z` is an annotated tag on the release
8
8
  # commit and is on origin, creating and pushing it only when it is missing.
9
9
  class Tagger
10
- # @param git [Git] the repository the release is cut from
11
- # @param console [Console] asks before the tag is created or pushed
12
- # @param dry_run [Boolean] report what would happen and change nothing
13
10
  def initialize(git:, console:, dry_run:)
14
11
  @git = git
15
12
  @console = console
16
13
  @dry_run = dry_run
17
14
  end
18
15
 
19
- # Brings the tag to the state described above, saying what it did.
20
- #
21
- # @param facts [Preflight::Facts] the version and commit being released
22
- # @return [Boolean] false when the person declined the question, true otherwise
23
- # @raise [Refusal] if the tag already points at another commit, locally or on origin
24
- # @raise [CommandFailed] if creating or pushing the tag fails
16
+ # Brings the tag to the state described above, saying what it did; returns
17
+ # false only when the person declined the confirmation.
25
18
  def ensure!(facts)
26
19
  tag = "v#{facts.version}"
27
20
  local = local_commit(tag)
@@ -11,39 +11,10 @@ require_relative "runner/ci_publisher"
11
11
  module Hecks
12
12
  module Release
13
13
  # Performs the release once the release PR has merged: tags the merge
14
- # commit, publishes the gem to rubygems.org and gets the JavaScript client
15
- # published to npm.
14
+ # commit, publishes the gem, and gets the JS client published to npm.
16
15
  #
17
- # ## Why it exists
18
- #
19
- # A release is four steps that have to happen in one order, and only
20
- # the contributing guide remembered it. This runs them in that order, refuses to
21
- # start from a checkout that is not the merged `main`, and can be run again:
22
- # it asks the registries what is already published and skips it, so a run
23
- # that died between the gem and the package resumes at the package.
24
- #
25
- # ## Steps
26
- #
27
- # 1. Preflight: tools installed, on a clean `main` equal to `origin/main`,
28
- # the gem and the client at one version, the changelog naming it.
29
- # 2. Published state: which of the gem and the package are already out.
30
- # 3. Tag: an annotated `vX.Y.Z` on the release commit, pushed to origin.
31
- # 4. Gem: `bin/release_gem`, unchanged.
32
- # 5. npm, one of two ways:
33
- # - by default CI publishes: the tag triggers `publish-client.yml`, which
34
- # uses npm trusted publishing, and this waits for the version to appear
35
- # (`--no-wait` skips the wait);
36
- # - with `--npm-local` this publishes from the machine, with a
37
- # bypass-two-factor token 1Password holds: the way to make the first
38
- # publish, before trusted publishing can be configured, and the fallback
39
- # when CI is down.
40
- #
41
- # ## Injectable commands and time
42
- #
43
- # Every process is started through `commands`, any object answering
44
- # `capture(*argv, env:, chdir:)` and `run!(*argv, env:, chdir:)` like
45
- # {Commands}, so a spec drives the whole release with a recorder. The wait for
46
- # CI takes its clock and its pause as callables for the same reason.
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.
47
18
  class Runner
48
19
  # Which steps to run and how.
49
20
  class Options
@@ -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 = []