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
@@ -0,0 +1,66 @@
1
+ require "open3"
2
+
3
+ module Hecks
4
+ module Release
5
+ class Runner
6
+ # Raised when a command a release step runs exits non-zero or cannot start.
7
+ class CommandFailed < StandardError; end
8
+
9
+ # Raised when a check refuses the release; the message names the fix.
10
+ class Refusal < StandardError; end
11
+
12
+ # Runs the release's external commands (git, gem, npm, curl, op) through
13
+ # any object answering `capture`/`run!`, so a spec injects a recorder.
14
+ class Commands
15
+ # What a captured command printed and whether it succeeded.
16
+ #
17
+ # @!attribute [r] stdout
18
+ # @return [String] standard output
19
+ # @!attribute [r] stderr
20
+ # @return [String] standard error, or the reason the command could not start
21
+ # @!attribute [r] success
22
+ # @return [Boolean] true when the command exited zero
23
+ Result = Struct.new(:stdout, :stderr, :success, keyword_init: true) do
24
+ # Says whether the command exited zero.
25
+ #
26
+ # @return [Boolean] true when it did
27
+ def success?
28
+ success
29
+ end
30
+ end
31
+
32
+ # Runs a command and returns what it printed, whatever its exit status.
33
+ #
34
+ # @param argv [Array<String>] the command and its arguments, never a shell string
35
+ # @param env [Hash{String => String, nil}] variables to set, or unset with nil
36
+ # @param chdir [String, nil] the directory to run in; the current one when nil
37
+ # @return [Result] the output and status; a command that cannot start is a failed result
38
+ def capture(*argv, env: {}, chdir: nil)
39
+ stdout, stderr, status = Open3.capture3(env, *argv, **directory(chdir))
40
+ Result.new(stdout: stdout, stderr: stderr, success: status.success?)
41
+ rescue SystemCallError => e
42
+ Result.new(stdout: "", stderr: e.message, success: false)
43
+ end
44
+
45
+ # Runs a command with its output going straight to the terminal.
46
+ #
47
+ # @param argv [Array<String>] the command and its arguments, never a shell string
48
+ # @param env [Hash{String => String, nil}] variables to set, or unset with nil
49
+ # @param chdir [String, nil] the directory to run in; the current one when nil
50
+ # @return [true] when the command exits zero
51
+ # @raise [CommandFailed] if the command exits non-zero or cannot start
52
+ def run!(*argv, env: {}, chdir: nil)
53
+ return true if system(env, *argv, **directory(chdir))
54
+
55
+ raise CommandFailed, "`#{argv.first(2).join(' ')}` failed"
56
+ end
57
+
58
+ private
59
+
60
+ def directory(chdir)
61
+ chdir ? { chdir: chdir } : {}
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,52 @@
1
+ module Hecks
2
+ module Release
3
+ class Runner
4
+ # Talks to the person running the release: progress lines, warnings and the
5
+ # y/N questions that come before anything irreversible.
6
+ class Console
7
+ # @param input [IO] where answers are read from
8
+ # @param out [IO] where progress goes
9
+ # @param err [IO] where refusals and failures go
10
+ # @param assume_yes [Boolean] answer every question yes without asking
11
+ def initialize(input:, out:, err:, assume_yes: false)
12
+ @input = input
13
+ @out = out
14
+ @err = err
15
+ @assume_yes = assume_yes
16
+ end
17
+
18
+ # Prints a progress line.
19
+ #
20
+ # @param text [String] the line
21
+ # @return [void]
22
+ def say(text)
23
+ @out.puts(text)
24
+ end
25
+
26
+ # Prints a refusal or failure line.
27
+ #
28
+ # @param text [String] the line
29
+ # @return [void]
30
+ def warn(text)
31
+ @err.puts(text)
32
+ end
33
+
34
+ # Asks a yes/no question, defaulting to no.
35
+ #
36
+ # @param question [String] what will happen if the answer is yes
37
+ # @return [Boolean] true for `y` or `yes`, or always under `assume_yes`; false on anything
38
+ # else, including a closed input
39
+ def confirm?(question)
40
+ if @assume_yes
41
+ say("#{question} yes (--yes)")
42
+ return true
43
+ end
44
+
45
+ @out.print("#{question} [y/N] ")
46
+ @out.flush
47
+ %w[y yes].include?(@input.gets.to_s.strip.downcase)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,43 @@
1
+ require "fileutils"
2
+ require_relative "commands"
3
+
4
+ module Hecks
5
+ module Release
6
+ class Runner
7
+ # The gem step: hands the push to `bin/release_gem`, which builds and pushes
8
+ # it with the key 1Password holds; a dry run only builds it, then deletes it.
9
+ class GemPublisher
10
+ # @param root [String] the repository root
11
+ # @param commands [#run!] runs the build and the push
12
+ # @param console [Console] progress output
13
+ def initialize(root:, commands:, console:)
14
+ @root = root
15
+ @commands = commands
16
+ @console = console
17
+ end
18
+
19
+ # Pushes the gem, or with `dry_run` only builds it.
20
+ #
21
+ # @param version [String] the version being released
22
+ # @param dry_run [Boolean] build and delete the gem, pushing nothing
23
+ # @return [void]
24
+ # @raise [CommandFailed] if the build or the push fails
25
+ def publish!(version, dry_run:)
26
+ return build_only(version) if dry_run
27
+
28
+ @console.say("Publishing hecks #{version} to rubygems.org via bin/release_gem...")
29
+ @commands.run!(File.join(@root, "bin/release_gem"), chdir: @root)
30
+ end
31
+
32
+ private
33
+
34
+ def build_only(version)
35
+ @console.say("Dry run: building hecks-#{version}.gem (nothing is pushed)...")
36
+ @commands.run!("gem", "build", "hecks.gemspec", chdir: @root)
37
+ ensure
38
+ FileUtils.rm_f(File.join(@root, "hecks-#{version}.gem"))
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,48 @@
1
+ require "hecks/vendoring/git_environment"
2
+ require_relative "commands"
3
+
4
+ module Hecks
5
+ module Release
6
+ class Runner
7
+ # The release's view of the repository: git run in the checkout, with the
8
+ # repository-pinning environment variables a git hook exports unset.
9
+ class Git
10
+ # @param root [String] the repository root
11
+ # @param commands [#capture, #run!] runs the git commands
12
+ def initialize(root:, commands:)
13
+ @root = root
14
+ @commands = commands
15
+ end
16
+
17
+ # Runs a git command and returns what it printed.
18
+ #
19
+ # @param args [Array<String>] git's arguments
20
+ # @return [String] standard output
21
+ # @raise [Refusal] if git exits non-zero
22
+ def read(*args)
23
+ result = capture(*args)
24
+ raise Refusal, "git #{args.first} failed: #{result.stderr.strip}" unless result.success?
25
+
26
+ result.stdout
27
+ end
28
+
29
+ # Runs a git command and returns the result whatever its status.
30
+ #
31
+ # @param args [Array<String>] git's arguments
32
+ # @return [Commands::Result] output and status
33
+ def capture(*)
34
+ @commands.capture("git", *, env: Vendoring::GitEnvironment.clean, chdir: @root)
35
+ end
36
+
37
+ # Runs a git command that changes something, showing its output.
38
+ #
39
+ # @param args [Array<String>] git's arguments
40
+ # @return [true] when git exits zero
41
+ # @raise [CommandFailed] if git exits non-zero
42
+ def run!(*)
43
+ @commands.run!("git", *, env: Vendoring::GitEnvironment.clean, chdir: @root)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,75 @@
1
+ require "tempfile"
2
+ require_relative "commands"
3
+
4
+ module Hecks
5
+ module Release
6
+ class Runner
7
+ # The npm step: publishes `@hecks/client` from `packages/hecks-client`
8
+ # with the token 1Password holds.
9
+ class NpmPublisher
10
+ PACKAGE_DIR = "packages/hecks-client".freeze
11
+ ENV_FILE = "release/npm_publish.env".freeze
12
+ APPROVAL_NOTICE = "npm will print an approval link; open it and approve with your security key or passkey. " \
13
+ "This step waits for you.".freeze
14
+ USERCONFIG_LINE = "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}".freeze
15
+
16
+ def initialize(root:, commands:, console:)
17
+ @root = root
18
+ @commands = commands
19
+ @console = console
20
+ end
21
+
22
+ # Publishes the package, or with `dry_run` only packs it, which needs no
23
+ # credentials.
24
+ def publish!(version, dry_run:)
25
+ install_dependencies
26
+ if dry_run
27
+ @console.say("Dry run: npm publish --dry-run for @hecks/client #{version} (nothing is published)...")
28
+ @commands.run!("npm", "publish", "--dry-run", "--access", "public", chdir: package_dir)
29
+ else
30
+ @console.say("Publishing @hecks/client #{version} to npm (1Password will prompt for Touch ID)...")
31
+ @console.say(APPROVAL_NOTICE)
32
+ publish_with_token
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def package_dir
39
+ File.join(@root, PACKAGE_DIR)
40
+ end
41
+
42
+ # npm's `prepack` hook builds `dist/`; there is no separate build step here.
43
+ def install_dependencies
44
+ return if Dir.exist?(File.join(package_dir, "node_modules"))
45
+
46
+ @console.say("Installing the client's dependencies (npm ci)...")
47
+ @commands.run!("npm", "ci", chdir: package_dir)
48
+ end
49
+
50
+ # The token bypasses 2FA (the account's second factor is a passkey); this
51
+ # fallback publish runs with `--auth-type=web` and doesn't capture output,
52
+ # so an approval prompt reaches the terminal.
53
+ def publish_with_token
54
+ with_userconfig do |userconfig|
55
+ @commands.run!(
56
+ "op", "run", "--env-file=#{File.join(@root, ENV_FILE)}", "--",
57
+ "npm", "publish", "--access", "public", "--auth-type=web", "--userconfig", userconfig,
58
+ chdir: package_dir
59
+ )
60
+ end
61
+ end
62
+
63
+ def with_userconfig
64
+ file = Tempfile.new(["hecks-npmrc", ".npmrc"])
65
+ file.chmod(0o600)
66
+ file.write("#{USERCONFIG_LINE}\n")
67
+ file.flush
68
+ yield file.path
69
+ ensure
70
+ file&.close!
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,104 @@
1
+ require "json"
2
+ require_relative "commands"
3
+ require_relative "git"
4
+
5
+ module Hecks
6
+ module Release
7
+ class Runner
8
+ # The checks that run before a release does anything: tools installed, a
9
+ # clean current `main`, matching versions, and a changelog entry.
10
+ class Preflight
11
+ # What the checks learned; `sha` is also `origin/main`, enforced by check_current.
12
+ Facts = Struct.new(:version, :sha, keyword_init: true)
13
+
14
+ INSTALL_HINTS = {
15
+ "git" => "install git",
16
+ "gem" => "install Ruby, which ships gem",
17
+ "npm" => "install Node.js: brew install node",
18
+ "curl" => "install curl",
19
+ "op" => "install the 1Password CLI: brew install 1password-cli"
20
+ }.freeze
21
+ VERSION_LINE = /^\s*VERSION\s*=\s*"(?<version>[^"]+)"/
22
+ CLIENT_PACKAGE = "packages/hecks-client/package.json".freeze
23
+
24
+ def initialize(root:, commands:, git:, tools:)
25
+ @root = root
26
+ @commands = commands
27
+ @git = git
28
+ @tools = tools
29
+ end
30
+
31
+ # Runs every check; the first refusal stops it.
32
+ def check!
33
+ check_tools
34
+ check_branch
35
+ check_current
36
+ check_clean
37
+ version = declared_version
38
+ check_client_version(version)
39
+ check_changelog(version)
40
+ Facts.new(version: version, sha: git("rev-parse", "HEAD").strip)
41
+ end
42
+
43
+ private
44
+
45
+ def check_tools
46
+ @tools.each do |tool|
47
+ next if @commands.capture(tool, "--version").success?
48
+
49
+ raise Refusal, "#{tool} not found on PATH; #{INSTALL_HINTS.fetch(tool, "install #{tool}")}"
50
+ end
51
+ end
52
+
53
+ def check_branch
54
+ branch = git("rev-parse", "--abbrev-ref", "HEAD").strip
55
+ return if branch == "main"
56
+
57
+ raise Refusal, "on branch #{branch}, not main; release from main: git checkout main"
58
+ end
59
+
60
+ def check_current
61
+ fetch = @git.capture("fetch", "origin")
62
+ raise Refusal, "git fetch origin failed (#{fetch.stderr.strip}); check the network and the remote" unless fetch.success?
63
+
64
+ head = git("rev-parse", "HEAD").strip
65
+ upstream = git("rev-parse", "origin/main").strip
66
+ return if head == upstream
67
+
68
+ raise Refusal, "main (#{head[0, 7]}) is not origin/main (#{upstream[0, 7]}); " \
69
+ "merge the release PR, then git pull --ff-only"
70
+ end
71
+
72
+ def check_clean
73
+ return if git("status", "--porcelain").strip.empty?
74
+
75
+ raise Refusal, "the working tree has uncommitted changes; commit or discard them before releasing"
76
+ end
77
+
78
+ def declared_version
79
+ match = File.read(File.join(@root, "lib/hecks/version.rb")).match(VERSION_LINE)
80
+ raise Refusal, "lib/hecks/version.rb declares no VERSION; restore it" unless match
81
+
82
+ match[:version]
83
+ end
84
+
85
+ def check_client_version(version)
86
+ client = JSON.parse(File.read(File.join(@root, CLIENT_PACKAGE))).fetch("version")
87
+ return if client == version
88
+
89
+ raise Refusal, "packages/hecks-client is at #{client} but Hecks::VERSION is #{version}; bump the package first."
90
+ end
91
+
92
+ def check_changelog(version)
93
+ return if File.read(File.join(@root, "CHANGELOG.md")).match?(/^## \[#{Regexp.escape(version)}\]/)
94
+
95
+ raise Refusal, "CHANGELOG.md has no `## [#{version}]` heading; add the release entry in the release PR"
96
+ end
97
+
98
+ def git(*)
99
+ @git.read(*)
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,55 @@
1
+ require "json"
2
+ require_relative "commands"
3
+
4
+ module Hecks
5
+ module Release
6
+ class Runner
7
+ # Asks the registries whether a version is already out, so a re-run of the
8
+ # release skips what an earlier run finished.
9
+ #
10
+ # Neither question needs credentials.
11
+ class Published
12
+ GEM_VERSIONS_URL = "https://rubygems.org/api/v1/versions/hecks.json".freeze
13
+ NPM_PACKAGE = "@hecks/client".freeze
14
+
15
+ # @param root [String] the repository root
16
+ # @param commands [#capture] runs curl and npm
17
+ def initialize(root:, commands:)
18
+ @root = root
19
+ @commands = commands
20
+ end
21
+
22
+ # Says whether RubyGems lists the hecks gem at a version.
23
+ #
24
+ # @param version [String] the version to look for
25
+ # @return [Boolean] true when it is published
26
+ # @raise [Refusal] if RubyGems cannot be reached or answers with something unreadable
27
+ def gem?(version)
28
+ result = @commands.capture("curl", "-fsS", GEM_VERSIONS_URL, chdir: @root)
29
+ unless result.success?
30
+ raise Refusal, "could not list published hecks versions (#{result.stderr.strip}); " \
31
+ "check the network and re-run"
32
+ end
33
+
34
+ JSON.parse(result.stdout).any? { |entry| entry["number"] == version }
35
+ rescue JSON::ParserError
36
+ raise Refusal, "RubyGems answered with something other than a version list; re-run in a minute"
37
+ end
38
+
39
+ # Says whether npm lists @hecks/client at a version.
40
+ #
41
+ # @param version [String] the version to look for
42
+ # @return [Boolean] true when it is published
43
+ # @raise [Refusal] if npm fails for any reason other than the version not existing
44
+ def npm?(version)
45
+ result = @commands.capture("npm", "view", "#{NPM_PACKAGE}@#{version}", "version", chdir: @root)
46
+ return result.stdout.strip == version if result.success?
47
+ return false if result.stderr.include?("E404")
48
+
49
+ raise Refusal, "could not check npm for #{NPM_PACKAGE}@#{version} (#{result.stderr.strip.lines.first}); " \
50
+ "check the network and re-run"
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,80 @@
1
+ require_relative "commands"
2
+ require_relative "git"
3
+
4
+ module Hecks
5
+ module Release
6
+ class Runner
7
+ # Makes sure the release tag `vX.Y.Z` is an annotated tag on the release
8
+ # commit and is on origin, creating and pushing it only when it is missing.
9
+ class Tagger
10
+ def initialize(git:, console:, dry_run:)
11
+ @git = git
12
+ @console = console
13
+ @dry_run = dry_run
14
+ end
15
+
16
+ # Brings the tag to the state described above, saying what it did; returns
17
+ # false only when the person declined the confirmation.
18
+ def ensure!(facts)
19
+ tag = "v#{facts.version}"
20
+ local = local_commit(tag)
21
+ remote = remote_commit(tag)
22
+ refuse_elsewhere(tag, facts.sha, local: local, remote: remote)
23
+ return true.tap { already_there(tag) } if remote
24
+ return true.tap { preview(tag, facts.sha, local) } if @dry_run
25
+ return false unless @console.confirm?(question(tag, facts.sha, local))
26
+
27
+ create(tag, facts.sha) unless local
28
+ push(tag)
29
+ true
30
+ end
31
+
32
+ private
33
+
34
+ def already_there(tag)
35
+ @console.say("Tag #{tag} is already on origin at the release commit; skipping.")
36
+ end
37
+
38
+ def preview(tag, sha, local)
39
+ @console.say("Would create annotated tag #{tag} at #{sha[0, 7]}.") unless local
40
+ @console.say("Would push #{tag} to origin.")
41
+ end
42
+
43
+ def question(tag, sha, local)
44
+ verb = local ? "Push" : "Create and push"
45
+ "#{verb} annotated tag #{tag} at #{sha[0, 7]} to origin?"
46
+ end
47
+
48
+ def create(tag, sha)
49
+ @git.run!("tag", "-a", tag, "-m", "Release #{tag.delete_prefix('v')}", sha)
50
+ end
51
+
52
+ def push(tag)
53
+ @git.run!("push", "origin", tag)
54
+ end
55
+
56
+ def refuse_elsewhere(tag, sha, local:, remote:)
57
+ { "locally" => local, "on origin" => remote }.each do |where, commit|
58
+ next if commit.nil? || commit == sha
59
+
60
+ raise Refusal, "tag #{tag} already points at #{commit[0, 7]} #{where}, not at the release commit " \
61
+ "#{sha[0, 7]}; if that tag was never released, delete it (git tag -d #{tag}; " \
62
+ "git push origin :refs/tags/#{tag}) and re-run, otherwise main has moved since the release " \
63
+ "and the missing package must be published from a checkout of #{tag}"
64
+ end
65
+ end
66
+
67
+ def local_commit(tag)
68
+ result = @git.capture("rev-parse", "-q", "--verify", "refs/tags/#{tag}^{commit}")
69
+ result.success? ? result.stdout.strip : nil
70
+ end
71
+
72
+ def remote_commit(tag)
73
+ listing = @git.read("ls-remote", "--tags", "origin", "refs/tags/#{tag}", "refs/tags/#{tag}^{}")
74
+ shas = listing.lines.to_h { |line| line.split.then { |sha, ref| [ref, sha] } }
75
+ shas.fetch("refs/tags/#{tag}^{}") { shas["refs/tags/#{tag}"] }
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end