hecks 0.2.0 → 1.0.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 (419) hide show
  1. checksums.yaml +5 -5
  2. data/lib/hecks/adapters/driven/claude_code.adapter +3 -0
  3. data/lib/hecks/adapters/driven/claude_code.rb +127 -0
  4. data/lib/hecks/adapters/driven/d1.adapter +12 -0
  5. data/lib/hecks/adapters/driven/d1.rb +427 -0
  6. data/lib/hecks/adapters/driven/folder.adapter +3 -0
  7. data/lib/hecks/adapters/driven/folder.rb +199 -0
  8. data/lib/hecks/adapters/driven/google_authentication.adapter +3 -0
  9. data/lib/hecks/adapters/driven/google_authentication.rb +100 -0
  10. data/lib/hecks/adapters/driven/governance_authorization.adapter +3 -0
  11. data/lib/hecks/adapters/driven/governance_authorization.rb +101 -0
  12. data/lib/hecks/adapters/driven/heki/journal.rb +56 -0
  13. data/lib/hecks/adapters/driven/heki/saga_store.rb +99 -0
  14. data/lib/hecks/adapters/driven/heki/snapshot.rb +65 -0
  15. data/lib/hecks/adapters/driven/heki.adapter +4 -0
  16. data/lib/hecks/adapters/driven/heki.rb +179 -0
  17. data/lib/hecks/adapters/driven/identity_registry.adapter +3 -0
  18. data/lib/hecks/adapters/driven/identity_registry.rb +26 -0
  19. data/lib/hecks/adapters/driven/in_memory_ordering.rb +51 -0
  20. data/lib/hecks/adapters/driven/lambda/client.rb +63 -0
  21. data/lib/hecks/adapters/driven/lambda.adapter +4 -0
  22. data/lib/hecks/adapters/driven/lambda.rb +145 -0
  23. data/lib/hecks/adapters/driven/memory.adapter +3 -0
  24. data/lib/hecks/adapters/driven/memory.rb +103 -0
  25. data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +3 -0
  26. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +28 -0
  27. data/lib/hecks/adapters/driven/postgres/codec.rb +88 -0
  28. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +207 -0
  29. data/lib/hecks/adapters/driven/postgres.adapter +5 -0
  30. data/lib/hecks/adapters/driven/postgres.rb +438 -0
  31. data/lib/hecks/adapters/driven/postgres_era.adapter +17 -0
  32. data/lib/hecks/adapters/driven/prism.adapter +3 -0
  33. data/lib/hecks/adapters/driven/prism.rb +80 -0
  34. data/lib/hecks/adapters/driven/secure_random_identity.adapter +3 -0
  35. data/lib/hecks/adapters/driven/secure_random_identity.rb +14 -0
  36. data/lib/hecks/adapters/driven/sql_query_builder.rb +221 -0
  37. data/lib/hecks/adapters/driven/sqlite/codec.rb +80 -0
  38. data/lib/hecks/adapters/driven/sqlite/projection.rb +172 -0
  39. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +192 -0
  40. data/lib/hecks/adapters/driven/sqlite.adapter +9 -0
  41. data/lib/hecks/adapters/driven/sqlite.rb +306 -0
  42. data/lib/hecks/adapters/driven/system_clock.adapter +3 -0
  43. data/lib/hecks/adapters/driven/system_clock.rb +14 -0
  44. data/lib/hecks/adapters/driven.rb +54 -0
  45. data/lib/hecks/adapters.rb +6 -0
  46. data/lib/hecks/behaviors/dsl.rb +125 -0
  47. data/lib/hecks/behaviors/expectations.rb +340 -0
  48. data/lib/hecks/behaviors/ir.rb +31 -0
  49. data/lib/hecks/behaviors/rspec.rb +42 -0
  50. data/lib/hecks/behaviors/runner.rb +96 -0
  51. data/lib/hecks/behaviors.rb +25 -0
  52. data/lib/hecks/bluebook/aggregate.rb +108 -0
  53. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +134 -0
  54. data/lib/hecks/bluebook/assembly/build.rb +48 -0
  55. data/lib/hecks/bluebook/assembly/contract.rb +112 -0
  56. data/lib/hecks/bluebook/assembly/contracts.rb +438 -0
  57. data/lib/hecks/bluebook/assembly/marks.rb +228 -0
  58. data/lib/hecks/bluebook/assembly/specializer.rb +70 -0
  59. data/lib/hecks/bluebook/assembly.rb +91 -0
  60. data/lib/hecks/bluebook/attribute.rb +96 -0
  61. data/lib/hecks/bluebook/behaviour/aggregate.rb +83 -0
  62. data/lib/hecks/bluebook/behaviour/attribute.rb +27 -0
  63. data/lib/hecks/bluebook/behaviour/chapter.rb +72 -0
  64. data/lib/hecks/bluebook/behaviour/command.rb +116 -0
  65. data/lib/hecks/bluebook/behaviour/domain_port.rb +25 -0
  66. data/lib/hecks/bluebook/behaviour/entity.rb +59 -0
  67. data/lib/hecks/bluebook/behaviour/hexagon.rb +50 -0
  68. data/lib/hecks/bluebook/behaviour/lifecycle.rb +68 -0
  69. data/lib/hecks/bluebook/behaviour/policy.rb +52 -0
  70. data/lib/hecks/bluebook/behaviour/process_manager.rb +51 -0
  71. data/lib/hecks/bluebook/behaviour/query.rb +10 -0
  72. data/lib/hecks/bluebook/behaviour/read_model.rb +29 -0
  73. data/lib/hecks/bluebook/behaviour/traits.rb +81 -0
  74. data/lib/hecks/bluebook/behaviour/value_object.rb +33 -0
  75. data/lib/hecks/bluebook/chapter.rb +78 -0
  76. data/lib/hecks/bluebook/command.rb +124 -0
  77. data/lib/hecks/bluebook/domain_port.rb +102 -0
  78. data/lib/hecks/bluebook/dsl/adapter_builder.rb +34 -0
  79. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +1018 -0
  80. data/lib/hecks/bluebook/dsl/attribute_collector.rb +348 -0
  81. data/lib/hecks/bluebook/dsl/binding_proxy.rb +71 -0
  82. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +1087 -0
  83. data/lib/hecks/bluebook/dsl/command_builder.rb +714 -0
  84. data/lib/hecks/bluebook/dsl/const_shim.rb +81 -0
  85. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +79 -0
  86. data/lib/hecks/bluebook/dsl/entity_builder.rb +430 -0
  87. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +366 -0
  88. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +163 -0
  89. data/lib/hecks/bluebook/dsl/identity_declaration.rb +191 -0
  90. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +44 -0
  91. data/lib/hecks/bluebook/dsl/malformed.rb +7 -0
  92. data/lib/hecks/bluebook/dsl/policy_builder.rb +135 -0
  93. data/lib/hecks/bluebook/dsl/port_builder.rb +39 -0
  94. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +142 -0
  95. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +306 -0
  96. data/lib/hecks/bluebook/dsl/query_builder.rb +113 -0
  97. data/lib/hecks/bluebook/dsl/read_model_builder.rb +233 -0
  98. data/lib/hecks/bluebook/dsl/rule_reference.rb +173 -0
  99. data/lib/hecks/bluebook/dsl/translation_builder.rb +243 -0
  100. data/lib/hecks/bluebook/dsl/value_object_builder.rb +178 -0
  101. data/lib/hecks/bluebook/dsl/word_gate.rb +228 -0
  102. data/lib/hecks/bluebook/dsl/world_builder.rb +117 -0
  103. data/lib/hecks/bluebook/dsl.rb +45 -0
  104. data/lib/hecks/bluebook/entity.rb +103 -0
  105. data/lib/hecks/bluebook/expression/canonical_form.rb +123 -0
  106. data/lib/hecks/bluebook/expression/evaluator.rb +304 -0
  107. data/lib/hecks/bluebook/expression/projection.json +218 -0
  108. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +233 -0
  109. data/lib/hecks/bluebook/expression/resolver.rb +781 -0
  110. data/lib/hecks/bluebook/expression.rb +13 -0
  111. data/lib/hecks/bluebook/hexagon.rb +60 -0
  112. data/lib/hecks/bluebook/lifecycle.rb +42 -0
  113. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +54 -0
  114. data/lib/hecks/bluebook/meta_validator/judge.rb +621 -0
  115. data/lib/hecks/bluebook/meta_validator/plan.rb +332 -0
  116. data/lib/hecks/bluebook/meta_validator/port_judge.rb +51 -0
  117. data/lib/hecks/bluebook/meta_validator/readings.rb +360 -0
  118. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +351 -0
  119. data/lib/hecks/bluebook/meta_validator/shapes.rb +266 -0
  120. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +255 -0
  121. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +138 -0
  122. data/lib/hecks/bluebook/meta_validator/world_judge.rb +78 -0
  123. data/lib/hecks/bluebook/meta_validator.rb +542 -0
  124. data/lib/hecks/bluebook/model_check.rb +445 -0
  125. data/lib/hecks/bluebook/pattern_subset.rb +184 -0
  126. data/lib/hecks/bluebook/policy.rb +41 -0
  127. data/lib/hecks/bluebook/process_manager.rb +128 -0
  128. data/lib/hecks/bluebook/project_discovery.rb +30 -0
  129. data/lib/hecks/bluebook/project_loader.rb +40 -0
  130. data/lib/hecks/bluebook/project_register.rb +82 -0
  131. data/lib/hecks/bluebook/query.rb +61 -0
  132. data/lib/hecks/bluebook/read_model.rb +109 -0
  133. data/lib/hecks/bluebook/reference.rb +74 -0
  134. data/lib/hecks/bluebook/smoke_test.rb +166 -0
  135. data/lib/hecks/bluebook/synthesizer.rb +95 -0
  136. data/lib/hecks/bluebook/translation.rb +92 -0
  137. data/lib/hecks/bluebook/value_object.rb +66 -0
  138. data/lib/hecks/bluebook.rb +74 -0
  139. data/lib/hecks/codemod.rb +342 -0
  140. data/lib/hecks/construct.rb +71 -0
  141. data/lib/hecks/deploy/bluebook/deploy.bluebook +219 -0
  142. data/lib/hecks/deploy/bluebook/deploy.hecksagon +4 -0
  143. data/lib/hecks/deploy/oidc.json +18 -0
  144. data/lib/hecks/doc/reference.rb +410 -0
  145. data/lib/hecks/embryonaut_bluebook.rb +75 -0
  146. data/lib/hecks/facade/cli_door.rb +119 -0
  147. data/lib/hecks/facade/cli_runner.rb +190 -0
  148. data/lib/hecks/facade/command_request.rb +105 -0
  149. data/lib/hecks/facade/handle.rb +173 -0
  150. data/lib/hecks/facade/json_door.rb +166 -0
  151. data/lib/hecks/facade/surface/aggregate_door.rb +185 -0
  152. data/lib/hecks/facade/surface/chapter.rb +107 -0
  153. data/lib/hecks/facade/surface.rb +48 -0
  154. data/lib/hecks/facade.rb +44 -0
  155. data/lib/hecks/forms/app.rb +341 -0
  156. data/lib/hecks/forms/command_form_renderer.rb +113 -0
  157. data/lib/hecks/forms/examples/banking_console.bluebook +3 -0
  158. data/lib/hecks/forms/field_renderer.rb +177 -0
  159. data/lib/hecks/forms/field_shape.rb +232 -0
  160. data/lib/hecks/forms/html.rb +84 -0
  161. data/lib/hecks/forms/index_renderer.rb +35 -0
  162. data/lib/hecks/forms/page.rb +157 -0
  163. data/lib/hecks/forms/params.rb +161 -0
  164. data/lib/hecks/forms/port_argument.rb +46 -0
  165. data/lib/hecks/forms/query_form_renderer.rb +114 -0
  166. data/lib/hecks/forms/record_renderer.rb +119 -0
  167. data/lib/hecks/forms/record_table.rb +68 -0
  168. data/lib/hecks/forms/reference_options.rb +30 -0
  169. data/lib/hecks/forms/value_object_shape.rb +46 -0
  170. data/lib/hecks/forms.rb +54 -0
  171. data/lib/hecks/fqn.rb +94 -0
  172. data/lib/hecks/framework/bluebook/compliance.bluebook +1 -0
  173. data/lib/hecks/framework/bluebook/console_settings.bluebook +489 -0
  174. data/lib/hecks/framework/bluebook/framework.hecksagon +32 -0
  175. data/lib/hecks/framework/bluebook/governance.bluebook +145 -0
  176. data/lib/hecks/framework/bluebook/identity.bluebook +90 -0
  177. data/lib/hecks/framework/oidc.json +39 -0
  178. data/lib/hecks/framework.rb +90 -0
  179. data/lib/hecks/freezer.rb +67 -0
  180. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +527 -0
  181. data/lib/hecks/fuzzing/invalid_value_generator.rb +86 -0
  182. data/lib/hecks/fuzzing/isolated_boot.rb +286 -0
  183. data/lib/hecks/fuzzing/properties.rb +1192 -0
  184. data/lib/hecks/fuzzing/replay.rb +668 -0
  185. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +93 -0
  186. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +80 -0
  187. data/lib/hecks/fuzzing/sequence_generator/picker.rb +96 -0
  188. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +250 -0
  189. data/lib/hecks/fuzzing/sequence_generator.rb +131 -0
  190. data/lib/hecks/fuzzing/value_generator.rb +200 -0
  191. data/lib/hecks/fuzzing.rb +12 -0
  192. data/lib/hecks/grammar/evolve.rb +324 -0
  193. data/lib/hecks/grammar/expression.bluebook +420 -0
  194. data/lib/hecks/grammar/expression_operators.json +1648 -0
  195. data/lib/hecks/grammar/grammar.hecksagon +20 -0
  196. data/lib/hecks/grammar/oidc.json +69 -0
  197. data/lib/hecks/grammar/translation.bluebook +203 -0
  198. data/lib/hecks/grammar.rb +178 -0
  199. data/lib/hecks/ir.rb +126 -0
  200. data/lib/hecks/language/adapter.bluebook +116 -0
  201. data/lib/hecks/language/bluebook/aggregate.bluebook +581 -0
  202. data/lib/hecks/language/bluebook/attaches/paging.bluebook +76 -0
  203. data/lib/hecks/language/bluebook/bluebook.bluebook +256 -0
  204. data/lib/hecks/language/bluebook/bluebook.hecksagon +24 -0
  205. data/lib/hecks/language/bluebook/command.bluebook +471 -0
  206. data/lib/hecks/language/bluebook/entity.bluebook +392 -0
  207. data/lib/hecks/language/bluebook/policy.bluebook +189 -0
  208. data/lib/hecks/language/bluebook/process_manager.bluebook +381 -0
  209. data/lib/hecks/language/bluebook/projection.bluebook +267 -0
  210. data/lib/hecks/language/bluebook/query.bluebook +245 -0
  211. data/lib/hecks/language/bluebook/shape.bluebook +292 -0
  212. data/lib/hecks/language/bluebook/syntax.bluebook +448 -0
  213. data/lib/hecks/language/bluebook/vocabulary.bluebook +379 -0
  214. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +51 -0
  215. data/lib/hecks/language/hecksagon/domain_port.bluebook +76 -0
  216. data/lib/hecks/language/hecksagon/hecksagon.bluebook +131 -0
  217. data/lib/hecks/language/hecksagon/port_operation.bluebook +102 -0
  218. data/lib/hecks/language/oidc.json +333 -0
  219. data/lib/hecks/language/port.bluebook +120 -0
  220. data/lib/hecks/language/translation/translation.bluebook +110 -0
  221. data/lib/hecks/language/translation/translation_aggregate.bluebook +267 -0
  222. data/lib/hecks/language/world/wiring.bluebook +62 -0
  223. data/lib/hecks/language/world/world.bluebook +84 -0
  224. data/lib/hecks/literal.rb +125 -0
  225. data/lib/hecks/naming.rb +174 -0
  226. data/lib/hecks/ports/access_control.port +9 -0
  227. data/lib/hecks/ports/access_control.rb +62 -0
  228. data/lib/hecks/ports/agent/answers.rb +104 -0
  229. data/lib/hecks/ports/agent.port +8 -0
  230. data/lib/hecks/ports/agent.rb +167 -0
  231. data/lib/hecks/ports/authentication.port +6 -0
  232. data/lib/hecks/ports/authentication.rb +50 -0
  233. data/lib/hecks/ports/authorization.port +7 -0
  234. data/lib/hecks/ports/authorization.rb +62 -0
  235. data/lib/hecks/ports/clock.port +5 -0
  236. data/lib/hecks/ports/clock.rb +62 -0
  237. data/lib/hecks/ports/extraction.port +5 -0
  238. data/lib/hecks/ports/extraction.rb +37 -0
  239. data/lib/hecks/ports/identity_assignment.port +5 -0
  240. data/lib/hecks/ports/identity_assignment.rb +45 -0
  241. data/lib/hecks/ports/identity_generation.port +5 -0
  242. data/lib/hecks/ports/identity_generation.rb +49 -0
  243. data/lib/hecks/ports/identity_resolution.port +5 -0
  244. data/lib/hecks/ports/identity_resolution.rb +40 -0
  245. data/lib/hecks/ports/loading.port +4 -0
  246. data/lib/hecks/ports/loading.rb +13 -0
  247. data/lib/hecks/ports/persistence/append_only.rb +138 -0
  248. data/lib/hecks/ports/persistence/binding_policy.rb +56 -0
  249. data/lib/hecks/ports/persistence/execution.rb +23 -0
  250. data/lib/hecks/ports/persistence/null_saga_store.rb +25 -0
  251. data/lib/hecks/ports/persistence/plugin.rb +54 -0
  252. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +169 -0
  253. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +128 -0
  254. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +161 -0
  255. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +61 -0
  256. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +304 -0
  257. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +171 -0
  258. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +190 -0
  259. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +478 -0
  260. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +166 -0
  261. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +314 -0
  262. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +168 -0
  263. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +170 -0
  264. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +134 -0
  265. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +137 -0
  266. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +89 -0
  267. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +85 -0
  268. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +43 -0
  269. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +132 -0
  270. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +74 -0
  271. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +758 -0
  272. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +120 -0
  273. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +31 -0
  274. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +46 -0
  275. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +102 -0
  276. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +46 -0
  277. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +70 -0
  278. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +72 -0
  279. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +120 -0
  280. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +183 -0
  281. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +41 -0
  282. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +28 -0
  283. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +30 -0
  284. data/lib/hecks/ports/persistence/plugins/era/translation.rb +11 -0
  285. data/lib/hecks/ports/persistence/plugins/era.rb +48 -0
  286. data/lib/hecks/ports/persistence/remote_runtime.rb +42 -0
  287. data/lib/hecks/ports/persistence/repository_factory.rb +30 -0
  288. data/lib/hecks/ports/persistence.port +4 -0
  289. data/lib/hecks/ports/persistence.rb +39 -0
  290. data/lib/hecks/ports/projection.port +4 -0
  291. data/lib/hecks/ports/projection.rb +96 -0
  292. data/lib/hecks/ports/query/in_memory.rb +60 -0
  293. data/lib/hecks/ports/query/ordering.rb +41 -0
  294. data/lib/hecks/ports/query.rb +36 -0
  295. data/lib/hecks/ports.rb +27 -0
  296. data/lib/hecks/projections/diagrams.rb +642 -0
  297. data/lib/hecks/projections/ir.rb +18 -0
  298. data/lib/hecks/projections/model/deviations.rb +98 -0
  299. data/lib/hecks/projections/model.rb +145 -0
  300. data/lib/hecks/projections/oidc.rb +110 -0
  301. data/lib/hecks/projections/parser_table.rb +159 -0
  302. data/lib/hecks/projections/reference.rb +38 -0
  303. data/lib/hecks/projections/shape.rb +44 -0
  304. data/lib/hecks/projections/statements.rb +110 -0
  305. data/lib/hecks/projections/vocabulary.rb +100 -0
  306. data/lib/hecks/projections.rb +32 -0
  307. data/lib/hecks/projector/cli_projector.rb +446 -0
  308. data/lib/hecks/projector/docs_projector.rb +321 -0
  309. data/lib/hecks/projector/exporter.rb +158 -0
  310. data/lib/hecks/projector/ir_projector.rb +18 -0
  311. data/lib/hecks/projector/narrate_projector.rb +243 -0
  312. data/lib/hecks/projector/target.rb +97 -0
  313. data/lib/hecks/projector.rb +186 -0
  314. data/lib/hecks/query_ir.rb +411 -0
  315. data/lib/hecks/query_specification/common/authorization_spec.rb +9 -0
  316. data/lib/hecks/query_specification/common/comparators.rb +25 -0
  317. data/lib/hecks/query_specification/common/comparison.rb +174 -0
  318. data/lib/hecks/query_specification/common/cursor_spec.rb +9 -0
  319. data/lib/hecks/query_specification/common/dsl.rb +58 -0
  320. data/lib/hecks/query_specification/common/inspection_spec.rb +9 -0
  321. data/lib/hecks/query_specification/common/limit_spec.rb +9 -0
  322. data/lib/hecks/query_specification/common/null_policy.rb +92 -0
  323. data/lib/hecks/query_specification/common/null_semantics.rb +11 -0
  324. data/lib/hecks/query_specification/common/offset_spec.rb +9 -0
  325. data/lib/hecks/query_specification/common/options.rb +32 -0
  326. data/lib/hecks/query_specification/common/order_by.rb +9 -0
  327. data/lib/hecks/query_specification/common/specification.rb +10 -0
  328. data/lib/hecks/query_specification/common/where_clause.rb +9 -0
  329. data/lib/hecks/query_specification/field_path.rb +107 -0
  330. data/lib/hecks/query_specification/hop_path.rb +132 -0
  331. data/lib/hecks/query_specification/read_model/specification.rb +18 -0
  332. data/lib/hecks/query_specification.rb +14 -0
  333. data/lib/hecks/rendering.rb +48 -0
  334. data/lib/hecks/router/namespace_installer.rb +157 -0
  335. data/lib/hecks/router.rb +70 -0
  336. data/lib/hecks/runtime/aggregate_lock.rb +45 -0
  337. data/lib/hecks/runtime/boot_gates.rb +41 -0
  338. data/lib/hecks/runtime/caller.rb +66 -0
  339. data/lib/hecks/runtime/capability_graph.rb +44 -0
  340. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +135 -0
  341. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +260 -0
  342. data/lib/hecks/runtime/command_interpreter.rb +497 -0
  343. data/lib/hecks/runtime/command_rules/admissibility.rb +357 -0
  344. data/lib/hecks/runtime/command_rules/arithmetic.rb +260 -0
  345. data/lib/hecks/runtime/command_rules/authorization.rb +65 -0
  346. data/lib/hecks/runtime/command_rules/emission.rb +34 -0
  347. data/lib/hecks/runtime/command_rules/references.rb +189 -0
  348. data/lib/hecks/runtime/command_rules.rb +28 -0
  349. data/lib/hecks/runtime/dependency_planning.rb +245 -0
  350. data/lib/hecks/runtime/dispatcher.rb +301 -0
  351. data/lib/hecks/runtime/entity_element.rb +253 -0
  352. data/lib/hecks/runtime/entity_interpreter.rb +299 -0
  353. data/lib/hecks/runtime/errors.rb +122 -0
  354. data/lib/hecks/runtime/event.rb +51 -0
  355. data/lib/hecks/runtime/identity.rb +156 -0
  356. data/lib/hecks/runtime/instance.rb +175 -0
  357. data/lib/hecks/runtime/interpreting.rb +90 -0
  358. data/lib/hecks/runtime/loader.rb +184 -0
  359. data/lib/hecks/runtime/policy_interpreter.rb +366 -0
  360. data/lib/hecks/runtime/port_operation_interpreter.rb +210 -0
  361. data/lib/hecks/runtime/query_interpreter.rb +286 -0
  362. data/lib/hecks/runtime/reaction_invocation.rb +253 -0
  363. data/lib/hecks/runtime/read_model_interpreter.rb +341 -0
  364. data/lib/hecks/runtime/rebuild_sweep.rb +74 -0
  365. data/lib/hecks/runtime/reference_hop.rb +99 -0
  366. data/lib/hecks/runtime/refusal_wording.rb +126 -0
  367. data/lib/hecks/runtime/registry/saga_persistence.rb +142 -0
  368. data/lib/hecks/runtime/registry/verification.rb +246 -0
  369. data/lib/hecks/runtime/registry.rb +279 -0
  370. data/lib/hecks/runtime/remote_dispatcher.rb +143 -0
  371. data/lib/hecks/runtime/routing.rb +96 -0
  372. data/lib/hecks/runtime/saga_interpreter/correlation.rb +97 -0
  373. data/lib/hecks/runtime/saga_interpreter.rb +503 -0
  374. data/lib/hecks/runtime/saga_pending_dispatch.rb +45 -0
  375. data/lib/hecks/runtime/tenant_check.rb +84 -0
  376. data/lib/hecks/runtime/tenant_scope.rb +55 -0
  377. data/lib/hecks/runtime/value/admission.rb +130 -0
  378. data/lib/hecks/runtime/value/coercion.rb +565 -0
  379. data/lib/hecks/runtime/value/invariant_violation.rb +5 -0
  380. data/lib/hecks/runtime/value.rb +125 -0
  381. data/lib/hecks/runtime.rb +107 -0
  382. data/lib/hecks/storehouse.rb +632 -0
  383. data/lib/hecks/version.rb +16 -0
  384. data/lib/hecks/vocabulary.rb +218 -0
  385. data/lib/hecks.rb +123 -6
  386. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +90 -0
  387. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +128 -0
  388. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +160 -0
  389. metadata +412 -222
  390. data/bin/hecks +0 -7
  391. data/bin/hecks-package +0 -65
  392. data/bin/hecks_console +0 -12
  393. data/bin/hecks_serverless +0 -6
  394. data/lib/cli/build.rb +0 -14
  395. data/lib/cli/command_runner.rb +0 -28
  396. data/lib/cli/console.rb +0 -10
  397. data/lib/cli/generate.rb +0 -37
  398. data/lib/cli/hecks-cli.rb +0 -27
  399. data/lib/cli/test.rb +0 -57
  400. data/lib/console/commands.rb +0 -8
  401. data/lib/console/hecks-console.rb +0 -1
  402. data/lib/packager/README.md +0 -0
  403. data/lib/packager/app_runner.rb +0 -21
  404. data/lib/packager/args.rb +0 -26
  405. data/lib/packager/compatibility/fixnum.rb +0 -6
  406. data/lib/packager/hecks.rb +0 -39
  407. data/lib/packager/query_runner.rb +0 -21
  408. data/lib/packager/resources/Dockerfile +0 -11
  409. data/lib/packager/resources/app_binary +0 -7
  410. data/lib/packager/resources/bundle_config +0 -3
  411. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz +0 -0
  412. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-osx.tar.gz +0 -0
  413. data/lib/serverless/Domain +0 -32
  414. data/lib/serverless/cli.rb +0 -75
  415. data/lib/serverless/resources/command_name.js +0 -5
  416. data/lib/serverless/resources/environment.js +0 -7
  417. data/lib/serverless/resources/handler.js.tt +0 -28
  418. data/lib/serverless/resources/run_binary.js +0 -22
  419. data/lib/serverless/resources/serverless.yml +0 -20
@@ -0,0 +1,714 @@
1
+ require_relative "word_gate"
2
+ module Hecks
3
+ module Bluebook
4
+ module DSL
5
+ class CommandBuilder
6
+ GRAMMAR_CONTEXT = "Command"
7
+
8
+ include AttributeCollector
9
+ include RuleReference
10
+ include WordGate
11
+
12
+ # Vendored addition, not (yet) upstream hecks (migration plan
13
+ # task 4): a sentinel for "this keyword was never passed", distinct
14
+ # from Ruby's own nil/false. `then_set`'s ORIGINAL default (`to:
15
+ # nil`) could not tell "not given" apart from "given, and the
16
+ # value IS false" — `to || from` silently treats `to: false` the
17
+ # same as an absent `to:` and falls through to `from` (also
18
+ # absent), so `then_set :accepted, to: false` raised "names no
19
+ # operation" for the one value most likely to be written that way
20
+ # (a boolean flip). Confirmed real, live: miette's
21
+ # dream_interpretation.bluebook (`then_set :accepted, to: false`)
22
+ # and transparency.bluebook (`then_set :always, to: false`). TODO
23
+ # upstream via bin/evolve.
24
+ UNSET = Object.new.freeze
25
+ private_constant :UNSET
26
+
27
+ def initialize(name, owner: nil, from: nil, named_givens: {}, owner_attributes: [], owner_constructs: [],
28
+ entity_shared_givens: {})
29
+ @name = name
30
+ @owner = owner
31
+ @givens = []
32
+ @ensures = []
33
+ @mutations = []
34
+ @emits = []
35
+ @named_givens = named_givens
36
+ @owner_attributes = owner_attributes
37
+ @owner_constructs = owner_constructs
38
+ # THE AGGREGATE-WIDE cross-entity pool — see
39
+ # `AggregateBuilder#entity`'s own comment and `EntityBuilder#
40
+ # given`'s. Empty (never populated) for an AGGREGATE-owned
41
+ # command, which already checks its own owner's `named_givens`
42
+ # directly and has no siblings to reach across; real only for
43
+ # an ENTITY-owned command's own bare reference.
44
+ @entity_shared_givens = entity_shared_givens
45
+ # NORMALIZED the exact same way `StateTransition#from` already
46
+ # is — one state or several, a single spelling either way,
47
+ # both read back through `Array(...)` at check time.
48
+ @from = case from
49
+ when Array then from.map(&:to_s)
50
+ when nil then nil
51
+ else from.to_s
52
+ end
53
+ end
54
+
55
+ # A command carries ONE responsibility role — the language never
56
+ # declared an OR between two roles, so a second `role` call would
57
+ # otherwise silently win while the first still looked declared,
58
+ # exactly the failure mode `reference_to`'s own duplicate guard
59
+ # (below) already exists to prevent for a command's root.
60
+ #
61
+ # RENAMED FROM `role` — item #13's full metaprogrammed dispatch
62
+ # (slice 4). This is a uniqueness gate on PRIOR STATE (`@role`
63
+ # already set), not a pure function of the argument's own value —
64
+ # a genuinely different shape than a plain fill, so it stays
65
+ # hand-written and is reached through `calls:` like `attribute`
66
+ # was in slice 3. Bootstrap-reachable (every self-hosted command
67
+ # declares a role), so also named in
68
+ # `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`.
69
+ def role_impl(value)
70
+ raise Malformed,
71
+ "#{@name} declares role twice — a command carries ONE " \
72
+ "responsibility; the second would silently win and the " \
73
+ "first would still look declared" if @role
74
+
75
+ @role = value
76
+ end
77
+
78
+ def goal(value) = @goal = value
79
+
80
+ # See AggregateBuilder#provenance's own comment — identical shape,
81
+ # one level down.
82
+ # RENAMED FROM `provenance` — item #13's full metaprogrammed
83
+ # dispatch (slice 4c). Bootstrap-reachable, in
84
+ # GenericDispatch::BOOTSTRAP_CALLS_FALLBACK.
85
+ def provenance_impl(from:) = @provenance = from
86
+
87
+ # `optional:` rides here as well as on a plain attribute : `as:` makes a
88
+ # reference into a NAMED ARGUMENT, and a named argument is exactly the kind
89
+ # of fact that may or may not be given. The meta-domain's Verb.Declare
90
+ # points at the Entity a command belongs to — and most commands belong to no
91
+ # entity at all.
92
+ # RENAMED FROM `reference_to` — item #13's full metaprogrammed
93
+ # dispatch (slice 4b). Bootstrap-reachable, in
94
+ # GenericDispatch::BOOTSTRAP_CALLS_FALLBACK.
95
+ def reference_to_impl(type, as: nil, optional: false)
96
+ demodulised = Naming.demodulise(type)
97
+ # moved to the language: given "a command names what it acts on", on Verb.ActsOn
98
+
99
+ # `as:` MEANS "a named attribute", not "the root I act on" — so a command
100
+ # can point at another instance of its OWN kind. Without this,
101
+ # `reference_to Aggregate, as: :points_at` on a command owned by Aggregate
102
+ # read as a second self-reference and was refused as naming two roots,
103
+ # which is how the meta-domain's own Aggregate.Reference could not say the
104
+ # one thing it exists to say. Transfer has said
105
+ # `reference_to Account, as: :source` for as long as banking has existed;
106
+ # this is the same sentence when the target happens to be the owner.
107
+ return cross_reference(demodulised, as, optional) if as || demodulised.to_s != @owner.to_s
108
+
109
+ if @references
110
+ raise Malformed,
111
+ "#{@name} references #{@owner} twice — a command acts on ONE " \
112
+ "root ; the second would silently win and the first would " \
113
+ "still look declared"
114
+ end
115
+
116
+ @references = demodulised
117
+ end
118
+
119
+ private
120
+
121
+ def cross_reference(target, as, optional = false)
122
+ attribute_impl(as || default_reference_name(target), Reference.new(target), optional: optional)
123
+ end
124
+
125
+ public
126
+
127
+ # NO BLOCK is a REFERENCE, not a fresh declaration (S10, ADR
128
+ # 0025 — "a precondition shared across commands is declared
129
+ # once. An aggregate declares it by name and commands
130
+ # reference it"): the SAME word, the SAME shape
131
+ # (`AggregateBuilder#given`, block required there), so naming a
132
+ # precondition back is spelled exactly like declaring one would
133
+ # be, minus the block — one idea, one word, never a second
134
+ # spelling ("requires"/"precondition") for "use the one already
135
+ # named". Resolved against whatever the OWNING aggregate has
136
+ # declared so far — see `AggregateBuilder#command`'s own
137
+ # comment on why that means declaration order matters here.
138
+ # RENAMED FROM `given` — item #13's full metaprogrammed dispatch
139
+ # (slice 4b), same reasoning as reference_to_impl above.
140
+ def given_impl(description, &predicate)
141
+ return reference_named_given(description) unless predicate
142
+
143
+ # moved to the language: given "a rule says what it means", on Verb.Rule
144
+
145
+ @givens << build_rule(Given, description, predicate, owner_name: @name, word: "given",
146
+ extraction_failure: "its source could not be read, so no other runtime could ever evaluate it")
147
+ end
148
+
149
+ private
150
+
151
+ # PRIMITIVE 1 (RuleReference#resolve_hash_chain) — FIRST this
152
+ # command's own owner (as always), THEN — only for a piece-owned
153
+ # command, where it is real — a SIBLING piece's own entity-level
154
+ # declaration under the SAME aggregate (`@entity_shared_givens`,
155
+ # threaded from `EntityBuilder#given`'s own write-through).
156
+ # "customer is active" declared once on `Visit`, referenced bare
157
+ # by `KeyIssuance.Return` — two different pieces, same aggregate,
158
+ # same predicate — is exactly the shape this second pool exists
159
+ # for.
160
+ def reference_named_given(description)
161
+ verify_resolves_via!("given", "Command", "hash_chain")
162
+ named = resolve_hash_chain([@named_givens, @entity_shared_givens], description) ||
163
+ raise(Malformed,
164
+ "#{@name}'s given #{description.inspect} names no precondition " \
165
+ "#{@owner} declares, and no sibling piece under the same " \
166
+ "aggregate declares it either — declare it once with a block " \
167
+ "(#{@owner}'s own given(#{description.inspect}) { ... }), before " \
168
+ "the commands that reference it")
169
+
170
+ @givens << named
171
+ end
172
+
173
+ public
174
+
175
+ # The POSTCONDITION — a given for the far side of the mutations,
176
+ # evaluated against the settled record with `old` naming the state
177
+ # as it stood before them: `ensures("...") { old.balance.cents ==
178
+ # balance.cents + amount.cents }`. Same extraction, same Rule
179
+ # shape, same refusal form; EnsuresNotMet instead of GivenNotMet.
180
+ def ensures(description, &predicate)
181
+ @ensures << build_rule(Given, description, predicate, owner_name: @name, word: "ensures",
182
+ extraction_failure: "a postcondition is carried as text, and this one has none")
183
+ end
184
+
185
+ # `sets` is the word; `then_set` is the spelling every existing
186
+ # bluebook was written under (Syntax::Keyword carries the rename as
187
+ # `was:`), and it stays answered here forever — a renamed word's old
188
+ # era keeps booting, which is the whole point of the rename column.
189
+ #
190
+ # Vendored addition, not (yet) upstream hecks: `then_set
191
+ # :target, from: :source_field` (hecks_conception/miette, found
192
+ # live in body/doctor/bluebook/doctor.bluebook) -- semantically
193
+ # identical to `to:` (copy this argument/field into the target),
194
+ # different word. TODO upstream via bin/evolve (migration plan
195
+ # task 7): decide whether `from:` or `to:` becomes the canonical
196
+ # spelling.
197
+ #
198
+ # Vendored addition, not (yet) upstream hecks (migration plan
199
+ # task 4, i106 in-DSL math): `multiply:`/`clamp:` -- per-tick organ
200
+ # math (miette's body/organs/bluebook: strength decays ×0.98,
201
+ # weight/strength clamp to [0, 1]) that used to be shell-side awk
202
+ # and moved into the bluebook itself. `multiply:` mirrors
203
+ # increment/decrement's shape exactly (a Numeric amount, applied
204
+ # by CommandRules::Arithmetic -- see that file's own comment on
205
+ # the matching Float-support widening this required). `clamp:`
206
+ # is a genuinely different shape -- its source is always a literal
207
+ # `[min, max]` pair, never an argument reference, and it bounds
208
+ # the CURRENT value rather than combining it with an amount -- so
209
+ # it does not reuse `arithmetic`/`arithmetic_value_object` at all;
210
+ # see MutationApplier#apply's own `:clamp` branch.
211
+ #
212
+ # `remove:` -- vendored addition, not (yet) upstream hecks
213
+ # (migration plan task 4): the list-removal counterpart to
214
+ # `append:` (plan.bluebook's own RemoveDependency/DeactivateSprint
215
+ # commands: "the runtime list-remove primitive (then_set remove:)
216
+ # drops it from the list element-wise, with no read-modify-write
217
+ # -- so a concurrent Add can never be lost"). Matches an element
218
+ # by VALUE equality against `mutation.source` (resolved and
219
+ # Value-coerced the same way increment/decrement/multiply already
220
+ # coerce their own amount -- see MutationApplier#removed).
221
+ #
222
+ # `sets :field, true` -- vendored addition, not (yet) upstream
223
+ # hecks (migration plan task 8): a bare positional literal
224
+ # instead of `to:` -- 14 occurrences across hecks_nursury
225
+ # (oceanography.bluebook/volcanology.bluebook and others),
226
+ # always a boolean shorthand (`sets :deployed, true`, never
227
+ # a string/number positional -- checked directly, zero non-
228
+ # boolean occurrences of the bare-positional-second-arg shape
229
+ # anywhere in the corpus). Folded into `to:` itself rather than
230
+ # given its own mutation op -- semantically identical, same
231
+ # UNSET-sentinel discipline the `to: false` fix already
232
+ # established (a positional `false` must read as "set to
233
+ # false," not "absent," same as the keyword form). Only applied
234
+ # when `to:` itself was NOT also given, so an explicit `to:`
235
+ # keyword always wins over a stray positional.
236
+ #
237
+ # `sets` is the word (ADR 0025 reverts `then_set` — the grammar
238
+ # already declared `sets`, `was: "then_set"`, and 143 of 143 live
239
+ # call sites are `then_set`, so this method was the one thing
240
+ # still backwards). `to:` is OMITTABLE when it would only repeat
241
+ # the target — `sets :number` alone already means `to: :number`
242
+ # — and the REDUNDANT explicit spelling is refused outright
243
+ # (principle 1, "one idea, one spelling": `sets :number, to:
244
+ # :number` says nothing `sets :number` doesn't). `from:` — a
245
+ # pure synonym for `to:` the language's own refusal message had
246
+ # already forgotten about — is gone; write `to:`.
247
+ # THE OP EACH KWARG SELECTS — `spec/syntax_conformance_spec.rb`'s own
248
+ # "selects the same op..." check holds this constant to the self-
249
+ # hosted table's own `Argument#selects` column (`"op=set"`,
250
+ # `"op=append"`, ...; whole-project table-unification survey, item
251
+ # #1), the same field `rust/parser/src/keywords.rs`'s `ArgumentRow.
252
+ # selects` already carries. `to:` is the one kwarg whose own name
253
+ # differs from the op it selects — every other kwarg selects the op
254
+ # of its own name.
255
+ KWARG_TO_OP = { to: :set, append: :append, increment: :increment, decrement: :decrement,
256
+ multiply: :multiply, clamp: :clamp, remove: :remove }.freeze
257
+
258
+ # RENAMED FROM `sets` — item #13's full metaprogrammed dispatch
259
+ # (slice 4c). The `KWARG_TO_OP` op-selection mapping is already
260
+ # table-verified (`Argument#selects`), but the REST (UNSET-
261
+ # sentinel discipline, redundant-spelling refusal, omittable-
262
+ # `to:` fallback, one-mutation-only refusal, the position-
263
+ # preserving `resolve_*!` reinsertion) is keyed off RUNTIME
264
+ # STATE, not a pure function of a static row — stays hand-
265
+ # written, reached through `calls:` like everything else here.
266
+ # Bootstrap-reachable, in BOOTSTRAP_CALLS_FALLBACK.
267
+ def sets_impl(target, positional_to = UNSET, to: UNSET, append: UNSET,
268
+ increment: UNSET, decrement: UNSET, multiply: UNSET, clamp: UNSET, remove: UNSET)
269
+ # moved to the language: given "a mutation names a target", on Verb.Change
270
+
271
+ to = positional_to if to.equal?(UNSET) && !positional_to.equal?(UNSET)
272
+
273
+ # `to:` only ever REPEATS the target when it's a Symbol naming a
274
+ # field — a literal (`to: false`, the bare positional-boolean
275
+ # shorthand, a String, ...) is a VALUE, never a redundant name,
276
+ # so it never has `.to_sym` to compare in the first place.
277
+ if to.is_a?(Symbol) && to == target.to_sym
278
+ raise Malformed,
279
+ "#{@name}'s sets :#{target}, to: :#{target} repeats the target — " \
280
+ "sets :#{target} alone already means the same"
281
+ end
282
+
283
+ given = { to: to, append: append, increment: increment, decrement: decrement,
284
+ multiply: multiply, clamp: clamp, remove: remove }
285
+ .reject { |_, source| source.equal?(UNSET) }
286
+ named = given.to_h { |kwarg, source| [KWARG_TO_OP.fetch(kwarg), source] }
287
+
288
+ # THE OMITTABLE CASE. No operation was named at all — not even a
289
+ # bare `to:` — so this is `sets :field` alone, which means
290
+ # exactly what the redundant, refused spelling above would have.
291
+ named = { set: target } if named.empty?
292
+
293
+ if named.size > 1
294
+ raise Malformed,
295
+ "#{@name}'s sets :#{target} tries to #{named.keys.join(' and ')} " \
296
+ "at once — one mutation, one meaning"
297
+ end
298
+
299
+ op, source = named.first
300
+ @mutations << Mutation.new(target: target.to_sym, op: op, source: normalize_append_source(op, source))
301
+ end
302
+
303
+ # LEGACY UNDER SHADOW-PARSING (S0a's own bridge) — frozen era text
304
+ # minted before this rename still parses; live source refuses it,
305
+ # naming the replacement.
306
+ # RENAMED FROM `then_set` — item #13's full metaprogrammed
307
+ # dispatch (slice 5). Not bootstrap-reachable. Now has its own
308
+ # dedicated, `status: "deprecated"` Keyword row (syntax.bluebook)
309
+ # rather than living only as `sets`'s own `was:` — see that
310
+ # row's own comment for why.
311
+ def then_set_impl(target, positional_to = UNSET, **kwargs)
312
+ return legacy_then_set(target, positional_to, **kwargs) if MetaValidator.shadow_parsing?
313
+
314
+ raise Malformed, "#{@name}'s then_set is gone — sets is the word now"
315
+ end
316
+
317
+ # No raise here. "an event is named" is declared in the language itself —
318
+ # language/bluebook/behavior.bluebook, on Command.Announce — and MetaValidator is what
319
+ # enforces it. This is the first rule to move ACROSS rather than be
320
+ # duplicated : delete the declaration and an unnamed event is accepted,
321
+ # which is what makes the meta-domain load-bearing rather than decorative.
322
+ #
323
+ # BARE CONSTANT ACCEPTED (ADR 0025, S6 — "events first-class"),
324
+ # `emits Account::AccountFrozen`, resolved through `ConstShim` the
325
+ # same way `trigger`/`dispatch` already resolve a command
326
+ # reference (`Naming.event_ref`, that method's own header). NOT
327
+ # yet a REQUIRED spelling, deliberately, unlike `trigger`/
328
+ # `dispatch`'s own quoted-text refusal: those were safe to refuse
329
+ # only because command references are already 100% migrated
330
+ # across the live corpus (verified 2026-08-27) — `emits`/`on`
331
+ # are not, so refusing the quoted form here would break every
332
+ # live `.bluebook` site this pass didn't touch, not just frozen
333
+ # era text `shadow_parse` exists to keep readable. Both forms
334
+ # are accepted in live source until a full corpus migration
335
+ # lands and the same refusal this file's `reference_to`/
336
+ # `trigger_impl` already carry can be added here safely.
337
+ def emits(event_name)
338
+ @emits << Naming.event_ref(event_name)
339
+ end
340
+
341
+ # THE RECORD'S OWN VALUE AS A MUTATION SOURCE — `sets :positions,
342
+ # append: { ply: state(:ply), knights: state(:knights) }` copies
343
+ # what the record holds NOW into the new element; `sets :last,
344
+ # to: state(:current)` copies one field onto another. A bare
345
+ # Symbol always names an argument (see `resolve_append_fields!`),
346
+ # so without this a command could not snapshot its own state at
347
+ # all. `Literal::StateRef`'s own comment has the wire spelling.
348
+ def state(name) = StateRef.new(name.to_sym)
349
+
350
+ # THE SYNCHRONOUS COUSIN OF `trigger` — an AGGREGATE-level command
351
+ # that hands its own dispatch to ONE nested entity command, checked
352
+ # and applied within the SAME atomic dispatch rather than a second
353
+ # one. Built because `trigger`/`saga`'s own dispatch (`Dispatcher
354
+ # #reenter`) is a REACTION — the triggering command has already
355
+ # committed by the time it runs, and both `PolicyInterpreter#deliver`
356
+ # and `SagaInterpreter#deliver_saga_dispatch` rescue a target's own
357
+ # refusal and RECORD it rather than raising it back to the original
358
+ # caller. That is correct for what those two exist for (an
359
+ # eventually-consistent process that can compensate), and wrong for
360
+ # a caller who needs a synchronous yes/no on whether the thing they
361
+ # asked for actually happened — a chess move's own legality, for
362
+ # instance, checked live building `domain/chess` in a downstream
363
+ # project. `delegates_to` fills exactly that gap: the target
364
+ # entity command's own `given`/`ensures` are enforced as real,
365
+ # unrescued Ruby exceptions, so a refusal deep in the entity's own
366
+ # rules is the DELEGATING command's own refusal too, and nothing
367
+ # from either side is saved unless both sides pass.
368
+ #
369
+ # `target` is always ONE hop, `"Entity.Command"` — an aggregate
370
+ # names the entity it owns directly, same reach a bare `given`
371
+ # reference already has (see `Knight`'s own comment on this
372
+ # domain's shared givens), not a multi-segment dispatch chain.
373
+ # `with:` resolves the SAME way `sets ..., append: {...}`'s own
374
+ # field map and a policy's own `trigger ..., with: {...}` already
375
+ # do: each value names one of THIS command's own declared/implicit
376
+ # arguments, read at dispatch time and handed to the target under
377
+ # its own key.
378
+ #
379
+ # MUTUALLY EXCLUSIVE with `sets`/`emits` on the SAME command — a
380
+ # delegating command is a pure passthrough by design (see this
381
+ # method's own header), so it declares no OTHER mutation or event of
382
+ # its own; its result IS whatever the delegated entity command's own
383
+ # `sets`/`emits` produced. Enforced in `build`, once every builder
384
+ # call has already run, so declaration order does not matter.
385
+ #
386
+ # STORED AS A MUTATION, not a new Command field — a real, deliberate
387
+ # choice, not a shortcut. `Command`'s own shape (givens/ensures/
388
+ # mutations/emits/...) is not just Ruby: it round-trips through this
389
+ # language's OWN self-hosted meta-domain (`Bluebook::MetaValidator`
390
+ # dispatches every declaration into a "Bluebook" domain describing
391
+ # itself, then REBUILDS the real runtime graph from what THAT domain
392
+ # holds — `Hecks.bluebook` registers what `MetaValidator.call`
393
+ # returns, never the builder's own object graph directly, confirmed
394
+ # by reading `meta_validator.rb`'s own `self.call`/`self.hold`).
395
+ # A genuinely NEW top-level Command field needs the meta-domain's
396
+ # own grammar (`language/bluebook/behavior.bluebook` or wherever
397
+ # Verb.Rule/Ensure/Change live) taught to carry it too — the same
398
+ # scale of change as the real "item #13" migration this file's own
399
+ # comments document throughout. A NEW MUTATION OP does not: `sets`'s
400
+ # own `mutations:` field is ALREADY a fully round-tripped part of
401
+ # that contract (`Assembly::CONTRACTS["Command"].fields[:mutations]`),
402
+ # and an append-shaped mutation ALREADY carries a multi-key `fields:`
403
+ # hash the exact shape `with:` needs — so `delegates_to` rides that
404
+ # existing, already-correct wire format under a new `op: :delegate`
405
+ # instead of inventing a parallel one. `MutationOp`'s own closed set
406
+ # (vocabulary.bluebook) gained `"delegate"` alongside `"append"`
407
+ # for exactly this reason, and the THREE meta-domain touch points
408
+ # that hard-coded `op == "append"` for the multi-binding shape
409
+ # (`meta_validator/readings.rb#mutation_rows`,
410
+ # `meta_validator/shapes.rb#mutation`, `assembly/marks.rb#mutation`)
411
+ # now check for `:delegate` alongside it, each with a comment
412
+ # pointing back here.
413
+ # RENAMED FROM `delegates_to` to `delegates_to_impl` on declaration
414
+ # — matches `sets_impl`/`given_impl`/`reference_to_impl`'s own
415
+ # convention (language/bluebook/syntax.bluebook's own Keyword row
416
+ # for this word names `calls: "delegates_to_impl"`), the same
417
+ # `word`-vs-`_impl` split every hand-written (not yet item-#13-
418
+ # generic-dispatch-migrated) DSL word here already follows.
419
+ def delegates_to_impl(target, with: {})
420
+ entity_name, _dot, command_name = target.to_s.rpartition(".")
421
+ if entity_name.empty? || command_name.empty?
422
+ raise Malformed,
423
+ "#{@name}'s delegates_to #{target.inspect} does not name an entity and a command " \
424
+ "(\"Entity.Command\") — the same one-hop shape a bare given reference already uses"
425
+ end
426
+
427
+ @mutations << Mutation.new(target: target.to_s, op: :delegate, source: with)
428
+ end
429
+
430
+ # A COMMAND DECLARING WHAT PAST FACT IT AMENDS — the append-only
431
+ # answer to "what if this record's history turns out to have been
432
+ # wrong": never rewrite the original event (the log stays exactly
433
+ # what it was), always append a NEW fact on top. `event` names the
434
+ # event this command corrects; `as:` optionally binds the located
435
+ # instance for a `given`/`ensures` to reference, the same shape
436
+ # `ensures`'s own `old` binding already has; `reason:` is not
437
+ # descriptive-only the way `goal` is — it is carried as data, the
438
+ # one thing an audit trail actually needs ("we corrected this, and
439
+ # here is why"), refused when blank the same way a `given`'s own
440
+ # description is required to say something.
441
+ #
442
+ # STORED AS A MUTATION, not a new Command field — see the
443
+ # KeywordSeed row's own comment (command.bluebook) for why: this
444
+ # is the exact same choice `delegates_to` already made, for the
445
+ # exact same reason. Rides the SAME multi-binding wire shape
446
+ # `append`/`delegate` use — `as:`/`reason:`/`reverses:` assembled
447
+ # by hand into one `source` hash, the way `sets_impl` assembles up
448
+ # to seven kwargs into one `named` hash above.
449
+ #
450
+ # `reverses: true` NAMES an intent to auto-derive the corrective
451
+ # `sets` from the original event's own mutations, rather than the
452
+ # author writing it — see `AggregateBuilder#seal_correction_targets`,
453
+ # where that derivation actually happens (it needs every sibling
454
+ # command in the aggregate already known, which this builder alone
455
+ # cannot see). MUTUALLY EXCLUSIVE with an explicit `sets` on the
456
+ # same command — two ways of saying the same thing is exactly the
457
+ # redundancy `sets`'s own omittable-`to:` rule refuses elsewhere.
458
+ #
459
+ # `as:` IS ALWAYS STORED AS TEXT, never left a bare Symbol —
460
+ # `Mutation#classified_source`/`#appended_fields` (Behaviour::
461
+ # Mutation) classify any bare Symbol field as `kind: "argument"`,
462
+ # meaning "resolve this against one of THIS command's own declared
463
+ # attributes at dispatch time" (append/delegate's own meaning for a
464
+ # Symbol). `as:` names no such thing — it is a plain label, not yet
465
+ # wired into the expression evaluator (a future round's work, once
466
+ # a real runtime consumer exists) — so coercing it to a String here
467
+ # keeps it out of that machinery entirely rather than silently
468
+ # miscategorised as an unresolvable argument reference.
469
+ def corrects_impl(event, as: nil, reason: nil, reverses: false)
470
+ if reason.to_s.strip.empty?
471
+ raise Malformed,
472
+ "#{@name}'s corrects #{event.inspect} names no reason — a correction " \
473
+ "is carried as data (an audit trail needs to say WHY), the same way a " \
474
+ "given's own description must say something"
475
+ end
476
+
477
+ @mutations << Mutation.new(target: event.to_s, op: :corrects,
478
+ source: { as: as&.to_s, reason: reason.to_s, reverses: reverses })
479
+ end
480
+
481
+ def build
482
+ resolve_implicit_attributes!
483
+
484
+ delegation = @mutations.find { |mutation| mutation.op == :delegate }
485
+ if delegation && (@mutations.size > 1 || @emits.any?)
486
+ raise Malformed,
487
+ "#{@name} both delegates_to #{delegation.target} and declares its own " \
488
+ "sets/emits — a delegating command is a pure passthrough (see delegates_to's " \
489
+ "own comment); its result is the delegated command's own"
490
+ end
491
+
492
+ Command.declare(
493
+ name: @name,
494
+ role: @role,
495
+ goal: @goal,
496
+ attributes: attributes,
497
+ givens: @givens,
498
+ ensures: @ensures,
499
+ mutations: @mutations,
500
+ emits: @emits,
501
+ references: @references,
502
+ from: @from,
503
+ provenance: @provenance
504
+ )
505
+ end
506
+
507
+ def self.build(name, owner: nil, from: nil, named_givens: {}, owner_attributes: [], owner_constructs: [],
508
+ entity_shared_givens: {}, &block)
509
+ builder = new(name, owner: owner, from: from, named_givens: named_givens,
510
+ owner_attributes: owner_attributes, owner_constructs: owner_constructs,
511
+ entity_shared_givens: entity_shared_givens)
512
+ builder.instance_eval(&block) if block
513
+ builder.build
514
+ end
515
+
516
+ private
517
+
518
+ # RESOLUTION RULES — see `docs/resolution-rules/README.md` for the
519
+ # precise, language-agnostic algorithm each of `resolve_bare_set!`/
520
+ # `resolve_append_fields!` implements (`implicit-command-attributes.md`
521
+ # / `implicit-append-fields.md`) — the contract a Rust mirror is
522
+ # written from, not inferred from this comment.
523
+ #
524
+ # `sets :field` ALONE (S5's own bare form — no `to:`, meaning
525
+ # `to: :field`) already says the command accepts an argument
526
+ # named `:field`; requiring a SEPARATE `attribute :field, Type`
527
+ # line that retypes what the owning aggregate/entity already
528
+ # declared is the same redundancy S10's `given` reference already
529
+ # killed for preconditions ("a precondition shared across
530
+ # commands is declared once... a command references it by
531
+ # name"). Same move here, one level down: when the command
532
+ # hasn't declared its own `:field`, import the OWNER's
533
+ # already-built `Attribute` verbatim (same type, pattern,
534
+ # optional, admits) instead of retyping it.
535
+ #
536
+ # Only the exact self-referential shape qualifies — `sets
537
+ # :field, to: :other` names a genuinely different source and
538
+ # stays exactly as explicit as it always was; `sets :field, to:
539
+ # false` (or any other literal) isn't naming an argument at all.
540
+ # Comparing as text rather than identity handles both sides
541
+ # (`source` carries whatever `target` was passed as; `target`
542
+ # itself is always symbolized) without caring which.
543
+ #
544
+ # Declaration order matters here the same way it already does
545
+ # for `identified_by`/`given` — the owner's own attribute must
546
+ # exist by the time THIS builder's `build` runs, which every
547
+ # real bluebook already satisfies (the aggregate/entity always
548
+ # declares its attributes before the commands that act on them).
549
+ def resolve_implicit_attributes!
550
+ @mutations.each do |mutation|
551
+ case mutation.op
552
+ when :set then resolve_bare_set!(mutation)
553
+ when :append then resolve_append_fields!(mutation)
554
+ end
555
+ refuse_unknown_state_sources!(mutation)
556
+ end
557
+ end
558
+
559
+ # `state(:name)` names one of the OWNER'S OWN fields — a snapshot
560
+ # of something the record actually holds. Refused at build, by
561
+ # name, the way an unknown `given` reference is; nothing here can
562
+ # read a field the aggregate never declared.
563
+ def refuse_unknown_state_sources!(mutation)
564
+ sources = mutation.source.is_a?(Hash) ? mutation.source.values : [mutation.source]
565
+ sources.grep(StateRef).each do |ref|
566
+ next if @owner_attributes.any? { |attr| attr.name == ref.name }
567
+
568
+ raise Malformed, "#{@name}'s sets :#{mutation.target} reads state(:#{ref.name}), " \
569
+ "which the owner does not declare"
570
+ end
571
+ end
572
+
573
+ def resolve_bare_set!(mutation)
574
+ # A SYMBOL naming its own target — never a literal that merely
575
+ # spells the same word. `sets :moved, to: "moved"` (a chess rook
576
+ # recording that it has moved, into a closed set whose member is
577
+ # literally "moved") used to read as the shorthand and import
578
+ # the owner's `moved` attribute onto the command — a phantom
579
+ # argument nothing ever passes, harmless at runtime only
580
+ # because the owner's default filled it, and a real, silent
581
+ # divergence for every projection that reads the command's
582
+ # declared arguments.
583
+ return unless mutation.source.is_a?(Symbol) && mutation.source.to_s == mutation.target.to_s
584
+ return if attributes.any? { |attr| attr.name == mutation.target }
585
+
586
+ owner_attr = @owner_attributes.find { |attr| attr.name == mutation.target }
587
+ attributes << owner_attr if owner_attr
588
+ end
589
+
590
+ # ONE HOP DEEPER than `resolve_bare_set!` — an `append:` mutation
591
+ # (`sets :ledger, append: { narrative: :narrative, ... }`) builds
592
+ # a NEW element of a LIST field, not the command's own root
593
+ # record, so a bare self-referential field inside it (the hash
594
+ # key equals its own value, same shorthand `resolve_bare_set!`
595
+ # already reads) can't resolve against `@owner_attributes` — the
596
+ # aggregate itself never stores `:narrative`, only the list
597
+ # element's own construct does (`attribute :ledger,
598
+ # list_of(LedgerEntry)`, and `LedgerEntry` is what actually
599
+ # declares `:narrative`). Resolves the list field's own element
600
+ # TYPE first (`element_type_for`), then that construct's own
601
+ # attribute of the same name — same verbatim-import, one level
602
+ # further down the same reasoning `resolve_bare_set!`'s own
603
+ # comment already gives.
604
+ #
605
+ # A non-self-referential value (`direction: { value: "credit" }`,
606
+ # a nested literal) is untouched — only a bare symbol equal to
607
+ # its own key ever qualifies, identical to `resolve_bare_set!`'s
608
+ # own target/source text comparison.
609
+ #
610
+ # POSITION-PRESERVING, not appended at the end — the exported IR
611
+ # is array-order-sensitive (attributes carry their own declared
612
+ # order onto the wire), so an append's fields are resolved as
613
+ # ONE CONTIGUOUS GROUP, in the mutation's own hash order,
614
+ # reinserted at whichever position the group's leftmost STILL-
615
+ # DECLARED member already occupies (or the end, if every member
616
+ # of the group is resolved). A plain `attributes << owner_attr`
617
+ # here would only ever reproduce the original order when the
618
+ # missing field happened to already be last — real, live
619
+ # evidence: `Keyword#was`/`Argument#variadic` (both genuinely
620
+ # last in their own append hash) round-tripped correctly under
621
+ # the naive append; every OTHER field in the same hash did not,
622
+ # caught by this codemod's own reboot-and-diff safety net rather
623
+ # than silently landing wrong.
624
+ def resolve_append_fields!(mutation)
625
+ return unless mutation.source.is_a?(Hash)
626
+
627
+ element = element_type_for(mutation.target)
628
+ return unless element
629
+
630
+ self_ref_fields = mutation.source.select { |field, value| value.is_a?(Symbol) && value.to_s == field.to_s }.keys
631
+ return if self_ref_fields.empty?
632
+
633
+ present = self_ref_fields.filter_map { |field| attributes.find { |attr| attr.name == field } }
634
+ return if present.size == self_ref_fields.size # already fully declared — nothing to resolve
635
+
636
+ anchor = present.empty? ? attributes.length : present.map { |attr| attributes.index(attr) }.min
637
+ attributes.reject! { |attr| present.include?(attr) }
638
+
639
+ group = self_ref_fields.filter_map do |field|
640
+ present.find { |attr| attr.name == field } || element.attributes.find { |attr| attr.name == field }
641
+ end
642
+ attributes.insert(anchor, *group)
643
+ end
644
+
645
+ # The owner's own LIST attribute names its element type as TEXT
646
+ # (`Attribute#type`, unwrapped from `list_of(...)` at declare
647
+ # time) — resolved against `@owner_constructs` (the owner's own
648
+ # value objects and entities, the only two kinds an element can
649
+ # be) by `hecks_name`, the same lookup
650
+ # `AttributeCollector#resolve_identity_field!` already uses for
651
+ # a value object's own name.
652
+ def element_type_for(list_field)
653
+ list_attr = @owner_attributes.find { |attr| attr.name == list_field && attr.list? }
654
+ return nil unless list_attr
655
+
656
+ @owner_constructs.find { |construct| construct.hecks_name.to_s == list_attr.type.to_s }
657
+ end
658
+
659
+ # LEGACY — see `then_set`'s own comment. The ORIGINAL implementation,
660
+ # verbatim: `from:` still a synonym for `to:`, no omittable-`to:`
661
+ # shorthand, no refusal for the redundant `to: target` spelling —
662
+ # frozen era text was minted under this reading, and a legacy
663
+ # grammar exists precisely so re-parsing it never silently changes
664
+ # what it meant.
665
+ def legacy_then_set(target, positional_to = UNSET, to: UNSET, from: UNSET, append: UNSET,
666
+ increment: UNSET, decrement: UNSET, multiply: UNSET, clamp: UNSET, remove: UNSET)
667
+ to = positional_to if to.equal?(UNSET) && !positional_to.equal?(UNSET)
668
+ set_source = to.equal?(UNSET) ? from : to
669
+
670
+ named = { set: set_source, append: append, increment: increment, decrement: decrement,
671
+ multiply: multiply, clamp: clamp, remove: remove }
672
+ .reject { |_, source| source.equal?(UNSET) }
673
+
674
+ if named.empty?
675
+ raise Malformed,
676
+ "#{@name}'s then_set :#{target} names no operation — " \
677
+ "give it to:, append:, increment:, decrement:, multiply:, clamp:, or remove:"
678
+ end
679
+
680
+ if named.size > 1
681
+ raise Malformed,
682
+ "#{@name}'s then_set :#{target} tries to #{named.keys.join(' and ')} " \
683
+ "at once — one mutation, one meaning"
684
+ end
685
+
686
+ op, source = named.first
687
+ @mutations << Mutation.new(target: target.to_sym, op: op, source: normalize_append_source(op, source))
688
+ end
689
+
690
+ # `append:` NORMALLY binds several fields at once (`append: {
691
+ # name: :name, amount: :amount }`) — `Mutation#appended_fields`/
692
+ # `MutationApplier#appended`/the meta-validator Judge's own
693
+ # `mutation_rows` all read `mutation.source` as a Hash
694
+ # unconditionally. A BARE value (`append: :single_field`, or any
695
+ # non-Hash literal) is the one-field shorthand: exactly what an
696
+ # explicit `append: { value: :single_field }` would have meant,
697
+ # named the same way a single-field value object's own implicit
698
+ # member already is (`MutationApplier#appended`'s own `:value`
699
+ # scalar-unwrap). Without this, that shorthand built a Mutation
700
+ # whose `source` was a bare Symbol, which crashed with a raw
701
+ # `NoMethodError` on `#transform_values` the moment anything
702
+ # downstream read it — at dispatch (`MutationApplier#appended`),
703
+ # at IR emission (`Mutation#appended_fields`), and in the
704
+ # meta-validator's own Judge (`Readings#mutation_rows`). #138.
705
+ def normalize_append_source(op, source)
706
+ return source unless op == :append
707
+ return source if source.is_a?(::Hash)
708
+
709
+ { value: source }
710
+ end
711
+ end
712
+ end
713
+ end
714
+ end