hecks 2.6.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 (216) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +13 -0
  3. data/lib/hecks/adapters/driven/google_authentication.rb +1 -1
  4. data/lib/hecks/adapters/driven/lambda/client.rb +1 -1
  5. data/lib/hecks/adapters/driven/memory.rb +1 -1
  6. data/lib/hecks/adapters/driven/postgres.rb +1 -1
  7. data/lib/hecks/adapters/driven/prism.rb +2 -2
  8. data/lib/hecks/adapters/driven/sqlite/projection.rb +1 -1
  9. data/lib/hecks/adapters/driven/tenant_provisioner.rb +31 -10
  10. data/lib/hecks/behaviors/expectations.rb +2 -2
  11. data/lib/hecks/bluebook/assembly/contract.rb +1 -1
  12. data/lib/hecks/bluebook/assembly/contracts.rb +5 -5
  13. data/lib/hecks/bluebook/assembly/specializer.rb +2 -2
  14. data/lib/hecks/bluebook/assembly.rb +1 -1
  15. data/lib/hecks/bluebook/behaviour/chapter.rb +5 -2
  16. data/lib/hecks/bluebook/behaviour/read_model.rb +18 -0
  17. data/lib/hecks/bluebook/capabilities.rb +2 -2
  18. data/lib/hecks/bluebook/chapter.rb +1 -1
  19. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +2 -2
  20. data/lib/hecks/bluebook/dsl/binding_proxy.rb +6 -1
  21. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +2 -2
  22. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
  23. data/lib/hecks/bluebook/dsl/command_builder.rb +3 -3
  24. data/lib/hecks/bluebook/dsl/entity_builder.rb +1 -1
  25. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +1 -1
  26. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +14 -9
  27. data/lib/hecks/bluebook/dsl/port_operation_builder.rb +1 -1
  28. data/lib/hecks/bluebook/dsl/rule_reference.rb +3 -3
  29. data/lib/hecks/bluebook/dsl/translation_builder.rb +1 -1
  30. data/lib/hecks/bluebook/dsl/world_builder.rb +47 -3
  31. data/lib/hecks/bluebook/hexagon.rb +15 -7
  32. data/lib/hecks/bluebook/meta_validator/judge.rb +3 -3
  33. data/lib/hecks/bluebook/meta_validator/plan.rb +4 -4
  34. data/lib/hecks/bluebook/meta_validator/readings.rb +1 -1
  35. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +2 -2
  36. data/lib/hecks/bluebook/meta_validator/shapes.rb +1 -1
  37. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +11 -10
  38. data/lib/hecks/bluebook/meta_validator/world_judge.rb +3 -1
  39. data/lib/hecks/bluebook/meta_validator.rb +1 -1
  40. data/lib/hecks/bluebook/model_check/client_profile.rb +4 -70
  41. data/lib/hecks/bluebook/model_check.rb +13 -14
  42. data/lib/hecks/cache_dir.rb +103 -0
  43. data/lib/hecks/cli/document.rb +104 -0
  44. data/lib/hecks/cli/ir.rb +47 -0
  45. data/lib/hecks/cli/mcp.rb +32 -0
  46. data/lib/hecks/cli/mcp_door.rb +461 -0
  47. data/lib/hecks/cli/model_check.rb +270 -0
  48. data/lib/hecks/cli/project_cli.rb +123 -0
  49. data/lib/hecks/cli/project_diagrams.rb +43 -0
  50. data/lib/hecks/cli/run.rb +219 -0
  51. data/lib/hecks/cli/smoke_test.rb +59 -0
  52. data/lib/hecks/cli/stores.rb +54 -0
  53. data/lib/hecks/cli.rb +166 -0
  54. data/lib/hecks/embryonaut_bluebook/lock.rb +72 -0
  55. data/lib/hecks/embryonaut_bluebook/shape.rb +47 -0
  56. data/lib/hecks/embryonaut_bluebook/vendor.rb +194 -0
  57. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +88 -0
  58. data/lib/hecks/embryonaut_bluebook.rb +29 -3
  59. data/lib/hecks/facade/handle.rb +2 -2
  60. data/lib/hecks/facade/surface/aggregate_door.rb +1 -1
  61. data/lib/hecks/forms/value_object_shape.rb +1 -1
  62. data/lib/hecks/framework/bluebook/console_settings.bluebook +3 -3
  63. data/lib/hecks/language/bluebook/vocabulary.bluebook +16 -0
  64. data/lib/hecks/language/world/world.bluebook +32 -2
  65. data/lib/hecks/mcp_door_scope.rb +176 -0
  66. data/lib/hecks/ports/key_vault.rb +1 -1
  67. data/lib/hecks/ports/persistence/append_only.rb +1 -1
  68. data/lib/hecks/ports/persistence/binding_policy.rb +16 -5
  69. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +3 -2
  70. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +2 -2
  71. data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +2 -2
  72. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +132 -0
  73. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +1 -1
  74. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +2 -2
  75. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +1 -1
  76. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +1 -1
  77. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +13 -7
  78. data/lib/hecks/ports/persistence/plugins/era.rb +7 -4
  79. data/lib/hecks/ports/persistence/postgres_dump/connection.rb +73 -0
  80. data/lib/hecks/ports/persistence/postgres_dump.rb +125 -0
  81. data/lib/hecks/ports/persistence/repository_factory.rb +2 -2
  82. data/lib/hecks/ports/persistence/state_codec.rb +1 -1
  83. data/lib/hecks/ports/persistence.rb +4 -0
  84. data/lib/hecks/projections/deploy/fargate/assembly.rb +193 -0
  85. data/lib/hecks/projections/deploy/fargate/cdn.rb +318 -0
  86. data/lib/hecks/projections/deploy/fargate/cdn_default.rb +87 -0
  87. data/lib/hecks/projections/deploy/fargate/check.rb +172 -0
  88. data/lib/hecks/projections/deploy/fargate/containers.rb +412 -0
  89. data/lib/hecks/projections/deploy/fargate/extras.rb +268 -0
  90. data/lib/hecks/projections/deploy/fargate/monitoring.rb +448 -0
  91. data/lib/hecks/projections/deploy/fargate/settings.rb +235 -0
  92. data/lib/hecks/projections/deploy/fargate/yaml.rb +116 -0
  93. data/lib/hecks/projections/deploy/fargate.rb +188 -139
  94. data/lib/hecks/projections/deploy/lambda.rb +8 -8
  95. data/lib/hecks/projections/deploy/preview/access_yaml.rb +145 -0
  96. data/lib/hecks/projections/deploy/preview/containers.rb +218 -0
  97. data/lib/hecks/projections/deploy/preview/preview.sh.erb +446 -0
  98. data/lib/hecks/projections/deploy/preview/routing_yaml.rb +216 -0
  99. data/lib/hecks/projections/deploy/preview/script.rb +40 -0
  100. data/lib/hecks/projections/deploy/preview/settings.rb +140 -0
  101. data/lib/hecks/projections/deploy/preview/task_yaml.rb +215 -0
  102. data/lib/hecks/projections/deploy/preview/template.rb +247 -0
  103. data/lib/hecks/projections/deploy/preview/yaml_text.rb +48 -0
  104. data/lib/hecks/projections/deploy/preview.rb +103 -0
  105. data/lib/hecks/projections/deploy/scripts/deploy-service.sh.tmpl +182 -0
  106. data/lib/hecks/projections/deploy/scripts/expected-era.header +17 -0
  107. data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +54 -0
  108. data/lib/hecks/projections/deploy/scripts/settings.rb +114 -0
  109. data/lib/hecks/projections/deploy/scripts/smoke-after-deploy.sh.tmpl +232 -0
  110. data/lib/hecks/projections/deploy/scripts.rb +171 -0
  111. data/lib/hecks/projections/deploy/smoke/harness.js +366 -0
  112. data/lib/hecks/projections/deploy/smoke/workflow.yml.tmpl +56 -0
  113. data/lib/hecks/projections/deploy/smoke.rb +159 -0
  114. data/lib/hecks/projections/deploy/template_diff/comparer.rb +231 -0
  115. data/lib/hecks/projections/deploy/template_diff/loader.rb +105 -0
  116. data/lib/hecks/projections/deploy/template_diff.rb +173 -0
  117. data/lib/hecks/projections/glossary/sensitivity.rb +1 -1
  118. data/lib/hecks/projections/reference.rb +7 -1
  119. data/lib/hecks/projections.rb +2 -0
  120. data/lib/hecks/projector/exporter.rb +4 -4
  121. data/lib/hecks/projector/target.rb +1 -1
  122. data/lib/hecks/release/gem_pin/published_versions.rb +58 -0
  123. data/lib/hecks/release/gem_pin.rb +112 -0
  124. data/lib/hecks/release/runner/ci_publisher.rb +96 -0
  125. data/lib/hecks/release/runner/commands.rb +68 -0
  126. data/lib/hecks/release/runner/console.rb +52 -0
  127. data/lib/hecks/release/runner/gem_publisher.rb +44 -0
  128. data/lib/hecks/release/runner/git.rb +48 -0
  129. data/lib/hecks/release/runner/npm_publisher.rb +89 -0
  130. data/lib/hecks/release/runner/preflight.rb +119 -0
  131. data/lib/hecks/release/runner/published.rb +55 -0
  132. data/lib/hecks/release/runner/tagger.rb +87 -0
  133. data/lib/hecks/release/runner.rb +244 -0
  134. data/lib/hecks/router/namespace_installer.rb +1 -1
  135. data/lib/hecks/runtime/command_interpreter.rb +3 -3
  136. data/lib/hecks/runtime/command_rules/admissibility.rb +2 -2
  137. data/lib/hecks/runtime/command_rules/arithmetic.rb +2 -2
  138. data/lib/hecks/runtime/entity_element.rb +1 -1
  139. data/lib/hecks/runtime/entity_interpreter.rb +1 -1
  140. data/lib/hecks/runtime/invocation.rb +1 -1
  141. data/lib/hecks/runtime/loader.rb +27 -3
  142. data/lib/hecks/runtime/outbox.rb +1 -1
  143. data/lib/hecks/runtime/reaction_invocation.rb +3 -3
  144. data/lib/hecks/runtime/read_model_interpreter.rb +37 -15
  145. data/lib/hecks/runtime/reference_hop.rb +4 -4
  146. data/lib/hecks/runtime/registry/saga_persistence.rb +8 -2
  147. data/lib/hecks/runtime/registry/verification.rb +15 -13
  148. data/lib/hecks/runtime/registry/world_defaults.rb +106 -0
  149. data/lib/hecks/runtime/registry.rb +22 -13
  150. data/lib/hecks/runtime/saga_interpreter.rb +1 -1
  151. data/lib/hecks/runtime/tenant_check.rb +1 -1
  152. data/lib/hecks/runtime/value/coercion.rb +3 -3
  153. data/lib/hecks/runtime/value/entity_list_coercion.rb +1 -1
  154. data/lib/hecks/storehouse.rb +18 -12
  155. data/lib/hecks/vendoring/git_environment.rb +35 -0
  156. data/lib/hecks/vendoring/git_source.rb +102 -0
  157. data/lib/hecks/vendoring.rb +102 -0
  158. data/lib/hecks/version.rb +1 -1
  159. data/lib/hecks/vocabulary.rb +5 -0
  160. data/lib/hecks.rb +8 -2
  161. metadata +72 -58
  162. data/lib/hecks/bench/cli.rb +0 -74
  163. data/lib/hecks/bench/environment.rb +0 -100
  164. data/lib/hecks/bench/postgres_probe.rb +0 -41
  165. data/lib/hecks/bench/report.rb +0 -116
  166. data/lib/hecks/bench/ruby_runner.rb +0 -151
  167. data/lib/hecks/bench/run.rb +0 -55
  168. data/lib/hecks/bench/rust_runner.rb +0 -165
  169. data/lib/hecks/bench/stats.rb +0 -75
  170. data/lib/hecks/bench/suite.rb +0 -172
  171. data/lib/hecks/bench/workload.rb +0 -137
  172. data/lib/hecks/bench.rb +0 -28
  173. data/lib/hecks/codemod.rb +0 -443
  174. data/lib/hecks/corpus.rb +0 -479
  175. data/lib/hecks/doc/reference.rb +0 -585
  176. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -684
  177. data/lib/hecks/fuzzing/combination_miner.rb +0 -178
  178. data/lib/hecks/fuzzing/concurrent_dispatch.rb +0 -469
  179. data/lib/hecks/fuzzing/coverage_campaign.rb +0 -161
  180. data/lib/hecks/fuzzing/differential.rb +0 -192
  181. data/lib/hecks/fuzzing/domain_generator.rb +0 -871
  182. data/lib/hecks/fuzzing/era_boundary.rb +0 -154
  183. data/lib/hecks/fuzzing/form_census.rb +0 -285
  184. data/lib/hecks/fuzzing/generated_domain_check.rb +0 -171
  185. data/lib/hecks/fuzzing/invalid_value_generator.rb +0 -125
  186. data/lib/hecks/fuzzing/isolated_boot.rb +0 -604
  187. data/lib/hecks/fuzzing/nondeterministic.rb +0 -79
  188. data/lib/hecks/fuzzing/persistence_parity.rb +0 -253
  189. data/lib/hecks/fuzzing/properties/corrections.rb +0 -125
  190. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +0 -688
  191. data/lib/hecks/fuzzing/properties/guards.rb +0 -398
  192. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +0 -399
  193. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +0 -137
  194. data/lib/hecks/fuzzing/properties/outbox.rb +0 -180
  195. data/lib/hecks/fuzzing/properties/querying.rb +0 -260
  196. data/lib/hecks/fuzzing/properties.rb +0 -267
  197. data/lib/hecks/fuzzing/qa_settings.rb +0 -164
  198. data/lib/hecks/fuzzing/replay.rb +0 -930
  199. data/lib/hecks/fuzzing/rotation_priority.rb +0 -114
  200. data/lib/hecks/fuzzing/rust_gap_manifest.rb +0 -139
  201. data/lib/hecks/fuzzing/self_consistency.rb +0 -825
  202. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +0 -532
  203. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +0 -171
  204. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +0 -127
  205. data/lib/hecks/fuzzing/sequence_generator/picker.rb +0 -116
  206. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +0 -138
  207. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +0 -344
  208. data/lib/hecks/fuzzing/sequence_generator.rb +0 -311
  209. data/lib/hecks/fuzzing/shrinker.rb +0 -309
  210. data/lib/hecks/fuzzing/structural_skips.rb +0 -53
  211. data/lib/hecks/fuzzing/sweep_depth.rb +0 -61
  212. data/lib/hecks/fuzzing/target_capabilities.rb +0 -255
  213. data/lib/hecks/fuzzing/value_generator.rb +0 -340
  214. data/lib/hecks/fuzzing.rb +0 -23
  215. data/lib/hecks/grammar/evolve.rb +0 -524
  216. data/lib/hecks/query_ir.rb +0 -482
@@ -1,340 +0,0 @@
1
- require_relative "../runtime/value"
2
-
3
- module Hecks
4
- module Fuzzing
5
- # One attribute, one value — in the exact JSON shape the hand-written
6
- # corpus already uses (a value-object-typed attribute is a nested hash keyed
7
- # by its own field names ; a reference is the bare ID of the head it points
8
- # at, because that is what a reference is). Everything
9
- # returned is a plain, JSON-safe Ruby value : String keys throughout, never
10
- # symbols, so a generated step can be dumped straight to JSON.
11
- #
12
- # Biased toward the edge cases a hand-written corpus tends to under-sample —
13
- # empty strings, quotes, commas, unicode, zero, negative numbers — because
14
- # `marks.rb` names literal-encoding loss "the largest family of bug in this
15
- # codebase," and a fixed corpus of a few dozen examples rarely happens to
16
- # exercise the boundary that actually breaks. But mostly not : an attribute
17
- # named `email`/`currency` almost always needs a specific shape just to get
18
- # past its own invariant (`address.include?("@")`, `currency.size == 3`) —
19
- # banking's very first fuzz run never got past Customer.Register, because
20
- # no plain random word ever contains "@". Reaching deep state matters more
21
- # than edge-casing every single field every single time, so the "normal"
22
- # path is now name-aware for the couple of shapes that are genuinely
23
- # common across domains, and edge cases stay real but less frequent.
24
- module ValueGenerator
25
- module_function
26
-
27
- EDGE_CASE_PROBABILITY = 0.2
28
- INVALID_MEMBER_PROBABILITY = 0.1
29
- INVALID_REFERENCE_PROBABILITY = 0.2
30
- # `Value.for_attribute` → `fields_for`'s own bare-scalar branch
31
- # (lib/hecks/runtime/value/coercion.rb) has accepted a bare
32
- # `"large"` in place of `{"value" => "large"}` for any single-field
33
- # value object since 86727afd — but until this generator actually
34
- # produces that shape, nothing exercises it: neither the adapter-
35
- # agreement gate nor the Rust/WASM `from_json` codegen (rust/project/
36
- # json_codec.rb) can ever be caught drifting on a shape they're
37
- # never handed.
38
- BARE_SCALAR_PROBABILITY = 0.2
39
-
40
- STRING_EDGE_CASES = [
41
- "", "x", "with \"quotes\"", "with, a comma", "with a \\backslash",
42
- "unicode héllo wörld 🎉", "x" * 200, " leading and trailing "
43
- ].freeze
44
- # PRD 05 (numeric-boundary-coverage) — Bignum (`2**100`, past i64's
45
- # own ceiling, which Ruby's own `Integer` has no such ceiling for —
46
- # `rust/src/kernel/json.rs`'s own `integral_i64` doc comment names
47
- # exactly this: a Rust kernel value cannot represent it, so this
48
- # exercises a real cross-runtime capability gap, not a Ruby-only
49
- # edge) and its negative twin. Both round-trip through Ruby's own
50
- # arithmetic/JSON cleanly (confirmed directly: `(2**100).clamp(...)`
51
- # and `JSON.generate(2**100)` both just work — Integer has no
52
- # ceiling here), so nothing in this runtime needed a fix for these;
53
- # they're included so a real generated sequence occasionally
54
- # produces the value at all, since nothing had, repo-wide, before.
55
- INTEGER_EDGE_CASES = [0, -1, 2_147_483_647, -2_147_483_648, 2**100, -(2**100)].freeze
56
- # BUG#35 (QualityControl QA ledger, `lease-clock-json-precision`) —
57
- # a Bignum edge case (`2**100`, above) landing on an Integer-typed
58
- # clock or count reading fires the already-catalogued `Json::Num`/
59
- # f64 precision-loss class (`rust/src/kernel/json.rs`'s
60
- # `parse_number` parses every number through `s.parse::<f64>()`
61
- # before any target-type conversion runs, and `Json::Num` is a
62
- # plain `f64` end to end — see that file's own header) on a new
63
- # site every time a new clock/count-shaped field is authored,
64
- # without adding any new coverage: both engines already refuse
65
- # (`TypeMismatch`, out of `i64` range either way) for this shape,
66
- # `tenant_ledger`'s own NOTES.md already logged the identical class
67
- # on a stored money attribute, and `spec/support/
68
- # rust_conformance_helpers.rb`'s own `reduce_to_wire_precision`
69
- # already documents and normalizes past the wire format's own loss
70
- # for a query's echoed args. An exact-integer Rust JSON
71
- # deserialization path was considered and rejected as this
72
- # generator's own fix instead: `Json::Num(f64)` is pattern-matched
73
- # throughout `rust/src/kernel` (query_comparators.rs,
74
- # query_ordering.rs, read_model.rs, named_query.rs, this kernel's
75
- # own `Fielded` impl, arithmetic overflow checks) and emitted by
76
- # codegen (`rust/project/reactions.rb`, `rust/codegen/src/
77
- # reactions.rs`) — adding a second, exact-integer numeric variant
78
- # would mean auditing and updating every one of those sites, a
79
- # refactor of the JSON layer itself, not a one-domain fix (exactly
80
- # the ledger's own stated concern: "risks affecting every other
81
- # domain's large-integer handling"). So instead, per the ledger's
82
- # own sanctioned fallback: an Integer-typed field whose name reads
83
- # as a clock or a count is capped to a narrower, still-real
84
- # edge-case pool below (still exercises the ordinary i32
85
- # boundaries, just never a value past f64's own 2**53 exact-
86
- # integer ceiling) — every other Integer-typed field (a money
87
- # amount, an identity sequence, anything not clock/count-shaped)
88
- # still draws from the full `INTEGER_EDGE_CASES` pool above,
89
- # unchanged.
90
- SAFE_INTEGER_EDGE_CASES = [0, -1, 2_147_483_647, -2_147_483_648].freeze
91
- # Matched against the value object's own declared name plus the
92
- # attribute's own name (`value_for`'s `"#{context} #{attribute.
93
- # name}"`, e.g. "LeaseInstant value", "RetryCount value") — a
94
- # clock reading ("instant"/"clock"/"expir(es/y)"/"ttl"/"now"/
95
- # "timestamp"/"epoch") or a quantity capped by a policy ("count").
96
- # Deliberately narrow: matches the ledger's own "clock/count"
97
- # wording exactly, not every plausibly-large-looking name (an
98
- # "amount"/"cents"/"sequence" field is not clock/count-shaped and
99
- # keeps the full Bignum edge-case pool).
100
- CLOCK_OR_COUNT_NAME_PATTERN = /clock|instant|expir|ttl|\bnow\b|timestamp|epoch|count/i
101
- # NaN and +/-Infinity — the real find (see `spec/runtime/
102
- # numeric_boundary_spec.rb`): `Value::Coercion#check_numeric_fields`
103
- # type-checks all three (each really is a Float) before they can
104
- # reach either `CommandRules::Arithmetic#clamp` (raw
105
- # `ArgumentError`, not a domain refusal) or `JSON.generate`
106
- # (`JSON::GeneratorError`, also not a domain refusal) — both fixed
107
- # at the source, so these are safe to generate. -0.0 is
108
- # deliberately included too even though it was already safe
109
- # (finite, round-trips through JSON as `-0.0` cleanly) — a signed
110
- # zero is exactly the kind of boundary a hand-written corpus never
111
- # happens to type, and the fuzzer existing to cover it is the
112
- # point.
113
- FLOAT_EDGE_CASES = [0.0, -0.0, -0.5, -100.25, Float::NAN, Float::INFINITY, -Float::INFINITY].freeze
114
- WORDS = %w[alpha bravo charlie delta echo foxtrot golf hotel india juliet].freeze
115
- CURRENCY_CODES = %w[USD EUR GBP JPY].freeze
116
-
117
- # The value for one command/query/value-object attribute. `known_ids` is
118
- # `{aggregate_or_entity_name => [scalar id, ...]}`, supplied by the
119
- # sequence generator — a reference draws a real id from it most of the
120
- # time and a fabricated one sometimes, to exercise NotFound as often as
121
- # the happy path.
122
- # `context` carries the enclosing value object's own declared name down
123
- # into a nested primitive field — `EmailAddress`'s `address` field is
124
- # what actually needs to look email-shaped, but the field itself is
125
- # just named "address"; the VO's own name is where "email" lives. A
126
- # combined hint catches both spellings without needing to guess which
127
- # level a domain happened to name the thing on.
128
- #
129
- # @param attribute [Bluebook::Attribute] the attribute to generate a value for
130
- # @param aggregate [Bluebook::Aggregate] the aggregate `attribute` belongs to;
131
- # resolves a same-chapter identity value object
132
- # @param random [Random] the RNG driving every draw this call makes
133
- # @param known_ids [Hash{String => Array<String>}] known real ids, keyed by
134
- # aggregate or entity name; a reference draws from this pool
135
- # @param context [String, nil] the enclosing value object's own declared name,
136
- # for name-aware primitive generation; nil at the top level
137
- # @return [String, Integer, Float, Boolean, Hash] a JSON-safe value in the corpus's
138
- # own shape: a nested Hash for a value-object-typed attribute, a bare id String
139
- # for a reference, or a plain primitive otherwise
140
- def value_for(attribute, aggregate, random:, known_ids: {}, context: nil)
141
- return reference_value(attribute, random: random, known_ids: known_ids) if attribute.reference?
142
-
143
- # Local first, falling back to a same-chapter identity value object
144
- # declared on a sibling aggregate — the exact resolution
145
- # `Value.value_object_for` already does for a real dispatch
146
- # (runtime/value/coercion.rb). `safe_deposit_boxes.bluebook`'s own
147
- # `attribute :customer, CustomerNumber` is declared on SafeDepositBox
148
- # but CustomerNumber itself lives on Customer; without this fallback
149
- # the generator raised "does not know primitive type" for any such
150
- # cross-aggregate identity attribute rather than reusing the same
151
- # lookup the runtime relies on.
152
- value_object = Runtime::Value.value_object_for(aggregate, attribute.type.to_s)
153
- return object_for(value_object, aggregate, random: random, known_ids: known_ids) if value_object
154
-
155
- primitive(attribute.type.to_s, random: random, name: "#{context} #{attribute.name}")
156
- end
157
-
158
- # Generates a nested value for a whole value object: a random admitted
159
- # row for a closed set, or a value per declared attribute otherwise —
160
- # occasionally unwrapped to a bare scalar for a genuinely single-field
161
- # value object.
162
- #
163
- # @param value_object [Bluebook::ValueObject] the value object to generate a value for
164
- # @param aggregate [Bluebook::Aggregate] the aggregate `value_object` is reached
165
- # from, forwarded to nested `value_for` calls
166
- # @param random [Random] the RNG driving every draw this call makes
167
- # @param known_ids [Hash{String => Array<String>}] known real ids, keyed by
168
- # aggregate or entity name, forwarded to nested `value_for` calls
169
- # @return [Hash, Object] `{field name => value, ...}` for each declared attribute
170
- # or closed-set member; unwrapped to that lone value directly when the value
171
- # object has exactly one field and the bare-scalar draw hits
172
- def object_for(value_object, aggregate, random:, known_ids:)
173
- fields =
174
- if value_object.closed_set? && !value_object.members.empty?
175
- return invalid_member(value_object, random: random) if random.rand < INVALID_MEMBER_PROBABILITY
176
-
177
- member = value_object.members.sample(random: random)
178
- member.to_h { |field, value| [field.to_s, value] }
179
- else
180
- value_object.attributes.to_h do |field|
181
- [field.name.to_s,
182
- value_for(field, aggregate, random: random, known_ids: known_ids, context: value_object.hecks_name)]
183
- end
184
- end
185
-
186
- # `fields.size == 1` — the same test `Behaviour::ValueObject#
187
- # sole_attribute` names: a genuinely single-field value object,
188
- # not merely "this particular closed-set member happened to pick
189
- # one field." Unwrapping only here, not in `invalid_member`
190
- # above — a deliberately-wrong combination stays a Hash so its
191
- # own wrongness is what gets exercised, not a second, unrelated
192
- # shape question.
193
- return fields.values.first if fields.size == 1 && random.rand < BARE_SCALAR_PROBABILITY
194
-
195
- fields
196
- end
197
-
198
- # A combination that (almost certainly) isn't one of the closed set's
199
- # admitted rows — deliberately, to exercise the refusal a `one_of`
200
- # exists to enforce, not just its happy path.
201
- #
202
- # @param value_object [Bluebook::ValueObject] the closed-set value object to
203
- # generate a non-admitted combination for
204
- # @param random [Random] the RNG driving every draw this call makes
205
- # @return [Hash] `{field name => value, ...}` for every declared attribute, each
206
- # drawn independently rather than sampled from an admitted row
207
- def invalid_member(value_object, random:)
208
- value_object.attributes.to_h do |field|
209
- [field.name.to_s, primitive(field.type.to_s, random: random, name: field.name.to_s)]
210
- end
211
- end
212
-
213
- # The ID itself, unwrapped: the payload gate refuses a reference stored
214
- # wrapped as `{"value" => id}`, so a fuzzer emitting that shape would
215
- # have every generated reference refused and the silent guard would
216
- # report the fuzzer broken rather than the runtime.
217
- #
218
- # @param attribute [Bluebook::Attribute] the reference-typed attribute to
219
- # generate a value for
220
- # @param random [Random] the RNG driving every draw this call makes
221
- # @param known_ids [Hash{String => Array<String>}] known real ids, keyed by
222
- # aggregate or entity name; looked up under `attribute.type.target_name`
223
- # @return [String] a real id drawn from the matching pool most of the time; a
224
- # fabricated `"missing-..."` id when the pool is empty or the invalid-reference
225
- # draw hits
226
- def reference_value(attribute, random:, known_ids:)
227
- pool = known_ids[attribute.type.target_name.to_s] || []
228
- return "missing-#{random.bytes(4).unpack1('H*')}" if pool.empty? || random.rand < INVALID_REFERENCE_PROBABILITY
229
-
230
- pool.sample(random: random)
231
- end
232
-
233
- # Generates a value for one Ruby-primitive-typed attribute.
234
- #
235
- # @param type_name [String] the primitive type name: `"String"`, `"Integer"`,
236
- # `"Float"`, `"TrueClass"`, or `"FalseClass"`
237
- # @param random [Random] the RNG driving every draw this call makes
238
- # @param name [String, nil] a name hint (attribute name, optionally
239
- # context-prefixed) that draws an email-/currency-shaped string or a
240
- # clock/count-shaped integer
241
- # @return [String, Integer, Float, Boolean] a value of the type `type_name` names
242
- # @raise [ArgumentError] if `type_name` names anything else
243
- def primitive(type_name, random:, name: nil)
244
- case type_name
245
- when "String" then string_value(random, name: name)
246
- when "Integer" then integer_value(random, name: name)
247
- when "Float" then float_value(random)
248
- when "TrueClass", "FalseClass" then random.rand(2).zero?
249
- else raise ArgumentError, "ValueGenerator does not know primitive type #{type_name.inspect}"
250
- end
251
- end
252
-
253
- # Generates a String value, name-aware for the couple of shapes that
254
- # need to look a specific way to get past their own invariant.
255
- #
256
- # @param random [Random] the RNG driving every draw this call makes
257
- # @param name [String, nil] a name hint; an `"email"` or `"currency"` match
258
- # draws a shaped value instead of random words
259
- # @return [String] an edge-case string, an email address, a currency code, or
260
- # 1-3 random words joined by spaces
261
- def string_value(random, name: nil)
262
- return STRING_EDGE_CASES.sample(random: random) if random.rand < EDGE_CASE_PROBABILITY
263
- return email_value(random) if name&.match?(/email/i)
264
- return CURRENCY_CODES.sample(random: random) if name&.match?(/currency/i)
265
-
266
- Array.new(random.rand(1..3)) { WORDS.sample(random: random) }.join(" ")
267
- end
268
-
269
- # Generates a fabricated, syntactically valid email address.
270
- #
271
- # @param random [Random] the RNG driving every draw this call makes
272
- # @return [String] a `"word@word.example"` address
273
- def email_value(random)
274
- "#{WORDS.sample(random: random)}@#{WORDS.sample(random: random)}.example"
275
- end
276
-
277
- # Generates an Integer value, skewed positive.
278
- #
279
- # Skewed positive : `cents.positive?`/`!cents.negative?`-style
280
- # invariants are common across this codebase's example domains, and a
281
- # sequence that can never get past one never reaches the state a deeper
282
- # bug would need. Zero and negative are still real, reachable outcomes —
283
- # via the edge-case pool, deliberately, not by starving them entirely.
284
- #
285
- # @param random [Random] the RNG driving every draw this call makes
286
- # @param name [String, nil] a name hint; a clock/count-shaped match narrows
287
- # the edge-case pool to `SAFE_INTEGER_EDGE_CASES`
288
- # @return [Integer] an edge-case integer sometimes, else a random count in 1..1000
289
- def integer_value(random, name: nil)
290
- if random.rand < EDGE_CASE_PROBABILITY
291
- pool = clock_or_count_shaped?(name) ? SAFE_INTEGER_EDGE_CASES : INTEGER_EDGE_CASES
292
- return pool.sample(random: random)
293
- end
294
-
295
- random.rand(1..1000)
296
- end
297
-
298
- # Reports whether a field name reads as a clock reading or a
299
- # policy-capped count, per `CLOCK_OR_COUNT_NAME_PATTERN`'s own comment.
300
- #
301
- # @param name [String, nil] the name to test
302
- # @return [Boolean] true if `name` matches the clock/count name pattern
303
- def clock_or_count_shaped?(name)
304
- name.to_s.match?(CLOCK_OR_COUNT_NAME_PATTERN)
305
- end
306
-
307
- # Generates a Float value.
308
- #
309
- # @param random [Random] the RNG driving every draw this call makes
310
- # @return [Float] an edge-case float sometimes, else a random value in
311
- # 0.01..1000.0, rounded to 2 decimal places
312
- def float_value(random)
313
- return FLOAT_EDGE_CASES.sample(random: random) if random.rand < EDGE_CASE_PROBABILITY
314
-
315
- random.rand(0.01..1000.0).round(2)
316
- end
317
-
318
- # The bare scalar a generated identity value stands for, for recording into
319
- # `known_ids`. An identity is declared as a value object, so this opens one ;
320
- # a reference pointing at this record is already that scalar and needs no
321
- # opening at all — the two are different readings, not interchangeable.
322
- #
323
- # @param identity_value [Hash, Object] a generated identity value, as `value_for`
324
- # produced it — a Hash for a multi-field value object, or the bare scalar already
325
- # for a single-field one
326
- # @return [String] the identity's bare scalar, stringified
327
- def scalar_of(identity_value)
328
- identity_value.is_a?(Hash) ? identity_value.values.first.to_s : identity_value.to_s
329
- end
330
-
331
- # Generates a fabricated id unrelated to anything `known_ids` tracks.
332
- #
333
- # @param random [Random] the RNG driving every draw this call makes
334
- # @return [String] a `"gen-"`-prefixed id with 8 random hex characters
335
- def random_id(random)
336
- "gen-#{random.bytes(4).unpack1('H*')}"
337
- end
338
- end
339
- end
340
- end
data/lib/hecks/fuzzing.rb DELETED
@@ -1,23 +0,0 @@
1
- module Hecks
2
- # The fuzzing toolkit bin/fuzz and bin/generate drive. Not required by
3
- # lib/hecks.rb on purpose — a booted domain never needs it.
4
- module Fuzzing
5
- end
6
- end
7
-
8
- require_relative "fuzzing/value_generator"
9
- require_relative "fuzzing/invalid_value_generator"
10
- require_relative "fuzzing/sequence_generator"
11
- require_relative "fuzzing/nondeterministic"
12
- require_relative "fuzzing/replay"
13
- require_relative "fuzzing/shrinker"
14
- require_relative "fuzzing/properties"
15
- require_relative "fuzzing/qa_settings"
16
- require_relative "fuzzing/rotation_priority"
17
- require_relative "fuzzing/sweep_depth"
18
- require_relative "fuzzing/persistence_parity"
19
- require_relative "fuzzing/form_census"
20
- require_relative "fuzzing/target_capabilities"
21
- require_relative "fuzzing/rust_gap_manifest"
22
- require_relative "fuzzing/structural_skips"
23
- require_relative "fuzzing/coverage_campaign"