hecks 7.0.0 → 7.1.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 (257) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hecks/adapters/driven/postgres_era/lineage/chain_sql.rb +2 -1
  3. data/lib/hecks/adapters/driven/postgres_era/lineage/provisioning.rb +4 -2
  4. data/lib/hecks/adapters/driven/postgres_era/lineage/split_sql.rb +16 -8
  5. data/lib/hecks/adapters/driven/postgres_era/lineage/transform_sql.rb +7 -3
  6. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/era_resolver.rb +2 -1
  7. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/mint_request.rb +2 -1
  8. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/refusals.rb +2 -1
  9. data/lib/hecks/adapters/driving/cli_runner/bang_rule.rb +3 -0
  10. data/lib/hecks/adapters/driving/cli_runner/completion_words.rb +2 -0
  11. data/lib/hecks/adapters/driving/cli_runner/read_context.rb +6 -3
  12. data/lib/hecks/adapters/driving/cli_runner/renamed_names.rb +2 -1
  13. data/lib/hecks/adapters/driving/cli_runner/resolution.rb +2 -2
  14. data/lib/hecks/adapters/driving/cli_runner/resume_words.rb +1 -0
  15. data/lib/hecks/adapters/driving/cli_runner/run_reference.rb +6 -3
  16. data/lib/hecks/adapters/driving/cli_runner/search_words.rb +2 -0
  17. data/lib/hecks/adapters/driving/cli_runner/tour_words.rb +1 -0
  18. data/lib/hecks/adapters/driving/cli_runner/verb_names.rb +2 -1
  19. data/lib/hecks/adapters/driving/cli_runner/verbosity.rb +2 -0
  20. data/lib/hecks/adapters/driving/cli_runner/word_help.rb +40 -0
  21. data/lib/hecks/adapters/driving/cli_runner.rb +2 -0
  22. data/lib/hecks/adapters/driving/mcp/progress_sink.rb +4 -2
  23. data/lib/hecks/adapters/driving/mcp.rb +2 -1
  24. data/lib/hecks/adapters/driving/mcp_scope/argument_policy.rb +2 -1
  25. data/lib/hecks/adapters/driving/ruby/aggregate_module/hooks.rb +28 -14
  26. data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +4 -2
  27. data/lib/hecks/assembly.rb +18 -4
  28. data/lib/hecks/behaviors/expectations/adapter_selection.rb +82 -0
  29. data/lib/hecks/behaviors/expectations/boot.rb +12 -5
  30. data/lib/hecks/bluebook/acknowledgements.rb +1 -1
  31. data/lib/hecks/bluebook/command.rb +3 -2
  32. data/lib/hecks/bluebook/declaration.rb +2 -1
  33. data/lib/hecks/bluebook/dsl/adapter_builder.rb +9 -1
  34. data/lib/hecks/bluebook/dsl/bluebook_builder/metadata.rb +6 -4
  35. data/lib/hecks/bluebook/dsl/bluebook_builder/modules.rb +6 -3
  36. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/correlation_keys.rb +2 -1
  37. data/lib/hecks/bluebook/dsl/command_builder/folded_sets.rb +4 -2
  38. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +2 -1
  39. data/lib/hecks/bluebook/hecksagon.rb +6 -3
  40. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +9 -2
  41. data/lib/hecks/bluebook/meta_validator.rb +1 -1
  42. data/lib/hecks/bluebook/model_check/aggregate_checks.rb +2 -1
  43. data/lib/hecks/bluebook/model_check/classification_checks.rb +51 -0
  44. data/lib/hecks/bluebook/model_check/context_checks.rb +2 -0
  45. data/lib/hecks/bluebook/model_check/redundant_relationships.rb +2 -1
  46. data/lib/hecks/ci_speed/adapters/shard_partitioner.adapter +7 -0
  47. data/lib/hecks/ci_speed/adapters/shard_partitioner.rb +66 -0
  48. data/lib/hecks/ci_speed/adapters/timings_source.adapter +7 -0
  49. data/lib/hecks/ci_speed/adapters/timings_source.rb +68 -0
  50. data/lib/hecks/ci_speed/ci_speed.bluebook +111 -0
  51. data/lib/hecks/ci_speed/ci_speed.ports.hecksagon +22 -0
  52. data/lib/hecks/ci_workflows/ci_workflows.bluebook +50 -6
  53. data/lib/hecks/coder/coder.bluebook +3 -1
  54. data/lib/hecks/command_names/index.rb +1 -1
  55. data/lib/hecks/command_names/resolution.rb +15 -5
  56. data/lib/hecks/composer/toolbox.rb +2 -1
  57. data/lib/hecks/corpus.rb +4 -0
  58. data/lib/hecks/doc/reference/readme.rb +1 -1
  59. data/lib/hecks/framework_bluebooks.rb +3 -2
  60. data/lib/hecks/fuzzing/differential.rb +1 -1
  61. data/lib/hecks/fuzzing/era_boundary.rb +1 -1
  62. data/lib/hecks/fuzzing/isolated_boot/postgres_scratch.rb +1 -1
  63. data/lib/hecks/fuzzing/persistence_parity.rb +1 -1
  64. data/lib/hecks/fuzzing/target_capabilities.rb +1 -1
  65. data/lib/hecks/hecks/adapters/ci_runs.adapter +7 -0
  66. data/lib/hecks/hecks/adapters/ci_runs.rb +80 -0
  67. data/lib/hecks/hecks/adapters/claude_agent_list.rb +43 -0
  68. data/lib/hecks/hecks/adapters/claude_agents.rb +50 -19
  69. data/lib/hecks/hecks/adapters/claude_jobs.rb +36 -0
  70. data/lib/hecks/hecks/adapters/codebase/corpus_queries.rb +2 -1
  71. data/lib/hecks/hecks/adapters/codebase/minitest_runs.rb +4 -2
  72. data/lib/hecks/hecks/adapters/codebase/run_options.rb +2 -1
  73. data/lib/hecks/hecks/adapters/codebase/test_suite.rb +3 -19
  74. data/lib/hecks/hecks/adapters/dispatching/brief_text.rb +5 -3
  75. data/lib/hecks/hecks/adapters/dispatching/launcher.rb +2 -1
  76. data/lib/hecks/hecks/adapters/dispatching/plan.rb +8 -4
  77. data/lib/hecks/hecks/adapters/dispatching/settings.rb +2 -1
  78. data/lib/hecks/hecks/adapters/dispatching/settler.rb +4 -2
  79. data/lib/hecks/hecks/adapters/dispatching.rb +6 -3
  80. data/lib/hecks/hecks/adapters/doctor_reading.rb +4 -2
  81. data/lib/hecks/hecks/adapters/git_landing/carried.rb +42 -0
  82. data/lib/hecks/hecks/adapters/git_landing/finisher.rb +2 -1
  83. data/lib/hecks/hecks/adapters/git_landing/flagged.rb +32 -0
  84. data/lib/hecks/hecks/adapters/git_landing/tidy.rb +27 -14
  85. data/lib/hecks/hecks/adapters/git_landing.rb +30 -13
  86. data/lib/hecks/hecks/adapters/git_leftovers.adapter +7 -0
  87. data/lib/hecks/hecks/adapters/git_leftovers.rb +49 -0
  88. data/lib/hecks/hecks/adapters/git_main_pull.rb +2 -2
  89. data/lib/hecks/hecks/adapters/git_train.rb +9 -5
  90. data/lib/hecks/hecks/adapters/git_worktree_add.rb +8 -4
  91. data/lib/hecks/hecks/adapters/github_pull_requests.rb +2 -1
  92. data/lib/hecks/hecks/adapters/in_process_operations.rb +2 -1
  93. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +1 -1
  94. data/lib/hecks/hecks/adapters/journal_store/readings/composition.rb +2 -1
  95. data/lib/hecks/hecks/adapters/journal_store/translation_write/shape_edge.rb +4 -2
  96. data/lib/hecks/hecks/adapters/journal_store/translation_write.rb +4 -2
  97. data/lib/hecks/hecks/adapters/landed_branches.rb +38 -0
  98. data/lib/hecks/hecks/adapters/main_boarding.adapter +7 -0
  99. data/lib/hecks/hecks/adapters/main_boarding.rb +85 -0
  100. data/lib/hecks/hecks/adapters/main_integration/checks.rb +63 -0
  101. data/lib/hecks/hecks/adapters/main_integration/command_runner.rb +32 -0
  102. data/lib/hecks/hecks/adapters/main_integration/range_guard.rb +49 -0
  103. data/lib/hecks/hecks/adapters/main_integration/repository.rb +83 -0
  104. data/lib/hecks/hecks/adapters/main_integration/runs.rb +51 -0
  105. data/lib/hecks/hecks/adapters/main_integration/train.rb +74 -0
  106. data/lib/hecks/hecks/adapters/main_integration.adapter +7 -0
  107. data/lib/hecks/hecks/adapters/main_integration.rb +77 -0
  108. data/lib/hecks/hecks/adapters/main_verification.adapter +7 -0
  109. data/lib/hecks/hecks/adapters/main_verification.rb +89 -0
  110. data/lib/hecks/hecks/adapters/push_recorder.adapter +7 -0
  111. data/lib/hecks/hecks/adapters/push_recorder.rb +78 -0
  112. data/lib/hecks/hecks/adapters/red_flag_guard.rb +62 -0
  113. data/lib/hecks/hecks/adapters/red_flag_marker.rb +101 -0
  114. data/lib/hecks/hecks/adapters/red_flag_report.rb +59 -0
  115. data/lib/hecks/hecks/adapters/red_flag_survey.rb +65 -0
  116. data/lib/hecks/hecks/adapters/red_flagging.adapter +7 -0
  117. data/lib/hecks/hecks/adapters/red_flagging.rb +98 -0
  118. data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +6 -1
  119. data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +6 -3
  120. data/lib/hecks/hecks/adapters/settings_reading/event_tap_row.rb +33 -0
  121. data/lib/hecks/hecks/adapters/settings_reading.rb +3 -1
  122. data/lib/hecks/hecks/codebase.behaviors +0 -6
  123. data/lib/hecks/hecks/custodian.behaviors +22 -1
  124. data/lib/hecks/hecks/delegate/delegate.bluebook +128 -15
  125. data/lib/hecks/hecks/deliver/deliver.bluebook +450 -2
  126. data/lib/hecks/hecks/environments/memory.world +5 -0
  127. data/lib/hecks/hecks/hecks.contextmap +8 -0
  128. data/lib/hecks/hecks/hecks.hecksagon +106 -6
  129. data/lib/hecks/hecks/hecks.world +9 -1
  130. data/lib/hecks/hecks/migrate/migrate.bluebook +6 -1
  131. data/lib/hecks/hecks/redflag/redflag.bluebook +249 -0
  132. data/lib/hecks/hecks/test/test.bluebook +0 -19
  133. data/lib/hecks/language/adapter.bluebook +13 -0
  134. data/lib/hecks/language/bluebook/vocabulary.bluebook +8 -9
  135. data/lib/hecks/language/sentence/sentence.bluebook +2 -0
  136. data/lib/hecks/pipeline/pipeline.bluebook +251 -0
  137. data/lib/hecks/pipeline/pipeline.hecksagon +17 -0
  138. data/lib/hecks/pipeline_board/adapters/cache.rb +43 -0
  139. data/lib/hecks/pipeline_board/adapters/pipeline_fill.rb +56 -0
  140. data/lib/hecks/pipeline_board/adapters/pipeline_git.rb +82 -0
  141. data/lib/hecks/pipeline_board/adapters/pipeline_github/runs.rb +66 -0
  142. data/lib/hecks/pipeline_board/adapters/pipeline_github.adapter +7 -0
  143. data/lib/hecks/pipeline_board/adapters/pipeline_github.rb +70 -0
  144. data/lib/hecks/pipeline_board/adapters/pipeline_jobs.adapter +7 -0
  145. data/lib/hecks/pipeline_board/adapters/pipeline_jobs.rb +87 -0
  146. data/lib/hecks/pipeline_board/adapters/pipeline_journal/chain.rb +40 -0
  147. data/lib/hecks/pipeline_board/adapters/pipeline_journal/tap.rb +75 -0
  148. data/lib/hecks/pipeline_board/adapters/pipeline_journal.adapter +7 -0
  149. data/lib/hecks/pipeline_board/adapters/pipeline_journal.rb +83 -0
  150. data/lib/hecks/pipeline_board/adapters/pipeline_stages/ci_tiles.rb +85 -0
  151. data/lib/hecks/pipeline_board/adapters/pipeline_stages/fanout.rb +73 -0
  152. data/lib/hecks/pipeline_board/adapters/pipeline_stages/gather.rb +57 -0
  153. data/lib/hecks/pipeline_board/adapters/pipeline_stages/metrics.rb +36 -0
  154. data/lib/hecks/pipeline_board/adapters/pipeline_stages/strip.rb +102 -0
  155. data/lib/hecks/pipeline_board/adapters/pipeline_stages/tile_kit.rb +57 -0
  156. data/lib/hecks/pipeline_board/adapters/pipeline_stages/work_tiles.rb +59 -0
  157. data/lib/hecks/pipeline_board/adapters/pipeline_stages.adapter +7 -0
  158. data/lib/hecks/pipeline_board/adapters/pipeline_stages.rb +84 -0
  159. data/lib/hecks/pipeline_board/adapters/pipeline_tickets.adapter +7 -0
  160. data/lib/hecks/pipeline_board/adapters/pipeline_tickets.rb +77 -0
  161. data/lib/hecks/pipeline_board/adapters/pipeline_web/page.html +292 -0
  162. data/lib/hecks/pipeline_board/adapters/pipeline_web/sequence.html +190 -0
  163. data/lib/hecks/pipeline_board/adapters/pipeline_web/server.rb +105 -0
  164. data/lib/hecks/pipeline_board/adapters/pipeline_web/snapshot.rb +57 -0
  165. data/lib/hecks/pipeline_board/adapters/pipeline_web/stream.rb +59 -0
  166. data/lib/hecks/pipeline_board/adapters/pipeline_web/wiring.html +536 -0
  167. data/lib/hecks/pipeline_board/adapters/pipeline_web.adapter +7 -0
  168. data/lib/hecks/pipeline_board/adapters/pipeline_web.rb +80 -0
  169. data/lib/hecks/pipeline_board/adapters/pipeline_wiring/details.rb +85 -0
  170. data/lib/hecks/pipeline_board/adapters/pipeline_wiring/graph.rb +120 -0
  171. data/lib/hecks/pipeline_board/adapters/pipeline_wiring.adapter +7 -0
  172. data/lib/hecks/pipeline_board/adapters/pipeline_wiring.rb +36 -0
  173. data/lib/hecks/pipeline_board/adapters/probe.rb +48 -0
  174. data/lib/hecks/pipeline_board/pipeline_board.bluebook +144 -0
  175. data/lib/hecks/pipeline_board/pipeline_board.hecksagon +12 -0
  176. data/lib/hecks/pipeline_board/pipeline_board.ports.hecksagon +47 -0
  177. data/lib/hecks/ports/persistence/binding_policy.rb +2 -1
  178. data/lib/hecks/ports/persistence/plugins/era/translation/compose.rb +4 -3
  179. data/lib/hecks/ports/persistence/repository_factory.rb +2 -1
  180. data/lib/hecks/projections/ci_pipeline/table.rb +2 -1
  181. data/lib/hecks/projections/deploy/lambda/stack/recipes.rb +2 -2
  182. data/lib/hecks/projections/undo.rb +2 -1
  183. data/lib/hecks/projector/cli_projector/command_help.rb +5 -4
  184. data/lib/hecks/projector/cli_projector/renames.rb +4 -2
  185. data/lib/hecks/projector/cli_projector/usage.rb +4 -3
  186. data/lib/hecks/projector/exporter/adapters.rb +3 -2
  187. data/lib/hecks/projector/narrate_projector/use_cases.rb +74 -0
  188. data/lib/hecks/projector/narrate_projector.rb +10 -3
  189. data/lib/hecks/quality_control/adapters/main_maintenance.rb +9 -5
  190. data/lib/hecks/quality_control/adapters/qa_auto_mint.rb +21 -2
  191. data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +1 -1
  192. data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +2 -1
  193. data/lib/hecks/quality_control/cli/qa_fix_main_bug/publishing.rb +8 -4
  194. data/lib/hecks/quality_control/cli/qa_fix_main_bug.rb +10 -8
  195. data/lib/hecks/quality_control/cli/qa_generated_domains/promotion.rb +2 -2
  196. data/lib/hecks/quality_control/cli/qa_main_gate/gate_run.rb +6 -3
  197. data/lib/hecks/quality_control/cli/qa_main_gate/logging.rb +8 -4
  198. data/lib/hecks/quality_control/cli/qa_main_gate/shell.rb +4 -2
  199. data/lib/hecks/quality_control/cli/qa_main_gate/sweeping.rb +8 -4
  200. data/lib/hecks/quality_control/cli/qa_main_gate/worktree.rb +3 -2
  201. data/lib/hecks/quality_control/cli/qa_main_gate.rb +10 -5
  202. data/lib/hecks/quality_control/cli/qa_sweep/all_mode/children.rb +1 -1
  203. data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +1 -1
  204. data/lib/hecks/quality_control/cli/qa_sweep.rb +1 -1
  205. data/lib/hecks/quality_control/cli/qa_tick.rb +6 -3
  206. data/lib/hecks/quality_control/quality_control.bluebook +11 -11
  207. data/lib/hecks/quality_control/quality_control.ports.hecksagon +6 -3
  208. data/lib/hecks/query_ir/touchpoints.rb +2 -1
  209. data/lib/hecks/refused.rb +2 -1
  210. data/lib/hecks/release/approval.rb +4 -2
  211. data/lib/hecks/runtime/event_log.rb +27 -0
  212. data/lib/hecks/runtime/event_tap.rb +106 -0
  213. data/lib/hecks/runtime/loader/boot_steps.rb +1 -1
  214. data/lib/hecks/runtime/loader.rb +35 -19
  215. data/lib/hecks/runtime/registry/durability_warnings.rb +12 -5
  216. data/lib/hecks/runtime/registry/runtime_state.rb +4 -1
  217. data/lib/hecks/runtime/registry/saga_anchor.rb +54 -0
  218. data/lib/hecks/runtime/registry/saga_durability_rule.rb +66 -0
  219. data/lib/hecks/runtime/registry/saga_persistence.rb +58 -12
  220. data/lib/hecks/runtime/registry/verification.rb +7 -3
  221. data/lib/hecks/runtime/saga_interpreter/lifecycle.rb +2 -1
  222. data/lib/hecks/runtime/saga_interpreter.rb +1 -1
  223. data/lib/hecks/rust_format.rb +2 -1
  224. data/lib/hecks/shape_ledger/edge_hygiene.rb +10 -5
  225. data/lib/hecks/shape_ledger.rb +1 -1
  226. data/lib/hecks/storehouse/audit.rb +2 -1
  227. data/lib/hecks/storehouse/identity.rb +6 -3
  228. data/lib/hecks/storehouse.rb +2 -1
  229. data/lib/hecks/tickets/bluebook/tickets.bluebook +5 -4
  230. data/lib/hecks/tools/ci_workflows.rb +2 -1
  231. data/lib/hecks/tools/document.rb +12 -1
  232. data/lib/hecks/tools/downstream_model_check.rb +4 -2
  233. data/lib/hecks/tools/fuzz_sweep/shards.rb +9 -30
  234. data/lib/hecks/tools/model_check/examination.rb +25 -2
  235. data/lib/hecks/tools/move_disables.rb +4 -2
  236. data/lib/hecks/tools/regeneration_run/scratch.rb +2 -1
  237. data/lib/hecks/tools/rspec_shard_files.rb +12 -15
  238. data/lib/hecks/tools/sequence_script.rb +1 -1
  239. data/lib/hecks/tools/shard_planner.rb +65 -0
  240. data/lib/hecks/tools/stage_progress.rb +2 -1
  241. data/lib/hecks/tools/stress_concurrency_specs.rb +2 -1
  242. data/lib/hecks/version.rb +1 -1
  243. data/lib/hecks/vocabulary.rb +8 -9
  244. data/lib/hecks.rb +5 -2
  245. data/prebuilt/{13c04bdc5970295b310f0a980d397ba722232c8da99cc1a0cdb383249921e871.marshal → 4b81646f769ae5b1e7457f9b11c48d8bc1093e96a1dd6fb61864065121ca6f01.marshal} +0 -0
  246. data/prebuilt/{e3857b77f47d9ecc1787b2e1f5136364f3cb3444ef4aea5479f3b98167affea2.marshal → 681890632e3f4a99646623dcae44af36b1bad66c515f5abf67a2460372b9c6fd.marshal} +0 -0
  247. data/rust/host/HECKS_RELEASE +1 -1
  248. data/rust/parser/src/emit.rs +5 -1
  249. data/rust/parser/src/ir.rs +2 -0
  250. data/rust/parser/src/keywords.rs +12 -0
  251. data/rust/parser/src/main.rs +1 -0
  252. data/rust/parser/src/parse/adapter.rs +1 -0
  253. data/rust/parser/src/phrases.rs +16 -0
  254. data/rust/parser/src/sentence/examples.rs +93 -0
  255. data/rust/parser/src/sentence/mod.rs +1 -0
  256. metadata +89 -5
  257. data/lib/hecks/tools/refresh_rspec_runtime_baseline.rb +0 -166
@@ -33,13 +33,14 @@ module Hecks
33
33
  }.merge(optional_adapter_keys(adapter))
34
34
  end
35
35
 
36
- # The keys only some adapters carry: `provides` when it names a capability, and a
37
- # plugin's `answers` and `runs`.
36
+ # The keys only some adapters carry: `provides` when it names a capability, `stays_inside`
37
+ # when the adapter says it, and a plugin's `answers` and `runs`.
38
38
  #
39
39
  # @param adapter [Bluebook::Hecksagon::Adapter] the adapter to export
40
40
  # @return [Hash{String => Object}] the keys it carries beyond the five every adapter has
41
41
  def optional_adapter_keys(adapter)
42
42
  keys = adapter.capabilities.empty? ? {} : { "provides" => adapter.capabilities }
43
+ keys["stays_inside"] = true if adapter.stays_inside?
43
44
  return keys unless adapter.plugin?
44
45
 
45
46
  keys.merge("answers" => adapter.answered_queries, "runs" => adapter.runs)
@@ -0,0 +1,74 @@
1
+ module Hecks
2
+ module Projector
3
+ module NarrateProjector
4
+ # The prose for the goals an `.examples` file declares (ADR 0173): each goal as a use case,
5
+ # its first example the main scenario and the rest its extensions. Every line is quoted from
6
+ # the file; `hecks-parse examples` has already checked it against the bluebook.
7
+ module UseCases
8
+ GOAL = /\A(\s*)Goal:\s*(.+?)\s*\z/
9
+ EXAMPLE = /\A\s*Example:\s*(.+?)\s*\z/
10
+ STEP = /\A\s*(Given|When|Then|And)\s+(.+?)\s*\z/
11
+ INDENT = /\A\s*/
12
+
13
+ module_function
14
+
15
+ # The "Use cases" section, or `nil` when no file declares a goal.
16
+ #
17
+ # @param paths [Array<String>] `.examples` files
18
+ # @param depth [Integer] the section's heading level
19
+ # @return [String, nil] Markdown
20
+ def narrative(paths, depth)
21
+ goals = Array(paths).flat_map { |path| goals_in(File.readlines(path, chomp: true)) }
22
+ return nil if goals.empty?
23
+
24
+ [DocsProjector.h(depth, "Use cases"), *goals.map { |goal| goal_text(goal, depth + 1) }].join("\n\n")
25
+ end
26
+
27
+ # @param lines [Array<String>] one `.examples` file
28
+ # @return [Array<Hash>] each goal with its examples, each example with its step lines
29
+ def goals_in(lines)
30
+ open = nil
31
+ lines.each_with_object([]) do |line, goals|
32
+ open = follow(line, open, goals) unless line.strip.empty?
33
+ end
34
+ end
35
+
36
+ # Reads one line against the goal still open, answering the goal open after it: a `Goal:`
37
+ # opens one, a line indented under it belongs to it, any other line closes it.
38
+ def follow(line, open, goals)
39
+ if (goal = line.match(GOAL))
40
+ goals << { indent: goal[1].size, goal: goal[2], examples: [] }
41
+ return goals.last
42
+ end
43
+ return nil unless open && indent_of(line) > open[:indent]
44
+
45
+ add_to(open, line)
46
+ open
47
+ end
48
+
49
+ # Files one line of a goal: an example under it, or a step of its latest example.
50
+ def add_to(goal, line)
51
+ if (example = line.match(EXAMPLE))
52
+ goal[:examples] << { title: example[1], steps: [] }
53
+ elsif (step = line.match(STEP)) && goal[:examples].any?
54
+ goal[:examples].last[:steps] << "#{step[1]} #{step[2]}"
55
+ end
56
+ end
57
+
58
+ def indent_of(line) = line[INDENT].size
59
+
60
+ def goal_text(goal, depth)
61
+ main, *extensions = goal[:examples]
62
+ [DocsProjector.h(depth, goal[:goal]), scenario("Main scenario", main),
63
+ *extensions.map { |extension| scenario("Extension", extension) }].compact.join("\n\n")
64
+ end
65
+
66
+ def scenario(label, example)
67
+ return nil unless example
68
+
69
+ ["**#{label}: #{example[:title]}**", *example[:steps].map { |step| "- #{step}" }].join("\n")
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -11,8 +11,9 @@ module Hecks
11
11
  # Projects `bluebook` as prose; `options[:heading]` sets the top heading level.
12
12
  #
13
13
  # @param bluebook [Bluebook::Behaviour::Bluebook] the bluebook to narrate
14
- # @param options [Hash] `:heading` (Integer, default 1) and `:aggregate` (narrows to one
15
- # aggregate, omitting the bluebook intro and reactions)
14
+ # @param options [Hash] `:heading` (Integer, default 1), `:aggregate` (narrows to one
15
+ # aggregate, omitting the bluebook intro and reactions) and `:examples` (`.examples` files
16
+ # whose goals are told as use cases, ADR 0173)
16
17
  # @return [String] Markdown prose ending in a newline
17
18
  # @raise [Runtime::NotFound] if `options[:aggregate]` names no declared aggregate
18
19
  def call(bluebook:, options: {})
@@ -22,10 +23,15 @@ module Hecks
22
23
  sections = []
23
24
  sections << bluebook_intro(bluebook, depth) unless only
24
25
  sections.concat(aggregate_narratives(bluebook, only, only ? depth : depth + 1))
25
- sections << Reactions.narrative(bluebook, depth + 1) unless only
26
+ sections.concat(closing_sections(bluebook, options[:examples], depth + 1)) unless only
26
27
  "#{sections.compact.join("\n\n").rstrip}\n"
27
28
  end
28
29
 
30
+ # What the bluebook causes on its own, then the use cases its examples declare.
31
+ def closing_sections(bluebook, examples, depth)
32
+ [Reactions.narrative(bluebook, depth), UseCases.narrative(examples, depth)]
33
+ end
34
+
29
35
  # One narrative per aggregate, at heading level `depth`.
30
36
  def aggregate_narratives(bluebook, only, depth)
31
37
  Array(DocsProjector.aggregates(bluebook, only)).map { |aggregate| aggregate_narrative(aggregate, depth) }
@@ -140,3 +146,4 @@ end
140
146
 
141
147
  require_relative "narrate_projector/commands"
142
148
  require_relative "narrate_projector/reactions"
149
+ require_relative "narrate_projector/use_cases"
@@ -138,12 +138,13 @@ module Hecks
138
138
  # @param request [Hash] the `MintRequested` attributes: `run`, `domain`, `era`, `sha`,
139
139
  # `branch`,
140
140
  # `watermark`, `rehearsal_verdict`, `refusal_total`, `ledger_dir` and `confirm`
141
- # @return [Hash] `run`, `domain`, `era`, `sha`, `branch` and `label`
141
+ # @return [Hash] `run`, `domain`, `era`, `sha` and `label` as values; `branch` and
142
+ # `ledger_dir` as the strings the landing takes
142
143
  def mint(**request)
143
144
  refuse_while_stranded!
144
145
  refuse_unrehearsed!(value_of(request[:rehearsal_verdict]))
145
- label = run_mint!(request, authorize!(request))
146
- %i[run domain era sha branch].to_h { |key| [key, wrap(request[key])] }.merge(label: wrap(label))
146
+ label = wrap(run_mint!(request, authorize!(request)))
147
+ %i[run domain era sha].to_h { |key| [key, wrap(request[key])] }.merge(label:, **request.slice(:branch, :ledger_dir))
147
148
  end
148
149
 
149
150
  # Answers a held mint's approval request: the mint waits for the owner, and nothing follows
@@ -160,9 +161,12 @@ module Hecks
160
161
  # mint
161
162
  # refuses until the owner removes it.
162
163
  #
164
+ # `ledger_dir` names the minted ledger's bluebook directory; an empty one is the
165
+ # QualityControl ledger, which lands in this checkout.
166
+ #
163
167
  # @return [Hash] `run`, `era`, `sha` and `branch`, which `MintStranded` takes on refusal
164
- def land(run:, era:, sha:, branch:, **)
165
- output, landed = @commands.land(@root, value_of(branch))
168
+ def land(run:, era:, sha:, branch:, **answer)
169
+ output, landed = @commands.land(QaAutoMint.checkout_for(@root, value_of(answer[:ledger_dir])), value_of(branch))
166
170
  strand!(run, era, sha, output) unless landed
167
171
  { run: wrap(run), era: wrap(era), sha: wrap(sha), branch: wrap(branch) }
168
172
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "open3"
3
4
  require_relative "qa_era_mint"
4
5
  require_relative "../../adapters/driven/postgres_era/lineage_manager/mint_decision"
5
6
 
@@ -49,10 +50,28 @@ module Hecks
49
50
  def held_bluebook(latest) = PostgresEra::LineageManager.held_bluebook(latest)
50
51
 
51
52
  # The bluebook directory a mint is for: the one the request names, or the QualityControl
52
- # ledger.
53
+ # ledger when it names none or an empty one.
53
54
  #
54
55
  # @return [String]
55
- def ledger_for(root, requested) = requested || File.join(root, QaEraMint::LEDGER)
56
+ def ledger_for(root, requested)
57
+ requested.to_s.empty? ? File.join(root, QaEraMint::LEDGER) : requested
58
+ end
59
+
60
+ # The checkout a minted ledger's branch lands in: `root` for the QualityControl ledger, else
61
+ # the git checkout around the ledger's directory.
62
+ #
63
+ # @param root [String] the Hecks checkout
64
+ # @param requested [String, nil] the ledger's bluebook directory, or nil or empty for QA's
65
+ # @return [String] the checkout's top directory
66
+ # @raise [Runtime::WiringError] if the ledger's directory is in no git checkout
67
+ def checkout_for(root, requested)
68
+ return root if requested.to_s.empty?
69
+
70
+ top, status = Open3.capture2e("git", "-C", requested, "rev-parse", "--show-toplevel")
71
+ raise Runtime::WiringError, "#{requested} is in no git checkout: #{top.strip}" unless status.success?
72
+
73
+ top.strip
74
+ end
56
75
 
57
76
  # The ledger a mint is for, refused by name when its world declares it disposable: its boot
58
77
  # holds every shape change itself, so there is no era to mint.
@@ -10,7 +10,7 @@ module Hecks
10
10
  # The command behind `hecks quality_control sweep.race`: one racer of
11
11
  # `Hecks::Fuzzing::ConcurrentDispatch`, run as its own OS process.
12
12
  #
13
- # It is spawned, not forked: the caller (`hecks quality_control query sweep.run`) holds live
13
+ # It is spawned, not forked: the caller (`hecks quality_control sweep.run`) holds live
14
14
  # `PostgresEra` connections, and fork duplicates their file descriptors and SSL state,
15
15
  # corrupting them when a child exits.
16
16
  #
@@ -99,7 +99,8 @@ module Hecks
99
99
  [argv.first(split), argv.drop(split + 1).reject { |arg| arg == "--against" }]
100
100
  end
101
101
 
102
- # `hecks build.project_rust!` reads exactly `<name>/bluebook/<name>.bluebook`, so refuse anything
102
+ # `hecks build.project_rust!` reads exactly `<name>/bluebook/<name>.bluebook`, so refuse
103
+ # anything
103
104
  # else early.
104
105
  def refuse_unless_stress_domain_shaped!(path)
105
106
  expected = File.join(path, "bluebook", "#{File.basename(path)}.bluebook")
@@ -3,9 +3,12 @@
3
3
  module Hecks
4
4
  module QualityControlCli
5
5
  class QaFixMainBug
6
- # What happens after a fix is committed: the ledger records the investigation and the fix, the branch
7
- # lands on local main, the ledger records the verification, and main is pushed through the push train.
8
- # The push runs only after the quick gate is green on main's head, and origin's main is fetched right
6
+ # What happens after a fix is committed: the ledger records the investigation and the fix, the
7
+ # branch
8
+ # lands on local main, the ledger records the verification, and main is pushed through the
9
+ # push train.
10
+ # The push runs only after the quick gate is green on main's head, and origin's main is
11
+ # fetched right
9
12
  # before it.
10
13
  module Publishing
11
14
  private
@@ -62,7 +65,8 @@ module Hecks
62
65
  unpushed("the push train refused main: #{output.strip}")
63
66
  end
64
67
 
65
- # Fetches origin's main right before the push. @return [String, nil] why main cannot be pushed as it is
68
+ # Fetches origin's main right before the push. @return [String, nil] why main cannot be
69
+ # pushed as it is
66
70
  def behind_origin
67
71
  QaMainGate::Worktree.git!(@root, "fetch", "origin", "main")
68
72
  return nil if QaMainGate::Worktree.git?(@root, "merge-base", "--is-ancestor", "origin/main", "main")
@@ -11,14 +11,15 @@ require_relative "qa_fix_main_bug/publishing"
11
11
 
12
12
  module Hecks
13
13
  module QualityControlCli
14
- # The command behind `hecks quality_control bug.fix_and_land --bug BUG#n`: takes one self-contained red-main
15
- # bug through a fix on a fresh branch off main, the quick gate on that exact commit, a landing on local main,
16
- # and a push through the push train. A bug triaged bigger, or one whose fixer changes nothing or leaves a
17
- # check red, stops before anything lands and stays open for the owner. Nothing is landed unless the gate is
18
- # green on the commit, and nothing is pushed unless the gate is green on main's head.
14
+ # The command behind `hecks quality_control bug.fix_and_land --bug BUG#n`: takes one
15
+ # self-contained red-main bug through a fix on a fresh branch off main, the quick gate on that
16
+ # exact commit, a landing on local main, and a push through the push train. A bug triaged
17
+ # bigger, or one whose fixer changes nothing or leaves a check red, stops before anything lands
18
+ # and stays open for the owner. Nothing is landed unless the gate is green on the commit, and
19
+ # nothing is pushed unless the gate is green on main's head.
19
20
  #
20
- # Exit 0 when the fix is landed (pushed, or the push's refusal is printed), 1 when refused before any fix,
21
- # 2 when the fix stays red.
21
+ # Exit 0 when the fix is landed (pushed, or the push's refusal is printed), 1 when refused
22
+ # before any fix, 2 when the fix stays red.
22
23
  class QaFixMainBug
23
24
  include Publishing
24
25
 
@@ -140,7 +141,8 @@ module Hecks
140
141
  QaMainGate::Worktree.git?(@root, "rev-parse", "--verify", "--quiet", "refs/heads/#{branch}")
141
142
  end
142
143
 
143
- # The fixer runs, then the gate on the exact commit it makes. Returns the commit, or what kept it red.
144
+ # The fixer runs, then the gate on the exact commit it makes. Returns the commit, or what kept
145
+ # it red.
144
146
  def fix_in(dir, row)
145
147
  @shell.rubocop(dir, files_of(row))
146
148
  return Stuck.new("the fixer changed nothing") unless changed?(dir)
@@ -67,7 +67,7 @@ module Hecks
67
67
  <<~NOTES
68
68
  # #{name}
69
69
 
70
- Promoted from a `hecks quality_control query target.check_generated_domains` finding — #{origin_phrase(recorded)}, not
70
+ Promoted from a `hecks quality_control target.check_generated_domains` finding — #{origin_phrase(recorded)}, not
71
71
  hand-written. The bluebook is the minimal form of the domain that
72
72
  surprised; `QaGenerated` was renamed to `#{camelize(name)}` and nothing
73
73
  else changed.
@@ -102,7 +102,7 @@ module Hecks
102
102
  end
103
103
 
104
104
  def origin_phrase(recorded)
105
- recorded&.key?("source") ? "written by `hecks quality_control query target.mine_combinations`' agent" : "generated"
105
+ recorded&.key?("source") ? "written by `hecks quality_control target.mine_combinations`' agent" : "generated"
106
106
  end
107
107
  end
108
108
  end
@@ -5,8 +5,10 @@ require "hecks/tools/gate"
5
5
  module Hecks
6
6
  module QualityControlCli
7
7
  class QaMainGate
8
- # Runs the quick gate's checks in a directory, all at once, and says which passed. The checks are
9
- # the `GateStage` rows of the Vocabulary bluebook, so a red check is reported with the output it
8
+ # Runs the quick gate's checks in a directory, all at once, and says which passed. The checks
9
+ # are
10
+ # the `GateStage` rows of the Vocabulary bluebook, so a red check is reported with the output
11
+ # it
10
12
  # printed, the same way `hecks gate_run.gate! stage=quick` reports one.
11
13
  module GateRun
12
14
  # One check of the quick gate after it ran.
@@ -21,7 +23,8 @@ module Hecks
21
23
  # @return [Boolean] whether it exited 0
22
24
  Result = Struct.new(:id, :title, :output, :passed)
23
25
 
24
- # Stands in for the gate's progress lines, which go to standard error of a person's terminal.
26
+ # Stands in for the gate's progress lines, which go to standard error of a person's
27
+ # terminal.
25
28
  class Quiet
26
29
  # @return [nil]
27
30
  def start(_id) = nil
@@ -3,17 +3,21 @@
3
3
  module Hecks
4
4
  module QualityControlCli
5
5
  class QaMainGate
6
- # Turns one red check into one QA bug, proven by the check's own output, and triages it. A check the
6
+ # Turns one red check into one QA bug, proven by the check's own output, and triages it. A
7
+ # check the
7
8
  # command can fix mechanically is `self_contained`; every other is `bigger`, for the owner.
8
9
  module Logging
9
- # Checks with a mechanical fixer. `rubocop -a` fixes style offenses it can fix; metrics and the
10
+ # Checks with a mechanical fixer. `rubocop -a` fixes style offenses it can fix; metrics and
11
+ # the
10
12
  # rest need a person.
11
13
  SELF_CONTAINED = %w[rubocop].freeze
12
14
  SUBMITTER = "qa_main_gate"
13
15
  NAME_PREFIX = "main-red-"
14
16
 
15
- # A bug already open for the same check is not logged again: the commit moved, the red did not.
16
- # @return [Array(String, String, Boolean)] the reference, its disposition, and whether it was new
17
+ # A bug already open for the same check is not logged again: the commit moved, the red did
18
+ # not.
19
+ # @return [Array(String, String, Boolean)] the reference, its disposition, and whether it
20
+ # was new
17
21
  def log_red(sweep, result, sha)
18
22
  open = open_bug_named(bug_name(result.id))
19
23
  return [open, disposition(result.id), false] if open
@@ -5,7 +5,8 @@ require "open3"
5
5
  module Hecks
6
6
  module QualityControlCli
7
7
  class QaMainGate
8
- # The commands outside the ledger that a red-main fix runs: the mechanical fixer, the landing, and the
8
+ # The commands outside the ledger that a red-main fix runs: the mechanical fixer, the landing,
9
+ # and the
9
10
  # push through the push train. Each answers its output and whether it exited 0.
10
11
  module Shell
11
12
  # The push train's own launcher, in its detached checkout.
@@ -13,7 +14,8 @@ module Hecks
13
14
 
14
15
  module_function
15
16
 
16
- # Applies `rubocop -a` to the files, which fixes what it can; whether the rest is fixed is the gate's
17
+ # Applies `rubocop -a` to the files, which fixes what it can; whether the rest is fixed is
18
+ # the gate's
17
19
  # to say.
18
20
  #
19
21
  # @return [Array(String, Boolean)]
@@ -3,8 +3,10 @@
3
3
  module Hecks
4
4
  module QualityControlCli
5
5
  class QaMainGate
6
- # The sweep a red run of the gate on main is written as. Main is not a bluebook, so the sweep runs
7
- # against one standing target, `main-gate`, that is shelved: the rotation never claims it. Each gate
6
+ # The sweep a red run of the gate on main is written as. Main is not a bluebook, so the sweep
7
+ # runs
8
+ # against one standing target, `main-gate`, that is shelved: the rotation never claims it.
9
+ # Each gate
8
10
  # check is one `Check`, its expectation written before the answer is read.
9
11
  module Sweeping
10
12
  TARGET = "main-gate"
@@ -42,8 +44,10 @@ module Hecks
42
44
  dispatch("QualityControl::Sweep.Check.Held", id: sweep.id, sequence: { value: sequence },
43
45
  observation: { value: "exit 0" })
44
46
  else
45
- # Not `Check.Surprised`: its `SuspendOnSurprise` policy can only suspend a held target, and this
46
- # one is shelved, so the policy would be refused on every red run. The red is carried by the
47
+ # Not `Check.Surprised`: its `SuspendOnSurprise` policy can only suspend a held target,
48
+ # and this
49
+ # one is shelved, so the policy would be refused on every red run. The red is carried by
50
+ # the
47
51
  # `MainCheck` record and the bugs logged from the output.
48
52
  dispatch("QualityControl::Sweep.Check.Unsettled", id: sweep.id, sequence: { value: sequence },
49
53
  observation: { value: result.output })
@@ -7,7 +7,8 @@ require "tmpdir"
7
7
  module Hecks
8
8
  module QualityControlCli
9
9
  class QaMainGate
10
- # A temporary git worktree, made for one block and removed when it returns, so a gate or a fix never
10
+ # A temporary git worktree, made for one block and removed when it returns, so a gate or a fix
11
+ # never
11
12
  # runs in the owner's checkout.
12
13
  module Worktree
13
14
  # Raised when a git command the worktree needs fails.
@@ -17,7 +18,7 @@ module Hecks
17
18
 
18
19
  # @param root [String] the repository the worktree belongs to
19
20
  # @param ref [String] the commit the worktree starts at
20
- # @param branch [String, nil] a new branch checked out in it; nil leaves HEAD detached
21
+ # @param branch [String, nil] a new branch checked out in it; nil leaves head detached
21
22
  # @yieldparam dir [String] the worktree's directory
22
23
  # @return [Object] whatever the block returns
23
24
  def with(root:, ref:, branch: nil)
@@ -13,13 +13,17 @@ require_relative "qa_fix_main_bug"
13
13
 
14
14
  module Hecks
15
15
  module QualityControlCli
16
- # The command behind `hecks quality_control main_check.check_main`, the tick's main step. It runs
17
- # the quick gate on local main in a temporary worktree, records the verdict against the commit, and
16
+ # The command behind `hecks quality_control main_check.check_main`, the tick's main step. It
17
+ # runs
18
+ # the quick gate on local main in a temporary worktree, records the verdict against the commit,
19
+ # and
18
20
  # for each red check logs one bug proven by its output. A self-contained bug is handed to
19
- # `QaFixMainBug`. A commit already on record is answered from the record, so a tick that finds it again
21
+ # `QaFixMainBug`. A commit already on record is answered from the record, so a tick that finds
22
+ # it again
20
23
  # logs nothing twice.
21
24
  #
22
- # Exit 0 when main is green, 2 when a check is red (the bugs are on the ledger), 1 on an operational
25
+ # Exit 0 when main is green, 2 when a check is red (the bugs are on the ledger), 1 on an
26
+ # operational
23
27
  # error. `QA_SWEEP_DOMAIN_DIR` picks the ledger, as for the other QA commands.
24
28
  class QaMainGate
25
29
  include Sweeping
@@ -36,7 +40,8 @@ module Hecks
36
40
  # @param argv [Array<String>] no arguments
37
41
  # @param root [String] the repository root, whose local `main` is checked
38
42
  # @param env [Hash{String => String}] `QA_SWEEP_DOMAIN_DIR` picks another ledger
39
- # @param gate [#run] runs the quick gate in a directory and answers its results; a spec injects one
43
+ # @param gate [#run] runs the quick gate in a directory and answers its results; a spec
44
+ # injects one
40
45
  # @param shell [Module] the fixer, landing and push commands; a spec injects one
41
46
  # @return [Integer] the exit status
42
47
  def self.call(argv, root:, env: ENV, gate: GateRun, shell: Shell)
@@ -16,7 +16,7 @@ module Hecks
16
16
 
17
17
  private
18
18
 
19
- # Spawns `hecks quality_control query sweep.run target=<target>` as a fresh process, not a
19
+ # Spawns `hecks quality_control sweep.run target=<target>` as a fresh process, not a
20
20
  # fork.
21
21
  def spawn_sweep_child(target_reference, flags, modes:, parity: false)
22
22
  # Unlinked at once: the open fd stays readable, nothing is left on disk, and no fixed
@@ -66,7 +66,7 @@ module Hecks
66
66
 
67
67
  # Coverage-guided generation's corpus, one JSON file per (target, mode): `--all`'s waves can
68
68
  # run a target's primary sweep and its persistence-parity/concurrency variants as separate
69
- # `hecks quality_control query sweep.run` processes within the same tick, and each explores
69
+ # `hecks quality_control sweep.run` processes within the same tick, and each explores
70
70
  # a conceptually independent axis of the same domain, so each keeps its own file rather than
71
71
  # sharing one that nothing here locks.
72
72
  def coverage_corpus_path(target_reference, mode)
@@ -28,7 +28,7 @@ require_relative "qa_sweep/conclusion"
28
28
 
29
29
  module Hecks
30
30
  module QualityControlCli
31
- # The command behind `hecks quality_control query sweep.run`: runs one claim, sweep, conclude
31
+ # The command behind `hecks quality_control sweep.run`: runs one claim, sweep, conclude
32
32
  # and release cycle against a `QualityControl` target (`USAGE` lists the forms, from a bare
33
33
  # sweep of the least recently swept target to `--all` and `--release`). It stops with a report
34
34
  # and exit 2 on the first surprised check, and never logs a `Bug` itself.
@@ -122,7 +122,8 @@ module Hecks
122
122
  report(steps, reclaimed_holds(steps[1].output), write_tick_log!(steps))
123
123
  end
124
124
 
125
- # The steps that look for findings, in the order the report prints them. The event pump is last.
125
+ # The steps that look for findings, in the order the report prints them. The event pump is
126
+ # last.
126
127
  def finding_steps
127
128
  [
128
129
  guarded("check_pull_requests") { run_pr_check },
@@ -161,7 +162,8 @@ module Hecks
161
162
  [output, exit_code]
162
163
  end
163
164
 
164
- # The quick gate on local main, last: each red check it finds becomes a bug and a self-contained one
165
+ # The quick gate on local main, last: each red check it finds becomes a bug and a
166
+ # self-contained one
165
167
  # is fixed, landed and pushed by `QaMainGate`.
166
168
  def run_main_check
167
169
  banner "hecks quality_control check_main"
@@ -170,7 +172,8 @@ module Hecks
170
172
  [output, exit_code]
171
173
  end
172
174
 
173
- # @return [Object] the dial's value from `qa/settings.yml`, or the fallback when it is not loaded
175
+ # @return [Object] the dial's value from `qa/settings.yml`, or the fallback when it is not
176
+ # loaded
174
177
  def dial(name, fallback)
175
178
  return fallback unless defined?(::QualityControlDials) && ::QualityControlDials.const_defined?(name)
176
179
 
@@ -45,7 +45,7 @@ Hecks.bluebook "QualityControl" do
45
45
  attribute :last_swept, SweptIn
46
46
  # Recency-decayed count of how often this bluebook was worth sweeping; see `YieldScore`.
47
47
  attribute :yield_score, YieldScore
48
- # Consecutive `Release`s that came back clean; `hecks quality_control query sweep.run` reads it to choose sweep depth.
48
+ # Consecutive `Release`s that came back clean; `hecks quality_control sweep.run` reads it to choose sweep depth.
49
49
  # Zero means no streak yet, shared on purpose by a never-swept target and a just-surprised one.
50
50
  attribute :clean_streak, CleanStreak
51
51
  # Comma-joined mode names recorded at release from what the runner inferred. Stored so
@@ -267,7 +267,7 @@ Hecks.bluebook "QualityControl" do
267
267
  end
268
268
 
269
269
  query "Suspended" do
270
- description "Bluebooks a check surprised, waiting for a person — each with the reason the policy wrote. Nothing automatic touches these; hecks quality_control query sweep.run <target> --release --notes is how one comes back."
270
+ description "Bluebooks a check surprised, waiting for a person — each with the reason the policy wrote. Nothing automatic touches these; hecks quality_control sweep.run <target> --release --notes is how one comes back."
271
271
  where(status: "suspended")
272
272
  order_by :reference
273
273
  end
@@ -2287,7 +2287,7 @@ Hecks.bluebook "QualityControl" do
2287
2287
  # a boot never mints, and the mint names the commit its landing puts on main, so the ledger and main move
2288
2288
  # together. The rehearsal ref and the expected watermark are checked against the live ledger by the era check.
2289
2289
  # `refusal_total` is the rehearsal's refusal count and `ledger_dir` the bluebook directory of the ledger
2290
- # the era belongs to (the QualityControl ledger when omitted). A mint with no `confirm` runs only when
2290
+ # the era belongs to (the QualityControl ledger when empty, its default, so it is always on the event). A mint with no `confirm` runs only when
2291
2291
  # the auto-mint rule (`AutoMintAdditive`) holds; the mint entry point checks it.
2292
2292
  command "MintEra" do
2293
2293
  role "QA engineer"
@@ -2301,7 +2301,7 @@ Hecks.bluebook "QualityControl" do
2301
2301
  attribute :watermark, Integer
2302
2302
  attribute :rehearsal_verdict, MintVerdict
2303
2303
  attribute :refusal_total, Integer, optional: true
2304
- attribute :ledger_dir, String, optional: true
2304
+ attribute :ledger_dir, String, default: ""
2305
2305
  attribute :confirm, Confirmation, optional: true
2306
2306
 
2307
2307
  emits "MintRequested"
@@ -3039,14 +3039,14 @@ Hecks.bluebook "QualityControl" do
3039
3039
  # Nothing reacts to `TicketRaised` on purpose: a ticket waits until somebody submits it.
3040
3040
 
3041
3041
  # The rotation's one automatic reaction. Releasing on `SweepConcluded` cannot work: that event
3042
- # carries no target id, `next_streak` or yield score, so `hecks quality_control query sweep.run` still releases.
3042
+ # carries no target id, `next_streak` or yield score, so `hecks quality_control sweep.run` still releases.
3043
3043
  # `on Sweep::CheckSurprised`, not `Sweep::Check::...`: an entity command's event is stamped with
3044
3044
  # its parent aggregate. `Check.Surprised` restates `target` so the id rides in.
3045
3045
  policy "SuspendOnSurprise" do
3046
3046
  on Sweep::CheckSurprised
3047
3047
  trigger Target::Suspend, with: {
3048
3048
  target: :target,
3049
- reason: { value: "a check surprised — a person releases with hecks quality_control query sweep.run <target> --release --notes" }
3049
+ reason: { value: "a check surprised — a person releases with hecks quality_control sweep.run <target> --release --notes" }
3050
3050
  }
3051
3051
  end
3052
3052
 
@@ -3193,14 +3193,14 @@ Hecks.bluebook "QualityControl" do
3193
3193
  # (d) no stranded marker exists.
3194
3194
  # Otherwise the mint is held: the entry point refuses it as "needs owner approval", and the policy below
3195
3195
  # asks for the approval, which answers `MintNeedsApproval` and stops. A stale watermark or a stranded
3196
- # marker is a refusal, never a held mint. The ask forwards no `ledger_dir`: an optional attribute a call
3197
- # leaves out is not on the event, so the entry point takes the QualityControl ledger by default.
3196
+ # marker is a refusal, never a held mint. The ask forwards `ledger_dir`, empty for the QualityControl
3197
+ # ledger, so a mint of another domain's ledger reaches that ledger.
3198
3198
  policy "AutoMintAdditive" do
3199
3199
  on "Maintenance.MintRequested"
3200
3200
  where { rehearsal_verdict.value == "would_mint" }
3201
3201
  ask :mint, with: { run: :run, domain: :domain, era: :era, sha: :sha, branch: :branch,
3202
3202
  watermark: :watermark, rehearsal_verdict: :rehearsal_verdict,
3203
- refusal_total: :refusal_total, confirm: :confirm }
3203
+ refusal_total: :refusal_total, ledger_dir: :ledger_dir, confirm: :confirm }
3204
3204
  end
3205
3205
 
3206
3206
  # Holds a mint the auto-mint rule does not cover for the owner. Reads the refusal the entry point named.
@@ -3212,8 +3212,8 @@ Hecks.bluebook "QualityControl" do
3212
3212
 
3213
3213
  policy "LandOnMinted" do
3214
3214
  on "Maintenance.Minted"
3215
- where { branch.value != "" }
3216
- ask :land, with: { run: :run, era: :era, sha: :sha, branch: :branch }
3215
+ where { branch != "" }
3216
+ ask :land, with: { run: :run, era: :era, sha: :sha, branch: :branch, ledger_dir: :ledger_dir }
3217
3217
  end
3218
3218
 
3219
3219
  # The release PR chain. A version bump opens its PR; the conditions judged on `ReleaseChecked` either
@@ -164,10 +164,13 @@ Hecks.hecksagon "QualityControl" do
164
164
  refuses "MintApprovalRefused"
165
165
  end
166
166
 
167
+ # The landing runs in the repository that holds the minted ledger: the Hecks checkout when
168
+ # `ledger_dir` is empty, else the git checkout around that directory.
167
169
  asks "Land", to: Maintenance do
168
- attribute :era, MintedEra
169
- attribute :sha, MaintenanceSha
170
- attribute :branch, String
170
+ attribute :era, MintedEra
171
+ attribute :sha, MaintenanceSha
172
+ attribute :branch, String
173
+ attribute :ledger_dir, String
171
174
  answers "Landed"
172
175
  refuses "MintStranded"
173
176
  end
@@ -54,7 +54,8 @@ module Hecks
54
54
 
55
55
  # Whether the hand-typed Reconstruction method reads `field`. Answers nil, not false, for a
56
56
  # construct without one: nil means "not applicable", false means "applicable, and it fails",
57
- # and the touchpoint reports each differently. Named without `?` because it is not a predicate.
57
+ # and the touchpoint reports each differently. Named without `?` because it is not a
58
+ # predicate.
58
59
  def reconstruction_reads(name, field)
59
60
  method_name = RECONSTRUCTION_METHODS[name]
60
61
  return nil unless method_name
data/lib/hecks/refused.rb CHANGED
@@ -18,7 +18,8 @@ module Hecks
18
18
  # The last constant name of a class's path, such as `NotFound` of `Hecks::Runtime::NotFound`.
19
19
  LAST_NAME = /\w+\z/
20
20
 
21
- # The boundary between a lower-case or digit letter and a capital, where a snake-case word starts.
21
+ # The boundary between a lower-case or digit letter and a capital, where a snake-case word
22
+ # starts.
22
23
  CAMEL_BOUNDARY = /([a-z\d])([A-Z])/
23
24
 
24
25
  # @return [Symbol, nil] the attribute the refusal is about, when it is about one; the