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,409 @@
1
+ module Hecks
2
+ module Bluebook
3
+ class Assembly
4
+ # ONE TABLE, WHERE THERE WERE FIVE HAND-WRITTEN MIRRORS OF IT.
5
+ #
6
+ # `bluebook.bluebook` already declares what every construct is made of, and
7
+ # `Plan` already reads it — parent, fields, lists, setters. What the language
8
+ # cannot say is the three things Ruby needs to rebuild one, so those are here
9
+ # and ONLY those:
10
+ #
11
+ # holder which class holds it
12
+ # make :declare for a construct that became a class, :new for an
13
+ # instance — the boundary `Query` sits on, since its body is
14
+ # inherited instance methods
15
+ # fields each keyword the constructor takes, as
16
+ # keyword => [key in the declaration, how to read it]
17
+ #
18
+ # A READER is a symbol naming a method on Marks, or :plain for a value that
19
+ # needs no decoding, or [:each, reader] for a list. Nothing here is a rule
20
+ # about whether a declaration is admissible — the language settled that on the
21
+ # way in. This is only how a spelling becomes an object again.
22
+ #
23
+ # WHY A TABLE AND NOT A METHOD PER CATEGORY. The judge used to carry one
24
+ # hand-written branch per category, and the price was fourteen verbs the
25
+ # language declared and the walk never offered — every rule hanging off them
26
+ # decoration, and nothing red, because a branch that does not exist cannot
27
+ # fail. An assembler with a method per category is the same shape. So the
28
+ # table is checked AGAINST the language by spec/assembly_spec: a field the
29
+ # language declares that no contract consumes is a failure, not a silence.
30
+ #
31
+ # The `derived` list is how a field says it needs no assembling — a parent
32
+ # pointer the containment tree already knows, or something computed from what
33
+ # is here (`query_name` is `Naming.snake(name)`, `creates?` is whether a verb
34
+ # names a root). Naming one is a claim, and the coverage gate holds it.
35
+ def self.contract(category) = CONTRACTS.fetch(category.to_s)
36
+
37
+ CONTRACTS = {
38
+ "Bluebook" => Contract.new(
39
+ holder: Chapter, make: :new,
40
+ fields: {
41
+ name: [:name, :plain],
42
+ version: [:version, :plain],
43
+ vision: [:vision, :plain],
44
+ classification: [:classification, :plain],
45
+ formerly_known_as: [:formerly_known_as, :plain],
46
+ attaches_to: [:attaches_to, :plain]
47
+ },
48
+ rows: { normalisations: :normalisation_table },
49
+ derived: { normalisations: :elsewhere }
50
+ ),
51
+
52
+ "Aggregate" => Contract.new(
53
+ holder: Aggregate, make: :new,
54
+ fields: {
55
+ name: [:name, :plain],
56
+ description: [:description, :plain],
57
+ identified_by: [:identified_by, :plain],
58
+ attributes: [:attributes, [:each, :attribute]],
59
+ # THE AGGREGATE BOUNDARY, and the precondition a command may
60
+ # reference by name (S10, ADR 0025 — "Rules"). Same reader
61
+ # shapes ValueObject's own `invariants`/Command's own
62
+ # `givens` already use — `invariant` builds an Invariant,
63
+ # `given` a Given, the same struct AggregateBuilder#given
64
+ # hands a referencing command's own resolved list.
65
+ invariants: [:invariants, [:each, :invariant]],
66
+ preconditions: [:preconditions, [:each, :given]],
67
+ # S12, ADR 0025 — the local half of "projects :name, from:
68
+ # :\"reference.remote_field\"", read the same way
69
+ # invariants/preconditions above are: a synthetic command
70
+ # (Aggregate.Projects) the judge dispatches once per
71
+ # declaration, folded into a list on the owning aggregate.
72
+ projected_fields: [:projected_fields, [:each, :projected_field]],
73
+ provenance: [:provenance, :plain]
74
+ },
75
+ rows: { transitions: :transition_rows, value_objects: :value_object_names, identified_by: :identity_rows },
76
+ reads: { identified_by: [:each, :identity_path], attributes: [:each_with_id, :attribute],
77
+ invariants: [:each, :rule], preconditions: [:each, :rule],
78
+ projected_fields: [:each, :projected_field] },
79
+ derived: {
80
+ position: :walk,
81
+ state_field: [:folded, :lifecycle, :field],
82
+ state_start: [:folded, :lifecycle, :default],
83
+ transitions: [:folded, :lifecycle, :transitions],
84
+ value_objects: :children
85
+ }
86
+ ),
87
+
88
+ "Command" => Contract.new(
89
+ holder: Command, make: :declare,
90
+ fields: {
91
+ name: [:name, :plain],
92
+ role: [:role, :plain],
93
+ goal: [:goal, :plain],
94
+ references: [:references, :plain],
95
+ attributes: [:attributes, [:each, :shape_field]],
96
+ givens: [:givens, [:each, :given]],
97
+ ensures: [:ensures, [:each, :given]],
98
+ mutations: [:mutations, [:each, :mutation]],
99
+ emits: [:emits, :plain],
100
+ # LIFECYCLE STATE AS A COMMAND GUARD (S10, ADR 0025) — a
101
+ # literal the same way `provenance`/`default:` already are;
102
+ # one state or an array of them, or nil for a command with
103
+ # no such guard.
104
+ from: [:from, :plain],
105
+ provenance: [:provenance, :plain]
106
+ },
107
+ rows: { mutations: :mutation_rows },
108
+ reads: { attributes: [:each, :shape_field], givens: [:each, :rule], ensures: [:each, :rule],
109
+ mutations: [:call, :mutations], emits: :names, provenance: :provenance, from: :from },
110
+ derived: { position: :walk }
111
+ ),
112
+
113
+ "ValueObject" => Contract.new(
114
+ holder: ValueObject, make: :declare,
115
+ fields: {
116
+ name: [:name, :plain],
117
+ attributes: [:attributes, [:each, :shape_field]],
118
+ invariants: [:invariants, [:each, :invariant]],
119
+ members: [:members, [:each, :member]],
120
+ closed_set: [:closed_set, :flag]
121
+ },
122
+ # The language counts the admitted rows ; the IR keeps a flag and the rows.
123
+ reads: { attributes: [:each, :shape_field], invariants: [:each, :rule],
124
+ closed_set: [:call, :closed_set_of], members: [:call, :members_row] },
125
+ derived: { position: :walk, rows: [:folded, %i[closed_set members], nil] }
126
+ ),
127
+
128
+ "Query" => Contract.new(
129
+ holder: Query, make: :new,
130
+ fields: {
131
+ name: [:name, :plain],
132
+ description: [:description, :plain],
133
+ attributes: [:attributes, [:each, :shape_field]],
134
+ wheres: [:wheres, [:each, :where_clause]],
135
+ order_by: [:order_by, :order_by],
136
+ limit: [:limit, :limit],
137
+ # Held by the language as an OPEN MAP, so every one of these reads the
138
+ # same way and a ninth option needs no new field on either side.
139
+ offset: [:offset, [:option, :offset]],
140
+ cursor: [:cursor, [:option, :cursor]],
141
+ null_semantics: [:null_semantics, [:option, :null_semantics]],
142
+ authorization: [:authorization, [:option, :authorization]],
143
+ inspection: [:inspection, [:option, :inspection]]
144
+ },
145
+ rows: { wheres: :where_rows, options: :option_rows },
146
+ reads: { attributes: [:each, :shape_field], wheres: [:each, :where_clause],
147
+ order_by: [:call, :order_by], limit: [:call, :limit] },
148
+ derived: {
149
+ position: :walk,
150
+ order_field: [:folded, :order_by, :field],
151
+ order_way: [:folded, :order_by, :direction],
152
+ options: [:folded, %i[offset cursor null_semantics authorization inspection], nil]
153
+ }
154
+ ),
155
+
156
+ "Entity" => Contract.new(
157
+ holder: Entity, make: :declare,
158
+ fields: {
159
+ name: [:name, :plain],
160
+ description: [:description, :plain],
161
+ # A LIST OF PATHS, exactly as an aggregate's is. The two used to differ
162
+ # — a Symbol here and a String there, which byte equality with `to_h`
163
+ # COULD NOT SEE because both render as a string, so the assembled graph
164
+ # got a String and `element_of` looked up `args["sequence"]` in a
165
+ # symbol-keyed payload and found nothing: "Reverse acts on one
166
+ # LedgerEntry — pass sequence:", while passing sequence. There is one
167
+ # spelling now, and no room left for that difference.
168
+ identified_by: [:identified_by, :plain],
169
+ attributes: [:attributes, [:each, :shape_field]],
170
+ # ADR 0028 — the SAME shape Aggregate's own `preconditions`
171
+ # already carries, one level down: a piece's own named
172
+ # `given`, referenced back by one of its own commands.
173
+ preconditions: [:preconditions, [:each, :given]],
174
+ # Round 7 — the SAME shape Aggregate's own `invariants`
175
+ # already carries, one level down: checked against every
176
+ # instance of this piece, not the aggregate's own flat state.
177
+ invariants: [:invariants, [:each, :invariant]]
178
+ },
179
+ rows: { transitions: :transition_rows, identified_by: :identity_rows },
180
+ reads: { identified_by: [:each, :identity_path], attributes: [:each, :shape_field],
181
+ preconditions: [:each, :rule], invariants: [:each, :rule] },
182
+ derived: {
183
+ position: :walk,
184
+ owner: :parent,
185
+ state_field: [:folded, :lifecycle, :field],
186
+ state_start: [:folded, :lifecycle, :default],
187
+ transitions: [:folded, :lifecycle, :transitions]
188
+ }
189
+ ),
190
+
191
+ "Policy" => Contract.new(
192
+ holder: Policy, make: :new,
193
+ fields: {
194
+ name: [:name, :plain],
195
+ aggregate: [:aggregate, :plain],
196
+ on_event: [:on_event, :plain],
197
+ trigger_command: [:trigger_command, :plain],
198
+ target_domain: [:target_domain, :plain],
199
+ where: [:where, :plain],
200
+ for_each: [:for_each, :plain],
201
+ with_spec: [:with_spec, :bindings]
202
+ },
203
+ rows: { with_spec: :with_spec_rows },
204
+ reads: { with_spec: [:from, :with_spec] },
205
+ derived: { position: :walk }
206
+ ),
207
+
208
+ "ProcessManager" => Contract.new(
209
+ holder: ProcessManager, make: :new,
210
+ fields: {
211
+ name: [:name, :plain],
212
+ # A SYMBOL. `SagaInterpreter` does `event.payload[pm.correlates_by]` — a
213
+ # hash lookup on a symbol-keyed payload — and `value == pm.correlates_by`
214
+ # when resolving a leg s bindings. A String there finds nothing and
215
+ # resolves to nothing, so the wire never advanced and a drawer that
216
+ # should have held 2500 held 0. to_h could not show it: it stringifies.
217
+ correlates_by: [:correlates_by, :identity],
218
+ starts_on: [:starts_on, :plain],
219
+ ends_on: [:ends_on, :plain],
220
+ states: [:states, :plain]
221
+ },
222
+ reads: { states: :names },
223
+ # S17, ADR 0026 — `handlers` is a REAL feature of the LANGUAGE's
224
+ # own "ProcessManager" declaration now (`attribute :handlers,
225
+ # list_of(Handler)`, reaction.bluebook), consumed by the judge
226
+ # walking `Plan`'s own containment tree (`Handler`'s own
227
+ # `.parent == "ProcessManager"`) rather than by any field this
228
+ # contract itself reads — the same `:children` shape Aggregate's
229
+ # own `value_objects` claim already uses, one level in.
230
+ derived: { position: :walk, handlers: :children }
231
+ ),
232
+
233
+ # S17, ADR 0026 — Handler is a genuine entity now, nested under
234
+ # ProcessManager (`entity "Handler"`, reaction.bluebook). Neither
235
+ # `position` nor `handler` is a stored field any more: a saga
236
+ # answers each event ONCE, so `event_type` is Handler's own real,
237
+ # non-positional identity (no walk-minted `position` to derive),
238
+ # and the process manager it belongs to is structural now — which
239
+ # list this element sits in, not a stored field to fold a parent
240
+ # pointer out of.
241
+ "Handler" => Contract.new(
242
+ holder: ProcessManagerHandler, make: :new,
243
+ fields: {
244
+ event_type: [:event_type, :plain],
245
+ from_state: [:from_state, :plain],
246
+ to_state: [:to_state, :plain]
247
+ },
248
+ # `dispatches` — same reason ProcessManager's own `handlers`
249
+ # claim, above, is `:children` : a real feature of the
250
+ # LANGUAGE's own "Handler" declaration (`attribute :dispatches,
251
+ # list_of(Dispatch)`), consumed by the judge walking `Plan`'s
252
+ # own containment tree rather than by any field this contract
253
+ # reads.
254
+ derived: { dispatches: :children }
255
+ ),
256
+
257
+ # S17, ADR 0026 — Dispatch is a genuine entity now, nested under
258
+ # Handler (`entity "Dispatch"`, reaction.bluebook) — two levels
259
+ # deep, "no life outside its Handler" (the ADR's own words).
260
+ # `command_name` is Dispatch's own real, non-positional identity
261
+ # — neither `position` nor `handler` is a stored field any more,
262
+ # the same reason Handler's own contract, above, dropped them.
263
+ "Dispatch" => Contract.new(
264
+ holder: DispatchSpec, make: :new,
265
+ fields: {
266
+ command_name: [:command_name, :plain],
267
+ with_spec: [:with_spec, :bindings]
268
+ },
269
+ rows: { with_spec: :with_spec_rows },
270
+ reads: { with_spec: [:from, :with_spec] },
271
+ derived: {}
272
+ ),
273
+
274
+ # S14, ADR 0026 — Syntax/Keyword/Argument are never built via
275
+ # `Build`/`Reconstruction`'s own generic path — their own data
276
+ # lives in a dedicated repository `SyntaxBoot` (meta_validator/
277
+ # syntax_boot.rb) reads directly, never through the meta-
278
+ # domain's own reconstruction, the same reason `Member`'s own
279
+ # entry (above) carries `holder: nil, make: nil` too. These
280
+ # entries exist purely so the introspection specs
281
+ # (assembly_spec.rb) can hold them to the same "every field
282
+ # claimed" discipline every other category answers to.
283
+ "Syntax" => Contract.new(
284
+ holder: nil, make: nil,
285
+ fields: { name: [:name, :plain] },
286
+ derived: { keywords: :children, arguments: :children }
287
+ ),
288
+
289
+ "Keyword" => Contract.new(
290
+ holder: nil, make: nil,
291
+ fields: {
292
+ word: [:word, :plain],
293
+ context: [:context, :plain],
294
+ body: [:body, :plain],
295
+ inner: [:inner, :plain],
296
+ opens: [:opens, :plain],
297
+ fills: [:fills, :plain],
298
+ was: [:was, :plain],
299
+ resolves_via: [:resolves_via, :plain],
300
+ disambiguator: [:disambiguator, :plain],
301
+ calls: [:calls, :plain]
302
+ },
303
+ derived: { position: :walk }
304
+ ),
305
+
306
+ "Argument" => Contract.new(
307
+ holder: nil, make: nil,
308
+ fields: {
309
+ keyword: [:keyword, :plain],
310
+ context: [:context, :plain],
311
+ at: [:at, :plain],
312
+ named: [:named, :plain],
313
+ kind: [:kind, :plain],
314
+ required: [:required, :plain],
315
+ fills: [:fills, :plain],
316
+ selects: [:selects, :plain],
317
+ pair_key_fills: [:pair_key_fills, :plain],
318
+ pair_value_fills: [:pair_value_fills, :plain],
319
+ pairs_shape: [:pairs_shape, :plain],
320
+ variadic: [:variadic, :plain],
321
+ minimum: [:minimum, :plain],
322
+ coerce: [:coerce, :plain],
323
+ blank_message: [:blank_message, :plain]
324
+ },
325
+ derived: { position: :walk }
326
+ ),
327
+
328
+ "ReadModel" => Contract.new(
329
+ holder: ReadModel, make: :new,
330
+ fields: {
331
+ name: [:name, :plain],
332
+ description: [:description, :plain],
333
+ reference_name: [:reference_name, :identity],
334
+ reference_target: [:reference_target, :plain],
335
+ aggregate_heads: [:aggregate_heads, [:each, :head]],
336
+ group_by: [:group_by, [:each, :group_by_field]],
337
+ # `count`/`median_field` are `group_by`'s own two siblings —
338
+ # scalars, not lists, so no `[:each, ...]` shape ; `:plain`
339
+ # is what `Assembly::Build` needs (fed the native `to_h`
340
+ # value directly, already `true`/`nil`/a String), and the
341
+ # `reads:` entry below is what `Reconstruction` needs
342
+ # instead (fed the STRINGIFIED meta-domain row).
343
+ count: [:count, :plain],
344
+ median_field: [:median_field, :plain],
345
+ # A read model inherits every option an ask has, so it reads them the
346
+ # same way — see Query.
347
+ wheres: [:wheres, [:each, :where_clause]],
348
+ order_by: [:order_by, :order_by],
349
+ limit: [:limit, :limit],
350
+ offset: [:offset, [:option, :offset]],
351
+ cursor: [:cursor, [:option, :cursor]],
352
+ null_semantics: [:null_semantics, [:option, :null_semantics]],
353
+ authorization: [:authorization, [:option, :authorization]],
354
+ inspection: [:inspection, [:option, :inspection]]
355
+ },
356
+ rows: { options: :read_model_option_rows },
357
+ # `wheres` needs its own reader for the same reason Query's does — a
358
+ # list defaults to `[]`, not the generic `text(row[key])` cell reader's
359
+ # `nil` — even though a read model's row never carries `wheres` as a
360
+ # native field the way Query's does (`[:each, :where_clause]` over an
361
+ # absent `row[:wheres]` is `Array(nil).map { ... }`, i.e. `[]`, always).
362
+ # The REAL values, when a read model declares any, arrive through
363
+ # `options_of(row)`'s merge in `read_model` below (dispatched as
364
+ # generic Option rows, `read_model_option_rows`/`filter_options` — not
365
+ # as dedicated where-clause rows), which overrides this default. Until
366
+ # `ReadModel#to_h` spelled `wheres`/`order_by`/`limit`
367
+ # unconditionally, `round_trip_spec`'s own "SOURCE KEYS ONLY" +compare
368
+ # never asked about this key at all, so the `nil`-vs-`[]` gap between
369
+ # this contract's default and `to_h`'s own `[]` default went unnoticed.
370
+ # `order_by`/`limit` need no matching entry — the generic reader's
371
+ # `nil` already agrees with `to_h`'s own nil-when-undeclared default
372
+ # for those two. `group_by` needs its own reader the same way — a
373
+ # rootless read model's grouping keys, added independently on main.
374
+ reads: { reference_name: :symbol, aggregate_heads: [:each, :head],
375
+ group_by: [:each, :group_by_field], wheres: [:each, :where_clause],
376
+ # `count` needs the boolean coercion `Shapes#read_model_count`
377
+ # gives it (a stringified "true"/absent on the wire, a real
378
+ # `true`/`nil` in `to_h`) — `median_field` needs none: the
379
+ # default `text(row[key])` cell reader already returns the
380
+ # same String-or-nil `ReadModel#to_h`'s own `&.to_s` does.
381
+ count: :read_model_count },
382
+ derived: {
383
+ position: :walk,
384
+ query_name: [:computed, :query_name],
385
+ options: [:folded, %i[offset cursor null_semantics authorization inspection], nil]
386
+ }
387
+ ),
388
+
389
+ # A member's pairs are an OPEN MAP, which is why Member is its own root in
390
+ # the language. The IR keeps them as a plain hash on the value object, so
391
+ # they are assembled with their shape rather than as a construct.
392
+ # S17, ADR 0026 — Member is a genuine entity now, nested under
393
+ # ValueObject (`entity "Member"`, shape.bluebook). It no longer
394
+ # holds a `shape` field at all (that was the free-text, un-parsed
395
+ # spelling a standalone root once needed ; an entity's element is
396
+ # never serialized as text) — only `position` (walk-minted, see
397
+ # `entity_own_identity`, judge.rb) and `pairs` (still an open map,
398
+ # still one row per entry, still why a value object cannot hold it
399
+ # directly).
400
+ "Member" => Contract.new(
401
+ holder: nil, make: nil,
402
+ fields: {},
403
+ rows: { pairs: :pair_rows },
404
+ derived: { position: :walk, pairs: [:folded, %i[members], nil] }
405
+ )
406
+ }.freeze
407
+ end
408
+ end
409
+ end
@@ -0,0 +1,227 @@
1
+ module Hecks
2
+ module Bluebook
3
+ class Assembly
4
+ # The leaf shapes an assembly reads, and the encodings it undoes.
5
+ #
6
+ # `to_h` spells things as text so the export stands on its own, and every
7
+ # one of those spellings has to come back apart here. This is the same family
8
+ # of work `MetaValidator::Shapes` does for the reconstruction — the difference
9
+ # is that Shapes rebuilds HASHES and this rebuilds OBJECTS, so it has to
10
+ # recover types rather than just strings.
11
+ #
12
+ # ENCODING LOSSES ARE THE LARGEST FAMILY OF BUG IN THIS CODEBASE, and every
13
+ # member has the same shape: reading an object where `to_h` holds a spelling.
14
+ # So each method below names the spelling it inverts.
15
+ module Marks
16
+ module_function
17
+
18
+ # `Attribute#to_h` spells a type with `to_s`, so a reference arrives as
19
+ # "Reference<Customer>" and has to become an edge again. Everything else is
20
+ # a name and stays one.
21
+ def attribute(field)
22
+ type = field[:type].to_s
23
+ target = type[/\AReference<(.+)>\z/, 1]
24
+
25
+ Attribute.new(
26
+ name: field[:name],
27
+ type: target ? Reference.new(target) : type,
28
+ list: field[:list] ? true : false,
29
+ default: field[:default],
30
+ # The LAST place optionality can be dropped, and the one that was
31
+ # dropping it. Every bluebook in the registry is the round-trip
32
+ # product — MetaValidator dispatches the declaration in and reads it
33
+ # back — so a fact this constructor does not carry is a fact the
34
+ # language cannot state about itself, however plainly the source
35
+ # wrote it.
36
+ optional: field[:optional] ? true : false,
37
+ pattern: field[:pattern],
38
+ # THE SAME LESSON, ONE FACT LATER. `admits` is not on `to_h` — the
39
+ # wire does not carry it, on purpose — but it must still survive the
40
+ # round trip, because the grammar registry keeps the ASSEMBLED graph
41
+ # and downstream projections read the link off that. Dropped here, the
42
+ # language could not say `admits` about itself no matter how plainly
43
+ # the source wrote it, which is word for word what the note above
44
+ # already learned about `optional`.
45
+ admits: field[:admits],
46
+ relationship: field[:relationship]
47
+ )
48
+ end
49
+
50
+ # The same shape. Two names because a head's field and a verb's argument are
51
+ # different things in the language even though the IR keeps one class for
52
+ # both — `Aggregate.Attribute` and `Command.Argument` are separate verbs.
53
+ def shape_field(field) = attribute(field)
54
+
55
+ # ONE PART OF AN IDENTITY. It goes in as a row so the language can hold an
56
+ # ordered list of them, and comes back out as the path it always was —
57
+ # a String, because `identity_paths` splits paths and never symbols.
58
+ def identity_path(part) = part[:value].to_s
59
+
60
+ # A member's fields — an OPEN MAP, which is why Member is its own root in
61
+ # the language and why the pairs arrive as a list rather than a value object.
62
+ #
63
+ # The values are UNMARKED, because `ValueObject#to_h` spells them with `to_s`
64
+ # and the language stores them as text: `member code: "JPY", minor_units: 0`
65
+ # came back with a minor_units of "0", and a closed set that admits the string
66
+ # would refuse the number the caller passes.
67
+ def member(pairs)
68
+ pairs.to_h { |key, value| [key.to_sym, unmark_scalar(value)] }
69
+ end
70
+
71
+ # A read model's gathered head. The keys must be symbols whichever way the
72
+ # declaration arrived, and `as` must be one too: it NAMES the reader the
73
+ # projection answers to, and `ReadModel#to_h` spells it `to_s`.
74
+ def head(row)
75
+ row.to_h { |key, value| [key.to_sym, key.to_sym == :as ? value.to_sym : value] }
76
+ end
77
+
78
+ # A group_by field's own name — the builder's native shape is already
79
+ # `{field: :symbol}`, so this matches it rather than leaving `field`
80
+ # as the String `Shapes#group_by_field` reads back.
81
+ def group_by_field(row) = { field: row[:field].to_sym }
82
+
83
+ # A scalar that was written as itself rather than inspected — a member's
84
+ # value, where the language holds text and the type has to be read back from
85
+ # the shape of it. Unlike `read`, a bare word stays a String here, because
86
+ # a closed set admits words far more often than symbols.
87
+ def unmark_scalar(value)
88
+ text = value.to_s
89
+ return true if text == "true"
90
+ return false if text == "false"
91
+ return text.to_i if text.match?(/\A-?\d+\z/)
92
+ return text.to_f if text.match?(/\A-?\d+\.\d+\z/)
93
+
94
+ text
95
+ end
96
+
97
+ # A saga's argument bindings. Each value rides Literal's spelling, which
98
+ # marks a Symbol with a leading colon — lose it and an argument reads as a
99
+ # string of the same name.
100
+ def bindings(with) = Array(with).to_h { |key, value| [key.to_sym, read(value)] }
101
+
102
+ def invariant(rule)
103
+ Invariant.new(description: rule[:description], canonical: rule[:canonical])
104
+ end
105
+
106
+ def given(rule)
107
+ Given.new(description: rule[:description], canonical: rule[:canonical])
108
+ end
109
+
110
+ # S12, ADR 0025 — `projects :name, from: :"reference.remote_field"`.
111
+ # All three fields are identifiers, unlike Invariant/Given's own
112
+ # free text, so — like `attribute`'s own `name`/`type` below —
113
+ # they come back as Symbols.
114
+ def projected_field(row)
115
+ ProjectedField.new(name: row[:name].to_sym, reference: row[:reference].to_sym,
116
+ remote_field: row[:remote_field].to_sym)
117
+ end
118
+
119
+ # `Mutation#to_h` branches on the operation, so this does too.
120
+ #
121
+ # An APPEND binds several fields at once, each either an ARGUMENT (a
122
+ # Symbol, wearing its colon) or a LITERAL — the distinction that is the
123
+ # whole reason `append: { direction: "out" }` was once indistinguishable
124
+ # from an argument named `out`.
125
+ def mutation(change)
126
+ target = change[:target].to_sym
127
+ op = change[:op].to_sym
128
+
129
+ # `:delegate` (CommandBuilder#delegates_to's own comment) rides
130
+ # the SAME multi-binding shape `:append` does.
131
+ return Mutation.new(target: target, op: op, source: appended(change[:fields])) if [:append, :delegate].include?(op)
132
+
133
+ Mutation.new(target: target, op: op, source: classified(change[:source]))
134
+ end
135
+
136
+ def appended(fields)
137
+ Array(fields).to_h { |field, source| [field.to_sym, read(source)] }
138
+ end
139
+
140
+ # A SET reads one thing, and `classified_source` said which: an argument by
141
+ # name, or a literal by value.
142
+ def classified(source)
143
+ return nil if source.nil?
144
+
145
+ case source[:kind].to_s
146
+ when "argument" then source[:name].to_sym
147
+ when "state" then StateRef.new(source[:name].to_sym)
148
+ else source[:value]
149
+ end
150
+ end
151
+
152
+ # EVERY LITERAL FIELD ON THE WIRE, read back — one spelling, one reader.
153
+ #
154
+ # A where-clause value, a saga's argument bindings, an append binding, a
155
+ # limit: all of them ride Literal's self-describing form, so all of them
156
+ # come back through here. There were two readers (`read` and `unmark`)
157
+ # that disagreed about quoted strings and numbers, and which one a call
158
+ # site got was a coin toss the comments had to keep apologising for.
159
+ #
160
+ # AN OBJECT LITERAL is the one that bit. A saga leg binds `narrative: {
161
+ # text: "transfer out" }` — a value object's fields written inline — and
162
+ # `to_s` on a Hash used to be its inspect form, so it came back as text.
163
+ # Read as a string it reached the runtime as `"{:text=>\"transfer out\"}"`,
164
+ # coercion refused it, the debit leg was never delivered, and the whole
165
+ # settlement wire stopped: banking emitted TransferRequested five times and
166
+ # TransferDebited never. A whole-history replay gate caught what every other
167
+ # gate missed, because a saga that silently does nothing looks exactly like
168
+ # a saga with nothing to do.
169
+ def read(value) = Literal.read(value)
170
+
171
+ def where_clause(clause)
172
+ QuerySpecification::Common::WhereClause.new(
173
+ field: clause[:field], op: clause[:op].to_sym, value: read(clause[:value])
174
+ )
175
+ end
176
+
177
+ def order_by(declared)
178
+ return nil unless declared
179
+
180
+ QuerySpecification::Common::OrderBy.new(
181
+ field: declared[:field], direction: declared[:direction].to_sym
182
+ )
183
+ end
184
+
185
+ def limit(declared)
186
+ return nil unless declared
187
+
188
+ QuerySpecification::Common::LimitSpec.new(value: read(declared[:value]))
189
+ end
190
+
191
+ # EVERY OTHER SPECIFICATION OPTION, from one table.
192
+ #
193
+ # Each entry names the struct and which of its members carry a value that
194
+ # rode Literal's spelling rather than plain text. A
195
+ # ninth option is one row here and nothing else — the language already holds
196
+ # it, because it holds options as an open map rather than a field each.
197
+ OPTIONS = {
198
+ offset: [QuerySpecification::Common::OffsetSpec, %i[value]],
199
+ cursor: [QuerySpecification::Common::CursorSpec, %i[value]],
200
+ null_semantics: [QuerySpecification::Common::NullSemantics, []],
201
+ authorization: [QuerySpecification::Common::AuthorizationSpec, %i[policy tenant]],
202
+ inspection: [QuerySpecification::Common::InspectionSpec, []]
203
+ }.freeze
204
+
205
+ # `mode` and `policy` are read as symbols because the DSL declares them that
206
+ # way — `nulls :last`, `authorize :customer_access` — and `to_h` spells them
207
+ # with `to_s`, so the colon is not there to strip.
208
+ SYMBOLIC = %i[mode policy tenant].freeze
209
+
210
+ def option(name, declared)
211
+ return nil if declared.nil?
212
+
213
+ holder, marked = OPTIONS.fetch(name)
214
+ holder.new(**Hash(declared).to_h { |key, value| [key, option_value(key, value, marked)] })
215
+ end
216
+
217
+ def option_value(key, value, marked)
218
+ return nil if value.nil?
219
+ return read(value) if marked.include?(key)
220
+ return value.to_sym if SYMBOLIC.include?(key)
221
+
222
+ value
223
+ end
224
+ end
225
+ end
226
+ end
227
+ end