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.
- checksums.yaml +4 -4
- data/exe/hecks +13 -0
- data/lib/hecks/adapters/driven/google_authentication.rb +1 -1
- data/lib/hecks/adapters/driven/lambda/client.rb +1 -1
- data/lib/hecks/adapters/driven/memory.rb +1 -1
- data/lib/hecks/adapters/driven/postgres.rb +1 -1
- data/lib/hecks/adapters/driven/prism.rb +2 -2
- data/lib/hecks/adapters/driven/sqlite/projection.rb +1 -1
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +31 -10
- data/lib/hecks/behaviors/expectations.rb +2 -2
- data/lib/hecks/bluebook/assembly/contract.rb +1 -1
- data/lib/hecks/bluebook/assembly/contracts.rb +5 -5
- data/lib/hecks/bluebook/assembly/specializer.rb +2 -2
- data/lib/hecks/bluebook/assembly.rb +1 -1
- data/lib/hecks/bluebook/behaviour/chapter.rb +5 -2
- data/lib/hecks/bluebook/behaviour/read_model.rb +18 -0
- data/lib/hecks/bluebook/capabilities.rb +2 -2
- data/lib/hecks/bluebook/chapter.rb +1 -1
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +2 -2
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +6 -1
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +2 -2
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +3 -3
- data/lib/hecks/bluebook/dsl/entity_builder.rb +1 -1
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +1 -1
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +14 -9
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +1 -1
- data/lib/hecks/bluebook/dsl/rule_reference.rb +3 -3
- data/lib/hecks/bluebook/dsl/translation_builder.rb +1 -1
- data/lib/hecks/bluebook/dsl/world_builder.rb +47 -3
- data/lib/hecks/bluebook/hexagon.rb +15 -7
- data/lib/hecks/bluebook/meta_validator/judge.rb +3 -3
- data/lib/hecks/bluebook/meta_validator/plan.rb +4 -4
- data/lib/hecks/bluebook/meta_validator/readings.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +2 -2
- data/lib/hecks/bluebook/meta_validator/shapes.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +11 -10
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +3 -1
- data/lib/hecks/bluebook/meta_validator.rb +1 -1
- data/lib/hecks/bluebook/model_check/client_profile.rb +4 -70
- data/lib/hecks/bluebook/model_check.rb +13 -14
- data/lib/hecks/cache_dir.rb +103 -0
- data/lib/hecks/cli/document.rb +104 -0
- data/lib/hecks/cli/ir.rb +47 -0
- data/lib/hecks/cli/mcp.rb +32 -0
- data/lib/hecks/cli/mcp_door.rb +461 -0
- data/lib/hecks/cli/model_check.rb +270 -0
- data/lib/hecks/cli/project_cli.rb +123 -0
- data/lib/hecks/cli/project_diagrams.rb +43 -0
- data/lib/hecks/cli/run.rb +219 -0
- data/lib/hecks/cli/smoke_test.rb +59 -0
- data/lib/hecks/cli/stores.rb +54 -0
- data/lib/hecks/cli.rb +166 -0
- data/lib/hecks/embryonaut_bluebook/lock.rb +72 -0
- data/lib/hecks/embryonaut_bluebook/shape.rb +47 -0
- data/lib/hecks/embryonaut_bluebook/vendor.rb +194 -0
- data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +88 -0
- data/lib/hecks/embryonaut_bluebook.rb +29 -3
- data/lib/hecks/facade/handle.rb +2 -2
- data/lib/hecks/facade/surface/aggregate_door.rb +1 -1
- data/lib/hecks/forms/value_object_shape.rb +1 -1
- data/lib/hecks/framework/bluebook/console_settings.bluebook +3 -3
- data/lib/hecks/language/bluebook/vocabulary.bluebook +16 -0
- data/lib/hecks/language/world/world.bluebook +32 -2
- data/lib/hecks/mcp_door_scope.rb +176 -0
- data/lib/hecks/ports/key_vault.rb +1 -1
- data/lib/hecks/ports/persistence/append_only.rb +1 -1
- data/lib/hecks/ports/persistence/binding_policy.rb +16 -5
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +3 -2
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +132 -0
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +13 -7
- data/lib/hecks/ports/persistence/plugins/era.rb +7 -4
- data/lib/hecks/ports/persistence/postgres_dump/connection.rb +73 -0
- data/lib/hecks/ports/persistence/postgres_dump.rb +125 -0
- data/lib/hecks/ports/persistence/repository_factory.rb +2 -2
- data/lib/hecks/ports/persistence/state_codec.rb +1 -1
- data/lib/hecks/ports/persistence.rb +4 -0
- data/lib/hecks/projections/deploy/fargate/assembly.rb +193 -0
- data/lib/hecks/projections/deploy/fargate/cdn.rb +318 -0
- data/lib/hecks/projections/deploy/fargate/cdn_default.rb +87 -0
- data/lib/hecks/projections/deploy/fargate/check.rb +172 -0
- data/lib/hecks/projections/deploy/fargate/containers.rb +412 -0
- data/lib/hecks/projections/deploy/fargate/extras.rb +268 -0
- data/lib/hecks/projections/deploy/fargate/monitoring.rb +448 -0
- data/lib/hecks/projections/deploy/fargate/settings.rb +235 -0
- data/lib/hecks/projections/deploy/fargate/yaml.rb +116 -0
- data/lib/hecks/projections/deploy/fargate.rb +188 -139
- data/lib/hecks/projections/deploy/lambda.rb +8 -8
- data/lib/hecks/projections/deploy/preview/access_yaml.rb +145 -0
- data/lib/hecks/projections/deploy/preview/containers.rb +218 -0
- data/lib/hecks/projections/deploy/preview/preview.sh.erb +446 -0
- data/lib/hecks/projections/deploy/preview/routing_yaml.rb +216 -0
- data/lib/hecks/projections/deploy/preview/script.rb +40 -0
- data/lib/hecks/projections/deploy/preview/settings.rb +140 -0
- data/lib/hecks/projections/deploy/preview/task_yaml.rb +215 -0
- data/lib/hecks/projections/deploy/preview/template.rb +247 -0
- data/lib/hecks/projections/deploy/preview/yaml_text.rb +48 -0
- data/lib/hecks/projections/deploy/preview.rb +103 -0
- data/lib/hecks/projections/deploy/scripts/deploy-service.sh.tmpl +182 -0
- data/lib/hecks/projections/deploy/scripts/expected-era.header +17 -0
- data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +54 -0
- data/lib/hecks/projections/deploy/scripts/settings.rb +114 -0
- data/lib/hecks/projections/deploy/scripts/smoke-after-deploy.sh.tmpl +232 -0
- data/lib/hecks/projections/deploy/scripts.rb +171 -0
- data/lib/hecks/projections/deploy/smoke/harness.js +366 -0
- data/lib/hecks/projections/deploy/smoke/workflow.yml.tmpl +56 -0
- data/lib/hecks/projections/deploy/smoke.rb +159 -0
- data/lib/hecks/projections/deploy/template_diff/comparer.rb +231 -0
- data/lib/hecks/projections/deploy/template_diff/loader.rb +105 -0
- data/lib/hecks/projections/deploy/template_diff.rb +173 -0
- data/lib/hecks/projections/glossary/sensitivity.rb +1 -1
- data/lib/hecks/projections/reference.rb +7 -1
- data/lib/hecks/projections.rb +2 -0
- data/lib/hecks/projector/exporter.rb +4 -4
- data/lib/hecks/projector/target.rb +1 -1
- data/lib/hecks/release/gem_pin/published_versions.rb +58 -0
- data/lib/hecks/release/gem_pin.rb +112 -0
- data/lib/hecks/release/runner/ci_publisher.rb +96 -0
- data/lib/hecks/release/runner/commands.rb +68 -0
- data/lib/hecks/release/runner/console.rb +52 -0
- data/lib/hecks/release/runner/gem_publisher.rb +44 -0
- data/lib/hecks/release/runner/git.rb +48 -0
- data/lib/hecks/release/runner/npm_publisher.rb +89 -0
- data/lib/hecks/release/runner/preflight.rb +119 -0
- data/lib/hecks/release/runner/published.rb +55 -0
- data/lib/hecks/release/runner/tagger.rb +87 -0
- data/lib/hecks/release/runner.rb +244 -0
- data/lib/hecks/router/namespace_installer.rb +1 -1
- data/lib/hecks/runtime/command_interpreter.rb +3 -3
- data/lib/hecks/runtime/command_rules/admissibility.rb +2 -2
- data/lib/hecks/runtime/command_rules/arithmetic.rb +2 -2
- data/lib/hecks/runtime/entity_element.rb +1 -1
- data/lib/hecks/runtime/entity_interpreter.rb +1 -1
- data/lib/hecks/runtime/invocation.rb +1 -1
- data/lib/hecks/runtime/loader.rb +27 -3
- data/lib/hecks/runtime/outbox.rb +1 -1
- data/lib/hecks/runtime/reaction_invocation.rb +3 -3
- data/lib/hecks/runtime/read_model_interpreter.rb +37 -15
- data/lib/hecks/runtime/reference_hop.rb +4 -4
- data/lib/hecks/runtime/registry/saga_persistence.rb +8 -2
- data/lib/hecks/runtime/registry/verification.rb +15 -13
- data/lib/hecks/runtime/registry/world_defaults.rb +106 -0
- data/lib/hecks/runtime/registry.rb +22 -13
- data/lib/hecks/runtime/saga_interpreter.rb +1 -1
- data/lib/hecks/runtime/tenant_check.rb +1 -1
- data/lib/hecks/runtime/value/coercion.rb +3 -3
- data/lib/hecks/runtime/value/entity_list_coercion.rb +1 -1
- data/lib/hecks/storehouse.rb +18 -12
- data/lib/hecks/vendoring/git_environment.rb +35 -0
- data/lib/hecks/vendoring/git_source.rb +102 -0
- data/lib/hecks/vendoring.rb +102 -0
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +5 -0
- data/lib/hecks.rb +8 -2
- metadata +72 -58
- data/lib/hecks/bench/cli.rb +0 -74
- data/lib/hecks/bench/environment.rb +0 -100
- data/lib/hecks/bench/postgres_probe.rb +0 -41
- data/lib/hecks/bench/report.rb +0 -116
- data/lib/hecks/bench/ruby_runner.rb +0 -151
- data/lib/hecks/bench/run.rb +0 -55
- data/lib/hecks/bench/rust_runner.rb +0 -165
- data/lib/hecks/bench/stats.rb +0 -75
- data/lib/hecks/bench/suite.rb +0 -172
- data/lib/hecks/bench/workload.rb +0 -137
- data/lib/hecks/bench.rb +0 -28
- data/lib/hecks/codemod.rb +0 -443
- data/lib/hecks/corpus.rb +0 -479
- data/lib/hecks/doc/reference.rb +0 -585
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -684
- data/lib/hecks/fuzzing/combination_miner.rb +0 -178
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +0 -469
- data/lib/hecks/fuzzing/coverage_campaign.rb +0 -161
- data/lib/hecks/fuzzing/differential.rb +0 -192
- data/lib/hecks/fuzzing/domain_generator.rb +0 -871
- data/lib/hecks/fuzzing/era_boundary.rb +0 -154
- data/lib/hecks/fuzzing/form_census.rb +0 -285
- data/lib/hecks/fuzzing/generated_domain_check.rb +0 -171
- data/lib/hecks/fuzzing/invalid_value_generator.rb +0 -125
- data/lib/hecks/fuzzing/isolated_boot.rb +0 -604
- data/lib/hecks/fuzzing/nondeterministic.rb +0 -79
- data/lib/hecks/fuzzing/persistence_parity.rb +0 -253
- data/lib/hecks/fuzzing/properties/corrections.rb +0 -125
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +0 -688
- data/lib/hecks/fuzzing/properties/guards.rb +0 -398
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +0 -399
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +0 -137
- data/lib/hecks/fuzzing/properties/outbox.rb +0 -180
- data/lib/hecks/fuzzing/properties/querying.rb +0 -260
- data/lib/hecks/fuzzing/properties.rb +0 -267
- data/lib/hecks/fuzzing/qa_settings.rb +0 -164
- data/lib/hecks/fuzzing/replay.rb +0 -930
- data/lib/hecks/fuzzing/rotation_priority.rb +0 -114
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +0 -139
- data/lib/hecks/fuzzing/self_consistency.rb +0 -825
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +0 -532
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +0 -171
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +0 -127
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +0 -116
- data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +0 -138
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +0 -344
- data/lib/hecks/fuzzing/sequence_generator.rb +0 -311
- data/lib/hecks/fuzzing/shrinker.rb +0 -309
- data/lib/hecks/fuzzing/structural_skips.rb +0 -53
- data/lib/hecks/fuzzing/sweep_depth.rb +0 -61
- data/lib/hecks/fuzzing/target_capabilities.rb +0 -255
- data/lib/hecks/fuzzing/value_generator.rb +0 -340
- data/lib/hecks/fuzzing.rb +0 -23
- data/lib/hecks/grammar/evolve.rb +0 -524
- data/lib/hecks/query_ir.rb +0 -482
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
require "json"
|
|
2
|
-
require_relative "../bluebook/model_check"
|
|
3
|
-
require_relative "../bluebook/meta_validator"
|
|
4
|
-
require_relative "../ports/query/in_memory"
|
|
5
|
-
require_relative "../query_specification/field_path"
|
|
6
|
-
require_relative "../runtime/value"
|
|
7
|
-
require_relative "properties/lifecycle_and_replay"
|
|
8
|
-
require_relative "properties/querying"
|
|
9
|
-
require_relative "properties/guards"
|
|
10
|
-
require_relative "properties/dispatch_and_mutations"
|
|
11
|
-
require_relative "properties/invariants_and_aggregation"
|
|
12
|
-
require_relative "properties/corrections"
|
|
13
|
-
require_relative "properties/outbox"
|
|
14
|
-
|
|
15
|
-
module Hecks
|
|
16
|
-
module Fuzzing
|
|
17
|
-
# Declared properties, checked over a replayed history — the other
|
|
18
|
-
# half of property-based testing the fuzzer was missing: it already
|
|
19
|
-
# generates and (with bin/fuzz's shrinker) minimizes, but checked
|
|
20
|
-
# nothing beyond "did the interpreter crash" and "did the replay
|
|
21
|
-
# match the claim." A property here is a fact that should hold of
|
|
22
|
-
# any history a valid domain produces, independent of which seed
|
|
23
|
-
# produced it.
|
|
24
|
-
#
|
|
25
|
-
# ## Property shape
|
|
26
|
-
#
|
|
27
|
-
# Each property is `name => ->(history) { true/false, or a message
|
|
28
|
-
# string naming what broke }` — a truthy return (including `true`)
|
|
29
|
-
# is a pass; a String return is a failure, and the string is the
|
|
30
|
-
# finding. `history` is Replay's return shape.
|
|
31
|
-
#
|
|
32
|
-
# ## Feature coverage
|
|
33
|
-
#
|
|
34
|
-
# Every property declares the language feature it covers, in
|
|
35
|
-
# `FEATURE_COVERAGE` below — a "Construct#attribute" pair spelled
|
|
36
|
-
# exactly as `Bluebook::MetaValidator.grammar_registry` names it,
|
|
37
|
-
# the same meta-domain that judges every real bluebook (see that
|
|
38
|
-
# module's own header: "the language IS the source"). That is the
|
|
39
|
-
# link this file exists to make real: a construct the language
|
|
40
|
-
# declares is a fact `spec/meta_domain_coverage_spec.rb` can
|
|
41
|
-
# enumerate on its own, without anyone re-typing the list here —
|
|
42
|
-
# so a new attribute added to `language/bluebook/*.bluebook` shows
|
|
43
|
-
# up in that spec as unclaimed the moment it lands, not whenever
|
|
44
|
-
# someone remembers to go looking. Claiming a feature here is a
|
|
45
|
-
# deliberate act (a real property, checked at least once failing
|
|
46
|
-
# and once passing — `spec/fuzzing/properties_spec.rb`'s own
|
|
47
|
-
# discipline) or an explicit, reasoned exemption in that same
|
|
48
|
-
# spec — never silence.
|
|
49
|
-
module Properties
|
|
50
|
-
# Grouped by responsibility across properties/*.rb (lifecycle_and_
|
|
51
|
-
# replay, querying, guards, dispatch_and_mutations, invariants_and_
|
|
52
|
-
# aggregation) — `extend`ed here rather than `include`d, matching
|
|
53
|
-
# `module_function` below: every property is reachable as
|
|
54
|
-
# `Properties.foo(history)`, a module-level call with no instance in
|
|
55
|
-
# play, the same relationship AggregateBuilder::Sealing's `include`
|
|
56
|
-
# has to instance methods and BluebookBuilder::Validation's `extend`
|
|
57
|
-
# has to `self.` methods — the same "one namespace, its methods,
|
|
58
|
-
# filed across files by responsibility" pattern, `module_function`
|
|
59
|
-
# already made this module's own particular shape.
|
|
60
|
-
extend LifecycleAndReplay
|
|
61
|
-
extend Querying
|
|
62
|
-
extend Guards
|
|
63
|
-
extend DispatchAndMutations
|
|
64
|
-
extend DryRuns
|
|
65
|
-
extend InvariantsAndAggregation
|
|
66
|
-
extend Corrections
|
|
67
|
-
extend Outbox
|
|
68
|
-
|
|
69
|
-
module_function
|
|
70
|
-
|
|
71
|
-
# Which language feature each property is answerable for. Not
|
|
72
|
-
# exhaustive of everything a property's body happens to touch —
|
|
73
|
-
# `Command#attributes`, say, is exercised by nearly every property
|
|
74
|
-
# here without being what any of them was written to guard — but
|
|
75
|
-
# exhaustive of the feature that would go unchecked if this
|
|
76
|
-
# property did not exist. That is the question the coverage gate
|
|
77
|
-
# actually asks.
|
|
78
|
-
FEATURE_COVERAGE = {
|
|
79
|
-
lifecycle_values_are_declared: %w[Aggregate#state_field Aggregate#state_start Aggregate#transitions
|
|
80
|
-
Entity#state_field Entity#state_start Entity#transitions],
|
|
81
|
-
saga_advances_follow_declared_handlers: %w[Handler#from_state Handler#to_state Handler#event_type],
|
|
82
|
-
query_answers_match_reference: %w[Query#wheres Query#order_field Query#order_way Query#limit],
|
|
83
|
-
paging_offset_partitions_correctly: %w[Query#options],
|
|
84
|
-
authorize_scopes_or_refuses: %w[Query#options],
|
|
85
|
-
guard_refusals_are_declared: %w[Command#givens Command#ensures],
|
|
86
|
-
lifecycle_guard_and_given_violations_are_refused: %w[Command#from Aggregate#preconditions Entity#preconditions],
|
|
87
|
-
# Dispatch#command_name/Dispatch#with_spec are not claimable
|
|
88
|
-
# feature names — META_DOMAIN_ALL_FEATURES only walks one level
|
|
89
|
-
# of entity nesting (`agg.entities.flat_map`, meta_domain_
|
|
90
|
-
# coverage_spec.rb), and Dispatch sits two deep (ProcessManager
|
|
91
|
-
# -> Handler -> Dispatch), so those strings never exist there
|
|
92
|
-
# to claim — a pre-existing meta-domain coverage-generation gap,
|
|
93
|
-
# found here (their old META_DOMAIN_KNOWN_GAPS entries were
|
|
94
|
-
# themselves already-orphaned strings no completeness check ever
|
|
95
|
-
# verified, since KNOWN_GAPS has no "never lets a gap rot" check
|
|
96
|
-
# the way FEATURE_COVERAGE/GUARANTEED_BY_CONSTRUCTION both do).
|
|
97
|
-
# This property still closes the real behavior both would have
|
|
98
|
-
# named — a Dispatch's own command_name/with_spec are exactly
|
|
99
|
-
# what dispatch_args resolves and this property checks — the
|
|
100
|
-
# grammar just has no feature string for either one.
|
|
101
|
-
dispatch_binding_fidelity: %w[Handler#dispatches Policy#with_spec],
|
|
102
|
-
mutations_match_recompute: %w[Command#mutations],
|
|
103
|
-
sagas_rehydrate_cleanly: %w[ProcessManager#states ProcessManager#correlates_by
|
|
104
|
-
ProcessManager#starts_on ProcessManager#ends_on],
|
|
105
|
-
fanout_dispatches_once_per_matching_row: %w[Policy#for_each Policy#where],
|
|
106
|
-
aggregation_matches_recompute: %w[ReadModel#count ReadModel#median_field],
|
|
107
|
-
stored_records_satisfy_declared_invariants: %w[Aggregate#invariants Entity#invariants],
|
|
108
|
-
group_by_matches_recompute: %w[ReadModel#group_by],
|
|
109
|
-
# A runtime door, not a grammar construct — `Dispatcher#dry_run?`
|
|
110
|
-
# is something an application asks of a booted domain, not a
|
|
111
|
-
# word a bluebook can declare, so there is no feature string
|
|
112
|
-
# for it to claim. Listed (empty) rather than omitted so the
|
|
113
|
-
# discipline this table states — every property names what it
|
|
114
|
-
# is answerable for — has no silent exception.
|
|
115
|
-
dry_runs_leave_no_trace: [],
|
|
116
|
-
# Another runtime door, not a grammar construct — same reasoning
|
|
117
|
-
# as dry_runs_leave_no_trace right above: `Runtime::Outbox` is
|
|
118
|
-
# something a persistence adapter provides underneath a booted
|
|
119
|
-
# domain, never a word a bluebook declares.
|
|
120
|
-
outbox_rows_match_reactions: [],
|
|
121
|
-
# **The `corrects` mutation's own target** — this property reads
|
|
122
|
-
# `command.mutations.select { op == :corrects }` and asks whether
|
|
123
|
-
# the event each one names was ever actually emitted, so the
|
|
124
|
-
# feature it answers for is the mutation list, the same one
|
|
125
|
-
# `mutations_match_recompute` reads for a different question.
|
|
126
|
-
# (Not `Command#references`: that field is the dangling-reference
|
|
127
|
-
# question no property asks yet, and it stays a named gap.)
|
|
128
|
-
corrections_reference_an_emitted_event: %w[Command#mutations],
|
|
129
|
-
# No feature string exists for what this one reads. It depends on
|
|
130
|
-
# an argument's own `relationship` (which reference-typed argument
|
|
131
|
-
# points at which aggregate) — but `Argument` is a value object,
|
|
132
|
-
# and the meta-domain walk enumerates aggregate and entity fields
|
|
133
|
-
# only, so no `Argument#…` name is claimable. The declaration side
|
|
134
|
-
# it shares with queries, `authorize …, tenant:`, is
|
|
135
|
-
# `Query#options`, already claimed by `authorize_scopes_or_refuses`;
|
|
136
|
-
# claiming it twice would say this property covers a query
|
|
137
|
-
# question it never asks. Listed (empty) rather than omitted, the
|
|
138
|
-
# same discipline the two runtime doors above keep.
|
|
139
|
-
commands_respect_tenant_scope: []
|
|
140
|
-
}.freeze
|
|
141
|
-
|
|
142
|
-
# Features a replay property could never catch violated, because the
|
|
143
|
-
# runtime's own construction makes the violation impossible to
|
|
144
|
-
# produce in the first place — not "untested," but unfalsifiable by
|
|
145
|
-
# a history, the same class of guarantee this codebase already
|
|
146
|
-
# states for identity ("nothing is minted" — command_interpreter.rb's
|
|
147
|
-
# own header) and now generalises. Each entry names the one place in
|
|
148
|
-
# the runtime that makes it true, universally, for every domain and
|
|
149
|
-
# every adapter — never per-domain logic a future domain could
|
|
150
|
-
# accidentally route around.
|
|
151
|
-
#
|
|
152
|
-
# The bluebook/hecksagon boundary is why this works: a bluebook
|
|
153
|
-
# declares shape (attribute types, patterns, closed sets, VO
|
|
154
|
-
# invariants — see docs/decisions/0009), and shape is enforced by
|
|
155
|
-
# one coercion door every domain's every attribute passes through
|
|
156
|
-
# (`Runtime::Value.build`, via value/coercion.rb + value/admission.rb)
|
|
157
|
-
# regardless of which hecksagon later binds the aggregate to Memory,
|
|
158
|
-
# Postgres, or anything else. A value that violated its own declared
|
|
159
|
-
# pattern, invariant, or closed set could never be coerced, so it
|
|
160
|
-
# could never be stored, so it could never appear in a replay's own
|
|
161
|
-
# `:instances` to be caught violating it. Checking for it after the
|
|
162
|
-
# fact would be watching for something the construction path already
|
|
163
|
-
# made impossible.
|
|
164
|
-
#
|
|
165
|
-
# Not a place to hide a real gap — a feature belongs here only once
|
|
166
|
-
# the specific enforcing code path has been read and confirmed, the
|
|
167
|
-
# same discipline `spec/fuzzing/meta_domain_coverage_spec.rb` demands
|
|
168
|
-
# of `KNOWN_GAPS` in the other direction. `Entity#identified_by`
|
|
169
|
-
# illustrates the discipline: `command_interpreter.rb`'s
|
|
170
|
-
# `AlreadyExists` refusal covers every creating aggregate command
|
|
171
|
-
# uniformly, but nothing else on its own covers an entity's own
|
|
172
|
-
# append — so this entry names the path that actually does,
|
|
173
|
-
# `MutationApplier#check_entity_collision` (command_interpreter/
|
|
174
|
-
# mutation_applier.rb), which runs unconditionally on both branches
|
|
175
|
-
# an entity identity can arrive by (caller-supplied, or composite —
|
|
176
|
-
# the two the auto-mint branch doesn't cover), the same way
|
|
177
|
-
# `command_interpreter#hydrate`'s own check is unconditional for
|
|
178
|
-
# every creating aggregate command. The collision this closes is
|
|
179
|
-
# real, not hypothetical: SafeDepositBox's Visit/KeyIssuance entities
|
|
180
|
-
# reproduce it, confirmed by spec/runtime/safe_deposit_box_spec.rb.
|
|
181
|
-
GUARANTEED_BY_CONSTRUCTION = {
|
|
182
|
-
"Aggregate#attributes" => "every field's pattern/closed-set/type passes through Value.build's one coercion " \
|
|
183
|
-
"door (value/coercion.rb#check_patterns, value/admission.rb) before it can exist " \
|
|
184
|
-
"— a stored value that violated its own declared shape was never producible to " \
|
|
185
|
-
"begin with",
|
|
186
|
-
"Aggregate#value_objects" => "the shape being coerced above — same door, same guarantee",
|
|
187
|
-
# S17, ADR 0026 — the list `saga_advances_follow_declared_handlers`
|
|
188
|
-
# (below) already walks to find each handler's own event_type/
|
|
189
|
-
# from_state/to_state (the three it claims) — a property cannot
|
|
190
|
-
# check a handler's own fields without iterating the list that
|
|
191
|
-
# holds them, so the list itself is exercised by the same door.
|
|
192
|
-
"ProcessManager#handlers" => "saga_advances_follow_declared_handlers already walks this list to find " \
|
|
193
|
-
"event_type/from_state/to_state — same door, same guarantee",
|
|
194
|
-
"Aggregate#identified_by" => "CommandInterpreter's data-driven dispatch order refuses AlreadyExists " \
|
|
195
|
-
"(command_interpreter.rb, command.creates?) for every creating command uniformly, " \
|
|
196
|
-
"before a duplicate id can ever be stored — collision is refused at the door, not " \
|
|
197
|
-
"produced and later caught",
|
|
198
|
-
"Entity#identified_by" => "EntityElement.check_entity_collision (runtime/entity_element.rb, moved there " \
|
|
199
|
-
"BUG#145 so both call sites share it) checks Array(current) against every part " \
|
|
200
|
-
"of the entity's own identity before an append can land — MutationApplier#" \
|
|
201
|
-
"entity_element's aggregate-owned call (Workspace.boards, on both branches " \
|
|
202
|
-
"identity arrives by: caller-supplied, or composite) AND EntityElement#" \
|
|
203
|
-
"appended_to_element's entity-owned, nested-one-hop-further call (Board.cards — " \
|
|
204
|
-
"unconditional, no auto-mint branch exists at that depth) — the same " \
|
|
205
|
-
"AlreadyExists refusal Aggregate#identified_by gets above, one or two levels " \
|
|
206
|
-
"down. Auto-minted (aggregate-owned) entities never reach the check " \
|
|
207
|
-
"(current.size + 1 can't repeat unless something remove:s from the list between " \
|
|
208
|
-
"mints, which no real domain does today — see the comment on #entity_element " \
|
|
209
|
-
"itself)",
|
|
210
|
-
"Command#attributes" => "command arguments are coerced through the SAME Value.build door as any other " \
|
|
211
|
-
"attribute — an accepted dispatch's own args already passed pattern/admits/invariant checks",
|
|
212
|
-
"Command#emits" => "CommandRules::Emission#emit iterates command.emits ITSELF to construct every " \
|
|
213
|
-
"announced Event (command_rules/emission.rb) — there is no other path to emit, so " \
|
|
214
|
-
"a command can never announce a name its own declaration doesn't list",
|
|
215
|
-
"Query#attributes" => "query arguments are coerced through the same Value.build door — same guarantee as " \
|
|
216
|
-
"Command#attributes",
|
|
217
|
-
"Entity#attributes" => "same coercion door, one level in — an entity's own attributes are Value-typed exactly " \
|
|
218
|
-
"the way an aggregate's are",
|
|
219
|
-
"ValueObject#attributes" => "the shape Value.build enforces IS this declaration — the guarantee and the " \
|
|
220
|
-
"feature are the same fact seen from two sides",
|
|
221
|
-
"ValueObject#invariants" => "run inside the SAME coercion call (coercion.rb, before construction returns) " \
|
|
222
|
-
"that pattern-checks a VO's fields — a VO whose invariant did not hold could not " \
|
|
223
|
-
"finish being built",
|
|
224
|
-
"ValueObject#rows" => "closed-set membership is checked in value/admission.rb, the second half of the same " \
|
|
225
|
-
"one construction door",
|
|
226
|
-
# S17, ADR 0026 — Member is a genuine entity now (nested under
|
|
227
|
-
# ValueObject), so this reads "Member#pairs", not "Member#shape" —
|
|
228
|
-
# the free-text, un-parsed spelling a standalone root once needed
|
|
229
|
-
# no longer exists at all, an entity's own element is never
|
|
230
|
-
# serialized as text. "ValueObject#members" is the same fact
|
|
231
|
-
# "ValueObject#rows" already counts, seen from the other side — a
|
|
232
|
-
# value object cannot declare admitted rows without a members list
|
|
233
|
-
# to hold them, and vice versa.
|
|
234
|
-
"ValueObject#members" => "the members list IS what ValueObject#rows counts — same door, same guarantee",
|
|
235
|
-
"Member#pairs" => "one level into ValueObject#rows — same door"
|
|
236
|
-
}.freeze
|
|
237
|
-
|
|
238
|
-
# Runs the standard property battery over one replayed history —
|
|
239
|
-
# everything except determinism, which needs to replay twice itself
|
|
240
|
-
# and so takes the steps directly rather than a single history.
|
|
241
|
-
#
|
|
242
|
-
# @param history [Hash] a replayed history, as returned by `Fuzzing::Replay.call`
|
|
243
|
-
# @return [Hash{Symbol => true, String}] each property name mapped to `true`
|
|
244
|
-
# (passed) or a message string naming what broke
|
|
245
|
-
def check(history)
|
|
246
|
-
{ lifecycle_values_are_declared: lifecycle_values_are_declared(history),
|
|
247
|
-
saga_advances_follow_declared_handlers: saga_advances_follow_declared_handlers(history),
|
|
248
|
-
query_answers_match_reference: query_answers_match_reference(history),
|
|
249
|
-
guard_refusals_are_declared: guard_refusals_are_declared(history),
|
|
250
|
-
sagas_rehydrate_cleanly: sagas_rehydrate_cleanly(history),
|
|
251
|
-
fanout_dispatches_once_per_matching_row: fanout_dispatches_once_per_matching_row(history),
|
|
252
|
-
aggregation_matches_recompute: aggregation_matches_recompute(history),
|
|
253
|
-
stored_records_satisfy_declared_invariants: stored_records_satisfy_declared_invariants(history),
|
|
254
|
-
group_by_matches_recompute: group_by_matches_recompute(history),
|
|
255
|
-
paging_offset_partitions_correctly: paging_offset_partitions_correctly(history),
|
|
256
|
-
lifecycle_guard_and_given_violations_are_refused: lifecycle_guard_and_given_violations_are_refused(history),
|
|
257
|
-
authorize_scopes_or_refuses: authorize_scopes_or_refuses(history),
|
|
258
|
-
commands_respect_tenant_scope: commands_respect_tenant_scope(history),
|
|
259
|
-
dispatch_binding_fidelity: dispatch_binding_fidelity(history),
|
|
260
|
-
mutations_match_recompute: mutations_match_recompute(history),
|
|
261
|
-
dry_runs_leave_no_trace: dry_runs_leave_no_trace(history),
|
|
262
|
-
corrections_reference_an_emitted_event: corrections_reference_an_emitted_event(history),
|
|
263
|
-
outbox_rows_match_reactions: outbox_rows_match_reactions(history) }
|
|
264
|
-
end
|
|
265
|
-
end
|
|
266
|
-
end
|
|
267
|
-
end
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
require "yaml"
|
|
2
|
-
|
|
3
|
-
module Hecks
|
|
4
|
-
module Fuzzing
|
|
5
|
-
# Reads `qa/settings.yml` — the hecks_qa practice's own dials, now
|
|
6
|
-
# data in a file rather than Ruby constant literals. The why of each
|
|
7
|
-
# dial (what it does, who reads it) stays exactly one place: the
|
|
8
|
-
# comments on `QualityControlDials` in `qa/bluebook/quality_control.
|
|
9
|
-
# bluebook`, which now sources every value from an instance of this
|
|
10
|
-
# class instead of writing it inline. This class is only the loading
|
|
11
|
-
# and the validation — no dial policy lives here.
|
|
12
|
-
#
|
|
13
|
-
# **Fails loud, not quiet** — the whole practice's own opening line
|
|
14
|
-
# ("the enemy is the quiet divergence") applies to its own settings
|
|
15
|
-
# file too: a missing key, an extra key nothing recognises, or a
|
|
16
|
-
# value of the wrong shape all raise immediately, at load time
|
|
17
|
-
# (which is bluebook-load time, i.e. `Hecks.boot`), naming exactly
|
|
18
|
-
# what's wrong — never a `nil` dial silently reaching a script that
|
|
19
|
-
# assumes a number.
|
|
20
|
-
#
|
|
21
|
-
# **Plain data in, frozen data out**. `.load` parses the YAML with
|
|
22
|
-
# `Psych.safe_load_file` (no custom tags, no arbitrary Ruby objects)
|
|
23
|
-
# and hands back an instance whose accessors are the exact values a
|
|
24
|
-
# human wrote in the file — a `Hash`/`Array` for the nested dials,
|
|
25
|
-
# never a second, richer wrapper type nothing else in this practice
|
|
26
|
-
# expects.
|
|
27
|
-
class QaSettings
|
|
28
|
-
# One entry per dial this class knows about: the accessor name
|
|
29
|
-
# (matching `qa/settings.yml`'s own key, and `QualityControlDials`'
|
|
30
|
-
# constant name snake_cased) mapped to the class (or classes) a
|
|
31
|
-
# valid value must be an instance of. `TrueClass`/`FalseClass`
|
|
32
|
-
# both name a boolean dial — Ruby has no single class both `true`
|
|
33
|
-
# and `false` share. `Numeric` admits both an Integer and a Float
|
|
34
|
-
# for a fraction dial (`0` and `0.0` are both a human plausibly
|
|
35
|
-
# types for "off").
|
|
36
|
-
EXPECTED_TYPES = {
|
|
37
|
-
cadence_seconds: Integer,
|
|
38
|
-
pr_cap_per_day: Integer,
|
|
39
|
-
widening_tiers: Array,
|
|
40
|
-
sweep_max_parallel: Integer,
|
|
41
|
-
liveness_fallback_seconds: Integer,
|
|
42
|
-
draft_only: [TrueClass, FalseClass],
|
|
43
|
-
auto_merge: [TrueClass, FalseClass],
|
|
44
|
-
branch_prefix: String,
|
|
45
|
-
adversarial_fraction: Numeric,
|
|
46
|
-
guided_generation: [TrueClass, FalseClass],
|
|
47
|
-
corpus_splice_probability: Numeric,
|
|
48
|
-
favor_rare_verbs: Integer,
|
|
49
|
-
self_consistency_checks: [TrueClass, FalseClass],
|
|
50
|
-
shrink_budget: Integer,
|
|
51
|
-
yield_weight_seconds: Integer,
|
|
52
|
-
yield_decay_percent: Integer,
|
|
53
|
-
rotation_stale_floor_seconds: Integer,
|
|
54
|
-
persistence_parity_seed_cap: Integer,
|
|
55
|
-
concurrency_seed_cap: Integer,
|
|
56
|
-
adapter_parity_pairs: Hash,
|
|
57
|
-
modes: Hash,
|
|
58
|
-
role_draw_probability: Numeric,
|
|
59
|
-
dry_run_fraction: Numeric,
|
|
60
|
-
generated_domains_per_tick: Integer,
|
|
61
|
-
generated_domains_rust: [TrueClass, FalseClass],
|
|
62
|
-
generated_domain_seeds: Integer,
|
|
63
|
-
structural_refusal_boundary: Array
|
|
64
|
-
}.freeze
|
|
65
|
-
|
|
66
|
-
attr_reader(*EXPECTED_TYPES.keys)
|
|
67
|
-
|
|
68
|
-
# **The real file, always** — resolved off this file's own `__dir__`
|
|
69
|
-
# (lib/hecks/fuzzing/), never off the caller's. `QualityControlDials`
|
|
70
|
-
# is defined inside `qa/bluebook/quality_control.bluebook`, and that
|
|
71
|
-
# exact directory gets copied to a tmpdir for every isolated/replayed
|
|
72
|
-
# boot (`Hecks::Fuzzing::IsolatedBoot#copy_dereferencing` copies only
|
|
73
|
-
# `qa/bluebook`'s own contents, never its parent `qa/`) — a path
|
|
74
|
-
# resolved from the bluebook's own `__dir__` would silently point at
|
|
75
|
-
# a copy with no `settings.yml` beside it at all. `qa/settings.yml`
|
|
76
|
-
# is read-only, human-edited data with no lifecycle (see this class's
|
|
77
|
-
# own header) — there is no isolation reason to ever read a copy of
|
|
78
|
-
# it, real boot or fuzzed one, so every caller gets the one real file
|
|
79
|
-
# by default. `qa_settings_spec.rb` passes its own fixture paths
|
|
80
|
-
# explicitly instead, the same way every other test in this practice
|
|
81
|
-
# that needs a non-default dial passes one in rather than mutating
|
|
82
|
-
# global state.
|
|
83
|
-
DEFAULT_PATH = File.expand_path("../../../qa/settings.yml", __dir__)
|
|
84
|
-
|
|
85
|
-
class << self
|
|
86
|
-
# Loads and validates `qa/settings.yml` (or `path`), returning a frozen instance.
|
|
87
|
-
#
|
|
88
|
-
# @param path [String] path to the YAML settings file; defaults to `DEFAULT_PATH`
|
|
89
|
-
# @return [Hecks::Fuzzing::QaSettings] the validated, frozen settings
|
|
90
|
-
# @raise [ArgumentError] if `path` does not exist, is not valid YAML, is not a
|
|
91
|
-
# YAML mapping at the top level, is missing a required key, declares an
|
|
92
|
-
# unknown key, or gives a value the wrong type for its dial
|
|
93
|
-
def load(path = DEFAULT_PATH)
|
|
94
|
-
raise ArgumentError, "qa settings file not found: #{path}" unless File.file?(path)
|
|
95
|
-
|
|
96
|
-
raw = begin
|
|
97
|
-
YAML.safe_load_file(path, symbolize_names: true)
|
|
98
|
-
rescue Psych::SyntaxError => e
|
|
99
|
-
raise ArgumentError, "#{path} is not valid YAML: #{e.message}"
|
|
100
|
-
end
|
|
101
|
-
raise ArgumentError, "#{path} must be a YAML mapping at the top level, got #{raw.class}" unless raw.is_a?(Hash)
|
|
102
|
-
|
|
103
|
-
new(raw, path)
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
# @param raw [Hash] parsed YAML settings keyed by symbol, one entry per dial in
|
|
108
|
-
# `EXPECTED_TYPES`
|
|
109
|
-
# @param path [String] path to the settings file, used only in error messages
|
|
110
|
-
# @raise [ArgumentError] if `raw` is missing a required key, declares an unknown
|
|
111
|
-
# key, or gives a value the wrong type for its dial
|
|
112
|
-
def initialize(raw, path)
|
|
113
|
-
missing = EXPECTED_TYPES.keys - raw.keys
|
|
114
|
-
raise ArgumentError, "#{path} is missing #{missing.sort.join(', ')}" if missing.any?
|
|
115
|
-
|
|
116
|
-
extra = raw.keys - EXPECTED_TYPES.keys
|
|
117
|
-
if extra.any?
|
|
118
|
-
raise ArgumentError,
|
|
119
|
-
"#{path} declares unknown key(s) #{extra.sort.join(', ')} — " \
|
|
120
|
-
"Hecks::Fuzzing::QaSettings::EXPECTED_TYPES doesn't recognise them"
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
EXPECTED_TYPES.each do |key, expected|
|
|
124
|
-
value = raw.fetch(key)
|
|
125
|
-
expected_classes = Array(expected)
|
|
126
|
-
unless expected_classes.any? { |klass| value.is_a?(klass) }
|
|
127
|
-
raise ArgumentError,
|
|
128
|
-
"#{path}: #{key} must be a #{expected_classes.map(&:name).join(' or ')}, " \
|
|
129
|
-
"got #{value.class} (#{value.inspect})"
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
instance_variable_set(:"@#{key}", value)
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
symbolize_adapter_parity_pairs!(path)
|
|
136
|
-
freeze_values!
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
private
|
|
140
|
-
|
|
141
|
-
# `left:`/`right:` name adapters `IsolatedBoot` case-matches by
|
|
142
|
-
# symbol (`case adapter when :memory ...`), and YAML has no way to
|
|
143
|
-
# spell a bare Ruby Symbol as a mapping value — only
|
|
144
|
-
# `symbolize_names:` turns a key into one. So `adapter_parity_
|
|
145
|
-
# pairs` is the one dial that needs a coercion step after the
|
|
146
|
-
# type check above, rather than every dial growing one.
|
|
147
|
-
def symbolize_adapter_parity_pairs!(path)
|
|
148
|
-
@adapter_parity_pairs = @adapter_parity_pairs.to_h do |mode, pair|
|
|
149
|
-
unless pair.is_a?(Hash) && pair.key?(:left) && pair.key?(:right)
|
|
150
|
-
raise ArgumentError,
|
|
151
|
-
"#{path}: adapter_parity_pairs.#{mode} must have both left and right, got #{pair.inspect}"
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
[mode, { left: pair[:left].to_sym, right: pair[:right].to_sym }]
|
|
155
|
-
end
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
def freeze_values!
|
|
159
|
-
EXPECTED_TYPES.each_key { |key| instance_variable_get(:"@#{key}").freeze }
|
|
160
|
-
freeze
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
end
|