hecks 1.3.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 +72 -7
- data/lib/hecks/adapters/driven/d1.rb +187 -23
- data/lib/hecks/adapters/driven/folder.rb +83 -10
- data/lib/hecks/adapters/driven/google_authentication.rb +33 -12
- data/lib/hecks/adapters/driven/governance_authorization.rb +87 -18
- data/lib/hecks/adapters/driven/heki/journal.rb +13 -4
- data/lib/hecks/adapters/driven/heki/saga_store.rb +56 -10
- data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
- data/lib/hecks/adapters/driven/heki.rb +106 -9
- data/lib/hecks/adapters/driven/identity_registry.rb +12 -2
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +25 -3
- 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 +67 -14
- data/lib/hecks/adapters/driven/lambda.rb +82 -35
- data/lib/hecks/adapters/driven/local_storage.rb +83 -10
- data/lib/hecks/adapters/driven/memory.rb +205 -9
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +21 -1
- data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
- data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
- data/lib/hecks/adapters/driven/postgres.rb +175 -28
- data/lib/hecks/adapters/driven/prism.rb +50 -11
- data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
- data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
- data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
- data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
- data/lib/hecks/adapters/driven/sqlite.rb +181 -21
- 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 +6 -4
- data/lib/hecks/adapters/driving/github_webhook.rb +31 -18
- data/lib/hecks/behaviors/dsl.rb +60 -2
- data/lib/hecks/behaviors/expectations.rb +190 -29
- data/lib/hecks/behaviors/ir.rb +12 -1
- data/lib/hecks/behaviors/rspec.rb +9 -1
- data/lib/hecks/behaviors/runner.rb +21 -2
- data/lib/hecks/behaviors.rb +9 -1
- data/lib/hecks/bluebook/aggregate.rb +43 -13
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +17 -10
- data/lib/hecks/bluebook/assembly/build.rb +26 -5
- data/lib/hecks/bluebook/assembly/contract.rb +98 -23
- data/lib/hecks/bluebook/assembly/contracts.rb +59 -52
- data/lib/hecks/bluebook/assembly/marks.rb +159 -30
- data/lib/hecks/bluebook/assembly/specializer.rb +38 -21
- data/lib/hecks/bluebook/assembly.rb +32 -14
- data/lib/hecks/bluebook/attribute.rb +26 -12
- data/lib/hecks/bluebook/behaviour/aggregate.rb +43 -11
- data/lib/hecks/bluebook/behaviour/attribute.rb +18 -5
- data/lib/hecks/bluebook/behaviour/chapter.rb +76 -5
- data/lib/hecks/bluebook/behaviour/command.rb +55 -25
- data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -7
- data/lib/hecks/bluebook/behaviour/entity.rb +20 -8
- data/lib/hecks/bluebook/behaviour/hexagon.rb +30 -4
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +27 -6
- data/lib/hecks/bluebook/behaviour/policy.rb +42 -17
- data/lib/hecks/bluebook/behaviour/process_manager.rb +39 -8
- data/lib/hecks/bluebook/behaviour/query.rb +6 -1
- data/lib/hecks/bluebook/behaviour/read_model.rb +29 -8
- data/lib/hecks/bluebook/behaviour/traits.rb +48 -12
- data/lib/hecks/bluebook/behaviour/value_object.rb +21 -9
- data/lib/hecks/bluebook/capabilities.rb +27 -0
- data/lib/hecks/bluebook/chapter.rb +51 -9
- data/lib/hecks/bluebook/command.rb +62 -17
- data/lib/hecks/bluebook/domain_port.rb +34 -9
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +282 -123
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +112 -75
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +81 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +486 -117
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +179 -47
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +284 -122
- data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
- data/lib/hecks/bluebook/dsl/entity_builder.rb +191 -61
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +130 -30
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +38 -21
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
- data/lib/hecks/bluebook/dsl/policy_builder.rb +86 -36
- data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +111 -47
- data/lib/hecks/bluebook/dsl/query_builder.rb +37 -8
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +127 -52
- data/lib/hecks/bluebook/dsl/rule_reference.rb +97 -43
- data/lib/hecks/bluebook/dsl/translation_builder.rb +150 -44
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +68 -20
- data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
- data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
- data/lib/hecks/bluebook/entity.rb +40 -11
- data/lib/hecks/bluebook/expression/ast_json.rb +128 -36
- data/lib/hecks/bluebook/expression/ast_reader.rb +32 -3
- data/lib/hecks/bluebook/expression/canonical_form.rb +55 -16
- data/lib/hecks/bluebook/expression/evaluator.rb +221 -43
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +54 -18
- data/lib/hecks/bluebook/expression/resolver.rb +369 -128
- data/lib/hecks/bluebook/hexagon.rb +35 -1
- data/lib/hecks/bluebook/lifecycle.rb +12 -1
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/judge.rb +126 -108
- data/lib/hecks/bluebook/meta_validator/plan.rb +81 -46
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +3 -2
- data/lib/hecks/bluebook/meta_validator/readings.rb +200 -50
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +68 -41
- data/lib/hecks/bluebook/meta_validator/shapes.rb +166 -21
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +286 -46
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +11 -10
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -5
- data/lib/hecks/bluebook/meta_validator.rb +235 -139
- data/lib/hecks/bluebook/model_check.rb +434 -104
- data/lib/hecks/bluebook/pattern_subset.rb +32 -10
- data/lib/hecks/bluebook/policy.rb +15 -13
- data/lib/hecks/bluebook/process_manager.rb +27 -14
- 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 +50 -6
- data/lib/hecks/bluebook/query.rb +31 -4
- data/lib/hecks/bluebook/read_model.rb +35 -15
- data/lib/hecks/bluebook/reference.rb +26 -13
- data/lib/hecks/bluebook/smoke_test.rb +46 -23
- data/lib/hecks/bluebook/synthesizer.rb +46 -12
- data/lib/hecks/bluebook/translation.rb +34 -5
- data/lib/hecks/bluebook/value_object.rb +29 -11
- data/lib/hecks/bluebook.rb +5 -6
- data/lib/hecks/codemod.rb +138 -50
- data/lib/hecks/construct.rb +21 -7
- data/lib/hecks/corpus.rb +438 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
- data/lib/hecks/doc/reference.rb +200 -31
- data/lib/hecks/embryonaut_bluebook.rb +38 -15
- data/lib/hecks/facade/cli_door.rb +69 -10
- data/lib/hecks/facade/cli_runner.rb +105 -24
- data/lib/hecks/facade/command_request.rb +23 -0
- data/lib/hecks/facade/handle.rb +155 -35
- data/lib/hecks/facade/json_door.rb +106 -25
- data/lib/hecks/facade/surface/aggregate_door.rb +50 -27
- data/lib/hecks/facade/surface/chapter.rb +26 -17
- data/lib/hecks/facade/surface.rb +16 -3
- data/lib/hecks/facade.rb +15 -4
- data/lib/hecks/forms/app.rb +46 -30
- data/lib/hecks/forms/command_form_renderer.rb +70 -9
- data/lib/hecks/forms/field_renderer.rb +142 -6
- data/lib/hecks/forms/field_shape.rb +183 -19
- data/lib/hecks/forms/html.rb +51 -7
- data/lib/hecks/forms/index_renderer.rb +14 -2
- data/lib/hecks/forms/page.rb +14 -0
- data/lib/hecks/forms/params.rb +120 -23
- data/lib/hecks/forms/port_argument.rb +14 -2
- data/lib/hecks/forms/query_form_renderer.rb +65 -2
- data/lib/hecks/forms/record_renderer.rb +60 -2
- data/lib/hecks/forms/record_table.rb +28 -1
- data/lib/hecks/forms/reference_options.rb +24 -0
- data/lib/hecks/forms/value_object_shape.rb +13 -3
- data/lib/hecks/forms.rb +24 -4
- data/lib/hecks/fqn.rb +59 -1
- data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
- data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
- data/lib/hecks/framework/oidc.json +15 -0
- data/lib/hecks/framework.rb +79 -25
- data/lib/hecks/freezer.rb +27 -11
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +241 -106
- data/lib/hecks/fuzzing/combination_miner.rb +178 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +241 -45
- data/lib/hecks/fuzzing/coverage_campaign.rb +161 -0
- data/lib/hecks/fuzzing/differential.rb +192 -0
- data/lib/hecks/fuzzing/domain_generator.rb +871 -0
- data/lib/hecks/fuzzing/era_boundary.rb +68 -19
- data/lib/hecks/fuzzing/form_census.rb +121 -20
- data/lib/hecks/fuzzing/generated_domain_check.rb +171 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +45 -6
- data/lib/hecks/fuzzing/isolated_boot.rb +137 -80
- data/lib/hecks/fuzzing/nondeterministic.rb +79 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +111 -21
- data/lib/hecks/fuzzing/properties/corrections.rb +34 -9
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +300 -51
- data/lib/hecks/fuzzing/properties/guards.rb +86 -42
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +80 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +40 -35
- data/lib/hecks/fuzzing/properties/outbox.rb +70 -32
- data/lib/hecks/fuzzing/properties/querying.rb +82 -28
- data/lib/hecks/fuzzing/properties.rb +84 -51
- data/lib/hecks/fuzzing/qa_settings.rb +164 -0
- data/lib/hecks/fuzzing/replay.rb +241 -133
- data/lib/hecks/fuzzing/rotation_priority.rb +48 -28
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +139 -0
- data/lib/hecks/fuzzing/self_consistency.rb +315 -137
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +46 -40
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +18 -11
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +13 -12
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +21 -12
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +59 -32
- data/lib/hecks/fuzzing/sequence_generator.rb +158 -23
- data/lib/hecks/fuzzing/shrinker.rb +309 -0
- data/lib/hecks/fuzzing/structural_skips.rb +37 -130
- data/lib/hecks/fuzzing/sweep_depth.rb +12 -4
- data/lib/hecks/fuzzing/target_capabilities.rb +107 -29
- data/lib/hecks/fuzzing/value_generator.rb +110 -22
- data/lib/hecks/fuzzing.rb +5 -0
- data/lib/hecks/grammar/evolve.rb +188 -12
- data/lib/hecks/grammar.rb +53 -7
- data/lib/hecks/ir.rb +51 -20
- data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
- data/lib/hecks/language/bluebook/policy.bluebook +11 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +348 -13
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/literal.rb +41 -9
- data/lib/hecks/naming.rb +112 -31
- data/lib/hecks/ports/access_control.rb +53 -2
- data/lib/hecks/ports/agent/answers.rb +83 -6
- data/lib/hecks/ports/agent.rb +119 -35
- data/lib/hecks/ports/authentication.rb +44 -4
- data/lib/hecks/ports/authorization.rb +50 -11
- data/lib/hecks/ports/clock.rb +42 -23
- data/lib/hecks/ports/extraction.rb +16 -0
- data/lib/hecks/ports/identity_assignment.rb +23 -2
- data/lib/hecks/ports/identity_generation.rb +17 -3
- data/lib/hecks/ports/identity_resolution.rb +17 -1
- data/lib/hecks/ports/key_vault.port +6 -0
- data/lib/hecks/ports/key_vault.rb +58 -0
- data/lib/hecks/ports/loading.rb +4 -0
- data/lib/hecks/ports/persistence/append_only.rb +172 -8
- data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
- data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
- data/lib/hecks/ports/persistence/execution.rb +4 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
- data/lib/hecks/ports/persistence/plugin.rb +42 -4
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +118 -66
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +34 -5
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +33 -9
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +95 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +314 -90
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +48 -8
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +58 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +96 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +9 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
- data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
- data/lib/hecks/ports/persistence/repository_factory.rb +23 -3
- data/lib/hecks/ports/persistence/state_codec.rb +319 -0
- data/lib/hecks/ports/persistence.rb +36 -1
- data/lib/hecks/ports/projection.rb +61 -7
- data/lib/hecks/ports/query/in_memory.rb +41 -8
- data/lib/hecks/ports/query/ordering.rb +21 -6
- data/lib/hecks/ports/query.rb +35 -0
- data/lib/hecks/ports.rb +1 -0
- data/lib/hecks/projections/bootstrap_table.rb +147 -0
- data/lib/hecks/projections/diagrams.rb +315 -79
- data/lib/hecks/projections/glossary/html.rb +91 -3
- data/lib/hecks/projections/glossary/markdown.rb +50 -3
- data/lib/hecks/projections/glossary/mermaid.rb +49 -1
- data/lib/hecks/projections/glossary/sections.rb +1 -1
- data/lib/hecks/projections/glossary/sentences.rb +110 -5
- data/lib/hecks/projections/glossary.rb +173 -31
- data/lib/hecks/projections/ir.rb +1 -1
- data/lib/hecks/projections/model/deviations.rb +62 -17
- data/lib/hecks/projections/model.rb +75 -21
- data/lib/hecks/projections/oidc.rb +45 -8
- data/lib/hecks/projections/parser_table.rb +53 -12
- data/lib/hecks/projections/reference.rb +15 -3
- data/lib/hecks/projections/rust_vocabulary.rb +646 -0
- data/lib/hecks/projections/shape.rb +10 -2
- data/lib/hecks/projections/statements.rb +69 -22
- data/lib/hecks/projections/vocabulary.rb +26 -9
- data/lib/hecks/projections.rb +5 -3
- data/lib/hecks/projector/cli_projector.rb +245 -37
- data/lib/hecks/projector/docs_projector.rb +154 -28
- data/lib/hecks/projector/exporter.rb +104 -29
- data/lib/hecks/projector/ir_projector.rb +7 -1
- data/lib/hecks/projector/narrate_projector.rb +144 -23
- data/lib/hecks/projector/target.rb +42 -18
- data/lib/hecks/projector.rb +86 -18
- data/lib/hecks/query_ir.rb +94 -47
- data/lib/hecks/query_specification/common/comparators.rb +19 -3
- data/lib/hecks/query_specification/common/comparison.rb +109 -27
- data/lib/hecks/query_specification/common/dsl.rb +65 -9
- data/lib/hecks/query_specification/common/null_policy.rb +61 -15
- data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
- data/lib/hecks/query_specification/common/options.rb +25 -0
- data/lib/hecks/query_specification/field_path.rb +69 -15
- data/lib/hecks/query_specification/hop_path.rb +57 -20
- data/lib/hecks/query_specification/read_model/specification.rb +4 -0
- data/lib/hecks/rendering.rb +9 -3
- data/lib/hecks/router/namespace_installer.rb +16 -3
- data/lib/hecks/router.rb +56 -1
- data/lib/hecks/runtime/aggregate_lock.rb +20 -11
- data/lib/hecks/runtime/boot_gates.rb +21 -3
- data/lib/hecks/runtime/caller.rb +40 -8
- data/lib/hecks/runtime/capability_graph.rb +13 -2
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +40 -41
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +45 -67
- data/lib/hecks/runtime/command_interpreter.rb +195 -113
- data/lib/hecks/runtime/command_rules/admissibility.rb +231 -80
- data/lib/hecks/runtime/command_rules/arithmetic.rb +154 -96
- data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
- data/lib/hecks/runtime/command_rules/emission.rb +18 -1
- data/lib/hecks/runtime/command_rules/references.rb +145 -55
- data/lib/hecks/runtime/command_rules.rb +3 -0
- data/lib/hecks/runtime/dependency_planning.rb +56 -11
- data/lib/hecks/runtime/dispatcher.rb +263 -92
- data/lib/hecks/runtime/entity_element.rb +304 -67
- data/lib/hecks/runtime/entity_interpreter.rb +149 -88
- data/lib/hecks/runtime/errors.rb +37 -23
- data/lib/hecks/runtime/event.rb +15 -10
- data/lib/hecks/runtime/identity.rb +91 -23
- data/lib/hecks/runtime/instance.rb +103 -18
- data/lib/hecks/runtime/interpreting.rb +24 -16
- data/lib/hecks/runtime/invocation.rb +358 -0
- data/lib/hecks/runtime/loader.rb +105 -19
- data/lib/hecks/runtime/outbox.rb +164 -26
- data/lib/hecks/runtime/policy_interpreter.rb +73 -60
- data/lib/hecks/runtime/port_operation_interpreter.rb +42 -19
- data/lib/hecks/runtime/query_interpreter.rb +93 -74
- data/lib/hecks/runtime/reaction_invocation.rb +73 -28
- data/lib/hecks/runtime/read_model_interpreter.rb +60 -44
- data/lib/hecks/runtime/rebuild_sweep.rb +32 -4
- data/lib/hecks/runtime/reference_hop.rb +48 -6
- data/lib/hecks/runtime/refusal_wording.rb +142 -115
- data/lib/hecks/runtime/registry/saga_persistence.rb +32 -21
- data/lib/hecks/runtime/registry/verification.rb +153 -28
- data/lib/hecks/runtime/registry.rb +202 -32
- data/lib/hecks/runtime/remote_dispatcher.rb +125 -24
- data/lib/hecks/runtime/routing.rb +36 -154
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +25 -27
- data/lib/hecks/runtime/saga_interpreter.rb +90 -76
- data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
- data/lib/hecks/runtime/tenant_check.rb +33 -13
- data/lib/hecks/runtime/tenant_scope.rb +23 -5
- data/lib/hecks/runtime/value/admission.rb +75 -30
- data/lib/hecks/runtime/value/coercion.rb +555 -142
- data/lib/hecks/runtime/value/entity_list_coercion.rb +132 -60
- data/lib/hecks/runtime/value.rb +71 -21
- data/lib/hecks/runtime.rb +39 -7
- data/lib/hecks/storehouse.rb +368 -72
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
- data/lib/hecks/version.rb +3 -3
- data/lib/hecks/vocabulary.rb +205 -4
- data/lib/hecks.rb +91 -11
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +19 -11
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +24 -12
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +38 -14
- metadata +28 -2
|
@@ -14,32 +14,36 @@ require_relative "properties/outbox"
|
|
|
14
14
|
|
|
15
15
|
module Hecks
|
|
16
16
|
module Fuzzing
|
|
17
|
-
# Declared properties, checked over a
|
|
17
|
+
# Declared properties, checked over a replayed history — the other
|
|
18
18
|
# half of property-based testing the fuzzer was missing: it already
|
|
19
19
|
# generates and (with bin/fuzz's shrinker) minimizes, but checked
|
|
20
20
|
# nothing beyond "did the interpreter crash" and "did the replay
|
|
21
21
|
# match the claim." A property here is a fact that should hold of
|
|
22
|
-
#
|
|
22
|
+
# any history a valid domain produces, independent of which seed
|
|
23
23
|
# produced it.
|
|
24
24
|
#
|
|
25
|
+
# ## Property shape
|
|
26
|
+
#
|
|
25
27
|
# Each property is `name => ->(history) { true/false, or a message
|
|
26
28
|
# string naming what broke }` — a truthy return (including `true`)
|
|
27
|
-
# is a pass; a String return is a failure, and the string
|
|
29
|
+
# is a pass; a String return is a failure, and the string is the
|
|
28
30
|
# finding. `history` is Replay's return shape.
|
|
29
31
|
#
|
|
30
|
-
#
|
|
32
|
+
# ## Feature coverage
|
|
33
|
+
#
|
|
34
|
+
# Every property declares the language feature it covers, in
|
|
31
35
|
# `FEATURE_COVERAGE` below — a "Construct#attribute" pair spelled
|
|
32
36
|
# exactly as `Bluebook::MetaValidator.grammar_registry` names it,
|
|
33
|
-
# the
|
|
37
|
+
# the same meta-domain that judges every real bluebook (see that
|
|
34
38
|
# module's own header: "the language IS the source"). That is the
|
|
35
39
|
# link this file exists to make real: a construct the language
|
|
36
40
|
# declares is a fact `spec/meta_domain_coverage_spec.rb` can
|
|
37
41
|
# enumerate on its own, without anyone re-typing the list here —
|
|
38
42
|
# so a new attribute added to `language/bluebook/*.bluebook` shows
|
|
39
|
-
# up in that spec as
|
|
43
|
+
# up in that spec as unclaimed the moment it lands, not whenever
|
|
40
44
|
# someone remembers to go looking. Claiming a feature here is a
|
|
41
45
|
# deliberate act (a real property, checked at least once failing
|
|
42
|
-
#
|
|
46
|
+
# and once passing — `spec/fuzzing/properties_spec.rb`'s own
|
|
43
47
|
# discipline) or an explicit, reasoned exemption in that same
|
|
44
48
|
# spec — never silence.
|
|
45
49
|
module Properties
|
|
@@ -64,11 +68,11 @@ module Hecks
|
|
|
64
68
|
|
|
65
69
|
module_function
|
|
66
70
|
|
|
67
|
-
#
|
|
71
|
+
# Which language feature each property is answerable for. Not
|
|
68
72
|
# exhaustive of everything a property's body happens to touch —
|
|
69
73
|
# `Command#attributes`, say, is exercised by nearly every property
|
|
70
|
-
# here without being what any of them was
|
|
71
|
-
# exhaustive of the feature that would go
|
|
74
|
+
# here without being what any of them was written to guard — but
|
|
75
|
+
# exhaustive of the feature that would go unchecked if this
|
|
72
76
|
# property did not exist. That is the question the coverage gate
|
|
73
77
|
# actually asks.
|
|
74
78
|
FEATURE_COVERAGE = {
|
|
@@ -80,17 +84,17 @@ module Hecks
|
|
|
80
84
|
authorize_scopes_or_refuses: %w[Query#options],
|
|
81
85
|
guard_refusals_are_declared: %w[Command#givens Command#ensures],
|
|
82
86
|
lifecycle_guard_and_given_violations_are_refused: %w[Command#from Aggregate#preconditions Entity#preconditions],
|
|
83
|
-
# Dispatch#command_name/Dispatch#with_spec are
|
|
84
|
-
# feature names — META_DOMAIN_ALL_FEATURES only walks
|
|
87
|
+
# Dispatch#command_name/Dispatch#with_spec are not claimable
|
|
88
|
+
# feature names — META_DOMAIN_ALL_FEATURES only walks one level
|
|
85
89
|
# of entity nesting (`agg.entities.flat_map`, meta_domain_
|
|
86
|
-
# coverage_spec.rb), and Dispatch sits
|
|
90
|
+
# coverage_spec.rb), and Dispatch sits two deep (ProcessManager
|
|
87
91
|
# -> Handler -> Dispatch), so those strings never exist there
|
|
88
92
|
# to claim — a pre-existing meta-domain coverage-generation gap,
|
|
89
93
|
# found here (their old META_DOMAIN_KNOWN_GAPS entries were
|
|
90
94
|
# themselves already-orphaned strings no completeness check ever
|
|
91
95
|
# verified, since KNOWN_GAPS has no "never lets a gap rot" check
|
|
92
96
|
# the way FEATURE_COVERAGE/GUARANTEED_BY_CONSTRUCTION both do).
|
|
93
|
-
# This property still closes the
|
|
97
|
+
# This property still closes the real behavior both would have
|
|
94
98
|
# named — a Dispatch's own command_name/with_spec are exactly
|
|
95
99
|
# what dispatch_args resolves and this property checks — the
|
|
96
100
|
# grammar just has no feature string for either one.
|
|
@@ -102,58 +106,78 @@ module Hecks
|
|
|
102
106
|
aggregation_matches_recompute: %w[ReadModel#count ReadModel#median_field],
|
|
103
107
|
stored_records_satisfy_declared_invariants: %w[Aggregate#invariants Entity#invariants],
|
|
104
108
|
group_by_matches_recompute: %w[ReadModel#group_by],
|
|
105
|
-
# A
|
|
109
|
+
# A runtime door, not a grammar construct — `Dispatcher#dry_run?`
|
|
106
110
|
# is something an application asks of a booted domain, not a
|
|
107
111
|
# word a bluebook can declare, so there is no feature string
|
|
108
112
|
# for it to claim. Listed (empty) rather than omitted so the
|
|
109
113
|
# discipline this table states — every property names what it
|
|
110
114
|
# is answerable for — has no silent exception.
|
|
111
115
|
dry_runs_leave_no_trace: [],
|
|
112
|
-
#
|
|
116
|
+
# Another runtime door, not a grammar construct — same reasoning
|
|
113
117
|
# as dry_runs_leave_no_trace right above: `Runtime::Outbox` is
|
|
114
118
|
# something a persistence adapter provides underneath a booted
|
|
115
119
|
# domain, never a word a bluebook declares.
|
|
116
|
-
outbox_rows_match_reactions: []
|
|
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: []
|
|
117
140
|
}.freeze
|
|
118
141
|
|
|
119
|
-
#
|
|
120
|
-
#
|
|
142
|
+
# Features a replay property could never catch violated, because the
|
|
143
|
+
# runtime's own construction makes the violation impossible to
|
|
121
144
|
# produce in the first place — not "untested," but unfalsifiable by
|
|
122
145
|
# a history, the same class of guarantee this codebase already
|
|
123
|
-
# states for identity ("
|
|
124
|
-
# own header) and now generalises. Each entry names the
|
|
146
|
+
# states for identity ("nothing is minted" — command_interpreter.rb's
|
|
147
|
+
# own header) and now generalises. Each entry names the one place in
|
|
125
148
|
# the runtime that makes it true, universally, for every domain and
|
|
126
149
|
# every adapter — never per-domain logic a future domain could
|
|
127
150
|
# accidentally route around.
|
|
128
151
|
#
|
|
129
|
-
#
|
|
130
|
-
# declares
|
|
152
|
+
# The bluebook/hecksagon boundary is why this works: a bluebook
|
|
153
|
+
# declares shape (attribute types, patterns, closed sets, VO
|
|
131
154
|
# invariants — see docs/decisions/0009), and shape is enforced by
|
|
132
|
-
#
|
|
155
|
+
# one coercion door every domain's every attribute passes through
|
|
133
156
|
# (`Runtime::Value.build`, via value/coercion.rb + value/admission.rb)
|
|
134
157
|
# regardless of which hecksagon later binds the aggregate to Memory,
|
|
135
158
|
# Postgres, or anything else. A value that violated its own declared
|
|
136
|
-
# pattern, invariant, or closed set could never be
|
|
137
|
-
# could never be
|
|
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
|
|
138
161
|
# `:instances` to be caught violating it. Checking for it after the
|
|
139
162
|
# fact would be watching for something the construction path already
|
|
140
163
|
# made impossible.
|
|
141
164
|
#
|
|
142
|
-
#
|
|
143
|
-
# the
|
|
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
|
|
144
167
|
# same discipline `spec/fuzzing/meta_domain_coverage_spec.rb` demands
|
|
145
|
-
# of `KNOWN_GAPS` in the other direction. `Entity#identified_by`
|
|
146
|
-
#
|
|
147
|
-
# `
|
|
148
|
-
#
|
|
149
|
-
#
|
|
150
|
-
#
|
|
151
|
-
# unconditionally on both branches
|
|
152
|
-
# (caller-supplied, or composite —
|
|
153
|
-
# doesn't cover), the same way
|
|
154
|
-
# check is unconditional for
|
|
155
|
-
#
|
|
156
|
-
#
|
|
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.
|
|
157
181
|
GUARANTEED_BY_CONSTRUCTION = {
|
|
158
182
|
"Aggregate#attributes" => "every field's pattern/closed-set/type passes through Value.build's one coercion " \
|
|
159
183
|
"door (value/coercion.rb#check_patterns, value/admission.rb) before it can exist " \
|
|
@@ -171,13 +195,18 @@ module Hecks
|
|
|
171
195
|
"(command_interpreter.rb, command.creates?) for every creating command uniformly, " \
|
|
172
196
|
"before a duplicate id can ever be stored — collision is refused at the door, not " \
|
|
173
197
|
"produced and later caught",
|
|
174
|
-
"Entity#identified_by" => "
|
|
175
|
-
"checks Array(current) against every part
|
|
176
|
-
"
|
|
177
|
-
"
|
|
178
|
-
"
|
|
179
|
-
"
|
|
180
|
-
"
|
|
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)",
|
|
181
210
|
"Command#attributes" => "command arguments are coerced through the SAME Value.build door as any other " \
|
|
182
211
|
"attribute — an accepted dispatch's own args already passed pattern/admits/invariant checks",
|
|
183
212
|
"Command#emits" => "CommandRules::Emission#emit iterates command.emits ITSELF to construct every " \
|
|
@@ -198,7 +227,7 @@ module Hecks
|
|
|
198
227
|
# ValueObject), so this reads "Member#pairs", not "Member#shape" —
|
|
199
228
|
# the free-text, un-parsed spelling a standalone root once needed
|
|
200
229
|
# no longer exists at all, an entity's own element is never
|
|
201
|
-
# serialized as text. "ValueObject#members" is the
|
|
230
|
+
# serialized as text. "ValueObject#members" is the same fact
|
|
202
231
|
# "ValueObject#rows" already counts, seen from the other side — a
|
|
203
232
|
# value object cannot declare admitted rows without a members list
|
|
204
233
|
# to hold them, and vice versa.
|
|
@@ -206,9 +235,13 @@ module Hecks
|
|
|
206
235
|
"Member#pairs" => "one level into ValueObject#rows — same door"
|
|
207
236
|
}.freeze
|
|
208
237
|
|
|
209
|
-
#
|
|
210
|
-
# except determinism, which needs to replay
|
|
211
|
-
# takes the steps directly rather than a single history.
|
|
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
|
|
212
245
|
def check(history)
|
|
213
246
|
{ lifecycle_values_are_declared: lifecycle_values_are_declared(history),
|
|
214
247
|
saga_advances_follow_declared_handlers: saga_advances_follow_declared_handlers(history),
|
|
@@ -0,0 +1,164 @@
|
|
|
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
|