hecks 2.7.0 → 2.8.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 (150) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +13 -0
  3. data/lib/hecks/adapters/driven/memory.rb +1 -1
  4. data/lib/hecks/adapters/driven/postgres.rb +1 -1
  5. data/lib/hecks/adapters/driven/prism.rb +1 -1
  6. data/lib/hecks/adapters/driven/sqlite/projection.rb +1 -1
  7. data/lib/hecks/adapters/driven/tenant_provisioner.rb +31 -10
  8. data/lib/hecks/behaviors/expectations.rb +2 -2
  9. data/lib/hecks/bluebook/assembly/contract.rb +1 -1
  10. data/lib/hecks/bluebook/assembly/contracts.rb +5 -5
  11. data/lib/hecks/bluebook/assembly/specializer.rb +2 -2
  12. data/lib/hecks/bluebook/assembly.rb +1 -1
  13. data/lib/hecks/bluebook/behaviour/chapter.rb +5 -2
  14. data/lib/hecks/bluebook/behaviour/read_model.rb +18 -0
  15. data/lib/hecks/bluebook/capabilities.rb +2 -2
  16. data/lib/hecks/bluebook/chapter.rb +1 -1
  17. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +2 -2
  18. data/lib/hecks/bluebook/dsl/binding_proxy.rb +6 -1
  19. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +2 -2
  20. data/lib/hecks/bluebook/dsl/entity_builder.rb +1 -1
  21. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +1 -1
  22. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +14 -9
  23. data/lib/hecks/bluebook/dsl/rule_reference.rb +3 -3
  24. data/lib/hecks/bluebook/dsl/translation_builder.rb +1 -1
  25. data/lib/hecks/bluebook/dsl/world_builder.rb +5 -0
  26. data/lib/hecks/bluebook/meta_validator/judge.rb +3 -3
  27. data/lib/hecks/bluebook/meta_validator/plan.rb +4 -4
  28. data/lib/hecks/bluebook/meta_validator/readings.rb +1 -1
  29. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +2 -2
  30. data/lib/hecks/bluebook/meta_validator/shapes.rb +1 -1
  31. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +11 -10
  32. data/lib/hecks/bluebook/meta_validator.rb +1 -1
  33. data/lib/hecks/bluebook/model_check/client_profile.rb +4 -70
  34. data/lib/hecks/bluebook/model_check.rb +12 -13
  35. data/lib/hecks/cache_dir.rb +103 -0
  36. data/lib/hecks/cli/document.rb +104 -0
  37. data/lib/hecks/cli/ir.rb +47 -0
  38. data/lib/hecks/cli/mcp.rb +32 -0
  39. data/lib/hecks/cli/mcp_door.rb +461 -0
  40. data/lib/hecks/cli/model_check.rb +270 -0
  41. data/lib/hecks/cli/project_cli.rb +123 -0
  42. data/lib/hecks/cli/project_diagrams.rb +43 -0
  43. data/lib/hecks/cli/run.rb +219 -0
  44. data/lib/hecks/cli/smoke_test.rb +59 -0
  45. data/lib/hecks/cli/stores.rb +54 -0
  46. data/lib/hecks/cli.rb +166 -0
  47. data/lib/hecks/facade/handle.rb +2 -2
  48. data/lib/hecks/facade/surface/aggregate_door.rb +1 -1
  49. data/lib/hecks/language/bluebook/vocabulary.bluebook +16 -0
  50. data/lib/hecks/mcp_door_scope.rb +176 -0
  51. data/lib/hecks/ports/persistence/append_only.rb +1 -1
  52. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +1 -1
  53. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +2 -2
  54. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +2 -2
  55. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +2 -2
  56. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +1 -1
  57. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +1 -1
  58. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +12 -6
  59. data/lib/hecks/ports/persistence/state_codec.rb +1 -1
  60. data/lib/hecks/projections/deploy/fargate.rb +1 -1
  61. data/lib/hecks/projections/deploy/lambda.rb +3 -3
  62. data/lib/hecks/projections/reference.rb +7 -1
  63. data/lib/hecks/projector/exporter.rb +4 -4
  64. data/lib/hecks/projector/target.rb +1 -1
  65. data/lib/hecks/release/runner/ci_publisher.rb +96 -0
  66. data/lib/hecks/release/runner/commands.rb +68 -0
  67. data/lib/hecks/release/runner/console.rb +52 -0
  68. data/lib/hecks/release/runner/gem_publisher.rb +44 -0
  69. data/lib/hecks/release/runner/git.rb +48 -0
  70. data/lib/hecks/release/runner/npm_publisher.rb +89 -0
  71. data/lib/hecks/release/runner/preflight.rb +119 -0
  72. data/lib/hecks/release/runner/published.rb +55 -0
  73. data/lib/hecks/release/runner/tagger.rb +87 -0
  74. data/lib/hecks/release/runner.rb +244 -0
  75. data/lib/hecks/router/namespace_installer.rb +1 -1
  76. data/lib/hecks/runtime/command_interpreter.rb +3 -3
  77. data/lib/hecks/runtime/command_rules/admissibility.rb +2 -2
  78. data/lib/hecks/runtime/entity_element.rb +1 -1
  79. data/lib/hecks/runtime/entity_interpreter.rb +1 -1
  80. data/lib/hecks/runtime/invocation.rb +1 -1
  81. data/lib/hecks/runtime/loader.rb +1 -1
  82. data/lib/hecks/runtime/outbox.rb +1 -1
  83. data/lib/hecks/runtime/reaction_invocation.rb +3 -3
  84. data/lib/hecks/runtime/read_model_interpreter.rb +37 -15
  85. data/lib/hecks/runtime/reference_hop.rb +4 -4
  86. data/lib/hecks/runtime/registry/verification.rb +11 -11
  87. data/lib/hecks/runtime/registry.rb +8 -4
  88. data/lib/hecks/runtime/saga_interpreter.rb +1 -1
  89. data/lib/hecks/runtime/value/coercion.rb +3 -3
  90. data/lib/hecks/runtime/value/entity_list_coercion.rb +1 -1
  91. data/lib/hecks/storehouse.rb +18 -12
  92. data/lib/hecks/version.rb +1 -1
  93. data/lib/hecks/vocabulary.rb +5 -0
  94. data/lib/hecks.rb +6 -1
  95. metadata +28 -58
  96. data/lib/hecks/bench/cli.rb +0 -74
  97. data/lib/hecks/bench/environment.rb +0 -100
  98. data/lib/hecks/bench/postgres_probe.rb +0 -41
  99. data/lib/hecks/bench/report.rb +0 -116
  100. data/lib/hecks/bench/ruby_runner.rb +0 -151
  101. data/lib/hecks/bench/run.rb +0 -55
  102. data/lib/hecks/bench/rust_runner.rb +0 -165
  103. data/lib/hecks/bench/stats.rb +0 -75
  104. data/lib/hecks/bench/suite.rb +0 -172
  105. data/lib/hecks/bench/workload.rb +0 -137
  106. data/lib/hecks/bench.rb +0 -28
  107. data/lib/hecks/codemod.rb +0 -443
  108. data/lib/hecks/corpus.rb +0 -479
  109. data/lib/hecks/doc/reference.rb +0 -585
  110. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -684
  111. data/lib/hecks/fuzzing/combination_miner.rb +0 -178
  112. data/lib/hecks/fuzzing/concurrent_dispatch.rb +0 -469
  113. data/lib/hecks/fuzzing/coverage_campaign.rb +0 -161
  114. data/lib/hecks/fuzzing/differential.rb +0 -192
  115. data/lib/hecks/fuzzing/domain_generator.rb +0 -871
  116. data/lib/hecks/fuzzing/era_boundary.rb +0 -154
  117. data/lib/hecks/fuzzing/form_census.rb +0 -285
  118. data/lib/hecks/fuzzing/generated_domain_check.rb +0 -171
  119. data/lib/hecks/fuzzing/invalid_value_generator.rb +0 -125
  120. data/lib/hecks/fuzzing/isolated_boot.rb +0 -658
  121. data/lib/hecks/fuzzing/nondeterministic.rb +0 -79
  122. data/lib/hecks/fuzzing/persistence_parity.rb +0 -253
  123. data/lib/hecks/fuzzing/properties/corrections.rb +0 -125
  124. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +0 -688
  125. data/lib/hecks/fuzzing/properties/guards.rb +0 -398
  126. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +0 -399
  127. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +0 -137
  128. data/lib/hecks/fuzzing/properties/outbox.rb +0 -180
  129. data/lib/hecks/fuzzing/properties/querying.rb +0 -260
  130. data/lib/hecks/fuzzing/properties.rb +0 -267
  131. data/lib/hecks/fuzzing/qa_settings.rb +0 -164
  132. data/lib/hecks/fuzzing/replay.rb +0 -930
  133. data/lib/hecks/fuzzing/rotation_priority.rb +0 -114
  134. data/lib/hecks/fuzzing/rust_gap_manifest.rb +0 -139
  135. data/lib/hecks/fuzzing/self_consistency.rb +0 -825
  136. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +0 -532
  137. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +0 -171
  138. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +0 -127
  139. data/lib/hecks/fuzzing/sequence_generator/picker.rb +0 -116
  140. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +0 -138
  141. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +0 -344
  142. data/lib/hecks/fuzzing/sequence_generator.rb +0 -311
  143. data/lib/hecks/fuzzing/shrinker.rb +0 -309
  144. data/lib/hecks/fuzzing/structural_skips.rb +0 -53
  145. data/lib/hecks/fuzzing/sweep_depth.rb +0 -61
  146. data/lib/hecks/fuzzing/target_capabilities.rb +0 -255
  147. data/lib/hecks/fuzzing/value_generator.rb +0 -340
  148. data/lib/hecks/fuzzing.rb +0 -23
  149. data/lib/hecks/grammar/evolve.rb +0 -524
  150. data/lib/hecks/query_ir.rb +0 -482
@@ -1,114 +0,0 @@
1
- module Hecks
2
- module Fuzzing
3
- # The weighted half of `QualityControl::Target.Rotation`
4
- # (qa/bluebook/quality_control.bluebook) — see that query's own
5
- # comment for why the ledger itself still only offers
6
- # least-recently-swept order: `order_by` sorts by exactly one stored
7
- # field, and a target's actual rotation priority wants two blended
8
- # together (time since last swept, and recent yield), which nothing
9
- # in the query sublanguage can express. `Rotation` stays the raw
10
- # view; this module is where the blend actually happens, against the
11
- # rows that query already returns.
12
- #
13
- # ## Pure, deliberately
14
- #
15
- # Every method here is a function of its own arguments only — no clock
16
- # read, no query dispatched, nothing random — so this is unit-testable
17
- # with plain hashes and no ledger boot at all, and reruns identically
18
- # given the same rotation, the same `now` and the same dials. `now`
19
- # arrives as an argument for the exact reason `Target.Claim`'s own
20
- # comment already gives: a pure function cannot ask the time, so
21
- # whoever calls this supplies it, exactly as it supplies an id.
22
- #
23
- # ## Where this actually matters, and where it does not
24
- #
25
- # Checked against `bin/qa_sweep`'s own code, not assumed. `bin/qa_sweep
26
- # --all` sweeps every currently-waiting target in one pass, regardless
27
- # of order (`run_all_mode`'s own `waiting.map { spawn_sweep_child }` —
28
- # every element the query returned, never `.first`), so nothing about
29
- # this module's own ordering changes what `--all` does; it already
30
- # sweeps everything the ledger has to offer. What it changes is
31
- # `bin/qa_sweep` invoked with no target argument, which today picks
32
- # exactly one target via `Target.Rotation.first` — wiring `.pick` in
33
- # there instead is this module's one real caller, and (per the same
34
- # investigation) its only one: there is no separate scheduling or
35
- # concurrency-limit mechanism elsewhere in this repository for it to
36
- # feed instead.
37
- module RotationPriority
38
- module_function
39
-
40
- # The next stored `Target.yield_score` — decay what survives from
41
- # before, then add whatever this just-concluded period actually
42
- # found. Integer division, like every other count in this ledger
43
- # (`Sweep.CheckCount`, `Bug.BugOrder`): a yield score is a
44
- # priority signal, not a precise statistic, and a fractional score
45
- # would need a value object this ledger does not have.
46
- #
47
- # `decay_percent` defaults to the dial, not a literal, so a caller
48
- # that does not care still gets the practice's own current answer
49
- # to "how much of this is 'recent'" rather than a second, silently
50
- # drifting copy of the same number.
51
- #
52
- # @param old_score [Integer] the target's `Target.yield_score` before this period
53
- # @param surprises_this_period [Integer] how many Surprised checks this period found
54
- # @param decay_percent [Integer] percent of `old_score` that survives into the new
55
- # score; defaults to `QualityControlDials::YIELD_DECAY_PERCENT`
56
- # @return [Integer] the next `Target.yield_score` to store
57
- # @raise [ArgumentError] if `old_score` is negative
58
- # @raise [ArgumentError] if `surprises_this_period` is negative
59
- def next_yield_score(old_score:, surprises_this_period:,
60
- decay_percent: QualityControlDials::YIELD_DECAY_PERCENT)
61
- raise ArgumentError, "old_score must not be negative" if old_score.negative?
62
- raise ArgumentError, "surprises_this_period must not be negative" if surprises_this_period.negative?
63
-
64
- ((old_score * decay_percent) / 100) + surprises_this_period
65
- end
66
-
67
- # The pick itself. `rows` is whatever `Target.Rotation` returned —
68
- # each row a hash carrying at least `:last_swept` and
69
- # `:yield_score`, both `{ value: Integer }`, the shape every
70
- # VO-typed field in this ledger already comes back as. Returns
71
- # `nil` for an empty rotation, the same "nothing waiting" case
72
- # `bin/qa_sweep` already handles by seeding the default targets.
73
- #
74
- # The floor wins outright — no starvation, on purpose (see
75
- # `QualityControlDials::ROTATION_STALE_FLOOR_SECONDS`'s own
76
- # comment for why a floor and not a total exclusion). A row stale
77
- # past `floor_seconds` is picked ahead of every row that is not,
78
- # oldest-first among however many have crossed it — the same
79
- # tiebreak `Rotation` itself already uses for everything, which
80
- # keeps the floor from becoming a new source of starvation between
81
- # two long-neglected targets.
82
- #
83
- # Below the floor, highest combined score wins. `staleness +
84
- # (yield_score * weight_seconds)` — plain addition, never a ratio
85
- # or anything that could divide by zero, and the units are
86
- # deliberately the same (seconds) so `weight_seconds` reads as
87
- # "how many seconds of extra staleness one point of yield is
88
- # worth" rather than an opaque multiplier nobody could sanity
89
- # check by eye.
90
- #
91
- # @param rows [Array<Hash>] rows from `Target.Rotation`, each carrying at least
92
- # `:last_swept` and `:yield_score` as `{ value: Integer }`
93
- # @param now [Integer] the current time, Unix epoch seconds
94
- # @param weight_seconds [Integer] seconds of extra staleness one point of yield is
95
- # worth; defaults to `QualityControlDials::YIELD_WEIGHT_SECONDS`
96
- # @param floor_seconds [Integer] staleness, in seconds, past which a row is picked
97
- # ahead of every row not yet that stale; defaults to
98
- # `QualityControlDials::ROTATION_STALE_FLOOR_SECONDS`
99
- # @return [Hash, nil] the picked row, or `nil` if `rows` is empty
100
- def pick(rows, now:, weight_seconds: QualityControlDials::YIELD_WEIGHT_SECONDS,
101
- floor_seconds: QualityControlDials::ROTATION_STALE_FLOOR_SECONDS)
102
- return nil if rows.empty?
103
-
104
- floored = rows.select { |row| staleness(row, now) >= floor_seconds }
105
- return floored.min_by { |row| row[:last_swept][:value] } unless floored.empty?
106
-
107
- rows.max_by { |row| staleness(row, now) + (row[:yield_score][:value] * weight_seconds) }
108
- end
109
-
110
- def staleness(row, now) = now - row[:last_swept][:value]
111
- private_class_method :staleness
112
- end
113
- end
114
- end
@@ -1,139 +0,0 @@
1
- require "json"
2
- require "hecks/naming"
3
-
4
- module Hecks
5
- module Fuzzing
6
- # What a compiled Rust binary declares it did not generate — read off the
7
- # `manifest.json` files `rust/project/domain_generator.rb` writes beside
8
- # every generated module, never inferred from Rust's refusal wording.
9
- #
10
- # The differential fuzzer (`Differential.diff`) drops a query or
11
- # read-model verb from the comparison if and only if the generator's own
12
- # manifest recorded it as `generated: false`, with the `gap_class` and
13
- # `construct` that explain why — never by pattern-matching Rust's
14
- # refusal wording ("is not generated for this domain"), which would
15
- # tolerate whatever Rust happened to say, including a codegen
16
- # regression that silently started refusing a construct it once
17
- # generated. A refusal the manifest does not account for stays in the
18
- # comparison and fails it.
19
- #
20
- # ## Which manifests describe a binary
21
- #
22
- # `build_and_pin` (spec/support/rust_conformance_helpers.rb) and
23
- # `bin/qa_generated_domains` both pin a feature's binary at
24
- # `<rust_dir>/target/debug/rust-<feature>`. That binary compiles
25
- # `src/generated/<feature>/` plus every shared framework chapter (a
26
- # generated directory with no `merged.rs` of its own — `governance`,
27
- # `identity`; see the generated `mod.rs` header). Ids in those manifests
28
- # are domain-qualified, so a chapter a domain never attaches contributes
29
- # entries no sequence for that domain can name.
30
- #
31
- # ## A missing manifest tolerates nothing
32
- #
33
- # A hand-written fixture crate, or a tree generated before manifests
34
- # existed, has no declaration to honour, so every refusal it produces
35
- # is compared as-is. That is the fail-closed direction.
36
- class RustGapManifest
37
- # The only kinds the kernel answers as a query step. A not-generated
38
- # command changes state, so dropping its refusal would not make the
39
- # comparison honest; those stay compared and fail.
40
- TOLERABLE_KINDS = %w[query read_model].freeze
41
- PINNED_BINARY = %r{\A(?<rust_dir>.+)/target/debug/rust-(?<feature>[a-z0-9_]+)\z}
42
-
43
- attr_reader :rust_dir, :feature, :entries
44
-
45
- # Builds the manifest reader for the pinned conformance binary at `binary`.
46
- #
47
- # @param binary [String] path to a pinned binary, `<rust_dir>/target/debug/rust-<feature>`
48
- # @return [Fuzzing::RustGapManifest] the manifest reader for that binary's feature
49
- # @raise [ArgumentError] if `binary` does not match the pinned-binary path shape
50
- def self.for_binary(binary)
51
- match = PINNED_BINARY.match(File.expand_path(binary.to_s))
52
- unless match
53
- raise ArgumentError, "#{binary.inspect} is not a pinned conformance binary " \
54
- "(<rust_dir>/target/debug/rust-<feature>) — cannot tell which manifest describes it"
55
- end
56
-
57
- new(rust_dir: match[:rust_dir], feature: match[:feature])
58
- end
59
-
60
- # Every committed manifest entry under `rust_dir`, each tagged with the
61
- # generated module it came from — what the boundary ratchet and
62
- # bin/rust_coverage's allowlist staleness check read.
63
- #
64
- # @param rust_dir [String] path to the Rust project root (holds `src/generated/*/`)
65
- # @return [Array<Hash>] every `src/generated/*/manifest.json` entry, each a
66
- # String-keyed manifest Hash plus `"module" => String` naming the directory it came from
67
- def self.all_entries(rust_dir)
68
- Dir.glob(File.join(rust_dir, "src/generated/*/manifest.json")).flat_map do |path|
69
- module_name = File.basename(File.dirname(path))
70
- JSON.parse(File.read(path)).map { |entry| entry.merge("module" => module_name) }
71
- end
72
- end
73
-
74
- # @param rust_dir [String] path to the Rust project root (holds `src/generated/*/`)
75
- # @param feature [String] the domain feature this manifest reader describes
76
- def initialize(rust_dir:, feature:)
77
- @rust_dir = rust_dir
78
- @feature = feature
79
- @entries = manifest_paths.flat_map { |path| JSON.parse(File.read(path)) }.freeze
80
- @not_generated = index_not_generated
81
- end
82
-
83
- # The manifest entry that declares `verb` not generated, or nil. `verb`
84
- # is the wire spelling a sequence step uses; an ad hoc filter (a Hash)
85
- # is never declared and answers nil.
86
- #
87
- # @param verb [String, Object] the wire-spelled query or read-model verb to look up
88
- # @return [Hash, nil] the String-keyed manifest entry declaring `verb` not generated,
89
- # or nil if `verb` is not a String or no entry declares it
90
- def not_generated(verb)
91
- return nil unless verb.is_a?(String)
92
-
93
- @not_generated[verb]
94
- end
95
-
96
- # Answers whether `verb` is declared not generated by this manifest.
97
- #
98
- # @param verb [String, Object] the wire-spelled query or read-model verb to look up
99
- # @return [Boolean] whether the manifest declares `verb` not generated
100
- def not_generated?(verb) = !not_generated(verb).nil?
101
-
102
- # Lists every verb this manifest declares not generated.
103
- #
104
- # @return [Set<String>] every verb the manifest declares not generated
105
- def not_generated_verbs = @not_generated.keys.to_set
106
-
107
- private
108
-
109
- def manifest_paths
110
- generated = File.join(rust_dir, "src/generated")
111
- own = File.join(generated, feature, "manifest.json")
112
- chapters = Dir.glob(File.join(generated, "*/manifest.json")).reject do |path|
113
- dir = File.dirname(path)
114
- File.basename(dir) == feature || File.exist?(File.join(dir, "merged.rs"))
115
- end
116
- ([own] + chapters.sort).select { |path| File.exist?(path) }
117
- end
118
-
119
- def index_not_generated
120
- @entries.each_with_object({}) do |entry, index|
121
- next unless TOLERABLE_KINDS.include?(entry["kind"]) && entry["generated"] == false
122
-
123
- wire_verbs(entry).each { |verb| index[verb] = entry }
124
- end.freeze
125
- end
126
-
127
- # A query is asked by its id. A read model's id is "Domain::Name", but
128
- # it is asked as "Domain.Name" or "Domain.name_in_snake_case" — the two
129
- # spellings `kernel::read_model::find` accepts (`matches_snake_alias`,
130
- # a port of `Hecks::Naming.snake`).
131
- def wire_verbs(entry)
132
- return [entry["id"]] if entry["kind"] == "query"
133
-
134
- domain, name = entry["id"].split("::", 2)
135
- ["#{domain}.#{name}", "#{domain}.#{Hecks::Naming.snake(name)}"].uniq
136
- end
137
- end
138
- end
139
- end