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,360 @@
1
+ module Hecks
2
+ module Bluebook
3
+ module MetaValidator
4
+ # The parts of a bluebook the walk cannot read by name alone.
5
+ #
6
+ # The language now spells its fields exactly as the IR spells them, so the
7
+ # judge reads almost everything straight through: `command.givens`,
8
+ # `value_object.invariants`, `read_model.aggregate_heads`. What remains here
9
+ # is not naming drift — it is places where the IR's SHAPE differs from the
10
+ # language's, and no amount of renaming would close that:
11
+ #
12
+ # transitions one declaration expands to SEVERAL rows, because `from`
13
+ # may be a list of states
14
+ # value_objects the IR holds objects ; the language holds their names
15
+ # normalisations not on the bluebook at all — they come from the canonical
16
+ # form table the expression grammar keeps
17
+ # members plain hashes, one Member root per row, pairs per entry
18
+ # lifecycle one IR object feeding two separate fields
19
+ #
20
+ # Everything in this file is a difference in shape. If something here is
21
+ # only a difference in NAME, it is in the wrong file: rename the language.
22
+ module Readings
23
+ # A list the walk is about to offer, as rows it can shape into dispatches.
24
+ #
25
+ # FROM THE TABLE. This was nine hand-written cases keyed "Category.list", and
26
+ # every one of them was a fact `Assembly::Contracts` is the right place to
27
+ # keep: which shaper turns this list into rows. A list with no shaper reads
28
+ # straight off the node, which is most of them.
29
+ def rows_for(category, list_name, node)
30
+ shaper = Assembly.contract(category).shaper(list_name)
31
+ return Array(node.public_send(list_name)) unless shaper
32
+
33
+ public_send(shaper, node)
34
+ end
35
+
36
+ # A where-clause is read through its own to_h, which is where the IR spells a
37
+ # symbol argument as ":ceiling". Reading the OBJECT instead lost the colon,
38
+ # and nothing downstream could tell an argument from a literal of the same
39
+ # name.
40
+ def where_rows(node) = Array(node.wheres).map(&:to_h)
41
+
42
+ # The language holds a value object's NAME here ; the IR holds the object.
43
+ def value_object_names(node) = node.value_objects.map { |shape| { name: shape.hecks_name } }
44
+
45
+ # AN IDENTITY IS A LIST OF PARTS, so it is offered one part at a time — the
46
+ # same way attributes and transitions are. The IR holds the paths ; the
47
+ # language holds a row per path, and the ORDER between them is the whole
48
+ # meaning, because the identity is their join.
49
+ def identity_rows(node) = node.identity_paths.map { |path| { value: path } }
50
+
51
+ # Through to_h, which is where Bluebook.render_value spells a symbol argument as
52
+ # ":source". The raw with_spec lost the colon, and a binding that reads an
53
+ # argument became indistinguishable from one carrying a literal string.
54
+ def with_spec_rows(node) = pair_rows(node.to_h[:with_spec])
55
+
56
+ # THE SAME READ, ONE LEVEL IN — `compensates` folds `DispatchSpec`
57
+ # into the language's own `compensates_command_name`/
58
+ # `compensates_with_spec` (`Assembly::Contracts`' own comment on
59
+ # "Dispatch"), so its OWN with_spec pairs live nested one hash
60
+ # down from where `with_spec_rows` looks. `&.dig(...)` — no
61
+ # compensation at all is not an error, it is `pair_rows(nil)`, empty.
62
+ def compensates_with_spec_rows(node) = pair_rows(node.to_h[:compensates]&.dig(:with_spec))
63
+
64
+ # A read model carries the same options an ask does, plus its filters — see
65
+ # option_rows.
66
+ def read_model_option_rows(node) = option_rows(node, filters: true)
67
+
68
+ # The canonical-form table is the expression grammar's, not this chapter's, so
69
+ # the node is not consulted at all.
70
+ def normalisation_table(_node) = normalisation_rows
71
+
72
+ # `lifecycle :status do transition "Retire" => "retired", from: ["issued", "active"] end`
73
+ # is ONE declaration and TWO transitions. Offering it once would leave the
74
+ # second unjudged, which is the whole failure this judge exists to avoid.
75
+ def transition_rows(node)
76
+ lifecycle = node.respond_to?(:lifecycle) ? node.lifecycle : nil
77
+ return [] unless lifecycle
78
+
79
+ lifecycle.transitions.flat_map do |command, transition|
80
+ froms = transition.constrained? ? Array(transition.from) : [nil]
81
+ froms.map do |from|
82
+ { command: command, from_state: from, to_state: transition.target }
83
+ end
84
+ end
85
+ end
86
+
87
+ # An OPEN MAP — a member's fields, a dispatch's argument bindings — has no
88
+ # value object that can hold it, so each entry becomes its own row. This is
89
+ # why Member and Dispatch are roots in the language rather than lists.
90
+ def pair_rows(map)
91
+ Array(map&.to_h).map { |key, value| { key: key, value: value } }
92
+ end
93
+
94
+ # EVERY SPECIFICATION OPTION AN ASK CARRIES, flattened to rows.
95
+ #
96
+ # `offset`, `cursor`, `nulls`, `authorize` and `inspect_query` are
97
+ # five options, one compound (authorize names a policy AND a
98
+ # tenant). `extra_options_to_h` already spells every one of them
99
+ # and drops the absent ones, so this reads that rather than
100
+ # naming them here — a sixth option needs no change on either
101
+ # side.
102
+ #
103
+ # `filters: true` adds a read model's wheres, order_by and limit —
104
+ # `at` tells repeated rows apart, so two wheres do not collapse.
105
+ #
106
+ # THE LANGUAGE MAY HOLD MORE THAN `to_h` CARRIES, and this is where that
107
+ # mattered. Until 2026-08-11, `ReadModel#to_h` omitted all three —
108
+ # `extra_options_to_h` rejects them by name, still does — so a read
109
+ # model's filtering had never been in the wire contract, and I first
110
+ # read that as a wall: if the wire cannot carry it, the language cannot
111
+ # hold it, and a graph assembled from the language must lose it.
112
+ #
113
+ # That was the wrong conclusion. `to_h` is a PROJECTION ; the language
114
+ # is the SOURCE. They have to agree about everything
115
+ # to_h spells, not about everything the language knows. Held as option
116
+ # rows, the filters survived the round trip regardless of whether the
117
+ # wire carried them too — which is exactly why, when a LATER task
118
+ # (Rust read-model codegen) needed `wheres`/`order_by`/`limit` on the
119
+ # wire for an unrelated reason, `ReadModel#to_h` could be extended to
120
+ # spell them (the same mechanism `Query#to_h` already used) without
121
+ # touching this method at all: this reads `node.wheres`/`node.
122
+ # order_by`/`node.limit` off the live object directly below
123
+ # (`filter_options`), never off `to_h`, so the wire format moving did
124
+ # not move this.
125
+ #
126
+ # Named `wheres`, `order_by` and `limit` so they gather back into exactly the
127
+ # declaration keys the assembly already reads.
128
+ def option_rows(node, filters: false)
129
+ return [] unless node.respond_to?(:extra_options_to_h)
130
+
131
+ spelled = node.extra_options_to_h
132
+ spelled = filter_options(node).merge(spelled) if filters
133
+
134
+ spelled.flat_map do |option, held|
135
+ case held
136
+ when Array then held.each_with_index.flat_map { |one, at| parts(option, one, at) }
137
+ else parts(option, held, nil)
138
+ end
139
+ end
140
+ end
141
+
142
+ def filter_options(node)
143
+ {
144
+ wheres: Array(node.wheres).map(&:to_h),
145
+ order_by: node.order_by&.to_h,
146
+ limit: node.limit&.to_h
147
+ }.reject { |_, held| held.nil? || held == [] }
148
+ end
149
+
150
+ def parts(option, held, at)
151
+ Hash(held).map do |key, value|
152
+ { option: option.to_s, key: key.to_s, value: value, at: at&.to_s }
153
+ end
154
+ end
155
+
156
+ # A mutation is ONE declaration, but the language's Change holds a single
157
+ # field/kind/source triple — and an append binds SEVERAL fields at once
158
+ # (`append: { name: :name, amount: :amount }`). So an append is offered
159
+ # once per binding, and each one is judged.
160
+ #
161
+ # The judge used to send `field: v(""), kind: v("argument"), source: v("")`
162
+ # here — three stubbed values, so every rule about what a mutation reads
163
+ # was being handed a blank and could never refuse.
164
+ def mutation_rows(node)
165
+ Array(node.mutations).flat_map do |mutation|
166
+ # `:delegate`/`:corrects` (CommandBuilder#delegates_to's and
167
+ # #corrects_impl's own comments) ride the SAME multi-binding
168
+ # shape `:append` does — `with: {...}`/the assembled
169
+ # `as:`/`reason:`/`reverses:` hash is a field map, same as
170
+ # append's own `fields:`.
171
+ next set_row(mutation) unless [:append, :delegate, :corrects].include?(mutation.op)
172
+
173
+ mutation.source.map do |field, argument|
174
+ # Spelled the way Mutation#appended_fields spells it, because
175
+ # Assembly::Marks reads this row back through the same reader it
176
+ # reads that field with. `then_set :marks, append: { direction:
177
+ # "out" }` binds a LITERAL, and storing it raw made it
178
+ # indistinguishable from an argument called out.
179
+ { target: mutation.target, op: mutation.op, field: field,
180
+ kind: argument.is_a?(Symbol) ? "argument" : "literal",
181
+ source: Literal.render(argument) }
182
+ end
183
+ end
184
+ end
185
+
186
+ # A set/increment/decrement reads one thing: a command argument, or a
187
+ # literal written into the bluebook.
188
+ def set_row(mutation)
189
+ classified = mutation.to_h[:source] || {}
190
+
191
+ [{ target: mutation.target, op: mutation.op, field: mutation.target,
192
+ kind: classified[:kind],
193
+ source: classified[:name] || encode_literal(classified[:value]) }]
194
+ end
195
+
196
+ # The normalisation table belongs to the expression grammar, not to any one
197
+ # bluebook — it is how the canonical form of a rule is spelled. The language
198
+ # models it because a bluebook's rules are canonicalised on the way in.
199
+ def normalisation_rows
200
+ table = Expression::CanonicalForm.table
201
+ return [] unless table
202
+
203
+ table.map do |entry|
204
+ {
205
+ strategy: entry[:strategy],
206
+ source_token: entry[:source_token],
207
+ replacement: entry[:replacement],
208
+ boundary: entry[:boundary],
209
+ position: entry[:position]
210
+ }
211
+ end
212
+ rescue StandardError
213
+ # The table is a convenience of the Ruby side ; a bluebook that cannot
214
+ # produce one is not malformed.
215
+ []
216
+ end
217
+
218
+ # What the BLUEBOOK calls a node, whichever kind of thing the node is.
219
+ #
220
+ # This used to sniff — `respond_to?(:hecks_name) ? … : node.name` — because
221
+ # only value objects had crossed over. Every construct answers now, so
222
+ # there is nothing to choose between. It disappears entirely when the DSL
223
+ # stops handing the judge nodes at all.
224
+ def declared_name(node) = node.hecks_name
225
+
226
+ # One field of a Declare payload. Mostly a reader of the same name — the
227
+ # exceptions are fields the IR keeps somewhere else, or not at all.
228
+ # READ FROM THE TABLE, not from a branch per category.
229
+ #
230
+ # These were eight hand-written cases — `Entity.owner`, `Member.shape`, two
231
+ # lifecycle members twice over, and three of a query's — each one restating
232
+ # something `Assembly::Contracts` already declares. A parent pointer is
233
+ # `:parent` there ; a folded field names the object and member it lives in.
234
+ # So the exceptions are looked up rather than repeated, and a new fold is one
235
+ # line in one file instead of two lines in two.
236
+ def field_value(category, node, field, parent_id)
237
+ return declared_name(node) if field == :name
238
+
239
+ contract = Assembly.contract(category)
240
+ # A setter names its target as a STRING and a Declare field arrives a Symbol,
241
+ # so the lookup keys on a Symbol either way. The case statement this replaced
242
+ # was type-blind because it interpolated ; a Hash is not.
243
+ named = field.to_sym
244
+ return parent_id if contract.kind_of(named) == :parent
245
+
246
+ object, member = contract.folded(named)
247
+ return through(node, object, member) if member
248
+
249
+ # `limit` is a language field AND an object in the IR — `Array(an_object)`
250
+ # wraps rather than destructures, so offering it stored
251
+ # "#<struct LimitSpec value=3>".
252
+ return node.limit&.to_h&.fetch(:value, nil) if "#{category}.#{field}" == "Query.limit"
253
+
254
+ # `provenance from: {...}` is a HASH offered into a text field, and
255
+ # handing it over raw let the runtime's own coercion spell it — which
256
+ # meant Ruby's `Hash#to_s`, whose spelling changed under us between
257
+ # 3.3 and 3.4. Encoded here, the same way `default:` already is and the
258
+ # same way Shapes#provenance reads it back.
259
+ return encode_literal(node.provenance) if field == :provenance
260
+
261
+ # `identified_by` is no longer a FIELD of any declaration — it is a list,
262
+ # filled by Identify one part at a time, so it is read through `identity_rows`
263
+ # like every other list rather than special-cased here. What this branch
264
+ # existed to protect is now structural : a path cannot come back as its head,
265
+ # because there is nowhere left that holds only a head.
266
+
267
+ node.respond_to?(field) ? node.public_send(field) : nil
268
+ end
269
+
270
+ # One member of the object a field folds into. `to_h` first, because the
271
+ # member names are the ones the IR SPELLS — a Lifecycle's `default`, an
272
+ # OrderBy's `direction` — and reading the object raw is how a colon or a type
273
+ # goes missing.
274
+ def through(node, object, member)
275
+ held = node.respond_to?(object) ? node.public_send(object) : nil
276
+ return nil unless held
277
+
278
+ member == :transitions ? held : held.to_h[member]
279
+ end
280
+
281
+ # What a setting command writes. A setter whose source is absent is not
282
+ # dispatched at all — ABSENT is not EMPTY, and offering "" would turn every
283
+ # "if you declare it, declare something" rule into "you must declare it".
284
+ def setter_value(category, node, target)
285
+ # `rows` folds into `closed_set` and `members` between them, with no single
286
+ # member to name, so it keeps its own reading — see Contract#folded.
287
+ return closed_set_size(node) if "#{category}.#{target}" == "ValueObject.rows"
288
+
289
+ object, member = Assembly.contract(category).folded(target.to_sym)
290
+ return through(node, object, member) if member
291
+
292
+ node.respond_to?(target) ? node.public_send(target) : nil
293
+ end
294
+
295
+ # Only a DECLARED closed set has a row count. An empty one is the defect,
296
+ # so `rows` must stay absent rather than arrive as zero.
297
+ def closed_set_size(node)
298
+ return nil unless node.respond_to?(:closed_set?) && node.closed_set?
299
+
300
+ Array(node.members).size
301
+ end
302
+
303
+ # `Reference<Customer>` is an IR ENCODING, not a domain fact. The fact is
304
+ # that the attribute points at Customer's head — so the language is offered
305
+ # that head's ID, and resolution does the rest. Encoding and decoding both
306
+ # live here, because this is where the IR's shape differs from the
307
+ # language's and nowhere else should know the spelling.
308
+ def points_at(row, aggregate_id)
309
+ return nil unless row.reference?
310
+
311
+ # THE CHAPTER THIS HEAD IS IN, AND THE HEAD IT POINTS AT — which is exactly
312
+ # how an aggregate is identified, so it is built the same way rather than
313
+ # spelled again with a separator of its own. This is dispatched as a REAL
314
+ # REFERENCE VALUE (`Aggregate.Reference`'s `points_at:`), resolved by
315
+ # `repository.find` against the target Aggregate-within-Meta record's OWN
316
+ # stored id — so it MUST equal what that record's identity actually
317
+ # derives, not a wire-format spelling. `reference_type`, below, is the
318
+ # separate later reader that un-derives it back into "Reference<X>".
319
+ Naming.identity([aggregate_id.split(Naming::IDENTITY_JOIN).first, row.type.target_name])
320
+ end
321
+
322
+ # A LITERAL, written so it can be read back exactly.
323
+ #
324
+ # The language holds a default and a literal mutation source as text, and
325
+ # `to_s` threw the type away: 0.0 came back "0.0", and `{ value: "good" }`
326
+ # came back its inspect string with nowhere to say it had been a hash. The
327
+ # language already stores code as text — `canonical: "cents >= 0"` — so an
328
+ # encoding is in keeping; it simply has to be SELF-DESCRIBING. That rule is
329
+ # now Hecks::Literal's, stated once and shared with every other
330
+ # to_h-bound literal field ; Shapes#decode_literal reads it back.
331
+ #
332
+ # nil stays nil rather than becoming "nil": absent is a real answer here,
333
+ # and the language's own field is optional.
334
+ def encode_literal(value) = value.nil? ? nil : Literal.render(value)
335
+
336
+ # The way back out: an aggregate id becomes the type the IR spells. The
337
+ # id is a JOIN of chapter + name (Naming::IDENTITY_JOIN, the same join
338
+ # `points_at` built it with, not the "::" a real bluebook's own type
339
+ # names never carry) ; the wire format wants only the bare name.
340
+ def reference_type(points_at_id) = "Reference<#{points_at_id.to_s.split(Naming::IDENTITY_JOIN).last}>"
341
+
342
+ # One value out of a row, named by the value object's field.
343
+ def row_value(row, field)
344
+ # A Hash FIRST. Hash answers to `key` (Hash#key(value)) and to `value` on
345
+ # some rows, so asking respond_to? before checking for a Hash reads a
346
+ # member pair through entirely the wrong method.
347
+ return row[field] if row.is_a?(Hash)
348
+ return row.public_send(field) if row.respond_to?(field)
349
+ # A Struct answers to [] but RAISES for a member it does not have, so it
350
+ # is read through to_h — a field the row simply lacks reads as absent.
351
+ return row.to_h[field] if row.respond_to?(:to_h) && !row.is_a?(String)
352
+
353
+ # A bare scalar row — `emits` is a list of event NAMES, and the
354
+ # Announcement value object has to call that string something.
355
+ row
356
+ end
357
+ end
358
+ end
359
+ end
360
+ end