hecks 1.4.0 → 1.5.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/lib/hecks/adapters/driven/claude_code.rb +65 -0
- data/lib/hecks/adapters/driven/folder.rb +73 -0
- data/lib/hecks/adapters/driven/google_authentication.rb +25 -4
- data/lib/hecks/adapters/driven/governance_authorization.rb +57 -6
- data/lib/hecks/adapters/driven/heki/journal.rb +8 -0
- data/lib/hecks/adapters/driven/heki/saga_store.rb +53 -7
- data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
- data/lib/hecks/adapters/driven/heki.rb +93 -2
- data/lib/hecks/adapters/driven/identity_registry.rb +10 -0
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +22 -0
- data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
- data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +35 -7
- data/lib/hecks/adapters/driven/lambda.rb +43 -2
- data/lib/hecks/adapters/driven/local_storage.rb +67 -1
- data/lib/hecks/adapters/driven/memory.rb +13 -13
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +20 -0
- data/lib/hecks/adapters/driven/prism.rb +46 -7
- data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
- data/lib/hecks/adapters/driven/system_clock.rb +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
- data/lib/hecks/adapters/driven.rb +2 -0
- data/lib/hecks/adapters/driving/github_webhook.rb +14 -1
- data/lib/hecks/behaviors/dsl.rb +58 -0
- data/lib/hecks/behaviors/expectations.rb +168 -7
- data/lib/hecks/behaviors/ir.rb +11 -0
- data/lib/hecks/behaviors/rspec.rb +8 -0
- data/lib/hecks/behaviors/runner.rb +19 -0
- data/lib/hecks/behaviors.rb +8 -0
- data/lib/hecks/bluebook/aggregate.rb +30 -0
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +7 -0
- data/lib/hecks/bluebook/assembly/build.rb +25 -4
- data/lib/hecks/bluebook/assembly/contract.rb +66 -14
- data/lib/hecks/bluebook/assembly/contracts.rb +24 -19
- data/lib/hecks/bluebook/assembly/marks.rb +143 -14
- data/lib/hecks/bluebook/assembly/specializer.rb +19 -0
- data/lib/hecks/bluebook/assembly.rb +25 -7
- data/lib/hecks/bluebook/attribute.rb +17 -3
- data/lib/hecks/bluebook/behaviour/aggregate.rb +32 -0
- data/lib/hecks/bluebook/behaviour/attribute.rb +13 -0
- data/lib/hecks/bluebook/behaviour/chapter.rb +55 -2
- data/lib/hecks/bluebook/behaviour/command.rb +33 -3
- data/lib/hecks/bluebook/behaviour/domain_port.rb +20 -0
- data/lib/hecks/bluebook/behaviour/entity.rb +12 -0
- data/lib/hecks/bluebook/behaviour/hexagon.rb +26 -0
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +23 -2
- data/lib/hecks/bluebook/behaviour/policy.rb +34 -9
- data/lib/hecks/bluebook/behaviour/process_manager.rb +32 -1
- data/lib/hecks/bluebook/behaviour/query.rb +5 -0
- data/lib/hecks/bluebook/behaviour/read_model.rb +21 -0
- data/lib/hecks/bluebook/behaviour/traits.rb +36 -0
- data/lib/hecks/bluebook/behaviour/value_object.rb +17 -5
- data/lib/hecks/bluebook/chapter.rb +23 -0
- data/lib/hecks/bluebook/command.rb +53 -8
- data/lib/hecks/bluebook/domain_port.rb +25 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +196 -37
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +79 -42
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +59 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +385 -53
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +135 -21
- data/lib/hecks/bluebook/dsl/command_builder.rb +189 -27
- data/lib/hecks/bluebook/dsl/entity_builder.rb +139 -9
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +41 -0
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +23 -6
- data/lib/hecks/bluebook/dsl/policy_builder.rb +60 -19
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +79 -15
- data/lib/hecks/bluebook/dsl/query_builder.rb +33 -4
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +102 -27
- data/lib/hecks/bluebook/dsl/rule_reference.rb +60 -8
- data/lib/hecks/bluebook/dsl/translation_builder.rb +146 -40
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +55 -7
- data/lib/hecks/bluebook/entity.rb +29 -0
- data/lib/hecks/bluebook/expression/ast_json.rb +115 -23
- data/lib/hecks/bluebook/expression/ast_reader.rb +29 -0
- data/lib/hecks/bluebook/expression/canonical_form.rb +48 -9
- data/lib/hecks/bluebook/expression/evaluator.rb +207 -29
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +36 -0
- data/lib/hecks/bluebook/expression/resolver.rb +320 -77
- data/lib/hecks/bluebook/hexagon.rb +34 -0
- data/lib/hecks/bluebook/lifecycle.rb +11 -0
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -0
- data/lib/hecks/bluebook/meta_validator/judge.rb +30 -26
- data/lib/hecks/bluebook/meta_validator/plan.rb +43 -8
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +1 -0
- data/lib/hecks/bluebook/meta_validator/readings.rb +157 -7
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -4
- data/lib/hecks/bluebook/meta_validator/shapes.rb +141 -0
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +149 -23
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +5 -4
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +1 -0
- data/lib/hecks/bluebook/meta_validator.rb +180 -84
- data/lib/hecks/bluebook/model_check.rb +268 -24
- data/lib/hecks/bluebook/pattern_subset.rb +23 -1
- data/lib/hecks/bluebook/process_manager.rb +13 -0
- data/lib/hecks/bluebook/project_discovery.rb +5 -0
- data/lib/hecks/bluebook/project_loader.rb +40 -0
- data/lib/hecks/bluebook/project_register.rb +44 -0
- data/lib/hecks/bluebook/query.rb +27 -0
- data/lib/hecks/bluebook/read_model.rb +21 -1
- data/lib/hecks/bluebook/reference.rb +21 -8
- data/lib/hecks/bluebook/smoke_test.rb +29 -6
- data/lib/hecks/bluebook/synthesizer.rb +34 -0
- data/lib/hecks/bluebook/translation.rb +30 -1
- data/lib/hecks/bluebook/value_object.rb +23 -5
- data/lib/hecks/bluebook.rb +3 -4
- data/lib/hecks/codemod.rb +107 -20
- data/lib/hecks/construct.rb +15 -1
- data/lib/hecks/corpus.rb +146 -25
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
- data/lib/hecks/doc/reference.rb +185 -16
- data/lib/hecks/embryonaut_bluebook.rb +32 -9
- data/lib/hecks/facade/handle.rb +76 -3
- data/lib/hecks/facade/surface/aggregate_door.rb +8 -0
- data/lib/hecks/forms/field_shape.rb +3 -0
- data/lib/hecks/forms/page.rb +14 -0
- data/lib/hecks/forms/port_argument.rb +12 -0
- data/lib/hecks/forms/query_form_renderer.rb +63 -0
- data/lib/hecks/forms/record_renderer.rb +58 -0
- data/lib/hecks/forms/record_table.rb +27 -0
- data/lib/hecks/forms/reference_options.rb +24 -0
- data/lib/hecks/forms/value_object_shape.rb +10 -0
- data/lib/hecks/fqn.rb +58 -0
- data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
- data/lib/hecks/framework/oidc.json +15 -0
- data/lib/hecks/framework.rb +43 -20
- data/lib/hecks/freezer.rb +17 -1
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +159 -24
- data/lib/hecks/fuzzing/combination_miner.rb +59 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +109 -8
- data/lib/hecks/fuzzing/coverage_campaign.rb +56 -13
- data/lib/hecks/fuzzing/differential.rb +34 -0
- data/lib/hecks/fuzzing/domain_generator.rb +188 -11
- data/lib/hecks/fuzzing/era_boundary.rb +45 -15
- data/lib/hecks/fuzzing/form_census.rb +86 -0
- data/lib/hecks/fuzzing/generated_domain_check.rb +76 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +39 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +79 -22
- data/lib/hecks/fuzzing/nondeterministic.rb +13 -1
- data/lib/hecks/fuzzing/persistence_parity.rb +95 -3
- data/lib/hecks/fuzzing/properties/corrections.rb +25 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +158 -14
- data/lib/hecks/fuzzing/properties/guards.rb +44 -0
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +48 -0
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +18 -0
- data/lib/hecks/fuzzing/properties/outbox.rb +49 -11
- data/lib/hecks/fuzzing/properties/querying.rb +68 -14
- data/lib/hecks/fuzzing/properties.rb +24 -15
- data/lib/hecks/fuzzing/qa_settings.rb +12 -0
- data/lib/hecks/fuzzing/replay.rb +137 -29
- data/lib/hecks/fuzzing/rotation_priority.rb +41 -21
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +46 -20
- data/lib/hecks/fuzzing/self_consistency.rb +189 -40
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +12 -6
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +1 -1
- data/lib/hecks/fuzzing/sequence_generator.rb +47 -0
- data/lib/hecks/fuzzing/shrinker.rb +114 -2
- data/lib/hecks/fuzzing/structural_skips.rb +18 -4
- data/lib/hecks/fuzzing/sweep_depth.rb +8 -0
- data/lib/hecks/fuzzing/target_capabilities.rb +61 -14
- data/lib/hecks/fuzzing/value_generator.rb +98 -10
- data/lib/hecks/grammar/evolve.rb +178 -2
- data/lib/hecks/grammar.rb +46 -0
- data/lib/hecks/ir.rb +38 -7
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
- data/lib/hecks/literal.rb +32 -0
- data/lib/hecks/naming.rb +88 -7
- data/lib/hecks/ports/access_control.rb +5 -10
- data/lib/hecks/ports/authorization.rb +3 -6
- data/lib/hecks/ports/identity_assignment.rb +1 -2
- data/lib/hecks/ports/identity_resolution.rb +1 -2
- data/lib/hecks/ports/key_vault.port +6 -0
- data/lib/hecks/ports/key_vault.rb +58 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +6 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +38 -2
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +40 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +92 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +8 -0
- data/lib/hecks/ports/query/in_memory.rb +39 -6
- data/lib/hecks/ports/query/ordering.rb +15 -0
- data/lib/hecks/ports.rb +1 -0
- data/lib/hecks/projections/bootstrap_table.rb +43 -8
- data/lib/hecks/projections/diagrams.rb +243 -7
- data/lib/hecks/projections/glossary/html.rb +88 -0
- data/lib/hecks/projections/glossary/markdown.rb +47 -0
- data/lib/hecks/projections/glossary/mermaid.rb +48 -0
- data/lib/hecks/projections/glossary/sentences.rb +105 -0
- data/lib/hecks/projections/glossary.rb +161 -19
- data/lib/hecks/projections/model/deviations.rb +44 -0
- data/lib/hecks/projections/model.rb +51 -1
- data/lib/hecks/projections/oidc.rb +40 -3
- data/lib/hecks/projections/parser_table.rb +49 -8
- data/lib/hecks/projections/reference.rb +12 -0
- data/lib/hecks/projections/rust_vocabulary.rb +219 -16
- data/lib/hecks/projections/shape.rb +8 -0
- data/lib/hecks/projections/statements.rb +63 -16
- data/lib/hecks/projections/vocabulary.rb +17 -0
- data/lib/hecks/projector/cli_projector.rb +218 -10
- data/lib/hecks/projector/docs_projector.rb +145 -19
- data/lib/hecks/projector/exporter.rb +65 -11
- data/lib/hecks/projector/ir_projector.rb +6 -0
- data/lib/hecks/projector/narrate_projector.rb +136 -15
- data/lib/hecks/projector/target.rb +29 -5
- data/lib/hecks/projector.rb +74 -6
- data/lib/hecks/query_ir.rb +47 -0
- data/lib/hecks/query_specification/common/null_policy.rb +5 -3
- data/lib/hecks/rendering.rb +6 -0
- data/lib/hecks/router/namespace_installer.rb +13 -0
- data/lib/hecks/router.rb +55 -0
- data/lib/hecks/runtime/aggregate_lock.rb +9 -0
- data/lib/hecks/runtime/boot_gates.rb +18 -0
- data/lib/hecks/runtime/caller.rb +32 -0
- data/lib/hecks/runtime/capability_graph.rb +11 -0
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +23 -21
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +14 -15
- data/lib/hecks/runtime/command_interpreter.rb +42 -17
- data/lib/hecks/runtime/command_rules/admissibility.rb +165 -14
- data/lib/hecks/runtime/command_rules/arithmetic.rb +17 -5
- data/lib/hecks/runtime/command_rules/references.rb +118 -28
- data/lib/hecks/runtime/dependency_planning.rb +45 -0
- data/lib/hecks/runtime/dispatcher.rb +28 -50
- data/lib/hecks/runtime/entity_element.rb +161 -8
- data/lib/hecks/runtime/entity_interpreter.rb +44 -9
- data/lib/hecks/runtime/errors.rb +18 -4
- data/lib/hecks/runtime/event.rb +10 -5
- data/lib/hecks/runtime/identity.rb +71 -3
- data/lib/hecks/runtime/instance.rb +67 -7
- data/lib/hecks/runtime/interpreting.rb +13 -5
- data/lib/hecks/runtime/invocation.rb +118 -36
- data/lib/hecks/runtime/loader.rb +94 -8
- data/lib/hecks/runtime/outbox.rb +145 -7
- data/lib/hecks/runtime/policy_interpreter.rb +22 -9
- data/lib/hecks/runtime/port_operation_interpreter.rb +20 -0
- data/lib/hecks/runtime/query_interpreter.rb +40 -12
- data/lib/hecks/runtime/reaction_invocation.rb +53 -8
- data/lib/hecks/runtime/read_model_interpreter.rb +23 -7
- data/lib/hecks/runtime/rebuild_sweep.rb +28 -0
- data/lib/hecks/runtime/reference_hop.rb +42 -0
- data/lib/hecks/runtime/refusal_wording.rb +50 -0
- data/lib/hecks/runtime/registry/saga_persistence.rb +11 -0
- data/lib/hecks/runtime/registry/verification.rb +119 -4
- data/lib/hecks/runtime/registry.rb +157 -4
- data/lib/hecks/runtime/remote_dispatcher.rb +92 -6
- data/lib/hecks/runtime/routing.rb +27 -2
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +10 -12
- data/lib/hecks/runtime/saga_interpreter.rb +27 -13
- data/lib/hecks/runtime/tenant_check.rb +26 -6
- data/lib/hecks/runtime/tenant_scope.rb +18 -0
- data/lib/hecks/runtime/value/coercion.rb +255 -33
- data/lib/hecks/runtime/value/entity_list_coercion.rb +102 -30
- data/lib/hecks/runtime/value.rb +50 -0
- data/lib/hecks/runtime.rb +32 -0
- data/lib/hecks/storehouse.rb +305 -9
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks.rb +79 -1
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -0
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +12 -2
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +29 -5
- metadata +11 -5
- data/lib/hecks/codemod/legacy_dispatch_args.rb +0 -299
- data/lib/hecks/codemod/legacy_dispatch_recorder.rb +0 -186
- data/lib/hecks/deprecation.rb +0 -95
|
@@ -7,6 +7,8 @@ module Hecks
|
|
|
7
7
|
# A small, valid bluebook, written from a seed — so the QA loop can test
|
|
8
8
|
# construct combinations nobody has hand-authored yet.
|
|
9
9
|
#
|
|
10
|
+
# ## Why this exists
|
|
11
|
+
#
|
|
10
12
|
# The ledger's most productive moments were a new stress domain's first
|
|
11
13
|
# sweep: `corrections` found four bugs, `referral_chain` four,
|
|
12
14
|
# `tenant_ledger` two. Every one of those domains was written by hand,
|
|
@@ -20,14 +22,17 @@ module Hecks
|
|
|
20
22
|
# policy, a role), and renders the whole thing as ordinary bluebook
|
|
21
23
|
# source any runtime boots.
|
|
22
24
|
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
# `
|
|
30
|
-
#
|
|
25
|
+
# ## Blueprint, not source, is the unit
|
|
26
|
+
#
|
|
27
|
+
# `generate` answers a plain, JSON-shaped Hash (string keys) — a small
|
|
28
|
+
# IR of its own — and `render` turns it into source. That split is what
|
|
29
|
+
# makes a finding shrinkable at the domain level: `shrink_candidates`
|
|
30
|
+
# removes one element at a time (a query, a command, an entity, an
|
|
31
|
+
# aggregate, a given…) and `prune` drops whatever that removal left
|
|
32
|
+
# dangling, using the explicit `requires` tokens every dependent
|
|
33
|
+
# element carries. Nothing here parses Ruby back.
|
|
34
|
+
#
|
|
35
|
+
# ## One fixed domain name
|
|
31
36
|
#
|
|
32
37
|
# Every generated domain is the same domain name — `QaGenerated`, in a
|
|
33
38
|
# `qa_generated/bluebook/qa_generated.bluebook` directory — because the
|
|
@@ -60,6 +65,16 @@ module Hecks
|
|
|
60
65
|
|
|
61
66
|
module_function
|
|
62
67
|
|
|
68
|
+
# Builds a random blueprint from a seed and prunes it to a fixpoint,
|
|
69
|
+
# ready for `write` to render into a fresh `QaGenerated` domain.
|
|
70
|
+
#
|
|
71
|
+
# @param seed [Integer] the random seed; the same seed and `forms` always
|
|
72
|
+
# produce the same blueprint
|
|
73
|
+
# @param forms [Array<String, Symbol>, nil] the `FormCensus::FORMS` names to
|
|
74
|
+
# force onto the primary aggregate; two random forms from `FORMS` when nil
|
|
75
|
+
# @return [Hash{String => Object}] the pruned blueprint, with `"seed"` and
|
|
76
|
+
# `"forms"` merged in
|
|
77
|
+
# @raise [ArgumentError] if `forms` names anything outside `FORMS`
|
|
63
78
|
def generate(seed:, forms: nil)
|
|
64
79
|
random = Random.new(seed)
|
|
65
80
|
forms = Array(forms || FORMS.sample(2, random: random)).map(&:to_s)
|
|
@@ -77,6 +92,11 @@ module Hecks
|
|
|
77
92
|
# A blueprint carrying `"source"` is bluebook text someone else wrote
|
|
78
93
|
# (`bin/qa_mine_combinations`' agent): it is adopted, not rendered,
|
|
79
94
|
# and its empty `aggregates`/`policies` leave nothing to shrink.
|
|
95
|
+
#
|
|
96
|
+
# @param blueprint [Hash{String => Object}] a blueprint as `generate` answers,
|
|
97
|
+
# or one carrying a `"source"` key instead of `"aggregates"`/`"policies"`
|
|
98
|
+
# @param root [String] the directory to write the domain and blueprint under
|
|
99
|
+
# @return [String] the written domain's directory (`<root>/qa_generated`)
|
|
80
100
|
def write(blueprint, root)
|
|
81
101
|
domain = File.join(root, DIRECTORY)
|
|
82
102
|
FileUtils.rm_rf(domain)
|
|
@@ -89,12 +109,21 @@ module Hecks
|
|
|
89
109
|
|
|
90
110
|
# Renames an outside bluebook's own chapter to `QaGenerated`, the one
|
|
91
111
|
# name the scratch crate and child processes are built around.
|
|
112
|
+
#
|
|
113
|
+
# @param source [String] bluebook source text declaring one chapter
|
|
114
|
+
# @return [String] `source` with its `Hecks.bluebook "..."` chapter name
|
|
115
|
+
# replaced by `"QaGenerated"`
|
|
92
116
|
def adopt(source)
|
|
93
117
|
source.sub(/Hecks\.bluebook\s*\(?\s*(["'])[^"']+\1/) { "Hecks.bluebook #{DOMAIN_NAME.inspect}" }
|
|
94
118
|
end
|
|
95
119
|
|
|
96
120
|
# ── rendering ───────────────────────────────────────────────────
|
|
97
121
|
|
|
122
|
+
# Renders a whole blueprint into one bluebook source file.
|
|
123
|
+
#
|
|
124
|
+
# @param blueprint [Hash{String => Object}] a blueprint as `generate` answers
|
|
125
|
+
# @return [String] the complete `Hecks.bluebook "QaGenerated" do ... end` source,
|
|
126
|
+
# ending in a newline
|
|
98
127
|
def render(blueprint)
|
|
99
128
|
out = []
|
|
100
129
|
out << "# GENERATED by Hecks::Fuzzing::DomainGenerator — seed #{blueprint['seed']}, forms " \
|
|
@@ -109,6 +138,11 @@ module Hecks
|
|
|
109
138
|
"#{out.join("\n")}\n"
|
|
110
139
|
end
|
|
111
140
|
|
|
141
|
+
# Renders one aggregate's whole `aggregate ... end` block, including
|
|
142
|
+
# every value object, entity, command and query it declares.
|
|
143
|
+
#
|
|
144
|
+
# @param aggregate [Hash{String => Object}] one aggregate's blueprint entry
|
|
145
|
+
# @return [Array<String>] source lines, unindented at the block's own `aggregate`/`end`
|
|
112
146
|
def render_aggregate(aggregate)
|
|
113
147
|
out = aggregate_header(aggregate)
|
|
114
148
|
aggregate["attributes"].each { |attribute| out << " #{render_attribute(attribute)}" }
|
|
@@ -122,12 +156,22 @@ module Hecks
|
|
|
122
156
|
out << " end"
|
|
123
157
|
end
|
|
124
158
|
|
|
159
|
+
# Renders an aggregate block's opening lines, up through `identified_by`.
|
|
160
|
+
#
|
|
161
|
+
# @param aggregate [Hash{String => Object}] one aggregate's blueprint entry
|
|
162
|
+
# @return [Array<String>] the `aggregate ... do`, `description` and
|
|
163
|
+
# `identified_by` lines
|
|
125
164
|
def aggregate_header(aggregate)
|
|
126
165
|
["", " aggregate #{aggregate['name'].inspect} do",
|
|
127
166
|
" description #{"A generated #{aggregate['name'].downcase}.".inspect}", "",
|
|
128
167
|
" identified_by #{aggregate['identity'].map { |part| ":#{part}" }.join(', ')}"]
|
|
129
168
|
end
|
|
130
169
|
|
|
170
|
+
# Renders one entity's whole `entity ... end` block.
|
|
171
|
+
#
|
|
172
|
+
# @param entity [Hash{String => Object}] one entity's blueprint entry, as built
|
|
173
|
+
# by `Builder#entity`
|
|
174
|
+
# @return [Array<String>] source lines, unindented at the block's own `entity`/`end`
|
|
131
175
|
def render_entity(entity)
|
|
132
176
|
out = ["", " entity #{entity['name'].inspect} do",
|
|
133
177
|
" description #{"A generated #{entity['name'].downcase}.".inspect}"]
|
|
@@ -138,6 +182,12 @@ module Hecks
|
|
|
138
182
|
out << " end"
|
|
139
183
|
end
|
|
140
184
|
|
|
185
|
+
# Renders one `attribute` declaration line.
|
|
186
|
+
#
|
|
187
|
+
# @param attribute [Hash{String => Object}] `"name"`, `"type"`, and optional
|
|
188
|
+
# `"list"`, `"optional"`, `"default"` keys
|
|
189
|
+
# @return [String] one `attribute :name, Type` line, with `list_of`, `optional:`
|
|
190
|
+
# and `default:` added as the Hash calls for
|
|
141
191
|
def render_attribute(attribute)
|
|
142
192
|
type = attribute["list"] ? "list_of(#{attribute['type']})" : attribute["type"]
|
|
143
193
|
line = "attribute :#{attribute['name']}, #{type}"
|
|
@@ -146,6 +196,13 @@ module Hecks
|
|
|
146
196
|
line
|
|
147
197
|
end
|
|
148
198
|
|
|
199
|
+
# Renders one `value_object ... end` block.
|
|
200
|
+
#
|
|
201
|
+
# @param name [String] the value object's declared name
|
|
202
|
+
# @param value_object [Hash{String => Object}] `"kind"` (`"string"`, `"positive"`,
|
|
203
|
+
# `"integer"` or `"closed"`) and, for `"closed"`, a `"members"` array
|
|
204
|
+
# @param indent [String] leading whitespace for every rendered line
|
|
205
|
+
# @return [Array<String>] source lines, indented by `indent`
|
|
149
206
|
def render_value_object(name, value_object, indent)
|
|
150
207
|
body =
|
|
151
208
|
case value_object["kind"]
|
|
@@ -162,6 +219,12 @@ module Hecks
|
|
|
162
219
|
["", "#{indent}value_object #{name.inspect} do", *body.map { |line| "#{indent} #{line}" }, "#{indent}end"]
|
|
163
220
|
end
|
|
164
221
|
|
|
222
|
+
# Renders one `lifecycle ... end` block.
|
|
223
|
+
#
|
|
224
|
+
# @param lifecycle [Hash{String => Object}] `"field"`, `"default"` and
|
|
225
|
+
# `"transitions"` (each `"command"`, `"to"`, `"from"`)
|
|
226
|
+
# @param indent [String] leading whitespace for every rendered line
|
|
227
|
+
# @return [Array<String>] source lines, indented by `indent`
|
|
165
228
|
def render_lifecycle(lifecycle, indent)
|
|
166
229
|
out = ["", "#{indent}lifecycle :#{lifecycle['field']}, default: #{lifecycle['default'].inspect} do"]
|
|
167
230
|
lifecycle["transitions"].each do |transition|
|
|
@@ -171,6 +234,15 @@ module Hecks
|
|
|
171
234
|
out << "#{indent}end"
|
|
172
235
|
end
|
|
173
236
|
|
|
237
|
+
# Renders one `command ... end` block.
|
|
238
|
+
#
|
|
239
|
+
# @param command [Hash{String => Object}] `"name"`, `"creates"`, `"role"`,
|
|
240
|
+
# `"references"`, `"args"`, `"givens"`, `"sets"` and `"emits"`
|
|
241
|
+
# @param self_name [String, nil] the owning aggregate's name, added as a
|
|
242
|
+
# `reference_to` on a non-creating entity command; nil for an
|
|
243
|
+
# aggregate-level command, which needs no reference to itself
|
|
244
|
+
# @param indent [String] leading whitespace for every rendered line
|
|
245
|
+
# @return [Array<String>] source lines, indented by `indent`
|
|
174
246
|
def render_command(command, self_name, indent)
|
|
175
247
|
out = ["", "#{indent}command #{command['name'].inspect} do"]
|
|
176
248
|
out << "#{indent} role #{command['role'].inspect}" if command["role"]
|
|
@@ -184,6 +256,12 @@ module Hecks
|
|
|
184
256
|
out << "#{indent}end"
|
|
185
257
|
end
|
|
186
258
|
|
|
259
|
+
# Renders one `sets` line inside a command block.
|
|
260
|
+
#
|
|
261
|
+
# @param set [Hash{String => Object}] `"target"`, plus either `"to"` (rename) or
|
|
262
|
+
# `"append"` (a Hash of field => value token to append with); neither means a
|
|
263
|
+
# bare positional set
|
|
264
|
+
# @return [String] one `sets :target[, to: :value | append: { ... }]` line
|
|
187
265
|
def render_set(set)
|
|
188
266
|
return "sets :#{set['target']}, to: :#{set['to']}" if set["to"]
|
|
189
267
|
return "sets :#{set['target']}, append: { #{set['append'].map { |k, v| "#{k}: :#{v}" }.join(', ')} }" if set["append"]
|
|
@@ -191,6 +269,11 @@ module Hecks
|
|
|
191
269
|
"sets :#{set['target']}"
|
|
192
270
|
end
|
|
193
271
|
|
|
272
|
+
# Renders one `query ... end` block.
|
|
273
|
+
#
|
|
274
|
+
# @param query [Hash{String => Object}] `"name"`, `"wheres"` (each `"field"`,
|
|
275
|
+
# `"value"`) and `"order_by"`
|
|
276
|
+
# @return [Array<String>] source lines, unindented at the block's own `query`/`end`
|
|
194
277
|
def render_query(query)
|
|
195
278
|
out = ["", " query #{query['name'].inspect} do", " description #{"Generated #{query['name']}.".inspect}"]
|
|
196
279
|
query["wheres"].each do |where|
|
|
@@ -200,6 +283,10 @@ module Hecks
|
|
|
200
283
|
out << " end"
|
|
201
284
|
end
|
|
202
285
|
|
|
286
|
+
# Renders one `policy ... end` block.
|
|
287
|
+
#
|
|
288
|
+
# @param policy [Hash{String => Object}] `"name"`, `"on"` and `"trigger"`
|
|
289
|
+
# @return [Array<String>] source lines, unindented at the block's own `policy`/`end`
|
|
203
290
|
def render_policy(policy)
|
|
204
291
|
["", " policy #{policy['name'].inspect} do", " on #{policy['on']}", " trigger #{policy['trigger']}", " end"]
|
|
205
292
|
end
|
|
@@ -209,10 +296,19 @@ module Hecks
|
|
|
209
296
|
# Every blueprint one removal smaller, each already pruned. Identity
|
|
210
297
|
# attributes and creating commands are never offered: without them
|
|
211
298
|
# there is no domain left to dispatch against.
|
|
299
|
+
#
|
|
300
|
+
# @param blueprint [Hash{String => Object}] a blueprint as `generate` answers
|
|
301
|
+
# @return [Array<Hash{String => Object}>] one pruned blueprint per candidate removal,
|
|
302
|
+
# deduplicated and never including `blueprint` itself
|
|
212
303
|
def shrink_candidates(blueprint)
|
|
213
304
|
removals(blueprint).map { |path| prune(remove_at(blueprint, path)) }.uniq.reject { |candidate| candidate == blueprint }
|
|
214
305
|
end
|
|
215
306
|
|
|
307
|
+
# Every path into `blueprint` naming one element a shrink could remove.
|
|
308
|
+
#
|
|
309
|
+
# @param blueprint [Hash{String => Object}] a blueprint as `generate` answers
|
|
310
|
+
# @return [Array<Array<String, Integer>>] one path per removable element, each
|
|
311
|
+
# a sequence of Hash keys and Array indices `Hash#dig` can follow
|
|
216
312
|
def removals(blueprint)
|
|
217
313
|
paths = blueprint["policies"].each_index.map { |i| ["policies", i] }
|
|
218
314
|
blueprint["aggregates"].each_with_index do |aggregate, a|
|
|
@@ -229,6 +325,12 @@ module Hecks
|
|
|
229
325
|
paths
|
|
230
326
|
end
|
|
231
327
|
|
|
328
|
+
# `removals`'s own entity half — one path per entity's lifecycle (if any) and
|
|
329
|
+
# per entity command.
|
|
330
|
+
#
|
|
331
|
+
# @param aggregate [Hash{String => Object}] one aggregate's blueprint entry
|
|
332
|
+
# @param at [Array<String, Integer>] the path prefix to `aggregate` itself
|
|
333
|
+
# @return [Array<Array<String, Integer>>] one path per removable entity element
|
|
232
334
|
def entity_removals(aggregate, at)
|
|
233
335
|
aggregate["entities"].each_with_index.flat_map do |entity, e|
|
|
234
336
|
paths = entity["lifecycle"] ? [[*at, "entities", e, "lifecycle"]] : []
|
|
@@ -236,6 +338,13 @@ module Hecks
|
|
|
236
338
|
end
|
|
237
339
|
end
|
|
238
340
|
|
|
341
|
+
# `removals`'s own command half — one path for the whole command (unless it
|
|
342
|
+
# creates the aggregate), one per given, and one for its role or its last
|
|
343
|
+
# extra emit.
|
|
344
|
+
#
|
|
345
|
+
# @param command [Hash{String => Object}] one command's blueprint entry
|
|
346
|
+
# @param at [Array<String, Integer>] the path prefix to `command` itself
|
|
347
|
+
# @return [Array<Array<String, Integer>>] one path per removable command element
|
|
239
348
|
def command_removals(command, at)
|
|
240
349
|
paths = command["creates"] ? [] : [at]
|
|
241
350
|
command["givens"].each_index { |g| paths << [*at, "givens", g] }
|
|
@@ -244,6 +353,13 @@ module Hecks
|
|
|
244
353
|
paths
|
|
245
354
|
end
|
|
246
355
|
|
|
356
|
+
# Deep-copies `blueprint` and deletes whatever `path` names.
|
|
357
|
+
#
|
|
358
|
+
# @param blueprint [Hash{String => Object}] a blueprint as `generate` answers
|
|
359
|
+
# @param path [Array<String, Integer>] a path as `removals` returns, its last
|
|
360
|
+
# element the Hash key or Array index to delete
|
|
361
|
+
# @return [Hash{String => Object}] a deep copy of `blueprint` with that one
|
|
362
|
+
# element removed
|
|
247
363
|
def remove_at(blueprint, path)
|
|
248
364
|
copy = JSON.parse(JSON.generate(blueprint))
|
|
249
365
|
*parents, last = path
|
|
@@ -257,6 +373,11 @@ module Hecks
|
|
|
257
373
|
# what must still exist (`attribute:Ticket.score`, `lifecycle:Desk`,
|
|
258
374
|
# `reference:Ticket->Desk`, `command:Ticket.Close`, …) — so this is
|
|
259
375
|
# set arithmetic over those tokens, never a reading of the source.
|
|
376
|
+
#
|
|
377
|
+
# @param blueprint [Hash{String => Object}] a blueprint as `generate` answers
|
|
378
|
+
# @return [Hash{String => Object}] a deep copy of `blueprint` with every element
|
|
379
|
+
# whose `requires` tokens are not all still present removed, repeated until
|
|
380
|
+
# nothing more can be dropped
|
|
260
381
|
def prune(blueprint)
|
|
261
382
|
current = JSON.parse(JSON.generate(blueprint))
|
|
262
383
|
loop do
|
|
@@ -268,12 +389,28 @@ module Hecks
|
|
|
268
389
|
current
|
|
269
390
|
end
|
|
270
391
|
|
|
392
|
+
# One pass of `prune` — drops every policy, then every aggregate element,
|
|
393
|
+
# whose `requires` tokens are not all in `available`.
|
|
394
|
+
#
|
|
395
|
+
# @param blueprint [Hash{String => Object}] the blueprint being pruned, mutated
|
|
396
|
+
# in place
|
|
397
|
+
# @param available [Set<String>] every token `tokens` finds still declared
|
|
398
|
+
# @return [void]
|
|
271
399
|
def prune_once!(blueprint, available)
|
|
272
400
|
keep = ->(item) { Array(item["requires"]).all? { |token| available.include?(token) } }
|
|
273
401
|
blueprint["policies"].select!(&keep)
|
|
274
402
|
blueprint["aggregates"].each { |aggregate| prune_aggregate!(aggregate, available, keep) }
|
|
275
403
|
end
|
|
276
404
|
|
|
405
|
+
# `prune_once!`'s own per-aggregate pass — drops attributes, invariants,
|
|
406
|
+
# entities, unsatisfied references and empty queries, then recurses into
|
|
407
|
+
# each remaining lifecycle and command.
|
|
408
|
+
#
|
|
409
|
+
# @param aggregate [Hash{String => Object}] one aggregate's blueprint entry,
|
|
410
|
+
# mutated in place
|
|
411
|
+
# @param available [Set<String>] every token `tokens` finds still declared
|
|
412
|
+
# @param keep [Proc] the `requires`-satisfied predicate `prune_once!` built
|
|
413
|
+
# @return [void]
|
|
277
414
|
def prune_aggregate!(aggregate, available, keep)
|
|
278
415
|
aggregate["references"].select! { |target| available.include?("aggregate:#{target}") }
|
|
279
416
|
%w[attributes invariants entities].each { |key| aggregate[key].select!(&keep) }
|
|
@@ -285,6 +422,14 @@ module Hecks
|
|
|
285
422
|
end
|
|
286
423
|
end
|
|
287
424
|
|
|
425
|
+
# Drops transitions whose own command no longer exists, then whatever that
|
|
426
|
+
# leaves unreachable from the default state; clears `"lifecycle"` outright
|
|
427
|
+
# once no transition survives.
|
|
428
|
+
#
|
|
429
|
+
# @param owner [Hash{String => Object}] an aggregate or entity blueprint entry,
|
|
430
|
+
# mutated in place
|
|
431
|
+
# @param available [Set<String>] every token `tokens` finds still declared
|
|
432
|
+
# @return [void]
|
|
288
433
|
def prune_lifecycle!(owner, available)
|
|
289
434
|
return unless owner["lifecycle"]
|
|
290
435
|
|
|
@@ -297,9 +442,14 @@ module Hecks
|
|
|
297
442
|
|
|
298
443
|
# Every state a path from the default reaches. A transition out of a
|
|
299
444
|
# state nothing reaches can never fire, so a removal that orphans one
|
|
300
|
-
# goes too
|
|
301
|
-
# behind
|
|
302
|
-
#
|
|
445
|
+
# goes too — without this, shrinking away `Close` leaves `Reopen from
|
|
446
|
+
# closed` behind, the shape `qa/stress_domains/generated_revalued_shape`
|
|
447
|
+
# was promoted with, which `bin/model_check` reports as a dead transition.
|
|
448
|
+
#
|
|
449
|
+
# @param default [String] the lifecycle's default state name
|
|
450
|
+
# @param transitions [Array<Hash{String => Object}>] the lifecycle's surviving
|
|
451
|
+
# transitions, each with `"from"` (an Array of state names) and `"to"`
|
|
452
|
+
# @return [Array<String>] every state reachable from `default`, `default` included
|
|
303
453
|
def reachable_states(default, transitions)
|
|
304
454
|
reachable = [default]
|
|
305
455
|
loop do
|
|
@@ -310,12 +460,25 @@ module Hecks
|
|
|
310
460
|
end
|
|
311
461
|
end
|
|
312
462
|
|
|
463
|
+
# Drops a command's own unsatisfied references, args, givens and sets.
|
|
464
|
+
#
|
|
465
|
+
# @param command [Hash{String => Object}] one command's blueprint entry,
|
|
466
|
+
# mutated in place
|
|
467
|
+
# @param available [Set<String>] every token `tokens` finds still declared
|
|
468
|
+
# @return [void]
|
|
313
469
|
def prune_command!(command, available)
|
|
314
470
|
keep = ->(item) { Array(item["requires"]).all? { |token| available.include?(token) } }
|
|
315
471
|
command["references"].select! { |target| available.include?("aggregate:#{target}") }
|
|
316
472
|
%w[args givens sets].each { |key| command[key].select!(&keep) }
|
|
317
473
|
end
|
|
318
474
|
|
|
475
|
+
# Every declaration token `blueprint` currently makes available — what a
|
|
476
|
+
# `requires` entry elsewhere in the blueprint can point at.
|
|
477
|
+
#
|
|
478
|
+
# @param blueprint [Hash{String => Object}] a blueprint as `generate` answers
|
|
479
|
+
# @return [Set<String>] tokens such as `"aggregate:Ticket"`,
|
|
480
|
+
# `"attribute:Ticket.score"`, `"lifecycle:Desk"`, `"reference:Ticket->Desk"`,
|
|
481
|
+
# `"command:Ticket.Close"` and `"event:Ticket.TicketOpened"`
|
|
319
482
|
def tokens(blueprint)
|
|
320
483
|
blueprint["aggregates"].each_with_object(Set.new) do |aggregate, set|
|
|
321
484
|
name = aggregate["name"]
|
|
@@ -336,6 +499,11 @@ module Hecks
|
|
|
336
499
|
end
|
|
337
500
|
end
|
|
338
501
|
|
|
502
|
+
# Converts a rendered `CamelCase` aggregate/entity name to the `snake_case`
|
|
503
|
+
# form its own `reference_to`/`sets` field uses.
|
|
504
|
+
#
|
|
505
|
+
# @param name [String] a `CamelCase` name
|
|
506
|
+
# @return [String] `name` in `snake_case`
|
|
339
507
|
def snake(name) = name.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
|
|
340
508
|
|
|
341
509
|
# ── building ────────────────────────────────────────────────────
|
|
@@ -343,6 +511,10 @@ module Hecks
|
|
|
343
511
|
# One seeded pass: the aggregates the forms need, the forms forced
|
|
344
512
|
# onto the first ("primary") aggregate, then extras everywhere.
|
|
345
513
|
class Builder
|
|
514
|
+
# @param random [Random] the seeded generator every random choice this
|
|
515
|
+
# builder makes draws from
|
|
516
|
+
# @param forms [Array<String>] the `FormCensus::FORMS` names to force onto
|
|
517
|
+
# the primary aggregate
|
|
346
518
|
def initialize(random, forms)
|
|
347
519
|
@random = random
|
|
348
520
|
@forms = forms
|
|
@@ -350,6 +522,11 @@ module Hecks
|
|
|
350
522
|
@policies = []
|
|
351
523
|
end
|
|
352
524
|
|
|
525
|
+
# Builds the aggregates the forced forms need, applies each forced form to
|
|
526
|
+
# the primary aggregate, then sprinkles extras onto every aggregate.
|
|
527
|
+
#
|
|
528
|
+
# @return [Hash{String => Object}] `"aggregates"` and `"policies"`, the two
|
|
529
|
+
# keys `DomainGenerator.generate` merges `"seed"`/`"forms"` into
|
|
353
530
|
def build
|
|
354
531
|
chain = @forms.intersect?(CHAIN_FORMS)
|
|
355
532
|
count = if chain then 3
|
|
@@ -8,7 +8,9 @@ module Hecks
|
|
|
8
8
|
# target's own real, already-configured `PostgresEra` ledger:
|
|
9
9
|
# does any ancestor era still hold writes nobody has merged forward?
|
|
10
10
|
#
|
|
11
|
-
#
|
|
11
|
+
# ## The bug this targets, named exactly
|
|
12
|
+
#
|
|
13
|
+
# Minting a new era (an attribute
|
|
12
14
|
# or aggregate addition — `StorageShape.project`, lib/hecks/ports/
|
|
13
15
|
# persistence/plugins/era/storage_shape.rb) advances the readable head
|
|
14
16
|
# to a new partition; an old checkout, or a process that boots slower
|
|
@@ -26,6 +28,8 @@ module Hecks
|
|
|
26
28
|
# line already answers by hand, run as an ordinary sweep Check instead
|
|
27
29
|
# of only when a human remembers to ask.
|
|
28
30
|
#
|
|
31
|
+
# ## Connection and safety
|
|
32
|
+
#
|
|
29
33
|
# The target's real database, read-only, never a disposable one — every
|
|
30
34
|
# other Postgres-touching mode here (`persistence_parity`,
|
|
31
35
|
# `adapter_parity_sqlite`) owns a throwaway schema for the exact
|
|
@@ -44,19 +48,27 @@ module Hecks
|
|
|
44
48
|
module EraBoundary
|
|
45
49
|
module_function
|
|
46
50
|
|
|
51
|
+
# Reports whether any ancestor `PostgresEra` era still holds writes the
|
|
52
|
+
# head has never merged forward, for one target's real database.
|
|
53
|
+
#
|
|
47
54
|
# `{ checked: true, diverged_total:, breakdown: [{ordinal:, diverged:}] }`
|
|
48
55
|
# or `{ checked: false, kind:, reason: "..." }`. A `true` with
|
|
49
56
|
# `diverged_total.positive?` is the finding this module exists to
|
|
50
57
|
# surface: real post-cut writes an ancestor era is still holding.
|
|
51
58
|
#
|
|
52
|
-
# `kind:`
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
# found nothing: counted toward `sweep.made` and the target's clean
|
|
59
|
+
# `kind:` distinguishes the two different reasons `checked: false` can
|
|
60
|
+
# occur — without it, `bin/qa_sweep` would log a refused connection
|
|
61
|
+
# and a `Lineage` defect exactly like an audit that ran and found
|
|
62
|
+
# nothing, counting either toward `sweep.made` and the target's clean
|
|
57
63
|
# streak. `:not_applicable` is a target with no lineage to audit at
|
|
58
64
|
# all; `:error` is the audit failing to run, which the caller
|
|
59
65
|
# surfaces as a finding rather than holding.
|
|
66
|
+
#
|
|
67
|
+
# @param domain_path [String] filesystem path to the target domain's bluebook
|
|
68
|
+
# directory, or a directory containing one
|
|
69
|
+
# @return [Hash{Symbol => Object}] on success, `{checked: true, era_count: Integer,
|
|
70
|
+
# breakdown: Array<Hash{ordinal: Integer, diverged: Integer}>, diverged_total: Integer}`;
|
|
71
|
+
# otherwise `{checked: false, kind: Symbol, reason: String}`
|
|
60
72
|
def diverged_ancestor_writes(domain_path)
|
|
61
73
|
registry, directory = load_registry(domain_path)
|
|
62
74
|
bluebook = registry.bluebooks.values.first
|
|
@@ -92,22 +104,40 @@ module Hecks
|
|
|
92
104
|
end
|
|
93
105
|
rescue StandardError => e
|
|
94
106
|
# Could not audit is not the same as nothing to audit — a refused
|
|
95
|
-
# connection, a `Lineage` defect, malformed `.world` settings.
|
|
96
|
-
#
|
|
107
|
+
# connection, a `Lineage` defect, malformed `.world` settings. Each
|
|
108
|
+
# of these answers `:error`, not the benign `:not_applicable` shape,
|
|
109
|
+
# so `bin/qa_sweep` surfaces it as a finding rather than holding it
|
|
110
|
+
# as a clean audit.
|
|
97
111
|
failed("#{e.class}: #{e.message}")
|
|
98
112
|
end
|
|
99
113
|
|
|
100
|
-
#
|
|
101
|
-
# ancestor era can carry post-cut writes
|
|
102
|
-
#
|
|
114
|
+
# Builds the `checked: false` result for a target with no PostgresEra
|
|
115
|
+
# lineage to audit — no ancestor era can carry post-cut writes when
|
|
116
|
+
# there is no lineage at all. Not a finding, and not evidence of
|
|
117
|
+
# anything either: the caller logs no Check at all.
|
|
118
|
+
#
|
|
119
|
+
# @param reason [String] human-readable explanation of why no lineage applies
|
|
120
|
+
# @return [Hash{Symbol => Object}] `{checked: false, kind: :not_applicable, reason: String}`
|
|
103
121
|
def not_applicable(reason) = { checked: false, kind: :not_applicable, reason: reason }
|
|
104
122
|
|
|
105
|
-
#
|
|
123
|
+
# Builds the `checked: false` result for an audit that could not run
|
|
124
|
+
# — reported as a finding, never held.
|
|
125
|
+
#
|
|
126
|
+
# @param reason [String] human-readable explanation of what failed, typically
|
|
127
|
+
# `"#{exception.class}: #{exception.message}"`
|
|
128
|
+
# @return [Hash{Symbol => Object}] `{checked: false, kind: :error, reason: String}`
|
|
106
129
|
def failed(reason) = { checked: false, kind: :error, reason: reason }
|
|
107
130
|
|
|
108
|
-
#
|
|
109
|
-
#
|
|
110
|
-
#
|
|
131
|
+
# Boots a fresh registry for the domain at `domain_path` — the same
|
|
132
|
+
# load `bin/merge_tail` itself performs (that script's own top half),
|
|
133
|
+
# never the ledger's own registry (this asks about the swept target's
|
|
134
|
+
# lineage, not QualityControl's own).
|
|
135
|
+
#
|
|
136
|
+
# @param domain_path [String] filesystem path to the target domain's bluebook
|
|
137
|
+
# directory, or a directory containing one
|
|
138
|
+
# @return [Array(Runtime::Registry, String)] the booted registry and the resolved
|
|
139
|
+
# bluebook directory path
|
|
140
|
+
# @raise [Errno::ENOENT] if `domain_path` names no existing domain directory
|
|
111
141
|
def load_registry(domain_path)
|
|
112
142
|
loading = Hecks::Ports::Loading.bootstrap
|
|
113
143
|
directory = loading.bluebook_directory(domain_path)
|