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,33 +5,11 @@ require_relative "../rendering"
5
5
 
6
6
  module Hecks
7
7
  module Runtime
8
- # **One dispatch, as data** — the verb, the receiver it is addressed to
9
- # (`target`, a `Routing::Envelope` or nil), and every fact the caller did
10
- # or did not offer. Built once per dispatch by `Invocation.from_call`, the
11
- # only place the runtime interprets the shape of a call (`to:` vs `with:`
12
- # vs the flat facts hash `dispatch_flat` carries, a port operation's
13
- # reference attribute
14
- # lifted into `to:`). `Runtime::Routing.envelope`/`.payload` delegate here
15
- # and hold none of that logic themselves.
16
- #
17
- # `facts` maps a fact name to exactly one of:
18
- #
19
- # - `Invocation::Absent` — the key was never offered (every declared
20
- # attribute the caller left out appears so)
21
- # - `Invocation::Null` — the key was offered with an explicit nil
22
- # - `Invocation::Present` — the key was offered with a value
23
- #
24
- # Keys are kept exactly as offered: `with:` keys are symbolized (as they
25
- # always were), a flat facts hash keeps whatever key the wire used. Undeclared keys a caller
26
- # offered are kept too — refusing them is
27
- # still `refuse_unknown_arguments`' job, a dispatch step, not this one.
28
- #
29
- # I1 changes no behavior: every interpreter still reads `ctx.args`,
30
- # which is `#to_args` — the same Hash `Routing.payload` returns.
31
- # Roadmap I2 moves the Ruby `decode_arguments` step onto `facts` itself.
8
+ # One dispatch, as data: the verb, the resolved receiver (`target`, a
9
+ # `Routing::Envelope` or nil), and every fact the caller offered or didn't.
32
10
  Invocation = Data.define(:verb, :target, :facts)
33
11
 
34
- # The fact markers, the fact readers, and `from_call` — see above.
12
+ # Adds the fact wrappers and builders for an `Invocation`.
35
13
  class Invocation
36
14
  # A fact the caller offered with a real (non-nil) value.
37
15
  Present = Data.define(:value) do
@@ -42,7 +20,6 @@ module Hecks
42
20
  # The class of the two frozen marker singletons below — never
43
21
  # instantiated anywhere else.
44
22
  class Marker
45
- # @param name [String] the marker's own label, used by #inspect/#to_s
46
23
  def initialize(name)
47
24
  @name = name
48
25
  freeze
@@ -56,51 +33,24 @@ module Hecks
56
33
  Absent = Marker.new("Absent")
57
34
  Null = Marker.new("Null")
58
35
 
59
- # @param verb [String] the fully qualified verb this invocation dispatches
60
- # @param target [Routing::Envelope, nil] the resolved receiver, or nil when
61
- # the facts carry the identity themselves
62
- # @param facts [Hash{String, Symbol => Object}] every candidate fact, keyed by
63
- # offered (or declared) name; frozen, deep-duped, before being stored
36
+ # Dupes and freezes `facts` so a caller can't mutate them after
37
+ # construction.
64
38
  def initialize(verb:, target:, facts:)
65
39
  super(verb: verb, target: target, facts: facts.dup.freeze)
66
40
  end
67
41
 
68
- # Reads the fact recorded under `name`.
69
- #
70
- # The fact recorded under `name` — `Absent` for a name never offered
71
- # and never declared either.
72
- #
73
- # @param name [String, Symbol] the fact name to look up
74
- # @return [Invocation::Present, Invocation::Null, Invocation::Absent] the
75
- # recorded marker for `name`
42
+ # The fact recorded under `name`; `Absent` when never offered (and not
43
+ # declared either).
76
44
  def fact(name) = facts.fetch(name, Absent)
77
45
 
78
- # Reports whether `name` was offered with a real, non-nil value.
79
- #
80
- # @param name [String, Symbol] the fact name to check
81
- # @return [Boolean] true if `name` was offered with a non-nil value
82
46
  def present?(name) = fact(name).is_a?(Present)
83
47
 
84
- # Reports whether `name` was offered with an explicit nil.
85
- #
86
- # @param name [String, Symbol] the fact name to check
87
- # @return [Boolean] true if `name` was offered with an explicit nil
88
48
  def null?(name) = fact(name).equal?(Null)
89
49
 
90
- # Reports whether `name` was never offered.
91
- #
92
- # @param name [String, Symbol] the fact name to check
93
- # @return [Boolean] true if `name` was never offered (and, if declared, left out)
94
50
  def absent?(name) = fact(name).equal?(Absent)
95
51
 
96
- # A Present fact's value; nil for an explicit Null. Raises KeyError for
97
- # an Absent fact — "never offered" has no value, and answering nil
98
- # would re-conflate it with an explicit null, the very ambiguity this
99
- # type exists to remove. Ask `absent?`/`present?` first.
100
- #
101
- # @param name [String, Symbol] the fact name to read
102
- # @return [Object, nil] the offered value; nil for an explicit Null fact
103
- # @raise [KeyError] if `name` names an Absent fact (never offered)
52
+ # A Present fact's value (nil for Null). Raises for Absent rather than
53
+ # returning nil, so "never offered" isn't conflated with an explicit null.
104
54
  def value(name)
105
55
  case (found = fact(name))
106
56
  when Present then found.value
@@ -109,12 +59,8 @@ module Hecks
109
59
  end
110
60
  end
111
61
 
112
- # The flat args hash, byte for byte what `Routing.payload` returned
113
- # before this type existed: offered keys in offered order, Absent keys
114
- # omitted, Null keys mapped to nil. A fresh Hash every call.
115
- #
116
- # @return [Hash{String, Symbol => Object}] the offered facts by name; a fact
117
- # offered as nil is kept as nil
62
+ # The offered facts as a plain Hash, in offered order: Absent keys
63
+ # omitted, Null mapped to nil. A fresh Hash every call.
118
64
  def to_args
119
65
  facts.each_with_object({}) do |(name, found), args|
120
66
  next if found.equal?(Absent)
@@ -124,47 +70,10 @@ module Hecks
124
70
  end
125
71
 
126
72
  class << self
127
- # The one reading of a call's shape. `receiver:` picks which of the
128
- # three dispatch shapes this is, because each has always checked its
129
- # parts in its own order and a malformed call's refusal depends on
130
- # that order:
131
- #
132
- # :aggregate — facts first (`with:` checks), then `to:`
133
- # :entity — `to:` first, then the block (which resolves the
134
- # entity chain, UnknownVerb), then facts
135
- # :port — a reference attribute / identity field lifted into
136
- # `to:`, then `to:` (which a port operation requires),
137
- # then facts
138
- #
139
- # The block answers the declaring command or port operation (anything
140
- # with `hecks_name` and `attributes`); it is called exactly once, at
141
- # the point in that order where the caller always resolved it.
142
- # `aggregate:` is the owning aggregate construct, read for `:port`
143
- # only.
144
- #
145
- # @param verb [String] the fully qualified verb being dispatched
146
- # @param to [String, Hash, nil] a bare aggregate identity, or a Hash with
147
- # `aggregate:` and `entity:`/`entities:`; nil for no explicit receiver
148
- # @param with [Hash, nil] the command's facts, keyed by attribute name; nil
149
- # when the caller offers `flat` instead
150
- # @param flat [Hash] the command's facts as a flat args hash, used when `with`
151
- # is nil; for `:port`, may also carry the receiver identity under a
152
- # reference attribute's own name
153
- # @param receiver [Symbol] which of the three dispatch shapes this is:
154
- # `:aggregate`, `:entity` or `:port`
155
- # @param entity_depth [Integer] the number of entity-hop identities `to:`
156
- # must carry, for `:entity` only
157
- # @param aggregate [Bluebook::Aggregate, nil] the owning aggregate construct,
158
- # read for `:port` only
159
- # @yieldreturn [Class] the declaring command or port-operation class (anything
160
- # with `hecks_name` and `attributes`)
161
- # @return [Runtime::Invocation] the built invocation
162
- # @raise [Runtime::TypeMismatch] if `to:` is malformed, its entity count is
163
- # wrong, or (`:port` only) the operation resolves no receiving aggregate
164
- # @raise [Runtime::UnknownArgument] if `with:` names an attribute the command
165
- # does not declare
166
- # @raise [Runtime::AbsentArgument] if `with:` omits a required attribute
167
- # @raise [ArgumentError] if `receiver` is none of `:aggregate`, `:entity` or `:port`
73
+ # Builds an Invocation for one of three call shapes (:aggregate,
74
+ # :entity, :port). Each checks its own parts in a fixed order, because
75
+ # a malformed call's refusal depends on that order; `declaring` is
76
+ # called exactly once, at that shape's point in the order below.
168
77
  def from_call(verb, to:, with:, flat:, receiver: :aggregate, entity_depth: 0, aggregate: nil, &declaring)
169
78
  case receiver
170
79
  when :aggregate
@@ -182,16 +91,7 @@ module Hecks
182
91
  end
183
92
  end
184
93
 
185
- # `to:` as a `Routing::Envelope`, or nil when no `to:` was given.
186
- #
187
- # @param to [String, Hash, nil] a bare aggregate identity, or a Hash with
188
- # `aggregate:` and `entity:`/`entities:`; nil for no receiver
189
- # @param entity_depth [Integer] the number of entity-hop identities `to`
190
- # must carry
191
- # @return [Routing::Envelope, nil] the resolved envelope, or nil when `to`
192
- # is nil
193
- # @raise [Runtime::TypeMismatch] if `to` is malformed, or its entity count
194
- # does not match `entity_depth`
94
+ # `to` as a `Routing::Envelope`, or nil when no `to:` was given.
195
95
  def route(to, entity_depth: 0)
196
96
  return nil if to.nil?
197
97
 
@@ -210,24 +110,8 @@ module Hecks
210
110
  Routing::Envelope.new(aggregate: aggregate, entities: entities)
211
111
  end
212
112
 
213
- # The offered facts for `declaring`, as Absent/Null/Present — offered
214
- # keys first, in offered order, then every declared attribute that was
215
- # not offered, as Absent.
216
- #
217
- # @param declaring [Class] the command or port-operation class (anything
218
- # with `hecks_name` and `attributes`) whose declared attributes are read
219
- # @param with [Hash, nil] the command's facts, keyed by attribute name; nil
220
- # when the caller offers `flat` instead
221
- # @param flat [Hash] the command's facts as a flat args hash, used when `with`
222
- # is nil
223
- # @return [Hash{String, Symbol => Invocation::Absent, Invocation::Null,
224
- # Invocation::Present}] each candidate fact keyed by its offered (or
225
- # declared) name
226
- # @raise [Runtime::TypeMismatch] if both `with` and a non-empty `flat` are
227
- # given, or if `with` is not a Hash
228
- # @raise [Runtime::UnknownArgument] if `with:` names an attribute `declaring`
229
- # does not declare
230
- # @raise [Runtime::AbsentArgument] if `with:` omits a required attribute
113
+ # Every candidate fact for `declaring`, as Absent/Null/Present: offered
114
+ # keys first in offered order, then remaining declared attributes as Absent.
231
115
  def facts_for(declaring, with:, flat:)
232
116
  offered = offered_facts(declaring, with: with, flat: flat)
233
117
  facts = offered.each_with_object({}) do |(name, value), found|
@@ -242,19 +126,10 @@ module Hecks
242
126
 
243
127
  private
244
128
 
245
- # **The port operation shape** — moved from `Dispatcher#port_invocation`.
246
- #
247
129
  # A Reference-typed attribute naming the owning aggregate is routing,
248
130
  # not a fact: lifted out of the flat facts into `to:` when no `to:`
249
- # was given. A `to:`-declared operation carries no Reference
250
- # attribute at all (PortOperationBuilder#initialize's own comment),
251
- # so its receiver is read — not removed — from the plain attribute
252
- # named for the owner's first `identified_by` field: that attribute
253
- # is still a declared external fact `refuse_absent_arguments`
254
- # expects to find (Rust's own comment: "declare only external facts
255
- # with attribute"; a real AbsentArgument confirmed this before `[]`
256
- # replaced `delete`). Composite identity is not attempted — `.first`
257
- # only, no domain in the corpus needs more for a port operation.
131
+ # was given. Only `.first` of a composite identity is used — no
132
+ # domain in the corpus needs more for a port operation.
258
133
  def port_call(verb, aggregate, operation, to:, with:, flat:)
259
134
  flat = flat.dup
260
135
  identity = operation.identity_attribute(aggregate.hecks_name)
@@ -271,15 +146,9 @@ module Hecks
271
146
  new(verb: verb, target: target, facts: facts_for(operation, with: with, flat: flat))
272
147
  end
273
148
 
274
- # BUG#7 — a non-Hash `to:` must be a String, matching Rust's
275
- # `RoutingEnvelope::from_json` (kernel/routing.rs), which refuses
276
- # anything neither a JSON string nor object before the domain payload
277
- # is examined. Surfaced on `examples/roster`'s `Mark`, whose own
278
- # attribute is literally named `to`: a flat-facts dispatch steals
279
- # that key into this parameter, and without this check an out-of-range
280
- # Integer would be accepted as the aggregate identity, leaving Mark's
281
- # `to` fact absent (AbsentArgument in Ruby, TypeMismatch in Rust). See
282
- # spec/runtime/routing_envelope_shape_spec.rb.
149
+ # Must be a String, matching Rust's own parity check for `to:` — this
150
+ # keeps a same-named `to` attribute from being stolen as the
151
+ # aggregate identity by a flat-facts dispatch.
283
152
  def scalar_envelope(to)
284
153
  return [to, []] if to.is_a?(String)
285
154
 
@@ -294,13 +163,9 @@ module Hecks
294
163
  [hash[:aggregate], entity_identities(hash)]
295
164
  end
296
165
 
297
- # BUG#18 — an entity route naming no entity (`entities: []`, or
298
- # neither key) refuses here, unconditionally, before entity_depth is
299
- # consulted: for an aggregate-level command (depth 0), without this,
300
- # `[].size == 0` would satisfy the depth check and let the degenerate
301
- # Hash reach the command's own validation. Rust's `RoutingEnvelope::
302
- # from_json` always refused it at this point. A bare aggregate identity
303
- # String remains the ordinary aggregate-command shape.
166
+ # An entity route naming no entity is refused here, unconditionally,
167
+ # before entity_depth is checked — otherwise a depth-0 (aggregate)
168
+ # command would let the degenerate `entities: []` Hash slip through.
304
169
  def entity_identities(hash)
305
170
  raise TypeMismatch, "to: takes entity: or entities:, not both" if hash.key?(:entities) && hash.key?(:entity)
306
171
 
@@ -312,9 +177,8 @@ module Hecks
312
177
 
313
178
  # `with:` is deliberately strict: a caller choosing the explicit
314
179
  # envelope cannot smuggle receiver identity back into the payload,
315
- # and may not mix it with a flat facts hash. Without `with:`
316
- # (nil or false) the flat facts are the facts, unread —
317
- # whether `to:` was given never enters this decision (BUG#17).
180
+ # and may not mix it with a flat facts hash. Without `with:`, the
181
+ # flat facts are taken as-is, unread.
318
182
  def offered_facts(declaring, with:, flat:)
319
183
  if with && !flat.empty?
320
184
  raise TypeMismatch,
@@ -8,49 +8,20 @@ require_relative "registry"
8
8
 
9
9
  module Hecks
10
10
  module Runtime
11
- # The boot entry point: loads a bluebook directory (or an explicit
12
- # file list) into a fresh Registry, runs every registered boot gate
13
- # (era checks, saga rehydration, …), and hands back the Dispatcher
14
- # (or RemoteDispatcher, for a domain declaring `dispatched_by
15
- # ("Lambda")`) bound to it — installing the `Widget::Item.Add(...)`
16
- # facade sugar unless the caller opts out.
11
+ # Boots a domain: loads its bluebook directory into a fresh Registry, runs
12
+ # every boot gate, and hands back the bound Dispatcher (or RemoteDispatcher).
17
13
  class Loader
18
- # `install_facade:` defaults on — every ordinary caller wants
19
- # `Widget::Item.Add(...)` sugar. A caller that only ever dispatches
20
- # by FQN string (`SmokeTest`, the one caller so far) can pass
21
- # `false` to skip it: `Facade::Surface.install` puts a bare global
22
- # Ruby constant on `Object` per domain and per aggregate name, with
23
- # no scoping and no cleanup hook, so a tool booting arbitrary
24
- # throwaway domains under generic names ("Widget", "Item", "Tag")
25
- # would otherwise leak those names into the rest of the process —
26
- # measured, not hypothetical: this exact leak once made an
27
- # unrelated `dsl_spec.rb` example resolve a bare `Widget` constant
28
- # to a stale smoke-test facade from a deleted temp directory instead
29
- # of raising, corrupting that spec's own unrelated build. Skipping
30
- # the install is safe because nothing downstream of a raw
31
- # `Dispatcher` needs the sugar — `Dispatcher#dispatch`/`#query` work
32
- # identically either way.
33
- # `environment:` — see Adapters::Folder#load_domain's own comment
34
- # for the mechanism. hecks never reads ENV itself (every other
35
- # env-var lookup in this codebase lives in app-owned .world/
36
- # .hecksagon files, never library internals) — a caller resolves
37
- # its own env var name and passes the resulting string straight
38
- # through, e.g. `Hecks.boot(path, environment:
39
- # ENV.fetch("MYAPP_ENV", "development"))`.
14
+ # Boots `path`: loads its bluebook directory into a fresh Registry, runs
15
+ # every registered boot gate, and returns the bound dispatcher. Pass
16
+ # `install_facade: false` to skip the `Widget::Item.Add(...)` global
17
+ # facade sugar (only a caller dispatching by FQN string needs to).
40
18
  #
41
- # @param path [String] a domain directory to boot, resolved through
42
- # `Ports::Loading#bluebook_directory`
43
- # @param shared [String, nil] an explicit shared ports/adapters root override; nil
44
- # resolves it by walking up from the domain directory
45
- # @param install_facade [Boolean] whether to install the `Widget::Item.Add(...)`
46
- # facade sugar
47
- # @param environment [String, nil] the environment overlay to load after the
48
- # domain's own files (e.g. `"production"`); nil loads none
19
+ # @param path [String] a domain directory to boot
20
+ # @param shared [String, nil] shared ports/adapters root; nil walks up from `path`
21
+ # @param environment [String, nil] environment overlay loaded after the domain; nil for none
49
22
  # @return [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted dispatcher
50
- # @raise [Errno::ENOENT] if `path` names neither a domain directory nor one holding
51
- # a `bluebook/` subdirectory
52
- # @raise [Runtime::WiringError] if a boot gate finds a wiring problem (an undeclared
53
- # bind, a compute/rekey rule with no persistence plugin loaded, …)
23
+ # @raise [Errno::ENOENT] if `path` names no domain directory
24
+ # @raise [Runtime::WiringError] if a boot gate finds a wiring problem
54
25
  def self.boot(path, shared: nil, install_facade: true, environment: nil)
55
26
  loading = Ports::Loading.bootstrap
56
27
  directory = loading.bluebook_directory(path)
@@ -70,36 +41,20 @@ module Hecks
70
41
  install_facade ? bind_runtime(dispatcher) : dispatcher
71
42
  end
72
43
 
73
- # The outbox's boot-time reconciliation — after the dispatcher
74
- # exists (a row's consumer runs through its interpreters, so this
75
- # cannot be a plain registry gate the way saga rehydration is) and
76
- # after saga rehydration (a redriven row may advance a saga, which
77
- # must already be in memory). `pending` rows are redriven —
78
- # provably never started; `claimed` rows are surfaced, never
79
- # auto-redriven — see `Runtime::Outbox`. A remote dispatcher has
80
- # no local stores to scan.
81
- #
82
- # @param dispatcher [Runtime::Dispatcher, Runtime::RemoteDispatcher] the
83
- # just-booted dispatcher; a no-op unless it exposes an `outbox`
84
- # @return [void]
44
+ # Redrives pending outbox rows after the dispatcher and saga
45
+ # rehydration both exist (a redriven row may advance a saga, which
46
+ # must already be in memory). Claimed rows are surfaced, never
47
+ # auto-redriven — see `Runtime::Outbox`. A no-op for a remote dispatcher.
85
48
  def self.redrive_outbox!(dispatcher)
86
49
  return unless dispatcher.respond_to?(:outbox)
87
50
 
88
51
  dispatcher.outbox.redrive!
89
52
  end
90
53
 
91
- # Turns every `.hecksagon`-declared `AggregateDoor#mark_sensitive` fact into a real
92
- # `Privacy::Marking.Mark` — after the dispatcher exists (dispatch needs a bound
93
- # repository), idempotent across reboots (a marking already present is never
94
- # re-dispatched, the same "provably never started" restraint `redrive_outbox!`
95
- # holds itself to above). A no-op when nothing declared one, or when the domain
96
- # never attached Privacy at all.
97
- #
98
- # @param dispatcher [Runtime::Dispatcher, Runtime::RemoteDispatcher] the just-booted
99
- # dispatcher `Privacy::Marking.Mark` dispatches through
100
- # @param registry [Runtime::Registry] the just-booted registry `pending_privacy_markings`
101
- # was recorded on
102
- # @return [void]
54
+ # Turns every `.hecksagon`-declared `mark_sensitive` fact into a real
55
+ # `Privacy::Marking.Mark`. Idempotent across reboots — a marking already
56
+ # present is never re-dispatched. A no-op when nothing declared one, or
57
+ # the domain never attached Privacy at all.
103
58
  def self.seed_privacy_markings!(dispatcher, registry)
104
59
  return if registry.pending_privacy_markings.empty?
105
60
  return unless registry.bluebook("Privacy")
@@ -115,40 +70,16 @@ module Hecks
115
70
  end
116
71
  end
117
72
 
118
- # The explicit-file form — `paths` names the exact bluebook/hecksagon/
119
- # world files to boot, in place, wherever they actually live. `boot`
120
- # above only ever takes a directory and globs it; that is the right
121
- # shape for a real deployment (`examples/banking`, a domain someone
122
- # `cd`s into), and the wrong one for a caller that wants to declare a
123
- # narrow, explicit scope and have it booted exactly as declared — a
124
- # `.behaviors` file's own `loads` line is the motivating caller
125
- # (`Hecks::Behaviors`), but this carries no behaviors-specific
126
- # logic and is not gated behind requiring that module.
73
+ # Boots the exact bluebook/hecksagon/world files in `paths`, in place —
74
+ # unlike `boot`, no directory globbing and no copying into a temp dir
75
+ # (a `persisted_by` path must resolve against the real project root,
76
+ # not a copy's).
127
77
  #
128
- # No copying, no temp directory. A prior port of this same idea
129
- # (vendored into a downstream consumer, read before writing this)
130
- # scoped a per-test boot by copying files into `Dir.mktmpdir` — which
131
- # destroys real relative paths, and worse, makes a `persisted_by`
132
- # path resolve against the temp copy's root instead of the project's
133
- # own (confirmed there: a file adapter kept reading and writing the
134
- # same deterministic tmp copy across an entire session, because
135
- # `Hecks.boot`'s own `root` is always `File.dirname` of whatever
136
- # directory it was handed). `directory` here is `File.dirname` of the
137
- # first real path in `paths` — genuinely on disk, not a copy — so
138
- # every downstream path (`EraCheck`, `persisted_by`, `shared_root`)
139
- # resolves exactly as an ordinary directory boot's would.
140
- #
141
- # @param paths [Array<String>, String] the exact bluebook/hecksagon/world file paths
142
- # to boot, in the order they should load
143
- # @param shared [String, nil] an explicit shared ports/adapters root override; nil
144
- # resolves it by walking up from `File.dirname` of the first path
145
- # @param install_facade [Boolean] whether to install the `Widget::Item.Add(...)`
146
- # facade sugar
147
- # @param environment [String, nil] the environment overlay to load after the
148
- # named files (e.g. `"production"`); nil loads none
78
+ # @param paths [Array<String>, String] the exact file paths to boot, in load order
79
+ # @param shared [String, nil] shared ports/adapters root; nil walks up from the first path
80
+ # @param environment [String, nil] environment overlay loaded after the named files
149
81
  # @return [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted dispatcher
150
- # @raise [Runtime::WiringError] if a boot gate finds a wiring problem (an undeclared
151
- # bind, a compute/rekey rule with no persistence plugin loaded, …)
82
+ # @raise [Runtime::WiringError] if a boot gate finds a wiring problem
152
83
  def self.boot_files(paths, shared: nil, install_facade: true, environment: nil)
153
84
  loading = Ports::Loading.bootstrap
154
85
  files = Array(paths).map { |path| File.expand_path(path) }
@@ -167,31 +98,10 @@ module Hecks
167
98
  install_facade ? bind_runtime(dispatcher) : dispatcher
168
99
  end
169
100
 
170
- # Runs every registered boot gate against `registry`, in order.
171
- #
172
- # ADR 0031 — a per-boot `BootGates` instance holds exactly the gates this
173
- # registry's own bound adapters have a capability for, rather than two
174
- # hardcoded, unconditional calls. Ordering is
175
- # preserved: era-checking (when a persistence plugin contributes one)
176
- # still runs before `verify!`, saga rehydration still runs after
177
- # (conservative — see `SagaPersistence#rehydrate_sagas!`'s own
178
- # comment).
179
- #
180
- # ADR 0033 — this loader does not name `EraCheck`, or any other
181
- # era-specific class, at all. Every loaded persistence plugin
182
- # (`Ports::Persistence.each_plugin` — nothing here if nothing was
183
- # ever `require`d, or brought in by a bound adapter, see
184
- # `load_bound_adapters!`) is asked to contribute its own `:pre_verify`/
185
- # `:post_verify` gates generically; `:saga_rehydration` is the one
186
- # gate core still registers directly, because ADR 0031 already
187
- # proved it's not era-specific.
188
- #
189
- # @param registry [Runtime::Registry] the registry mid-boot, with its bluebooks,
190
- # hecksagons, ports, adapters and worlds already loaded
191
- # @param directory [String] the domain directory the gates report against in a refusal
192
- # @return [Runtime::BootGates] the gates instance built and run for this boot
193
- # @raise [Runtime::WiringError] if `registry.verify!` or any contributed gate finds
194
- # a wiring problem
101
+ # Runs every registered boot gate against `registry`, in order:
102
+ # era-checking (if a plugin contributes one) before `verify!`, saga
103
+ # rehydration after. No era-specific class is named here — each loaded
104
+ # persistence plugin contributes its own gates generically (ADR 0031).
195
105
  def self.run_boot_gates!(registry, directory)
196
106
  gates = BootGates.new
197
107
  load_bound_adapters!(registry)
@@ -208,17 +118,11 @@ module Hecks
208
118
  end
209
119
 
210
120
  # Resolves the Ruby implementation of every adapter a hecksagon binds.
211
- #
212
- # An adapter whose implementation loads on first mention (`PostgresEra`,
213
- # through the era plugin) registers its persistence plugin as a side effect
214
- # of that load. Resolving here, before the gates are collected, is what makes
215
- # a domain that declares `persisted_by "PostgresEra"` boot with the plugin
216
- # loaded and its era gates registered, without the app requiring the plugin
217
- # itself. An adapter with no Ruby implementation is left for `verify!`, which
218
- # refuses it with the bind it came from.
219
- #
220
- # @param registry [Runtime::Registry] the registry mid-boot, whose hecksagons name the binds
221
- # @return [void]
121
+ # An adapter's implementation can register its own persistence plugin as
122
+ # a side effect of loading (e.g. `PostgresEra`) — resolving here, before
123
+ # gates are collected, is what registers those plugins' gates without
124
+ # the app requiring them itself. A bind with no implementation is left
125
+ # for `verify!` to refuse.
222
126
  def self.load_bound_adapters!(registry)
223
127
  registry.hecksagons.each_value do |hexagon|
224
128
  hexagon.binds.each do |bind|
@@ -229,23 +133,10 @@ module Hecks
229
133
  end
230
134
  end
231
135
 
232
- # The one piece of the old, era-owned `check_compute_rules!` core
233
- # still carries — deliberately thinner. `registry.translations` is
234
- # plain `Bluebook::Translation`/`TranslationAggregate`/
235
- # `TranslationCompute`/`TranslationRekey` data (`bluebook/
236
- # translation.rb`, core, no era-specific class involved), so this
237
- # needs nothing plugin-specific to ask "does anything declare a
238
- # compute/rekey rule at all." A loaded persistence plugin (e.g. the
239
- # era plugin's own `:era_compute_rules` gate, registered above) runs
240
- # the real, adapter-aware version of this check and refuses by name
241
- # ("...is bound to Memory") long before this ever would; this only
242
- # fires when nothing did, because nothing was loaded to.
243
- #
244
- # @param registry [Runtime::Registry] the registry mid-boot, whose declared
245
- # `translations` are checked
246
- # @return [void]
247
- # @raise [Runtime::WiringError] if any translation declares a `computes`/`rekeys`
248
- # rule while no persistence plugin is loaded to interpret it
136
+ # A backstop only: fires when a translation declares a `computes`/
137
+ # `rekeys` rule but no persistence plugin is loaded to interpret it.
138
+ # Any loaded plugin's own compute-rules gate refuses earlier, by name,
139
+ # whenever one is loaded — this only runs when nothing was.
249
140
  def self.check_compute_rules_backstop!(registry)
250
141
  return if Ports::Persistence.plugins_loaded?
251
142
 
@@ -261,31 +152,11 @@ module Hecks
261
152
  end
262
153
  end
263
154
 
264
- # `RemoteDispatcher` for a domain routed through Lambda,
265
- # `Dispatcher` otherwise — the one place this decision gets
266
- # made, so everything built on top (`Handle`, `AggregateDoor`,
267
- # `Facade::Surface`) never has to know which class it's holding.
268
- # `registry.bluebooks.keys.first` is the just-booted domain's own
269
- # name (insertion order — the target's own bluebook loads before
270
- # any `uses_framework` chapter, `bin/project_rust`'s own header
271
- # draws the identical distinction), not a directory basename.
272
- #
273
- # `dispatched_by("Lambda")` is its own, explicit verb — not
274
- # inferred from `deployed_to("AwsLambda")`'s mere presence. Both
275
- # Banking's and Embryonaut's `.world` files already declare
276
- # `deployed_to("AwsLambda")` (it only means "a deploy target
277
- # exists"), so treating that alone as "boot this domain against
278
- # Lambda" would have silently rerouted Banking's every local
279
- # boot — every spec, every `bin/console` session — the moment
280
- # this landed. A domain opts in explicitly, the same way
281
- # `persisted_by("PostgresEra")` is never inferred from anything
282
- # else either.
283
- #
284
- # @param registry [Runtime::Registry] the just-booted registry, whose just-booted
285
- # domain's `.world` is checked for `dispatched_by("Lambda")`
286
- # @return [Runtime::Dispatcher, Runtime::RemoteDispatcher] a `RemoteDispatcher`
287
- # bound to `registry` if the domain declares `dispatched_by("Lambda")`, a plain
288
- # `Dispatcher` otherwise
155
+ # `RemoteDispatcher` for a domain declaring `dispatched_by("Lambda")`,
156
+ # `Dispatcher` otherwise. That's an explicit opt-in, never inferred from
157
+ # `deployed_to("AwsLambda")` alone — that verb only means a deploy
158
+ # target exists, and treating it as "dispatch via Lambda" would reroute
159
+ # every local boot of a domain that merely declares one.
289
160
  def self.dispatcher_for(registry)
290
161
  domain = registry.bluebooks.keys.first
291
162
  settings = registry.world(domain)&.for_verb("dispatched_by") || {}
@@ -294,18 +165,9 @@ module Hecks
294
165
  RemoteDispatcher.new(registry, region: settings.fetch(:region, "us-east-1"), function: settings[:function])
295
166
  end
296
167
 
297
- # Installs the facade sugar (`Widget::Item.Add(...)`), closed over
298
- # `dispatcher`, and hands `dispatcher` back.
299
- #
300
- # The door is installed here, not stamped: the facade's modules close
301
- # over this dispatcher, so the binding lives in the surface a boot
302
- # installs rather than in a class-level global (`ruby_class.runtime =`
303
- # on every aggregate's class) that would make two boots in one process
304
- # share one name.
305
- #
306
- # @param dispatcher [Runtime::Dispatcher, Runtime::RemoteDispatcher] the
307
- # just-booted dispatcher the facade should call into
308
- # @return [Runtime::Dispatcher, Runtime::RemoteDispatcher] `dispatcher`, unchanged
168
+ # Installs the facade sugar (`Widget::Item.Add(...)`) closed over
169
+ # `dispatcher`. The binding lives in the facade's own modules, not a
170
+ # class-level global, so two boots in one process don't share one name.
309
171
  def self.bind_runtime(dispatcher)
310
172
  Facade::Surface.install(dispatcher)
311
173
  dispatcher