hecks 2.8.0 → 2.9.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 (404) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +4 -6
  3. data/lib/hecks/adapters/driven/claude_code.rb +4 -104
  4. data/lib/hecks/adapters/driven/d1.rb +44 -237
  5. data/lib/hecks/adapters/driven/folder.rb +17 -76
  6. data/lib/hecks/adapters/driven/google_authentication.rb +15 -49
  7. data/lib/hecks/adapters/driven/governance_authorization.rb +23 -70
  8. data/lib/hecks/adapters/driven/heki/journal.rb +8 -41
  9. data/lib/hecks/adapters/driven/heki/saga_store.rb +12 -54
  10. data/lib/hecks/adapters/driven/heki/snapshot.rb +4 -12
  11. data/lib/hecks/adapters/driven/heki.rb +13 -42
  12. data/lib/hecks/adapters/driven/identity_registry.rb +3 -14
  13. data/lib/hecks/adapters/driven/in_memory_ordering.rb +9 -26
  14. data/lib/hecks/adapters/driven/in_process_key_vault.rb +2 -14
  15. data/lib/hecks/adapters/driven/lambda/client.rb +10 -51
  16. data/lib/hecks/adapters/driven/lambda.rb +10 -84
  17. data/lib/hecks/adapters/driven/local_storage.rb +5 -40
  18. data/lib/hecks/adapters/driven/memory.rb +17 -98
  19. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +5 -21
  20. data/lib/hecks/adapters/driven/postgres/codec.rb +6 -28
  21. data/lib/hecks/adapters/driven/postgres/outbox.rb +3 -12
  22. data/lib/hecks/adapters/driven/postgres/reconnect.rb +4 -30
  23. data/lib/hecks/adapters/driven/postgres/schema_builder.rb +34 -108
  24. data/lib/hecks/adapters/driven/postgres.rb +145 -134
  25. data/lib/hecks/adapters/driven/postgres_era.adapter +4 -15
  26. data/lib/hecks/adapters/driven/prism.rb +7 -30
  27. data/lib/hecks/adapters/driven/sql_query_builder.rb +21 -121
  28. data/lib/hecks/adapters/driven/sqlite/codec.rb +8 -40
  29. data/lib/hecks/adapters/driven/sqlite/projection.rb +26 -82
  30. data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +40 -84
  31. data/lib/hecks/adapters/driven/sqlite.rb +128 -65
  32. data/lib/hecks/adapters/driven/tenant_provisioner.rb +5 -48
  33. data/lib/hecks/adapters/driven.rb +5 -36
  34. data/lib/hecks/adapters/driving/github_webhook.rb +8 -76
  35. data/lib/hecks/behaviors/dsl.rb +7 -25
  36. data/lib/hecks/behaviors/expectations.rb +31 -141
  37. data/lib/hecks/behaviors/ir.rb +7 -20
  38. data/lib/hecks/behaviors/rspec.rb +6 -17
  39. data/lib/hecks/behaviors/runner.rb +10 -34
  40. data/lib/hecks/behaviors.rb +4 -8
  41. data/lib/hecks/bluebook/aggregate.rb +9 -75
  42. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +9 -38
  43. data/lib/hecks/bluebook/assembly/build.rb +4 -15
  44. data/lib/hecks/bluebook/assembly/contract.rb +15 -136
  45. data/lib/hecks/bluebook/assembly/contracts.rb +54 -174
  46. data/lib/hecks/bluebook/assembly/marks.rb +31 -223
  47. data/lib/hecks/bluebook/assembly/specializer.rb +6 -52
  48. data/lib/hecks/bluebook/assembly.rb +6 -45
  49. data/lib/hecks/bluebook/attribute.rb +12 -61
  50. data/lib/hecks/bluebook/behaviour/aggregate.rb +10 -39
  51. data/lib/hecks/bluebook/behaviour/attribute.rb +3 -14
  52. data/lib/hecks/bluebook/behaviour/chapter.rb +14 -42
  53. data/lib/hecks/bluebook/behaviour/command.rb +14 -66
  54. data/lib/hecks/bluebook/behaviour/domain_port.rb +4 -20
  55. data/lib/hecks/bluebook/behaviour/entity.rb +7 -27
  56. data/lib/hecks/bluebook/behaviour/hexagon.rb +23 -41
  57. data/lib/hecks/bluebook/behaviour/lifecycle.rb +15 -40
  58. data/lib/hecks/bluebook/behaviour/policy.rb +23 -52
  59. data/lib/hecks/bluebook/behaviour/process_manager.rb +30 -57
  60. data/lib/hecks/bluebook/behaviour/read_model.rb +25 -31
  61. data/lib/hecks/bluebook/behaviour/traits.rb +27 -64
  62. data/lib/hecks/bluebook/behaviour/value_object.rb +5 -24
  63. data/lib/hecks/bluebook/capabilities.rb +17 -49
  64. data/lib/hecks/bluebook/chapter.rb +10 -48
  65. data/lib/hecks/bluebook/command.rb +23 -81
  66. data/lib/hecks/bluebook/domain_port.rb +14 -63
  67. data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +50 -229
  68. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +50 -377
  69. data/lib/hecks/bluebook/dsl/attribute_collector.rb +33 -204
  70. data/lib/hecks/bluebook/dsl/binding_proxy.rb +16 -69
  71. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +86 -708
  72. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +56 -170
  73. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
  74. data/lib/hecks/bluebook/dsl/command_builder.rb +74 -546
  75. data/lib/hecks/bluebook/dsl/const_shim.rb +8 -41
  76. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +12 -87
  77. data/lib/hecks/bluebook/dsl/entity_builder.rb +44 -330
  78. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +20 -269
  79. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +26 -194
  80. data/lib/hecks/bluebook/dsl/identity_declaration.rb +13 -88
  81. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +6 -20
  82. data/lib/hecks/bluebook/dsl/policy_builder.rb +16 -88
  83. data/lib/hecks/bluebook/dsl/port_builder.rb +2 -6
  84. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +22 -96
  85. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +31 -184
  86. data/lib/hecks/bluebook/dsl/query_builder.rb +11 -42
  87. data/lib/hecks/bluebook/dsl/read_model_builder.rb +75 -201
  88. data/lib/hecks/bluebook/dsl/rule_reference.rb +22 -163
  89. data/lib/hecks/bluebook/dsl/translation_builder.rb +28 -114
  90. data/lib/hecks/bluebook/dsl/value_object_builder.rb +10 -124
  91. data/lib/hecks/bluebook/dsl/word_gate.rb +16 -162
  92. data/lib/hecks/bluebook/dsl/world_builder.rb +19 -111
  93. data/lib/hecks/bluebook/entity.rb +13 -71
  94. data/lib/hecks/bluebook/expression/ast_json.rb +24 -190
  95. data/lib/hecks/bluebook/expression/ast_reader.rb +12 -54
  96. data/lib/hecks/bluebook/expression/canonical_form.rb +11 -88
  97. data/lib/hecks/bluebook/expression/evaluator.rb +47 -295
  98. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +30 -147
  99. data/lib/hecks/bluebook/expression/resolver.rb +37 -660
  100. data/lib/hecks/bluebook/hexagon.rb +5 -28
  101. data/lib/hecks/bluebook/lifecycle.rb +5 -16
  102. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -7
  103. data/lib/hecks/bluebook/meta_validator/judge.rb +111 -361
  104. data/lib/hecks/bluebook/meta_validator/plan.rb +33 -162
  105. data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -7
  106. data/lib/hecks/bluebook/meta_validator/readings.rb +56 -342
  107. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -176
  108. data/lib/hecks/bluebook/meta_validator/shapes.rb +53 -267
  109. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +43 -305
  110. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +7 -43
  111. data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -18
  112. data/lib/hecks/bluebook/meta_validator.rb +79 -349
  113. data/lib/hecks/bluebook/model_check/client_profile.rb +6 -21
  114. data/lib/hecks/bluebook/model_check.rb +100 -597
  115. data/lib/hecks/bluebook/pattern_subset.rb +11 -69
  116. data/lib/hecks/bluebook/process_manager.rb +13 -83
  117. data/lib/hecks/bluebook/project_loader.rb +3 -39
  118. data/lib/hecks/bluebook/project_register.rb +12 -60
  119. data/lib/hecks/bluebook/query.rb +9 -48
  120. data/lib/hecks/bluebook/read_model.rb +57 -81
  121. data/lib/hecks/bluebook/reference.rb +7 -41
  122. data/lib/hecks/bluebook/smoke_test.rb +8 -98
  123. data/lib/hecks/bluebook/synthesizer.rb +13 -62
  124. data/lib/hecks/bluebook/translation.rb +26 -62
  125. data/lib/hecks/bluebook/value_object.rb +11 -36
  126. data/lib/hecks/bluebook.rb +5 -22
  127. data/lib/hecks/cache_dir.rb +4 -23
  128. data/lib/hecks/cli/document.rb +3 -21
  129. data/lib/hecks/cli/ir.rb +3 -5
  130. data/lib/hecks/cli/mcp.rb +2 -3
  131. data/lib/hecks/cli/mcp_door.rb +19 -114
  132. data/lib/hecks/cli/model_check.rb +18 -85
  133. data/lib/hecks/cli/project_cli.rb +7 -40
  134. data/lib/hecks/cli/project_diagrams.rb +0 -7
  135. data/lib/hecks/cli/run.rb +12 -58
  136. data/lib/hecks/cli/smoke_test.rb +2 -16
  137. data/lib/hecks/cli/stores.rb +1 -2
  138. data/lib/hecks/cli.rb +18 -24
  139. data/lib/hecks/construct.rb +8 -34
  140. data/lib/hecks/deploy/bluebook/deploy.bluebook +33 -142
  141. data/lib/hecks/deploy/bluebook/deploy.hecksagon +2 -9
  142. data/lib/hecks/embryonaut_bluebook/lock.rb +3 -35
  143. data/lib/hecks/embryonaut_bluebook/shape.rb +1 -17
  144. data/lib/hecks/embryonaut_bluebook/vendor.rb +8 -53
  145. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +4 -19
  146. data/lib/hecks/embryonaut_bluebook.rb +4 -71
  147. data/lib/hecks/facade/cli_door.rb +24 -106
  148. data/lib/hecks/facade/cli_runner.rb +28 -172
  149. data/lib/hecks/facade/command_request.rb +13 -33
  150. data/lib/hecks/facade/handle.rb +34 -121
  151. data/lib/hecks/facade/json_door.rb +31 -146
  152. data/lib/hecks/facade/surface/aggregate_door.rb +39 -104
  153. data/lib/hecks/facade/surface/chapter.rb +10 -54
  154. data/lib/hecks/facade/surface.rb +8 -23
  155. data/lib/hecks/facade.rb +10 -25
  156. data/lib/hecks/forms/app.rb +15 -70
  157. data/lib/hecks/forms/command_form_renderer.rb +10 -29
  158. data/lib/hecks/forms/field_renderer.rb +17 -156
  159. data/lib/hecks/forms/field_shape.rb +30 -222
  160. data/lib/hecks/forms/html.rb +12 -47
  161. data/lib/hecks/forms/index_renderer.rb +1 -4
  162. data/lib/hecks/forms/page.rb +3 -9
  163. data/lib/hecks/forms/params.rb +14 -60
  164. data/lib/hecks/forms/port_argument.rb +2 -19
  165. data/lib/hecks/forms/query_form_renderer.rb +4 -79
  166. data/lib/hecks/forms/record_renderer.rb +8 -75
  167. data/lib/hecks/forms/record_table.rb +7 -50
  168. data/lib/hecks/forms/reference_options.rb +3 -30
  169. data/lib/hecks/forms/value_object_shape.rb +8 -35
  170. data/lib/hecks/forms.rb +5 -33
  171. data/lib/hecks/fqn.rb +5 -12
  172. data/lib/hecks/framework/bluebook/compliance.bluebook +16 -65
  173. data/lib/hecks/framework/bluebook/console_settings.bluebook +47 -134
  174. data/lib/hecks/framework/bluebook/framework.hecksagon +5 -14
  175. data/lib/hecks/framework/bluebook/governance.bluebook +13 -34
  176. data/lib/hecks/framework/bluebook/identity.bluebook +11 -26
  177. data/lib/hecks/framework/bluebook/privacy.bluebook +8 -33
  178. data/lib/hecks/framework.rb +11 -80
  179. data/lib/hecks/freezer.rb +5 -39
  180. data/lib/hecks/grammar/expression.bluebook +15 -40
  181. data/lib/hecks/grammar/translation.bluebook +3 -6
  182. data/lib/hecks/grammar.rb +16 -41
  183. data/lib/hecks/ir.rb +14 -83
  184. data/lib/hecks/language/adapter.bluebook +2 -7
  185. data/lib/hecks/language/bluebook/aggregate.bluebook +56 -221
  186. data/lib/hecks/language/bluebook/attaches/paging.bluebook +6 -15
  187. data/lib/hecks/language/bluebook/bluebook.bluebook +15 -83
  188. data/lib/hecks/language/bluebook/bluebook.hecksagon +3 -15
  189. data/lib/hecks/language/bluebook/command.bluebook +27 -144
  190. data/lib/hecks/language/bluebook/entity.bluebook +24 -97
  191. data/lib/hecks/language/bluebook/policy.bluebook +16 -67
  192. data/lib/hecks/language/bluebook/process_manager.bluebook +28 -151
  193. data/lib/hecks/language/bluebook/projection.bluebook +126 -60
  194. data/lib/hecks/language/bluebook/query.bluebook +12 -43
  195. data/lib/hecks/language/bluebook/shape.bluebook +23 -73
  196. data/lib/hecks/language/bluebook/syntax.bluebook +51 -262
  197. data/lib/hecks/language/bluebook/vocabulary.bluebook +82 -197
  198. data/lib/hecks/language/hecksagon/adapter_binding.bluebook +3 -10
  199. data/lib/hecks/language/hecksagon/domain_port.bluebook +4 -19
  200. data/lib/hecks/language/hecksagon/hecksagon.bluebook +6 -25
  201. data/lib/hecks/language/hecksagon/port_operation.bluebook +3 -14
  202. data/lib/hecks/language/oidc.json +35 -0
  203. data/lib/hecks/language/port.bluebook +4 -17
  204. data/lib/hecks/language/translation/translation.bluebook +3 -9
  205. data/lib/hecks/language/translation/translation_aggregate.bluebook +5 -24
  206. data/lib/hecks/language/world/wiring.bluebook +1 -3
  207. data/lib/hecks/language/world/world.bluebook +5 -7
  208. data/lib/hecks/literal.rb +8 -50
  209. data/lib/hecks/mcp_door_scope.rb +9 -90
  210. data/lib/hecks/mcp_stdio_guard.rb +4 -41
  211. data/lib/hecks/naming.rb +29 -137
  212. data/lib/hecks/ports/access_control.rb +7 -23
  213. data/lib/hecks/ports/agent/answers.rb +5 -23
  214. data/lib/hecks/ports/agent.rb +47 -172
  215. data/lib/hecks/ports/authentication.rb +12 -39
  216. data/lib/hecks/ports/authorization.rb +16 -43
  217. data/lib/hecks/ports/clock.rb +5 -45
  218. data/lib/hecks/ports/extraction.rb +7 -12
  219. data/lib/hecks/ports/identity_assignment.rb +8 -29
  220. data/lib/hecks/ports/identity_generation.rb +6 -29
  221. data/lib/hecks/ports/identity_resolution.rb +2 -10
  222. data/lib/hecks/ports/key_vault.rb +9 -45
  223. data/lib/hecks/ports/persistence/append_only.rb +61 -64
  224. data/lib/hecks/ports/persistence/binding_policy.rb +4 -14
  225. data/lib/hecks/ports/persistence/codec_boundary.rb +20 -80
  226. data/lib/hecks/ports/persistence/null_saga_store.rb +4 -20
  227. data/lib/hecks/ports/persistence/plugin.rb +13 -42
  228. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +38 -141
  229. data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +15 -115
  230. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +16 -84
  231. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +6 -25
  232. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -22
  233. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +55 -181
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/events.rb +77 -0
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +12 -122
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +28 -128
  237. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +73 -474
  238. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +31 -124
  239. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +45 -219
  240. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +13 -108
  241. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +18 -75
  242. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +9 -49
  243. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +6 -123
  244. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +23 -55
  245. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +14 -72
  246. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +7 -16
  247. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +14 -115
  248. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +13 -45
  249. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +127 -554
  250. data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +13 -63
  251. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +2 -17
  252. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +3 -14
  253. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +13 -58
  254. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -17
  255. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -26
  256. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +9 -25
  257. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -124
  258. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +34 -123
  259. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +5 -12
  260. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +3 -9
  261. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -14
  262. data/lib/hecks/ports/persistence/plugins/era.rb +4 -23
  263. data/lib/hecks/ports/persistence/postgres_dump/connection.rb +7 -23
  264. data/lib/hecks/ports/persistence/postgres_dump.rb +4 -35
  265. data/lib/hecks/ports/persistence/remote_runtime.rb +2 -24
  266. data/lib/hecks/ports/persistence/repository_factory.rb +8 -27
  267. data/lib/hecks/ports/persistence/state_codec.rb +23 -103
  268. data/lib/hecks/ports/persistence.rb +7 -14
  269. data/lib/hecks/ports/projection.rb +42 -84
  270. data/lib/hecks/ports/query/in_memory.rb +10 -58
  271. data/lib/hecks/ports/query/ordering.rb +11 -32
  272. data/lib/hecks/ports/query.rb +14 -37
  273. data/lib/hecks/ports.rb +2 -8
  274. data/lib/hecks/projections/bootstrap_table.rb +11 -43
  275. data/lib/hecks/projections/deploy/fargate/assembly.rb +2 -15
  276. data/lib/hecks/projections/deploy/fargate/cdn.rb +1 -48
  277. data/lib/hecks/projections/deploy/fargate/cdn_default.rb +2 -11
  278. data/lib/hecks/projections/deploy/fargate/check.rb +5 -14
  279. data/lib/hecks/projections/deploy/fargate/containers.rb +3 -78
  280. data/lib/hecks/projections/deploy/fargate/extras.rb +3 -44
  281. data/lib/hecks/projections/deploy/fargate/monitoring.rb +0 -44
  282. data/lib/hecks/projections/deploy/fargate/settings.rb +2 -54
  283. data/lib/hecks/projections/deploy/fargate/yaml.rb +13 -58
  284. data/lib/hecks/projections/deploy/fargate.rb +35 -202
  285. data/lib/hecks/projections/deploy/lambda.rb +150 -678
  286. data/lib/hecks/projections/deploy/preview/access_yaml.rb +1 -33
  287. data/lib/hecks/projections/deploy/preview/containers.rb +6 -111
  288. data/lib/hecks/projections/deploy/preview/routing_yaml.rb +4 -56
  289. data/lib/hecks/projections/deploy/preview/script.rb +2 -14
  290. data/lib/hecks/projections/deploy/preview/settings.rb +3 -33
  291. data/lib/hecks/projections/deploy/preview/task_yaml.rb +2 -38
  292. data/lib/hecks/projections/deploy/preview/template.rb +2 -58
  293. data/lib/hecks/projections/deploy/preview.rb +2 -67
  294. data/lib/hecks/projections/deploy/scripts/settings.rb +3 -25
  295. data/lib/hecks/projections/deploy/scripts.rb +4 -57
  296. data/lib/hecks/projections/deploy/shared.rb +47 -146
  297. data/lib/hecks/projections/deploy/smoke.rb +4 -61
  298. data/lib/hecks/projections/deploy/template_diff/comparer.rb +3 -25
  299. data/lib/hecks/projections/deploy/template_diff/loader.rb +2 -11
  300. data/lib/hecks/projections/deploy/template_diff.rb +5 -57
  301. data/lib/hecks/projections/diagrams.rb +20 -582
  302. data/lib/hecks/projections/glossary/html.rb +14 -130
  303. data/lib/hecks/projections/glossary/markdown.rb +8 -69
  304. data/lib/hecks/projections/glossary/mermaid.rb +9 -66
  305. data/lib/hecks/projections/glossary/sensitivity.rb +5 -34
  306. data/lib/hecks/projections/glossary/sentences.rb +14 -159
  307. data/lib/hecks/projections/glossary.rb +30 -222
  308. data/lib/hecks/projections/ir.rb +3 -10
  309. data/lib/hecks/projections/model/deviations.rb +15 -42
  310. data/lib/hecks/projections/model.rb +12 -51
  311. data/lib/hecks/projections/oidc.rb +12 -56
  312. data/lib/hecks/projections/parser_table.rb +9 -28
  313. data/lib/hecks/projections/reference.rb +2 -15
  314. data/lib/hecks/projections/rust_vocabulary.rb +12 -250
  315. data/lib/hecks/projections/shape.rb +3 -29
  316. data/lib/hecks/projections/statements.rb +7 -86
  317. data/lib/hecks/projections/vocabulary.rb +6 -22
  318. data/lib/hecks/projections.rb +2 -16
  319. data/lib/hecks/projector/cli_projector.rb +60 -375
  320. data/lib/hecks/projector/docs_projector.rb +10 -201
  321. data/lib/hecks/projector/exporter.rb +42 -134
  322. data/lib/hecks/projector/ir_projector.rb +2 -9
  323. data/lib/hecks/projector/narrate_projector.rb +15 -189
  324. data/lib/hecks/projector/target.rb +13 -76
  325. data/lib/hecks/projector.rb +30 -121
  326. data/lib/hecks/query_specification/common/comparators.rb +5 -26
  327. data/lib/hecks/query_specification/common/comparison.rb +35 -143
  328. data/lib/hecks/query_specification/common/dsl.rb +8 -24
  329. data/lib/hecks/query_specification/common/null_policy.rb +20 -70
  330. data/lib/hecks/query_specification/common/options.rb +2 -6
  331. data/lib/hecks/query_specification/common/where_clause.rb +2 -10
  332. data/lib/hecks/query_specification/field_path.rb +37 -77
  333. data/lib/hecks/query_specification/hop_path.rb +12 -76
  334. data/lib/hecks/release/gem_pin.rb +2 -26
  335. data/lib/hecks/release/runner/ci_publisher.rb +3 -6
  336. data/lib/hecks/release/runner/commands.rb +2 -4
  337. data/lib/hecks/release/runner/gem_publisher.rb +2 -3
  338. data/lib/hecks/release/runner/npm_publisher.rb +6 -20
  339. data/lib/hecks/release/runner/preflight.rb +4 -19
  340. data/lib/hecks/release/runner/tagger.rb +2 -9
  341. data/lib/hecks/release/runner.rb +3 -32
  342. data/lib/hecks/rendering.rb +4 -26
  343. data/lib/hecks/router/namespace_installer.rb +12 -36
  344. data/lib/hecks/router.rb +4 -8
  345. data/lib/hecks/runtime/aggregate_lock.rb +8 -33
  346. data/lib/hecks/runtime/boot_gates.rb +3 -18
  347. data/lib/hecks/runtime/caller.rb +19 -59
  348. data/lib/hecks/runtime/capability_graph.rb +5 -21
  349. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
  350. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +23 -160
  351. data/lib/hecks/runtime/command_interpreter.rb +124 -306
  352. data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
  353. data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
  354. data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
  355. data/lib/hecks/runtime/command_rules/emission.rb +8 -21
  356. data/lib/hecks/runtime/command_rules/references.rb +23 -202
  357. data/lib/hecks/runtime/dependency_planning.rb +30 -102
  358. data/lib/hecks/runtime/dispatcher.rb +82 -241
  359. data/lib/hecks/runtime/entity_element.rb +40 -462
  360. data/lib/hecks/runtime/entity_interpreter.rb +64 -219
  361. data/lib/hecks/runtime/errors.rb +32 -115
  362. data/lib/hecks/runtime/event.rb +3 -25
  363. data/lib/hecks/runtime/identity.rb +18 -154
  364. data/lib/hecks/runtime/instance.rb +18 -150
  365. data/lib/hecks/runtime/interpreting.rb +11 -70
  366. data/lib/hecks/runtime/invocation.rb +28 -164
  367. data/lib/hecks/runtime/loader.rb +48 -186
  368. data/lib/hecks/runtime/outbox.rb +38 -261
  369. data/lib/hecks/runtime/policy_interpreter.rb +38 -200
  370. data/lib/hecks/runtime/port_operation_interpreter.rb +26 -87
  371. data/lib/hecks/runtime/query_interpreter.rb +45 -187
  372. data/lib/hecks/runtime/reaction_invocation.rb +36 -138
  373. data/lib/hecks/runtime/read_model_interpreter.rb +117 -208
  374. data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
  375. data/lib/hecks/runtime/reference_hop.rb +8 -81
  376. data/lib/hecks/runtime/refusal_wording.rb +8 -89
  377. data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
  378. data/lib/hecks/runtime/registry/verification.rb +52 -225
  379. data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
  380. data/lib/hecks/runtime/registry.rb +99 -244
  381. data/lib/hecks/runtime/remote_dispatcher.rb +37 -152
  382. data/lib/hecks/runtime/routing.rb +4 -15
  383. data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
  384. data/lib/hecks/runtime/saga_interpreter.rb +79 -263
  385. data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
  386. data/lib/hecks/runtime/tenant_check.rb +6 -68
  387. data/lib/hecks/runtime/tenant_scope.rb +3 -28
  388. data/lib/hecks/runtime/value/admission.rb +16 -67
  389. data/lib/hecks/runtime/value/coercion.rb +155 -641
  390. data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
  391. data/lib/hecks/runtime/value.rb +12 -84
  392. data/lib/hecks/runtime.rb +17 -65
  393. data/lib/hecks/storehouse.rb +141 -568
  394. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +8 -32
  395. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
  396. data/lib/hecks/vendoring/git_environment.rb +2 -8
  397. data/lib/hecks/vendoring/git_source.rb +4 -10
  398. data/lib/hecks/vendoring.rb +7 -46
  399. data/lib/hecks/version.rb +3 -14
  400. data/lib/hecks.rb +26 -81
  401. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
  402. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
  403. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
  404. metadata +3 -2
@@ -2,13 +2,8 @@ require_relative "behaviour/read_model"
2
2
 
3
3
  module Hecks
4
4
  module Bluebook
5
- # A read model — an ask that gathers heads from more than one aggregate.
6
- #
7
- # Like a query it crosses over as an instance, and for the same reason: its
8
- # body is inherited from `QuerySpecification::ReadModel::Specification`, whose
9
- # readers the runtime and the SQLite adapter both call on the object. It gains
10
- # an identity and an owner ; it keeps the name it always had, because only a
11
- # class ever had a competing answer for `name`.
5
+ # A read model: an ask that gathers heads from more than one aggregate.
6
+ # Crosses over as an instance, like a query.
12
7
  class ReadModel < QuerySpecification::ReadModel::Specification
13
8
  include Construct
14
9
 
@@ -27,31 +22,25 @@ module Hecks
27
22
  )
28
23
 
29
24
  attr_reader :name, :description, :reference_name, :reference_target, :aggregate_heads, :group_by,
30
- :count, :median_field
25
+ :count, :median_field, :sum_field, :avg_field, :min_field, :max_field,
26
+ :percentile_field, :percentile_at, :any_field, :all_field
31
27
 
32
- # `reference_name:`/`reference_target:` are nil for a rootless read
33
- # model (no `reference_to` declared) — `&.` throughout, rather than
34
- # calling `.to_s`/`.to_sym` unconditionally, so
35
- # `reference_target.nil?` stays a real, checkable fact for the
36
- # interpreter instead of silently becoming `""`.
28
+ # Nil `reference_name`/`reference_target` mean a rootless read model, so `&.` keeps
29
+ # them nil. `count` through `all_field` are `Behaviour::ReadModel::REDUCTION_FIELDS`,
30
+ # one keyword per wire field: `Assembly::Build`/`Reconstruction` construct this
31
+ # generically off the contract's own flat field list (contracts.rb), so grouping
32
+ # them into a nested object would break that reflection.
37
33
  #
38
34
  # @param name [String, Symbol] the read model's declared name
39
- # @param description [String, nil] the read model's declared prose description
40
- # @param reference_name [Symbol, String, nil] the local reference attribute this
41
- # read model roots at, or `nil` for a rootless read model
42
- # @param reference_target [String, Symbol, nil] the rooted aggregate's name, or
43
- # `nil` for a rootless read model
44
- # @param aggregate_heads [Array<Hash{Symbol => Object}>] the included aggregates, each
45
- # a row with `:aggregate` (the aggregate's name), `:as` (the output field name) and
46
- # `:many` (whether this head is a many-side join)
47
- # @param group_by [Array<Hash{field: Symbol}>] the declared group-by fields, one row
48
- # per field
49
- # @param count [Boolean, nil] whether this read model reduces to a row count; `nil`
50
- # when undeclared
51
- # @param median_field [Symbol, nil] the field this read model reduces to the median
52
- # of, or `nil` when undeclared
35
+ # @param reference_name [Symbol, String, nil] the local reference attribute it roots at
36
+ # @param reference_target [String, Symbol, nil] the rooted aggregate's name
37
+ # @param aggregate_heads [Array<Hash{Symbol => Object}>] rows of `:aggregate`, `:as`, `:many`
38
+ # @param group_by [Array<Hash{field: Symbol}>] the declared group-by fields, one row each
39
+ # rubocop:disable-next Metrics/ParameterLists
53
40
  def initialize(name:, description: nil, reference_name: nil, reference_target: nil, aggregate_heads: [],
54
- group_by: [], count: nil, median_field: nil, **)
41
+ group_by: [], count: nil, median_field: nil, sum_field: nil, avg_field: nil,
42
+ min_field: nil, max_field: nil, percentile_field: nil, percentile_at: nil,
43
+ any_field: nil, all_field: nil, **)
55
44
  super(joins: aggregate_heads, **)
56
45
  @name = name.to_s
57
46
  @hecks_name = @name
@@ -59,71 +48,58 @@ module Hecks
59
48
  @reference_name = reference_name&.to_sym
60
49
  @reference_target = reference_target&.to_s
61
50
  @aggregate_heads = aggregate_heads
62
- # Hash rows (`{field: :agg}`), same shape as `aggregate_heads` —
63
- # `group_by_fields` is the convenience reader everything but
64
- # `to_h`/the Judge's own generic walk actually wants.
51
+ # Rows are `{field: :agg}` hashes, the same shape as `aggregate_heads`.
65
52
  @group_by = group_by
66
- # **Absent is not false**. `@count` stays nil rather than becoming
67
- # `false` when undeclared — the same "if you declare it, declare
68
- # something" reading `Lifecycle`'s own optional fields rely on
69
- # (MetaValidator::Judge#setters skips a setter whose every
70
- # source is `nil`, so an always-false `@count` would dispatch
71
- # `ReadModel.Count` on every read model that never wrote the
72
- # word at all). `!!count` collapses a truthy DSL `true` (or a
73
- # reconstructed `true`) to the same `true`/`nil` pair either path
74
- # produces — never `false`.
75
- @count = count ? true : nil
76
- @median_field = median_field&.to_sym
53
+ assign_reductions(count: count, median_field: median_field, sum_field: sum_field, avg_field: avg_field,
54
+ min_field: min_field, max_field: max_field, percentile_field: percentile_field,
55
+ percentile_at: percentile_at, any_field: any_field, all_field: all_field)
77
56
  end
78
57
 
79
- # `.to_sym` regardless of source — the direct builder path stores
80
- # symbols, but `Reconstruction::Shapes#group_by_field` (the
81
- # replayed-from-the-meta-domain path every real boot actually
82
- # goes through) reads the field back as a String, the same way
83
- # `aggregate_heads`' own `:as` does. Row hash keys built by
84
- # `Value.materialize_unwrapped` are symbols (`attr.name`), so
85
- # this has to be too, or `row[field]` in `nest` silently misses.
86
-
87
- # `wheres`/`order_by`/`limit` are spelled explicitly here, the same
88
- # mechanism `Query#to_h` already uses (query.rb, read directly
89
- # before this was written) — always present, `wheres` a (possibly
90
- # empty) array and `order_by`/`limit` nil when undeclared, exactly
91
- # like a Query's. `extra_options_to_h` still excludes these three by
92
- # name (options.rb), so nothing here double-spells them; the two
93
- # constructs now share one encoding for the same three words rather
94
- # than a reader needing to learn a second one. See this file's own
95
- # `filtered_head_name` and language/bluebook/syntax.bluebook's
96
- # `ReadModel` `where`/`order_by`/`limit` member rows for the history
97
- # of why this was narrower, and 2026-08-11's read-model where/
98
- # order_by/limit task for why it stopped being.
99
- # Same dynamic tail as a Query's, plus two collections whose
100
- # rows are plain hashes rather than constructs — they are
101
- # normalised here rather than by `many`, which recurses through
102
- # `to_h` and would have nothing to call.
103
- # `count`/`median_field` are absent (no key at all), not `nil`,
104
- # when undeclared — unlike `group_by` (always a `[]`, never
105
- # missing) they are the first fields this construct has ever
106
- # added that no older real corpus member could possibly carry, so
107
- # merging them unconditionally would have put a `null` onto every
108
- # existing read model's own wire shape (banking's
109
- # `ComplianceDashboard`/`CustomerPortfolio`, the language's own
110
- # `WholeBluebook`, ...) for a fact nothing about them changed —
111
- # the exact `extra_options_to_h` reads for `cursor`/`offset`/etc,
112
- # applied here for the same reason.
58
+ # Every reduction is omitted from the export, not nil, when undeclared, so
59
+ # existing read models keep their wire shape.
113
60
  #
114
61
  # @return [Hash] the declared emission, with `aggregate_heads`/`group_by` rows
115
- # stringified, `count`/`median_field` merged in only when declared, and
62
+ # stringified, each reduction merged in only when declared, and
116
63
  # `extra_options_to_h`'s own dynamic tail merged last
117
64
  def to_h
118
- reductions = {}
119
- reductions[:count] = true if @count
120
- reductions[:median_field] = @median_field.to_s if @median_field
121
65
  super
122
66
  .merge(aggregate_heads: @aggregate_heads.map { |head| head.merge(as: head[:as].to_s) })
123
67
  .merge(group_by: @group_by.map { |row| row.merge(field: row[:field].to_s) })
124
- .merge(reductions)
68
+ .merge(reduction_pairs)
125
69
  .merge(extra_options_to_h)
126
70
  end
71
+
72
+ private
73
+
74
+ # Stays nil (never false) when undeclared: the Judge skips setters whose source is nil.
75
+ def assign_reductions(count:, median_field:, sum_field:, avg_field:, min_field:, max_field:,
76
+ percentile_field:, percentile_at:, any_field:, all_field:)
77
+ @count = count ? true : nil
78
+ @median_field = median_field&.to_sym
79
+ @sum_field = sum_field&.to_sym
80
+ @avg_field = avg_field&.to_sym
81
+ @min_field = min_field&.to_sym
82
+ @max_field = max_field&.to_sym
83
+ @percentile_field = percentile_field&.to_sym
84
+ @percentile_at = percentile_at&.to_f
85
+ @any_field = any_field&.to_sym
86
+ @all_field = all_field&.to_sym
87
+ end
88
+
89
+ def reduction_pairs
90
+ pairs = {}
91
+ pairs[:count] = true if @count
92
+ pairs[:median_field] = @median_field.to_s if @median_field
93
+ pairs[:sum_field] = @sum_field.to_s if @sum_field
94
+ pairs[:avg_field] = @avg_field.to_s if @avg_field
95
+ pairs[:min_field] = @min_field.to_s if @min_field
96
+ pairs[:max_field] = @max_field.to_s if @max_field
97
+ pairs[:percentile_field] = @percentile_field.to_s if @percentile_field
98
+ pairs[:percentile_at] = @percentile_at if @percentile_field
99
+ pairs[:any_field] = @any_field.to_s if @any_field
100
+ pairs[:all_field] = @all_field.to_s if @all_field
101
+ pairs
102
+ end
127
103
  end
128
104
  end
129
105
  end
@@ -2,32 +2,13 @@ module Hecks
2
2
  module Bluebook
3
3
  # An attribute that points at another aggregate's head.
4
4
  #
5
- # Holds the target directly, and answers `resolve` with the target's
6
- # Aggregate, rather than being spelled as the string `"Reference<Customer>"`
7
- # that `AggregateBuilder#reference_to` and `CommandBuilder#cross_reference`
8
- # would otherwise mint from the real constant just handed in. That string
9
- # would need five separate readers to parse back apart — a regex in the
10
- # command interpreter, string equality in the read-model interpreter and
11
- # the SQLite adapter, and `delete_prefix` in the bluebook builder — one
12
- # writer inventing a spelling, five readers reparsing it.
13
- #
14
- # Resolution is lazy and deliberately so: `reference_to
15
- # Customer` may name an aggregate declared lower in the file — banking's
16
- # Account points at Customer and survives only because Customer happens to
17
- # be written above — so the edge cannot be resolved at declaration time.
18
- # It resolves through the chapter's own IR (`Bluebook#aggregate`),
19
- # which is scoped by construction : the lookup cannot walk anywhere but
20
- # this chapter's declared heads, so a same-named aggregate in another
21
- # loaded domain is unreachable rather than defended against.
22
- #
23
- # `to_s` still spells `"Reference<Customer>"`, because `Attribute#to_h` is
24
- # part of the pinned byte-for-byte export contract. IR objects in
25
- # the graph, strings in the export.
5
+ # Holds the target name and resolves lazily through the chapter's own IR, since
6
+ # `reference_to` may name an aggregate declared later in the file.
7
+ # `to_s` spells `"Reference<Customer>"`, which the export contract pins.
26
8
  class Reference
27
9
  attr_reader :target_name
28
10
 
29
- # The Aggregate whose declaration carries this reference — the way
30
- # up to the chapter, stamped once every sibling has been read.
11
+ # The Aggregate whose declaration carries this reference, stamped once siblings are read.
31
12
  attr_accessor :declared_in
32
13
 
33
14
  # @param target_name [Module, String, Symbol] the aggregate constant this
@@ -36,9 +17,7 @@ module Hecks
36
17
  @target_name = Naming.demodulise(target_name).to_s
37
18
  end
38
19
 
39
- # The Aggregate this points at, or nil when the target belongs to
40
- # another domain — a cross-domain target may legitimately not be loaded,
41
- # the same reading `across` policies get.
20
+ # The Aggregate this points at, or nil when the target belongs to another domain.
42
21
  #
43
22
  # @return [Bluebook::Aggregate, nil] the target aggregate, or `nil` when
44
23
  # `declared_in`'s own chapter does not declare one by this name
@@ -61,21 +40,8 @@ module Hecks
61
40
  def to_s = "Reference<#{@target_name}>"
62
41
  def inspect = "#<Reference #{@target_name}>"
63
42
 
64
- # Value equality, not identity — without this, two references to
65
- # the same target, parsed from two separate bluebook reads (era
66
- # N's own boot and a held era's own shadow reconstruction,
67
- # coverage_check.rb's own comparison), are different objects and
68
- # compare unequal by Ruby's default `==`. `EraGuard::ShapeDiff
69
- # #diff_type`'s `held_type != current_type` check then reads as
70
- # true for every reference_to attribute on every mint, regardless
71
- # of whether the reference actually changed — a real refusal for
72
- # attributes nothing about. `declared_in` (which aggregate carries
73
- # this reference) is deliberately excluded: the same reference
74
- # attribute exists once, but its `held_type`/`current_type` come
75
- # from separately-parsed bluebooks with structurally different
76
- # (if same-shaped) owning aggregates, and `declared_in` is a
77
- # cross-reference for `resolve`, not part of what this attribute
78
- # itself is.
43
+ # Value equality on the target; `declared_in` is excluded so references parsed from
44
+ # separate reads compare equal (EraGuard's shape diff depends on it).
79
45
  #
80
46
  # @param other [Object] the value to compare against
81
47
  # @return [Boolean] whether `other` is a `Reference` to the same target
@@ -4,43 +4,8 @@ require "fileutils"
4
4
 
5
5
  module Hecks
6
6
  module Bluebook
7
- # Boots a real bluebook and actually dispatches against it — the
8
- # sibling `bin/model_check` never had: that tool proves a bluebook
9
- # is structurally sound (no dead states, no unreachable transitions)
10
- # without ever running a single command; this proves it works,
11
- # catching exactly the class of bug static analysis structurally
12
- # cannot see. Built and proved inside `bin/interview`'s own `smoke`
13
- # command, where it first found two real bugs — a lifecycle field
14
- # colliding with an explicit attribute, and a non-creating command
15
- # missing `Command.ActsOn` — that reloaded clean, checked clean,
16
- # and only broke once something actually dispatched against them.
17
- #
18
- # Synthesized args, never random (`Synthesizer`) — a String becomes
19
- # a fixed marker, a closed set uses its own first admitted member, a
20
- # reference reuses whatever this same run already minted for that
21
- # target. One call per declared command, one call per declared
22
- # report, per created root; every failure is collected and reported
23
- # together rather than stopping at the first.
24
- #
25
- # ## What this cannot catch
26
- #
27
- # A call that dispatches cleanly but answers wrong — the read-model join
28
- # bug this exact tool's own first user hit is the textbook case, a query
29
- # that silently returned an empty array rather than raising.
30
- # Smoke-testing proves nothing crashes; it does not prove the answer is
31
- # the right one.
32
- #
33
- # ## False positives from a naive synthesizer
34
- #
35
- # A failure here is not necessarily a domain bug, either — it may
36
- # just be `Synthesizer`'s own naive values (0, an empty list,
37
- # "smoke-test") not satisfying a real invariant this simple
38
- # generator was never built to reason about (a positive amount, a
39
- # non-empty topping list, an email pattern). Confirmed against
40
- # Banking and Pizzas: both real domains, both showing exactly that
41
- # class of failure, neither one an actual bug. Building a
42
- # synthesizer that respects arbitrary declared invariants is real
43
- # constraint-aware generation — a separate, larger tool than this.
7
+ # Boots a bluebook and dispatches synthesized calls against it, collecting every failure.
8
+ # Catches what static checks miss, but not wrong answers, and naive values may trip invariants.
44
9
  module SmokeTest
45
10
  Failure = Struct.new(:domain, :aggregate, :command, :error, keyword_init: true) do
46
11
  def to_s = "#{domain}::#{aggregate}.#{command}: #{error}"
@@ -48,44 +13,8 @@ module Hecks
48
13
 
49
14
  module_function
50
15
 
51
- # `dir` is any real, bootable hecks app directory — a saved
52
- # domain, or a throwaway one a caller rendered into a temp dir
53
- # for exactly this purpose. Plain `Hecks.boot`, not
54
- # `Router.boot` — proven all night, and it works against a
55
- # domain with framework-attached chapters (`uses_framework
56
- # "Governance"`, say) that `Router.boot` refuses outright: those
57
- # chapters carry no `.world` of their own, which `Router`'s
58
- # per-domain realm resolution requires and a plain boot never
59
- # did. Reports are reachable the identical way regardless of
60
- # boot path — `dispatcher.query("Domain.report_name", **args)`
61
- # is the same `Dispatcher#query` either boot hands back; Router
62
- # was never actually required for this, only its own namespace
63
- # sugar (`Domain.report_name(...)` as a bare method call) was.
64
- #
65
- # Never boots `dir`'s own real bindings — measured, not a
66
- # precaution taken on spec: pointed at `examples/pizzas` (a real,
67
- # persistent store carrying real accumulated records), a
68
- # synthesized `CreatePizza` collided with an actual pre-existing
69
- # record literally named "smoke-test" — a coincidence this time,
70
- # data pollution the next. `isolate!` copies only the `.bluebook`
71
- # (and `.world`, for its realm) into a throwaway directory and
72
- # deliberately leaves the real `.hecksagon` behind — with none
73
- # present, `Ports::Persistence::BindingPolicy.default_binding`
74
- # binds every aggregate to Memory on its own, no settings needed.
75
- # Whatever `dir` is really bound to in production is never
76
- # touched.
77
- # `install_facade: false` — this only ever dispatches by FQN string
78
- # (below), so it never needs the `Widget::Item.Add(...)` Ruby
79
- # sugar `Hecks.boot` installs by default. Skipping it matters
80
- # here specifically: that sugar lands as a bare global constant on
81
- # `Object` per domain and per aggregate name, with no scoping and
82
- # no cleanup — and this tool boots throwaway domains under
83
- # whatever generic names the caller's `.bluebook` happens to use
84
- # ("Widget", "Item", "Tag" in this tool's own specs). Installing
85
- # the facade would leak those names into the rest of the process —
86
- # measured, not hypothetical: it once left a stale `Widget` constant
87
- # that corrupted an unrelated spec's own unrelated use of the same
88
- # bare name.
16
+ # Boots an isolated copy of `dir` on Memory bindings, so real stores are never touched.
17
+ # Skips the facade: its bare global constants would leak into the rest of the process.
89
18
  #
90
19
  # @param dir [String] path to a bootable hecks app directory — a saved
91
20
  # domain, or a throwaway one rendered into a temp dir for this purpose
@@ -102,18 +31,8 @@ module Hecks
102
31
  end
103
32
  end
104
33
 
105
- # Only the `.bluebook`, never `.world` either — a real world's own
106
- # settings (`persisted_by("Heki") { dir "..." }`) are keyed to the
107
- # real adapter it names, not to Memory; copied verbatim, they
108
- # apply to the wrong binding and refuse with a `WiringError`
109
- # ("Memory does not declare :dir") — measured, not assumed, the
110
- # first time this ran against Banking. A plain `Hecks.boot`
111
- # needs no realm to address anything (unlike `Router.boot`), so
112
- # leaving the world out entirely is the correct minimal isolation,
113
- # not a workaround. `translations/*.bluebook` also deliberately
114
- # excluded — those are earlier, superseded versions of the same
115
- # domain; smoke-testing the current declaration is the point, not
116
- # its whole history.
34
+ # Copies only the `.bluebook` files: a real `.world` carries settings keyed to its own
35
+ # adapter and refuses under Memory.
117
36
  #
118
37
  # @param dir [String] path to the source hecks app directory to isolate
119
38
  # @param scratch [String] path to the throwaway directory to copy the
@@ -126,16 +45,7 @@ module Hecks
126
45
  Dir.glob(File.join(source, "*.bluebook")).each { |file| FileUtils.cp(file, target) }
127
46
  end
128
47
 
129
- # Aggregates walked in declaration order, on purpose — so whatever
130
- # a later aggregate's own shape needs from an earlier one (a
131
- # `TripItem` needing a real `PackingItem`, say) already exists by
132
- # the time that aggregate's own turn comes, the same reason
133
- # `Synthesizer#args_for`'s `created` map is threaded through in
134
- # this same order.
135
- # `created` and `failures` thread through both the aggregate loop and the
136
- # read_models loop below in the declaration order the comment above already
137
- # documents; splitting would turn that ordering into an implicit contract on
138
- # parameter/return passing instead of one visible sequence.
48
+ # Walks aggregates in declaration order so later ones can reference earlier `created` ids.
139
49
  # rubocop:disable-next Metrics/AbcSize
140
50
  #
141
51
  # @param dispatcher [Runtime::Dispatcher, Runtime::RemoteDispatcher] the
@@ -178,7 +88,7 @@ module Hecks
178
88
 
179
89
  chapter.read_models.each do |model|
180
90
  root_id = created[model.reference_target]
181
- # Nothing was created for this root — nothing to smoke-test yet, not a failure.
91
+ # No root was created, so there is nothing to query yet.
182
92
  next unless root_id
183
93
 
184
94
  dispatcher.query("#{domain}.#{model.query_name}", model.reference_name => root_id)
@@ -1,58 +1,21 @@
1
1
  module Hecks
2
2
  module Bluebook
3
- # **One synthesized argument per declared attribute** — never a random
4
- # guess. Reads only real `IR` (`Aggregate`, `Command`,
5
- # `Attribute`, `ValueObject`), so it works against any
6
- # loaded bluebook, not one particular domain: a String becomes a
7
- # fixed marker; an Integer becomes 0; a closed set uses its own
8
- # first admitted member (never a value a real `one_of` would
9
- # legitimately refuse); a reference uses whatever id the caller
10
- # already knows for that target (`created`), so a command whose
11
- # own shape depends on an earlier-created record — a `TripItem`
12
- # needing a real `PackingItem`, say — gets something real to work
13
- # with instead of a guess that would only ever fail.
14
- #
15
- # Extracted from `bin/interview`'s own smoke test, where this first
16
- # got built and proved — the logic never actually knew anything
17
- # about the Interview domain or any one app; it only ever read IR.
3
+ # Synthesizes one argument per declared attribute from the IR: fixed markers for scalars,
4
+ # a closed set's first member, and a known `created` id for references. Never random.
18
5
  module Synthesizer
19
- # The only type names that are ever truly scalar — anything else
20
- # a value object's own field names is another value object,
21
- # nested (`Pizza.price_cents: Price`, itself wrapping `cents:
22
- # Integer` — a real, ordinary shape, not an edge case). Missing
23
- # this the first time round meant a nested field silently
24
- # synthesized as the bare string `"smoke-test"` instead of the
25
- # real nested structure a command actually declares — caught
26
- # only by testing against Pizzas instead of re-testing against
27
- # the one simpler domain this was first built for, where every
28
- # value object happened to be single-field and primitive-typed.
29
- # The same list `Attribute::PRIMITIVES` holds, referenced rather
30
- # than repeated. It was a second, byte-identical copy — and only
31
- # the Attribute one is held to `vocabulary.bluebook` by
32
- # spec/vocabulary_conformance_spec, so this copy could drift from
33
- # the language and nothing would say so.
6
+ # Shared with `Attribute::PRIMITIVES` so the two lists cannot drift.
34
7
  PRIMITIVES = Attribute::PRIMITIVES
35
8
 
36
9
  module_function
37
10
 
38
- # `chapter`/`aggregate` are the real `Bluebook`/`Aggregate`
39
- # the command belongs to — needed to resolve a value object type
40
- # name back to its own declared shape. `created` maps an
41
- # aggregate name to a real id already minted for it earlier in
42
- # the same run; a reference whose target isn't in there yet gets
43
- # a placeholder instead of failing outright, since the caller may
44
- # not care about that particular argument's real value.
11
+ # Synthesizes arguments for `command`. A reference whose target is not in `created`
12
+ # gets a placeholder id.
45
13
  #
46
- # @param chapter [Bluebook::Chapter] the chapter the command's aggregate
47
- # belongs to, needed to resolve a value object type name back to its
48
- # own declared shape
14
+ # @param chapter [Bluebook::Chapter] resolves value object type names to their shapes
49
15
  # @param aggregate [Bluebook::Aggregate] the aggregate `command` belongs to
50
- # @param command [Class] the command class (a `Bluebook::Command`
51
- # subclass) to synthesize arguments for
52
- # @param created [Hash{String => Object}] aggregate name mapped to a real
53
- # id already minted for it earlier in this same run
54
- # @return [Hash{Symbol => Object}] one synthesized value per declared
55
- # attribute, keyed by attribute name
16
+ # @param command [Class] the `Bluebook::Command` subclass to synthesize arguments for
17
+ # @param created [Hash{String => Object}] aggregate name mapped to an id minted earlier
18
+ # @return [Hash{Symbol => Object}] one synthesized value per declared attribute
56
19
  def args_for(chapter, aggregate, command, created = {})
57
20
  command.attributes.to_h do |attribute|
58
21
  if attribute.reference?
@@ -63,13 +26,8 @@ module Hecks
63
26
  end
64
27
  end
65
28
 
66
- # A value object's own synthesized value — its first admitted
67
- # member if it's a closed set (the one value guaranteed not to be
68
- # refused), otherwise one synthesized scalar per declared field.
69
- # Falls back to searching every aggregate in the chapter, the
70
- # same tolerance `bin/interview shape`'s own lookup already
71
- # needed: a value object referenced by name doesn't have to be
72
- # declared on the same aggregate using it.
29
+ # Synthesizes a value object: its first member if a closed set, else one value per field.
30
+ # Falls back to searching every aggregate, since a value object need not be local.
73
31
  #
74
32
  # @param chapter [Bluebook::Chapter] the chapter to search when
75
33
  # `type_name` is not declared on `aggregate` itself
@@ -91,11 +49,7 @@ module Hecks
91
49
  value_object.attributes.to_h { |field| [field.name, field_value_for(chapter, aggregate, field.type)] }
92
50
  end
93
51
 
94
- # A single field's own synthesized value — a plain scalar if its
95
- # type is one of the true primitives, otherwise that type names
96
- # another value object, resolved the identical way `value_for`
97
- # resolves any other one (its own closed set or its own nested
98
- # fields, however deep that nesting actually goes).
52
+ # A field's value: a scalar for a primitive type, otherwise the nested value object.
99
53
  #
100
54
  # @param chapter [Bluebook::Chapter] see `value_for`
101
55
  # @param aggregate [Bluebook::Aggregate] see `value_for`
@@ -107,10 +61,7 @@ module Hecks
107
61
  PRIMITIVES.include?(type_name.to_s) ? scalar_for(type_name) : value_for(chapter, aggregate, type_name)
108
62
  end
109
63
 
110
- # A bare primitive's own synthesized value. Never used for a
111
- # closed set's own field (that's `value_for`'s job, reading the
112
- # set's real first member) — only for a plain scalar field with
113
- # no declared vocabulary to respect.
64
+ # A primitive's synthesized value; closed-set fields are handled by `value_for`.
114
65
  #
115
66
  # @param primitive [String, Symbol] the primitive type name, such as
116
67
  # `"Integer"` or `"String"`
@@ -1,79 +1,47 @@
1
1
  module Hecks
2
2
  module Bluebook
3
- # One field crossing a value-object boundary: a scalar becoming a VO
4
- # member, a VO member becoming a scalar, or a rename across the move.
3
+ # One field crossing a value-object boundary, or carried under a new name.
5
4
  # Paths are dotted ("price.cents") for a VO member, bare otherwise.
6
5
  TranslationMove = Struct.new(:from, :to)
7
6
 
8
- # One field whose value, not just its name or position, changed —
9
- # an old value has nothing in common with a new one, so the only
10
- # honest way to bridge it is a declared, exhaustive lookup table.
11
- # Paths follow `TranslationMove`'s convention (dotted reaches a VO
12
- # member); `values` maps every old-era value that can appear to its
13
- # new-era replacement.
14
- # `:values` shadows Struct#values on purpose (same reasoning as
15
- # AttributeCollector::OneOf) — no caller reads the built-in behavior.
7
+ # One field whose value changed, bridged by an exhaustive lookup table.
8
+ # Paths follow `TranslationMove`; `values` maps each old-era value to its new-era one.
9
+ # `:values` shadows Struct#values on purpose; no caller reads the built-in.
16
10
  # rubocop:disable-next Lint/StructNewOverride
17
11
  TranslationConvert = Struct.new(:from, :to, :values)
18
12
 
19
- # A value object's or entity's own type name changed with its member
20
- # structure unchanged — the one drift `rename`/`move` cannot express,
21
- # because the attribute kept its name and only the type it points at
22
- # changed name. Mirrors `was:` one level deeper.
13
+ # A value object's or entity's type name changed with its members unchanged.
14
+ # The attribute kept its name, so `rename`/`move` cannot express it.
23
15
  TranslationRetype = Struct.new(:from, :to)
24
16
 
25
- # A computed transform — rescale, reformat, split, merge — whose only
26
- # implementation is the SQL expression itself, evaluated exclusively
27
- # inside the compiled Postgres head. No in-process reference exists
28
- # to check it against; any aggregate carrying one refuses to boot on
29
- # every other adapter.
17
+ # A computed transform whose only implementation is a SQL expression run inside
18
+ # the compiled Postgres head; an aggregate carrying one refuses to boot elsewhere.
30
19
  TranslationCompute = Struct.new(:from, :to, :sql)
31
20
 
32
- # The aggregate's own identity changed what it's computed from — not
33
- # a field crossing a boundary (that's `move`), a value objects's type
34
- # name (`retype`), or a value transform (`compute`): the record's own
35
- # key. No `from:`/`to:` path, unlike every other rule here, because
36
- # nothing is being consumed from or moved into `state` — the state
37
- # a record already holds is untouched; only what identifies it is
38
- # recomputed. Same SQL-only, Postgres-only, no-in-process-reference
39
- # shape `compute` already has, and for the same reason: there is
40
- # nothing to check this against outside the compiled head.
21
+ # The aggregate's own key is recomputed by a SQL expression; stored state is untouched.
22
+ # Postgres-only like `compute`, and it has no `from:`/`to:` path.
41
23
  TranslationRekey = Struct.new(:sql)
42
24
 
43
- # A newly added, required attribute with no source in old data at
44
- # all — not a rename, move, or convert, all of which need a from
45
- # path in the old shape. `default` is the value an existing record
46
- # reads until the next command against it writes a real one; unlike
47
- # `compute`, this is adapter-agnostic — applied in-process by
48
- # `Lineage#translate`, the same as rename/move/drop, because there is
49
- # nothing to compute, only a value to declare.
25
+ # A newly added, required attribute with no source in old data.
26
+ # `default` is what an existing record reads until a command writes a real value;
27
+ # applied in-process by `Lineage#translate` on every adapter.
50
28
  TranslationBackfill = Struct.new(:name, :default)
51
29
 
52
- # One aggregate's part of a translation: which attributes were
53
- # renamed, moved, converted, or deliberately dropped on the way from
54
- # the old era to the new one. `drops` exists so data loss is a
55
- # declared decision, not a field that quietly stopped being
56
- # explained.
30
+ # One aggregate's part of a translation between eras.
31
+ # `drops` makes data loss a declared decision.
57
32
  class TranslationAggregate
58
33
  attr_reader :name, :was, :renames, :moves, :converts, :drops, :retypes, :computes, :rekeys, :backfills
59
34
 
60
35
  # @param name [String, Symbol] the aggregate's name in the destination era
61
- # @param was [String, Symbol, nil] the aggregate's name in the origin era, or `nil`
62
- # if it was not renamed
63
- # @param renames [Hash{Symbol => Symbol}] each renamed field, old name to new name
64
- # @param moves [Array<Bluebook::TranslationMove>] fields crossing a value-object
65
- # boundary
66
- # @param converts [Array<Bluebook::TranslationConvert>] fields whose value is
67
- # remapped through a declared lookup table
36
+ # @param was [String, Symbol, nil] its name in the origin era, or `nil` if unchanged
37
+ # @param renames [Hash{Symbol => Symbol}] old field name to new field name
38
+ # @param moves [Array<Bluebook::TranslationMove>] fields crossing a value-object boundary
39
+ # @param converts [Array<Bluebook::TranslationConvert>] fields remapped via a lookup table
68
40
  # @param drops [Array<Symbol>] fields deliberately not carried forward
69
- # @param retypes [Array<Bluebook::TranslationRetype>] value object or entity type
70
- # renames with their member structure unchanged
71
- # @param computes [Array<Bluebook::TranslationCompute>] fields computed by a SQL
72
- # expression evaluated only inside the compiled Postgres head
73
- # @param rekeys [Array<Bluebook::TranslationRekey>] SQL expressions that recompute
74
- # the aggregate's own identity
75
- # @param backfills [Array<Bluebook::TranslationBackfill>] newly added required
76
- # fields with no source in old data
41
+ # @param retypes [Array<Bluebook::TranslationRetype>] value object or entity type changes
42
+ # @param computes [Array<Bluebook::TranslationCompute>] fields computed by Postgres-only SQL
43
+ # @param rekeys [Array<Bluebook::TranslationRekey>] SQL recomputing the aggregate's identity
44
+ # @param backfills [Array<Bluebook::TranslationBackfill>] new required fields, no old source
77
45
  def initialize(name:, was: nil, renames: {}, moves: [], converts: [], drops: [], retypes: [],
78
46
  computes: [], rekeys: [], backfills: [])
79
47
  @name = name.to_s
@@ -89,12 +57,8 @@ module Hecks
89
57
  end
90
58
  end
91
59
 
92
- # A declared map from one era of a domain's storage shape to the
93
- # next. Renames and moves — the smallest slice that proves the
94
- # concept: old records are translated when they are replayed, never
95
- # rewritten. `retired` acknowledges aggregates that are gone
96
- # outright — not renamed — so their disappearance is a decision,
97
- # not an accident.
60
+ # A declared map from one era of a domain's storage shape to the next.
61
+ # Old records are translated on replay, never rewritten; `retired` names aggregates removed.
98
62
  class Translation
99
63
  attr_reader :domain, :from, :to, :aggregates, :retired
100
64
 
@@ -104,7 +68,7 @@ module Hecks
104
68
  # @param aggregates [Array<Bluebook::TranslationAggregate>] each aggregate's own
105
69
  # translation rules
106
70
  # @param retired [Array<String>] the names of aggregates gone outright in the
107
- # destination era, rather than renamed
71
+ # destination era
108
72
  def initialize(domain:, from:, to:, aggregates: [], retired: [])
109
73
  @domain = domain.to_s
110
74
  @from = from