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,9 @@
1
+ module Hecks
2
+ module QuerySpecification
3
+ module Common
4
+ InspectionSpec = Struct.new(:mode, keyword_init: true) do
5
+ def to_h = { mode: mode.to_s }
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Hecks
2
+ module QuerySpecification
3
+ module Common
4
+ LimitSpec = Struct.new(:value, keyword_init: true) do
5
+ def to_h = { value: QuerySpecification.render_value(value) }
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,80 @@
1
+ module Hecks
2
+ module QuerySpecification
3
+ module Common
4
+ module NullPolicy
5
+ module_function
6
+
7
+ # STABLE on purpose : rows arrive already in identity order from
8
+ # Ports::Query::Ordering, and that base is what makes a tie deterministic
9
+ # rather than store-dependent. A plain sort_by is not stable in Ruby, so
10
+ # equal keys would shuffle and the identity tier would be lost exactly
11
+ # where it is needed. Descending reverses both partitions, so a tie reads
12
+ # identity-descending too — the same total order sql_order renders as
13
+ # `field DESC, id DESC`.
14
+ def order(records, direction:, policy: nil, &key)
15
+ null_rows, valued_rows = records.partition { |record| key.call(record).nil? }
16
+ sorted = valued_rows.each_with_index.sort_by { |record, index| [key.call(record), index] }.map(&:first)
17
+ if direction.to_s == "desc"
18
+ sorted.reverse!
19
+ null_rows.reverse!
20
+ end
21
+ case policy&.mode.to_s
22
+ when "first" then null_rows + sorted
23
+ when "last" then sorted + null_rows
24
+ else direction.to_s == "desc" ? sorted + null_rows : null_rows + sorted
25
+ end
26
+ end
27
+
28
+ def sql_order(expression, direction, policy)
29
+ direction = direction.to_s.downcase == "desc" ? "DESC" : "ASC"
30
+ nulls = case policy&.mode.to_s
31
+ when "first" then " NULLS FIRST"
32
+ when "last" then " NULLS LAST"
33
+ else ""
34
+ end
35
+ "#{expression} #{direction}#{nulls}, id #{direction}"
36
+ end
37
+
38
+ # THE COMPARATORS A NULL CANNOT SATISFY — the other half of
39
+ # `sql_predicate` below. That one answers "the value COMPARED TO is
40
+ # null" (`eq: nil` -> IS NULL, `ne: nil` -> IS NOT NULL, a real
41
+ # convention both adapters already shared). This one answers the
42
+ # case nothing covered: the ROW'S OWN value is null and the value
43
+ # compared to is not.
44
+ #
45
+ # SQL says unknown. `NULL <> 'red'` is NULL, not true, so the row
46
+ # is not returned. Ruby says `nil != "red"` is true, so it is. The
47
+ # two adapters therefore answered the SAME query on the SAME data
48
+ # differently — Memory returning a row SQLite omitted — which is
49
+ # not a difference of opinion a caller can plan around.
50
+ #
51
+ # Resolved toward SQL, and not because SQL is the store: an absent
52
+ # or null field is UNKNOWN, not a value, and a comparison against
53
+ # unknown is unknown rather than true. Making SQL match Ruby
54
+ # instead would mean compiling every `ne:` to
55
+ # `(col <> $1 OR col IS NULL)` — more to get right in two
56
+ # dialects, and a reliable way to lose an index — to make a real
57
+ # query engine agree with an in-memory one.
58
+ #
59
+ # `none_in_state` is deliberately NOT here: it is a 9th, vendored
60
+ # comparator whose `held` is a reference id, and a row holding no
61
+ # reference is genuinely "not in that state" rather than unknown.
62
+ NULL_UNMATCHABLE = %w[eq ne lt lte gt gte in contains].freeze
63
+
64
+ def unmatchable?(operation, held, want)
65
+ held.nil? && !want.nil? && NULL_UNMATCHABLE.include?(operation.to_s)
66
+ end
67
+
68
+ def sql_predicate(expression, operation, value)
69
+ if value.nil? && operation.to_s == "eq"
70
+ ["#{expression} IS NULL", []]
71
+ elsif value.nil? && operation.to_s == "ne"
72
+ ["#{expression} IS NOT NULL", []]
73
+ else
74
+ nil
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,11 @@
1
+ module Hecks
2
+ module QuerySpecification
3
+ module Common
4
+ NullSemantics = Struct.new(:mode, keyword_init: true) do
5
+ def to_h = { mode: mode.to_s }
6
+
7
+ def self.default = new(mode: :native)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ module Hecks
2
+ module QuerySpecification
3
+ module Common
4
+ OffsetSpec = Struct.new(:value, keyword_init: true) do
5
+ def to_h = { value: QuerySpecification.render_value(value) }
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,32 @@
1
+ require_relative "null_semantics"
2
+
3
+ module Hecks
4
+ module QuerySpecification
5
+ module Common
6
+ class Options
7
+ attr_reader :wheres, :order_by, :limit, :offset, :cursor,
8
+ :authorization, :null_semantics, :inspection
9
+
10
+ def initialize(wheres: [], order_by: nil, limit: nil, offset: nil, cursor: nil,
11
+ authorization: nil,
12
+ inspection: nil, null_semantics: NullSemantics.default)
13
+ @wheres, @order_by, @limit, @offset, @cursor = wheres, order_by, limit, offset, cursor
14
+ @authorization, @null_semantics, @inspection = authorization, null_semantics, inspection
15
+ end
16
+
17
+ def options_to_h
18
+ { wheres: @wheres.map(&:to_h), order_by: @order_by&.to_h, limit: @limit&.to_h,
19
+ offset: @offset&.to_h, cursor: @cursor&.to_h, authorization: @authorization&.to_h,
20
+ null_semantics: @null_semantics&.to_h, inspection: @inspection&.to_h }
21
+ end
22
+
23
+ def extra_options_to_h
24
+ options_to_h.reject { |key, value|
25
+ value.nil? || value == [] || (key == :null_semantics && value == { mode: "native" })
26
+ }
27
+ .reject { |key, _| %i[wheres order_by limit].include?(key) }
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,9 @@
1
+ module Hecks
2
+ module QuerySpecification
3
+ module Common
4
+ OrderBy = Struct.new(:field, :direction, keyword_init: true) do
5
+ def to_h = { field: field.to_s, direction: direction.to_s }
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ require_relative "comparators"
2
+ require_relative "where_clause"
3
+ require_relative "order_by"
4
+ require_relative "limit_spec"
5
+ require_relative "offset_spec"
6
+ require_relative "cursor_spec"
7
+ require_relative "authorization_spec"
8
+ require_relative "null_semantics"
9
+ require_relative "inspection_spec"
10
+ require_relative "options"
@@ -0,0 +1,9 @@
1
+ module Hecks
2
+ module QuerySpecification
3
+ module Common
4
+ WhereClause = Struct.new(:field, :op, :value, keyword_init: true) do
5
+ def to_h = { field: field.to_s, op: op.to_s, value: QuerySpecification.render_value(value) }
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,89 @@
1
+ module Hecks
2
+ module QuerySpecification
3
+ # ONE reading of a dotted query-field path, shared by every place that
4
+ # used to invent its own. The reference interpreter did a bare
5
+ # `record[field]` lookup (a dotted path silently matched nothing), the
6
+ # SQL side split dots but judged numericness from the FIRST nested
7
+ # segment only (a two-level path quietly compared text), and the build
8
+ # seal walked the declaration graph a third way. Three
9
+ # implementations of "what does pizza.price_cents.cents mean" is two
10
+ # more than a language gets to have.
11
+ #
12
+ # Two sides, deliberately in one file so they cannot drift:
13
+ #
14
+ # dig(holder, field) — VALUE side: walk a record's held state,
15
+ # segment by segment, through Value
16
+ # objects and plain hashes alike.
17
+ # leaf_attribute / numeric? — DECLARATION side: walk the declared
18
+ # shape to the attribute a path lands on.
19
+ # Callers supply value-object lookup as a
20
+ # block, because an Aggregate and a
21
+ # mid-build AggregateBuilder hold their
22
+ # shapes differently but walk the same.
23
+ module FieldPath
24
+ module_function
25
+
26
+ NUMERIC_PRIMITIVES = %w[Integer Float].freeze
27
+ SCALAR_PRIMITIVES = %w[String Integer Float TrueClass FalseClass].freeze
28
+
29
+ # The held value a dotted field names, or nil — never a raise. The
30
+ # first segment reads off the record (an Instance, a Value, or a
31
+ # plain row hash); the rest read through whatever each step holds. A
32
+ # stored nested value object is a plain hash by the time it is read
33
+ # back, keyed by symbol in memory and by string off a wire decode,
34
+ # so both spellings are tried.
35
+ def dig(holder, field)
36
+ return nil if field.nil?
37
+
38
+ field.to_s.split(".").reduce(holder) { |current, segment| read(current, segment) }
39
+ end
40
+
41
+ def read(current, segment)
42
+ return nil if current.nil?
43
+ return current[segment.to_sym] || current[segment] if current.is_a?(Hash)
44
+
45
+ current[segment]
46
+ end
47
+
48
+ # The declared attribute a path lands on, or nil. `segments` is the
49
+ # dotted tail — for a bare field it is empty and the root attribute
50
+ # is its own leaf. The walk stops dead at a reference (an id is a
51
+ # scalar; nothing nests under it) and at any member the declared
52
+ # value object does not carry.
53
+ def leaf_attribute(attribute, segments)
54
+ current = attribute
55
+ segments.each do |segment|
56
+ return nil if current.nil? || current.reference?
57
+
58
+ shape = yield(current.type.to_s)
59
+ current = shape&.attributes&.find { |member| member.name.to_s == segment }
60
+ end
61
+ current
62
+ end
63
+
64
+ # May an ordered comparator (lt/lte/gt/gte) land here, and may an
65
+ # ORDER BY cast here numerically? A bare field keeps the one-level
66
+ # convention every adapter already implements: a numeric primitive,
67
+ # or a value object carrying at least one numeric member. A dotted
68
+ # path must land on a numeric primitive itself — the convention does
69
+ # not reach through a named path, it IS the absence of one.
70
+ def numeric?(attribute, segments, &value_object)
71
+ leaf = leaf_attribute(attribute, segments, &value_object)
72
+ return false if leaf.nil? || leaf.list? || leaf.reference?
73
+ return true if NUMERIC_PRIMITIVES.include?(leaf.type.to_s)
74
+ return false unless segments.empty?
75
+
76
+ shape = yield(leaf.type.to_s)
77
+ !shape.nil? && shape.attributes.any? { |member| NUMERIC_PRIMITIVES.include?(member.type.to_s) }
78
+ end
79
+
80
+ # A dotted path must end on a SCALAR member — landing on a value
81
+ # object would hand SQL a JSON object where the reference
82
+ # interpreter unwraps a hash, and the two would answer differently.
83
+ def scalar_leaf?(attribute, segments, &value_object)
84
+ leaf = leaf_attribute(attribute, segments, &value_object)
85
+ !leaf.nil? && !leaf.list? && !leaf.reference? && SCALAR_PRIMITIVES.include?(leaf.type.to_s)
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,130 @@
1
+ require_relative "../naming"
2
+
3
+ module Hecks
4
+ module QuerySpecification
5
+ # ONE reading of a dotted query-field path that hops THROUGH a
6
+ # reference into another aggregate's own shape — FieldPath's
7
+ # sibling, not its member. FieldPath walks a SHAPE, which cannot
8
+ # loop, and answers nil, never raising, because there is nothing
9
+ # left to say beyond "not found." HopPath walks the REFERENCE
10
+ # GRAPH instead, which the language does not guarantee acyclic —
11
+ # aggregates only refuse a DIRECT bidirectional pair
12
+ # (BluebookBuilder#validate_no_bidirectional_references! explicitly
13
+ # declines to take a position on a longer ring) — so a walk here
14
+ # needs to say WHY it stopped, not just that it did.
15
+ #
16
+ # Every method below takes an ATTRIBUTE ARRAY, never a "shape"
17
+ # object — deliberately, because the two real callers hold their
18
+ # attributes differently. `AggregateBuilder` (tier-1 seal, mid-
19
+ # build) exposes `attributes` as a plain reader, but its own
20
+ # `attribute(name, type = String, ...)` is the DSL's attribute-
21
+ # DECLARING method — calling it as a finder would silently mint a
22
+ # new String attribute instead of looking one up. A sealed
23
+ # `Aggregate` (tier-2 seal, and every runtime target) has a
24
+ # real `attribute(name)` finder, but taking the array either way
25
+ # sidesteps the mismatch instead of asking every caller to know
26
+ # which kind of object it holds.
27
+ module HopPath
28
+ module_function
29
+
30
+ Hop = Struct.new(:attribute, :target_name, :target, keyword_init: true)
31
+
32
+ # refusal: nil (clean), :unresolvable (a hop's target cannot be
33
+ # found — cross-domain, or simply not declared in this chapter),
34
+ # :too_deep (see MAX_HOPS below).
35
+ Plan = Struct.new(:hops, :tail, :refusal, keyword_init: true)
36
+
37
+ # A hop chain long enough to matter is long enough to be a
38
+ # mistake — NOT a guard against a walk that cannot terminate.
39
+ # Nothing here loops forever regardless of how the reference
40
+ # graph is shaped: a hop chain is a literal dotted string, fixed
41
+ # at declaration time, and every step consumes exactly one of
42
+ # its own segments — the walk is bounded by what was TYPED, not
43
+ # by the graph. A self-referential aggregate hopping through
44
+ # itself more than once (`"parent.parent.name"`, a grandparent
45
+ # query) is real, common, and perfectly safe; this exists only
46
+ # to refuse a chain nobody meant to write this long.
47
+ MAX_HOPS = 8
48
+
49
+ # The segment name a Reference answers to in a hop path — its own
50
+ # declared attribute name, unchanged (ADR 0025, "References":
51
+ # `reference_to` mints that bare name now, no `_id`, so there is
52
+ # no derivation left to apply). `proposal/client` (a query hop)
53
+ # and `proposal.client` (the Ruby accessor,
54
+ # `Facade::Handle#define_reference_accessors`) name the same
55
+ # concept the same way.
56
+ def hop_name(attribute) = attribute.name.to_s
57
+
58
+ # Does this path's HEAD cross into another record via `/`? THE
59
+ # OPERATOR IS THE ANSWER NOW, not a name collision to arbitrate —
60
+ # `.` walks fields inside this record, `/` crosses into another
61
+ # one, so a path with no `/` is never a hop, full stop, and the
62
+ # OLD "a real local attribute wins first" rule (needed only
63
+ # because `.` was overloaded for both meanings, and `client_id`
64
+ # vs `client` was how the two were told apart) has nothing left
65
+ # to arbitrate. Answerable from `attributes` alone — a Reference
66
+ # knows its own `target_name` at declaration, before it can
67
+ # `resolve` it — which is what lets the AGGREGATE seal recognise
68
+ # a hop it cannot yet check.
69
+ def hop_head?(field, attributes)
70
+ head, rest = field.to_s.split("/", 2)
71
+ return false unless rest
72
+
73
+ attributes.any? { |candidate| candidate.reference? && hop_name(candidate) == head }
74
+ end
75
+
76
+ # ONE STEP: does `field`'s head hop through one of `attributes`'
77
+ # own references? Answers the resolved `Hop` plus the string
78
+ # still left to walk (itself possibly another `/`-hop, against
79
+ # the TARGET's own attributes, or a plain `.`-dotted field walk
80
+ # once the hops run out) — or nil, when the head names nothing or
81
+ # the path has no `/` at all. This is the one primitive
82
+ # Runtime::ReferenceHop needs: it recurses hop by hop through its
83
+ # own `apply`, one ordinary same-aggregate query at a time, and
84
+ # never needs the whole chain resolved up front the way a seal
85
+ # does.
86
+ def next_hop(field, attributes)
87
+ head, rest = field.to_s.split("/", 2)
88
+ return nil unless rest
89
+
90
+ attribute = attributes.find { |candidate| candidate.reference? && hop_name(candidate) == head }
91
+ return nil unless attribute
92
+
93
+ hop = Hop.new(attribute: attribute, target_name: attribute.type.target_name, target: attribute.type.resolve)
94
+ [hop, rest]
95
+ end
96
+
97
+ # The WHOLE chain, resolved — every hop's target found, in
98
+ # order — for the one caller that needs it all at once:
99
+ # BluebookBuilder#validate_query_hops!, checking a hop chain
100
+ # before anything ever dispatches it.
101
+ def plan(field, attributes)
102
+ hops = []
103
+ remaining = field.to_s
104
+ current = attributes
105
+
106
+ loop do
107
+ step = next_hop(remaining, current)
108
+ break unless step
109
+
110
+ hop, rest = step
111
+ return Plan.new(hops: hops, tail: nil, refusal: :too_deep) if hops.size >= MAX_HOPS
112
+
113
+ # Pushed even unresolved — a caller reporting :unresolvable
114
+ # needs THIS hop's own target_name (real, known at
115
+ # declaration, regardless of whether resolve succeeded), not
116
+ # whatever hop came before it. `.target` is nil on this one
117
+ # entry; every caller checking `hops.last.target` already has
118
+ # to handle that, the same way `next_hop`'s own caller does.
119
+ hops << hop
120
+ return Plan.new(hops: hops, tail: nil, refusal: :unresolvable) unless hop.target
121
+
122
+ current = hop.target.attributes
123
+ remaining = rest
124
+ end
125
+
126
+ Plan.new(hops: hops, tail: remaining, refusal: nil)
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,18 @@
1
+ require_relative "../common/options"
2
+
3
+ module Hecks
4
+ module QuerySpecification
5
+ module ReadModel
6
+ class Specification < Common::Options
7
+ attr_reader :joins
8
+
9
+ def initialize(joins: [], **options)
10
+ super(**options)
11
+ @joins = joins
12
+ end
13
+
14
+ def to_h = options_to_h.merge(joins: @joins)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ module Hecks
2
+ # The declarative shape of an ask — what a query says, held apart from
3
+ # any engine that answers it. `common/` is the vocabulary every ask
4
+ # shares; `read_model/` the read-model-specific specification.
5
+ module QuerySpecification
6
+ end
7
+ end
8
+
9
+ require_relative "query_specification/common/specification"
10
+ require_relative "query_specification/common/null_policy"
11
+ require_relative "query_specification/common/dsl"
12
+ require_relative "query_specification/field_path"
13
+ require_relative "query_specification/hop_path"
14
+ require_relative "query_specification/read_model/specification"
@@ -0,0 +1,48 @@
1
+ require "json"
2
+
3
+ module Hecks
4
+ # HOW A VALUE READS INSIDE A REFUSAL, in one place, because a refusal is an
5
+ # ANSWER and its wording is contract — pinned byte-for-byte by the corpus.
6
+ #
7
+ # `inspect` and JSON agree on scalars and disagree on everything composite :
8
+ # a hash is `{"cents"=>100}` against `{"cents":100}`, an array is `["a", "b"]`
9
+ # against `["a","b"]`. Each such spelling is invisible until a composite
10
+ # actually reaches a message, which is why both known cases were found by
11
+ # something deliberately passing the wrong shape rather than by review — the
12
+ # first by hand in spec/corpus/banking.json, the second by bin/fuzz, at a
13
+ # different site, after the first was patched where it was found.
14
+ #
15
+ # So it lives here rather than in whichever file noticed it, and the house style
16
+ # is JSON because that is what every other refusal in the corpus already prints.
17
+ module Rendering
18
+ module_function
19
+
20
+ def describe(value)
21
+ case value
22
+ when nil then "nil"
23
+ when Hash, Array then JSON.generate(value)
24
+ else
25
+ # A Hecks::Runtime::Value reaching a refusal message (an
26
+ # arithmetic op's own `amount`/`current`, an Admissibility
27
+ # `current`, ...) had no case here, falling straight through to
28
+ # Ruby's own #inspect and leaking a raw object pointer
29
+ # ("#<Hecks::Runtime::Value:0x...>") into an otherwise
30
+ # correct domain refusal. Duck-typed on `respond_to?(:to_h)`
31
+ # rather than naming `Runtime::Value` directly — `Runtime::Value`
32
+ # itself requires THIS file (`runtime/value.rb`'s own
33
+ # `require_relative "../rendering"`), so naming it here would be
34
+ # circular. A single-field wrapper (the overwhelming common case
35
+ # — an amount, an id, a lifecycle field) unwraps to its bare
36
+ # scalar, described recursively; a genuinely multi-field value
37
+ # renders as its fields' JSON, same as a bare Hash already does
38
+ # two lines up.
39
+ if value.respond_to?(:to_h) && !value.is_a?(Hash) && !value.is_a?(Array)
40
+ fields = value.to_h
41
+ fields.size == 1 ? describe(fields.values.first) : JSON.generate(fields)
42
+ else
43
+ value.inspect
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,157 @@
1
+ require_relative "../fqn"
2
+ require_relative "../facade/handle"
3
+
4
+ module Hecks
5
+ class Router
6
+ # Installs optional Ruby syntax over an already-loaded router. FQN
7
+ # resolution stays in Router; this object only adapts it to constants and
8
+ # method calls.
9
+ class NamespaceInstaller
10
+ class OptionsProxy
11
+ def initialize(router:, realm:, domain:, aggregate:, options:)
12
+ unknown = options.keys - [:version]
13
+ raise ArgumentError, "unknown router options: #{unknown.join(', ')}" unless unknown.empty?
14
+
15
+ @router, @realm, @domain, @aggregate = router, realm, domain, aggregate
16
+ @version = options[:version]&.to_s
17
+ end
18
+
19
+ def method_missing(verb, **args)
20
+ fqn = fqn_for(verb)
21
+ fqn.command? ? @router.dispatch(fqn.to_s, **args) : @router.query(fqn.to_s, **args)
22
+ end
23
+
24
+ def respond_to_missing?(verb, include_private = false)
25
+ Fqn.command_name?(verb) || Fqn.query_name?(verb) || super
26
+ end
27
+
28
+ private
29
+
30
+ def fqn_for(verb)
31
+ if Fqn.command_name?(verb)
32
+ Fqn.command(realm: @realm, domain: @domain, version: @version, aggregate: @aggregate, command: verb)
33
+ elsif Fqn.query_name?(verb)
34
+ Fqn.query(realm: @realm, domain: @domain, version: @version, aggregate: @aggregate, query: verb)
35
+ else
36
+ raise NoMethodError, "#{verb.inspect} is not a Bluebook command or query"
37
+ end
38
+ end
39
+ end
40
+
41
+ def initialize(router)
42
+ @router = router
43
+ end
44
+
45
+ def install!
46
+ current_entries.each { |entry| install_namespace_entry(entry) }
47
+ install_shortcuts!
48
+ install_aggregate_doors!
49
+ self
50
+ end
51
+
52
+ private
53
+
54
+ attr_reader :router
55
+
56
+ def current_entries = router.available.reject { |entry| entry.fqn.version }
57
+
58
+ def install_namespace_entry(entry)
59
+ target = namespace_for(entry.fqn.realm, entry.fqn.domain, entry.fqn.aggregate)
60
+ address = entry.fqn.to_s
61
+ active_router = router
62
+ target.define_singleton_method(:options) do |**options|
63
+ OptionsProxy.new(router: active_router, realm: entry.fqn.realm, domain: entry.fqn.domain,
64
+ aggregate: entry.fqn.aggregate, options: options)
65
+ end
66
+ if entry.command?
67
+ target.define_singleton_method(entry.fqn.verb) { |**args| active_router.dispatch(address, **args) }
68
+ else
69
+ target.define_singleton_method(entry.fqn.verb) { |**args| active_router.query(address, **args) }
70
+ end
71
+ end
72
+
73
+ # `.find`/`.all`/`.count`/`.events`/`.repository` — the SAME read/CRUD
74
+ # surface `Facade::Surface::AggregateDoor` gives a plain `Hecks
75
+ # .boot`, missing here until now: `install_namespace_entry` above
76
+ # installs ONE method per declared VERB, so an aggregate with no
77
+ # commands or queries of its own shape (or simply never asked for
78
+ # a `find`-shaped query) had no way to look up one record by id
79
+ # through the router surface at all — real gap, hit live building
80
+ # a `List` aggregate meant to be read this way. Grouped by
81
+ # (realm, domain, aggregate) rather than installed per-verb,
82
+ # because unlike a command or query this is the SAME five methods
83
+ # regardless of which verb happened to trigger this aggregate's
84
+ # own namespace module into existing.
85
+ def install_aggregate_doors!
86
+ current_entries.reject { |entry| entry.fqn.aggregate.nil? }
87
+ .group_by { |entry| [entry.fqn.realm, entry.fqn.domain, entry.fqn.aggregate] }
88
+ .each_value { |entries| install_aggregate_door(entries.first) }
89
+ end
90
+
91
+ def install_aggregate_door(entry)
92
+ dispatcher = entry.dispatcher
93
+ domain = entry.fqn.domain
94
+ ir = dispatcher.registry.bluebook(domain)&.aggregate(entry.fqn.aggregate)
95
+ return unless ir # a query/command-only entry whose owner isn't a real aggregate root
96
+
97
+ fqn = "#{domain}::#{ir.hecks_name}"
98
+ target = namespace_for(entry.fqn.realm, domain, entry.fqn.aggregate)
99
+
100
+ target.define_singleton_method(:repository) { dispatcher.registry.repository(domain, ir) }
101
+ target.define_singleton_method(:count) { dispatcher.registry.repository(domain, ir).count }
102
+ target.define_singleton_method(:events) { dispatcher.events.select { |event| event.aggregate == fqn } }
103
+
104
+ target.define_singleton_method(:find) do |id|
105
+ found = dispatcher.registry.repository(domain, ir).find(id)
106
+ found && Facade::Handle.new(dispatcher: dispatcher, domain: domain, ir: ir, instance: found)
107
+ end
108
+
109
+ target.define_singleton_method(:all) do
110
+ dispatcher.registry.repository(domain, ir).all.map do |instance|
111
+ Facade::Handle.new(dispatcher: dispatcher, domain: domain, ir: ir, instance: instance)
112
+ end
113
+ end
114
+ end
115
+
116
+ def install_shortcuts!
117
+ installer = self
118
+ current_entries.reject { |entry| entry.fqn.aggregate.nil? }
119
+ .group_by { |entry| [entry.fqn.aggregate, entry.fqn.verb] }
120
+ .each do |(aggregate, verb), candidates|
121
+ shortcut_target(aggregate).define_singleton_method(verb) { |**args|
122
+ installer.send(:dispatch_short, candidates, **args)
123
+ }
124
+ end
125
+ end
126
+
127
+ def dispatch_short(candidates, **args)
128
+ if candidates.length > 1
129
+ shown = candidates.map { |entry| entry.fqn.to_s }.sort.join(", ")
130
+ aggregate, verb = candidates.first.fqn.aggregate, candidates.first.fqn.verb
131
+ raise AmbiguousShortRoute, "#{aggregate}.#{verb} is ambiguous — choose one of: #{shown}"
132
+ end
133
+
134
+ entry = candidates.fetch(0)
135
+ entry.command? ? router.dispatch(entry.fqn.to_s, **args) : router.query(entry.fqn.to_s, **args)
136
+ end
137
+
138
+ def shortcut_target(aggregate)
139
+ constant = Object.const_get(aggregate, false) if Object.const_defined?(aggregate, false)
140
+ raise NameError,
141
+ "cannot install Bluebook shortcut #{aggregate}: it is not a module" if constant && !constant.is_a?(Module)
142
+
143
+ constant || Object.const_set(aggregate, Module.new)
144
+ end
145
+
146
+ def namespace_for(realm, domain, aggregate)
147
+ [realm, domain, aggregate].compact.reduce(Object) do |parent, name|
148
+ constant = parent.const_get(name, false) if parent.const_defined?(name, false)
149
+ raise NameError,
150
+ "cannot install Bluebook route under #{parent}::#{name}: it is not a module" if constant && !constant.is_a?(Module)
151
+
152
+ constant || parent.const_set(name, Module.new)
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end