hecks 2.7.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 (463) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +11 -0
  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 +18 -99
  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 +15 -37
  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 +32 -142
  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 +17 -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 +38 -26
  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 +17 -65
  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 -189
  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 -106
  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 +54 -268
  109. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +47 -308
  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 +10 -91
  114. data/lib/hecks/bluebook/model_check.rb +102 -600
  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 +84 -0
  128. data/lib/hecks/cli/document.rb +86 -0
  129. data/lib/hecks/cli/ir.rb +45 -0
  130. data/lib/hecks/cli/mcp.rb +31 -0
  131. data/lib/hecks/cli/mcp_door.rb +366 -0
  132. data/lib/hecks/cli/model_check.rb +203 -0
  133. data/lib/hecks/cli/project_cli.rb +90 -0
  134. data/lib/hecks/cli/project_diagrams.rb +36 -0
  135. data/lib/hecks/cli/run.rb +173 -0
  136. data/lib/hecks/cli/smoke_test.rb +45 -0
  137. data/lib/hecks/cli/stores.rb +53 -0
  138. data/lib/hecks/cli.rb +160 -0
  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 +98 -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 +95 -0
  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 +62 -65
  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 +7 -26
  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 +22 -122
  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 +151 -679
  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 +9 -16
  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 +46 -138
  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 +93 -0
  336. data/lib/hecks/release/runner/commands.rb +66 -0
  337. data/lib/hecks/release/runner/console.rb +52 -0
  338. data/lib/hecks/release/runner/gem_publisher.rb +43 -0
  339. data/lib/hecks/release/runner/git.rb +48 -0
  340. data/lib/hecks/release/runner/npm_publisher.rb +75 -0
  341. data/lib/hecks/release/runner/preflight.rb +104 -0
  342. data/lib/hecks/release/runner/published.rb +55 -0
  343. data/lib/hecks/release/runner/tagger.rb +80 -0
  344. data/lib/hecks/release/runner.rb +215 -0
  345. data/lib/hecks/rendering.rb +4 -26
  346. data/lib/hecks/router/namespace_installer.rb +12 -36
  347. data/lib/hecks/router.rb +4 -8
  348. data/lib/hecks/runtime/aggregate_lock.rb +8 -33
  349. data/lib/hecks/runtime/boot_gates.rb +3 -18
  350. data/lib/hecks/runtime/caller.rb +19 -59
  351. data/lib/hecks/runtime/capability_graph.rb +5 -21
  352. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
  353. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +23 -160
  354. data/lib/hecks/runtime/command_interpreter.rb +125 -307
  355. data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
  356. data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
  357. data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
  358. data/lib/hecks/runtime/command_rules/emission.rb +8 -21
  359. data/lib/hecks/runtime/command_rules/references.rb +23 -202
  360. data/lib/hecks/runtime/dependency_planning.rb +30 -102
  361. data/lib/hecks/runtime/dispatcher.rb +82 -241
  362. data/lib/hecks/runtime/entity_element.rb +40 -462
  363. data/lib/hecks/runtime/entity_interpreter.rb +65 -220
  364. data/lib/hecks/runtime/errors.rb +32 -115
  365. data/lib/hecks/runtime/event.rb +3 -25
  366. data/lib/hecks/runtime/identity.rb +18 -154
  367. data/lib/hecks/runtime/instance.rb +18 -150
  368. data/lib/hecks/runtime/interpreting.rb +11 -70
  369. data/lib/hecks/runtime/invocation.rb +28 -164
  370. data/lib/hecks/runtime/loader.rb +48 -186
  371. data/lib/hecks/runtime/outbox.rb +38 -261
  372. data/lib/hecks/runtime/policy_interpreter.rb +38 -200
  373. data/lib/hecks/runtime/port_operation_interpreter.rb +26 -87
  374. data/lib/hecks/runtime/query_interpreter.rb +45 -187
  375. data/lib/hecks/runtime/reaction_invocation.rb +36 -138
  376. data/lib/hecks/runtime/read_model_interpreter.rb +139 -208
  377. data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
  378. data/lib/hecks/runtime/reference_hop.rb +8 -81
  379. data/lib/hecks/runtime/refusal_wording.rb +8 -89
  380. data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
  381. data/lib/hecks/runtime/registry/verification.rb +52 -225
  382. data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
  383. data/lib/hecks/runtime/registry.rb +103 -244
  384. data/lib/hecks/runtime/remote_dispatcher.rb +37 -152
  385. data/lib/hecks/runtime/routing.rb +4 -15
  386. data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
  387. data/lib/hecks/runtime/saga_interpreter.rb +79 -263
  388. data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
  389. data/lib/hecks/runtime/tenant_check.rb +6 -68
  390. data/lib/hecks/runtime/tenant_scope.rb +3 -28
  391. data/lib/hecks/runtime/value/admission.rb +16 -67
  392. data/lib/hecks/runtime/value/coercion.rb +155 -641
  393. data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
  394. data/lib/hecks/runtime/value.rb +12 -84
  395. data/lib/hecks/runtime.rb +17 -65
  396. data/lib/hecks/storehouse.rb +146 -567
  397. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +8 -32
  398. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
  399. data/lib/hecks/vendoring/git_environment.rb +2 -8
  400. data/lib/hecks/vendoring/git_source.rb +4 -10
  401. data/lib/hecks/vendoring.rb +7 -46
  402. data/lib/hecks/version.rb +3 -14
  403. data/lib/hecks/vocabulary.rb +5 -0
  404. data/lib/hecks.rb +32 -82
  405. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
  406. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
  407. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
  408. metadata +30 -59
  409. data/lib/hecks/bench/cli.rb +0 -74
  410. data/lib/hecks/bench/environment.rb +0 -100
  411. data/lib/hecks/bench/postgres_probe.rb +0 -41
  412. data/lib/hecks/bench/report.rb +0 -116
  413. data/lib/hecks/bench/ruby_runner.rb +0 -151
  414. data/lib/hecks/bench/run.rb +0 -55
  415. data/lib/hecks/bench/rust_runner.rb +0 -165
  416. data/lib/hecks/bench/stats.rb +0 -75
  417. data/lib/hecks/bench/suite.rb +0 -172
  418. data/lib/hecks/bench/workload.rb +0 -137
  419. data/lib/hecks/bench.rb +0 -28
  420. data/lib/hecks/codemod.rb +0 -443
  421. data/lib/hecks/corpus.rb +0 -479
  422. data/lib/hecks/doc/reference.rb +0 -585
  423. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -684
  424. data/lib/hecks/fuzzing/combination_miner.rb +0 -178
  425. data/lib/hecks/fuzzing/concurrent_dispatch.rb +0 -469
  426. data/lib/hecks/fuzzing/coverage_campaign.rb +0 -161
  427. data/lib/hecks/fuzzing/differential.rb +0 -192
  428. data/lib/hecks/fuzzing/domain_generator.rb +0 -871
  429. data/lib/hecks/fuzzing/era_boundary.rb +0 -154
  430. data/lib/hecks/fuzzing/form_census.rb +0 -285
  431. data/lib/hecks/fuzzing/generated_domain_check.rb +0 -171
  432. data/lib/hecks/fuzzing/invalid_value_generator.rb +0 -125
  433. data/lib/hecks/fuzzing/isolated_boot.rb +0 -658
  434. data/lib/hecks/fuzzing/nondeterministic.rb +0 -79
  435. data/lib/hecks/fuzzing/persistence_parity.rb +0 -253
  436. data/lib/hecks/fuzzing/properties/corrections.rb +0 -125
  437. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +0 -688
  438. data/lib/hecks/fuzzing/properties/guards.rb +0 -398
  439. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +0 -399
  440. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +0 -137
  441. data/lib/hecks/fuzzing/properties/outbox.rb +0 -180
  442. data/lib/hecks/fuzzing/properties/querying.rb +0 -260
  443. data/lib/hecks/fuzzing/properties.rb +0 -267
  444. data/lib/hecks/fuzzing/qa_settings.rb +0 -164
  445. data/lib/hecks/fuzzing/replay.rb +0 -930
  446. data/lib/hecks/fuzzing/rotation_priority.rb +0 -114
  447. data/lib/hecks/fuzzing/rust_gap_manifest.rb +0 -139
  448. data/lib/hecks/fuzzing/self_consistency.rb +0 -825
  449. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +0 -532
  450. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +0 -171
  451. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +0 -127
  452. data/lib/hecks/fuzzing/sequence_generator/picker.rb +0 -116
  453. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +0 -138
  454. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +0 -344
  455. data/lib/hecks/fuzzing/sequence_generator.rb +0 -311
  456. data/lib/hecks/fuzzing/shrinker.rb +0 -309
  457. data/lib/hecks/fuzzing/structural_skips.rb +0 -53
  458. data/lib/hecks/fuzzing/sweep_depth.rb +0 -61
  459. data/lib/hecks/fuzzing/target_capabilities.rb +0 -255
  460. data/lib/hecks/fuzzing/value_generator.rb +0 -340
  461. data/lib/hecks/fuzzing.rb +0 -23
  462. data/lib/hecks/grammar/evolve.rb +0 -524
  463. data/lib/hecks/query_ir.rb +0 -482
@@ -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.7.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
@@ -254,6 +254,10 @@ module Hecks
254
254
  {"refusal"=>"InvariantViolation", "site"=>"admits_declared_set", "argument"=>"offered", "shape"=>"scalar", "quoting"=>"none", "separator"=>"", "sorted"=>"false", "when_empty"=>""}.freeze,
255
255
  {"refusal"=>"InvariantViolation", "site"=>"undeclared_set", "argument"=>"name", "shape"=>"scalar", "quoting"=>"none", "separator"=>"", "sorted"=>"false", "when_empty"=>""}.freeze,
256
256
  {"refusal"=>"InvariantViolation", "site"=>"undeclared_set", "argument"=>"admits", "shape"=>"scalar", "quoting"=>"none", "separator"=>"", "sorted"=>"false", "when_empty"=>""}.freeze,
257
+ {"refusal"=>"InvariantViolation", "site"=>"group_by_collision", "argument"=>"read_model", "shape"=>"scalar", "quoting"=>"none", "separator"=>"", "sorted"=>"false", "when_empty"=>""}.freeze,
258
+ {"refusal"=>"InvariantViolation", "site"=>"group_by_collision", "argument"=>"fields", "shape"=>"list", "quoting"=>"none", "separator"=>", ", "sorted"=>"false", "when_empty"=>""}.freeze,
259
+ {"refusal"=>"InvariantViolation", "site"=>"group_by_collision", "argument"=>"ids", "shape"=>"list", "quoting"=>"inspect", "separator"=>", ", "sorted"=>"true", "when_empty"=>""}.freeze,
260
+ {"refusal"=>"InvariantViolation", "site"=>"group_by_collision", "argument"=>"key", "shape"=>"scalar", "quoting"=>"none", "separator"=>"", "sorted"=>"false", "when_empty"=>""}.freeze,
257
261
  {"refusal"=>"Unauthorized", "site"=>"tenant_required", "argument"=>"query", "shape"=>"scalar", "quoting"=>"none", "separator"=>"", "sorted"=>"false", "when_empty"=>""}.freeze,
258
262
  {"refusal"=>"Unauthorized", "site"=>"tenant_required", "argument"=>"field", "shape"=>"scalar", "quoting"=>"none", "separator"=>"", "sorted"=>"false", "when_empty"=>""}.freeze,
259
263
  {"refusal"=>"Unauthorized", "site"=>"role_mismatch", "argument"=>"command", "shape"=>"scalar", "quoting"=>"none", "separator"=>"", "sorted"=>"false", "when_empty"=>""}.freeze,
@@ -314,6 +318,7 @@ module Hecks
314
318
  {"refusal"=>"InvariantViolation", "site"=>"value_object_invariant", "template"=>"{name} invariant violated — {description} (given {offered})"}.freeze,
315
319
  {"refusal"=>"InvariantViolation", "site"=>"admits_declared_set", "template"=>"{name} admits {admits} — {admitted} — got {offered}"}.freeze,
316
320
  {"refusal"=>"InvariantViolation", "site"=>"undeclared_set", "template"=>"{name} admits {admits}, which this chapter does not declare — a closed set is named Aggregate::SetName, and it must be one the bluebook actually holds"}.freeze,
321
+ {"refusal"=>"InvariantViolation", "site"=>"group_by_collision", "template"=>"{read_model} groups by {fields}, but rows {ids} share {key} — a group_by leaf holds one row; add a field that tells them apart"}.freeze,
317
322
  {"refusal"=>"Unauthorized", "site"=>"tenant_required", "template"=>"{query} declares authorize with tenant: {field} — pass {field}: to name which {field} this ask is scoped to"}.freeze,
318
323
  {"refusal"=>"Unauthorized", "site"=>"role_mismatch", "template"=>"{command} refused — role: {role}, and the caller stated {caller_role}"}.freeze,
319
324
  {"refusal"=>"Unauthorized", "site"=>"cross_tenant_reference", "template"=>"{aggregate} {field} is {tenant}, but {attribute} names a {target} whose own {target_field} is {other} — a cross-tenant reference"}.freeze,
data/lib/hecks.rb CHANGED
@@ -32,70 +32,43 @@ require_relative "hecks/facade/cli_door"
32
32
  require_relative "hecks/facade/cli_runner"
33
33
  require_relative "hecks/storehouse"
34
34
  require_relative "hecks/mcp_stdio_guard"
35
+ require_relative "hecks/mcp_door_scope"
35
36
  require_relative "hecks/framework"
36
- require_relative "hecks/corpus"
37
37
  require_relative "hecks/vendoring"
38
38
  require_relative "hecks/embryonaut_bluebook"
39
39
 
40
- # The root namespace and public facade of the whole DSL/runtime: `Hecks.boot`/
41
- # `.boot_files` assemble a running domain from `.bluebook`/`.hecksagon`/
42
- # `.world` files, and `.bluebook`/`.hecksagon`/`.port`/`.adapter`/`.world`/
43
- # `.data_translation` are the top-level declaration words every such file
44
- # opens with, each collecting its built construct into the currently booting
45
- # Registry (`#collect`, private below).
40
+ # Repository-only tooling: the corpus table walks `examples/`, `qa/` and
41
+ # `spec/`, which only a checkout has, so it loads on first use and the
42
+ # packaged gem leaves it out (ADR 0066).
43
+ Hecks.autoload(:Corpus, File.expand_path("hecks/corpus", __dir__))
44
+
45
+ # Root namespace and public facade of the DSL/runtime: `Hecks.boot`/`.boot_files`
46
+ # assemble a running domain from `.bluebook`/`.hecksagon`/`.world` files.
46
47
  module Hecks
47
48
  class LoadOutsideBoot < StandardError; end
48
49
 
49
50
  class << self
50
- # The loading words below collect into the registry the runtime is
51
- # holding open ; booting and that ambient state belong to the runtime
52
- # layer, so this module is their facade and Hecks::Runtime is where
53
- # they live.
54
- # `install_facade:` — see Runtime::Loader.boot. Defaults on; a caller
55
- # that only dispatches by FQN string can skip the global sugar.
56
- #
57
- # `environment:` — recovered, not new: this parameter (and the
58
- # `environments/<name>.hecksagon` / `.world` overlay it loads —
59
- # see Adapters::Folder#load_domain) existed on a prior commit of
60
- # this repo (933d1dd), was vendored out to a real consumer
61
- # (a client site's domain), and was then lost from this repo's own
62
- # history (no branch here reaches that commit). Ported forward
63
- # from the consumer's vendor snapshot — the only surviving copy —
64
- # and generalized: the original only loaded a `.hecksagon`
65
- # overlay; this also loads a same-named `.world` overlay, both
66
- # merged into the base rather than replacing it (Registry#add_hecksagon
67
- # / #add_world). hecks never reads ENV itself — a caller
68
- # resolves its own env var name and passes the resulting string
69
- # straight through, e.g. `Hecks.boot(path, environment:
70
- # ENV.fetch("MYAPP_ENV", "development"))`.
71
- #
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`.
72
54
  # @param path [String] path to a domain directory, or a file inside one
73
55
  # @param shared [String, nil] a shared-root override; see `Runtime::Loader.boot`
74
- # @param install_facade [Boolean] whether to install the `Widget::Item.Add`-style
75
- # Ruby facade constants for this boot
76
- # @param environment [String, nil] the environment name whose
77
- # `environments/<name>.hecksagon`/`.world` overlay, if present, loads after the
78
- # domain's own
79
- # @return [Runtime::Dispatcher, Runtime::RemoteDispatcher] the dispatcher bound
80
- # 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
81
60
  def boot(path, shared: nil, install_facade: true, environment: nil)
82
61
  Runtime.boot(path, shared: shared, install_facade: install_facade, environment: environment)
83
62
  end
84
63
 
85
- # `paths` — an explicit list of files to boot (a `.bluebook`, its
86
- # `.hecksagon`, optionally a `.world`), loaded in place from wherever
87
- # they actually live — see Runtime::Loader.boot_files's own header for
88
- # why this exists beside `boot` rather than as a special case of it.
89
- #
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`.
90
67
  # @param paths [String, Array<String>] one or more file paths within the domain
91
- # to load, instead of the whole directory
92
68
  # @param shared [String, nil] a shared-root override; see `Runtime::Loader.boot_files`
93
- # @param install_facade [Boolean] whether to install the `Widget::Item.Add`-style
94
- # Ruby facade constants for this boot
95
- # @param environment [String, nil] the environment name passed through to the
96
- # selected-file loader
97
- # @return [Runtime::Dispatcher, Runtime::RemoteDispatcher] the dispatcher bound
98
- # 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
99
72
  def boot_files(paths, shared: nil, install_facade: true, environment: nil)
100
73
  Runtime.boot_files(paths, shared: shared, install_facade: install_facade, environment: environment)
101
74
  end
@@ -112,21 +85,8 @@ module Hecks
112
85
  # @return [Runtime::Registry, nil] the current registry, or nil outside a boot
113
86
  def current_registry = Runtime.current_registry
114
87
 
115
- # Bind who is dispatching for the duration of the block — checked
116
- # against a command's declared `role`, if it has one. Unbound (the
117
- # default), a command's role stays exactly what it is without this:
118
- # decoration.
119
- #
120
- # `actor_id` is optional — a caller naming only a role is checked by
121
- # string equality against the command's own `role`, exactly as
122
- # before. A caller that also names who it is lets the check run
123
- # against a real Governance `RoleAssignment` instead, once the
124
- # command's domain has Governance attached — see
125
- # `CommandRules::Authorization`'s own header.
126
- #
127
- # `as_of` and `scope` are optional too, same shape — see
128
- # `Runtime.as_caller`'s own header for what each does.
129
- #
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.
130
90
  # @param role [String, Symbol] the role to check the caller against
131
91
  # @param actor_id [String, nil] who is calling, checked against a real Governance
132
92
  # `RoleAssignment` when given; string-equality only against `role` when nil
@@ -160,26 +120,16 @@ module Hecks
160
120
  # @return [Bluebook::Hecksagon] the built hecksagon
161
121
  # @raise [LoadOutsideBoot] if called outside `Hecks.boot`/`.boot_files`
162
122
  def hecksagon(name, &) = collect(:add_hecksagon, Bluebook::DSL::HecksagonBuilder.build(name, &))
163
- # Repointed to DomainPortBuilder — the migration DomainPort's own
164
- # class comment names as its goal, now landed for the top-level
165
- # `.port` file callers too (the aggregate-scoped `Thing.port(...)`,
166
- # binding_proxy.rb, already went through this builder). Every real
167
- # `.port` file only ever spells `verb`/`signal` (no `.port` file
168
- # declares operations — that's DomainPort's own newer shape), and
169
- # DomainPortBuilder's own bare-verb branch produces the exact same
170
- # `Port` object PortBuilder itself did (dsl_spec.rb's own byte-
171
- # identity check) — a pure repoint, no behavior change for any
172
- # existing caller reading `.verb`/`.signal` off what comes back.
123
+
124
+ # Declares a port — a `.port` file's top-level word.
173
125
  #
174
- # `legacy_bare_port: true` — the one real semantic gap this repoint
175
- # would otherwise open: `PortBuilder#build` never refused a
176
- # completely empty build (no verb, no signal even), a real shape
177
- # dsl_spec.rb's own "a port" tests exercise (`signal`-only, no
178
- # `verb`). `DomainPortBuilder`'s own "declares no verb and no
179
- # operations" refusal is real and correct for its other two callers
180
- # (`BindingProxy#port`, `HecksagonBuilder#port_impl`) — only this,
181
- # the literal top-level `.port` file entry point, keeps the older,
182
- # 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`
183
133
  def port(name, &) = collect(:add_port, Bluebook::DSL::DomainPortBuilder.build(name, legacy_bare_port: true, &))
184
134
 
185
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)