hecks 3.0.4 → 3.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 (159) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +25 -7
  3. data/lib/hecks/adapters/driven/lambda/client.rb +1 -1
  4. data/lib/hecks/adapters/driven/lambda.rb +1 -1
  5. data/lib/hecks/adapters/driven/tenant_provisioner.rb +2 -2
  6. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +59 -32
  7. data/lib/hecks/bluebook/hexagon.rb +45 -25
  8. data/lib/hecks/bluebook/model_check.rb +7 -7
  9. data/lib/hecks/chapters.rb +31 -3
  10. data/lib/hecks/cli/check_era.rb +1 -1
  11. data/lib/hecks/cli/console.rb +24 -8
  12. data/lib/hecks/cli/deploy_template_diff.rb +1 -1
  13. data/lib/hecks/cli/doc_coverage.rb +1 -1
  14. data/lib/hecks/cli/domain_stub.rb +128 -0
  15. data/lib/hecks/cli/interview_draft.rb +246 -0
  16. data/lib/hecks/cli/model_check.rb +1 -1
  17. data/lib/hecks/cli/project_cli.rb +69 -18
  18. data/lib/hecks/cli/reference.rb +2 -1
  19. data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +1 -1
  20. data/lib/hecks/cli/seed_semantics_corpus.rb +38 -9
  21. data/lib/hecks/cli/spec_example.rb +1 -1
  22. data/lib/hecks/cli/stress_concurrency_specs.rb +14 -5
  23. data/lib/hecks/corpus.rb +11 -8
  24. data/lib/hecks/deploy/bluebook/deploy.bluebook +85 -12
  25. data/lib/hecks/deploy/oidc.json +5 -0
  26. data/lib/hecks/doc/reference.rb +4 -3
  27. data/lib/hecks/doors/cli_door.rb +13 -13
  28. data/lib/hecks/doors/cli_runner.rb +52 -32
  29. data/lib/hecks/doors/launcher_options.rb +23 -5
  30. data/lib/hecks/doors/mcp_door.rb +40 -18
  31. data/lib/hecks/doors/mcp_door_scope.rb +244 -24
  32. data/lib/hecks/doors.rb +1 -1
  33. data/lib/hecks/embryonaut_bluebook.rb +2 -2
  34. data/lib/hecks/forms/banking_presentation.rb +1 -1
  35. data/lib/hecks/framework/bluebook/compliance.bluebook +1 -1
  36. data/lib/hecks/framework/bluebook/console_settings.bluebook +1 -1
  37. data/lib/hecks/framework/bluebook/framework.hecksagon +4 -4
  38. data/lib/hecks/framework.rb +1 -1
  39. data/lib/hecks/fuzzing/combination_miner.rb +1 -1
  40. data/lib/hecks/fuzzing/concurrent_dispatch.rb +1 -1
  41. data/lib/hecks/fuzzing/differential.rb +4 -2
  42. data/lib/hecks/fuzzing/era_boundary.rb +1 -1
  43. data/lib/hecks/fuzzing/form_census.rb +2 -2
  44. data/lib/hecks/fuzzing/generated_domain_check.rb +1 -1
  45. data/lib/hecks/fuzzing/isolated_boot.rb +10 -3
  46. data/lib/hecks/fuzzing/persistence_parity.rb +2 -2
  47. data/lib/hecks/fuzzing/target_capabilities.rb +3 -3
  48. data/lib/hecks/gate/stages.yml +8 -4
  49. data/lib/hecks/grammar/grammar.hecksagon +2 -2
  50. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +4 -4
  51. data/lib/hecks/hecks/adapters/host_http.rb +4 -3
  52. data/lib/hecks/hecks/adapters/local_files.rb +59 -0
  53. data/lib/hecks/hecks/adapters/site_toolchain.adapter +7 -0
  54. data/lib/hecks/hecks/adapters/site_toolchain.rb +54 -0
  55. data/lib/hecks/hecks/codebase.bluebook +4 -3
  56. data/lib/hecks/hecks/context_map.hecksagon +2 -2
  57. data/lib/hecks/hecks/custodian.bluebook +45 -3
  58. data/lib/hecks/hecks/hecks.hecksagon +10 -4
  59. data/lib/hecks/hecks/hecks.world +7 -4
  60. data/lib/hecks/hecks/site.behaviors +17 -0
  61. data/lib/hecks/hecks/site.world +10 -0
  62. data/lib/hecks/language/bluebook/bluebook.hecksagon +1 -1
  63. data/lib/hecks/language/hecksagon/hecksagon.bluebook +24 -47
  64. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +4 -4
  65. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +2 -1
  66. data/lib/hecks/projections/deploy/box/settings.rb +248 -0
  67. data/lib/hecks/projections/deploy/box/templates/box.yaml.tmpl +267 -0
  68. data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +70 -0
  69. data/lib/hecks/projections/deploy/box/templates/fetch-secrets.sh +21 -0
  70. data/lib/hecks/projections/deploy/box/templates/rds.yaml.tmpl +165 -0
  71. data/lib/hecks/projections/deploy/box/templates/render-compose.sh.tmpl +43 -0
  72. data/lib/hecks/projections/deploy/box.rb +387 -0
  73. data/lib/hecks/projections/deploy/fargate/cdn.rb +13 -1
  74. data/lib/hecks/projections/deploy/fargate.rb +1 -1
  75. data/lib/hecks/projections/deploy/lambda.rb +11 -10
  76. data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +1 -1
  77. data/lib/hecks/projections/diagrams.rb +2 -2
  78. data/lib/hecks/projections/site/edge/behaviours.rb +107 -0
  79. data/lib/hecks/projections/site/edge/checks.rb +137 -0
  80. data/lib/hecks/projections/site/edge/pattern.rb +50 -0
  81. data/lib/hecks/projections/site/edge/rules.rb +128 -0
  82. data/lib/hecks/projections/site/edge.rb +136 -0
  83. data/lib/hecks/projections/site/regions.rb +44 -0
  84. data/lib/hecks/projections/site/routes_ts.rb +284 -0
  85. data/lib/hecks/projections/site/site_cdn.rb +78 -0
  86. data/lib/hecks/projections/site/table/checks.rb +124 -0
  87. data/lib/hecks/projections/site/table/row_builder.rb +155 -0
  88. data/lib/hecks/projections/site/table.rb +98 -0
  89. data/lib/hecks/projections.rb +2 -0
  90. data/lib/hecks/projector/cli_projector.rb +172 -95
  91. data/lib/hecks/quality_control/adapters/agent.adapter +1 -1
  92. data/lib/hecks/quality_control/adapters/agent.rb +2 -2
  93. data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +3 -3
  94. data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +8 -8
  95. data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +3 -3
  96. data/lib/hecks/quality_control/cli/qa_generated_domains.rb +4 -4
  97. data/lib/hecks/quality_control/cli/qa_log_bug.rb +2 -2
  98. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +6 -5
  99. data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +2 -2
  100. data/lib/hecks/quality_control/cli/qa_postgres_role.rb +5 -5
  101. data/lib/hecks/quality_control/cli/qa_pr_check.rb +3 -3
  102. data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +3 -3
  103. data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +2 -2
  104. data/lib/hecks/quality_control/cli/qa_sweep.rb +10 -9
  105. data/lib/hecks/quality_control/cli/qa_tick.rb +3 -3
  106. data/lib/hecks/quality_control/quality_control.bluebook +18 -18
  107. data/lib/hecks/quality_control/quality_control.hecksagon +1 -1
  108. data/lib/hecks/runtime/command_interpreter.rb +2 -0
  109. data/lib/hecks/runtime/interpreting.rb +21 -0
  110. data/lib/hecks/runtime/invocation.rb +5 -3
  111. data/lib/hecks/runtime/registry/verification.rb +15 -29
  112. data/lib/hecks/runtime/registry.rb +14 -20
  113. data/lib/hecks/rust_build/append_optionals.rb +79 -0
  114. data/lib/hecks/rust_build/conformance_fuzz.rb +2 -1
  115. data/lib/hecks/rust_build/domain_name.rb +26 -0
  116. data/lib/hecks/rust_build/kernel_input.rb +58 -0
  117. data/lib/hecks/rust_build/project_rust/codegen_run.rb +143 -0
  118. data/lib/hecks/rust_build/project_rust/root_mod.rb +1 -1
  119. data/lib/hecks/rust_build/project_rust.rb +57 -24
  120. data/lib/hecks/rust_build/rust_literal.rb +33 -0
  121. data/lib/hecks/rust_build/write_if_changed.rb +86 -0
  122. data/lib/hecks/site/bluebook/site.bluebook +205 -0
  123. data/lib/hecks/site/bluebook/site.hecksagon +5 -0
  124. data/lib/hecks/site/bluebook/site.ports.hecksagon +15 -0
  125. data/lib/hecks/sme/bluebook/sme.bluebook +336 -0
  126. data/lib/hecks/sme/bluebook/sme.hecksagon +5 -0
  127. data/lib/hecks/storehouse.rb +37 -7
  128. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +1 -1
  129. data/lib/hecks/three_zero/forms.yml +88 -88
  130. data/lib/hecks/tools/ci_gates.rb +1 -1
  131. data/lib/hecks/tools/deploy_recipe.rb +14 -6
  132. data/lib/hecks/tools/oidc_manifests.rb +2 -1
  133. data/lib/hecks/tools/sequence_script.rb +2 -2
  134. data/lib/hecks/tools/site_routes.rb +152 -0
  135. data/lib/hecks/tools/tenant_provisioning.rb +1 -1
  136. data/lib/hecks/tools.rb +1 -0
  137. data/lib/hecks/version.rb +1 -1
  138. data/rust/build/src/cargo_sync.rs +1 -1
  139. data/rust/build/src/pipeline.rs +23 -16
  140. data/rust/build/src/resolve.rs +39 -6
  141. data/rust/host/HECKS_RELEASE +1 -1
  142. data/rust/host/src/api.rs +1 -1
  143. data/rust/host/src/dispatch.rs +87 -7
  144. data/rust/host/src/needs.rs +111 -0
  145. data/rust/host/src/web/newsletter/template.rs +69 -0
  146. data/rust/host/src/web/newsletter/tests.rs +43 -0
  147. data/rust/host/src/web/newsletter.rs +6 -1
  148. data/rust/host/src/web/newsletter_send.rs +1 -1
  149. data/rust/parser/src/keywords.rs +7 -6
  150. data/rust/parser/src/main.rs +4 -3
  151. data/rust/parser/src/parse/chapter.rs +1 -1
  152. data/rust/parser/src/parse/hecksagon.rs +25 -10
  153. data/rust/project/mutations.rb +4 -30
  154. data/rust/project/naming.rb +8 -28
  155. data/rust/project/write_if_changed.rb +4 -80
  156. data/rust/project_rust_pipeline.rb +16 -12
  157. data/rust/src/kernel/cli.rs +2 -0
  158. data/rust/src/kernel/needs.rs +74 -1
  159. metadata +39 -2
@@ -7,8 +7,8 @@ require_relative "../../adapters/driven/heki"
7
7
 
8
8
  module Hecks
9
9
  module QualityControlCli
10
- # The command behind `hecks quality_control migrate_ledger_from_heki`: copies each aggregate's
11
- # current state from a Heki data directory into the repository that the domain's
10
+ # The command behind `hecks quality_control sweep.migrate_ledger_from_heki`: copies each
11
+ # aggregate's current state from a Heki data directory into the repository that the domain's
12
12
  # `.hecksagon`/`.world` files bind today (for example `PostgresEra`). It is domain-agnostic.
13
13
  #
14
14
  # migrate_ledger_from_heki <domain_dir> <heki_data_dir> [aggregate_name ...] # dry run
@@ -4,12 +4,12 @@ require "pg"
4
4
 
5
5
  module Hecks
6
6
  module QualityControlCli
7
- # The command behind `hecks quality_control create_ledger_role`: creates the ordinary Postgres
8
- # role the QA ledger connects as and makes it own the database. It is idempotent, and refuses a
9
- # superuser or `BYPASSRLS` role, which the era write-fence cannot bind.
7
+ # The command behind `hecks quality_control sweep.create_ledger_role`: creates the ordinary
8
+ # Postgres role the QA ledger connects as and makes it own the database. It is idempotent, and
9
+ # refuses a superuser or `BYPASSRLS` role, which the era write-fence cannot bind.
10
10
  #
11
- # hecks quality_control create_ledger_role <database> [--role <role>] # role defaults to
12
- # hecks_qa
11
+ # hecks quality_control sweep.create_ledger_role <database> [--role <role>] # role defaults
12
+ # to hecks_qa
13
13
  class QaPostgresRole
14
14
  USAGE = "usage: hecks quality_control create_ledger_role <database> [--role <role>]"
15
15
 
@@ -7,9 +7,9 @@ require_relative "../adapters/git_pr"
7
7
 
8
8
  module Hecks
9
9
  module QualityControlCli
10
- # The command behind `hecks quality_control check_pull_requests`: checks CI for every PR the
11
- # QualityControl ledger tracks as open (`Patch` and `Improvement`). It starts a `Clearance` per
12
- # unsettled head commit and lets the CI port's policies record the verdict.
10
+ # The command behind `hecks quality_control clearance.check_pull_requests`: checks CI for every
11
+ # PR the QualityControl ledger tracks as open (`Patch` and `Improvement`). It starts a
12
+ # `Clearance` per unsettled head commit and lets the CI port's policies record the verdict.
13
13
  #
14
14
  # Exit codes: 0 nothing to act on; 1 operational error (`gh`, bad sha, ledger boot);
15
15
  # 2 a newly red PR, with the details on stdout. A commit's `Clearance` is minted once and never
@@ -76,9 +76,9 @@ module Hecks
76
76
  query("Target.Held").select { |row| row[:claimed_at][:value] + row[:window][:value] <= now }
77
77
  end
78
78
 
79
- # Spawns `hecks quality_control ask run target=<target>` as a fresh process, not a fork.
80
- # Seeds/steps forward only when given, so each child derives its own depth from its own
81
- # streak.
79
+ # Spawns `hecks quality_control query sweep.run target=<target>` as a fresh process, not a
80
+ # fork. Seeds/steps forward only when given, so each child derives its own depth from its
81
+ # own streak.
82
82
  def spawn_sweep_child(target_reference, seeds_override, steps_override, self_consistency,
83
83
  modes:, parity: false)
84
84
  # Unlinked at once: the open fd stays readable, nothing is left on disk, and no fixed path
@@ -304,8 +304,8 @@ module Hecks
304
304
 
305
305
  # Coverage-guided generation's corpus, one JSON file per (target, mode): `--all`'s waves can
306
306
  # run a target's primary sweep and its persistence-parity/concurrency variants as separate
307
- # `hecks quality_control ask run` processes within the same tick, and each explores a
308
- # conceptually independent axis of the same domain, so each keeps its own file rather than
307
+ # `hecks quality_control query sweep.run` processes within the same tick, and each explores
308
+ # a conceptually independent axis of the same domain, so each keeps its own file rather than
309
309
  # sharing one that nothing here locks.
310
310
  def coverage_corpus_path(target_reference, mode)
311
311
  File.join(@coverage_corpus_dir, "#{filesystem_safe_component(target_reference)}-#{mode}.json")
@@ -22,10 +22,10 @@ require_relative "qa_sweep/finding_report"
22
22
 
23
23
  module Hecks
24
24
  module QualityControlCli
25
- # The command behind `hecks quality_control ask run`: runs one claim, sweep, conclude and
26
- # release cycle against a `QualityControl` target (`USAGE` lists the forms, from a bare sweep
27
- # of the least recently swept target to `--all` and `--release`). It stops with a report and
28
- # exit 2 on the first surprised check, and never logs a `Bug` itself.
25
+ # The command behind `hecks quality_control query sweep.run`: runs one claim, sweep, conclude
26
+ # and release cycle against a `QualityControl` target (`USAGE` lists the forms, from a bare
27
+ # sweep of the least recently swept target to `--all` and `--release`). It stops with a report
28
+ # and exit 2 on the first surprised check, and never logs a `Bug` itself.
29
29
  #
30
30
  # Exit codes: 0 clean; 1 operational error; 2 found something (the sweep stays open and the
31
31
  # target is suspended by the ledger's `SuspendOnSurprise` policy until `--release`).
@@ -136,11 +136,12 @@ module Hecks
136
136
  path.start_with?("/") ? File.expand_path(path) : File.expand_path(path, @root)
137
137
  end
138
138
 
139
- # A target reference may itself contain `/` (`hecks quality_control discover_external_domains`
140
- # suggests `repo/entity`-shaped references for an external domain), but every reference also
141
- # gets folded into a single filename component: a log prefix, a shrunk-repro filename, a
142
- # coverage-corpus filename. Left raw, an embedded `/` is read as an extra path segment that
143
- # nothing creates, breaking the write. This never changes what is stored as the
139
+ # A target reference may itself contain `/` (`hecks quality_control
140
+ # target.discover_external_domains` suggests `repo/entity`-shaped references for an external
141
+ # domain), but every reference also gets folded into a single filename component: a log
142
+ # prefix, a shrunk-repro filename, a coverage-corpus filename. Left raw, an embedded `/` is
143
+ # read as an extra path segment that nothing creates, breaking the write. This never changes
144
+ # what is stored as the
144
145
  # `Target`/`Sweep` reference itself.
145
146
  def filesystem_safe_component(reference)
146
147
  reference.gsub(/[^A-Za-z0-9_.-]/, "-")
@@ -9,9 +9,9 @@ require_relative "child"
9
9
 
10
10
  module Hecks
11
11
  module QualityControlCli
12
- # The command behind `hecks quality_control tick`: one QA tick. It needs a clean tree, rebases
13
- # on `origin/main`, then runs `QaPrCheck`, `QaSweep --all` and `QaGeneratedDomains --from-dials`
14
- # and prints one report.
12
+ # The command behind `hecks quality_control sweep.tick`: one QA tick. It needs a clean tree,
13
+ # rebases on `origin/main`, then runs `QaPrCheck`, `QaSweep --all` and `QaGeneratedDomains
14
+ # --from-dials` and prints one report.
15
15
  #
16
16
  # It exits 2 if any step found something, 1 if none did but a step errored or it refused, 0 if
17
17
  # all are clean. The step order is enforced here, not in prose. `QA_REPO_DIR` picks the
@@ -22,7 +22,7 @@ unless defined?(QualityControlDials)
22
22
  # `SweepDepth.for_streak`. The last row (`upto: Float::INFINITY`) is the ceiling.
23
23
  WIDENING_TIERS = settings.widening_tiers
24
24
 
25
- # How many `hecks quality_control ask run <target>` children `--all` keeps running at once.
25
+ # How many `hecks quality_control query sweep.run <target>` children `--all` keeps running at once.
26
26
  SWEEP_MAX_PARALLEL = settings.sweep_max_parallel
27
27
 
28
28
  # How long the `/loop hecks_qa` caller waits for a tick's report before assuming it stalled.
@@ -53,11 +53,11 @@ unless defined?(QualityControlDials)
53
53
  # How many of the least-hit verbs the next seed weights up; 0 turns the lever off.
54
54
  FAVOR_RARE_VERBS = settings.favor_rare_verbs
55
55
 
56
- # Whether `hecks quality_control ask run` also runs the self-consistency pass (cold rehydration, replay
56
+ # Whether `hecks quality_control query sweep.run` also runs the self-consistency pass (cold rehydration, replay
57
57
  # idempotency, value-object JSON round trip) on the same generated sequence.
58
58
  SELF_CONSISTENCY_CHECKS = settings.self_consistency_checks
59
59
 
60
- # Candidate replays `hecks quality_control ask run` may spend shrinking one surprised check; 0 turns shrinking
60
+ # Candidate replays `hecks quality_control query sweep.run` may spend shrinking one surprised check; 0 turns shrinking
61
61
  # off. `concurrency` is never shrunk: a race does not reproduce on demand.
62
62
  SHRINK_BUDGET = settings.shrink_budget
63
63
 
@@ -74,7 +74,7 @@ unless defined?(QualityControlDials)
74
74
  ROTATION_STALE_FLOOR_SECONDS = settings.rotation_stale_floor_seconds
75
75
 
76
76
  # Ceiling on `--seeds` for `--persistence-parity`, which pays for real Postgres per dispatch.
77
- # `hecks quality_control ask run` clamps down, never up, and prints a note.
77
+ # `hecks quality_control query sweep.run` clamps down, never up, and prints a note.
78
78
  PERSISTENCE_PARITY_SEED_CAP = settings.persistence_parity_seed_cap
79
79
 
80
80
  # Ceiling on `--seeds` for the concurrency mode, which needs two disposable schemas per seed.
@@ -85,11 +85,11 @@ unless defined?(QualityControlDials)
85
85
  # disposable database.
86
86
  ADAPTER_PARITY_PAIRS = settings.adapter_parity_pairs
87
87
 
88
- # Which comparison modes `hecks quality_control ask run` may run: the enabled ones intersected with what
88
+ # Which comparison modes `hecks quality_control query sweep.run` may run: the enabled ones intersected with what
89
89
  # `TargetCapabilities.infer(path)` finds in the target. `--modes a,b,c` overrides for one run.
90
90
  #
91
91
  # `wasm_front` and `adapter_parity_postgres` are off because nothing runs them; the others are
92
- # built and off because their I/O cost across the rotation is unmeasured. `hecks quality_control ask run`
92
+ # built and off because their I/O cost across the rotation is unmeasured. `hecks quality_control query sweep.run`
93
93
  # refuses to start when a mode outside `TargetCapabilities::RUNNABLE_MODES` is enabled.
94
94
  MODES = settings.modes
95
95
 
@@ -101,7 +101,7 @@ unless defined?(QualityControlDials)
101
101
  # off. Kept low: a dry run creates no state for later steps to act on.
102
102
  DRY_RUN_FRACTION = settings.dry_run_fraction
103
103
 
104
- # How many generated domains one `hecks quality_control tick` checks after the rotation sweep; 0 skips the
104
+ # How many generated domains one `hecks quality_control sweep.tick` checks after the rotation sweep; 0 skips the
105
105
  # step.
106
106
  GENERATED_DOMAINS_PER_TICK = settings.generated_domains_per_tick
107
107
 
@@ -140,7 +140,7 @@ Hecks.bluebook "QualityControl" do
140
140
  attribute :last_swept, SweptIn
141
141
  # Recency-decayed count of how often this chapter was worth sweeping; see `YieldScore`.
142
142
  attribute :yield_score, YieldScore
143
- # Consecutive `Release`s that came back clean; `hecks quality_control ask run` reads it to choose sweep depth.
143
+ # Consecutive `Release`s that came back clean; `hecks quality_control query sweep.run` reads it to choose sweep depth.
144
144
  # Zero means no streak yet, shared on purpose by a never-swept target and a just-surprised one.
145
145
  attribute :clean_streak, CleanStreak
146
146
  # Comma-joined mode names recorded at release from what the runner inferred. Stored so
@@ -367,7 +367,7 @@ Hecks.bluebook "QualityControl" do
367
367
  end
368
368
 
369
369
  query "Suspended" do
370
- description "Chapters a check surprised, waiting for a person — each with the reason the policy wrote. Nothing automatic touches these; hecks quality_control ask run <target> --release --notes is how one comes back."
370
+ description "Chapters 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."
371
371
  where(status: "suspended")
372
372
  order_by :reference
373
373
  end
@@ -854,7 +854,7 @@ Hecks.bluebook "QualityControl" do
854
854
  # `default:` keeps rows already on file hydrating as `undecided`.
855
855
  attribute :disposition, one_of("undecided", "self_contained", "bigger"), default: { value: "undecided" }
856
856
  # `default: yes` is true of every bug on file, since `Log` always required a failing
857
- # demonstration. `no` is the escape hatch for a real but flaky finding (`hecks quality_control log
857
+ # demonstration. `no` is the escape hatch for a real but flaky finding (`hecks quality_control bug.log
858
858
  # --reproduced no`).
859
859
  attribute :reproduced, one_of("yes", "no"), default: { value: "yes" }
860
860
  # Bugs are claimed like chapters so two agents do not fix the same one. A `given` rather than a
@@ -1023,7 +1023,7 @@ Hecks.bluebook "QualityControl" do
1023
1023
  # Optional; `Name` covers bugs logged without one. No `sets`: on a creation command an
1024
1024
  # argument sharing the aggregate field's name lands there directly.
1025
1025
  attribute :name, BugName, optional: true
1026
- # Optional; omitted, the aggregate's default `yes` applies. `hecks quality_control log --reproduced no`
1026
+ # Optional; omitted, the aggregate's default `yes` applies. `hecks quality_control bug.log --reproduced no`
1027
1027
  # uses it to skip its must-fail check for a finding with no reliable pass/fail signal.
1028
1028
  attribute :reproduced, one_of("yes", "no"), optional: true
1029
1029
 
@@ -1059,7 +1059,7 @@ Hecks.bluebook "QualityControl" do
1059
1059
  end
1060
1060
 
1061
1061
  # Not a lifecycle move (the `Rank` shape); `undecided` is only ever the birth value.
1062
- # `hecks quality_control log --triage` dispatches this right after `Log`.
1062
+ # `hecks quality_control bug.log --triage` dispatches this right after `Log`.
1063
1063
  command "Triage" do
1064
1064
  role "QA engineer"
1065
1065
  goal "Write down whether this can be fixed on the spot or needs a bigger judgment"
@@ -1712,7 +1712,7 @@ Hecks.bluebook "QualityControl" do
1712
1712
  end
1713
1713
  end
1714
1714
 
1715
- # A PR opened for a Bug's own fix, recorded at open so `hecks quality_control check_pull_requests` looks it up by number.
1715
+ # A PR opened for a Bug's own fix, recorded at open so `hecks quality_control clearance.check_pull_requests` looks it up by number.
1716
1716
  # Not named `PullRequest`: `Ticket::PullRequest` exists and aggregate names install on Object.
1717
1717
  aggregate "Patch" do
1718
1718
  description "One pull request opened for a Bug's own fix — number, branch, and the exact commit that makes checking its CI status a lookup instead of a guess."
@@ -1814,9 +1814,9 @@ Hecks.bluebook "QualityControl" do
1814
1814
  emits "PatchClosed"
1815
1815
  end
1816
1816
 
1817
- # The worklist for `hecks quality_control check_pull_requests`: ask `gh` about each row's exact number, never search.
1817
+ # The worklist for `hecks quality_control clearance.check_pull_requests`: ask `gh` about each row's exact number, never search.
1818
1818
  query "Open" do
1819
- description "Every PR we've opened that's still open, by number — hecks quality_control check_pull_requests's own worklist. For each of these, ask gh for exactly this number's CI status; nothing here is a guess."
1819
+ description "Every PR we've opened that's still open, by number — hecks quality_control clearance.check_pull_requests's own worklist. For each of these, ask gh for exactly this number's CI status; nothing here is a guess."
1820
1820
  where(status: "opened")
1821
1821
  order_by :number
1822
1822
  end
@@ -1979,7 +1979,7 @@ Hecks.bluebook "QualityControl" do
1979
1979
  end
1980
1980
 
1981
1981
  query "Open" do
1982
- description "Every PR we've landed for deliberate, non-bugfix work that's still open, by number — hecks quality_control check_pull_requests's own second worklist, the same shape Patch.Open already gives for a bug's own fix. For each of these, ask gh for exactly this number's CI status; nothing here is a guess."
1982
+ description "Every PR we've landed for deliberate, non-bugfix work that's still open, by number — hecks quality_control clearance.check_pull_requests's own second worklist, the same shape Patch.Open already gives for a bug's own fix. For each of these, ask gh for exactly this number's CI status; nothing here is a guess."
1983
1983
  where(status: { in: "opened,landed,needs_fix" })
1984
1984
  order_by :number
1985
1985
  end
@@ -2217,14 +2217,14 @@ Hecks.bluebook "QualityControl" do
2217
2217
  # Nothing reacts to `TicketRaised` on purpose: a ticket waits until somebody submits it.
2218
2218
 
2219
2219
  # The rotation's one automatic reaction. Releasing on `SweepConcluded` cannot work: that event
2220
- # carries no target id, `next_streak` or yield score, so `hecks quality_control ask run` still releases.
2220
+ # carries no target id, `next_streak` or yield score, so `hecks quality_control query sweep.run` still releases.
2221
2221
  # `on Sweep::CheckSurprised`, not `Sweep::Check::...`: an entity command's event is stamped with
2222
2222
  # its parent aggregate. `Check.Surprised` restates `target` so the id rides in.
2223
2223
  policy "SuspendOnSurprise" do
2224
2224
  on Sweep::CheckSurprised
2225
2225
  trigger Target::Suspend, with: {
2226
2226
  target: :target,
2227
- reason: { value: "a check surprised — a person releases with hecks quality_control ask run <target> --release --notes" }
2227
+ reason: { value: "a check surprised — a person releases with hecks quality_control query sweep.run <target> --release --notes" }
2228
2228
  }
2229
2229
  end
2230
2230
  end
@@ -3,7 +3,7 @@
3
3
  # Hecks chapter binds it through its world's `default_adapter`; the ports every one of them shares
4
4
  # are in quality_control.ports.hecksagon.
5
5
  Hecks.hecksagon "QualityControl" do
6
- uses_framework "Governance"
6
+ attaches "Governance"
7
7
  persisted_by "Memory"
8
8
  end
9
9
 
@@ -243,6 +243,8 @@ module Hecks
243
243
  target_args = ctx.args.merge(
244
244
  delegation.source.to_h { |target_key, source_key| [target_key.to_sym, ctx.args[source_key]] }
245
245
  )
246
+ # The target is dispatched as if called directly, so its needs and defaults are filled too.
247
+ target_args = enrich_arguments(target_command, target_args)
246
248
 
247
249
  refuse_unknown_arguments(ctx.domain, ctx.aggregate, target_command, target_args,
248
250
  extra_identity_heads: entity.identity_heads)
@@ -29,12 +29,33 @@ module Hecks
29
29
  # @return [Hash{Symbol => Object}] `args`, with each missing needed fact answered
30
30
  # @raise [Runtime::WiringError] when the port that answers a fact is not bound exactly once
31
31
  def enrich_arguments(command, args)
32
+ fill_defaults(command, fill_needs(command, args))
33
+ end
34
+
35
+ # Answers each fact the command `needs` that the caller left out.
36
+ def fill_needs(command, args)
32
37
  missing = command.needs.reject { |fact| args.key?(fact) || args.key?(fact.to_s) }
33
38
  return args if missing.empty?
34
39
 
35
40
  args.merge(missing.to_h { |fact| [fact, need_value(command, fact)] })
36
41
  end
37
42
 
43
+ # Gives each argument the caller left out the default its attribute declares
44
+ # (`attribute :runs, Count, default: 30`), so a declared default holds on every way in and
45
+ # not only where a door spells it out. An argument the caller passed is kept, whatever it is.
46
+ #
47
+ # @param command [Class] the command being dispatched
48
+ # @param args [Hash{Symbol => Object}] the arguments the caller passed
49
+ # @return [Hash{Symbol => Object}] `args`, with each absent defaulted argument filled
50
+ def fill_defaults(command, args)
51
+ absent = command.attributes.reject do |attribute|
52
+ attribute.default.nil? || args.key?(attribute.name.to_sym) || args.key?(attribute.name.to_s)
53
+ end
54
+ return args if absent.empty?
55
+
56
+ args.merge(absent.to_h { |attribute| [attribute.name.to_sym, attribute.default] })
57
+ end
58
+
38
59
  # The answer to one fact, in the shape the command's argument of that name takes: a bare
39
60
  # Integer for an Integer attribute, else the one-field value object the language declares
40
61
  # for an instant.
@@ -207,12 +207,14 @@ module Hecks
207
207
  declared: declared)
208
208
  end
209
209
 
210
- # A fact the command `needs` is not absent: the interpreter answers it before any refusal
211
- # reads the arguments.
210
+ # A fact the command `needs`, and an argument its attribute gives a default, are not absent:
211
+ # the interpreter fills them before any refusal reads the arguments.
212
212
  def refuse_absent_facts!(declaring, offered, declared)
213
213
  needed = declaring.respond_to?(:needs) ? declaring.needs.map(&:to_sym) : []
214
+ defaulted = declaring.attributes.select { |attribute| attribute.respond_to?(:default) && !attribute.default.nil? }
215
+ .map { |attribute| attribute.name.to_sym }
214
216
  absent = declaring.attributes.reject(&:optional?).map { |attribute| attribute.name.to_sym } -
215
- offered.keys - needed
217
+ offered.keys - needed - defaulted
216
218
  return if absent.empty?
217
219
 
218
220
  raise AbsentArgument,
@@ -20,8 +20,7 @@ module Hecks
20
20
 
21
21
  @hecksagons.each_value do |hexagon|
22
22
  refuse_ungoverned_roles!(hexagon)
23
- refuse_unwired_framework_members!(hexagon)
24
- refuse_unwired_vendored_bluebooks!(hexagon)
23
+ refuse_unwired_attachments!(hexagon)
25
24
  refuse_bounded_without_acl!(hexagon)
26
25
 
27
26
  hexagon.binds.each do |bind|
@@ -153,7 +152,7 @@ module Hecks
153
152
  private
154
153
 
155
154
  # Checked once against the merged hecksagon so a domain split across multiple
156
- # hecksagon files sees every uses_framework declaration first. A role is real
155
+ # hecksagon files sees every `attaches` declaration first. A role is real
157
156
  # access control only once an authorization provider exists to check it against
158
157
  # (ADR 0025); a provider is recognized by declaring authorization, not by name.
159
158
  def refuse_ungoverned_roles!(hexagon)
@@ -172,16 +171,18 @@ module Hecks
172
171
  "without that it is silent decoration, the exact defect this refusal exists to catch"
173
172
  end
174
173
 
175
- # `uses_embryonaut_bluebook` only loads a package's `.bluebook` files; persistence,
176
- # Governance and the `translates` ACL live on a sibling hecksagon the consumer must
177
- # declare, or cross-context field mapping has nowhere to be written.
178
- def refuse_unwired_vendored_bluebooks!(hexagon)
179
- Array(hexagon.vendored_bluebooks).each do |package|
180
- chapter_name = Naming.pascal(package)
174
+ # `attaches` loads a bounded context; the consumer must declare the sibling hecksagon
175
+ # that is its ACL (Governance/Identity/Privacy already do). A vendored package only
176
+ # loads its `.bluebook` files, so persistence, Governance and the `translates` ACL live
177
+ # on that sibling too, or cross-context field mapping has nowhere to be written.
178
+ def refuse_unwired_attachments!(hexagon)
179
+ hexagon.attachments.each do |attachment|
180
+ chapter_name = attachment.chapter_name
181
181
  next if hecksagon(chapter_name)
182
182
 
183
+ what = attachment.vendor? ? "vendored bluebook #{attachment.name.inspect}" : chapter_name.inspect
183
184
  raise WiringError,
184
- "#{hexagon.domain} attaches vendored bluebook #{package.inspect} " \
185
+ "#{hexagon.domain} attaches #{what} " \
185
186
  "(bounded context #{chapter_name}) but never declared " \
186
187
  "Hecks.hecksagon #{chapter_name.inspect} — put that sibling " \
187
188
  "(and any `translates` ACL) in context_map.hecksagon; " \
@@ -189,23 +190,8 @@ module Hecks
189
190
  end
190
191
  end
191
192
 
192
- # `uses_framework` and `attaches` load a bounded context; the consumer must declare the
193
- # sibling hecksagon that is its ACL (Governance/Identity/Privacy already do).
194
- def refuse_unwired_framework_members!(hexagon)
195
- hexagon.member_chapters.each do |member|
196
- next if hecksagon(member)
197
-
198
- raise WiringError,
199
- "#{hexagon.domain} attaches #{member.inspect} " \
200
- "(bounded context) but never declared Hecks.hecksagon " \
201
- "#{member.inspect} — put that sibling (and any `translates` " \
202
- "ACL) in context_map.hecksagon; same-name blocks merge, " \
203
- "order-independent."
204
- end
205
- end
206
-
207
- # An explicit `bounded` mark on a consumer chapter always needs an ACL. `uses_framework`
208
- # / `uses_embryonaut_bluebook` mark the attached chapter bounded and require the sibling
193
+ # An explicit `bounded` mark on a consumer chapter always needs an ACL. `attaches`
194
+ # marks the attached chapter bounded and requires the sibling
209
195
  # hecksagon above; they don't require a `translates` on it unless the consumer also
210
196
  # wrote `bounded`.
211
197
  # rust/host Google sign-in reads ir.json's membership/identity keys, never a deploy-time
@@ -217,7 +203,7 @@ module Hecks
217
203
  raise WiringError,
218
204
  "a chapter that provides \"membership\" is loaded, but none provides " \
219
205
  "\"identity\" — rust/host Google sign-in cannot register or link an " \
220
- "identity from the hecksagon/world. Attach Identity (`uses_framework " \
206
+ "identity from the hecksagon/world. Attach Identity (`attaches " \
221
207
  "\"Identity\"` plus a sibling Hecks.hecksagon \"Identity\") so the " \
222
208
  "identity verbs are exported onto ir.json, not guessed at deploy."
223
209
  end
@@ -387,7 +373,7 @@ module Hecks
387
373
  providers = Framework.providers_of(Bluebook::Capabilities::AUTHORIZATION)
388
374
  return "attaches a chapter that provides \"authorization\" (no framework member declares one)" if providers.empty?
389
375
 
390
- providers.map { |name| "uses_framework #{name.inspect}" }.join(" or ")
376
+ providers.map { |name| "attaches #{name.inspect}" }.join(" or ")
391
377
  end
392
378
 
393
379
  # Every command this domain declares, an aggregate's own and every entity nested
@@ -139,8 +139,7 @@ module Hecks
139
139
  mark_bounded(item.domain) if item.bounded?
140
140
  end
141
141
 
142
- # Marks `name` as a bounded context — called automatically by
143
- # `uses_framework` / `uses_embryonaut_bluebook`, and by
142
+ # Marks `name` as a bounded context — called automatically by `attaches`, and by
144
143
  # `add_hecksagon` when the block itself declared `bounded`.
145
144
  # A bounded chapter wraps in its own module (no Object shortcut)
146
145
  # and must have a `translates` ACL or boot refuses.
@@ -151,8 +150,7 @@ module Hecks
151
150
  @bounded_chapters[name.to_s] = true
152
151
  end
153
152
 
154
- # Says whether `name` is a bounded context — attached via
155
- # `uses_framework` / `uses_embryonaut_bluebook`, or a consumer
153
+ # Says whether `name` is a bounded context — attached via `attaches`, or a consumer
156
154
  # chapter that declared `bounded` on its own hecksagon.
157
155
  #
158
156
  # @param name [String, Symbol] the chapter name to check
@@ -242,7 +240,7 @@ module Hecks
242
240
  def verbs = @bluebooks.values.flat_map(&:verbs).sort
243
241
 
244
242
  # The chapter that answers a role check for `domain`: the domain's own chapter, or
245
- # a framework member its hecksagon attaches, that declares `provides "authorization"`.
243
+ # a chapter its hecksagon attaches, that declares `provides "authorization"`.
246
244
  #
247
245
  # @param domain [String, Symbol] the domain whose role checks are being resolved
248
246
  # @return [Bluebook::Chapter, nil] the chapter that answers `domain`'s role
@@ -275,7 +273,7 @@ module Hecks
275
273
  return attached if attached
276
274
 
277
275
  # Falls back to any loaded chapter: a consuming domain often wires Identity as
278
- # Hecks.hecksagon "Identity" rather than via uses_framework, so it's loaded but
276
+ # Hecks.hecksagon "Identity" rather than via `attaches`, so it's loaded but
279
277
  # not listed on the consumer's own hexagon.
280
278
  @bluebooks.values.find { |chapter| chapter.provides?(Bluebook::Capabilities::IDENTITY) }
281
279
  end
@@ -284,9 +282,8 @@ module Hecks
284
282
  # chapter, a framework member, or a vendored embryonaut bluebook it attaches,
285
283
  # that declares `provides "membership"`.
286
284
  #
287
- # Vendored packages are included here (unlike `authorization_provider_for`)
288
- # because membership ships as a vendored embryonaut_bluebooks chapter, not a
289
- # framework member.
285
+ # Vendored packages are included, since membership ships as a vendored
286
+ # embryonaut_bluebooks chapter, not a framework member.
290
287
  #
291
288
  # @param domain [String, Symbol] the domain whose sign-in aggregate is being resolved
292
289
  # @return [Bluebook::Chapter, nil] the chapter that answers `domain`'s membership
@@ -356,7 +353,7 @@ module Hecks
356
353
  #
357
354
  # Falls back to any loaded chapter, since a consuming domain often wires a
358
355
  # vendored chapter as its own `Hecks.hecksagon "Name"` rather than via
359
- # `uses_embryonaut_bluebook`, so it's loaded but not listed on the consumer's hexagon.
356
+ # `attaches ... from: :vendor`, so it's loaded but not listed on the consumer's hexagon.
360
357
  #
361
358
  # @param domain [String, Symbol] the domain the provider is resolved for
362
359
  # @param capability [String] the capability's name, such as
@@ -364,8 +361,7 @@ module Hecks
364
361
  # @return [Bluebook::Chapter, nil] the providing chapter, or nil if none loaded does
365
362
  def vendored_provider_for(domain, capability)
366
363
  hexagon = hecksagon(domain)
367
- vendored = Array(hexagon&.vendored_bluebooks).map { |name| Naming.pascal(name) }
368
- names = [domain.to_s, *Array(hexagon&.member_chapters), *vendored]
364
+ names = [domain.to_s, *Array(hexagon&.member_chapters)]
369
365
  attached = names.filter_map { |name| bluebook(name) }
370
366
  .find { |chapter| chapter.provides?(capability) }
371
367
  return attached if attached
@@ -517,14 +513,12 @@ module Hecks
517
513
  # Order-independent: list facts uniq, so loading context_map before or after the
518
514
  # domain file yields the same merged hecksagon.
519
515
  Bluebook::Hecksagon.new(
520
- domain: base.domain,
521
- binds: base.binds + overlay.binds,
522
- subscriptions: (base.subscriptions + overlay.subscriptions).uniq,
523
- framework_members: (base.framework_members + overlay.framework_members).uniq,
524
- vendored_bluebooks: (base.vendored_bluebooks + overlay.vendored_bluebooks).uniq,
525
- attached_chapters: (base.attached_chapters + overlay.attached_chapters).uniq,
526
- bounded: base.bounded? || overlay.bounded?,
527
- translates: (base.translates + overlay.translates).uniq
516
+ domain: base.domain,
517
+ binds: base.binds + overlay.binds,
518
+ subscriptions: (base.subscriptions + overlay.subscriptions).uniq,
519
+ attachments: (base.attachments + overlay.attachments).uniq,
520
+ bounded: base.bounded? || overlay.bounded?,
521
+ translates: (base.translates + overlay.translates).uniq
528
522
  )
529
523
  end
530
524
 
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hecks
4
+ module RustBuild
5
+ # Makes an appended element's field `Option<T>` in the IR when any command sources it from an
6
+ # optional argument, since Ruby stores the omitted nil without complaint. `ir.json` records
7
+ # the marking, so it runs before the IR is written or generated from.
8
+ #
9
+ # `rust/build/src/optional_pass.rs` is the Ruby-free build's port of this; it looks only at an
10
+ # aggregate's own value objects, where this reads the domain-wide set.
11
+ module AppendOptionals
12
+ # Marks every aggregate in the IR, in place.
13
+ #
14
+ # @param tree [Hash] a domain's IR, symbol-keyed
15
+ # @return [Hash] the same IR
16
+ def self.mark(tree)
17
+ domain_wide = by_name(tree[:aggregates].flat_map { |aggregate| aggregate[:value_objects] })
18
+ tree[:aggregates].each do |aggregate|
19
+ mark_aggregate(aggregate, domain_wide.merge(by_name(aggregate[:value_objects])))
20
+ end
21
+ tree
22
+ end
23
+
24
+ # Resolves an `append` target to its entity or value object, or nil if it is neither. A
25
+ # local entity wins over a domain-wide value object of the same name: Syntax's local
26
+ # `Argument` entity shares a name with Command's `Argument` value object.
27
+ #
28
+ # @param aggregate [Hash] the aggregate owning the command
29
+ # @param target_type [String] the appended-to attribute's element type
30
+ # @param value_objects_by_name [Hash{String => Hash}] the value objects in scope
31
+ # @return [Hash, nil] the element, or nil
32
+ def self.element(aggregate, target_type, value_objects_by_name)
33
+ aggregate[:entities].find { |entity| entity[:name] == target_type } ||
34
+ value_objects_by_name[target_type]
35
+ end
36
+
37
+ # @param aggregate [Hash] one aggregate of the IR
38
+ # @param value_objects_by_name [Hash{String => Hash}] the value objects in scope
39
+ # @return [void]
40
+ def self.mark_aggregate(aggregate, value_objects_by_name)
41
+ aggregate[:attributes].each do |target_attr|
42
+ target = element(aggregate, target_attr[:type], value_objects_by_name)
43
+ next unless target
44
+
45
+ aggregate[:commands].each do |command|
46
+ appends = command[:mutations].select { |m| append_to?(m, target_attr) }
47
+ appends.each { |mutation| mark_fields(mutation, command, target) }
48
+ end
49
+ end
50
+ end
51
+
52
+ def self.append_to?(mutation, target_attr)
53
+ mutation[:op].to_s == "append" && mutation[:target].to_s == target_attr[:name].to_s
54
+ end
55
+ private_class_method :append_to?
56
+
57
+ # A Symbol is an argument name, and an optional one is what this pass wants; anything
58
+ # else is a literal.
59
+ def self.mark_fields(mutation, command, target)
60
+ mutation[:fields].each do |field_name, source|
61
+ parsed = Hecks::Bluebook::Assembly::Marks.read(source)
62
+ next unless parsed.is_a?(Symbol)
63
+
64
+ source_attr = command[:attributes].find { |attr| attr[:name].to_s == parsed.to_s }
65
+ next unless source_attr && source_attr[:optional]
66
+
67
+ field_attr = target[:attributes].find { |attr| attr[:name].to_s == field_name.to_s }
68
+ field_attr[:optional] = true if field_attr
69
+ end
70
+ end
71
+ private_class_method :mark_fields
72
+
73
+ def self.by_name(value_objects)
74
+ value_objects.to_h { |value_object| [value_object[:name], value_object] }
75
+ end
76
+ private_class_method :by_name
77
+ end
78
+ end
79
+ end
@@ -6,6 +6,7 @@ require "tmpdir"
6
6
  require_relative "../cache_dir"
7
7
  require_relative "../rust_build"
8
8
  require_relative "conformance"
9
+ require_relative "kernel_input"
9
10
 
10
11
  module Hecks
11
12
  module RustBuild
@@ -59,7 +60,7 @@ module Hecks
59
60
  def replay(domain, artifact, seed, seeds, steps, scratch)
60
61
  sequence = Hecks::Fuzzing::SequenceGenerator.generate(domain, seed: seed, steps: steps)
61
62
  path = File.join(scratch, "seed-#{seed}.json")
62
- File.write(path, JSON.generate({ "steps" => sequence }))
63
+ File.write(path, KernelInput.json(domain, sequence))
63
64
  return if Conformance.call([domain, path, artifact]).zero?
64
65
 
65
66
  raise Failure, "hecks fuzz_conformance: seed #{seed}/#{seeds} diverged against #{artifact} " \