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
@@ -5,7 +5,8 @@ require "json"
5
5
  module Hecks
6
6
  module ShapeLedger
7
7
  # Finds the translation edges that no longer earn their file; it deletes nothing. The graph is
8
- # a bluebook's edge files plus its recorded implicit moves, aimed at the current shape. A file is:
8
+ # a bluebook's edge files plus its recorded implicit moves, aimed at the current shape. A file
9
+ # is:
9
10
  #
10
11
  # - a **self-loop** when its `from` equals its `to`.
11
12
  # - a **fork** branch when two edges leave a label and both reach the current shape; the branch
@@ -19,11 +20,13 @@ module Hecks
19
20
  # One edge: the file that declares it (nil for a recorded implicit move) and its two labels.
20
21
  Edge = Struct.new(:path, :from, :to)
21
22
 
22
- # The list beside the ledger of stale edges already known, each a finding key. A listed edge is
23
+ # The list beside the ledger of stale edges already known, each a finding key. A listed edge
24
+ # is
23
25
  # reported as a warning and does not fail the check; any other stale edge fails it.
24
26
  KNOWN_FILE = "known_stale_edges.json"
25
27
 
26
- # One problem: its kind, the bluebook, the file to remove, a stable key naming the edge, and what
28
+ # One problem: its kind, the bluebook, the file to remove, a stable key naming the edge, and
29
+ # what
27
30
  # to do.
28
31
  Finding = Struct.new(:kind, :domain, :path, :key, :message, keyword_init: true)
29
32
 
@@ -42,7 +45,8 @@ module Hecks
42
45
  listed.map { |finding| "\nwarning, known stale edge (#{KNOWN_FILE}) #{lines([finding]).first}" }.join
43
46
  end
44
47
 
45
- # @return [Array<String>] the finding keys of the known stale edges, empty when there is no list
48
+ # @return [Array<String>] the finding keys of the known stale edges, empty when there is no
49
+ # list
46
50
  def known_stale(path) = File.exist?(path) ? JSON.parse(File.read(path)) : []
47
51
 
48
52
  # @param directory [String] the domain directory holding `translations/`
@@ -144,7 +148,8 @@ module Hecks
144
148
 
145
149
  def leads_to?(label, current, graph) = label == current || reached_from(label, graph).include?(current)
146
150
 
147
- # @return [Array<String>] every label a chain of edges leads to from one label, itself included
151
+ # @return [Array<String>] every label a chain of edges leads to from one label, itself
152
+ # included
148
153
  def reached_from(label, graph)
149
154
  seen = [label]
150
155
  seen.each { |at| graph.each { |edge| seen << edge.to if edge.from == at && !seen.include?(edge.to) } }
@@ -130,7 +130,7 @@ module Hecks
130
130
  "#{name} moved with no edge. A change that only adds needs no edge file: settle it in one step " \
131
131
  "with `hecks conformance_run.check_shape_ledger! --confirm --additive --wait`, which the boot and " \
132
132
  "the mint treat as an implicit empty edge from #{from} to #{now}. Data that is renamed or reshaped " \
133
- "needs rules: `hecks query era.scaffold_translation domain.value=<directory>` writes them, reading " \
133
+ "needs rules: `hecks era.scaffold_translation domain.value=<directory>` writes them, reading " \
134
134
  "the eras from the database the domain's own world names."
135
135
  end
136
136
 
@@ -13,7 +13,8 @@ module Hecks
13
13
  # CamelCase word takes an underscore.
14
14
  CAMEL_CASE_BOUNDARY = /([a-z\d])([A-Z])/
15
15
 
16
- # The last segment of a qualified constant name, such as `TypeMismatch` in `Runtime::TypeMismatch`.
16
+ # The last segment of a qualified constant name, such as `TypeMismatch` in
17
+ # `Runtime::TypeMismatch`.
17
18
  UNQUALIFIED_NAME = /[^:]+\z/
18
19
 
19
20
  # Kept under Hecks::CacheDir, not the domain directory (part of the domain's
@@ -1,13 +1,16 @@
1
1
  module Hecks
2
2
  module Storehouse
3
- # The ids a call used, named in its result: the record it addressed, and the references its arguments
3
+ # The ids a call used, named in its result: the record it addressed, and the references its
4
+ # arguments
4
5
  # named. Extended onto `Storehouse`.
5
6
  module Identity
6
- # The arguments that name an id a call used: a run key, an earlier run it refers to, and the checkout
7
+ # The arguments that name an id a call used: a run key, an earlier run it refers to, and the
8
+ # checkout
7
9
  # and branch it resolved to.
8
10
  IDENTITY_KEYS = %i[run from checkout branch].freeze
9
11
 
10
- # The ids a call used: the record it addressed, and the references its arguments named, each unwrapped
12
+ # The ids a call used: the record it addressed, and the references its arguments named, each
13
+ # unwrapped
11
14
  # to its value. Absent ones are left out.
12
15
  # :nodoc:
13
16
  def ids_of(request, outcome)
@@ -29,7 +29,8 @@ module Hecks
29
29
  SOURCE_TAGS = %w[process-manager operator hook sidequest-agent cascade daemon].freeze
30
30
 
31
31
  # Which surface a call came in through, so the audit line says what made it: a person at a shell
32
- # (`cli`), an agent over the MCP server (`mcp`), or the domain itself (`policy`, `saga`, `reaction`).
32
+ # (`cli`), an agent over the MCP server (`mcp`), or the domain itself (`policy`, `saga`,
33
+ # `reaction`).
33
34
  SURFACES = %w[cli mcp policy saga reaction].freeze
34
35
 
35
36
  # The root every domain path resolves under; HECKS_STOREHOUSE_ROOT widens it.
@@ -387,11 +387,12 @@ Hecks.bluebook "Tickets" do
387
387
  emits "WorkStarted"
388
388
  end
389
389
 
390
- # The claim's area overlapped another started step's, or it would start more steps than the
391
- # cap allows: the WorkAreas or WorkLimits port refuses it by name, and the step goes back to
392
- # the plan with the refusal as its reason.
390
+ # A started step that may not go on goes back to the plan with the refusal as its reason: its
391
+ # area overlapped another started step's, it would start more steps than the cap allows (the
392
+ # WorkAreas or WorkLimits port refuses it by name), or the architect's review of its plan said
393
+ # rethink and the coder session handed it back.
393
394
  command "Yield" do
394
- goal "Return a step whose area another started step holds, with the refusal"
395
+ goal "Return a started step to the plan, with the refusal that sent it back"
395
396
 
396
397
  reference_to Work
397
398
  attribute :reason, WorkText, sets: true
@@ -16,7 +16,8 @@ module Hecks
16
16
  # A workflow with no row is hand-written and left alone. With `--check` nothing is written: the
17
17
  # tool answers 1 and names each file that differs from its rows.
18
18
  module CiWorkflows
19
- # The directory of the bluebook that declares the rows, which attaches the CiPipeline bluebook.
19
+ # The directory of the bluebook that declares the rows, which attaches the CiPipeline
20
+ # bluebook.
20
21
  BLUEBOOK_DIRECTORY = File.expand_path("../ci_workflows", __dir__)
21
22
 
22
23
  # The directory `ports/` and `adapters/` live under, loaded before the rows are.
@@ -32,12 +32,23 @@ module Hecks
32
32
 
33
33
  bluebook = boot_bluebook(path)
34
34
  begin
35
- puts Projector.call(projection, bluebook: bluebook, options: aggregate ? { aggregate: aggregate } : {})
35
+ puts Projector.call(projection, bluebook: bluebook, options: options_for(projection, path, aggregate))
36
36
  rescue Runtime::NotFound => e
37
37
  abort e.message
38
38
  end
39
39
  end
40
40
 
41
+ # The projection's options: the aggregate it narrows to, and for `:narrate` the `.examples`
42
+ # files under the domain, whose goals it tells as use cases (ADR 0173).
43
+ #
44
+ # @api private
45
+ def options_for(projection, path, aggregate)
46
+ options = aggregate ? { aggregate: aggregate } : {}
47
+ return options unless projection == :narrate
48
+
49
+ options.merge(examples: Dir.glob(File.join(path, "**", "*.examples")))
50
+ end
51
+
41
52
  # The domain's own bluebook, not a framework member it attached: insertion
42
53
  # order, the same distinction `Runtime::Loader.dispatcher_for` draws.
43
54
  #
@@ -4,7 +4,8 @@ require "fileutils"
4
4
 
5
5
  module Hecks
6
6
  module Tools
7
- # The model check a domain repository runs in its CI, generated from Hecks' own definition of it:
7
+ # The model check a domain repository runs in its CI, generated from Hecks' own definition of
8
+ # it:
8
9
  # the client profile, over the domain's bluebooks, from the hecks the Gemfile pins.
9
10
  #
10
11
  # A downstream repository commits the file this writes (`.github/workflows/model-check.yml`) and
@@ -80,7 +81,8 @@ module Hecks
80
81
  YAML
81
82
  end
82
83
 
83
- # Writes the workflow under `root`, or with `check` compares it and reports whether it is current.
84
+ # Writes the workflow under `root`, or with `check` compares it and reports whether it is
85
+ # current.
84
86
  #
85
87
  # @param root [String] the checkout
86
88
  # @param domain_directory [String] see {workflow}
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../../tools"
4
+ require_relative "../shard_planner"
4
5
 
5
6
  module Hecks
6
7
  module Tools
@@ -44,23 +45,8 @@ module Hecks
44
45
  # @param costs [Hash{String => Float}] each domain directory's cost, perhaps lacking some
45
46
  # @return [Array<Array<String>>] the domains of each shard, each in the order `domains` has
46
47
  def partition(domains, shards, costs = {})
47
- price = prices(domains, costs)
48
- order = domains.each_index.sort_by { |index| [-price[index], domains[index]] }
49
- deal(order, price, shards).map { |indexes| indexes.sort.map { |index| domains[index] } }
50
- end
51
-
52
- # @param order [Array<Integer>] the domain positions, in the order to deal them
53
- # @param price [Array<Float>] each position's cost
54
- # @param shards [Integer] how many shards to deal into
55
- # @return [Array<Array<Integer>>] the positions each shard took, each to the lightest shard
56
- # so far, the lowest numbered when they tie
57
- def deal(order, price, shards)
58
- loads = Array.new(shards, 0.0)
59
- order.each_with_object(Array.new(shards) { [] }) do |index, taken|
60
- lightest = loads.each_index.min_by { |shard| [loads[shard], shard] }
61
- loads[lightest] += price[index]
62
- taken[lightest] << index
63
- end
48
+ dealt = ShardPlanner.partition(domains, shards, price_table(domains, costs))
49
+ dealt.map { |members| domains.select { |domain| members.include?(domain) } }
64
50
  end
65
51
 
66
52
  # @param domains [Array<String>] the domain directories
@@ -84,7 +70,7 @@ module Hecks
84
70
  split = partition(domains, shards, costs)
85
71
  raise NotAPartition, "the shards are not a partition of the domains" unless split.flatten.sort == domains.sort
86
72
 
87
- price = domains.zip(prices(domains, costs)).to_h
73
+ price = price_table(domains, costs)
88
74
  split.each_with_index.flat_map do |members, index|
89
75
  members.map { |domain| row("#{index + 1}/#{shards}", price[domain], domain.delete_prefix("#{root}/")) }
90
76
  end
@@ -112,7 +98,7 @@ module Hecks
112
98
  # @param costs [Hash{String => Float}] each domain directory's cost
113
99
  # @return [Array<String>] the same domains, dearest first, ties in path order
114
100
  def dearest_first(domains, costs)
115
- price = domains.zip(prices(domains, costs)).to_h
101
+ price = price_table(domains, costs)
116
102
  domains.sort_by { |domain| [-price[domain], domain] }
117
103
  end
118
104
 
@@ -137,18 +123,11 @@ module Hecks
137
123
  .to_h { |line| line.split(":", 2).then { |dir, seconds| [File.join(root, dir), Float(seconds)] } }
138
124
  end
139
125
 
140
- # @return [Array<Float>] each domain's cost, a missing one at the table's median
141
- def prices(domains, costs)
142
- fallback = median(costs.values) || FLAT_COST
143
- domains.map { |domain| costs.fetch(domain, fallback) }
144
- end
145
-
146
- def median(values)
147
- return nil if values.empty?
126
+ # @return [Hash{String => Float}] each domain's cost, a missing one at the table's median
127
+ def price_table(domains, costs) = ShardPlanner.prices(domains, costs, ->(_domain) { FLAT_COST })
148
128
 
149
- ranked = values.sort
150
- (ranked[(ranked.size - 1) / 2] + ranked[ranked.size / 2]) / 2.0
151
- end
129
+ # @return [Array<Float>] each domain's cost, in the order of `domains`
130
+ def prices(domains, costs) = price_table(domains, costs).values_at(*domains)
152
131
  end
153
132
  end
154
133
  end
@@ -40,7 +40,8 @@ module Hecks
40
40
  edges = booted.flat_map do |_, registry|
41
41
  Projections::ContextRelationships.call(registry: registry, owners: owners)
42
42
  end
43
- findings = Bluebook::ModelCheck.context_cycle_findings(edges.uniq) + context_map_findings(booted, owners)
43
+ findings = Bluebook::ModelCheck.context_cycle_findings(edges.uniq) +
44
+ classification_findings(booted, owners) + context_map_findings(booted, owners)
44
45
  return true if findings.empty?
45
46
 
46
47
  puts "── contexts"
@@ -56,11 +57,33 @@ module Hecks
56
57
  booted.flat_map do |_, registry|
57
58
  registry.context_maps.flat_map do |map|
58
59
  edges = Projections::ContextRelationships.call(registry: registry, owners: owners)
59
- Bluebook::ModelCheck.context_map_findings(map, edges)
60
+ Bluebook::ModelCheck.context_map_findings(map, edges) +
61
+ Bluebook::ModelCheck.core_conforms_findings(map, classifications_of(registry))
60
62
  end
61
63
  end
62
64
  end
63
65
 
66
+ # What each booted system's classifications say about its own wiring (ADR 0172). Each system
67
+ # is read alone: two systems may each declare a context of the same name.
68
+ #
69
+ # @api private
70
+ # @return [Array<Bluebook::ModelCheck::Finding>]
71
+ def classification_findings(booted, owners)
72
+ booted.flat_map do |_, registry|
73
+ edges = Projections::ContextRelationships.call(registry: registry, owners: owners)
74
+ Bluebook::ModelCheck.generic_depends_on_core_findings(edges, classifications_of(registry))
75
+ end
76
+ end
77
+
78
+ # Each context's classification (`core`, `supporting`, `generic`), as its bluebook declares it.
79
+ #
80
+ # @api private
81
+ # @return [Hash{String => String}] only the contexts that declare one
82
+ def classifications_of(registry)
83
+ classified = registry.bluebooks.values.select(&:classification)
84
+ classified.to_h { |declaration| [declaration.name.to_s, declaration.classification.to_s] }
85
+ end
86
+
64
87
  # @api private
65
88
  def emitted_events_of(booted)
66
89
  booted.flat_map do |_, registry|
@@ -121,7 +121,8 @@ module Hecks
121
121
  # Removes the directives from one file's lines.
122
122
  #
123
123
  # @param lines [Array<String>] the file's lines, without their newlines
124
- # @return [Array(Array<String>, String)] the cops the directives named, and the file's new text
124
+ # @return [Array(Array<String>, String)] the cops the directives named, and the file's new
125
+ # text
125
126
  def strip(lines)
126
127
  touched = []
127
128
  out = lines.filter_map { |line| keep_line(line, touched) }
@@ -190,7 +191,8 @@ module Hecks
190
191
 
191
192
  # @param cop [String] a cop name
192
193
  # @param paths [Array<String>] the `Exclude` entries
193
- # @return [String] the cop's header, its reason, and its `Exclude`, to replace an existing header
194
+ # @return [String] the cop's header, its reason, and its `Exclude`, to replace an existing
195
+ # header
194
196
  def merged_block(cop, paths) = ["#{cop}:", " # #{REASONS.fetch(cop)}", " Exclude:", *paths].join("\n")
195
197
 
196
198
  # @param cop [String] a cop name
@@ -32,7 +32,8 @@ module Hecks
32
32
  end
33
33
 
34
34
  # Puts back, in the scratch crate, every module the named domains do not own, as the tree
35
- # has it: a run of some domains rewrites the shared bluebooks and the grammar as its own last
35
+ # has it: a run of some domains rewrites the shared bluebooks and the grammar as its own
36
+ # last
36
37
  # writer would, which is not what the whole corpus's last writer left in the tree.
37
38
  #
38
39
  # @param scratch [String] the scratch crate
@@ -1,16 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "parallel_tests/test/runner"
3
+ require_relative "shard_planner"
4
4
 
5
5
  module Hecks
6
6
  module Tools
7
7
  # The command behind `hecks shard_specs`: prints the spec files assigned to one group
8
- # (1-indexed) of a runtime-balanced N-way split. `rspec_shard` in ci-rspec.yml passes the
9
- # committed runtime baseline so every leg agrees.
8
+ # (1-indexed) of an N-way split by the shared `ShardPlanner`. It is the split a leg of
9
+ # `rspec_shard` in ci-rspec.yml falls back on when the plan job's plan cannot be read; with no
10
+ # runtime log it balances by file size, which every leg computes alike.
10
11
  #
11
12
  # It does not use `parallel_rspec --only-group`, which forces one process per leg; this suite
12
- # is CPU-bound, so each leg would use one of four vCPUs. The group's list comes from
13
- # `tests_in_groups`, leaving `parallel_rspec` its own workers.
13
+ # is CPU-bound, so each leg would use one of four vCPUs. The group's list comes from the
14
+ # planner, leaving `parallel_rspec` its own workers.
14
15
  module RspecShardFiles
15
16
  USAGE = "usage: hecks shard_specs <group 1-indexed> <num_groups> [runtime_log_path]"
16
17
 
@@ -60,21 +61,17 @@ module Hecks
60
61
  "refusing to hand parallel_rspec nothing to run"
61
62
  end
62
63
 
63
- # Splits the files by recorded runtime, falling back to file size on a cold cache.
64
- #
65
- # `allowed_missing_percent` covers gaps inside a log, not a missing file (`Runner.runtimes`
66
- # raises `Errno::ENOENT`).
64
+ # Splits the files by the seconds in the runtime log when one is given and exists, and by file
65
+ # size otherwise.
67
66
  #
68
67
  # @param files [Array<String>] the spec files, relative to the checkout
69
68
  # @param num_groups [Integer] how many groups to split into
70
- # @param runtime_log [String, nil] the runtime log's path, used when the file exists
69
+ # @param runtime_log [String, nil] a log of `path:seconds` lines, used when the file exists
71
70
  # @return [Array<Array<String>>] the files of each group
72
71
  def groups_of(files, num_groups, runtime_log)
73
- options = { group_by: :runtime, allowed_missing_percent: 100 }
74
- options[:runtime_log] = runtime_log if runtime_log && File.exist?(runtime_log)
75
- ParallelTests::Test::Runner.tests_in_groups(files, num_groups, **options)
76
- rescue Errno::ENOENT
77
- ParallelTests::Test::Runner.tests_in_groups(files, num_groups, group_by: :filesize)
72
+ table = runtime_log && File.exist?(runtime_log) ? ShardPlanner.parse(File.read(runtime_log)) : {}
73
+ prices = ShardPlanner.prices(files, table, ->(file) { File.size(file) })
74
+ ShardPlanner.partition(files, num_groups, prices)
78
75
  end
79
76
  end
80
77
  end
@@ -10,7 +10,7 @@ module Hecks
10
10
  #
11
11
  # hecks generate_sequence <domain> [--seed N] [--steps N] [--adversarial fraction]
12
12
  #
13
- # `--adversarial` mutates the sequence as `hecks quality_control query sweep.run` does; 0 (off)
13
+ # `--adversarial` mutates the sequence as `hecks quality_control sweep.run` does; 0 (off)
14
14
  # by default.
15
15
  module SequenceScript
16
16
  USAGE = "usage: hecks generate_sequence <domain> [--seed N] [--steps N] [--adversarial FRACTION]"
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hecks
4
+ module Tools
5
+ # The longest-processing-time split every sharded CI family shares: spec files of the rspec and
6
+ # postgres io jobs, and the domains of a fuzz sweep. It is a pure function of the items, the
7
+ # shard count and the prices, so the same input gives the same partition.
8
+ #
9
+ # A price is a recorded run time when the table holds one for the item, the table's median when
10
+ # it holds others but not this one, and the item's size when there is no table at all.
11
+ module ShardPlanner
12
+ # One `key:seconds` line; seconds can arrive in exponent form (`9.69e-05`). Anything else,
13
+ # such as RSpec's "Run options" line, is not a timing.
14
+ LINE = /\A(?<key>\S+):(?<seconds>\d+(?:\.\d+)?(?:e-?\d+)?)\z/
15
+
16
+ module_function
17
+
18
+ # @param text [String] lines of `key:seconds`
19
+ # @return [Hash{String => Float}] the seconds of each key; a malformed line is skipped
20
+ def parse(text)
21
+ lines = text.to_s.lines.map(&:strip)
22
+ timed = lines.filter_map { |line| LINE.match(line) }
23
+ timed.to_h { |match| [match[:key], Float(match[:seconds])] }
24
+ end
25
+
26
+ # @param texts [Array<String>] several shards' timing logs
27
+ # @return [Hash{String => Float}] one table; a key in two logs keeps the later seconds
28
+ def merge(texts) = texts.reduce({}) { |table, text| table.merge(parse(text)) }
29
+
30
+ # @param items [Array<String>] what is to be split
31
+ # @param table [Hash{String => Float}] recorded seconds, perhaps empty or lacking some items
32
+ # @param sizes [#call] answers an item's size in bytes, used only when the table is empty
33
+ # @return [Hash{String => Float}] each item's price
34
+ def prices(items, table, sizes)
35
+ return items.to_h { |item| [item, sizes.call(item).to_f] } if table.empty?
36
+
37
+ fallback = median(table.values)
38
+ items.to_h { |item| [item, table.fetch(item, fallback)] }
39
+ end
40
+
41
+ # @param items [Array<String>] what is to be split, each unique
42
+ # @param groups [Integer] how many groups to deal into
43
+ # @param prices [Hash{String => Float}] each item's price
44
+ # @return [Array<Array<String>>] the items of each group, dearest first, each to the lightest
45
+ # group so far, the lowest numbered when they tie
46
+ def partition(items, groups, prices)
47
+ loads = Array.new(groups, 0.0)
48
+ dealt = Array.new(groups) { [] }
49
+ items.sort_by { |item| [-prices.fetch(item), item] }.each do |item|
50
+ lightest = loads.each_index.min_by { |group| [loads[group], group] }
51
+ loads[lightest] += prices.fetch(item)
52
+ dealt[lightest] << item
53
+ end
54
+ dealt
55
+ end
56
+
57
+ # @param values [Array<Float>] at least one
58
+ # @return [Float] the middle value
59
+ def median(values)
60
+ ranked = values.sort
61
+ (ranked[(ranked.size - 1) / 2] + ranked[ranked.size / 2]) / 2.0
62
+ end
63
+ end
64
+ end
65
+ end
@@ -10,7 +10,8 @@ module Hecks
10
10
  # standard output, so a pipe still receives only the result.
11
11
  class StageProgress
12
12
  class << self
13
- # Whether the lines are left out, as `-q` asks: set by the launcher for the length of a line.
13
+ # Whether the lines are left out, as `-q` asks: set by the launcher for the length of a
14
+ # line.
14
15
  # @return [Boolean]
15
16
  attr_accessor :quiet
16
17
  end
@@ -8,7 +8,8 @@ require_relative "stress_concurrency_specs/reporting"
8
8
 
9
9
  module Hecks
10
10
  module Tools
11
- # The command behind `hecks test_suite_run.stress_concurrency!`: reruns the thread-safety specs under many
11
+ # The command behind `hecks test_suite_run.stress_concurrency!`: reruns the thread-safety specs
12
+ # under many
12
13
  # seeds, several OS processes at a time. A clean run shows only "not in this many tries", never
13
14
  # "impossible".
14
15
  #
data/lib/hecks/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Hecks
2
2
  # Read directly by the gemspec, so this file must never require the
3
3
  # rest of the gem (its dependencies aren't resolved yet at that point).
4
- VERSION = "7.0.0".freeze # rust/host/HECKS_RELEASE names the same release
4
+ VERSION = "7.1.0".freeze # rust/host/HECKS_RELEASE names the same release
5
5
  end
@@ -115,7 +115,7 @@ module Hecks
115
115
  {"name"=>"textarea", "pattern"=>"\\b(text|body|note|notes|description|message|comment)\\b", "resolves_to"=>"kind"}.freeze
116
116
  ].freeze,
117
117
  "GateCheck" => [
118
- {"name"=>"rspec", "id"=>"rspec", "title"=>"rspec (parallel)", "runs_in"=>"ci", "argv"=>"", "script"=>"# Workers are grouped by this machine's own last timings, kept under the git directory\n# (shared by every worktree, never committed). CI's committed baseline leaves out the io\n# specs this run includes, so it only seeds the first run; each run then rewrites the log.\nlog=\"$(git rev-parse --git-common-dir)/hecks_rspec_runtime.log\"\n[ -f \"$log\" ] || cp .github/rspec_runtime_baseline.log \"$log\"\nbundle exec parallel_rspec spec -n {workers} --group-by runtime --runtime-log \"$log\" --allowed-missing 100 \\\n -o \"--format progress --format ParallelTests::RSpec::RuntimeLogger --out $log\"\n", "blocked"=>"the suite is red."}.freeze,
118
+ {"name"=>"rspec", "id"=>"rspec", "title"=>"rspec (parallel)", "runs_in"=>"ci", "argv"=>"", "script"=>"# Workers are grouped by this machine's own last timings, kept under the git directory\n# (shared by every worktree, never committed); the first run, with none, groups by file size and\n# writes the log.\nlog=\"$(git rev-parse --git-common-dir)/hecks_rspec_runtime.log\"\nif [ -f \"$log\" ]; then grouping=(--group-by runtime --runtime-log \"$log\" --allowed-missing 100); else grouping=(--group-by filesize); fi\nbundle exec parallel_rspec spec -n {workers} \"${grouping[@]}\" \\\n -o \"--format progress --format ParallelTests::RSpec::RuntimeLogger --out $log\"\n", "blocked"=>"the suite is red."}.freeze,
119
119
  {"name"=>"fuzzing", "id"=>"fuzzing", "title"=>"spec/fuzzing, which the parallel run above excludes", "argv"=>"bundle exec rspec spec/fuzzing --tag fuzzing", "script"=>"", "blocked"=>"spec/fuzzing is red."}.freeze,
120
120
  {"name"=>"query_agreement", "id"=>"query_agreement", "title"=>"adapter agreement, one declared query and every engine giving the same answer", "argv"=>"bundle exec rspec spec/adapters/query_agreement_spec.rb --tag io", "script"=>"", "blocked"=>"the engines disagree about a declared query; one of them is wrong, and a self-consistent adapter spec cannot tell which."}.freeze,
121
121
  {"name"=>"engine_agreement", "id"=>"engine_agreement", "title"=>"engine agreement, every declared comparator on both engines", "argv"=>"bundle exec exe/hecks conformance_run.check_engine_agreement! --wait", "script"=>"", "blocked"=>"a comparator is missing a shared case or a cross-engine spec."}.freeze,
@@ -149,8 +149,8 @@ module Hecks
149
149
  {"name"=>"ci_gates", "id"=>"ci_gates", "title"=>"ci gates, the CiGate rows must match the marked regions of the workflows", "argv"=>"bundle exec exe/hecks regeneration_run.project_ci_gates! --wait", "script"=>"", "blocked"=>"a CiGate row changed with no projection, or a region was edited by hand; run `hecks regeneration_run.project_ci_gates! --confirm`."}.freeze,
150
150
  {"name"=>"ci_pipeline", "id"=>"ci_pipeline", "title"=>"ci pipeline, the workflow rows must match the files they project", "argv"=>"bundle exec exe/hecks regeneration_run.project_ci_pipeline! --wait", "script"=>"", "blocked"=>"a workflow row changed with no projection, or a projected workflow was edited by hand; run `hecks regeneration_run.project_ci_pipeline! --confirm`."}.freeze,
151
151
  {"name"=>"tools_doc", "id"=>"tools_doc", "title"=>"tools doc, docs/tools.md must match the RetiredScript rows", "argv"=>"bundle exec exe/hecks regeneration_run.project_tools_doc! --wait", "script"=>"", "blocked"=>"a command's arguments changed with no regenerated doc; run `hecks regeneration_run.project_tools_doc! --confirm`."}.freeze,
152
- {"name"=>"rust_coverage", "id"=>"rust_coverage", "title"=>"rust coverage, every generated module", "argv"=>"bundle exec exe/hecks query corpus_run.corpus_rust_coverage --wait", "script"=>"", "blocked"=>"a construct a domain declares has no generated Rust, or an entry on RUST_COVERAGE_PENDING is now fixed and must be deleted."}.freeze,
153
- {"name"=>"rust_coverage_codegen", "id"=>"rust_coverage_codegen", "title"=>"rust coverage against hecks-codegen's own manifest, pizzas governance identity banking meta", "argv"=>"", "script"=>"for module in pizzas governance identity banking meta; do\n bundle exec exe/hecks query build.rust_coverage module_name=$module codegen=rust --wait\ndone\n", "blocked"=>"hecks-codegen generated nothing for a construct a domain declares."}.freeze,
152
+ {"name"=>"rust_coverage", "id"=>"rust_coverage", "title"=>"rust coverage, every generated module", "argv"=>"bundle exec exe/hecks corpus_run.corpus_rust_coverage --wait", "script"=>"", "blocked"=>"a construct a domain declares has no generated Rust, or an entry on RUST_COVERAGE_PENDING is now fixed and must be deleted."}.freeze,
153
+ {"name"=>"rust_coverage_codegen", "id"=>"rust_coverage_codegen", "title"=>"rust coverage against hecks-codegen's own manifest, pizzas governance identity banking meta", "argv"=>"", "script"=>"for module in pizzas governance identity banking meta; do\n bundle exec exe/hecks build.rust_coverage module_name=$module codegen=rust --wait\ndone\n", "blocked"=>"hecks-codegen generated nothing for a construct a domain declares."}.freeze,
154
154
  {"name"=>"kernel_coverage", "id"=>"kernel_coverage", "title"=>"rust kernel coverage, every capability variant has a kernel file", "argv"=>"bundle exec exe/hecks kernel_run.measure_kernel_coverage! --wait", "script"=>"", "blocked"=>"a capability's enum variant has no rust/src/kernel file behind it."}.freeze,
155
155
  {"name"=>"rust_vocabulary_drift", "id"=>"rust_vocabulary_drift", "title"=>"rust vocabulary, the kernel vocab tables regenerated must match the committed ones", "argv"=>"", "script"=>"bundle exec exe/hecks language_run.project_rust_vocabulary! --confirm --wait\ngit diff --exit-code -- rust/src/kernel/vocab/\nuntracked=\"$(git status --porcelain --untracked-files=all -- rust/src/kernel/vocab/)\"\nif [ -n \"$untracked\" ]; then\n echo \"rust/src/kernel/vocab has uncommitted generated files:\" >&2\n echo \"$untracked\" >&2\n exit 1\nfi\n", "blocked"=>"the generated kernel vocab tables are stale or untracked; the check rewrote them, so commit the result."}.freeze
156
156
  ].freeze,
@@ -209,7 +209,7 @@ module Hecks
209
209
  {"name"=>"cadence_seconds", "type"=>"Integer", "meaning"=>"Minimum gap in seconds between one tick's report and the next dispatch; 0 is immediately. Read by the `/loop hecks_qa` caller, not by any script."}.freeze,
210
210
  {"name"=>"pr_cap_per_day", "type"=>"Integer", "meaning"=>"Most PRs (Patch plus Improvement) `hecks quality_control Patch.open!` opens per calendar day; 0 is uncapped."}.freeze,
211
211
  {"name"=>"widening_tiers", "type"=>"Array", "meaning"=>"Fuzz depth by `Target.clean_streak`, in three discrete tiers, read by `SweepDepth.for_streak`. The last row (`upto: Float::INFINITY`) is the ceiling."}.freeze,
212
- {"name"=>"sweep_max_parallel", "type"=>"Integer", "meaning"=>"How many `hecks quality_control query sweep.run <target>` children `--all` keeps running at once."}.freeze,
212
+ {"name"=>"sweep_max_parallel", "type"=>"Integer", "meaning"=>"How many `hecks quality_control sweep.run <target>` children `--all` keeps running at once."}.freeze,
213
213
  {"name"=>"liveness_fallback_seconds", "type"=>"Integer", "meaning"=>"How long the `/loop hecks_qa` caller waits for a tick's report before assuming it stalled."}.freeze,
214
214
  {"name"=>"draft_only", "type"=>"Boolean", "meaning"=>"Whether `hecks quality_control Patch.open!` opens its PR as a draft; false opens it ready for review."}.freeze,
215
215
  {"name"=>"auto_merge", "type"=>"Boolean", "meaning"=>"Whether `hecks quality_control Patch.open!` queues the PR for auto-merge once it is open."}.freeze,
@@ -218,15 +218,15 @@ module Hecks
218
218
  {"name"=>"corpus_splice_probability", "type"=>"Numeric", "meaning"=>"Fraction (0.0..1.0) of seeds that start from a prefix of an earlier seed's new coverage. Off by default: measured, it trades breadth for depth (banking reached 1.41x the deep-state tuples, chess lost 0.12x of all tuples), while favoring rare verbs alone never lost."}.freeze,
219
219
  {"name"=>"favor_rare_verbs", "type"=>"Integer", "meaning"=>"How many of the least-hit verbs the next seed weights up; 0 turns the lever off."}.freeze,
220
220
  {"name"=>"runtime_coverage_feedback", "type"=>"Boolean", "meaning"=>"Whether guided generation also admits a seed to the corpus for reaching a new line or branch of the Ruby runtime (`Hecks::Fuzzing::RuntimeCoverage`), not only a new (verb, kind, state, mutation, outcome) tuple. Off by default: tracing every line slows each seed."}.freeze,
221
- {"name"=>"self_consistency_checks", "type"=>"Boolean", "meaning"=>"Whether `hecks quality_control query sweep.run` also runs the self-consistency pass (cold rehydration, replay idempotency, value-object JSON round trip) on the same generated sequence."}.freeze,
222
- {"name"=>"shrink_budget", "type"=>"Integer", "meaning"=>"Candidate replays `hecks quality_control query sweep.run` may spend shrinking one surprised check; 0 turns shrinking off. `concurrency` is never shrunk: a race does not reproduce on demand."}.freeze,
221
+ {"name"=>"self_consistency_checks", "type"=>"Boolean", "meaning"=>"Whether `hecks quality_control sweep.run` also runs the self-consistency pass (cold rehydration, replay idempotency, value-object JSON round trip) on the same generated sequence."}.freeze,
222
+ {"name"=>"shrink_budget", "type"=>"Integer", "meaning"=>"Candidate replays `hecks quality_control sweep.run` may spend shrinking one surprised check; 0 turns shrinking off. `concurrency` is never shrunk: a race does not reproduce on demand."}.freeze,
223
223
  {"name"=>"yield_weight_seconds", "type"=>"Integer", "meaning"=>"Seconds of staleness that one point of yield score is worth in `RotationPriority.pick`. Zero would make yield invisible, so pick a number that lets it matter."}.freeze,
224
224
  {"name"=>"yield_decay_percent", "type"=>"Integer", "meaning"=>"Percent of the prior yield score kept at each release, so recent findings dominate. Applied by the caller: the mutation language cannot express \"decay, then add\" in `Release`."}.freeze,
225
225
  {"name"=>"rotation_stale_floor_seconds", "type"=>"Integer", "meaning"=>"Once a waiting target is this stale, `RotationPriority.pick` takes it whatever its yield score, so a quiet target cannot starve."}.freeze,
226
- {"name"=>"persistence_parity_seed_cap", "type"=>"Integer", "meaning"=>"Ceiling on `--seeds` for `--persistence-parity`, which pays for real Postgres per dispatch. `hecks quality_control query sweep.run` clamps down, never up, and prints a note."}.freeze,
226
+ {"name"=>"persistence_parity_seed_cap", "type"=>"Integer", "meaning"=>"Ceiling on `--seeds` for `--persistence-parity`, which pays for real Postgres per dispatch. `hecks quality_control sweep.run` clamps down, never up, and prints a note."}.freeze,
227
227
  {"name"=>"concurrency_seed_cap", "type"=>"Integer", "meaning"=>"Ceiling on `--seeds` for the concurrency mode, which needs two disposable schemas per seed."}.freeze,
228
228
  {"name"=>"adapter_parity_pairs", "type"=>"Hash", "meaning"=>"The two adapters each adapter-parity mode compares, by mode name; a new pairing is a new entry. `adapter_parity_sqlite` needs no `database:`/`schema:` because sqlite has no disposable database."}.freeze,
229
- {"name"=>"modes", "type"=>"Hash", "meaning"=>"Which comparison modes `hecks quality_control query sweep.run` may run: the enabled ones intersected with what `TargetCapabilities.infer(path)` finds in the target. `--modes a,b,c` overrides for one run. `wasm_front` and `adapter_parity_postgres` are off because nothing runs them; the others are built and off because their I/O cost across the rotation is unmeasured. `hecks quality_control query sweep.run` refuses to start when a mode outside `TargetCapabilities::RUNNABLE_MODES` is enabled."}.freeze,
229
+ {"name"=>"modes", "type"=>"Hash", "meaning"=>"Which comparison modes `hecks quality_control sweep.run` may run: the enabled ones intersected with what `TargetCapabilities.infer(path)` finds in the target. `--modes a,b,c` overrides for one run. `wasm_front` and `adapter_parity_postgres` are off because nothing runs them; the others are built and off because their I/O cost across the rotation is unmeasured. `hecks quality_control sweep.run` refuses to start when a mode outside `TargetCapabilities::RUNNABLE_MODES` is enabled."}.freeze,
230
230
  {"name"=>"role_draw_probability", "type"=>"Numeric", "meaning"=>"How often a generated role-gated command step carries a drawn caller (`role:`/`actor_id:`); 0 turns the draw off."}.freeze,
231
231
  {"name"=>"dry_run_fraction", "type"=>"Numeric", "meaning"=>"Fraction of generated command steps offered as a dry run instead of a real dispatch; 0 is off. Kept low: a dry run creates no state for later steps to act on."}.freeze,
232
232
  {"name"=>"generated_domains_per_tick", "type"=>"Integer", "meaning"=>"How many generated domains one `hecks quality_control sweep.tick` checks after the rotation sweep; 0 skips the step."}.freeze,
@@ -589,7 +589,6 @@ module Hecks
589
589
  {"script"=>"rspec_shard_files", "section"=>"Codebase", "aggregate"=>"TestSuiteRun", "verb"=>"shard_specs"}.freeze,
590
590
  {"script"=>"rspec_io_parallel_files", "section"=>"Codebase", "aggregate"=>"TestSuiteRun", "verb"=>"list_io_parallel_specs"}.freeze,
591
591
  {"script"=>"rspec_io_parallel_files", "section"=>"Codebase", "aggregate"=>"TestSuiteRun", "verb"=>"write_io_parallel_spec_list"}.freeze,
592
- {"script"=>"refresh_rspec_runtime_baseline", "section"=>"Codebase", "aggregate"=>"TestSuiteRun", "verb"=>"refresh_runtime_baseline"}.freeze,
593
592
  {"script"=>"spec_example", "section"=>"Codebase", "aggregate"=>"TestSuiteRun", "verb"=>"run_spec_example"}.freeze,
594
593
  {"script"=>"stress_concurrency_specs", "section"=>"Codebase", "aggregate"=>"TestSuiteRun", "verb"=>"stress_concurrency"}.freeze,
595
594
  {"script"=>"regenerate_persistence_legacy_fixtures", "section"=>"Codebase", "aggregate"=>"TestSuiteRun", "verb"=>"regenerate_legacy_fixtures"}.freeze,
data/lib/hecks.rb CHANGED
@@ -98,9 +98,12 @@ module Hecks
98
98
  # @param shared [String, nil] a shared-root override; see `Runtime::Loader.boot_files`
99
99
  # @param install_driving [Boolean] install the `Widget::Item.Add`-style facade
100
100
  # @param environment [String, nil] environment name passed to the selected-file loader
101
+ # @param adapters [Array<String>, nil] exactly the `.adapter` files to load, leaving the
102
+ # project's own unloaded; nil keeps the loader's own choice
101
103
  # @return [Runtime::Dispatcher, Adapters::Lambda::RemoteDispatcher] the domain's dispatcher
102
- def boot_files(paths, shared: nil, install_driving: true, environment: Assembly::FROM_ENV)
103
- Assembly.boot_files(paths, shared: shared, install_driving: install_driving, environment: environment)
104
+ def boot_files(paths, shared: nil, install_driving: true, environment: Assembly::FROM_ENV, adapters: nil)
105
+ Assembly.boot_files(paths, shared: shared, install_driving: install_driving, environment: environment,
106
+ adapters: adapters)
104
107
  end
105
108
 
106
109
  # Installs the Ruby driving adapter (`Widget::Item.Add(...)`) over a dispatcher built by hand.
@@ -1 +1 @@
1
- 7.0.0
1
+ 7.1.0
@@ -1030,7 +1030,8 @@ fn ruby_value_json(value: &ruby_value::Value) -> JsonValue {
1030
1030
  }
1031
1031
 
1032
1032
  /// One declared adapter, keys in the order the Ruby side prints them; an adapter that provides
1033
- /// capabilities adds `provides`, and a plugin adds `answers` and `runs` after them.
1033
+ /// capabilities adds `provides`, one that stays inside adds `stays_inside`, and a plugin adds
1034
+ /// `answers` and `runs` after them.
1034
1035
  pub fn adapter_json(adapter: &ir::Adapter) -> JsonValue {
1035
1036
  let JsonValue::Object(mut keys) = declared_adapter_json(adapter) else {
1036
1037
  unreachable!("an adapter is a JSON object")
@@ -1041,6 +1042,9 @@ pub fn adapter_json(adapter: &ir::Adapter) -> JsonValue {
1041
1042
  JsonValue::strings(&adapter.provides),
1042
1043
  ));
1043
1044
  }
1045
+ if adapter.stays_inside {
1046
+ keys.push(("stays_inside".to_string(), JsonValue::Bool(true)));
1047
+ }
1044
1048
  if let Some(runs) = &adapter.runs {
1045
1049
  keys.push(("answers".to_string(), JsonValue::strings(&adapter.answers)));
1046
1050
  keys.push(("runs".to_string(), JsonValue::str(runs.clone())));
@@ -486,4 +486,6 @@ pub struct Adapter {
486
486
  pub runs: Option<String>,
487
487
  // The capabilities the adapter provides beyond its port's contract, in declaration order.
488
488
  pub provides: Vec<String>,
489
+ // `stays_inside`: the adapter reaches nothing outside the process and the checkout (ADR 0171).
490
+ pub stays_inside: bool,
489
491
  }