hecks 0.2.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (419) hide show
  1. checksums.yaml +5 -5
  2. data/lib/hecks/adapters/driven/claude_code.adapter +3 -0
  3. data/lib/hecks/adapters/driven/claude_code.rb +127 -0
  4. data/lib/hecks/adapters/driven/d1.adapter +12 -0
  5. data/lib/hecks/adapters/driven/d1.rb +427 -0
  6. data/lib/hecks/adapters/driven/folder.adapter +3 -0
  7. data/lib/hecks/adapters/driven/folder.rb +199 -0
  8. data/lib/hecks/adapters/driven/google_authentication.adapter +3 -0
  9. data/lib/hecks/adapters/driven/google_authentication.rb +100 -0
  10. data/lib/hecks/adapters/driven/governance_authorization.adapter +3 -0
  11. data/lib/hecks/adapters/driven/governance_authorization.rb +101 -0
  12. data/lib/hecks/adapters/driven/heki/journal.rb +56 -0
  13. data/lib/hecks/adapters/driven/heki/saga_store.rb +99 -0
  14. data/lib/hecks/adapters/driven/heki/snapshot.rb +65 -0
  15. data/lib/hecks/adapters/driven/heki.adapter +4 -0
  16. data/lib/hecks/adapters/driven/heki.rb +179 -0
  17. data/lib/hecks/adapters/driven/identity_registry.adapter +3 -0
  18. data/lib/hecks/adapters/driven/identity_registry.rb +26 -0
  19. data/lib/hecks/adapters/driven/in_memory_ordering.rb +51 -0
  20. data/lib/hecks/adapters/driven/lambda/client.rb +63 -0
  21. data/lib/hecks/adapters/driven/lambda.adapter +4 -0
  22. data/lib/hecks/adapters/driven/lambda.rb +145 -0
  23. data/lib/hecks/adapters/driven/memory.adapter +3 -0
  24. data/lib/hecks/adapters/driven/memory.rb +103 -0
  25. data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +3 -0
  26. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +28 -0
  27. data/lib/hecks/adapters/driven/postgres/codec.rb +88 -0
  28. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +207 -0
  29. data/lib/hecks/adapters/driven/postgres.adapter +5 -0
  30. data/lib/hecks/adapters/driven/postgres.rb +438 -0
  31. data/lib/hecks/adapters/driven/postgres_era.adapter +17 -0
  32. data/lib/hecks/adapters/driven/prism.adapter +3 -0
  33. data/lib/hecks/adapters/driven/prism.rb +80 -0
  34. data/lib/hecks/adapters/driven/secure_random_identity.adapter +3 -0
  35. data/lib/hecks/adapters/driven/secure_random_identity.rb +14 -0
  36. data/lib/hecks/adapters/driven/sql_query_builder.rb +221 -0
  37. data/lib/hecks/adapters/driven/sqlite/codec.rb +80 -0
  38. data/lib/hecks/adapters/driven/sqlite/projection.rb +172 -0
  39. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +192 -0
  40. data/lib/hecks/adapters/driven/sqlite.adapter +9 -0
  41. data/lib/hecks/adapters/driven/sqlite.rb +306 -0
  42. data/lib/hecks/adapters/driven/system_clock.adapter +3 -0
  43. data/lib/hecks/adapters/driven/system_clock.rb +14 -0
  44. data/lib/hecks/adapters/driven.rb +54 -0
  45. data/lib/hecks/adapters.rb +6 -0
  46. data/lib/hecks/behaviors/dsl.rb +125 -0
  47. data/lib/hecks/behaviors/expectations.rb +340 -0
  48. data/lib/hecks/behaviors/ir.rb +31 -0
  49. data/lib/hecks/behaviors/rspec.rb +42 -0
  50. data/lib/hecks/behaviors/runner.rb +96 -0
  51. data/lib/hecks/behaviors.rb +25 -0
  52. data/lib/hecks/bluebook/aggregate.rb +108 -0
  53. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +134 -0
  54. data/lib/hecks/bluebook/assembly/build.rb +48 -0
  55. data/lib/hecks/bluebook/assembly/contract.rb +112 -0
  56. data/lib/hecks/bluebook/assembly/contracts.rb +438 -0
  57. data/lib/hecks/bluebook/assembly/marks.rb +228 -0
  58. data/lib/hecks/bluebook/assembly/specializer.rb +70 -0
  59. data/lib/hecks/bluebook/assembly.rb +91 -0
  60. data/lib/hecks/bluebook/attribute.rb +96 -0
  61. data/lib/hecks/bluebook/behaviour/aggregate.rb +83 -0
  62. data/lib/hecks/bluebook/behaviour/attribute.rb +27 -0
  63. data/lib/hecks/bluebook/behaviour/chapter.rb +72 -0
  64. data/lib/hecks/bluebook/behaviour/command.rb +116 -0
  65. data/lib/hecks/bluebook/behaviour/domain_port.rb +25 -0
  66. data/lib/hecks/bluebook/behaviour/entity.rb +59 -0
  67. data/lib/hecks/bluebook/behaviour/hexagon.rb +50 -0
  68. data/lib/hecks/bluebook/behaviour/lifecycle.rb +68 -0
  69. data/lib/hecks/bluebook/behaviour/policy.rb +52 -0
  70. data/lib/hecks/bluebook/behaviour/process_manager.rb +51 -0
  71. data/lib/hecks/bluebook/behaviour/query.rb +10 -0
  72. data/lib/hecks/bluebook/behaviour/read_model.rb +29 -0
  73. data/lib/hecks/bluebook/behaviour/traits.rb +81 -0
  74. data/lib/hecks/bluebook/behaviour/value_object.rb +33 -0
  75. data/lib/hecks/bluebook/chapter.rb +78 -0
  76. data/lib/hecks/bluebook/command.rb +124 -0
  77. data/lib/hecks/bluebook/domain_port.rb +102 -0
  78. data/lib/hecks/bluebook/dsl/adapter_builder.rb +34 -0
  79. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +1018 -0
  80. data/lib/hecks/bluebook/dsl/attribute_collector.rb +348 -0
  81. data/lib/hecks/bluebook/dsl/binding_proxy.rb +71 -0
  82. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +1087 -0
  83. data/lib/hecks/bluebook/dsl/command_builder.rb +714 -0
  84. data/lib/hecks/bluebook/dsl/const_shim.rb +81 -0
  85. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +79 -0
  86. data/lib/hecks/bluebook/dsl/entity_builder.rb +430 -0
  87. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +366 -0
  88. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +163 -0
  89. data/lib/hecks/bluebook/dsl/identity_declaration.rb +191 -0
  90. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +44 -0
  91. data/lib/hecks/bluebook/dsl/malformed.rb +7 -0
  92. data/lib/hecks/bluebook/dsl/policy_builder.rb +135 -0
  93. data/lib/hecks/bluebook/dsl/port_builder.rb +39 -0
  94. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +142 -0
  95. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +306 -0
  96. data/lib/hecks/bluebook/dsl/query_builder.rb +113 -0
  97. data/lib/hecks/bluebook/dsl/read_model_builder.rb +233 -0
  98. data/lib/hecks/bluebook/dsl/rule_reference.rb +173 -0
  99. data/lib/hecks/bluebook/dsl/translation_builder.rb +243 -0
  100. data/lib/hecks/bluebook/dsl/value_object_builder.rb +178 -0
  101. data/lib/hecks/bluebook/dsl/word_gate.rb +228 -0
  102. data/lib/hecks/bluebook/dsl/world_builder.rb +117 -0
  103. data/lib/hecks/bluebook/dsl.rb +45 -0
  104. data/lib/hecks/bluebook/entity.rb +103 -0
  105. data/lib/hecks/bluebook/expression/canonical_form.rb +123 -0
  106. data/lib/hecks/bluebook/expression/evaluator.rb +304 -0
  107. data/lib/hecks/bluebook/expression/projection.json +218 -0
  108. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +233 -0
  109. data/lib/hecks/bluebook/expression/resolver.rb +781 -0
  110. data/lib/hecks/bluebook/expression.rb +13 -0
  111. data/lib/hecks/bluebook/hexagon.rb +60 -0
  112. data/lib/hecks/bluebook/lifecycle.rb +42 -0
  113. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +54 -0
  114. data/lib/hecks/bluebook/meta_validator/judge.rb +621 -0
  115. data/lib/hecks/bluebook/meta_validator/plan.rb +332 -0
  116. data/lib/hecks/bluebook/meta_validator/port_judge.rb +51 -0
  117. data/lib/hecks/bluebook/meta_validator/readings.rb +360 -0
  118. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +351 -0
  119. data/lib/hecks/bluebook/meta_validator/shapes.rb +266 -0
  120. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +255 -0
  121. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +138 -0
  122. data/lib/hecks/bluebook/meta_validator/world_judge.rb +78 -0
  123. data/lib/hecks/bluebook/meta_validator.rb +542 -0
  124. data/lib/hecks/bluebook/model_check.rb +445 -0
  125. data/lib/hecks/bluebook/pattern_subset.rb +184 -0
  126. data/lib/hecks/bluebook/policy.rb +41 -0
  127. data/lib/hecks/bluebook/process_manager.rb +128 -0
  128. data/lib/hecks/bluebook/project_discovery.rb +30 -0
  129. data/lib/hecks/bluebook/project_loader.rb +40 -0
  130. data/lib/hecks/bluebook/project_register.rb +82 -0
  131. data/lib/hecks/bluebook/query.rb +61 -0
  132. data/lib/hecks/bluebook/read_model.rb +109 -0
  133. data/lib/hecks/bluebook/reference.rb +74 -0
  134. data/lib/hecks/bluebook/smoke_test.rb +166 -0
  135. data/lib/hecks/bluebook/synthesizer.rb +95 -0
  136. data/lib/hecks/bluebook/translation.rb +92 -0
  137. data/lib/hecks/bluebook/value_object.rb +66 -0
  138. data/lib/hecks/bluebook.rb +74 -0
  139. data/lib/hecks/codemod.rb +342 -0
  140. data/lib/hecks/construct.rb +71 -0
  141. data/lib/hecks/deploy/bluebook/deploy.bluebook +219 -0
  142. data/lib/hecks/deploy/bluebook/deploy.hecksagon +4 -0
  143. data/lib/hecks/deploy/oidc.json +18 -0
  144. data/lib/hecks/doc/reference.rb +410 -0
  145. data/lib/hecks/embryonaut_bluebook.rb +75 -0
  146. data/lib/hecks/facade/cli_door.rb +119 -0
  147. data/lib/hecks/facade/cli_runner.rb +190 -0
  148. data/lib/hecks/facade/command_request.rb +105 -0
  149. data/lib/hecks/facade/handle.rb +173 -0
  150. data/lib/hecks/facade/json_door.rb +166 -0
  151. data/lib/hecks/facade/surface/aggregate_door.rb +185 -0
  152. data/lib/hecks/facade/surface/chapter.rb +107 -0
  153. data/lib/hecks/facade/surface.rb +48 -0
  154. data/lib/hecks/facade.rb +44 -0
  155. data/lib/hecks/forms/app.rb +341 -0
  156. data/lib/hecks/forms/command_form_renderer.rb +113 -0
  157. data/lib/hecks/forms/examples/banking_console.bluebook +3 -0
  158. data/lib/hecks/forms/field_renderer.rb +177 -0
  159. data/lib/hecks/forms/field_shape.rb +232 -0
  160. data/lib/hecks/forms/html.rb +84 -0
  161. data/lib/hecks/forms/index_renderer.rb +35 -0
  162. data/lib/hecks/forms/page.rb +157 -0
  163. data/lib/hecks/forms/params.rb +161 -0
  164. data/lib/hecks/forms/port_argument.rb +46 -0
  165. data/lib/hecks/forms/query_form_renderer.rb +114 -0
  166. data/lib/hecks/forms/record_renderer.rb +119 -0
  167. data/lib/hecks/forms/record_table.rb +68 -0
  168. data/lib/hecks/forms/reference_options.rb +30 -0
  169. data/lib/hecks/forms/value_object_shape.rb +46 -0
  170. data/lib/hecks/forms.rb +54 -0
  171. data/lib/hecks/fqn.rb +94 -0
  172. data/lib/hecks/framework/bluebook/compliance.bluebook +1 -0
  173. data/lib/hecks/framework/bluebook/console_settings.bluebook +489 -0
  174. data/lib/hecks/framework/bluebook/framework.hecksagon +32 -0
  175. data/lib/hecks/framework/bluebook/governance.bluebook +145 -0
  176. data/lib/hecks/framework/bluebook/identity.bluebook +90 -0
  177. data/lib/hecks/framework/oidc.json +39 -0
  178. data/lib/hecks/framework.rb +90 -0
  179. data/lib/hecks/freezer.rb +67 -0
  180. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +527 -0
  181. data/lib/hecks/fuzzing/invalid_value_generator.rb +86 -0
  182. data/lib/hecks/fuzzing/isolated_boot.rb +286 -0
  183. data/lib/hecks/fuzzing/properties.rb +1192 -0
  184. data/lib/hecks/fuzzing/replay.rb +668 -0
  185. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +93 -0
  186. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +80 -0
  187. data/lib/hecks/fuzzing/sequence_generator/picker.rb +96 -0
  188. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +250 -0
  189. data/lib/hecks/fuzzing/sequence_generator.rb +131 -0
  190. data/lib/hecks/fuzzing/value_generator.rb +200 -0
  191. data/lib/hecks/fuzzing.rb +12 -0
  192. data/lib/hecks/grammar/evolve.rb +324 -0
  193. data/lib/hecks/grammar/expression.bluebook +420 -0
  194. data/lib/hecks/grammar/expression_operators.json +1648 -0
  195. data/lib/hecks/grammar/grammar.hecksagon +20 -0
  196. data/lib/hecks/grammar/oidc.json +69 -0
  197. data/lib/hecks/grammar/translation.bluebook +203 -0
  198. data/lib/hecks/grammar.rb +178 -0
  199. data/lib/hecks/ir.rb +126 -0
  200. data/lib/hecks/language/adapter.bluebook +116 -0
  201. data/lib/hecks/language/bluebook/aggregate.bluebook +581 -0
  202. data/lib/hecks/language/bluebook/attaches/paging.bluebook +76 -0
  203. data/lib/hecks/language/bluebook/bluebook.bluebook +256 -0
  204. data/lib/hecks/language/bluebook/bluebook.hecksagon +24 -0
  205. data/lib/hecks/language/bluebook/command.bluebook +471 -0
  206. data/lib/hecks/language/bluebook/entity.bluebook +392 -0
  207. data/lib/hecks/language/bluebook/policy.bluebook +189 -0
  208. data/lib/hecks/language/bluebook/process_manager.bluebook +381 -0
  209. data/lib/hecks/language/bluebook/projection.bluebook +267 -0
  210. data/lib/hecks/language/bluebook/query.bluebook +245 -0
  211. data/lib/hecks/language/bluebook/shape.bluebook +292 -0
  212. data/lib/hecks/language/bluebook/syntax.bluebook +448 -0
  213. data/lib/hecks/language/bluebook/vocabulary.bluebook +379 -0
  214. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +51 -0
  215. data/lib/hecks/language/hecksagon/domain_port.bluebook +76 -0
  216. data/lib/hecks/language/hecksagon/hecksagon.bluebook +131 -0
  217. data/lib/hecks/language/hecksagon/port_operation.bluebook +102 -0
  218. data/lib/hecks/language/oidc.json +333 -0
  219. data/lib/hecks/language/port.bluebook +120 -0
  220. data/lib/hecks/language/translation/translation.bluebook +110 -0
  221. data/lib/hecks/language/translation/translation_aggregate.bluebook +267 -0
  222. data/lib/hecks/language/world/wiring.bluebook +62 -0
  223. data/lib/hecks/language/world/world.bluebook +84 -0
  224. data/lib/hecks/literal.rb +125 -0
  225. data/lib/hecks/naming.rb +174 -0
  226. data/lib/hecks/ports/access_control.port +9 -0
  227. data/lib/hecks/ports/access_control.rb +62 -0
  228. data/lib/hecks/ports/agent/answers.rb +104 -0
  229. data/lib/hecks/ports/agent.port +8 -0
  230. data/lib/hecks/ports/agent.rb +167 -0
  231. data/lib/hecks/ports/authentication.port +6 -0
  232. data/lib/hecks/ports/authentication.rb +50 -0
  233. data/lib/hecks/ports/authorization.port +7 -0
  234. data/lib/hecks/ports/authorization.rb +62 -0
  235. data/lib/hecks/ports/clock.port +5 -0
  236. data/lib/hecks/ports/clock.rb +62 -0
  237. data/lib/hecks/ports/extraction.port +5 -0
  238. data/lib/hecks/ports/extraction.rb +37 -0
  239. data/lib/hecks/ports/identity_assignment.port +5 -0
  240. data/lib/hecks/ports/identity_assignment.rb +45 -0
  241. data/lib/hecks/ports/identity_generation.port +5 -0
  242. data/lib/hecks/ports/identity_generation.rb +49 -0
  243. data/lib/hecks/ports/identity_resolution.port +5 -0
  244. data/lib/hecks/ports/identity_resolution.rb +40 -0
  245. data/lib/hecks/ports/loading.port +4 -0
  246. data/lib/hecks/ports/loading.rb +13 -0
  247. data/lib/hecks/ports/persistence/append_only.rb +138 -0
  248. data/lib/hecks/ports/persistence/binding_policy.rb +56 -0
  249. data/lib/hecks/ports/persistence/execution.rb +23 -0
  250. data/lib/hecks/ports/persistence/null_saga_store.rb +25 -0
  251. data/lib/hecks/ports/persistence/plugin.rb +54 -0
  252. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +169 -0
  253. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +128 -0
  254. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +161 -0
  255. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +61 -0
  256. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +304 -0
  257. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +171 -0
  258. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +190 -0
  259. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +478 -0
  260. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +166 -0
  261. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +314 -0
  262. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +168 -0
  263. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +170 -0
  264. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +134 -0
  265. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +137 -0
  266. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +89 -0
  267. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +85 -0
  268. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +43 -0
  269. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +132 -0
  270. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +74 -0
  271. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +758 -0
  272. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +120 -0
  273. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +31 -0
  274. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +46 -0
  275. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +102 -0
  276. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +46 -0
  277. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +70 -0
  278. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +72 -0
  279. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +120 -0
  280. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +183 -0
  281. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +41 -0
  282. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +28 -0
  283. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +30 -0
  284. data/lib/hecks/ports/persistence/plugins/era/translation.rb +11 -0
  285. data/lib/hecks/ports/persistence/plugins/era.rb +48 -0
  286. data/lib/hecks/ports/persistence/remote_runtime.rb +42 -0
  287. data/lib/hecks/ports/persistence/repository_factory.rb +30 -0
  288. data/lib/hecks/ports/persistence.port +4 -0
  289. data/lib/hecks/ports/persistence.rb +39 -0
  290. data/lib/hecks/ports/projection.port +4 -0
  291. data/lib/hecks/ports/projection.rb +96 -0
  292. data/lib/hecks/ports/query/in_memory.rb +60 -0
  293. data/lib/hecks/ports/query/ordering.rb +41 -0
  294. data/lib/hecks/ports/query.rb +36 -0
  295. data/lib/hecks/ports.rb +27 -0
  296. data/lib/hecks/projections/diagrams.rb +642 -0
  297. data/lib/hecks/projections/ir.rb +18 -0
  298. data/lib/hecks/projections/model/deviations.rb +98 -0
  299. data/lib/hecks/projections/model.rb +145 -0
  300. data/lib/hecks/projections/oidc.rb +110 -0
  301. data/lib/hecks/projections/parser_table.rb +159 -0
  302. data/lib/hecks/projections/reference.rb +38 -0
  303. data/lib/hecks/projections/shape.rb +44 -0
  304. data/lib/hecks/projections/statements.rb +110 -0
  305. data/lib/hecks/projections/vocabulary.rb +100 -0
  306. data/lib/hecks/projections.rb +32 -0
  307. data/lib/hecks/projector/cli_projector.rb +446 -0
  308. data/lib/hecks/projector/docs_projector.rb +321 -0
  309. data/lib/hecks/projector/exporter.rb +158 -0
  310. data/lib/hecks/projector/ir_projector.rb +18 -0
  311. data/lib/hecks/projector/narrate_projector.rb +243 -0
  312. data/lib/hecks/projector/target.rb +97 -0
  313. data/lib/hecks/projector.rb +186 -0
  314. data/lib/hecks/query_ir.rb +411 -0
  315. data/lib/hecks/query_specification/common/authorization_spec.rb +9 -0
  316. data/lib/hecks/query_specification/common/comparators.rb +25 -0
  317. data/lib/hecks/query_specification/common/comparison.rb +174 -0
  318. data/lib/hecks/query_specification/common/cursor_spec.rb +9 -0
  319. data/lib/hecks/query_specification/common/dsl.rb +58 -0
  320. data/lib/hecks/query_specification/common/inspection_spec.rb +9 -0
  321. data/lib/hecks/query_specification/common/limit_spec.rb +9 -0
  322. data/lib/hecks/query_specification/common/null_policy.rb +92 -0
  323. data/lib/hecks/query_specification/common/null_semantics.rb +11 -0
  324. data/lib/hecks/query_specification/common/offset_spec.rb +9 -0
  325. data/lib/hecks/query_specification/common/options.rb +32 -0
  326. data/lib/hecks/query_specification/common/order_by.rb +9 -0
  327. data/lib/hecks/query_specification/common/specification.rb +10 -0
  328. data/lib/hecks/query_specification/common/where_clause.rb +9 -0
  329. data/lib/hecks/query_specification/field_path.rb +107 -0
  330. data/lib/hecks/query_specification/hop_path.rb +132 -0
  331. data/lib/hecks/query_specification/read_model/specification.rb +18 -0
  332. data/lib/hecks/query_specification.rb +14 -0
  333. data/lib/hecks/rendering.rb +48 -0
  334. data/lib/hecks/router/namespace_installer.rb +157 -0
  335. data/lib/hecks/router.rb +70 -0
  336. data/lib/hecks/runtime/aggregate_lock.rb +45 -0
  337. data/lib/hecks/runtime/boot_gates.rb +41 -0
  338. data/lib/hecks/runtime/caller.rb +66 -0
  339. data/lib/hecks/runtime/capability_graph.rb +44 -0
  340. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +135 -0
  341. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +260 -0
  342. data/lib/hecks/runtime/command_interpreter.rb +497 -0
  343. data/lib/hecks/runtime/command_rules/admissibility.rb +357 -0
  344. data/lib/hecks/runtime/command_rules/arithmetic.rb +260 -0
  345. data/lib/hecks/runtime/command_rules/authorization.rb +65 -0
  346. data/lib/hecks/runtime/command_rules/emission.rb +34 -0
  347. data/lib/hecks/runtime/command_rules/references.rb +189 -0
  348. data/lib/hecks/runtime/command_rules.rb +28 -0
  349. data/lib/hecks/runtime/dependency_planning.rb +245 -0
  350. data/lib/hecks/runtime/dispatcher.rb +301 -0
  351. data/lib/hecks/runtime/entity_element.rb +253 -0
  352. data/lib/hecks/runtime/entity_interpreter.rb +299 -0
  353. data/lib/hecks/runtime/errors.rb +122 -0
  354. data/lib/hecks/runtime/event.rb +51 -0
  355. data/lib/hecks/runtime/identity.rb +156 -0
  356. data/lib/hecks/runtime/instance.rb +175 -0
  357. data/lib/hecks/runtime/interpreting.rb +90 -0
  358. data/lib/hecks/runtime/loader.rb +184 -0
  359. data/lib/hecks/runtime/policy_interpreter.rb +366 -0
  360. data/lib/hecks/runtime/port_operation_interpreter.rb +210 -0
  361. data/lib/hecks/runtime/query_interpreter.rb +286 -0
  362. data/lib/hecks/runtime/reaction_invocation.rb +253 -0
  363. data/lib/hecks/runtime/read_model_interpreter.rb +341 -0
  364. data/lib/hecks/runtime/rebuild_sweep.rb +74 -0
  365. data/lib/hecks/runtime/reference_hop.rb +99 -0
  366. data/lib/hecks/runtime/refusal_wording.rb +126 -0
  367. data/lib/hecks/runtime/registry/saga_persistence.rb +142 -0
  368. data/lib/hecks/runtime/registry/verification.rb +246 -0
  369. data/lib/hecks/runtime/registry.rb +279 -0
  370. data/lib/hecks/runtime/remote_dispatcher.rb +143 -0
  371. data/lib/hecks/runtime/routing.rb +96 -0
  372. data/lib/hecks/runtime/saga_interpreter/correlation.rb +97 -0
  373. data/lib/hecks/runtime/saga_interpreter.rb +503 -0
  374. data/lib/hecks/runtime/saga_pending_dispatch.rb +45 -0
  375. data/lib/hecks/runtime/tenant_check.rb +84 -0
  376. data/lib/hecks/runtime/tenant_scope.rb +55 -0
  377. data/lib/hecks/runtime/value/admission.rb +130 -0
  378. data/lib/hecks/runtime/value/coercion.rb +565 -0
  379. data/lib/hecks/runtime/value/invariant_violation.rb +5 -0
  380. data/lib/hecks/runtime/value.rb +125 -0
  381. data/lib/hecks/runtime.rb +107 -0
  382. data/lib/hecks/storehouse.rb +632 -0
  383. data/lib/hecks/version.rb +16 -0
  384. data/lib/hecks/vocabulary.rb +218 -0
  385. data/lib/hecks.rb +123 -6
  386. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +90 -0
  387. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +128 -0
  388. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +160 -0
  389. metadata +412 -222
  390. data/bin/hecks +0 -7
  391. data/bin/hecks-package +0 -65
  392. data/bin/hecks_console +0 -12
  393. data/bin/hecks_serverless +0 -6
  394. data/lib/cli/build.rb +0 -14
  395. data/lib/cli/command_runner.rb +0 -28
  396. data/lib/cli/console.rb +0 -10
  397. data/lib/cli/generate.rb +0 -37
  398. data/lib/cli/hecks-cli.rb +0 -27
  399. data/lib/cli/test.rb +0 -57
  400. data/lib/console/commands.rb +0 -8
  401. data/lib/console/hecks-console.rb +0 -1
  402. data/lib/packager/README.md +0 -0
  403. data/lib/packager/app_runner.rb +0 -21
  404. data/lib/packager/args.rb +0 -26
  405. data/lib/packager/compatibility/fixnum.rb +0 -6
  406. data/lib/packager/hecks.rb +0 -39
  407. data/lib/packager/query_runner.rb +0 -21
  408. data/lib/packager/resources/Dockerfile +0 -11
  409. data/lib/packager/resources/app_binary +0 -7
  410. data/lib/packager/resources/bundle_config +0 -3
  411. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz +0 -0
  412. data/lib/packager/resources/traveling-ruby-20150715-2.2.2-osx.tar.gz +0 -0
  413. data/lib/serverless/Domain +0 -32
  414. data/lib/serverless/cli.rb +0 -75
  415. data/lib/serverless/resources/command_name.js +0 -5
  416. data/lib/serverless/resources/environment.js +0 -7
  417. data/lib/serverless/resources/handler.js.tt +0 -28
  418. data/lib/serverless/resources/run_binary.js +0 -22
  419. data/lib/serverless/resources/serverless.yml +0 -20
@@ -0,0 +1,58 @@
1
+ require_relative "authorization_spec"
2
+ require_relative "comparators"
3
+ require_relative "cursor_spec"
4
+ require_relative "inspection_spec"
5
+ require_relative "limit_spec"
6
+ require_relative "null_semantics"
7
+ require_relative "offset_spec"
8
+ require_relative "order_by"
9
+ require_relative "where_clause"
10
+
11
+ module Hecks
12
+ module QuerySpecification
13
+ module Common
14
+ module DSL
15
+ # RENAMED FROM `where`/`order_by`/`limit`/`offset`/`authorize`
16
+ # (all below) — item #13's full metaprogrammed dispatch (slice
17
+ # 4c). A SHARED mixin, same shape `attribute_impl` proved in
18
+ # slice 3: ONE renamed method each, both Query and ReadModel
19
+ # Keyword rows name it in `calls:`. `where`/`order_by` are
20
+ # bootstrap-reachable (every core chapter's own `read_model`
21
+ # filters its roster with them), so both are in
22
+ # BOOTSTRAP_CALLS_FALLBACK for the ReadModel context; `limit`/
23
+ # `offset`/`authorize` are not (checked directly).
24
+ def where_impl(clauses)
25
+ @wheres ||= []
26
+ clauses.each do |field, value|
27
+ op, operand = split_comparator(value)
28
+ @wheres << WhereClause.new(field: field, op: op, value: operand)
29
+ end
30
+ end
31
+
32
+ def order_by_impl(field, direction = :asc)
33
+ @order_by = OrderBy.new(field: field, direction: direction)
34
+ end
35
+
36
+ def limit_impl(value) = @limit = LimitSpec.new(value: value)
37
+ def offset_impl(value) = @offset = OffsetSpec.new(value: value)
38
+ def cursor(value) = @cursor = CursorSpec.new(value: value)
39
+ def authorize_impl(policy, tenant: nil) = @authorization = AuthorizationSpec.new(policy: policy, tenant: tenant)
40
+ def nulls(mode) = @null_semantics = NullSemantics.new(mode: mode)
41
+ def inspect_query(mode = :sql) = @inspection = InspectionSpec.new(mode: mode)
42
+
43
+ private
44
+
45
+ def split_comparator(value)
46
+ return [:eq, value] unless value.is_a?(Hash)
47
+
48
+ op, operand = value.first
49
+ unless value.size == 1 && COMPARATORS.include?(op.to_sym)
50
+ raise ArgumentError,
51
+ "unknown comparator #{value.inspect} — expected one of #{COMPARATORS.join(', ')}"
52
+ end
53
+ [op.to_sym, operand]
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -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,92 @@
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
+ # M3 — an UNDECLARED (`native`) null policy used to render no
29
+ # `NULLS ...` clause at all here, leaving each dialect's own
30
+ # default to decide: Postgres puts nulls LAST on ASC (and FIRST
31
+ # on DESC), while `#order` above — this same "native" default,
32
+ # for Memory — puts nulls FIRST on ASC (and LAST on DESC), the
33
+ # SQLite convention. Same query, same data, different row order
34
+ # depending only on which adapter ran it. Rendered explicitly
35
+ # here instead, so an undeclared policy means the SAME total
36
+ # order everywhere rather than "whatever this store already does"
37
+ # — matching `#order`'s own default rather than the other way
38
+ # round, since that default is unconditional (Memory/Heki have no
39
+ # dialect to defer to) and SQLite already agrees with it natively.
40
+ def sql_order(expression, direction, policy)
41
+ direction = direction.to_s.downcase == "desc" ? "DESC" : "ASC"
42
+ nulls = case policy&.mode.to_s
43
+ when "first" then " NULLS FIRST"
44
+ when "last" then " NULLS LAST"
45
+ else direction == "DESC" ? " NULLS LAST" : " NULLS FIRST"
46
+ end
47
+ "#{expression} #{direction}#{nulls}, id #{direction}"
48
+ end
49
+
50
+ # THE COMPARATORS A NULL CANNOT SATISFY — the other half of
51
+ # `sql_predicate` below. That one answers "the value COMPARED TO is
52
+ # null" (`eq: nil` -> IS NULL, `ne: nil` -> IS NOT NULL, a real
53
+ # convention both adapters already shared). This one answers the
54
+ # case nothing covered: the ROW'S OWN value is null and the value
55
+ # compared to is not.
56
+ #
57
+ # SQL says unknown. `NULL <> 'red'` is NULL, not true, so the row
58
+ # is not returned. Ruby says `nil != "red"` is true, so it is. The
59
+ # two adapters therefore answered the SAME query on the SAME data
60
+ # differently — Memory returning a row SQLite omitted — which is
61
+ # not a difference of opinion a caller can plan around.
62
+ #
63
+ # Resolved toward SQL, and not because SQL is the store: an absent
64
+ # or null field is UNKNOWN, not a value, and a comparison against
65
+ # unknown is unknown rather than true. Making SQL match Ruby
66
+ # instead would mean compiling every `ne:` to
67
+ # `(col <> $1 OR col IS NULL)` — more to get right in two
68
+ # dialects, and a reliable way to lose an index — to make a real
69
+ # query engine agree with an in-memory one.
70
+ #
71
+ # `none_in_state` is deliberately NOT here: it is a 9th, vendored
72
+ # comparator whose `held` is a reference id, and a row holding no
73
+ # reference is genuinely "not in that state" rather than unknown.
74
+ NULL_UNMATCHABLE = %w[eq ne lt lte gt gte in contains].freeze
75
+
76
+ def unmatchable?(operation, held, want)
77
+ held.nil? && !want.nil? && NULL_UNMATCHABLE.include?(operation.to_s)
78
+ end
79
+
80
+ def sql_predicate(expression, operation, value)
81
+ if value.nil? && operation.to_s == "eq"
82
+ ["#{expression} IS NULL", []]
83
+ elsif value.nil? && operation.to_s == "ne"
84
+ ["#{expression} IS NOT NULL", []]
85
+ else
86
+ nil
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ 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,107 @@
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 — `key?` first, never `||`, because
35
+ # `||` falls through a genuinely-stored `false` to the OTHER
36
+ # spelling (usually absent) and returns `nil` instead. The seal
37
+ # admits boolean leaves (`SCALAR_PRIMITIVES` below), so a `false`
38
+ # here is a real, held answer, not a missing one.
39
+ def dig(holder, field)
40
+ return nil if field.nil?
41
+
42
+ field.to_s.split(".").reduce(holder) { |current, segment| read(current, segment) }
43
+ end
44
+
45
+ def read(current, segment)
46
+ return nil if current.nil?
47
+
48
+ if current.is_a?(Hash)
49
+ sym = segment.to_sym
50
+ return current.key?(sym) ? current[sym] : current[segment]
51
+ end
52
+
53
+ # M5 — "or nil, never raise" is this method's whole contract, and
54
+ # an Array broke it: `Array#[]` demands an Integer index, so
55
+ # `current[segment]` (a String) raised `TypeError` straight
56
+ # through `dig` instead of answering nil. A dotted path stepping
57
+ # INTO a list_of attribute (`where "tags.name" == "x"` against a
58
+ # bare list, rather than each element) has no single member a
59
+ # bare index would name anyway — nil is the honest answer, the
60
+ # same one a dangling reference or a missing key already gets.
61
+ return nil if current.is_a?(Array)
62
+
63
+ current[segment]
64
+ end
65
+
66
+ # The declared attribute a path lands on, or nil. `segments` is the
67
+ # dotted tail — for a bare field it is empty and the root attribute
68
+ # is its own leaf. The walk stops dead at a reference (an id is a
69
+ # scalar; nothing nests under it) and at any member the declared
70
+ # value object does not carry.
71
+ def leaf_attribute(attribute, segments)
72
+ current = attribute
73
+ segments.each do |segment|
74
+ return nil if current.nil? || current.reference?
75
+
76
+ shape = yield(current.type.to_s)
77
+ current = shape&.attributes&.find { |member| member.name.to_s == segment }
78
+ end
79
+ current
80
+ end
81
+
82
+ # May an ordered comparator (lt/lte/gt/gte) land here, and may an
83
+ # ORDER BY cast here numerically? A bare field keeps the one-level
84
+ # convention every adapter already implements: a numeric primitive,
85
+ # or a value object carrying at least one numeric member. A dotted
86
+ # path must land on a numeric primitive itself — the convention does
87
+ # not reach through a named path, it IS the absence of one.
88
+ def numeric?(attribute, segments, &value_object)
89
+ leaf = leaf_attribute(attribute, segments, &value_object)
90
+ return false if leaf.nil? || leaf.list? || leaf.reference?
91
+ return true if NUMERIC_PRIMITIVES.include?(leaf.type.to_s)
92
+ return false unless segments.empty?
93
+
94
+ shape = yield(leaf.type.to_s)
95
+ !shape.nil? && shape.attributes.any? { |member| NUMERIC_PRIMITIVES.include?(member.type.to_s) }
96
+ end
97
+
98
+ # A dotted path must end on a SCALAR member — landing on a value
99
+ # object would hand SQL a JSON object where the reference
100
+ # interpreter unwraps a hash, and the two would answer differently.
101
+ def scalar_leaf?(attribute, segments, &value_object)
102
+ leaf = leaf_attribute(attribute, segments, &value_object)
103
+ !leaf.nil? && !leaf.list? && !leaf.reference? && SCALAR_PRIMITIVES.include?(leaf.type.to_s)
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,132 @@
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 — `BluebookBuilder#validate_no_bidirectional_references!`
11
+ # refuses any reference CYCLE at declaration time (ADR 0025,
12
+ # "References" — widened from a direct pair to any ring, DFS over
13
+ # the chapter's own reference graph), so what's left to guard here
14
+ # is depth, not cycles: MAX_HOPS below, and this module's own
15
+ # `refusal` states for a target the chapter doesn't resolve. A walk
16
+ # here still needs to say WHY it stopped, not just that it did.
17
+ #
18
+ # Every method below takes an ATTRIBUTE ARRAY, never a "shape"
19
+ # object — deliberately, because the two real callers hold their
20
+ # attributes differently. `AggregateBuilder` (tier-1 seal, mid-
21
+ # build) exposes `attributes` as a plain reader, but its own
22
+ # `attribute(name, type = String, ...)` is the DSL's attribute-
23
+ # DECLARING method — calling it as a finder would silently mint a
24
+ # new String attribute instead of looking one up. A sealed
25
+ # `Aggregate` (tier-2 seal, and every runtime target) has a
26
+ # real `attribute(name)` finder, but taking the array either way
27
+ # sidesteps the mismatch instead of asking every caller to know
28
+ # which kind of object it holds.
29
+ module HopPath
30
+ module_function
31
+
32
+ Hop = Struct.new(:attribute, :target_name, :target, keyword_init: true)
33
+
34
+ # refusal: nil (clean), :unresolvable (a hop's target cannot be
35
+ # found — cross-domain, or simply not declared in this chapter),
36
+ # :too_deep (see MAX_HOPS below).
37
+ Plan = Struct.new(:hops, :tail, :refusal, keyword_init: true)
38
+
39
+ # A hop chain long enough to matter is long enough to be a
40
+ # mistake — NOT a guard against a walk that cannot terminate.
41
+ # Nothing here loops forever regardless of how the reference
42
+ # graph is shaped: a hop chain is a literal dotted string, fixed
43
+ # at declaration time, and every step consumes exactly one of
44
+ # its own segments — the walk is bounded by what was TYPED, not
45
+ # by the graph. A self-referential aggregate hopping through
46
+ # itself more than once (`"parent.parent.name"`, a grandparent
47
+ # query) is real, common, and perfectly safe; this exists only
48
+ # to refuse a chain nobody meant to write this long.
49
+ MAX_HOPS = 8
50
+
51
+ # The segment name a Reference answers to in a hop path — its own
52
+ # declared attribute name, unchanged (ADR 0025, "References":
53
+ # `reference_to` mints that bare name now, no `_id`, so there is
54
+ # no derivation left to apply). `proposal/client` (a query hop)
55
+ # and `proposal.client` (the Ruby accessor,
56
+ # `Facade::Handle#define_reference_accessors`) name the same
57
+ # concept the same way.
58
+ def hop_name(attribute) = attribute.name.to_s
59
+
60
+ # Does this path's HEAD cross into another record via `/`? THE
61
+ # OPERATOR IS THE ANSWER NOW, not a name collision to arbitrate —
62
+ # `.` walks fields inside this record, `/` crosses into another
63
+ # one, so a path with no `/` is never a hop, full stop, and the
64
+ # OLD "a real local attribute wins first" rule (needed only
65
+ # because `.` was overloaded for both meanings, and `client_id`
66
+ # vs `client` was how the two were told apart) has nothing left
67
+ # to arbitrate. Answerable from `attributes` alone — a Reference
68
+ # knows its own `target_name` at declaration, before it can
69
+ # `resolve` it — which is what lets the AGGREGATE seal recognise
70
+ # a hop it cannot yet check.
71
+ def hop_head?(field, attributes)
72
+ head, rest = field.to_s.split("/", 2)
73
+ return false unless rest
74
+
75
+ attributes.any? { |candidate| candidate.reference? && hop_name(candidate) == head }
76
+ end
77
+
78
+ # ONE STEP: does `field`'s head hop through one of `attributes`'
79
+ # own references? Answers the resolved `Hop` plus the string
80
+ # still left to walk (itself possibly another `/`-hop, against
81
+ # the TARGET's own attributes, or a plain `.`-dotted field walk
82
+ # once the hops run out) — or nil, when the head names nothing or
83
+ # the path has no `/` at all. This is the one primitive
84
+ # Runtime::ReferenceHop needs: it recurses hop by hop through its
85
+ # own `apply`, one ordinary same-aggregate query at a time, and
86
+ # never needs the whole chain resolved up front the way a seal
87
+ # does.
88
+ def next_hop(field, attributes)
89
+ head, rest = field.to_s.split("/", 2)
90
+ return nil unless rest
91
+
92
+ attribute = attributes.find { |candidate| candidate.reference? && hop_name(candidate) == head }
93
+ return nil unless attribute
94
+
95
+ hop = Hop.new(attribute: attribute, target_name: attribute.type.target_name, target: attribute.type.resolve)
96
+ [hop, rest]
97
+ end
98
+
99
+ # The WHOLE chain, resolved — every hop's target found, in
100
+ # order — for the one caller that needs it all at once:
101
+ # BluebookBuilder#validate_query_hops!, checking a hop chain
102
+ # before anything ever dispatches it.
103
+ def plan(field, attributes)
104
+ hops = []
105
+ remaining = field.to_s
106
+ current = attributes
107
+
108
+ loop do
109
+ step = next_hop(remaining, current)
110
+ break unless step
111
+
112
+ hop, rest = step
113
+ return Plan.new(hops: hops, tail: nil, refusal: :too_deep) if hops.size >= MAX_HOPS
114
+
115
+ # Pushed even unresolved — a caller reporting :unresolvable
116
+ # needs THIS hop's own target_name (real, known at
117
+ # declaration, regardless of whether resolve succeeded), not
118
+ # whatever hop came before it. `.target` is nil on this one
119
+ # entry; every caller checking `hops.last.target` already has
120
+ # to handle that, the same way `next_hop`'s own caller does.
121
+ hops << hop
122
+ return Plan.new(hops: hops, tail: nil, refusal: :unresolvable) unless hop.target
123
+
124
+ current = hop.target.attributes
125
+ remaining = rest
126
+ end
127
+
128
+ Plan.new(hops: hops, tail: remaining, refusal: nil)
129
+ end
130
+ end
131
+ end
132
+ 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