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
@@ -39,7 +39,7 @@ module Hecks
39
39
  @event_log = []
40
40
  @reaction_log = []
41
41
  @saga_log = []
42
- # A DECLARATIVE FACT, NOT YET A DISPATCHED ONE — `AggregateDoor#
42
+ # A declarative fact, not yet a dispatched one — `AggregateDoor#
43
43
  # mark_sensitive` (called from a `.hecksagon` file, the same way
44
44
  # `port`/`persisted_by` already are) appends here at
45
45
  # hecksagon-build time; `Runtime::Loader.boot`'s own post-dispatcher
@@ -162,19 +162,23 @@ module Hecks
162
162
  # @return [Bluebook::Chapter] `item`, unchanged
163
163
  def add_bluebook(item) = @bluebooks[item.name] = item
164
164
 
165
- # PROVENANCE, SIDE-CHANNEL — which real `.bluebook` file(s)
165
+ # Provenance, side-channel — which real `.bluebook` file(s)
166
166
  # contributed to a chapter name, never part of the exported IR (a
167
167
  # boot-time loading fact, not a domain fact) and never Rust-mirrored
168
168
  # (the same "additive, Ruby-only" shape `@translations` above already
169
- # is). Legitimate accumulation (several files declaring the SAME
169
+ # is). Legitimate accumulation (several files declaring the same
170
170
  # chapter name on purpose — `lib/hecks/language/bluebook/*.bluebook`
171
171
  # all open `Hecks.bluebook "Bluebook"`) pushes more than one path
172
172
  # here too; that alone is not a problem. What this exists to let
173
173
  # `refuse_cross_package_bluebook_merge!` (registry/verification.rb)
174
- # catch is TWO UNRELATED PACKAGES accumulating into the same name by
174
+ # catch is two unrelated packages accumulating into the same name by
175
175
  # coincidence — a stale vendored fork's own copy of a real gem's
176
176
  # chapter, still reachable on the load path, silently merging its
177
177
  # aggregates into the real one via this exact accumulation mechanism.
178
+ #
179
+ # @param name [String, Symbol] the chapter name the file contributed to
180
+ # @param path [String] the `.bluebook` file that declared it
181
+ # @return [Array<String>] every path recorded for that chapter, `path` last
178
182
  def record_bluebook_source(name, path)
179
183
  (@bluebook_sources[name.to_s] ||= []) << path
180
184
  end
@@ -425,7 +425,7 @@ module Hecks
425
425
  #
426
426
  # A compensation that is itself refused does not unwind again, and needs no
427
427
  # flag to stop it: the state moves to the compensating leg's to_state before
428
- # its dispatches run, so a second refusal finds the instance no longer in
428
+ # its dispatches run, so a second refusal finds the instance already out of
429
429
  # from_state and records that instead. The check is the guard.
430
430
  def unwind(process_manager, event, instance, correlation, domain)
431
431
  return unless instance && process_manager.handles?(REFUSED)
@@ -916,11 +916,11 @@ module Hecks
916
916
  # C3.8 — the boundary check for an attribute whose type is a bare
917
917
  # primitive rather than a value object: `Integer`/`Float` by exact
918
918
  # numeric class (`NUMERIC`), `String`/booleans by rejecting only a
919
- # composite shape (`COMPOSITE_SHAPES`) — not the same as
919
+ # composite shape (`COMPOSITE_SHAPES`) — a looser test than
920
920
  # `check_scalar_shapes` holds a value object's own `String` field to
921
- # any more (QualityControl BUG#125 tightened that one to also refuse
921
+ # (QualityControl BUG#125 has that one also refuse
922
922
  # a non-string scalar; a bare `String` argument here still admits
923
- # any other scalar, left exactly as it was — a bare-primitive
923
+ # any other scalar — a bare-primitive
924
924
  # attribute was never part of BUG#125's own investigation or fix,
925
925
  # and whether it needs the same tightening, and against what real
926
926
  # Judge dependency if any, is still open); worded by the same
@@ -27,7 +27,7 @@ module Hecks
27
27
  # because coercion has to resolve value objects, and only the
28
28
  # root answers `.value_object` at all (Entity's own header
29
29
  # comment: an entity must not answer to it, or `Value.
30
- # for_attribute` could no longer tell a piece from a head). So
30
+ # for_attribute` could not tell a piece from a head). So
31
31
  # a nested entity — Dispatch, inside Handler — is not a direct
32
32
  # child of the root the way Handler itself is, and a plain
33
33
  # `aggregate.entities.find` stops one level short of it.
@@ -1,6 +1,7 @@
1
1
  require "json"
2
2
  require "fileutils"
3
3
  require "time"
4
+ require_relative "cache_dir"
4
5
  require_relative "facade/command_request"
5
6
  require_relative "facade/json_door"
6
7
  require_relative "naming"
@@ -8,7 +9,7 @@ require_relative "projector"
8
9
  require_relative "runtime/errors"
9
10
 
10
11
  module Hecks
11
- # **The bus, not a door** — `docs/hecks-survey-what-we-wish-we-had.md` and
12
+ # **The bus, not a door** — `docs/archive/hecks-survey-what-we-wish-we-had.md` and
12
13
  # `docs/future-features.md` both name the sibling project's own
13
14
  # "Storehouse" the single highest-priority gap this repo had: "no
14
15
  # per-command tool... the bluebook is the contract, the [door] just
@@ -90,19 +91,24 @@ module Hecks
90
91
  module Storehouse
91
92
  module_function
92
93
 
93
- # The same closed set `docs/hecks-survey-what-we-wish-we-had.md`'s
94
+ # The same closed set `docs/archive/hecks-survey-what-we-wish-we-had.md`'s
94
95
  # `SourceTag` names — who dispatched, not what. Optional: a caller
95
96
  # that omits it gets `source: nil` recorded, honestly, rather than a
96
97
  # guessed default.
97
98
  SOURCE_TAGS = %w[process-manager operator hook sidequest-agent cascade daemon].freeze
98
99
 
99
- # **The bus's own audit trail** — a JSONL file per domain, one line per
100
- # `dispatch`/`query`/`state`/dry-run call, `follow` tails it back.
101
- # `tmp/`, not the domain's own directory: this is the bus's record
102
- # of what was asked of it, not part of the domain's own persisted
103
- # state, and `tmp/` is already gitignored for exactly this kind of
104
- # local, disposable-but-useful-while-it-lasts file.
105
- LOG_ROOT = File.expand_path("../../tmp/storehouse", __dir__)
100
+ # Names the directory holding the bus's own audit trail.
101
+ #
102
+ # The trail is a JSONL file per domain, one line per
103
+ # `dispatch`/`query`/`state`/dry-run call, and `follow` tails it back.
104
+ # It is kept under `Hecks::CacheDir`, not the domain's own directory: this is
105
+ # the bus's record of what was asked of it, not part of the domain's own
106
+ # persisted state, and not the gem's own directory either, which is
107
+ # read-only on an installed gem.
108
+ #
109
+ # @return [String] the absolute path of the `storehouse` directory under
110
+ # the cache root
111
+ def log_root = Hecks::CacheDir.path("storehouse")
106
112
 
107
113
  # The root every `domain:`/`under:` must resolve under — the project
108
114
  # directory by default, `HECKS_STOREHOUSE_ROOT` to widen or move it.
@@ -321,9 +327,9 @@ module Hecks
321
327
  #
322
328
  # @param domain_name [String, Symbol, #to_s] the domain name
323
329
  # @return [String] the absolute path to that domain's JSONL log file, under
324
- # `LOG_ROOT`
330
+ # `log_root`
325
331
  def log_path(domain_name)
326
- File.join(LOG_ROOT, "#{domain_name.to_s.gsub(/[^A-Za-z0-9_-]/, '_')}.jsonl")
332
+ File.join(log_root, "#{domain_name.to_s.gsub(/[^A-Za-z0-9_-]/, '_')}.jsonl")
327
333
  end
328
334
 
329
335
  # Appends one call's outcome to its domain's audit log.
@@ -351,7 +357,7 @@ module Hecks
351
357
  entry = { time: Time.now.utc.iso8601, tool: tool, verb: verb, summary: summary, source: source,
352
358
  role: role, actor_id: actor_id,
353
359
  ok: outcome[:ok], id: outcome[:id], error: outcome[:error], events: outcome[:events] }.compact
354
- FileUtils.mkdir_p(LOG_ROOT)
360
+ FileUtils.mkdir_p(log_root)
355
361
  File.open(log_path(domain_name), "a") { |f| f.puts(JSON.generate(entry)) }
356
362
  rescue StandardError
357
363
  nil
data/lib/hecks/version.rb CHANGED
@@ -12,5 +12,5 @@ module Hecks
12
12
  # it there, or even in the consuming Gemfile, never closed the gap,
13
13
  # because gemspec evaluation happens before anything Bundler
14
14
  # resolves is actually loadable yet.
15
- VERSION = "2.7.0".freeze
15
+ VERSION = "2.8.0".freeze
16
16
  end
@@ -254,6 +254,10 @@ module Hecks
254
254
  {"refusal"=>"InvariantViolation", "site"=>"admits_declared_set", "argument"=>"offered", "shape"=>"scalar", "quoting"=>"none", "separator"=>"", "sorted"=>"false", "when_empty"=>""}.freeze,
255
255
  {"refusal"=>"InvariantViolation", "site"=>"undeclared_set", "argument"=>"name", "shape"=>"scalar", "quoting"=>"none", "separator"=>"", "sorted"=>"false", "when_empty"=>""}.freeze,
256
256
  {"refusal"=>"InvariantViolation", "site"=>"undeclared_set", "argument"=>"admits", "shape"=>"scalar", "quoting"=>"none", "separator"=>"", "sorted"=>"false", "when_empty"=>""}.freeze,
257
+ {"refusal"=>"InvariantViolation", "site"=>"group_by_collision", "argument"=>"read_model", "shape"=>"scalar", "quoting"=>"none", "separator"=>"", "sorted"=>"false", "when_empty"=>""}.freeze,
258
+ {"refusal"=>"InvariantViolation", "site"=>"group_by_collision", "argument"=>"fields", "shape"=>"list", "quoting"=>"none", "separator"=>", ", "sorted"=>"false", "when_empty"=>""}.freeze,
259
+ {"refusal"=>"InvariantViolation", "site"=>"group_by_collision", "argument"=>"ids", "shape"=>"list", "quoting"=>"inspect", "separator"=>", ", "sorted"=>"true", "when_empty"=>""}.freeze,
260
+ {"refusal"=>"InvariantViolation", "site"=>"group_by_collision", "argument"=>"key", "shape"=>"scalar", "quoting"=>"none", "separator"=>"", "sorted"=>"false", "when_empty"=>""}.freeze,
257
261
  {"refusal"=>"Unauthorized", "site"=>"tenant_required", "argument"=>"query", "shape"=>"scalar", "quoting"=>"none", "separator"=>"", "sorted"=>"false", "when_empty"=>""}.freeze,
258
262
  {"refusal"=>"Unauthorized", "site"=>"tenant_required", "argument"=>"field", "shape"=>"scalar", "quoting"=>"none", "separator"=>"", "sorted"=>"false", "when_empty"=>""}.freeze,
259
263
  {"refusal"=>"Unauthorized", "site"=>"role_mismatch", "argument"=>"command", "shape"=>"scalar", "quoting"=>"none", "separator"=>"", "sorted"=>"false", "when_empty"=>""}.freeze,
@@ -314,6 +318,7 @@ module Hecks
314
318
  {"refusal"=>"InvariantViolation", "site"=>"value_object_invariant", "template"=>"{name} invariant violated — {description} (given {offered})"}.freeze,
315
319
  {"refusal"=>"InvariantViolation", "site"=>"admits_declared_set", "template"=>"{name} admits {admits} — {admitted} — got {offered}"}.freeze,
316
320
  {"refusal"=>"InvariantViolation", "site"=>"undeclared_set", "template"=>"{name} admits {admits}, which this chapter does not declare — a closed set is named Aggregate::SetName, and it must be one the bluebook actually holds"}.freeze,
321
+ {"refusal"=>"InvariantViolation", "site"=>"group_by_collision", "template"=>"{read_model} groups by {fields}, but rows {ids} share {key} — a group_by leaf holds one row; add a field that tells them apart"}.freeze,
317
322
  {"refusal"=>"Unauthorized", "site"=>"tenant_required", "template"=>"{query} declares authorize with tenant: {field} — pass {field}: to name which {field} this ask is scoped to"}.freeze,
318
323
  {"refusal"=>"Unauthorized", "site"=>"role_mismatch", "template"=>"{command} refused — role: {role}, and the caller stated {caller_role}"}.freeze,
319
324
  {"refusal"=>"Unauthorized", "site"=>"cross_tenant_reference", "template"=>"{aggregate} {field} is {tenant}, but {attribute} names a {target} whose own {target_field} is {other} — a cross-tenant reference"}.freeze,
data/lib/hecks.rb CHANGED
@@ -32,11 +32,16 @@ require_relative "hecks/facade/cli_door"
32
32
  require_relative "hecks/facade/cli_runner"
33
33
  require_relative "hecks/storehouse"
34
34
  require_relative "hecks/mcp_stdio_guard"
35
+ require_relative "hecks/mcp_door_scope"
35
36
  require_relative "hecks/framework"
36
- require_relative "hecks/corpus"
37
37
  require_relative "hecks/vendoring"
38
38
  require_relative "hecks/embryonaut_bluebook"
39
39
 
40
+ # Repository-only tooling: the corpus table walks `examples/`, `qa/` and
41
+ # `spec/`, which only a checkout has, so it loads on first use and the
42
+ # packaged gem leaves it out (ADR 0066).
43
+ Hecks.autoload(:Corpus, File.expand_path("hecks/corpus", __dir__))
44
+
40
45
  # The root namespace and public facade of the whole DSL/runtime: `Hecks.boot`/
41
46
  # `.boot_files` assemble a running domain from `.bluebook`/`.hecksagon`/
42
47
  # `.world` files, and `.bluebook`/`.hecksagon`/`.port`/`.adapter`/`.world`/
metadata CHANGED
@@ -1,12 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hecks
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.7.0
4
+ version: 2.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Young
8
8
  autorequire:
9
- bindir: bin
9
+ bindir: exe
10
10
  cert_chain: []
11
11
  date: 2026-09-27 00:00:00.000000000 Z
12
12
  dependencies:
@@ -30,10 +30,12 @@ description: |
30
30
  Nothing is scripted: no handler body, no class you write, no schema
31
31
  you migrate.
32
32
  email:
33
- executables: []
33
+ executables:
34
+ - hecks
34
35
  extensions: []
35
36
  extra_rdoc_files: []
36
37
  files:
38
+ - exe/hecks
37
39
  - lib/hecks.rb
38
40
  - lib/hecks/adapters.rb
39
41
  - lib/hecks/adapters/driven.rb
@@ -94,17 +96,6 @@ files:
94
96
  - lib/hecks/behaviors/ir.rb
95
97
  - lib/hecks/behaviors/rspec.rb
96
98
  - lib/hecks/behaviors/runner.rb
97
- - lib/hecks/bench.rb
98
- - lib/hecks/bench/cli.rb
99
- - lib/hecks/bench/environment.rb
100
- - lib/hecks/bench/postgres_probe.rb
101
- - lib/hecks/bench/report.rb
102
- - lib/hecks/bench/ruby_runner.rb
103
- - lib/hecks/bench/run.rb
104
- - lib/hecks/bench/rust_runner.rb
105
- - lib/hecks/bench/stats.rb
106
- - lib/hecks/bench/suite.rb
107
- - lib/hecks/bench/workload.rb
108
99
  - lib/hecks/bluebook.rb
109
100
  - lib/hecks/bluebook/aggregate.rb
110
101
  - lib/hecks/bluebook/assembly.rb
@@ -199,13 +190,22 @@ files:
199
190
  - lib/hecks/bluebook/synthesizer.rb
200
191
  - lib/hecks/bluebook/translation.rb
201
192
  - lib/hecks/bluebook/value_object.rb
202
- - lib/hecks/codemod.rb
193
+ - lib/hecks/cache_dir.rb
194
+ - lib/hecks/cli.rb
195
+ - lib/hecks/cli/document.rb
196
+ - lib/hecks/cli/ir.rb
197
+ - lib/hecks/cli/mcp.rb
198
+ - lib/hecks/cli/mcp_door.rb
199
+ - lib/hecks/cli/model_check.rb
200
+ - lib/hecks/cli/project_cli.rb
201
+ - lib/hecks/cli/project_diagrams.rb
202
+ - lib/hecks/cli/run.rb
203
+ - lib/hecks/cli/smoke_test.rb
204
+ - lib/hecks/cli/stores.rb
203
205
  - lib/hecks/construct.rb
204
- - lib/hecks/corpus.rb
205
206
  - lib/hecks/deploy/bluebook/deploy.bluebook
206
207
  - lib/hecks/deploy/bluebook/deploy.hecksagon
207
208
  - lib/hecks/deploy/oidc.json
208
- - lib/hecks/doc/reference.rb
209
209
  - lib/hecks/embryonaut_bluebook.rb
210
210
  - lib/hecks/embryonaut_bluebook/lock.rb
211
211
  - lib/hecks/embryonaut_bluebook/shape.rb
@@ -246,47 +246,7 @@ files:
246
246
  - lib/hecks/framework/bluebook/privacy.bluebook
247
247
  - lib/hecks/framework/oidc.json
248
248
  - lib/hecks/freezer.rb
249
- - lib/hecks/fuzzing.rb
250
- - lib/hecks/fuzzing/bounded_exhaustive_expressions.rb
251
- - lib/hecks/fuzzing/combination_miner.rb
252
- - lib/hecks/fuzzing/concurrent_dispatch.rb
253
- - lib/hecks/fuzzing/coverage_campaign.rb
254
- - lib/hecks/fuzzing/differential.rb
255
- - lib/hecks/fuzzing/domain_generator.rb
256
- - lib/hecks/fuzzing/era_boundary.rb
257
- - lib/hecks/fuzzing/form_census.rb
258
- - lib/hecks/fuzzing/generated_domain_check.rb
259
- - lib/hecks/fuzzing/invalid_value_generator.rb
260
- - lib/hecks/fuzzing/isolated_boot.rb
261
- - lib/hecks/fuzzing/nondeterministic.rb
262
- - lib/hecks/fuzzing/persistence_parity.rb
263
- - lib/hecks/fuzzing/properties.rb
264
- - lib/hecks/fuzzing/properties/corrections.rb
265
- - lib/hecks/fuzzing/properties/dispatch_and_mutations.rb
266
- - lib/hecks/fuzzing/properties/guards.rb
267
- - lib/hecks/fuzzing/properties/invariants_and_aggregation.rb
268
- - lib/hecks/fuzzing/properties/lifecycle_and_replay.rb
269
- - lib/hecks/fuzzing/properties/outbox.rb
270
- - lib/hecks/fuzzing/properties/querying.rb
271
- - lib/hecks/fuzzing/qa_settings.rb
272
- - lib/hecks/fuzzing/replay.rb
273
- - lib/hecks/fuzzing/rotation_priority.rb
274
- - lib/hecks/fuzzing/rust_gap_manifest.rb
275
- - lib/hecks/fuzzing/self_consistency.rb
276
- - lib/hecks/fuzzing/sequence_generator.rb
277
- - lib/hecks/fuzzing/sequence_generator/adversary.rb
278
- - lib/hecks/fuzzing/sequence_generator/catalog.rb
279
- - lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb
280
- - lib/hecks/fuzzing/sequence_generator/picker.rb
281
- - lib/hecks/fuzzing/sequence_generator/query_binding.rb
282
- - lib/hecks/fuzzing/sequence_generator/step_builder.rb
283
- - lib/hecks/fuzzing/shrinker.rb
284
- - lib/hecks/fuzzing/structural_skips.rb
285
- - lib/hecks/fuzzing/sweep_depth.rb
286
- - lib/hecks/fuzzing/target_capabilities.rb
287
- - lib/hecks/fuzzing/value_generator.rb
288
249
  - lib/hecks/grammar.rb
289
- - lib/hecks/grammar/evolve.rb
290
250
  - lib/hecks/grammar/expression.bluebook
291
251
  - lib/hecks/grammar/expression_operators.json
292
252
  - lib/hecks/grammar/grammar.hecksagon
@@ -318,6 +278,7 @@ files:
318
278
  - lib/hecks/language/world/wiring.bluebook
319
279
  - lib/hecks/language/world/world.bluebook
320
280
  - lib/hecks/literal.rb
281
+ - lib/hecks/mcp_door_scope.rb
321
282
  - lib/hecks/mcp_stdio_guard.rb
322
283
  - lib/hecks/naming.rb
323
284
  - lib/hecks/ports.rb
@@ -460,7 +421,6 @@ files:
460
421
  - lib/hecks/projector/ir_projector.rb
461
422
  - lib/hecks/projector/narrate_projector.rb
462
423
  - lib/hecks/projector/target.rb
463
- - lib/hecks/query_ir.rb
464
424
  - lib/hecks/query_specification.rb
465
425
  - lib/hecks/query_specification/common/authorization_spec.rb
466
426
  - lib/hecks/query_specification/common/comparators.rb
@@ -481,6 +441,16 @@ files:
481
441
  - lib/hecks/query_specification/read_model/specification.rb
482
442
  - lib/hecks/release/gem_pin.rb
483
443
  - lib/hecks/release/gem_pin/published_versions.rb
444
+ - lib/hecks/release/runner.rb
445
+ - lib/hecks/release/runner/ci_publisher.rb
446
+ - lib/hecks/release/runner/commands.rb
447
+ - lib/hecks/release/runner/console.rb
448
+ - lib/hecks/release/runner/gem_publisher.rb
449
+ - lib/hecks/release/runner/git.rb
450
+ - lib/hecks/release/runner/npm_publisher.rb
451
+ - lib/hecks/release/runner/preflight.rb
452
+ - lib/hecks/release/runner/published.rb
453
+ - lib/hecks/release/runner/tagger.rb
484
454
  - lib/hecks/rendering.rb
485
455
  - lib/hecks/router.rb
486
456
  - lib/hecks/router/namespace_installer.rb
@@ -1,74 +0,0 @@
1
- require "optparse"
2
- require_relative "suite"
3
- require_relative "report"
4
-
5
- module Hecks
6
- module Bench
7
- # The command line behind `bin/bench`: parses flags, runs the `Suite`, prints the report.
8
- #
9
- # Kept in `lib/` so a spec can drive it with a tiny configuration and read what it
10
- # prints, rather than shelling out to a script.
11
- module CLI
12
- # The defaults `bin/bench` runs with when no flag says otherwise.
13
- DEFAULTS = { domains: %w[pizzas banking], targets: Suite::TARGETS, warmup: 200, iterations: 1000,
14
- runs: 3 }.freeze
15
-
16
- module_function
17
-
18
- # Runs the benchmark described by the command line and prints its report.
19
- #
20
- # @param argv [Array<String>] the command-line arguments
21
- # @param out [#puts] where the report goes
22
- # @param err [#puts] where progress and usage errors go
23
- # @return [Integer] the process exit status: 0 on success, 2 for a usage error
24
- def run(argv, out: $stdout, err: $stderr)
25
- options = parse(argv)
26
- result = Suite.call(options.fetch(:config), log: err)
27
- out.puts(options[:format] == "json" ? Report.json(result) : Report.markdown(result))
28
- File.write(options[:output], Report.json(result)) if options[:output]
29
- 0
30
- rescue OptionParser::ParseError, ArgumentError => e
31
- err.puts "bin/bench: #{e.message}"
32
- 2
33
- end
34
-
35
- # Turns command-line arguments into a configuration and output choices.
36
- #
37
- # @param argv [Array<String>] the command-line arguments
38
- # @return [Hash{Symbol => Object}] `:config` (a `Suite::Config`), `:format` and `:output`
39
- # @raise [OptionParser::ParseError] on an unknown or malformed flag
40
- def parse(argv)
41
- values = DEFAULTS.dup
42
- extras = { format: "markdown", output: nil, rust_binary: nil }
43
- parser(values, extras).parse(argv.dup)
44
- { config: Suite::Config.new(**values, rust_binary: extras[:rust_binary]),
45
- format: extras[:format], output: extras[:output] }
46
- end
47
-
48
- # Builds the flag parser.
49
- #
50
- # @param values [Hash{Symbol => Object}] the `Suite::Config` fields, filled in as flags parse
51
- # @param extras [Hash{Symbol => Object}] the output choices, filled in as flags parse
52
- # @return [OptionParser] the parser
53
- def parser(values, extras)
54
- OptionParser.new do |opts|
55
- opts.banner = "usage: bin/bench [options]"
56
- opts.on("--domain NAMES", Array, "pizzas, banking (default: both)") { |v| values[:domains] = v }
57
- opts.on("--targets NAMES", Array, "#{Suite::TARGETS.join(', ')} (default: all)") { |v| values[:targets] = v }
58
- opts.on("--iterations N", Integer, "timed cycles per run (default #{DEFAULTS[:iterations]})") do |v|
59
- values[:iterations] = v
60
- end
61
- opts.on("--warmup N", Integer, "discarded cycles before timing (default #{DEFAULTS[:warmup]})") do |v|
62
- values[:warmup] = v
63
- end
64
- opts.on("--runs N", Integer, "fresh boots per target, median reported (default #{DEFAULTS[:runs]})") do |v|
65
- values[:runs] = v
66
- end
67
- opts.on("--rust-binary PATH", "use this `rust` binary instead of building one") { |v| extras[:rust_binary] = v }
68
- opts.on("--format FORMAT", %w[markdown json], "markdown (default) or json") { |v| extras[:format] = v }
69
- opts.on("--output PATH", "also write the full JSON result here") { |v| extras[:output] = v }
70
- end
71
- end
72
- end
73
- end
74
- end
@@ -1,100 +0,0 @@
1
- require "etc"
2
- require "open3"
3
-
4
- module Hecks
5
- module Bench
6
- # A description of the machine and toolchain a benchmark ran on.
7
- #
8
- # A number without its hardware is not reproducible, so every report carries
9
- # this. Each probe is best effort: a missing tool becomes `"unknown"` rather
10
- # than an error, since the benchmark itself is still valid without it.
11
- module Environment
12
- module_function
13
-
14
- # Collects the machine and toolchain facts a report should carry.
15
- #
16
- # @return [Hash{Symbol => Object}] `:cpu`, `:cores`, `:memory_gib`, `:os`, `:ruby`,
17
- # `:yjit`, `:rustc`, `:cargo`, `:hecks`, `:commit` and `:load_average`
18
- def describe
19
- {
20
- cpu: cpu, cores: Etc.nprocessors, memory_gib: memory_gib, os: os,
21
- ruby: RUBY_DESCRIPTION, yjit: yjit?,
22
- rustc: capture("rustc", "-V"), cargo: capture("cargo", "-V"),
23
- hecks: Hecks::VERSION, commit: commit, load_average: load_average
24
- }
25
- end
26
-
27
- # Reads the one-minute load average, the quickest sign that something else was
28
- # busy while a benchmark ran. `Suite.call` reads it before and after and reports both.
29
- #
30
- # @return [Float, nil] the one-minute load average, or nil when it cannot be read
31
- def load_average
32
- text = RUBY_PLATFORM.include?("darwin") ? capture("sysctl", "-n", "vm.loadavg").delete("{}") : File.read("/proc/loadavg")
33
- text.split.first.to_f.round(2)
34
- rescue SystemCallError
35
- nil
36
- end
37
-
38
- # Names the processor.
39
- #
40
- # @return [String] the CPU model, or `"unknown"`
41
- def cpu
42
- return capture("sysctl", "-n", "machdep.cpu.brand_string") if RUBY_PLATFORM.include?("darwin")
43
-
44
- model = File.foreach("/proc/cpuinfo").find { |line| line.start_with?("model name") }
45
- model ? model.split(":", 2).last.strip : "unknown"
46
- rescue SystemCallError
47
- "unknown"
48
- end
49
-
50
- # Reads installed memory.
51
- #
52
- # @return [Float, String] physical memory in GiB, or `"unknown"`
53
- def memory_gib
54
- bytes =
55
- if RUBY_PLATFORM.include?("darwin")
56
- capture("sysctl", "-n", "hw.memsize").to_i
57
- else
58
- File.read("/proc/meminfo")[/MemTotal:\s+(\d+)/, 1].to_i * 1024
59
- end
60
- bytes.zero? ? "unknown" : (bytes / (1024.0**3)).round(1)
61
- rescue SystemCallError
62
- "unknown"
63
- end
64
-
65
- # Names the operating system.
66
- #
67
- # @return [String] the OS name and version, or `"unknown"`
68
- def os
69
- return "macOS #{capture('sw_vers', '-productVersion')} (#{capture('uname', '-m')})" if RUBY_PLATFORM.include?("darwin")
70
-
71
- capture("uname", "-sr")
72
- end
73
-
74
- # Says whether Ruby's `YJIT` compiler was running.
75
- #
76
- # @return [Boolean] true when `YJIT` is enabled in this process
77
- def yjit?
78
- defined?(RubyVM::YJIT) ? RubyVM::YJIT.enabled? : false
79
- end
80
-
81
- # Names the Hecks source revision under test.
82
- #
83
- # @return [String] the short commit hash, or `"unknown"` outside a git checkout
84
- def commit
85
- capture("git", "-C", File.expand_path("../../..", __dir__), "rev-parse", "--short", "HEAD")
86
- end
87
-
88
- # Runs a command and returns its first line of output.
89
- #
90
- # @param command [Array<String>] the program and its arguments
91
- # @return [String] the trimmed first line of stdout, or `"unknown"` if it cannot run
92
- def capture(*command)
93
- out, status = Open3.capture2(*command, err: File::NULL)
94
- status.success? && !out.strip.empty? ? out.lines.first.strip : "unknown"
95
- rescue SystemCallError
96
- "unknown"
97
- end
98
- end
99
- end
100
- end
@@ -1,41 +0,0 @@
1
- module Hecks
2
- module Bench
3
- # Decides whether the Postgres targets can run at all.
4
- #
5
- # Postgres is optional. A machine with no server, or without the `pg` gem, gets a
6
- # skipped target and a sentence saying why, never a stack trace. Connection
7
- # settings come from libpq's own environment (`PGHOST`, `PGPORT`, `PGUSER`,
8
- # `PGPASSWORD`), the same way the fuzzer's Postgres adapters read them.
9
- module PostgresProbe
10
- module_function
11
-
12
- # Tries to reach the server's maintenance database.
13
- #
14
- # @return [String, nil] nil when a server answered, otherwise one sentence saying why
15
- # the Postgres targets cannot run
16
- def unavailable_reason
17
- require "pg"
18
- PG.connect(dbname: "postgres", connect_timeout: 2).close
19
- nil
20
- rescue LoadError
21
- "the `pg` gem is not installed"
22
- rescue PG::Error => e
23
- "no Postgres server is reachable (#{e.message.lines.first.to_s.strip}); start one, or point " \
24
- "PGHOST, PGPORT and PGUSER at one"
25
- end
26
-
27
- # Reads the server's version for the report's environment block.
28
- #
29
- # @return [String] the `server_version` setting, or `"unknown"` if it cannot be read
30
- def server_version
31
- require "pg"
32
- connection = PG.connect(dbname: "postgres", connect_timeout: 2)
33
- connection.exec("SHOW server_version").getvalue(0, 0)
34
- rescue LoadError, PG::Error
35
- "unknown"
36
- ensure
37
- connection&.close
38
- end
39
- end
40
- end
41
- end