hecks 0.2.0 → 0.3.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 (408) 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 +403 -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 +62 -0
  12. data/lib/hecks/adapters/driven/heki/journal.rb +51 -0
  13. data/lib/hecks/adapters/driven/heki/saga_store.rb +0 -0
  14. data/lib/hecks/adapters/driven/heki/snapshot.rb +38 -0
  15. data/lib/hecks/adapters/driven/heki.adapter +4 -0
  16. data/lib/hecks/adapters/driven/heki.rb +148 -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 +131 -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 +80 -0
  28. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +168 -0
  29. data/lib/hecks/adapters/driven/postgres.adapter +5 -0
  30. data/lib/hecks/adapters/driven/postgres.rb +391 -0
  31. data/lib/hecks/adapters/driven/postgres_era/lineage/era_store.rb +171 -0
  32. data/lib/hecks/adapters/driven/postgres_era/lineage/field_cache.rb +190 -0
  33. data/lib/hecks/adapters/driven/postgres_era/lineage/head_compiler.rb +429 -0
  34. data/lib/hecks/adapters/driven/postgres_era/lineage/mint_transaction.rb +166 -0
  35. data/lib/hecks/adapters/driven/postgres_era/lineage/provisioning.rb +286 -0
  36. data/lib/hecks/adapters/driven/postgres_era/lineage/resumable_backfill.rb +168 -0
  37. data/lib/hecks/adapters/driven/postgres_era/lineage/tail_merge.rb +163 -0
  38. data/lib/hecks/adapters/driven/postgres_era/lineage/transform_installer.rb +114 -0
  39. data/lib/hecks/adapters/driven/postgres_era/lineage.rb +137 -0
  40. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/coverage_check.rb +89 -0
  41. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/era_resolver.rb +82 -0
  42. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/merge_coordinator.rb +43 -0
  43. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/minter.rb +132 -0
  44. data/lib/hecks/adapters/driven/postgres_era/lineage_manager.rb +74 -0
  45. data/lib/hecks/adapters/driven/postgres_era.adapter +6 -0
  46. data/lib/hecks/adapters/driven/postgres_era.rb +671 -0
  47. data/lib/hecks/adapters/driven/prism.adapter +3 -0
  48. data/lib/hecks/adapters/driven/prism.rb +80 -0
  49. data/lib/hecks/adapters/driven/secure_random_identity.adapter +3 -0
  50. data/lib/hecks/adapters/driven/secure_random_identity.rb +14 -0
  51. data/lib/hecks/adapters/driven/sql_query_builder.rb +221 -0
  52. data/lib/hecks/adapters/driven/sqlite/codec.rb +63 -0
  53. data/lib/hecks/adapters/driven/sqlite/projection.rb +105 -0
  54. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +180 -0
  55. data/lib/hecks/adapters/driven/sqlite.adapter +9 -0
  56. data/lib/hecks/adapters/driven/sqlite.rb +285 -0
  57. data/lib/hecks/adapters/driven/system_clock.adapter +3 -0
  58. data/lib/hecks/adapters/driven/system_clock.rb +14 -0
  59. data/lib/hecks/adapters/driven.rb +36 -0
  60. data/lib/hecks/adapters.rb +6 -0
  61. data/lib/hecks/behaviors/dsl.rb +96 -0
  62. data/lib/hecks/behaviors/expectations.rb +280 -0
  63. data/lib/hecks/behaviors/ir.rb +31 -0
  64. data/lib/hecks/behaviors/rspec.rb +42 -0
  65. data/lib/hecks/behaviors/runner.rb +96 -0
  66. data/lib/hecks/behaviors.rb +25 -0
  67. data/lib/hecks/bluebook/aggregate.rb +108 -0
  68. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +134 -0
  69. data/lib/hecks/bluebook/assembly/build.rb +48 -0
  70. data/lib/hecks/bluebook/assembly/contract.rb +112 -0
  71. data/lib/hecks/bluebook/assembly/contracts.rb +409 -0
  72. data/lib/hecks/bluebook/assembly/marks.rb +227 -0
  73. data/lib/hecks/bluebook/assembly/specializer.rb +70 -0
  74. data/lib/hecks/bluebook/assembly.rb +78 -0
  75. data/lib/hecks/bluebook/attribute.rb +96 -0
  76. data/lib/hecks/bluebook/behaviour/aggregate.rb +83 -0
  77. data/lib/hecks/bluebook/behaviour/attribute.rb +27 -0
  78. data/lib/hecks/bluebook/behaviour/chapter.rb +72 -0
  79. data/lib/hecks/bluebook/behaviour/command.rb +116 -0
  80. data/lib/hecks/bluebook/behaviour/domain_port.rb +25 -0
  81. data/lib/hecks/bluebook/behaviour/entity.rb +59 -0
  82. data/lib/hecks/bluebook/behaviour/hexagon.rb +50 -0
  83. data/lib/hecks/bluebook/behaviour/lifecycle.rb +51 -0
  84. data/lib/hecks/bluebook/behaviour/policy.rb +52 -0
  85. data/lib/hecks/bluebook/behaviour/process_manager.rb +38 -0
  86. data/lib/hecks/bluebook/behaviour/query.rb +10 -0
  87. data/lib/hecks/bluebook/behaviour/read_model.rb +29 -0
  88. data/lib/hecks/bluebook/behaviour/traits.rb +81 -0
  89. data/lib/hecks/bluebook/behaviour/value_object.rb +33 -0
  90. data/lib/hecks/bluebook/chapter.rb +68 -0
  91. data/lib/hecks/bluebook/command.rb +124 -0
  92. data/lib/hecks/bluebook/domain_port.rb +102 -0
  93. data/lib/hecks/bluebook/dsl/adapter_builder.rb +34 -0
  94. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +906 -0
  95. data/lib/hecks/bluebook/dsl/attribute_collector.rb +327 -0
  96. data/lib/hecks/bluebook/dsl/binding_proxy.rb +71 -0
  97. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +1018 -0
  98. data/lib/hecks/bluebook/dsl/command_builder.rb +626 -0
  99. data/lib/hecks/bluebook/dsl/const_shim.rb +81 -0
  100. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +79 -0
  101. data/lib/hecks/bluebook/dsl/entity_builder.rb +305 -0
  102. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +366 -0
  103. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +163 -0
  104. data/lib/hecks/bluebook/dsl/identity_declaration.rb +191 -0
  105. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +44 -0
  106. data/lib/hecks/bluebook/dsl/malformed.rb +7 -0
  107. data/lib/hecks/bluebook/dsl/policy_builder.rb +120 -0
  108. data/lib/hecks/bluebook/dsl/port_builder.rb +30 -0
  109. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +142 -0
  110. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +207 -0
  111. data/lib/hecks/bluebook/dsl/query_builder.rb +113 -0
  112. data/lib/hecks/bluebook/dsl/read_model_builder.rb +233 -0
  113. data/lib/hecks/bluebook/dsl/rule_reference.rb +172 -0
  114. data/lib/hecks/bluebook/dsl/translation_builder.rb +243 -0
  115. data/lib/hecks/bluebook/dsl/value_object_builder.rb +178 -0
  116. data/lib/hecks/bluebook/dsl/word_gate.rb +221 -0
  117. data/lib/hecks/bluebook/dsl/world_builder.rb +77 -0
  118. data/lib/hecks/bluebook/dsl.rb +45 -0
  119. data/lib/hecks/bluebook/entity.rb +103 -0
  120. data/lib/hecks/bluebook/expression/canonical_form.rb +55 -0
  121. data/lib/hecks/bluebook/expression/evaluator.rb +261 -0
  122. data/lib/hecks/bluebook/expression/projection.json +170 -0
  123. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +233 -0
  124. data/lib/hecks/bluebook/expression/resolver.rb +630 -0
  125. data/lib/hecks/bluebook/expression.rb +13 -0
  126. data/lib/hecks/bluebook/hexagon.rb +60 -0
  127. data/lib/hecks/bluebook/lifecycle.rb +42 -0
  128. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +54 -0
  129. data/lib/hecks/bluebook/meta_validator/judge.rb +559 -0
  130. data/lib/hecks/bluebook/meta_validator/plan.rb +332 -0
  131. data/lib/hecks/bluebook/meta_validator/port_judge.rb +47 -0
  132. data/lib/hecks/bluebook/meta_validator/readings.rb +350 -0
  133. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +312 -0
  134. data/lib/hecks/bluebook/meta_validator/shapes.rb +246 -0
  135. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +255 -0
  136. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +138 -0
  137. data/lib/hecks/bluebook/meta_validator/world_judge.rb +78 -0
  138. data/lib/hecks/bluebook/meta_validator.rb +452 -0
  139. data/lib/hecks/bluebook/model_check.rb +318 -0
  140. data/lib/hecks/bluebook/pattern_subset.rb +120 -0
  141. data/lib/hecks/bluebook/policy.rb +41 -0
  142. data/lib/hecks/bluebook/process_manager.rb +86 -0
  143. data/lib/hecks/bluebook/project_discovery.rb +30 -0
  144. data/lib/hecks/bluebook/project_loader.rb +40 -0
  145. data/lib/hecks/bluebook/project_register.rb +82 -0
  146. data/lib/hecks/bluebook/query.rb +61 -0
  147. data/lib/hecks/bluebook/read_model.rb +109 -0
  148. data/lib/hecks/bluebook/reference.rb +74 -0
  149. data/lib/hecks/bluebook/smoke_test.rb +166 -0
  150. data/lib/hecks/bluebook/synthesizer.rb +95 -0
  151. data/lib/hecks/bluebook/translation.rb +92 -0
  152. data/lib/hecks/bluebook/value_object.rb +58 -0
  153. data/lib/hecks/bluebook.rb +74 -0
  154. data/lib/hecks/codemod.rb +342 -0
  155. data/lib/hecks/construct.rb +71 -0
  156. data/lib/hecks/deploy/bluebook/deploy.bluebook +219 -0
  157. data/lib/hecks/deploy/bluebook/deploy.hecksagon +4 -0
  158. data/lib/hecks/deploy/oidc.json +18 -0
  159. data/lib/hecks/doc/reference.rb +389 -0
  160. data/lib/hecks/embryonaut_bluebook.rb +75 -0
  161. data/lib/hecks/facade/cli_door.rb +116 -0
  162. data/lib/hecks/facade/cli_runner.rb +190 -0
  163. data/lib/hecks/facade/command_request.rb +105 -0
  164. data/lib/hecks/facade/handle.rb +173 -0
  165. data/lib/hecks/facade/json_door.rb +154 -0
  166. data/lib/hecks/facade/surface/aggregate_door.rb +185 -0
  167. data/lib/hecks/facade/surface/chapter.rb +107 -0
  168. data/lib/hecks/facade/surface.rb +48 -0
  169. data/lib/hecks/facade.rb +44 -0
  170. data/lib/hecks/forms/app.rb +300 -0
  171. data/lib/hecks/forms/command_form_renderer.rb +113 -0
  172. data/lib/hecks/forms/examples/banking_console.bluebook +3 -0
  173. data/lib/hecks/forms/field_renderer.rb +170 -0
  174. data/lib/hecks/forms/field_shape.rb +232 -0
  175. data/lib/hecks/forms/html.rb +53 -0
  176. data/lib/hecks/forms/index_renderer.rb +35 -0
  177. data/lib/hecks/forms/page.rb +157 -0
  178. data/lib/hecks/forms/params.rb +132 -0
  179. data/lib/hecks/forms/query_form_renderer.rb +114 -0
  180. data/lib/hecks/forms/record_renderer.rb +115 -0
  181. data/lib/hecks/forms/record_table.rb +63 -0
  182. data/lib/hecks/forms/reference_options.rb +30 -0
  183. data/lib/hecks/forms/value_object_shape.rb +46 -0
  184. data/lib/hecks/forms.rb +54 -0
  185. data/lib/hecks/fqn.rb +94 -0
  186. data/lib/hecks/framework/bluebook/compliance.bluebook +1 -0
  187. data/lib/hecks/framework/bluebook/console_settings.bluebook +489 -0
  188. data/lib/hecks/framework/bluebook/framework.hecksagon +32 -0
  189. data/lib/hecks/framework/bluebook/governance.bluebook +130 -0
  190. data/lib/hecks/framework/bluebook/identity.bluebook +90 -0
  191. data/lib/hecks/framework/oidc.json +39 -0
  192. data/lib/hecks/framework.rb +90 -0
  193. data/lib/hecks/freezer.rb +67 -0
  194. data/lib/hecks/fuzzing/invalid_value_generator.rb +86 -0
  195. data/lib/hecks/fuzzing/isolated_boot.rb +92 -0
  196. data/lib/hecks/fuzzing/properties.rb +1146 -0
  197. data/lib/hecks/fuzzing/replay.rb +635 -0
  198. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +93 -0
  199. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +54 -0
  200. data/lib/hecks/fuzzing/sequence_generator/picker.rb +96 -0
  201. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +185 -0
  202. data/lib/hecks/fuzzing/sequence_generator.rb +130 -0
  203. data/lib/hecks/fuzzing/value_generator.rb +177 -0
  204. data/lib/hecks/fuzzing.rb +12 -0
  205. data/lib/hecks/grammar/evolve.rb +291 -0
  206. data/lib/hecks/grammar/expression.bluebook +417 -0
  207. data/lib/hecks/grammar/expression_operators.json +1152 -0
  208. data/lib/hecks/grammar/grammar.hecksagon +20 -0
  209. data/lib/hecks/grammar/oidc.json +69 -0
  210. data/lib/hecks/grammar/translation.bluebook +203 -0
  211. data/lib/hecks/grammar.rb +178 -0
  212. data/lib/hecks/ir.rb +126 -0
  213. data/lib/hecks/language/adapter.bluebook +116 -0
  214. data/lib/hecks/language/bluebook/aggregate.bluebook +581 -0
  215. data/lib/hecks/language/bluebook/attaches/paging.bluebook +76 -0
  216. data/lib/hecks/language/bluebook/bluebook.bluebook +256 -0
  217. data/lib/hecks/language/bluebook/bluebook.hecksagon +24 -0
  218. data/lib/hecks/language/bluebook/command.bluebook +437 -0
  219. data/lib/hecks/language/bluebook/entity.bluebook +316 -0
  220. data/lib/hecks/language/bluebook/policy.bluebook +172 -0
  221. data/lib/hecks/language/bluebook/process_manager.bluebook +264 -0
  222. data/lib/hecks/language/bluebook/projection.bluebook +267 -0
  223. data/lib/hecks/language/bluebook/query.bluebook +245 -0
  224. data/lib/hecks/language/bluebook/shape.bluebook +292 -0
  225. data/lib/hecks/language/bluebook/syntax.bluebook +447 -0
  226. data/lib/hecks/language/bluebook/vocabulary.bluebook +364 -0
  227. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +51 -0
  228. data/lib/hecks/language/hecksagon/domain_port.bluebook +76 -0
  229. data/lib/hecks/language/hecksagon/hecksagon.bluebook +131 -0
  230. data/lib/hecks/language/hecksagon/port_operation.bluebook +102 -0
  231. data/lib/hecks/language/oidc.json +313 -0
  232. data/lib/hecks/language/port.bluebook +92 -0
  233. data/lib/hecks/language/translation/translation.bluebook +110 -0
  234. data/lib/hecks/language/translation/translation_aggregate.bluebook +267 -0
  235. data/lib/hecks/language/world/wiring.bluebook +62 -0
  236. data/lib/hecks/language/world/world.bluebook +84 -0
  237. data/lib/hecks/literal.rb +125 -0
  238. data/lib/hecks/naming.rb +121 -0
  239. data/lib/hecks/ports/access_control.port +4 -0
  240. data/lib/hecks/ports/access_control.rb +62 -0
  241. data/lib/hecks/ports/agent/answers.rb +104 -0
  242. data/lib/hecks/ports/agent.port +4 -0
  243. data/lib/hecks/ports/agent.rb +167 -0
  244. data/lib/hecks/ports/authentication.port +4 -0
  245. data/lib/hecks/ports/authentication.rb +50 -0
  246. data/lib/hecks/ports/authorization.port +4 -0
  247. data/lib/hecks/ports/authorization.rb +59 -0
  248. data/lib/hecks/ports/clock.port +4 -0
  249. data/lib/hecks/ports/clock.rb +62 -0
  250. data/lib/hecks/ports/extraction.port +4 -0
  251. data/lib/hecks/ports/extraction.rb +37 -0
  252. data/lib/hecks/ports/identity_assignment.port +4 -0
  253. data/lib/hecks/ports/identity_assignment.rb +45 -0
  254. data/lib/hecks/ports/identity_generation.port +4 -0
  255. data/lib/hecks/ports/identity_generation.rb +49 -0
  256. data/lib/hecks/ports/identity_resolution.port +4 -0
  257. data/lib/hecks/ports/identity_resolution.rb +40 -0
  258. data/lib/hecks/ports/loading.port +4 -0
  259. data/lib/hecks/ports/loading.rb +13 -0
  260. data/lib/hecks/ports/persistence/append_only.rb +102 -0
  261. data/lib/hecks/ports/persistence/binding_policy.rb +56 -0
  262. data/lib/hecks/ports/persistence/execution.rb +18 -0
  263. data/lib/hecks/ports/persistence/lineage.rb +277 -0
  264. data/lib/hecks/ports/persistence/null_saga_store.rb +25 -0
  265. data/lib/hecks/ports/persistence/remote_runtime.rb +42 -0
  266. data/lib/hecks/ports/persistence/repository_factory.rb +30 -0
  267. data/lib/hecks/ports/persistence.port +4 -0
  268. data/lib/hecks/ports/persistence.rb +39 -0
  269. data/lib/hecks/ports/projection.port +4 -0
  270. data/lib/hecks/ports/projection.rb +76 -0
  271. data/lib/hecks/ports/query/in_memory.rb +60 -0
  272. data/lib/hecks/ports/query/ordering.rb +41 -0
  273. data/lib/hecks/ports/query.rb +36 -0
  274. data/lib/hecks/ports.rb +27 -0
  275. data/lib/hecks/projections/diagrams.rb +413 -0
  276. data/lib/hecks/projections/ir.rb +18 -0
  277. data/lib/hecks/projections/model/deviations.rb +98 -0
  278. data/lib/hecks/projections/model.rb +145 -0
  279. data/lib/hecks/projections/oidc.rb +110 -0
  280. data/lib/hecks/projections/parser_table.rb +159 -0
  281. data/lib/hecks/projections/reference.rb +38 -0
  282. data/lib/hecks/projections/shape.rb +31 -0
  283. data/lib/hecks/projections/statements.rb +110 -0
  284. data/lib/hecks/projections/vocabulary.rb +100 -0
  285. data/lib/hecks/projections.rb +32 -0
  286. data/lib/hecks/projector/cli_projector.rb +437 -0
  287. data/lib/hecks/projector/docs_projector.rb +321 -0
  288. data/lib/hecks/projector/exporter.rb +141 -0
  289. data/lib/hecks/projector/ir_projector.rb +18 -0
  290. data/lib/hecks/projector/narrate_projector.rb +243 -0
  291. data/lib/hecks/projector/target.rb +97 -0
  292. data/lib/hecks/projector.rb +186 -0
  293. data/lib/hecks/query_ir.rb +392 -0
  294. data/lib/hecks/query_specification/common/authorization_spec.rb +9 -0
  295. data/lib/hecks/query_specification/common/comparators.rb +25 -0
  296. data/lib/hecks/query_specification/common/comparison.rb +174 -0
  297. data/lib/hecks/query_specification/common/cursor_spec.rb +9 -0
  298. data/lib/hecks/query_specification/common/dsl.rb +58 -0
  299. data/lib/hecks/query_specification/common/inspection_spec.rb +9 -0
  300. data/lib/hecks/query_specification/common/limit_spec.rb +9 -0
  301. data/lib/hecks/query_specification/common/null_policy.rb +80 -0
  302. data/lib/hecks/query_specification/common/null_semantics.rb +11 -0
  303. data/lib/hecks/query_specification/common/offset_spec.rb +9 -0
  304. data/lib/hecks/query_specification/common/options.rb +32 -0
  305. data/lib/hecks/query_specification/common/order_by.rb +9 -0
  306. data/lib/hecks/query_specification/common/specification.rb +10 -0
  307. data/lib/hecks/query_specification/common/where_clause.rb +9 -0
  308. data/lib/hecks/query_specification/field_path.rb +89 -0
  309. data/lib/hecks/query_specification/hop_path.rb +130 -0
  310. data/lib/hecks/query_specification/read_model/specification.rb +18 -0
  311. data/lib/hecks/query_specification.rb +14 -0
  312. data/lib/hecks/rendering.rb +48 -0
  313. data/lib/hecks/router/namespace_installer.rb +157 -0
  314. data/lib/hecks/router.rb +70 -0
  315. data/lib/hecks/runtime/caller.rb +50 -0
  316. data/lib/hecks/runtime/capability_graph.rb +44 -0
  317. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +124 -0
  318. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +248 -0
  319. data/lib/hecks/runtime/command_interpreter.rb +413 -0
  320. data/lib/hecks/runtime/command_rules/admissibility.rb +307 -0
  321. data/lib/hecks/runtime/command_rules/arithmetic.rb +254 -0
  322. data/lib/hecks/runtime/command_rules/authorization.rb +64 -0
  323. data/lib/hecks/runtime/command_rules/emission.rb +34 -0
  324. data/lib/hecks/runtime/command_rules/references.rb +181 -0
  325. data/lib/hecks/runtime/command_rules.rb +28 -0
  326. data/lib/hecks/runtime/dependency_planning.rb +231 -0
  327. data/lib/hecks/runtime/dispatcher.rb +286 -0
  328. data/lib/hecks/runtime/entity_element.rb +253 -0
  329. data/lib/hecks/runtime/entity_interpreter.rb +228 -0
  330. data/lib/hecks/runtime/era_check.rb +136 -0
  331. data/lib/hecks/runtime/era_guard/shape_diff.rb +128 -0
  332. data/lib/hecks/runtime/era_guard.rb +193 -0
  333. data/lib/hecks/runtime/era_tamper.rb +61 -0
  334. data/lib/hecks/runtime/errors.rb +100 -0
  335. data/lib/hecks/runtime/event.rb +51 -0
  336. data/lib/hecks/runtime/identity.rb +128 -0
  337. data/lib/hecks/runtime/instance.rb +120 -0
  338. data/lib/hecks/runtime/interpreting.rb +69 -0
  339. data/lib/hecks/runtime/loader.rb +143 -0
  340. data/lib/hecks/runtime/policy_interpreter.rb +366 -0
  341. data/lib/hecks/runtime/port_operation_interpreter.rb +210 -0
  342. data/lib/hecks/runtime/query_interpreter.rb +254 -0
  343. data/lib/hecks/runtime/reaction_invocation.rb +245 -0
  344. data/lib/hecks/runtime/read_model_interpreter.rb +266 -0
  345. data/lib/hecks/runtime/rebuild_sweep.rb +74 -0
  346. data/lib/hecks/runtime/reference_hop.rb +99 -0
  347. data/lib/hecks/runtime/refusal_wording.rb +124 -0
  348. data/lib/hecks/runtime/registry/saga_persistence.rb +70 -0
  349. data/lib/hecks/runtime/registry/verification.rb +158 -0
  350. data/lib/hecks/runtime/registry.rb +218 -0
  351. data/lib/hecks/runtime/remote_dispatcher.rb +143 -0
  352. data/lib/hecks/runtime/routing.rb +96 -0
  353. data/lib/hecks/runtime/saga_interpreter/correlation.rb +97 -0
  354. data/lib/hecks/runtime/saga_interpreter.rb +301 -0
  355. data/lib/hecks/runtime/storage_shape.rb +120 -0
  356. data/lib/hecks/runtime/tenant_check.rb +84 -0
  357. data/lib/hecks/runtime/tenant_scope.rb +55 -0
  358. data/lib/hecks/runtime/value/admission.rb +112 -0
  359. data/lib/hecks/runtime/value/coercion.rb +500 -0
  360. data/lib/hecks/runtime/value/invariant_violation.rb +5 -0
  361. data/lib/hecks/runtime/value.rb +125 -0
  362. data/lib/hecks/runtime.rb +95 -0
  363. data/lib/hecks/translation/audit/approval_digest.rb +31 -0
  364. data/lib/hecks/translation/audit/layer_one.rb +37 -0
  365. data/lib/hecks/translation/audit/layer_two.rb +77 -0
  366. data/lib/hecks/translation/audit/unfed_report.rb +42 -0
  367. data/lib/hecks/translation/audit.rb +70 -0
  368. data/lib/hecks/translation/reattest.rb +72 -0
  369. data/lib/hecks/translation/rule_compiler.rb +120 -0
  370. data/lib/hecks/translation/scaffold/differ.rb +183 -0
  371. data/lib/hecks/translation/scaffold/renderer.rb +41 -0
  372. data/lib/hecks/translation/scaffold/writer.rb +28 -0
  373. data/lib/hecks/translation/scaffold.rb +30 -0
  374. data/lib/hecks/translation.rb +11 -0
  375. data/lib/hecks/version.rb +16 -0
  376. data/lib/hecks/vocabulary.rb +213 -0
  377. data/lib/hecks.rb +118 -6
  378. metadata +401 -222
  379. data/bin/hecks +0 -7
  380. data/bin/hecks-package +0 -65
  381. data/bin/hecks_console +0 -12
  382. data/bin/hecks_serverless +0 -6
  383. data/lib/cli/build.rb +0 -14
  384. data/lib/cli/command_runner.rb +0 -28
  385. data/lib/cli/console.rb +0 -10
  386. data/lib/cli/generate.rb +0 -37
  387. data/lib/cli/hecks-cli.rb +0 -27
  388. data/lib/cli/test.rb +0 -57
  389. data/lib/console/commands.rb +0 -8
  390. data/lib/console/hecks-console.rb +0 -1
  391. data/lib/packager/README.md +0 -0
  392. data/lib/packager/app_runner.rb +0 -21
  393. data/lib/packager/args.rb +0 -26
  394. data/lib/packager/compatibility/fixnum.rb +0 -6
  395. data/lib/packager/hecks.rb +0 -39
  396. data/lib/packager/query_runner.rb +0 -21
  397. data/lib/packager/resources/Dockerfile +0 -11
  398. data/lib/packager/resources/app_binary +0 -7
  399. data/lib/packager/resources/bundle_config +0 -3
  400. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz +0 -0
  401. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-osx.tar.gz +0 -0
  402. data/lib/serverless/Domain +0 -32
  403. data/lib/serverless/cli.rb +0 -75
  404. data/lib/serverless/resources/command_name.js +0 -5
  405. data/lib/serverless/resources/environment.js +0 -7
  406. data/lib/serverless/resources/handler.js.tt +0 -28
  407. data/lib/serverless/resources/run_binary.js +0 -22
  408. data/lib/serverless/resources/serverless.yml +0 -20
@@ -0,0 +1,626 @@
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: 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
+ def emits(event_name)
323
+ @emits << event_name.to_s
324
+ end
325
+
326
+ # THE RECORD'S OWN VALUE AS A MUTATION SOURCE — `sets :positions,
327
+ # append: { ply: state(:ply), knights: state(:knights) }` copies
328
+ # what the record holds NOW into the new element; `sets :last,
329
+ # to: state(:current)` copies one field onto another. A bare
330
+ # Symbol always names an argument (see `resolve_append_fields!`),
331
+ # so without this a command could not snapshot its own state at
332
+ # all. `Literal::StateRef`'s own comment has the wire spelling.
333
+ def state(name) = StateRef.new(name.to_sym)
334
+
335
+ # THE SYNCHRONOUS COUSIN OF `trigger` — an AGGREGATE-level command
336
+ # that hands its own dispatch to ONE nested entity command, checked
337
+ # and applied within the SAME atomic dispatch rather than a second
338
+ # one. Built because `trigger`/`saga`'s own dispatch (`Dispatcher
339
+ # #reenter`) is a REACTION — the triggering command has already
340
+ # committed by the time it runs, and both `PolicyInterpreter#deliver`
341
+ # and `SagaInterpreter#deliver_saga_dispatch` rescue a target's own
342
+ # refusal and RECORD it rather than raising it back to the original
343
+ # caller. That is correct for what those two exist for (an
344
+ # eventually-consistent process that can compensate), and wrong for
345
+ # a caller who needs a synchronous yes/no on whether the thing they
346
+ # asked for actually happened — a chess move's own legality, for
347
+ # instance, checked live building `domain/chess` in a downstream
348
+ # project. `delegates_to` fills exactly that gap: the target
349
+ # entity command's own `given`/`ensures` are enforced as real,
350
+ # unrescued Ruby exceptions, so a refusal deep in the entity's own
351
+ # rules is the DELEGATING command's own refusal too, and nothing
352
+ # from either side is saved unless both sides pass.
353
+ #
354
+ # `target` is always ONE hop, `"Entity.Command"` — an aggregate
355
+ # names the entity it owns directly, same reach a bare `given`
356
+ # reference already has (see `Knight`'s own comment on this
357
+ # domain's shared givens), not a multi-segment dispatch chain.
358
+ # `with:` resolves the SAME way `sets ..., append: {...}`'s own
359
+ # field map and a policy's own `trigger ..., with: {...}` already
360
+ # do: each value names one of THIS command's own declared/implicit
361
+ # arguments, read at dispatch time and handed to the target under
362
+ # its own key.
363
+ #
364
+ # MUTUALLY EXCLUSIVE with `sets`/`emits` on the SAME command — a
365
+ # delegating command is a pure passthrough by design (see this
366
+ # method's own header), so it declares no OTHER mutation or event of
367
+ # its own; its result IS whatever the delegated entity command's own
368
+ # `sets`/`emits` produced. Enforced in `build`, once every builder
369
+ # call has already run, so declaration order does not matter.
370
+ #
371
+ # STORED AS A MUTATION, not a new Command field — a real, deliberate
372
+ # choice, not a shortcut. `Command`'s own shape (givens/ensures/
373
+ # mutations/emits/...) is not just Ruby: it round-trips through this
374
+ # language's OWN self-hosted meta-domain (`Bluebook::MetaValidator`
375
+ # dispatches every declaration into a "Bluebook" domain describing
376
+ # itself, then REBUILDS the real runtime graph from what THAT domain
377
+ # holds — `Hecks.bluebook` registers what `MetaValidator.call`
378
+ # returns, never the builder's own object graph directly, confirmed
379
+ # by reading `meta_validator.rb`'s own `self.call`/`self.hold`).
380
+ # A genuinely NEW top-level Command field needs the meta-domain's
381
+ # own grammar (`language/bluebook/behavior.bluebook` or wherever
382
+ # Verb.Rule/Ensure/Change live) taught to carry it too — the same
383
+ # scale of change as the real "item #13" migration this file's own
384
+ # comments document throughout. A NEW MUTATION OP does not: `sets`'s
385
+ # own `mutations:` field is ALREADY a fully round-tripped part of
386
+ # that contract (`Assembly::CONTRACTS["Command"].fields[:mutations]`),
387
+ # and an append-shaped mutation ALREADY carries a multi-key `fields:`
388
+ # hash the exact shape `with:` needs — so `delegates_to` rides that
389
+ # existing, already-correct wire format under a new `op: :delegate`
390
+ # instead of inventing a parallel one. `MutationOp`'s own closed set
391
+ # (vocabulary.bluebook) gained `"delegate"` alongside `"append"`
392
+ # for exactly this reason, and the THREE meta-domain touch points
393
+ # that hard-coded `op == "append"` for the multi-binding shape
394
+ # (`meta_validator/readings.rb#mutation_rows`,
395
+ # `meta_validator/shapes.rb#mutation`, `assembly/marks.rb#mutation`)
396
+ # now check for `:delegate` alongside it, each with a comment
397
+ # pointing back here.
398
+ # RENAMED FROM `delegates_to` to `delegates_to_impl` on declaration
399
+ # — matches `sets_impl`/`given_impl`/`reference_to_impl`'s own
400
+ # convention (language/bluebook/syntax.bluebook's own Keyword row
401
+ # for this word names `calls: "delegates_to_impl"`), the same
402
+ # `word`-vs-`_impl` split every hand-written (not yet item-#13-
403
+ # generic-dispatch-migrated) DSL word here already follows.
404
+ def delegates_to_impl(target, with: {})
405
+ entity_name, _dot, command_name = target.to_s.rpartition(".")
406
+ if entity_name.empty? || command_name.empty?
407
+ raise Malformed,
408
+ "#{@name}'s delegates_to #{target.inspect} does not name an entity and a command " \
409
+ "(\"Entity.Command\") — the same one-hop shape a bare given reference already uses"
410
+ end
411
+
412
+ @mutations << Mutation.new(target: target.to_s, op: :delegate, source: with)
413
+ end
414
+
415
+ def build
416
+ resolve_implicit_attributes!
417
+
418
+ delegation = @mutations.find { |mutation| mutation.op == :delegate }
419
+ if delegation && (@mutations.size > 1 || @emits.any?)
420
+ raise Malformed,
421
+ "#{@name} both delegates_to #{delegation.target} and declares its own " \
422
+ "sets/emits — a delegating command is a pure passthrough (see delegates_to's " \
423
+ "own comment); its result is the delegated command's own"
424
+ end
425
+
426
+ Command.declare(
427
+ name: @name,
428
+ role: @role,
429
+ goal: @goal,
430
+ attributes: attributes,
431
+ givens: @givens,
432
+ ensures: @ensures,
433
+ mutations: @mutations,
434
+ emits: @emits,
435
+ references: @references,
436
+ from: @from,
437
+ provenance: @provenance
438
+ )
439
+ end
440
+
441
+ def self.build(name, owner: nil, from: nil, named_givens: {}, owner_attributes: [], owner_constructs: [],
442
+ entity_shared_givens: {}, &block)
443
+ builder = new(name, owner: owner, from: from, named_givens: named_givens,
444
+ owner_attributes: owner_attributes, owner_constructs: owner_constructs,
445
+ entity_shared_givens: entity_shared_givens)
446
+ builder.instance_eval(&block) if block
447
+ builder.build
448
+ end
449
+
450
+ private
451
+
452
+ # RESOLUTION RULES — see `docs/resolution-rules/README.md` for the
453
+ # precise, language-agnostic algorithm each of `resolve_bare_set!`/
454
+ # `resolve_append_fields!` implements (`implicit-command-attributes.md`
455
+ # / `implicit-append-fields.md`) — the contract a Rust mirror is
456
+ # written from, not inferred from this comment.
457
+ #
458
+ # `sets :field` ALONE (S5's own bare form — no `to:`, meaning
459
+ # `to: :field`) already says the command accepts an argument
460
+ # named `:field`; requiring a SEPARATE `attribute :field, Type`
461
+ # line that retypes what the owning aggregate/entity already
462
+ # declared is the same redundancy S10's `given` reference already
463
+ # killed for preconditions ("a precondition shared across
464
+ # commands is declared once... a command references it by
465
+ # name"). Same move here, one level down: when the command
466
+ # hasn't declared its own `:field`, import the OWNER's
467
+ # already-built `Attribute` verbatim (same type, pattern,
468
+ # optional, admits) instead of retyping it.
469
+ #
470
+ # Only the exact self-referential shape qualifies — `sets
471
+ # :field, to: :other` names a genuinely different source and
472
+ # stays exactly as explicit as it always was; `sets :field, to:
473
+ # false` (or any other literal) isn't naming an argument at all.
474
+ # Comparing as text rather than identity handles both sides
475
+ # (`source` carries whatever `target` was passed as; `target`
476
+ # itself is always symbolized) without caring which.
477
+ #
478
+ # Declaration order matters here the same way it already does
479
+ # for `identified_by`/`given` — the owner's own attribute must
480
+ # exist by the time THIS builder's `build` runs, which every
481
+ # real bluebook already satisfies (the aggregate/entity always
482
+ # declares its attributes before the commands that act on them).
483
+ def resolve_implicit_attributes!
484
+ @mutations.each do |mutation|
485
+ case mutation.op
486
+ when :set then resolve_bare_set!(mutation)
487
+ when :append then resolve_append_fields!(mutation)
488
+ end
489
+ refuse_unknown_state_sources!(mutation)
490
+ end
491
+ end
492
+
493
+ # `state(:name)` names one of the OWNER'S OWN fields — a snapshot
494
+ # of something the record actually holds. Refused at build, by
495
+ # name, the way an unknown `given` reference is; nothing here can
496
+ # read a field the aggregate never declared.
497
+ def refuse_unknown_state_sources!(mutation)
498
+ sources = mutation.source.is_a?(Hash) ? mutation.source.values : [mutation.source]
499
+ sources.grep(StateRef).each do |ref|
500
+ next if @owner_attributes.any? { |attr| attr.name == ref.name }
501
+
502
+ raise Malformed, "#{@name}'s sets :#{mutation.target} reads state(:#{ref.name}), " \
503
+ "which the owner does not declare"
504
+ end
505
+ end
506
+
507
+ def resolve_bare_set!(mutation)
508
+ # A SYMBOL naming its own target — never a literal that merely
509
+ # spells the same word. `sets :moved, to: "moved"` (a chess rook
510
+ # recording that it has moved, into a closed set whose member is
511
+ # literally "moved") used to read as the shorthand and import
512
+ # the owner's `moved` attribute onto the command — a phantom
513
+ # argument nothing ever passes, harmless at runtime only
514
+ # because the owner's default filled it, and a real, silent
515
+ # divergence for every projection that reads the command's
516
+ # declared arguments.
517
+ return unless mutation.source.is_a?(Symbol) && mutation.source.to_s == mutation.target.to_s
518
+ return if attributes.any? { |attr| attr.name == mutation.target }
519
+
520
+ owner_attr = @owner_attributes.find { |attr| attr.name == mutation.target }
521
+ attributes << owner_attr if owner_attr
522
+ end
523
+
524
+ # ONE HOP DEEPER than `resolve_bare_set!` — an `append:` mutation
525
+ # (`sets :ledger, append: { narrative: :narrative, ... }`) builds
526
+ # a NEW element of a LIST field, not the command's own root
527
+ # record, so a bare self-referential field inside it (the hash
528
+ # key equals its own value, same shorthand `resolve_bare_set!`
529
+ # already reads) can't resolve against `@owner_attributes` — the
530
+ # aggregate itself never stores `:narrative`, only the list
531
+ # element's own construct does (`attribute :ledger,
532
+ # list_of(LedgerEntry)`, and `LedgerEntry` is what actually
533
+ # declares `:narrative`). Resolves the list field's own element
534
+ # TYPE first (`element_type_for`), then that construct's own
535
+ # attribute of the same name — same verbatim-import, one level
536
+ # further down the same reasoning `resolve_bare_set!`'s own
537
+ # comment already gives.
538
+ #
539
+ # A non-self-referential value (`direction: { value: "credit" }`,
540
+ # a nested literal) is untouched — only a bare symbol equal to
541
+ # its own key ever qualifies, identical to `resolve_bare_set!`'s
542
+ # own target/source text comparison.
543
+ #
544
+ # POSITION-PRESERVING, not appended at the end — the exported IR
545
+ # is array-order-sensitive (attributes carry their own declared
546
+ # order onto the wire), so an append's fields are resolved as
547
+ # ONE CONTIGUOUS GROUP, in the mutation's own hash order,
548
+ # reinserted at whichever position the group's leftmost STILL-
549
+ # DECLARED member already occupies (or the end, if every member
550
+ # of the group is resolved). A plain `attributes << owner_attr`
551
+ # here would only ever reproduce the original order when the
552
+ # missing field happened to already be last — real, live
553
+ # evidence: `Keyword#was`/`Argument#variadic` (both genuinely
554
+ # last in their own append hash) round-tripped correctly under
555
+ # the naive append; every OTHER field in the same hash did not,
556
+ # caught by this codemod's own reboot-and-diff safety net rather
557
+ # than silently landing wrong.
558
+ def resolve_append_fields!(mutation)
559
+ return unless mutation.source.is_a?(Hash)
560
+
561
+ element = element_type_for(mutation.target)
562
+ return unless element
563
+
564
+ self_ref_fields = mutation.source.select { |field, value| value.is_a?(Symbol) && value.to_s == field.to_s }.keys
565
+ return if self_ref_fields.empty?
566
+
567
+ present = self_ref_fields.filter_map { |field| attributes.find { |attr| attr.name == field } }
568
+ return if present.size == self_ref_fields.size # already fully declared — nothing to resolve
569
+
570
+ anchor = present.empty? ? attributes.length : present.map { |attr| attributes.index(attr) }.min
571
+ attributes.reject! { |attr| present.include?(attr) }
572
+
573
+ group = self_ref_fields.filter_map do |field|
574
+ present.find { |attr| attr.name == field } || element.attributes.find { |attr| attr.name == field }
575
+ end
576
+ attributes.insert(anchor, *group)
577
+ end
578
+
579
+ # The owner's own LIST attribute names its element type as TEXT
580
+ # (`Attribute#type`, unwrapped from `list_of(...)` at declare
581
+ # time) — resolved against `@owner_constructs` (the owner's own
582
+ # value objects and entities, the only two kinds an element can
583
+ # be) by `hecks_name`, the same lookup
584
+ # `AttributeCollector#resolve_identity_field!` already uses for
585
+ # a value object's own name.
586
+ def element_type_for(list_field)
587
+ list_attr = @owner_attributes.find { |attr| attr.name == list_field && attr.list? }
588
+ return nil unless list_attr
589
+
590
+ @owner_constructs.find { |construct| construct.hecks_name.to_s == list_attr.type.to_s }
591
+ end
592
+
593
+ # LEGACY — see `then_set`'s own comment. The ORIGINAL implementation,
594
+ # verbatim: `from:` still a synonym for `to:`, no omittable-`to:`
595
+ # shorthand, no refusal for the redundant `to: target` spelling —
596
+ # frozen era text was minted under this reading, and a legacy
597
+ # grammar exists precisely so re-parsing it never silently changes
598
+ # what it meant.
599
+ def legacy_then_set(target, positional_to = UNSET, to: UNSET, from: UNSET, append: UNSET,
600
+ increment: UNSET, decrement: UNSET, multiply: UNSET, clamp: UNSET, remove: UNSET)
601
+ to = positional_to if to.equal?(UNSET) && !positional_to.equal?(UNSET)
602
+ set_source = to.equal?(UNSET) ? from : to
603
+
604
+ named = { set: set_source, append: append, increment: increment, decrement: decrement,
605
+ multiply: multiply, clamp: clamp, remove: remove }
606
+ .reject { |_, source| source.equal?(UNSET) }
607
+
608
+ if named.empty?
609
+ raise Malformed,
610
+ "#{@name}'s then_set :#{target} names no operation — " \
611
+ "give it to:, append:, increment:, decrement:, multiply:, clamp:, or remove:"
612
+ end
613
+
614
+ if named.size > 1
615
+ raise Malformed,
616
+ "#{@name}'s then_set :#{target} tries to #{named.keys.join(' and ')} " \
617
+ "at once — one mutation, one meaning"
618
+ end
619
+
620
+ op, source = named.first
621
+ @mutations << Mutation.new(target: target.to_sym, op: op, source: source)
622
+ end
623
+ end
624
+ end
625
+ end
626
+ end