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
@@ -6,127 +6,59 @@ require_relative "../value"
6
6
  module Hecks
7
7
  module Runtime
8
8
  class CommandRules
9
- # The arithmetic half of mutation: what a source resolves to, and how
10
- # increment/decrement land on an Integer or a one-numeric-field value
11
- # object.
9
+ # The arithmetic half of mutation: source resolution, and how
10
+ # increment/decrement land on an Integer or a one-numeric-field value object.
12
11
  module Arithmetic
13
- # The ops Runtime::CommandInterpreter applies — Vocabulary::MutationOp
14
- # (language/bluebook/vocabulary.bluebook), read off the generated
15
- # table, so increment/decrement's sign cannot drift from what the
16
- # language says each op means. Every other op (set, append, multiply,
17
- # clamp, remove, delegate, corrects) declares an empty sign — it does
18
- # no add-or-subtract arithmetic — which reads here as nil.
12
+ # Mirrors Vocabulary::MutationOp (language/bluebook/vocabulary.bluebook) so
13
+ # increment/decrement's sign cannot drift from the language's own definition.
14
+ # Ops with no arithmetic (set, append, multiply, clamp, remove, delegate,
15
+ # corrects) declare an empty sign, which reads here as nil.
19
16
  MutationOp = Struct.new(:name, :sign, keyword_init: true)
20
17
 
21
18
  MUTATION_OPS = Hecks::Vocabulary.rows("MutationOp").map do |row|
22
19
  MutationOp.new(name: row["name"], sign: row["sign"].empty? ? nil : Integer(row["sign"]))
23
20
  end.freeze
24
21
 
25
- # Reads what a mutation's source means: an argument's value for a Symbol, the literal
26
- # itself for anything else.
22
+ # Resolves a mutation's source: an argument's value for a Symbol, the
23
+ # literal itself otherwise.
27
24
  #
28
- # A mutation's source is either the name of an argument or a literal, and
29
- # the two are told apart by type : a Symbol is always a name, a String or a
30
- # number is always a value. Checked across all eight chapters — `to: :name`
31
- # and `to: "sold"`, never a Symbol meant as a value.
25
+ # Looked up unconditionally, with no `args.key?` guard, so an absent
26
+ # optional argument resolves to nil instead of falling back to the
27
+ # symbol itself as a value.
32
28
  #
33
- # The lookup is unconditional, with no `&& args.key?(source)` guard: such a
34
- # guard makes an absent argument fall through to `source` and return the symbol
35
- # itself as the value. `Customer.Register` without its `name` would set name to
36
- # the literal `:name`, coercion would meet a Symbol where a PersonName belongs,
37
- # and the refusal would read "name is a PersonName — pass its fields as an
38
- # object, not :name" — a message describing a mistake the caller had not
39
- # made. The real mistake, an absent argument, would never be the one refused,
40
- # which is what fuzz surfaced.
41
- #
42
- # An absent argument resolving to nil is correct, not pending work. The
43
- # language can say which arguments are optional (`attribute ...,
44
- # optional: true` — `CommandBuilder#attribute_impl`,
45
- # `attribute_collector.rb`): `sets` sources correctly from
46
- # an optional attribute, resolving absent to nil exactly as this
47
- # method does, and refusing it here would be wrong, not merely
48
- # undone work — `TillRoom::Till.TakeIn`'s own `note` (spec/
49
- # fixtures/till.bluebook) and Banking's `CardPayment.Authorize`'s
50
- # `tags` (payment_cards.bluebook) are real, live commands whose
51
- # `sets` mutation is deliberately sourced from an optional
52
- # attribute the caller may omit — `spec/runtime/command_rules_spec
53
- # .rb`'s own "says an absent optional argument is nil, not the
54
- # name of the argument" pins exactly this as correct, not pending.
55
- # The meta-domain's own self-hosted commands (Command.Declare's
56
- # `role`/`goal`/`provenance`/`from`/`position`, and ~35 more sites
57
- # across the language) all lean on the identical pattern — nil is
58
- # the right answer for a `sets` sourced from a declared-optional
59
- # attribute the caller left out, every time.
60
- #
61
- # The one narrow gap that leaves — a `sets` source Symbol naming
62
- # nothing the command declares at all (a typo, not an optional
63
- # argument), which would resolve to nil here, indistinguishable
64
- # from a legitimate optional absence — is closed at build time
65
- # instead of here: `CommandBuilder#refuse_unknown_argument_sources!`
66
- # refuses it the moment the `.bluebook` file loads, mirroring
67
- # `AggregateBuilder#seal_query_argument`'s identical check for a
68
- # query's own where-clause argument. This function stays a pure,
69
- # unconditional lookup because by the time any mutation reaches
70
- # it, the source has already been proven to name either a real,
71
- # possibly-optional argument, or a StateRef/literal; there is
72
- # nothing left here to refuse.
73
- #
74
- # @param source [Symbol, Object] a mutation's source: a Symbol names a command argument,
75
- # anything else (String, Numeric, Array, Hash, `StateRef`) is returned as is
29
+ # @param source [Symbol, Object] a Symbol names a command argument, anything else
30
+ # is returned as is
76
31
  # @param args [Hash{Symbol => Object}] the normalized command arguments
77
- # @return [Object, nil] the named argument's value, nil when the caller left that
78
- # argument out; otherwise `source` itself
32
+ # @return [Object, nil] the argument's value, nil if omitted, or `source` itself
79
33
  def resolve_source(source, args)
80
34
  return args[source] if source.is_a?(Symbol)
81
35
 
82
36
  source
83
37
  end
84
38
 
85
- # Adds or subtracts `amount` from an attribute's current value, on a bare number or on
86
- # the one numeric field two value objects share.
39
+ # Adds or subtracts `amount`, on a bare number or the one numeric field two value
40
+ # objects share.
87
41
  #
88
- # @param current [Numeric, Runtime::Value, nil] the attribute's pre-dispatch value; nil
89
- # (never set) counts as 0
90
- # @param amount [Numeric, Runtime::Value] how much to move by; a value object is combined
91
- # field by field with a value-object `current`, and otherwise unwrapped to its single
92
- # numeric field
93
- # @param target [Symbol, String] name of the attribute, used only to word a refusal
94
- # @param sign [Integer] 1 to increment, -1 to decrement, as `sign_of` answers
95
- # @return [Numeric, Runtime::Value] the new value: a `Runtime::Value` when both sides
96
- # are value objects, otherwise a bare number the caller re-wraps
97
- # @raise [Runtime::TypeMismatch] if `amount` or `current` is not numeric, or two value
98
- # objects do not share exactly one numeric field
99
- # @raise [Runtime::InvariantViolation] if a value-object result breaks one of its own
100
- # invariants
101
- # @raise [Bluebook::Expression::EvaluationError] if the result does not fit a signed
102
- # 64-bit Integer, or is a non-finite Float
42
+ # @param current [Numeric, Runtime::Value, nil] pre-dispatch value; nil (unset) is 0
43
+ # @param amount [Numeric, Runtime::Value] amount to move by
44
+ # @param target [Symbol, String] attribute name, for refusal wording only
45
+ # @param sign [Integer] 1 to increment, -1 to decrement
46
+ # @return [Numeric, Runtime::Value] `Runtime::Value` if both sides are, else a bare number
47
+ # @raise [Runtime::TypeMismatch] if either side isn't numeric, or shares no numeric field
48
+ # @raise [Runtime::InvariantViolation] if a value-object result breaks an invariant
49
+ # @raise [Bluebook::Expression::EvaluationError] if the result overflows Integer, or isn't
50
+ # finite
103
51
  def arithmetic(current, amount, target, sign)
104
52
  op = sign.positive? ? "increment" : "decrement"
105
53
  current ||= 0
106
54
 
107
55
  return arithmetic_value_object(current, amount, target, sign, op) if current.is_a?(Value) && amount.is_a?(Value)
108
56
 
109
- # `current` genuinely absent (no declared default, never set) and
110
- # `amount` arrives VO-wrapped — a real command argument typed the
111
- # same as the attribute, but with nothing to combine field-by-
112
- # field against yet (that is what `arithmetic_value_object`,
113
- # above, is for once both sides carry real fields). Before this,
114
- # falling straight to `unless amount.is_a?(Numeric)` below
115
- # refused with "increment needs an Integer, got 500" — true of
116
- # nothing: 500 is exactly the Integer it asked for, just still
117
- # wearing the Money wrapper the command's own declared attribute
118
- # type put it in. Unwrapped here, the same shape #clamp already
119
- # falls through to for an absent VO-typed attribute
120
- # (`current ||= 0`, then a raw scalar) — the mutation applier
121
- # re-wraps the raw result into the declared VO type on write,
122
- # the same way it already does for clamp's own result.
57
+ # A VO-typed attribute with no declared default is genuinely absent
58
+ # here (`current` is 0, not a Value), so `amount` still needs
59
+ # unwrapping to compare against a bare number.
123
60
  amount = unwrap_single_numeric_field(amount) if amount.is_a?(Value)
124
61
 
125
- # Widened from Integer to Numeric (migration plan task 4, i106):
126
- # an external project's organ math increments a Float (`increment: 0.02`) --
127
- # the raw, non-value-object path only ever mattered for Integer
128
- # counters before this corpus existed. Integer stays the common
129
- # case; Float is now accepted the same way.
130
62
  unless amount.is_a?(Numeric)
131
63
  raise TypeMismatch, RefusalWording.render_site("TypeMismatch", "arithmetic_amount",
132
64
  op: op, target: target, offered: Rendering.describe(amount))
@@ -139,22 +71,19 @@ module Hecks
139
71
  bounded(current + (sign * amount), op, current, sign * amount, sign.positive? ? "+" : "-")
140
72
  end
141
73
 
142
- # C3.3/C3.4 — an effect's arithmetic is held to the same value
143
- # model an expression's is: Integer is signed 64-bit, Float is
144
- # finite. A result outside that is an evaluation fault (never a
145
- # refusal, C8.3), worded as the Rust kernel's own generated
146
- # `checked_add`/`checked_sub`/`checked_mul` word it.
74
+ # An effect's arithmetic is held to the same value model an expression's is:
75
+ # Integer signed 64-bit, Float finite. Out of range is an evaluation fault,
76
+ # not a refusal (ADR checked_add/checked_sub/checked_mul wording).
147
77
  INT64_RANGE = (-(2**63))..((2**63) - 1)
148
78
 
149
- # Passes an arithmetic result through only if it fits the value model: a signed 64-bit
150
- # Integer or a finite Float.
79
+ # Passes an arithmetic result through only if it fits the value model: a signed
80
+ # 64-bit Integer or a finite Float.
151
81
  #
152
82
  # @param result [Numeric] the computed value to check
153
- # @param oper [String] the op's name (`"increment"`, `"decrement"`, `"multiply"`),
154
- # the first word of the fault message
83
+ # @param oper [String] the op's name, the first word of the fault message
155
84
  # @param lhs [Numeric] the left operand, quoted in the fault message
156
85
  # @param rhs [Numeric] the right operand; its absolute value is quoted
157
- # @param symbol [String] the operator to print between the operands: `"+"`, `"-"`, `"*"`
86
+ # @param symbol [String] the operator to print between the operands
158
87
  # @return [Numeric] `result`, unchanged
159
88
  # @raise [Bluebook::Expression::EvaluationError] if an Integer result is outside
160
89
  # `INT64_RANGE`, or a Float result is NaN or infinite
@@ -171,31 +100,23 @@ module Hecks
171
100
  "#{oper} overflowed: #{lhs} #{symbol} #{rhs.abs} is not a finite number"
172
101
  end
173
102
 
174
- # Adds or subtracts on the one numeric field two value objects share, answering a new
175
- # value object of `current`'s type.
103
+ # Adds or subtracts on the one numeric field two value objects share.
176
104
  #
177
105
  # @param current [Runtime::Value] the attribute's pre-dispatch value
178
- # @param amount [Runtime::Value] how much to move by, coerced to the attribute's type
179
- # @param target [Symbol, String] name of the attribute, used only to word a refusal
106
+ # @param amount [Runtime::Value] how much to move by
107
+ # @param target [Symbol, String] attribute name, for refusal wording only
180
108
  # @param sign [Integer] 1 to add, -1 to subtract
181
- # @param oper [String] `"increment"` or `"decrement"`, for refusal and fault wording
182
- # @return [Runtime::Value] a copy of `current` with the shared field replaced, rebuilt
183
- # and re-validated through `Value#with`
184
- # @raise [Runtime::TypeMismatch] if the two do not share exactly one numeric field, or
185
- # the rebuilt value object refuses the new field value
186
- # @raise [Runtime::InvariantViolation] if the new field value breaks one of the value
187
- # object's own invariants
188
- # @raise [Bluebook::Expression::EvaluationError] if the result does not fit a signed
189
- # 64-bit Integer, or is a non-finite Float
109
+ # @param oper [String] `"increment"`/`"decrement"`, for refusal/fault wording
110
+ # @return [Runtime::Value] `current`, with the shared field replaced
111
+ # @raise [Runtime::TypeMismatch] if the two share no numeric field, or the rebuilt refuses
112
+ # @raise [Runtime::InvariantViolation] if the new field value breaks an invariant
113
+ # @raise [Bluebook::Expression::EvaluationError] if the result overflows Integer, or isn't
114
+ # finite
190
115
  def arithmetic_value_object(current, amount, target, sign, oper)
191
116
  current_fields = current.to_h
192
117
  amount_fields = amount.to_h
193
- # Widened from Integer to Numeric -- see #arithmetic's own
194
- # comment. A synthesised value-object wrapper around a bare
195
- # Float attribute (an external project's Synapse#strength, auto-wrapped per
196
- # Part 3a's "bare primitives forbidden" finding) lands here as
197
- # a one-Float-field Value exactly the way a one-Integer-field
198
- # Value already did.
118
+ # A synthesized value-object wrapper around a bare numeric attribute
119
+ # always carries exactly one numeric field.
199
120
  shared_numeric = current_fields.keys.select do |field|
200
121
  current_fields[field].is_a?(Numeric) && amount_fields[field].is_a?(Numeric)
201
122
  end
@@ -209,54 +130,32 @@ module Hecks
209
130
  current[field], amount[field], sign.positive? ? "+" : "-"))
210
131
  end
211
132
 
212
- # Looks up whether a mutation op adds or subtracts, from the generated `MUTATION_OPS` table.
133
+ # Looks up whether a mutation op adds or subtracts, from the generated
134
+ # `MUTATION_OPS` table.
213
135
  #
214
- # Not a bare `.find(...)&.sign || -1` — that would silently answer
215
- # decrement's sign for both an op this table has never heard of
216
- # and a declared, real op that simply carries no sign at all
217
- # (set/append/multiply/clamp/remove — see `MUTATION_OPS` above).
218
- # Callers today only ever reach this for :increment/:decrement
219
- # (both MutationApplier#apply and EntityInterpreter#
220
- # apply_to_element gate every other op through their own `case`
221
- # first, each with its own loud WiringError backstop), so this
222
- # raise is not a real runtime path yet — it is the same
223
- # backstop one level down, in case a future caller reaches
224
- # #sign_of directly for an op that was never meant to have one.
136
+ # Not a bare `.find(...)&.sign || -1`: that would silently answer
137
+ # decrement's sign both for an unknown op and for a real op declared
138
+ # with no sign at all (set/append/multiply/clamp/remove/delegate/corrects).
225
139
  #
226
140
  # @param oper [Symbol, String] the mutation op's name, such as `:increment`
227
141
  # @return [Integer] 1 for increment, -1 for decrement
228
142
  # @raise [Runtime::WiringError] if the op is unknown, or is declared with no sign
229
- # (set, append, multiply, clamp, remove, delegate, corrects)
230
143
  def sign_of(oper)
231
144
  MUTATION_OPS.find { |candidate| candidate.name == oper.to_s }&.sign ||
232
145
  raise(WiringError, "no sign declared for mutation op #{oper.inspect} — add one before calling #sign_of")
233
146
  end
234
147
 
235
- # Scales an attribute's current value by `amount`, on a bare number or on the one numeric
148
+ # Scales an attribute's current value by `amount`, on a bare number or the one numeric
236
149
  # field two value objects share.
237
150
  #
238
- # Vendored addition, not (yet) upstream hecks (migration plan
239
- # task 4, i106): `current * amount` -- the scaling counterpart to
240
- # increment/decrement's add/subtract. Same raw-vs-value-object
241
- # branch shape as #arithmetic/#arithmetic_value_object, reused
242
- # rather than duplicated verb-for-verb (a `Proc` picks the actual
243
- # arithmetic; everything else -- the Value unwrap/rewrap, the
244
- # TypeMismatch refusals -- is identical to the additive pair).
245
- #
246
- # @param current [Numeric, Runtime::Value, nil] the attribute's pre-dispatch value; nil
247
- # (never set) counts as 0
248
- # @param amount [Numeric, Runtime::Value] the factor; a value object is combined field
249
- # by field with a value-object `current`, and otherwise unwrapped to its single
250
- # numeric field
251
- # @param target [Symbol, String] name of the attribute, used only to word a refusal
252
- # @return [Numeric, Runtime::Value] the product: a `Runtime::Value` when both sides are
253
- # value objects, otherwise a bare number the caller re-wraps
254
- # @raise [Runtime::TypeMismatch] if `amount` or `current` is not numeric, or two value
255
- # objects do not share exactly one numeric field
256
- # @raise [Runtime::InvariantViolation] if a value-object result breaks one of its own
257
- # invariants
258
- # @raise [Bluebook::Expression::EvaluationError] if the product does not fit a signed
259
- # 64-bit Integer, or is a non-finite Float
151
+ # @param current [Numeric, Runtime::Value, nil] pre-dispatch value; nil (unset) is 0
152
+ # @param amount [Numeric, Runtime::Value] the factor
153
+ # @param target [Symbol, String] attribute name, for refusal wording only
154
+ # @return [Numeric, Runtime::Value] the product, a `Runtime::Value` when both sides are
155
+ # @raise [Runtime::TypeMismatch] if either side isn't numeric, or shares no numeric field
156
+ # @raise [Runtime::InvariantViolation] if a value-object result breaks an invariant
157
+ # @raise [Bluebook::Expression::EvaluationError] if the product overflows Integer, or isn't
158
+ # finite
260
159
  def multiply(current, amount, target)
261
160
  current ||= 0
262
161
 
@@ -266,8 +165,7 @@ module Hecks
266
165
  end
267
166
  end
268
167
 
269
- # Same absent-`current`, VO-wrapped-`amount` gap as `#arithmetic`
270
- # — see that method's own comment.
168
+ # Same absent-current, VO-wrapped-amount gap as #arithmetic, above.
271
169
  amount = unwrap_single_numeric_field(amount) if amount.is_a?(Value)
272
170
 
273
171
  unless amount.is_a?(Numeric) && current.is_a?(Numeric)
@@ -282,35 +180,15 @@ module Hecks
282
180
  # Bounds an attribute's current value into `[min, max]`, on a bare number or on
283
181
  # the one numeric field the wrapping value object carries.
284
182
  #
285
- # Vendored addition, not (yet) upstream hecks (migration plan
286
- # task 4, i106): bound the current value into `[min, max]` -- no
287
- # "amount" to combine, so it does not go through
288
- # #arithmetic/#multiply's shared-numeric-field matching at all;
289
- # it clamps whichever single numeric field the wrapping value
290
- # object carries (a synthesised wrapper always carries exactly
291
- # one, per Part 3a's auto-synthesis).
292
- #
293
- # @param current [Numeric, Runtime::Value, nil] the attribute's pre-dispatch value; nil
294
- # (never set) counts as 0
183
+ # @param current [Numeric, Runtime::Value, nil] pre-dispatch value; nil (unset) is 0
295
184
  # @param bounds [Array<Numeric>] the two-element `[min, max]` range to clamp into
296
- # @param target [Symbol, String] name of the attribute, used only to word a refusal
185
+ # @param target [Symbol, String] attribute name, used only to word a refusal
297
186
  # @return [Numeric, Runtime::Value] the clamped value: a `Runtime::Value` when
298
187
  # `current` is one, otherwise a bare number the caller re-wraps
299
188
  # @raise [Runtime::TypeMismatch] if `current` is a value object with no single
300
189
  # numeric field, or is neither numeric nor a value object
301
190
  def clamp(current, bounds, target)
302
191
  min, max = bounds
303
- # The same `current ||= 0` #arithmetic/#multiply both give a
304
- # phantom (never-set) numeric field, one line up from each —
305
- # this was the one arithmetic op that didn't, so a VO-typed
306
- # attribute with no declared `default:` (genuinely absent,
307
- # `Instance.defaults`/`#default_for`) hit TypeMismatch on the
308
- # first clamp. (#arithmetic/#multiply's own absent-current gap
309
- # is a real, separate bug: they do not silently treat an absent
310
- # field as zero; they raise too, blaming a perfectly valid
311
- # `amount` for not being an Integer when it is one, just still
312
- # Money-wrapped. Fixed alongside this one — see
313
- # #unwrap_single_numeric_field.)
314
192
  current ||= 0
315
193
  if current.is_a?(Value)
316
194
  fields = current.to_h
@@ -330,17 +208,9 @@ module Hecks
330
208
 
331
209
  private
332
210
 
333
- # `amount` arrives VO-wrapped whenever the command's own declared
334
- # attribute type says so (a real `Money`, not a bare Integer) —
335
- # true whether or not `current` has ever been set. Only meaningful
336
- # to call once `current` is known not to be a Value itself (the
337
- # `current.is_a?(Value) && amount.is_a?(Value)` branch, above in
338
- # both callers, already owns the case where both sides carry real
339
- # fields to combine). Refuses rather than guesses when more than
340
- # one field is numeric — genuinely ambiguous which one an absent
341
- # `current` should be treated as zero for, the same reasoning
342
- # `combine_value_object`'s own `shared_numeric.size == 1` check
343
- # already holds to when both sides are present.
211
+ # Only meaningful once `current` is known not to itself be a Value (the
212
+ # both-sides-are-Values branch owns that case in each caller). Refuses
213
+ # rather than guesses when more than one field is numeric.
344
214
  def unwrap_single_numeric_field(value)
345
215
  fields = value.to_h
346
216
  numeric_fields = fields.keys.select { |field| fields[field].is_a?(Numeric) }
@@ -6,44 +6,21 @@ require_relative "../../ports/authorization"
6
6
  module Hecks
7
7
  module Runtime
8
8
  class CommandRules
9
- # Whether the caller may run this command at all — a `role` mismatch,
10
- # the one check that runs before any domain-state work, alongside the
11
- # argument gate rather than after it.
9
+ # Refuses a command whose declared `role` the ambient caller does not hold.
12
10
  #
13
- # **Two checks, not a replacement** — ADR 0025 §9's own caution against
14
- # "silently downgrading `role` to documentation" cuts both ways: a
15
- # caller who never named who they are (every caller before this) is
16
- # checked exactly the way it always has been, string equality
17
- # against the command's own `role`. Only a caller that also binds an
18
- # `actor_id` (`Hecks.as_caller(role:, actor_id:)`) reaches the
19
- # real check — a live lookup through `Ports::Authorization`, once the
20
- # command's domain has an authorization provider: its own chapter,
21
- # or a framework member it attaches, declaring `provides
22
- # "authorization"` (Governance, via `uses_framework "Governance"`).
23
- # Boot refuses a domain that declares a `role` and has no provider —
24
- # `Registry::Verification#refuse_ungoverned_roles!`. An identified caller is never
25
- # let back through the string fallback: a real identity that holds
26
- # no matching grant is refused, not waved through because it also
27
- # happens to type the right word.
11
+ # A caller with an `actor_id` is checked against live grants when the domain has an
12
+ # authorization provider (ADR 0025); one without is compared by role string only.
28
13
  module Authorization
29
- # Refuses the command when the ambient caller (`Caller.current`) does not hold the role
30
- # the command declares.
14
+ # Refuses the command when the ambient caller (`Caller.current`) lacks its declared role.
31
15
  #
32
- # Opt-in, on both sides. No caller bound: unchecked, exactly as
33
- # today. No role declared: unchecked too — `role` is genuinely
34
- # optional in this language (roughly a third of banking's own
35
- # commands declare none), so a command that never named a role has
36
- # nothing to check a caller against.
16
+ # Opt-in on both sides: no caller bound, or no role declared, means no check.
37
17
  #
38
- # @param command [Bluebook::Command] the command about to run; its `role` is the one
39
- # required, and a nil or empty role means no check
40
- # @param domain [String] name of the command's domain, whose authorization provider
41
- # answers the check
42
- # @return [nil] when no caller is bound, no role is declared, or the caller is authorized
43
- # @raise [Runtime::Unauthorized] if an identified caller holds no live grant of the role
44
- # where a provider is attached, or otherwise if the caller's role string differs
45
- # @raise [Runtime::WiringError] if an identified caller is checked and zero or more than
46
- # one adapter implements the authorization port
18
+ # @param command [Bluebook::Command] the command about to run
19
+ # @param domain [String] name of the command's domain, whose provider answers the check
20
+ # @return [nil] when unchecked or authorized
21
+ # @raise [Runtime::Unauthorized] if the caller holds no grant of the role (identified
22
+ # caller) or its role string differs (unidentified caller)
23
+ # @raise [Runtime::WiringError] if zero or several adapters implement the port
47
24
  def refuse_role_mismatch(command, domain)
48
25
  caller = Caller.current
49
26
  return unless caller
@@ -66,17 +43,8 @@ module Hecks
66
43
 
67
44
  private
68
45
 
69
- # **Declared, not named** — `Registry#authorization_provider_for`.
70
- #
71
- # The provider's own commands are looked up too, not waved through
72
- # the string fallback: `Governance::RoleAssignment.Assign` declares
73
- # `role "Governance administrator"`, and an identified caller
74
- # dispatching it is checked against a live assignment of that role
75
- # like any other gated command (ADR 0025 §9). The Rust kernel's
76
- # `check_role` (`rust/src/kernel/repository.rs`) does the same. The
77
- # consequence is deliberate: the first administrator grant has to
78
- # come from a caller that binds no `actor_id` (the unchecked,
79
- # string-compared path) — a bootstrap step, not a hole.
46
+ # The provider's own commands are checked too, so the first administrator grant must
47
+ # come from a caller with no `actor_id` (the string-compared path).
80
48
  def governance_attached?(domain)
81
49
  !registry.authorization_provider_for(domain).nil?
82
50
  end
@@ -3,33 +3,20 @@ require_relative "../event"
3
3
  module Hecks
4
4
  module Runtime
5
5
  class CommandRules
6
- # What a command's `emits` becomes: an Event in the registry's log,
7
- # and — where the store can hold one — a recorded event beside the
8
- # data it describes.
6
+ # Turns a command's `emits` into events in the registry's log and, where possible, the store.
9
7
  module Emission
10
- # Builds one frozen event per name the command `emits`, appends each to the registry's
11
- # event log, and records it in the store when the repository keeps events.
8
+ # Builds one frozen event per name the command `emits` and records each.
12
9
  #
13
- # `correlation` arrives here rather than being merged onto the
14
- # event afterwards. It is part of the transaction — known from
15
- # `dispatch`'s own argument before anything is emitted — and an
16
- # event that is still being written to is not yet a record of
17
- # what happened. Setting it at construction is what lets the
18
- # event be frozen the moment it exists.
10
+ # `correlation` is set at construction so the event can be frozen the moment it exists.
19
11
  #
20
12
  # @param command [Bluebook::Command] the command whose `emits` names the events
21
- # @param domain [String] name of the emitting domain, the prefix of each event's
22
- # `aggregate` (`"Domain::Aggregate"`)
13
+ # @param domain [String] the emitting domain's name
23
14
  # @param aggregate [Bluebook::Aggregate] the aggregate the record belongs to
24
15
  # @param instance [Runtime::Instance] the settled record; its `id` stamps every event
25
- # @param args [Hash{Symbol => Object}] the normalized command arguments, used as each
26
- # event's payload and deep-frozen by this call
27
- # @param repository [Ports::Persistence::AppendOnly] the aggregate's repository; asked to
28
- # `record_event` only if it responds to it
29
- # @param correlation [Hash, nil] saga correlation head => value for a saga-caused
30
- # dispatch; nil otherwise
31
- # @return [Array<Runtime::Event>] the emitted events in `emits` order, each frozen;
32
- # `[]` when the command emits nothing
16
+ # @param args [Hash{Symbol => Object}] the normalized arguments, used as each payload
17
+ # @param repository [Ports::Persistence::AppendOnly] asked to `record_event` if it can
18
+ # @param correlation [Hash, nil] saga correlation for a saga-caused dispatch
19
+ # @return [Array<Runtime::Event>] the emitted events in `emits` order
33
20
  def emit(command, domain, aggregate, instance, args, repository, correlation = nil)
34
21
  command.emits.map do |event_name|
35
22
  event = Event.new(