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
@@ -1,15 +1,5 @@
1
- # ITS OWN BOUNDED CONTEXT, booted centrally like `Deploy` — never
2
- # attached via `uses_framework` (a framework member's aggregate folds
3
- # into the ATTACHING domain's own per-boot registry, framework.rb:43-66
4
- # — since hecks gives each tenant its own separate `Runtime.boot`, a
5
- # framework-attached Tenant would exist as a single-row copy inside
6
- # EVERY tenant's own boot, never as one cross-tenant list, defeating
7
- # this domain's whole point). `Deploy::Tenant` (deploy.bluebook) stays
8
- # a SEPARATE, validation-only aggregate — a shape check before
9
- # provisioning; this is the durable, reactable runtime fact that
10
- # aggregate's own comment named and deliberately deferred. Two
11
- # different meanings of "Tenant," on purpose, translated at the one
12
- # explicit seam `tenancy.hecksagon`'s own `translates` line names.
1
+ # Its own bounded context, booted centrally like `Deploy` and never attached via `uses_framework`:
2
+ # an attached Tenant would be a single-row copy in every tenant's boot, not one cross-tenant list.
13
3
  Hecks.bluebook "Tenancy" do
14
4
  vision "Which tenants exist, which realm and storage each one owns, and whether each is currently allowed to run, is one queryable, historical fact — not scattered across a world-file overlay and a validation-only command."
15
5
 
@@ -50,14 +40,8 @@ Hecks.bluebook "Tenancy" do
50
40
  attribute :value, String, one_of: ["Active", "Suspended", "Retired"]
51
41
  end
52
42
 
53
- # FIRES AFTER PROVISIONING, NEVER BEFORE — a pure record with no
54
- # side effects, dispatched by `tenancy.hecksagon`'s own `translates`
55
- # reaction once `Deploy::Tenant.Provision`'s adapter has already
56
- # written the world overlay and proven the domain boots. Driving
57
- # provisioning FROM this command's own emission would need a
58
- # process manager reacting to it — the exact durable-saga cost
59
- # `deploy.bluebook`'s own `Tenant` aggregate comment already argued
60
- # three local, idempotent, single-process steps don't need.
43
+ # Fires after provisioning, never before: driving provisioning from this command's emission
44
+ # would need a process manager, the durable-saga cost `deploy.bluebook` argues against.
61
45
  command "Register" do
62
46
  goal "Record that a tenant now exists, after its shape has been validated and its schema provisioned"
63
47
 
@@ -74,13 +58,8 @@ Hecks.bluebook "Tenancy" do
74
58
  emits "TenantRegistered"
75
59
  end
76
60
 
77
- # RECORD-ONLY — nothing today reads `status` before dispatch or
78
- # boot, so suspending a tenant here does not yet stop anything from
79
- # running. Spelled out in this command's own `goal`, not just a
80
- # comment, so it survives into generated docs/corpus rather than
81
- # quietly reading as enforced when it isn't — the exact "a dial
82
- # says a check runs when nothing runs it" defect class this
83
- # project has already been burned by once.
61
+ # Record-only: nothing reads `status` before dispatch or boot yet. Stated in the `goal` too,
62
+ # so generated docs do not read it as enforced.
84
63
  command "Suspend" do
85
64
  goal "Record that a tenant should no longer be treated as active — nothing yet enforces this at dispatch or boot time"
86
65
 
@@ -101,11 +80,8 @@ Hecks.bluebook "Tenancy" do
101
80
  emits "TenantReactivated"
102
81
  end
103
82
 
104
- # RECORD-ONLY, DELIBERATELY — dropping a live Postgres schema as an
105
- # automatic side effect of a command handler would break the pure,
106
- # no-adapter-calls-from-a-handler shape every other command in this
107
- # corpus holds to. Real teardown, if ever built, is its own
108
- # separate, deliberately reviewed tool — never implicit here.
83
+ # Record-only: dropping a live schema from a command handler would break the pure, no-adapter
84
+ # handler shape. Teardown, if built, is a separate reviewed tool.
109
85
  command "Retire" do
110
86
  goal "Record that a tenant is permanently done — real schema teardown is a deliberately separate, manually-run tool, never automatic here"
111
87
 
@@ -1,30 +1,13 @@
1
1
  Hecks.hecksagon "Tenancy" do
2
2
  Tenancy::Tenant.persisted_by("LocalStorage")
3
3
 
4
- # THE TRANSLATION SEAM — the one explicit place Deploy's "Tenant"
5
- # (a validated request) becomes Tenancy's "Tenant" (a durable fact).
6
- # `on` deliberately omits the "Deploy::" prefix: PolicyInterpreter
7
- # matches by event name plus the emitting aggregate's own demodulised
8
- # name only, never the domain (confirmed live — a domain-qualified
9
- # `on` silently never matches).
4
+ # The one seam where Deploy's "Tenant" (a validated request) becomes Tenancy's "Tenant" (a fact).
5
+ # `on` omits the "Deploy::" prefix: PolicyInterpreter matches event and aggregate name only.
6
+ # `with:` is required: the event payload also carries the port's dispatch-time args, which
7
+ # `Register` does not declare and would refuse.
10
8
  #
11
- # `with:` IS REQUIRED, confirmed live — `TenantProvisioned`'s own
12
- # payload is the ANSWER (slug/domain/realm/schema) merged with the
13
- # port operation's OWN dispatch-time args (database/adapter,
14
- # PortOperationInterpreter#ask's `ctx.args.merge(spread(answer))`),
15
- # so the whole payload forwarded verbatim carries two fields
16
- # `Register` never declares and gets refused outright.
17
- # CHECKED TOGETHER, NOT IN ISOLATION — `bin/model_check`'s own
18
- # `deaf_policy` check only sees ONE domain's own emitted events by
19
- # default, which would flag this reaction as deaf (it reacts to a
20
- # FOREIGN event, not one Tenancy itself emits). Fixed by
21
- # `global_emitted_events:`, a cross-domain set `bin/model_check`
22
- # builds across every target it boots in one run — run `bin/model_check
23
- # lib/hecks/deploy lib/hecks/tenancy` (both targets, one invocation),
24
- # not each directory separately, or this reads as a false positive
25
- # again. Neither `deploy` nor `tenancy` is in
26
- # `Hecks::Corpus::MODEL_CHECK_KINDS`, so the automatic sweep never
27
- # reaches this at all, checked or not.
9
+ # Check with `bin/model_check lib/hecks/deploy lib/hecks/tenancy` in one run; alone, the
10
+ # reaction to a foreign event reads as a deaf policy.
28
11
  translates "RegisterProvisionedTenant" do
29
12
  on Tenant::TenantProvisioned
30
13
  trigger Tenant::Register, with: { slug: :slug, domain: :domain, realm: :realm, schema: :schema }
@@ -1,13 +1,7 @@
1
1
  module Hecks
2
2
  module Vendoring
3
- # The environment a `git` subprocess needs so it resolves its repository
4
- # from the directory it is pointed at, not from whoever launched Ruby.
5
- #
6
- # Git exports `GIT_DIR`, `GIT_INDEX_FILE` and their kin to the hooks it
7
- # runs (a push from a linked worktree runs `pre-push` this way). Every
8
- # `git` that a hook's process tree starts inherits them, and they outrank
9
- # both `git -C` and the working directory: a command aimed at a scratch
10
- # repository would read, or commit into, the repository that was pushing.
3
+ # The environment a `git` subprocess needs to target one repository:
4
+ # inherited GIT_DIR variables outrank `-C` and would redirect it elsewhere.
11
5
  module GitEnvironment
12
6
  # The variables that pin git to one repository, work tree or index.
13
7
  INHERITED = %w[
@@ -4,14 +4,8 @@ require_relative "git_environment"
4
4
 
5
5
  module Hecks
6
6
  module Vendoring
7
- # A local git repository read through the `git` executable, never through
8
- # its working tree.
9
- #
10
- # Everything here names a commit and reads what that commit holds, so a
11
- # shared checkout with uncommitted edits, or a different branch checked
12
- # out, cannot leak into what gets vendored. Every command runs under
13
- # {GitEnvironment.clean}, so a `GIT_DIR` inherited from a hook cannot
14
- # redirect it to some other repository.
7
+ # Reads one commit of a local git repository via the `git` executable,
8
+ # under {GitEnvironment.clean} so an inherited `GIT_DIR` can't redirect it.
15
9
  class GitSource
16
10
  # @param path [String] a directory that is a git repository (or a
17
11
  # worktree of one)
@@ -57,8 +51,8 @@ module Hecks
57
51
 
58
52
  # Lists the files directly inside a directory of a commit.
59
53
  #
60
- # Subdirectories are left out on purpose: a vendored package carries the
61
- # top-level files of its subtree and nothing nested beneath.
54
+ # Subdirectories are excluded on purpose; only the subtree's own
55
+ # top-level files are vendored.
62
56
  #
63
57
  # @param ref [String] the commit-ish to read from
64
58
  # @param subtree [String] a repository-relative directory, such as `"payments/bluebook"`
@@ -5,29 +5,7 @@ require_relative "vendoring/git_source"
5
5
 
6
6
  module Hecks
7
7
  # Pins an exact commit of one directory of another repository into a
8
- # project's own tree.
9
- #
10
- # ## What it is
11
- #
12
- # A project that consumes a package from a private repository cannot clone
13
- # that repository when its image builds, and a live pull would make the build
14
- # depend on whatever is checked out. So the files are exported from one
15
- # commit, committed into the consumer, and a marker file beside them records
16
- # which commit they came from. Only the files directly inside the named
17
- # subtree are taken; the source's own specs, ports, adapters and data stay
18
- # where they are.
19
- #
20
- # ## Marker
21
- #
22
- # `VENDORED_COMMIT` holds the full 40-character commit id and a newline, so
23
- # `git -C <source> show <id>:<subtree>` reproduces the vendored files. The
24
- # marker sits in `into`, beside the exported directory.
25
- #
26
- # ## Whole-directory replacement
27
- #
28
- # A pin replaces `into` entirely, so a file the source dropped does not
29
- # linger. Nothing is touched until the export succeeded and the optional
30
- # block accepted it.
8
+ # project's own tree, so a build doesn't depend on a live clone.
31
9
  module Vendoring
32
10
  # Raised when a pin cannot be made or is refused.
33
11
  class Error < StandardError; end
@@ -45,27 +23,17 @@ module Hecks
45
23
  # @return [Array<String>] the exported files' names, sorted
46
24
  Pin = Struct.new(:commit, :dir, :files, keyword_init: true)
47
25
 
48
- # Exports the top-level files of one directory at one commit into `into`.
49
- #
50
- # The exported files land in `into/<last segment of subtree>/`; the marker
51
- # lands in `into` itself. The optional block sees the staged files before
52
- # anything is replaced and may raise to refuse the pin.
26
+ # Exports the top-level files of one directory at one commit into `into`,
27
+ # replacing it wholly; the optional block may refuse before that happens.
53
28
  #
54
29
  # @param from [String] path of the local source repository
55
- # @param ref [String] the commit-ish to export (branch, tag or commit id)
56
- # @param subtree [String] repository-relative directory to export, such as
57
- # `"payments/bluebook"`
30
+ # @param ref [String] the commit-ish to export
31
+ # @param subtree [String] repository-relative directory to export
58
32
  # @param into [String] the directory to replace with the export
59
- # @param glob [String] `File.fnmatch` pattern each exported file's name must match
33
+ # @param glob [String] pattern each exported file's name must match
60
34
  # @param marker [String] name of the file recording the commit
61
- # @yield [staged, commit] the staged files, before `into` is touched
62
- # @yieldparam staged [String] directory holding the exported files
63
- # @yieldparam commit [String] the resolved commit id
64
- # @yieldreturn [Hash{String => String}, nil] extra files to write into `into`,
65
- # keyed by file name; nil for none
66
35
  # @return [Pin] the commit and where the files landed
67
- # @raise [Vendoring::Error] if the source is missing, `ref` names no commit,
68
- # the subtree holds no matching file, or the block refuses
36
+ # @raise [Vendoring::Error] if the source or a matching file is missing
69
37
  def self.pin(from:, ref:, subtree:, into:, glob: "*", marker: MARKER)
70
38
  source = GitSource.new(from)
71
39
  commit = source.commit(ref)
@@ -83,13 +51,6 @@ module Hecks
83
51
  end
84
52
 
85
53
  # Replaces `into` with the staged files, the marker and any extras.
86
- #
87
- # @param staged [String] directory holding the exported files
88
- # @param into [String] the directory to replace
89
- # @param marker [String] marker file name
90
- # @param commit [String] commit id the marker records
91
- # @param extras [Hash{String => String}] more files to write into `into`
92
- # @return [void]
93
54
  def self.install(staged, into, marker, commit, extras)
94
55
  FileUtils.rm_rf(into)
95
56
  FileUtils.mkdir_p(into)
data/lib/hecks/version.rb CHANGED
@@ -1,16 +1,5 @@
1
1
  module Hecks
2
- # **Isolated, on purpose** — the gemspec reads this file directly rather
3
- # than `require_relative "lib/hecks"` (the whole framework),
4
- # specifically so evaluating the gemspec never triggers any of this
5
- # gem's own dependencies (prism among them) before Bundler has even
6
- # resolved what to install. A real, live chicken-and-egg bug caught
7
- # deploying to a Ruby version that doesn't bundle prism for free
8
- # (AWS Lambda's ruby3.2): the gemspec's own `require_relative
9
- # "lib/hecks"` pulled in adapters/driven/prism.rb's
10
- # unconditional `require "prism"`, which failed before Bundler ever
11
- # read what the gemspec itself declared as a dependency — declaring
12
- # it there, or even in the consuming Gemfile, never closed the gap,
13
- # because gemspec evaluation happens before anything Bundler
14
- # resolves is actually loadable yet.
15
- VERSION = "2.8.0".freeze
2
+ # Read directly by the gemspec, so this file must never require the
3
+ # rest of the gem (its dependencies aren't resolved yet at that point).
4
+ VERSION = "2.9.0".freeze
16
5
  end
data/lib/hecks.rb CHANGED
@@ -42,65 +42,33 @@ require_relative "hecks/embryonaut_bluebook"
42
42
  # packaged gem leaves it out (ADR 0066).
43
43
  Hecks.autoload(:Corpus, File.expand_path("hecks/corpus", __dir__))
44
44
 
45
- # The root namespace and public facade of the whole DSL/runtime: `Hecks.boot`/
46
- # `.boot_files` assemble a running domain from `.bluebook`/`.hecksagon`/
47
- # `.world` files, and `.bluebook`/`.hecksagon`/`.port`/`.adapter`/`.world`/
48
- # `.data_translation` are the top-level declaration words every such file
49
- # opens with, each collecting its built construct into the currently booting
50
- # Registry (`#collect`, private below).
45
+ # Root namespace and public facade of the DSL/runtime: `Hecks.boot`/`.boot_files`
46
+ # assemble a running domain from `.bluebook`/`.hecksagon`/`.world` files.
51
47
  module Hecks
52
48
  class LoadOutsideBoot < StandardError; end
53
49
 
54
50
  class << self
55
- # The loading words below collect into the registry the runtime is
56
- # holding open ; booting and that ambient state belong to the runtime
57
- # layer, so this module is their facade and Hecks::Runtime is where
58
- # they live.
59
- # `install_facade:` — see Runtime::Loader.boot. Defaults on; a caller
60
- # that only dispatches by FQN string can skip the global sugar.
61
- #
62
- # `environment:` — recovered, not new: this parameter (and the
63
- # `environments/<name>.hecksagon` / `.world` overlay it loads —
64
- # see Adapters::Folder#load_domain) existed on a prior commit of
65
- # this repo (933d1dd), was vendored out to a real consumer
66
- # (a client site's domain), and was then lost from this repo's own
67
- # history (no branch here reaches that commit). Ported forward
68
- # from the consumer's vendor snapshot — the only surviving copy —
69
- # and generalized: the original only loaded a `.hecksagon`
70
- # overlay; this also loads a same-named `.world` overlay, both
71
- # merged into the base rather than replacing it (Registry#add_hecksagon
72
- # / #add_world). hecks never reads ENV itself — a caller
73
- # resolves its own env var name and passes the resulting string
74
- # straight through, e.g. `Hecks.boot(path, environment:
75
- # ENV.fetch("MYAPP_ENV", "development"))`.
76
- #
51
+ # Boots a domain from `path`, assembling a running dispatcher from its
52
+ # `.bluebook`/`.hecksagon`/`.world` files. hecks never reads ENV itself —
53
+ # a caller resolves its own env var name and passes it as `environment`.
77
54
  # @param path [String] path to a domain directory, or a file inside one
78
55
  # @param shared [String, nil] a shared-root override; see `Runtime::Loader.boot`
79
- # @param install_facade [Boolean] whether to install the `Widget::Item.Add`-style
80
- # Ruby facade constants for this boot
81
- # @param environment [String, nil] the environment name whose
82
- # `environments/<name>.hecksagon`/`.world` overlay, if present, loads after the
83
- # domain's own
84
- # @return [Runtime::Dispatcher, Runtime::RemoteDispatcher] the dispatcher bound
85
- # to the booted domain
56
+ # @param install_facade [Boolean] install the `Widget::Item.Add`-style facade
57
+ # @param environment [String, nil] env name; its `.hecksagon`/`.world` overlay,
58
+ # if present, loads after the domain's own
59
+ # @return [Runtime::Dispatcher, Runtime::RemoteDispatcher] dispatcher bound to the domain
86
60
  def boot(path, shared: nil, install_facade: true, environment: nil)
87
61
  Runtime.boot(path, shared: shared, install_facade: install_facade, environment: environment)
88
62
  end
89
63
 
90
- # `paths` — an explicit list of files to boot (a `.bluebook`, its
91
- # `.hecksagon`, optionally a `.world`), loaded in place from wherever
92
- # they actually live — see Runtime::Loader.boot_files's own header for
93
- # why this exists beside `boot` rather than as a special case of it.
94
- #
64
+ # Boots a domain from an explicit list of files (a `.bluebook`, its
65
+ # `.hecksagon`, optionally a `.world`) instead of a whole directory —
66
+ # see `Runtime::Loader.boot_files` for why this exists beside `boot`.
95
67
  # @param paths [String, Array<String>] one or more file paths within the domain
96
- # to load, instead of the whole directory
97
68
  # @param shared [String, nil] a shared-root override; see `Runtime::Loader.boot_files`
98
- # @param install_facade [Boolean] whether to install the `Widget::Item.Add`-style
99
- # Ruby facade constants for this boot
100
- # @param environment [String, nil] the environment name passed through to the
101
- # selected-file loader
102
- # @return [Runtime::Dispatcher, Runtime::RemoteDispatcher] the dispatcher bound
103
- # to the booted domain
69
+ # @param install_facade [Boolean] install the `Widget::Item.Add`-style facade
70
+ # @param environment [String, nil] environment name passed to the selected-file loader
71
+ # @return [Runtime::Dispatcher, Runtime::RemoteDispatcher] dispatcher bound to the domain
104
72
  def boot_files(paths, shared: nil, install_facade: true, environment: nil)
105
73
  Runtime.boot_files(paths, shared: shared, install_facade: install_facade, environment: environment)
106
74
  end
@@ -117,21 +85,8 @@ module Hecks
117
85
  # @return [Runtime::Registry, nil] the current registry, or nil outside a boot
118
86
  def current_registry = Runtime.current_registry
119
87
 
120
- # Bind who is dispatching for the duration of the block — checked
121
- # against a command's declared `role`, if it has one. Unbound (the
122
- # default), a command's role stays exactly what it is without this:
123
- # decoration.
124
- #
125
- # `actor_id` is optional — a caller naming only a role is checked by
126
- # string equality against the command's own `role`, exactly as
127
- # before. A caller that also names who it is lets the check run
128
- # against a real Governance `RoleAssignment` instead, once the
129
- # command's domain has Governance attached — see
130
- # `CommandRules::Authorization`'s own header.
131
- #
132
- # `as_of` and `scope` are optional too, same shape — see
133
- # `Runtime.as_caller`'s own header for what each does.
134
- #
88
+ # Binds who is dispatching for the duration of the block, checked against
89
+ # a command's declared `role`; unbound (the default) is a no-op.
135
90
  # @param role [String, Symbol] the role to check the caller against
136
91
  # @param actor_id [String, nil] who is calling, checked against a real Governance
137
92
  # `RoleAssignment` when given; string-equality only against `role` when nil
@@ -165,26 +120,16 @@ module Hecks
165
120
  # @return [Bluebook::Hecksagon] the built hecksagon
166
121
  # @raise [LoadOutsideBoot] if called outside `Hecks.boot`/`.boot_files`
167
122
  def hecksagon(name, &) = collect(:add_hecksagon, Bluebook::DSL::HecksagonBuilder.build(name, &))
168
- # Repointed to DomainPortBuilder — the migration DomainPort's own
169
- # class comment names as its goal, now landed for the top-level
170
- # `.port` file callers too (the aggregate-scoped `Thing.port(...)`,
171
- # binding_proxy.rb, already went through this builder). Every real
172
- # `.port` file only ever spells `verb`/`signal` (no `.port` file
173
- # declares operations — that's DomainPort's own newer shape), and
174
- # DomainPortBuilder's own bare-verb branch produces the exact same
175
- # `Port` object PortBuilder itself did (dsl_spec.rb's own byte-
176
- # identity check) — a pure repoint, no behavior change for any
177
- # existing caller reading `.verb`/`.signal` off what comes back.
123
+
124
+ # Declares a port — a `.port` file's top-level word.
178
125
  #
179
- # `legacy_bare_port: true` — the one real semantic gap this repoint
180
- # would otherwise open: `PortBuilder#build` never refused a
181
- # completely empty build (no verb, no signal even), a real shape
182
- # dsl_spec.rb's own "a port" tests exercise (`signal`-only, no
183
- # `verb`). `DomainPortBuilder`'s own "declares no verb and no
184
- # operations" refusal is real and correct for its other two callers
185
- # (`BindingProxy#port`, `HecksagonBuilder#port_impl`) — only this,
186
- # the literal top-level `.port` file entry point, keeps the older,
187
- # looser rule (see `DomainPortBuilder#initialize`'s own comment).
126
+ # `legacy_bare_port: true` — only this entry point allows a completely
127
+ # empty port body; see `DomainPortBuilder#initialize`'s own doc.
128
+ #
129
+ # @param name [String] the port's name
130
+ # @yield the port's body, evaluated against a `Bluebook::DSL::DomainPortBuilder`
131
+ # @return [Bluebook::Port, Bluebook::DomainPort] the built port
132
+ # @raise [LoadOutsideBoot] if called outside `Hecks.boot`/`.boot_files`
188
133
  def port(name, &) = collect(:add_port, Bluebook::DSL::DomainPortBuilder.build(name, legacy_bare_port: true, &))
189
134
 
190
135
  # Declares an adapter — an `.adapter` file's top-level word.
@@ -1,55 +1,15 @@
1
1
  module RuboCop
2
2
  module Cop
3
3
  module Hecks
4
- # Flags the exact shape behind 8+ real bugs already fixed one at a
5
- # time across this codebase (Tiers 1-5): `holder[a] || holder[b]` —
6
- # the same receiver looked up by two different keys, falling back to
7
- # the second lookup whenever the first is falsy. `||` cannot tell a
8
- # genuinely stored `false` from a missing key — both are falsy in
9
- # Ruby — so a real `false` sitting at `holder[a]` is silently
10
- # discarded and `holder[b]` (usually absent, so `nil`) is returned
11
- # instead. Every one of those 8+ instances was the identical
12
- # AST shape with a different pair of keys (most commonly
13
- # `h[k.to_sym] || h[k]`, reading a value that could arrive keyed
14
- # either by symbol or by string off the wire).
4
+ # Flags `holder[a] || holder[b]`: the same receiver looked up by two keys, falling back
5
+ # to the second when the first is falsy.
15
6
  #
16
- # ## The fix
17
- #
18
- # The fix this codebase already converged on — see
19
- # `lib/hecks/query_specification/field_path.rb#read`, the shared
20
- # digger this whole bug class got consolidated behind: check
21
- # `key?` first, never fall back through `||`.
22
- #
23
- # sym = segment.to_sym
24
- # return current.key?(sym) ? current[sym] : current[segment]
25
- #
26
- # That method's own comment says it plainly: "`key?` first, never
27
- # `||`, because `||` falls through a genuinely-stored `false` to
28
- # the other spelling (usually absent) and returns `nil` instead."
29
- # This cop exists so the next `holder[a] || holder[b]` gets caught
30
- # mechanically, before it becomes bug #9, rather than found by
31
- # hand again in a future audit.
32
- #
33
- # ## Scope
34
- #
35
- # Scoped to the `[]`/`[]` shape only, deliberately — a receiver
36
- # method-call is compared by AST structure (`==`, which ignores
37
- # source location), so `hash[a] || hash[b]` is flagged whether
38
- # `hash` is a local variable, a method call, or a constant, but
39
- # `a[k] || b[k]` (different receivers) and `value || default`
40
- # (an ordinary default, not a second lookup at all) are both left
41
- # alone — neither one can silently drop a stored `false` the way
42
- # a same-receiver double-lookup can.
7
+ # `||` cannot tell a stored `false` from a missing key, so a real `false` at `holder[a]` is
8
+ # discarded. Different receivers (`a[k] || b[k]`) and plain defaults are left alone.
43
9
  #
44
10
  # @example
45
- # # bad — a stored `false` at hash[:active] is discarded
46
- # hash[:active] || hash["active"]
47
- #
48
- # # good
49
- # hash.key?(:active) ? hash[:active] : hash["active"]
50
- #
51
- # # good — or the shared digger this codebase already has
52
- # Hecks::QuerySpecification::FieldPath.dig(hash, "active")
11
+ # hash[:active] || hash["active"] # bad
12
+ # hash.key?(:active) ? hash[:active] : hash["active"] # good
53
13
  class FallbackHashLookup < Base
54
14
  MSG = "`%<receiver>s[...] || %<receiver>s[...]` falls back to the second lookup " \
55
15
  "whenever the first is falsy — but `||` cannot tell a genuinely stored `false` " \
@@ -96,3 +56,5 @@ module RuboCop
96
56
  end
97
57
  end
98
58
  end
59
+
60
+ # AST `==` ignores source location, so two parses of the same receiver compare equal.
@@ -1,67 +1,17 @@
1
1
  module RuboCop
2
2
  module Cop
3
3
  module Hecks
4
- # Flags `hash[new] = hash.delete(old)` inside a loop — a method that
5
- # both reads and writes the same hash across a loop's iterations,
6
- # in the specific rename/permutation shape this codebase has
7
- # already lost real data to once.
8
- #
9
- # ## The bug this follows up on
10
- #
11
- # The bug this is the mechanical follow-up to (M27,
12
- # docs/audits/2026-08-10-main-bug-audit.md,
13
- # docs/audits/2026-08-11-bug-triage.md; fixed in
14
- # `Hecks::Ports::Persistence::Lineage#apply_renames`,
15
- # lib/hecks/ports/persistence/plugins/era/lineage.rb): a rename
16
- # rule set applied one rule at a time, `state[new_name] =
17
- # state.delete(old_name)` per rule, against the same hash it was
18
- # reading from — so a swap (`rename :a, to: :b` alongside `rename
19
- # :b, to: :a`) on `{a: 1, b: 2}` collapsed to `{a: 1}`. The first
20
- # rule wrote `b: 1` (clobbering the real `b: 2`) before the second
21
- # rule ever got a chance to read the value that was supposed to
22
- # move to `:a` — sequential edits stepping on each other, not the
23
- # simultaneous permutation a rename set actually means. The same
24
- # shape loses data identically for any chain longer than one link
25
- # (`a->b->c->a`), not just a two-element swap.
26
- #
27
- # ## The fix this cop points at
28
- #
29
- # Snapshot every old key's value
30
- # first, delete all old keys, then write all new keys, so no
31
- # rename in the pass ever reads a key this same pass has already
32
- # written:
33
- #
34
- # snapshot = renames.filter_map { |old_name, new_name| [old_name, new_name, state[old_name]]
35
- # if state.key?(old_name) }
36
- # snapshot.each { |old_name, _new_name, _value| state.delete(old_name) }
37
- # snapshot.each { |_old_name, new_name, value| state[new_name] = value }
38
- #
39
- # ## What this cop does not attempt
40
- #
41
- # The general checklist item is
42
- # broader than any AST pattern can safely automate ("any method
43
- # that both reads and writes the same collection across a loop"
44
- # covers plenty of correct code too — accumulating into a result
45
- # hash, memoizing into a cache). This cop stays narrow and
46
- # precise: only the exact `recv[x] = recv.delete(y)` shape, same
47
- # receiver both sides, inside something that iterates. A single
48
- # rename outside any loop is not this bug (nothing else can have
49
- # run first); `hash.store(...)` instead of `hash[...] =`, or
50
- # `hash.delete(...)` whose result is not immediately written back
51
- # into the same hash, are not this shape either and are left to
52
- # the broader code-review checklist item, not this cop.
4
+ # Flags `hash[new] = hash.delete(old)` inside a loop.
5
+ # Applying renames one at a time to the hash being read loses data on swaps and chains.
53
6
  #
54
7
  # @example
55
- # # bad — one rule at a time against the hash it reads from
56
- # renames.each do |old_name, new_name|
57
- # state[new_name] = state.delete(old_name)
58
- # end
8
+ # # bad
9
+ # renames.each { |old, new| state[new] = state.delete(old) }
59
10
  #
60
- # # good — snapshot first, then delete, then write: one permutation
61
- # snapshot = renames.filter_map { |old_name, new_name| [old_name, new_name, state[old_name]]
62
- # if state.key?(old_name) }
63
- # snapshot.each { |old_name, _new_name, _value| state.delete(old_name) }
64
- # snapshot.each { |_old_name, new_name, value| state[new_name] = value }
11
+ # # good: snapshot the values, delete every old key, then write the new keys
12
+ # pairs = renames.select { |old, _| state.key?(old) }.map { |old, new| [new, state[old]] }
13
+ # renames.each_key { |old| state.delete(old) }
14
+ # pairs.each { |new, value| state[new] = value }
65
15
  class SequentialHashRenameInLoop < Base
66
16
  MSG = "`%<recv>s[new] = %<recv>s.delete(old)` inside a loop applies one rename at a time against the " \
67
17
  "SAME hash it reads from — a swap (`{a: :b, b: :a}`) on `{a: 1, b: 2}` collapses to `{a: 1}` " \
@@ -78,22 +28,12 @@ module RuboCop
78
28
  inject reduce each_slice each_cons
79
29
  ].freeze
80
30
 
81
- # `hash[new_name] = hash.delete(old_name)` parses as a `send`
82
- # node (indexed assignment has no dedicated node type in Ruby's
83
- # own grammar): `(send recv :[]= new_key (send recv2 :delete
84
- # old_key))`. `$_recv`/`$_recv2` are captured (not merely
85
- # matched) so `same_receiver?` below can compare them
86
- # structurally — node-pattern has no built-in "same subtree
87
- # twice" backreference, so the equality check is real Ruby, not
88
- # the pattern itself.
31
+ # Indexed assignment parses as a `send`. Both receivers are captured because node-pattern
32
+ # has no backreference; `same_receiver?` compares them.
89
33
  def_node_matcher :rename_write?, <<~PATTERN
90
34
  (send $_recv :[]= _new_key (send $_recv2 :delete _old_key))
91
35
  PATTERN
92
36
 
93
- # Flags a `recv[new] = recv.delete(old)` write inside a loop, same receiver both sides.
94
- #
95
- # @param node [RuboCop::AST::SendNode] the `[]=` send node being visited
96
- # @return [void]
97
37
  def on_send(node)
98
38
  rename_write?(node) do |recv, recv2|
99
39
  next unless same_receiver?(recv, recv2)
@@ -105,20 +45,11 @@ module RuboCop
105
45
 
106
46
  private
107
47
 
108
- # Structural equality (type + children, recursively) — exactly
109
- # what `Parser::AST::Node#==` already gives, ignoring source
110
- # location, so `state` vs `state` (two separate `send(nil,
111
- # :state)` reader calls) and `@state` vs `@state` both count as
112
- # "the same collection", not just an identical local variable.
113
48
  def same_receiver?(recv, recv2)
114
49
  recv == recv2
115
50
  end
116
51
 
117
- # Walks every ancestor looking for something that iterates —
118
- # not just the immediate parent, since the offending assignment
119
- # may sit one or more non-loop nodes deep inside a loop's block
120
- # (an `if` guarding it, say) and still carries the exact same
121
- # hazard.
52
+ # Checks every ancestor: a guard node may sit between the assignment and the loop block.
122
53
  def in_loop?(node)
123
54
  node.each_ancestor(:block, :numblock, :for, :while, :until, :while_post, :until_post).any? do |ancestor|
124
55
  loop_ancestor?(ancestor)