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
@@ -9,36 +9,14 @@ require_relative "../../naming"
9
9
  module Hecks
10
10
  module Facade
11
11
  module Surface
12
- # One aggregate's door: creating verbs as module methods returning
13
- # the record in hand, CRUD delegation to the repository, the
14
- # `persisted_by`-style binding collector a `.hecksagon` lands on, and
15
- # the aggregate-scoped `port` a `.hecksagon` lands on beside it.
12
+ # One aggregate's door: creating verbs and queries as module methods, CRUD
13
+ # delegation, and the `.hecksagon` binding/port hooks it lands on.
16
14
  module AggregateDoor
17
- # Builds the anonymous module that is one aggregate's door: a `name!` method per
18
- # creating command, a bare method per query, `find`/`all`/`count`/`events`,
19
- # `project`/`docs`/`narrate`, and the `.hecksagon` binding hooks.
15
+ # Builds one aggregate's door module: creating-command methods, query methods,
16
+ # find/all/count/events, project/docs/narrate, and the port/binding hooks.
20
17
  #
21
- # Warns once for each attribute whose name is in `RESERVED`.
22
- #
23
- # One method building one `door` module's ~20 singleton methods
24
- # looks like it splits along each `define_singleton_method` call,
25
- # but three of those blocks (`:port`, `:method_missing`,
26
- # `:const_missing`) are a single cross-referencing essay on the
27
- # stale-facade-across-boots hazard — each one's comment explicitly
28
- # points at "below"/"above" as part of the same method. Splitting
29
- # into helper methods wouldn't break anything at runtime (no
30
- # shared mutable state beyond the closed-over args, which just
31
- # become parameters), but it would sever that narrative across
32
- # method boundaries for no functional gain.
33
- #
34
- # @param dispatcher [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted
35
- # dispatcher the door's methods dispatch, query and read repositories through
36
- # @param domain [String] the owning chapter's name, the first half of the
37
- # aggregate's FQN (`"Pizzas"` in `"Pizzas::Pizza"`)
38
- # @param aggregate [Bluebook::Aggregate] the aggregate to build a door for
39
- # @return [Module] a fresh, unnamed module carrying the door's singleton methods
40
- # @raise [Bluebook::DSL::Malformed] never while building; the returned door's
41
- # `port` raises it when called with no boot in progress
18
+ # Kept as one method: `:port`/`:method_missing`/`:const_missing` below
19
+ # cross-reference each other and would lose that thread if split up.
42
20
  # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
43
21
  def aggregate_module(dispatcher, domain, aggregate)
44
22
  fqn = "#{domain}::#{aggregate.hecks_name}"
@@ -50,16 +28,9 @@ module Hecks
50
28
  warn "[hecks] #{aggregate.hecks_name}##{attribute.name} shadows a built-in — no reader defined"
51
29
  end
52
30
 
53
- # A creating verb is a module method returning the new record in hand ;
54
- # a verb that reaches an existing record lives on the Handle. `!` —
55
- # a command does something (mutates, may refuse), Ruby's own
56
- # convention for that ; `Naming.snake` alone would leave a
57
- # creating command's bare name claiming the exact spelling a
58
- # query of the same business name also wants (`Account`'s own
59
- # "Open" — the creating command and a query listing open
60
- # accounts, a real same-aggregate collision this corpus already
61
- # has) — the suffix is what makes both nameable at all, not
62
- # merely a style choice.
31
+ # `!` marks a creating command (Ruby's mutate-and-may-refuse convention).
32
+ # Without it, a query sharing the same business name (e.g. Account's
33
+ # "Open") would collide with the creating command's own bare name.
63
34
  aggregate.commands.select(&:creates?).each do |command|
64
35
  door.define_singleton_method("#{Naming.snake(command.hecks_name)}!") do |**args|
65
36
  Handle.new(dispatcher: dispatcher, domain: domain, aggregate: aggregate,
@@ -67,12 +38,8 @@ module Hecks
67
38
  end
68
39
  end
69
40
 
70
- # A query is a module method too — same level as a creating
71
- # command, since neither needs an existing record in hand — but
72
- # bare: a query reads and returns, nothing to warn a caller
73
- # about the way `!` does for a command. Answers the raw row
74
- # array `dispatcher.query` itself answers, the same shape
75
- # `runtime.query("#{fqn}.Name")` already gave.
41
+ # A query is a bare module method (no `!`): it just reads and returns
42
+ # the same raw row array `dispatcher.query` itself answers.
76
43
  aggregate.queries.each do |query|
77
44
  door.define_singleton_method(Naming.snake(query.hecks_name)) do |**args|
78
45
  dispatcher.query("#{fqn}.#{query.hecks_name}", **args)
@@ -82,16 +49,9 @@ module Hecks
82
49
  door.define_singleton_method(:fqn) { fqn }
83
50
  door.define_singleton_method(:ir) { aggregate }
84
51
 
85
- # The same verb the chapter answers, one level down. Every
86
- # construct emits its own IR (Hecks::IR), so an aggregate
87
- # is a legitimate thing to project — `Pizzas::Order.project(
88
- # Projections::IR)` is this aggregate's IR, not the chapter's.
89
- #
90
- # A chapter-scoped target refuses here rather than inventing an
91
- # answer: `Projector.admits!` is what tells `Projections::IR`
92
- # (`from: :any`) apart from `Projections::Shape`
93
- # (`from: :chapter`), which would otherwise answer a confidently
94
- # empty `{"aggregates" => []}` for an aggregate.
52
+ # Every construct emits its own IR, so an aggregate is a legitimate
53
+ # `project` target too. A chapter-scoped target refuses here via
54
+ # `Projector.admits!` rather than answering a confidently empty result.
95
55
  door.define_singleton_method(:project) do |target, out: nil, **options|
96
56
  key = Projector.key_for(target)
97
57
  artifact = Projector.call(key, bluebook: aggregate, options: options)
@@ -104,16 +64,12 @@ module Hecks
104
64
  aggregate.commands.map { |c| "#{Naming.snake(c.hecks_name)}!" }.sort
105
65
  end
106
66
  door.define_singleton_method(:queries) { aggregate.queries.map { |q| Naming.snake(q.hecks_name) }.sort }
107
- # **One aggregate's usage document** — the same projection the chapter
108
- # answers with, narrowed to this head. `commands` above already
109
- # answers "what can I call"; this answers "and what does each one
110
- # want, refuse, and guarantee", which is the rest of the question.
67
+ # This chapter's own `:docs` projection, narrowed to this aggregate.
111
68
  door.define_singleton_method(:docs) do |**options|
112
69
  Projector.call(:docs, bluebook: dispatcher.registry.bluebook(domain),
113
70
  options: options.merge(aggregate: aggregate.hecks_name))
114
71
  end
115
- # **One aggregate, read back in english** — the same narrowing `:docs`
116
- # takes, aimed at `:narrate` instead.
72
+ # Same narrowing as `:docs`, aimed at `:narrate`.
117
73
  door.define_singleton_method(:narrate) do |**options|
118
74
  Projector.call(:narrate, bluebook: dispatcher.registry.bluebook(domain),
119
75
  options: options.merge(aggregate: aggregate.hecks_name))
@@ -132,44 +88,38 @@ module Hecks
132
88
  end
133
89
  end
134
90
 
135
- # The same reason `method_missing` below exists at all — a facade
136
- # left over from a previous boot in this process shadows the fresh
137
- # `BindingProxy` a `.hecksagon` would otherwise reach through
138
- # `ConstShim`/`const_missing`, so `Pizzas::Pizza.port(...)` lands
139
- # here instead once any boot has run before.
140
- #
141
- # Re-resolved, not the closed-over `aggregate` — this door can be a
142
- # stale one, built by a boot from earlier in this same process,
143
- # sitting on the `Pizzas`/`Pizza` constants only because nothing
144
- # has re-installed them since. Attaching to this door's own
145
- # `aggregate` would attach the port to a discarded aggregate from
146
- # that old boot, invisible to the current one actually being
147
- # loaded — silently, the exact way `method_missing` below already
148
- # has to avoid it for a plain bind, via `HecksagonBuilder.collector`
149
- # rather than anything this door closes over. `Hecks.current_registry`
150
- # is the same "whichever boot is actually in progress" indirection,
151
- # and `BindingProxy#port` already re-resolves through it the same
152
- # way — this is that door's fallback twin, not a shortcut past it.
91
+ # Re-resolves via `Hecks.current_registry` rather than the closed-over
92
+ # `aggregate`: this door can be stale from an earlier boot, and its own
93
+ # `aggregate` would silently port a discarded one instead of the live one.
153
94
  door.define_singleton_method(:port) do |name, &block|
154
95
  current = Hecks.current_registry&.bluebook(domain)&.aggregate(aggregate.hecks_name) or
155
96
  raise Bluebook::DSL::Malformed, "#{fqn}.port(#{name.inspect}) called outside a boot"
156
97
 
157
- domain_port = Bluebook::DSL::ConstShim.with(->(const) { const }) do
98
+ built = Bluebook::DSL::ConstShim.with(->(const) { const }) do
158
99
  Bluebook::DSL::DomainPortBuilder.build(name, owner: current.hecks_name, &block)
159
100
  end
160
- domain_port.operations.each do |operation|
101
+
102
+ # A `verb`-shaped port is a plain `Port`, the same struct `Hecks.port` registers —
103
+ # it belongs to no aggregate IR the way an operations-shaped `DomainPort` does, so
104
+ # it takes the registry's `add_port` directly. Mirrors `DSL::BindingProxy#port`,
105
+ # which handles the identical shape on a domain's first in-process boot (before
106
+ # this facade constant exists); this method is what a repeat boot reaches instead.
107
+ if built.is_a?(Bluebook::Port)
108
+ Hecks.current_registry.add_port(built)
109
+ return self
110
+ end
111
+
112
+ built.operations.each do |operation|
161
113
  operation.attributes.select(&:reference?).each { |attribute| attribute.type.declared_in = current }
162
114
  end
163
- current.add_port(domain_port)
115
+ current.add_port(built)
164
116
  self
165
117
  end
166
118
 
167
119
  door.define_singleton_method(:method_missing) do |verb, *args, **kwargs, &block|
168
- # A bare call starts a Privacy marking chain — see
169
- # `Bluebook::DSL::BindingProxy#method_missing`'s own header;
170
- # this is the same mechanism, reached when the constant is
171
- # already a real, installed door (a second boot in-process)
172
- # rather than a `.hecksagon`-parse-time `BindingProxy`.
120
+ # A bare call starts a Privacy marking chain (see
121
+ # `BindingProxy#method_missing`), reached here when the door is already
122
+ # installed (a second in-process boot) rather than at parse time.
173
123
  return Bluebook::DSL::AttributePath.new(fqn, [verb.to_s]) if args.empty? && kwargs.empty? && !block
174
124
 
175
125
  collector = Bluebook::DSL::HecksagonBuilder.collector
@@ -189,24 +139,9 @@ module Hecks
189
139
  !Bluebook::DSL::HecksagonBuilder.collector.nil? || super(name, include_private)
190
140
  end
191
141
 
192
- # The same stale-facade hazard `method_missing`/`port` above already
193
- # document, one door lower — `Surface.install` installs an aggregate's
194
- # own name as a bare top-level constant too (`Namespace.install(Object,
195
- # aggregate.hecks_name, ...)`, surface.rb's own `install`), not only
196
- # nested under its chapter. So once any domain has booted once in this
197
- # process, `Account::Debit` written while declaring some other
198
- # bluebook — same domain or a different one — reaches this door's
199
- # const_missing directly, never `Object.const_missing`/`ConstShim::
200
- # Hook` at all: real modules resolve without ever calling that.
201
- #
202
- # `aggregate.hecks_name`, not the qualified `fqn` — a scoped
203
- # reference has to read the same either way, whether or not a stale
204
- # door happens to be sitting on this process from an earlier boot;
205
- # qualifying it here would make `Account::Debit`'s own meaning
206
- # depend on incidental process history, which is the exact
207
- # instability S0b exists to remove (docs/dsl-work-slices.md — "two
208
- # domains in one registry" is this file's own reason for being
209
- # owned by that slice).
142
+ # Uses `aggregate.hecks_name`, not the qualified `fqn`: qualifying it here
143
+ # would make `Account::Debit`'s meaning depend on whether a stale door
144
+ # from an earlier boot happens to be sitting on this process.
210
145
  door.define_singleton_method(:const_missing) do |name|
211
146
  resolver = Bluebook::DSL::ConstShim.resolver
212
147
  return resolver.call("#{aggregate.hecks_name}::#{name}") if resolver
@@ -5,9 +5,8 @@ require_relative "../../bluebook/dsl/hecksagon_builder"
5
5
  module Hecks
6
6
  module Facade
7
7
  module Surface
8
- # One chapter's module: vision and aggregate roll-call on the
9
- # singleton, one aggregate door per declared head, and the
10
- # declaration hook for names the door does not carry.
8
+ # One chapter's module: vision and aggregate list on the singleton, one
9
+ # aggregate door per declared head, and a `const_missing` hook for undeclared names.
11
10
  module Chapter
12
11
  # Builds the anonymous module that stands for one booted chapter: `vision`,
13
12
  # `aggregates`, `docs`, `narrate` and `project` as singleton methods, plus one
@@ -24,49 +23,22 @@ module Hecks
24
23
  chapter.define_singleton_method(:vision) { bluebook.vision }
25
24
  chapter.define_singleton_method(:aggregates) { bluebook.aggregates.map(&:name).sort }
26
25
 
27
- # **The chapter, explaining itself**. `Projector::DocsProjector` reads
28
- # nothing but this bluebook's own IR, so the document cannot drift
29
- # from the domain — the same guarantee `bin/reference` gives the DSL
30
- # reference by generating it from the Syntax chapter.
31
- #
32
- # A method rather than only a script because that is what gets it
33
- # read: `QualityControl.docs` in a console, beside `vision` and
34
- # `aggregates`, at the moment somebody is wondering what a verb
35
- # wants. Projected on each call rather than memoised — it is a pure
36
- # read of the IR, and a stale document is the entire thing this
37
- # exists to prevent.
26
+ # The chapter's usage document, projected from its IR on each call so it
27
+ # cannot go stale.
38
28
  chapter.define_singleton_method(:docs) do |**options|
39
29
  Projector.call(:docs, bluebook: bluebook, options: options)
40
30
  end
41
31
 
42
- # **The chapter, read back in english** — `Projector::NarrateProjector`
43
- # beside `:docs`, for the reader who needs to confirm the domain is
44
- # right rather than call it: `QualityControl.narrate` in a console.
32
+ # The chapter read back in english, via `Projector::NarrateProjector`.
45
33
  chapter.define_singleton_method(:narrate) do |**options|
46
34
  Projector.call(:narrate, bluebook: bluebook, options: options)
47
35
  end
48
36
 
49
- # **The domain's own IR, projected**. `Projector` has taken
50
- # `call(name, bluebook:, options:)` since §30, but nothing could
51
- # reach it from a booted domain — this module already closes
52
- # over the one `Bluebook` every projector wants and simply
53
- # never handed it out, so every bin/ projector rebuilt a
54
- # Registry and reloaded the files to recover what was already
55
- # sitting here.
37
+ # Projects the chapter's IR through a target; `out:` writes the artifact and
38
+ # the remaining options go to the projector (`audience:` for OIDC).
56
39
  #
57
40
  # Pizzas.project(Projections::OIDC)
58
41
  # Pizzas.project(Projections::Shape, out: "shape.json")
59
- #
60
- # No `to_ir` companion: `project(Projections::IR)` already
61
- # answers with the canonical serialized form, and handing out
62
- # the live graph as well would be a second way to say the same
63
- # thing. Anything genuinely needing the graph is a projector,
64
- # and a projector is given it.
65
- #
66
- # `out:` writes and everything else is passed through to the
67
- # target — so `options` stays the projector's own vocabulary
68
- # (`audience:` for OIDC) rather than a fixed set this method
69
- # would have to know about.
70
42
  chapter.define_singleton_method(:project) do |target, out: nil, **options|
71
43
  key = Projector.key_for(target)
72
44
  artifact = Projector.call(key, bluebook: bluebook, options: options)
@@ -79,25 +51,9 @@ module Hecks
79
51
  chapter.const_set(aggregate.hecks_name, aggregate_module(dispatcher, bluebook.name, aggregate))
80
52
  end
81
53
 
82
- # Inside a hecksagon, a name is a declaration, not a lookup. A
83
- # `.hecksagon` may name an aggregate this door does not carry — a stale
84
- # door from an earlier boot resolving another registry's chapter, which
85
- # can happen because the facade is installed once a boot finishes, not
86
- # on every load. With a collector open, the name becomes a `BindingProxy`
87
- # recording the same bind the aggregate module would ; without one, it
88
- # is a genuine NameError.
89
- #
90
- # The other reader of a miss is S0b's own bridge (docs/dsl-work-
91
- # slices.md, const_shim.rb's `ScopedConstant`): a bluebook still being
92
- # declared that names `#{bluebook.name}::Something` — an event, a
93
- # command reference, an `admits:` — reaches here, not
94
- # `Object.const_missing`, the moment this chapter's own facade already
95
- # exists (a previous boot in the same process, or this same chapter
96
- # re-entering its own name). `ConstShim.active?` is exactly as true
97
- # here as it is at the top level, mid-declaration, and consulting the
98
- # same resolver is what makes a scoped reference resolve identically
99
- # whether or not a facade happens to be built yet — a bridge consulted
100
- # only at the top level works only before any facade exists.
54
+ # Inside a hecksagon a name is a declaration: with a collector open it becomes
55
+ # a `BindingProxy`. Otherwise a scoped reference from a bluebook mid-declaration
56
+ # goes to the `ConstShim` resolver, so it resolves whether or not a facade exists.
101
57
  chapter.define_singleton_method(:const_missing) do |name|
102
58
  collector = Bluebook::DSL::HecksagonBuilder.collector
103
59
  return Bluebook::DSL::BindingProxy.new("#{bluebook.name}::#{name}", collector) if collector
@@ -3,26 +3,13 @@ require_relative "surface/aggregate_door"
3
3
 
4
4
  module Hecks
5
5
  module Facade
6
- # **The door, without the classes**.
6
+ # The door without classes: anonymous per-boot modules whose singleton methods
7
+ # dispatch by FQN, installed by `Loader.bind_runtime` and replaced whole on the next boot.
7
8
  #
8
- # `Pizzas::Pizza.create_pizza!(...)` is the public surface (handover rule 3),
9
- # and this is what serves it now : anonymous per-boot modules whose
10
- # singleton methods close over the dispatcher and dispatch by FQN — the
11
- # same shape `Router::NamespaceInstaller` proved. Nothing here is a domain
12
- # class ; the IR is the only graph, and the door is a projection of it,
13
- # installed by `Loader.bind_runtime` and re-installed whole on the next
14
- # boot. How a chapter's module is built is surface/chapter.rb; how one
15
- # aggregate's door is built is surface/aggregate_door.rb.
16
- #
17
- # The hexagon-binding door rides along : `Pizzas::Pizza.persisted_by("Heki")`
18
- # in a `.hecksagon` file lands on the module's `method_missing`, which
19
- # records an `Bind` into whatever `HecksagonBuilder.collector` is open
20
- # at call time — so even a facade left over from a previous boot records
21
- # into the current builder, and a chapter with no constant at all falls
22
- # through to `ConstShim` → `BindingProxy`, which mints byte-identical binds.
9
+ # `persisted_by("Heki")` in a `.hecksagon` file lands on the module's `method_missing`,
10
+ # which records a `Bind` into the open `HecksagonBuilder.collector`.
23
11
  module Surface
24
- # These would shadow the machinery a Handle runs on, so a field named one
25
- # of them gets no reader and says so — the same warning the class door gave.
12
+ # Names that would shadow the machinery a Handle runs on; a field with one gets no reader.
26
13
  RESERVED = %i[id state events reload inspect to_h hash class].freeze
27
14
 
28
15
  extend Chapter
@@ -34,14 +21,12 @@ module Hecks
34
21
  # closing over `dispatcher`, so `Pizzas::Pizza` and the bare `Pizza` both open the
35
22
  # door of the boot that ran last.
36
23
  #
37
- # An aggregate sharing its chapter's name gets no second constant: the chapter
38
- # module already holds that name. A name that user code or the stdlib already owns
39
- # is left alone with a warning (see `Namespace.install`).
24
+ # An aggregate sharing its chapter's name gets no second constant. A name that user
25
+ # code or the stdlib already owns is left alone with a warning (see `Namespace.install`).
40
26
  #
41
27
  # @param dispatcher [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted
42
28
  # dispatcher every installed door dispatches and queries through
43
- # @return [Runtime::Dispatcher, Runtime::RemoteDispatcher] the same `dispatcher`,
44
- # so a boot can return the call's result
29
+ # @return [Runtime::Dispatcher, Runtime::RemoteDispatcher] the same `dispatcher`
45
30
  # @raise [NameError] if a chapter or aggregate name is not a valid constant name
46
31
  def install(dispatcher)
47
32
  dispatcher.registry.bluebooks.each_value do |bluebook|
data/lib/hecks/facade.rb CHANGED
@@ -1,39 +1,24 @@
1
1
  module Hecks
2
- # The class-free public surface — see facade/surface.rb for the door and
3
- # facade/handle.rb for the record in hand. Installed per boot by
4
- # `Runtime::Loader.bind_runtime`.
2
+ # The class-free public surface, installed per boot by `Runtime::Loader.bind_runtime`.
5
3
  module Facade
6
4
  end
7
5
 
8
- # The one careful way to put something at top level.
9
- #
10
- # Overwrites only what it itself installed (tracked in `GENERATED`), refuses —
11
- # with a warning, never a raise — to clobber a constant belonging to user
12
- # code or the stdlib. Which is why a chapter named `Set` never becomes a
13
- # constant, and why `Registry` keeps its own table of chapters rather than
14
- # trusting Ruby's. Each re-install replaces the previous boot's entry, so
15
- # what `GENERATED` retains is one small facade module per name, not a graph
16
- # per boot.
6
+ # Installs facade constants at top level, replacing only what it installed itself.
7
+ # A constant owned by user code or the stdlib is left alone with a warning.
17
8
  module Namespace
18
- # Not frozen — a real registry, mutated below
19
- # (`GENERATED[[container, name]] = value`). False positive for
20
- # Style/MutableConstant.
9
+ # A live registry, mutated by `install`, so it cannot be frozen.
21
10
  # rubocop:disable-next Style/MutableConstant
22
11
  GENERATED = {}
23
12
 
24
13
  module_function
25
14
 
26
- # Sets `container::name` to `value`, replacing only a constant this module installed
27
- # itself and leaving any other existing constant in place with a warning.
15
+ # Sets `container::name` to `value`, replacing only a constant this module installed.
28
16
  #
29
- # @param container [Module] the namespace to define the constant in; every caller in
30
- # this repository passes `Object`
31
- # @param name [String, Symbol] the constant name, such as a chapter or aggregate name
32
- # @param value [Module] the facade module to install under that name
33
- # @return [Object] `value` when it was installed; otherwise the constant already
34
- # defined under `name`, which this module does not own and did not replace
35
- # @raise [NameError] if `name` is not a valid constant name (raised by
36
- # `const_defined?`)
17
+ # @param container [Module] the namespace to define the constant in
18
+ # @param name [String, Symbol] the constant name
19
+ # @param value [Module] the facade module to install
20
+ # @return [Object] `value` when installed, else the foreign constant left in place
21
+ # @raise [NameError] if `name` is not a valid constant name
37
22
  def install(container, name, value)
38
23
  name = name.to_s
39
24
 
@@ -12,18 +12,8 @@ require_relative "../facade/json_door"
12
12
 
13
13
  module Hecks
14
14
  module Forms
15
- # The content-negotiated router: `GET /Banking/Account/Overdrawn.html`
16
- # renders the query view built in this directory; the identical path
17
- # with no extension (or `.json`) dispatches the same ask through
18
- # `Runtime::Dispatcher#query` and answers with its raw result — one
19
- # route, two representations, exactly the "change the file format on
20
- # the route" mechanism this was built around
21
- # (docs/command-form-and-query-form-bluebook.md).
22
- #
23
- # A plain Rack app (`#call(env)`) — no Sinatra, no Rails. `rack` itself
24
- # is a lazy dependency the same way `pg`/`oauth2`/`aws-sdk-lambda` are
25
- # for their own adapters (see the Gemfile's own comment) : a project
26
- # that never boots this file never needs it installed.
15
+ # A Rack app that content-negotiates one route: `/Banking/Account/Overdrawn.html`
16
+ # renders HTML, and the same path with no extension or `.json` answers JSON.
27
17
  class App
28
18
  # Builds the Rack app for a configured app name, exposing exactly the chapters its
29
19
  # `Forms.configure` block declared.
@@ -99,18 +89,8 @@ module Hecks
99
89
  raise RouteNotFound, "#{domain.inspect} is not exposed by this app — declared chapters: #{@exposed.join(', ')}"
100
90
  end
101
91
 
102
- # H12 (docs/audits/2026-08-10-main-bug-audit.md) — only a literal
103
- # trailing ".html"/".json" counts as a format; every other dot in the
104
- # segment is just part of the identity. Splitting on the first "."
105
- # instead truncates any identity value containing a dot (an email
106
- # `identified_by { email.address }`, a decimal-ish reference — an
107
- # aggregate's identity is free-form unless its value object declares
108
- # a `pattern:`, see S3 in the same audit) at its own first dot, so
109
- # `reference.value=c.1` 404s everywhere: detail page, JSON view, and
110
- # its own index-table link. An identity that itself happens to end in
111
- # exactly ".html" or ".json" is still ambiguous with a real format
112
- # suffix — the same tension any extension-based content-negotiation
113
- # scheme has, and not what H12 is about.
92
+ # Only a trailing ".html"/".json" is a format; other dots belong to the identity
93
+ # (an email, say). An identity ending in ".html" or ".json" stays ambiguous.
114
94
  def split_format(segment)
115
95
  segment = segment.to_s
116
96
  return [Regexp.last_match(1), Regexp.last_match(2)] if segment =~ /\A(.*)\.(html|json)\z/
@@ -118,8 +98,6 @@ module Hecks
118
98
  [segment, "json"]
119
99
  end
120
100
 
121
- # ---- home -------------------------------------------------------
122
-
123
101
  def home(request)
124
102
  format = request.params["format"] || "html"
125
103
  chapters = @exposed.to_h { |name| [name, @registry.bluebook(name)] }.compact
@@ -128,8 +106,6 @@ module Hecks
128
106
  html("Exposed domains", IndexRenderer.render(chapters))
129
107
  end
130
108
 
131
- # ---- /:domain/:aggregate.:format ---------------------------------
132
-
133
109
  def aggregate_route(request, chapter, aggregate_name, format)
134
110
  aggregate = find_aggregate(chapter, aggregate_name)
135
111
  return respond(405, "text/plain", "GET only") unless request.get?
@@ -140,31 +116,17 @@ module Hecks
140
116
  breadcrumbs: [[chapter.name, "/"], [aggregate.hecks_name, nil]])
141
117
  else
142
118
  instances = @registry.repository(chapter.name, aggregate).all
143
- # id last — see Instance#to_h's own comment: an aggregate free
144
- # to declare its own attribute literally named `id` has that
145
- # attribute's own wrapped value sitting in `i.state[:id]`
146
- # already, which silently clobbers the correct bare identity
147
- # if `id:` is merged first.
119
+ # `id:` is merged last so an attribute named `id` in the state cannot clobber it.
148
120
  json(200, instances.map { |i| i.state.merge(id: i.id) })
149
121
  end
150
122
  end
151
123
 
152
- # ---- /:domain/:aggregate/:verb_or_id.:format ----------------------
153
-
154
124
  def verb_or_record_route(request, chapter, aggregate_name, verb_or_id, format)
155
125
  aggregate = find_aggregate(chapter, aggregate_name)
156
126
  domain = chapter.name
157
127
 
158
- # L11 (docs/audits/2026-08-10-main-bug-audit.md) — a record's own
159
- # id is free-form (S3) and can collide with one of its own
160
- # aggregate's command/query names ("Close", "Overdrawn", ...).
161
- # A GET for such an id must still be able to reach that record's
162
- # own detail page when a record with that literal id actually
163
- # exists — checking the verb first would mean a record unlucky
164
- # enough to be named after a real verb could never be viewed.
165
- # POST never means "view a record" at all (`record_route` only
166
- # ever answers GET), so command submission there is unambiguous
167
- # and matches the verb first.
128
+ # A record id is free-form and can equal a verb name ("Close"), so a GET checks for
129
+ # a record first. POST never views a record, so it matches the verb first.
168
130
  if request.get? && (instance = @registry.repository(domain, aggregate).find(verb_or_id))
169
131
  return record_route(request, domain, aggregate, verb_or_id, format, instance: instance)
170
132
  end
@@ -204,8 +166,7 @@ module Hecks
204
166
  def submit_command(request, domain, aggregate, command, action)
205
167
  raw, envelope = submitted_command(request, aggregate, command)
206
168
  result = @dispatcher.dispatch_flat("#{domain}::#{aggregate.hecks_name}.#{command.hecks_name}", envelope)
207
- # L12 — the id is free-form (S3), so it must be percent-encoded as
208
- # a path segment here, not just interpolated raw.
169
+ # The id is free-form, so it is percent-encoded as a path segment.
209
170
  redirect("/#{domain}/#{aggregate.hecks_name}/#{Escape.path(result.id)}.html")
210
171
  rescue *Runtime::DOMAIN_REFUSALS, ArgumentError, TypeError, JSON::ParserError => e
211
172
  status = e.is_a?(Runtime::NotFound) ? 404 : 422
@@ -218,8 +179,7 @@ module Hecks
218
179
 
219
180
  _, envelope = submitted_command(request, aggregate, command)
220
181
  result = @dispatcher.dispatch_flat("#{domain}::#{aggregate.hecks_name}.#{command.hecks_name}", envelope)
221
- # id last — same reasoning as the other JSON-serializing call
222
- # sites in this file (see aggregate_route's own comment).
182
+ # `id:` last, as in `aggregate_route`.
223
183
  json(201, result.state.merge(id: result.id))
224
184
  rescue *Runtime::DOMAIN_REFUSALS, ArgumentError, TypeError, JSON::ParserError => e
225
185
  status = e.is_a?(Runtime::NotFound) ? 404 : 422
@@ -255,30 +215,18 @@ module Hecks
255
215
  results, error = run_query(domain, aggregate, query, fields, asked)
256
216
  return json(422, { error: error.class.name.split("::").last, message: error.message }) if error
257
217
 
258
- # id last — same reasoning as the other JSON-serializing call
259
- # sites in this file (see aggregate_route's own comment).
218
+ # `id:` last, as in `aggregate_route`.
260
219
  json(200, results.map { |i| i.state.merge(id: i.id) })
261
220
  end
262
221
 
263
- # `Dispatcher#query` answers an array of plain hashes
264
- # (`{id:}.merge(state)`, flattened by `QueryInterpreter#call` on
265
- # every path it can take — native pushdown or the in-memory
266
- # fallback alike), not `Runtime::Instance`. Wrapped into `Record`
267
- # here, once, so every renderer downstream reads one shape whether
268
- # its records came from a query or straight off a repository.
222
+ # `Dispatcher#query` answers plain hashes, not `Runtime::Instance`; wrapping them
223
+ # in `Record` gives the renderers one shape.
269
224
  def run_query(domain, aggregate, query, fields, asked)
270
225
  args = Params.extract(fields, asked)
271
226
  rows = @dispatcher.query("#{domain}::#{aggregate.hecks_name}.#{query.hecks_name}", **args)
272
227
  [rows.map { |row| Record.new(row[:id], row.except(:id)) }, nil]
273
- # L10 (docs/audits/2026-08-10-main-bug-audit.md) — `Params.extract`
274
- # (params.rb's `extract_list`) reads a list-of-value-object line as
275
- # JSON (the honest fallback for a multi-attribute list element this
276
- # prototype's textarea doesn't build a second widget for). A caller
277
- # who types a non-JSON line into that field raises `JSON::ParserError`
278
- # before dispatch ever sees it. It is rescued here, as both command
279
- # submission paths rescue it (`submit_command`, `command_json`), so
280
- # a malformed list-of-VO query shows the same 422 every other
281
- # bad-input path shows rather than a 500.
228
+ # `Params.extract` parses a list-of-value-object line as JSON; rescuing
229
+ # `JSON::ParserError` turns a malformed line into a 422 rather than a 500.
282
230
  rescue *Runtime::DOMAIN_REFUSALS, ArgumentError, TypeError, JSON::ParserError => e
283
231
  [nil, e]
284
232
  end
@@ -288,8 +236,7 @@ module Hecks
288
236
 
289
237
  instance ||= @registry.repository(domain, aggregate).find(id)
290
238
  return not_found(aggregate, id, format) unless instance
291
- # id last — same reasoning as the other JSON-serializing call
292
- # sites in this file (see aggregate_route's own comment).
239
+ # `id:` last, as in `aggregate_route`.
293
240
  return json(200, instance.state.merge(id: instance.id)) if format != "html"
294
241
 
295
242
  html("#{domain}::#{aggregate.hecks_name} #{id}",
@@ -339,8 +286,6 @@ module Hecks
339
286
  raw.merge("to" => raw["id"])
340
287
  end
341
288
 
342
- # ---- rendering ----------------------------------------------------
343
-
344
289
  def html(title, body, breadcrumbs: [], status: 200)
345
290
  respond(status, "text/html; charset=utf-8", Page.render(title: title, body: body, breadcrumbs: breadcrumbs))
346
291
  end