hecks 1.3.0 → 1.4.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 +7 -7
- data/lib/hecks/adapters/driven/d1.rb +187 -23
- data/lib/hecks/adapters/driven/folder.rb +10 -10
- data/lib/hecks/adapters/driven/google_authentication.rb +8 -8
- data/lib/hecks/adapters/driven/governance_authorization.rb +31 -13
- data/lib/hecks/adapters/driven/heki/journal.rb +5 -4
- data/lib/hecks/adapters/driven/heki/saga_store.rb +5 -5
- data/lib/hecks/adapters/driven/heki.rb +13 -7
- data/lib/hecks/adapters/driven/identity_registry.rb +2 -2
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +3 -3
- data/lib/hecks/adapters/driven/lambda/client.rb +34 -9
- data/lib/hecks/adapters/driven/lambda.rb +39 -33
- data/lib/hecks/adapters/driven/local_storage.rb +17 -10
- data/lib/hecks/adapters/driven/memory.rb +205 -9
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +1 -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 +4 -4
- 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.rb +4 -4
- data/lib/hecks/adapters/driving/github_webhook.rb +18 -18
- data/lib/hecks/behaviors/dsl.rb +2 -2
- data/lib/hecks/behaviors/expectations.rb +23 -23
- data/lib/hecks/behaviors/ir.rb +1 -1
- data/lib/hecks/behaviors/rspec.rb +1 -1
- data/lib/hecks/behaviors/runner.rb +2 -2
- data/lib/hecks/behaviors.rb +1 -1
- data/lib/hecks/bluebook/aggregate.rb +13 -13
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +10 -10
- data/lib/hecks/bluebook/assembly/build.rb +1 -1
- data/lib/hecks/bluebook/assembly/contract.rb +39 -16
- data/lib/hecks/bluebook/assembly/contracts.rb +40 -38
- data/lib/hecks/bluebook/assembly/marks.rb +18 -18
- data/lib/hecks/bluebook/assembly/specializer.rb +19 -21
- data/lib/hecks/bluebook/assembly.rb +9 -9
- data/lib/hecks/bluebook/attribute.rb +9 -9
- data/lib/hecks/bluebook/behaviour/aggregate.rb +11 -11
- data/lib/hecks/bluebook/behaviour/attribute.rb +5 -5
- data/lib/hecks/bluebook/behaviour/chapter.rb +23 -5
- data/lib/hecks/bluebook/behaviour/command.rb +23 -23
- data/lib/hecks/bluebook/behaviour/domain_port.rb +7 -7
- data/lib/hecks/bluebook/behaviour/entity.rb +8 -8
- data/lib/hecks/bluebook/behaviour/hexagon.rb +4 -4
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +5 -5
- data/lib/hecks/bluebook/behaviour/policy.rb +12 -12
- data/lib/hecks/bluebook/behaviour/process_manager.rb +7 -7
- data/lib/hecks/bluebook/behaviour/query.rb +1 -1
- data/lib/hecks/bluebook/behaviour/read_model.rb +8 -8
- data/lib/hecks/bluebook/behaviour/traits.rb +12 -12
- data/lib/hecks/bluebook/behaviour/value_object.rb +6 -6
- data/lib/hecks/bluebook/capabilities.rb +27 -0
- data/lib/hecks/bluebook/chapter.rb +28 -9
- data/lib/hecks/bluebook/command.rb +12 -12
- data/lib/hecks/bluebook/domain_port.rb +9 -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 +96 -96
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +41 -41
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +22 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +111 -74
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +48 -30
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +103 -103
- 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 +56 -56
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +89 -30
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +17 -17
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
- data/lib/hecks/bluebook/dsl/policy_builder.rb +30 -21
- 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 +35 -35
- data/lib/hecks/bluebook/dsl/query_builder.rb +5 -5
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +34 -34
- data/lib/hecks/bluebook/dsl/rule_reference.rb +41 -39
- data/lib/hecks/bluebook/dsl/translation_builder.rb +9 -9
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +16 -16
- 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 +11 -11
- data/lib/hecks/bluebook/expression/ast_json.rb +20 -20
- data/lib/hecks/bluebook/expression/ast_reader.rb +3 -3
- data/lib/hecks/bluebook/expression/canonical_form.rb +9 -9
- data/lib/hecks/bluebook/expression/evaluator.rb +18 -18
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +18 -18
- data/lib/hecks/bluebook/expression/resolver.rb +60 -62
- data/lib/hecks/bluebook/hexagon.rb +1 -1
- data/lib/hecks/bluebook/lifecycle.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/judge.rb +101 -87
- data/lib/hecks/bluebook/meta_validator/plan.rb +39 -39
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -2
- data/lib/hecks/bluebook/meta_validator/readings.rb +43 -43
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +44 -37
- data/lib/hecks/bluebook/meta_validator/shapes.rb +25 -21
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +145 -31
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +6 -6
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +5 -5
- data/lib/hecks/bluebook/meta_validator.rb +70 -70
- data/lib/hecks/bluebook/model_check.rb +170 -84
- data/lib/hecks/bluebook/pattern_subset.rb +9 -9
- data/lib/hecks/bluebook/policy.rb +15 -13
- data/lib/hecks/bluebook/process_manager.rb +14 -14
- data/lib/hecks/bluebook/project_register.rb +6 -6
- data/lib/hecks/bluebook/query.rb +4 -4
- data/lib/hecks/bluebook/read_model.rb +14 -14
- data/lib/hecks/bluebook/reference.rb +8 -8
- data/lib/hecks/bluebook/smoke_test.rb +19 -19
- data/lib/hecks/bluebook/synthesizer.rb +12 -12
- data/lib/hecks/bluebook/translation.rb +4 -4
- data/lib/hecks/bluebook/value_object.rb +6 -6
- data/lib/hecks/bluebook.rb +2 -2
- data/lib/hecks/codemod/legacy_dispatch_args.rb +299 -0
- data/lib/hecks/codemod/legacy_dispatch_recorder.rb +186 -0
- data/lib/hecks/codemod.rb +36 -35
- data/lib/hecks/construct.rb +6 -6
- data/lib/hecks/corpus.rb +317 -0
- data/lib/hecks/deprecation.rb +95 -0
- data/lib/hecks/doc/reference.rb +19 -19
- data/lib/hecks/embryonaut_bluebook.rb +11 -11
- 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 +79 -32
- data/lib/hecks/facade/json_door.rb +106 -25
- data/lib/hecks/facade/surface/aggregate_door.rb +42 -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 +180 -19
- data/lib/hecks/forms/html.rb +51 -7
- data/lib/hecks/forms/index_renderer.rb +14 -2
- data/lib/hecks/forms/params.rb +120 -23
- data/lib/hecks/forms/port_argument.rb +2 -2
- data/lib/hecks/forms/query_form_renderer.rb +2 -2
- data/lib/hecks/forms/record_renderer.rb +2 -2
- data/lib/hecks/forms/record_table.rb +1 -1
- data/lib/hecks/forms/value_object_shape.rb +3 -3
- data/lib/hecks/forms.rb +24 -4
- data/lib/hecks/fqn.rb +1 -1
- data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
- data/lib/hecks/framework.rb +48 -17
- data/lib/hecks/freezer.rb +11 -11
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +90 -90
- data/lib/hecks/fuzzing/combination_miner.rb +119 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +140 -45
- data/lib/hecks/fuzzing/coverage_campaign.rb +118 -0
- data/lib/hecks/fuzzing/differential.rb +158 -0
- data/lib/hecks/fuzzing/domain_generator.rb +694 -0
- data/lib/hecks/fuzzing/era_boundary.rb +37 -18
- data/lib/hecks/fuzzing/form_census.rb +35 -20
- data/lib/hecks/fuzzing/generated_domain_check.rb +95 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +6 -6
- data/lib/hecks/fuzzing/isolated_boot.rb +62 -62
- data/lib/hecks/fuzzing/nondeterministic.rb +67 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +19 -21
- data/lib/hecks/fuzzing/properties/corrections.rb +9 -9
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +154 -49
- data/lib/hecks/fuzzing/properties/guards.rb +42 -42
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +32 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +22 -35
- data/lib/hecks/fuzzing/properties/outbox.rb +24 -24
- data/lib/hecks/fuzzing/properties/querying.rb +17 -17
- data/lib/hecks/fuzzing/properties.rb +64 -40
- data/lib/hecks/fuzzing/qa_settings.rb +152 -0
- data/lib/hecks/fuzzing/replay.rb +114 -114
- data/lib/hecks/fuzzing/rotation_priority.rb +10 -10
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +113 -0
- data/lib/hecks/fuzzing/self_consistency.rb +141 -112
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +35 -35
- 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 +58 -31
- data/lib/hecks/fuzzing/sequence_generator.rb +111 -23
- data/lib/hecks/fuzzing/shrinker.rb +197 -0
- data/lib/hecks/fuzzing/structural_skips.rb +23 -130
- data/lib/hecks/fuzzing/sweep_depth.rb +4 -4
- data/lib/hecks/fuzzing/target_capabilities.rb +49 -18
- data/lib/hecks/fuzzing/value_generator.rb +14 -14
- data/lib/hecks/fuzzing.rb +5 -0
- data/lib/hecks/grammar/evolve.rb +10 -10
- data/lib/hecks/grammar.rb +7 -7
- data/lib/hecks/ir.rb +13 -13
- 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/oidc.json +5 -0
- data/lib/hecks/literal.rb +9 -9
- data/lib/hecks/naming.rb +25 -25
- data/lib/hecks/ports/access_control.rb +58 -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 +53 -11
- data/lib/hecks/ports/clock.rb +42 -23
- data/lib/hecks/ports/extraction.rb +16 -0
- data/lib/hecks/ports/identity_assignment.rb +24 -2
- data/lib/hecks/ports/identity_generation.rb +17 -3
- data/lib/hecks/ports/identity_resolution.rb +18 -1
- 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 +28 -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 +57 -4
- 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 +42 -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 +18 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +5 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +1 -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 +3 -3
- data/lib/hecks/ports/query/ordering.rb +6 -6
- data/lib/hecks/ports/query.rb +35 -0
- data/lib/hecks/projections/bootstrap_table.rb +112 -0
- data/lib/hecks/projections/diagrams.rb +75 -75
- data/lib/hecks/projections/glossary/html.rb +3 -3
- data/lib/hecks/projections/glossary/markdown.rb +3 -3
- data/lib/hecks/projections/glossary/mermaid.rb +1 -1
- data/lib/hecks/projections/glossary/sections.rb +1 -1
- data/lib/hecks/projections/glossary/sentences.rb +5 -5
- data/lib/hecks/projections/glossary.rb +15 -15
- data/lib/hecks/projections/ir.rb +1 -1
- data/lib/hecks/projections/model/deviations.rb +18 -17
- data/lib/hecks/projections/model.rb +25 -21
- data/lib/hecks/projections/oidc.rb +7 -7
- data/lib/hecks/projections/parser_table.rb +5 -5
- data/lib/hecks/projections/reference.rb +3 -3
- data/lib/hecks/projections/rust_vocabulary.rb +443 -0
- data/lib/hecks/projections/shape.rb +2 -2
- data/lib/hecks/projections/statements.rb +11 -11
- data/lib/hecks/projections/vocabulary.rb +9 -9
- data/lib/hecks/projections.rb +5 -3
- data/lib/hecks/projector/cli_projector.rb +29 -29
- data/lib/hecks/projector/docs_projector.rb +13 -13
- data/lib/hecks/projector/exporter.rb +42 -21
- data/lib/hecks/projector/ir_projector.rb +1 -1
- data/lib/hecks/projector/narrate_projector.rb +11 -11
- data/lib/hecks/projector/target.rb +13 -13
- data/lib/hecks/projector.rb +15 -15
- data/lib/hecks/query_ir.rb +47 -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 +57 -13
- 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 +3 -3
- data/lib/hecks/router/namespace_installer.rb +3 -3
- data/lib/hecks/router.rb +1 -1
- data/lib/hecks/runtime/aggregate_lock.rb +11 -11
- data/lib/hecks/runtime/boot_gates.rb +3 -3
- data/lib/hecks/runtime/caller.rb +8 -8
- data/lib/hecks/runtime/capability_graph.rb +2 -2
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +22 -25
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +42 -63
- data/lib/hecks/runtime/command_interpreter.rb +159 -102
- data/lib/hecks/runtime/command_rules/admissibility.rb +69 -69
- data/lib/hecks/runtime/command_rules/arithmetic.rb +137 -91
- 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 +35 -35
- data/lib/hecks/runtime/command_rules.rb +3 -0
- data/lib/hecks/runtime/dependency_planning.rb +11 -11
- data/lib/hecks/runtime/dispatcher.rb +283 -90
- data/lib/hecks/runtime/entity_element.rb +151 -67
- data/lib/hecks/runtime/entity_interpreter.rb +108 -82
- data/lib/hecks/runtime/errors.rb +19 -19
- data/lib/hecks/runtime/event.rb +6 -6
- data/lib/hecks/runtime/identity.rb +22 -22
- data/lib/hecks/runtime/instance.rb +39 -14
- data/lib/hecks/runtime/interpreting.rb +12 -12
- data/lib/hecks/runtime/invocation.rb +276 -0
- data/lib/hecks/runtime/loader.rb +14 -14
- data/lib/hecks/runtime/outbox.rb +23 -23
- data/lib/hecks/runtime/policy_interpreter.rb +54 -54
- data/lib/hecks/runtime/port_operation_interpreter.rb +22 -19
- data/lib/hecks/runtime/query_interpreter.rb +55 -64
- data/lib/hecks/runtime/reaction_invocation.rb +20 -20
- data/lib/hecks/runtime/read_model_interpreter.rb +40 -40
- data/lib/hecks/runtime/rebuild_sweep.rb +4 -4
- data/lib/hecks/runtime/reference_hop.rb +6 -6
- data/lib/hecks/runtime/refusal_wording.rb +92 -115
- data/lib/hecks/runtime/registry/saga_persistence.rb +21 -21
- data/lib/hecks/runtime/registry/verification.rb +36 -26
- data/lib/hecks/runtime/registry.rb +45 -28
- data/lib/hecks/runtime/remote_dispatcher.rb +38 -23
- data/lib/hecks/runtime/routing.rb +10 -153
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +17 -17
- data/lib/hecks/runtime/saga_interpreter.rb +64 -64
- data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
- data/lib/hecks/runtime/tenant_check.rb +9 -9
- data/lib/hecks/runtime/tenant_scope.rb +5 -5
- data/lib/hecks/runtime/value/admission.rb +75 -30
- data/lib/hecks/runtime/value/coercion.rb +318 -127
- data/lib/hecks/runtime/value/entity_list_coercion.rb +34 -34
- data/lib/hecks/runtime/value.rb +21 -21
- data/lib/hecks/runtime.rb +7 -7
- data/lib/hecks/storehouse.rb +64 -64
- data/lib/hecks/version.rb +3 -3
- data/lib/hecks/vocabulary.rb +205 -4
- data/lib/hecks.rb +13 -11
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +11 -11
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +14 -12
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +11 -11
- metadata +22 -2
|
@@ -2,20 +2,20 @@ require "hecks/ports/persistence/plugins/era"
|
|
|
2
2
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Fuzzing
|
|
5
|
-
#
|
|
6
|
-
# this file generates a sequence and compares two answers to the
|
|
5
|
+
# **The fork-loss class, as a check** — not a fuzz. Every other mode in
|
|
6
|
+
# this file generates a sequence and compares two answers to the same
|
|
7
7
|
# question; this one asks a single, unconditional question of a
|
|
8
|
-
# target's own
|
|
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**. Minting a new era (an attribute
|
|
12
12
|
# or aggregate addition — `StorageShape.project`, lib/hecks/ports/
|
|
13
13
|
# persistence/plugins/era/storage_shape.rb) advances the readable head
|
|
14
|
-
# to a
|
|
14
|
+
# to a new partition; an old checkout, or a process that boots slower
|
|
15
15
|
# than the mint, can keep writing into the era it still believes is
|
|
16
16
|
# current. Those writes are not lost — `Lineage#diverged_count`
|
|
17
17
|
# (postgres_era/lineage/tail_merge.rb) can always find them — but
|
|
18
|
-
# nothing
|
|
18
|
+
# nothing ever asked it automatically. The practice's own ledger found
|
|
19
19
|
# exactly this live, twice: once as an operational gap this session's
|
|
20
20
|
# Step 0 recovered by hand (`bin/merge_tail`, three conflicting
|
|
21
21
|
# records, three eras deep), and once as BUG#24 (a superuser
|
|
@@ -26,15 +26,15 @@ module Hecks
|
|
|
26
26
|
# line already answers by hand, run as an ordinary sweep Check instead
|
|
27
27
|
# of only when a human remembers to ask.
|
|
28
28
|
#
|
|
29
|
-
#
|
|
29
|
+
# The target's real database, read-only, never a disposable one — every
|
|
30
30
|
# other Postgres-touching mode here (`persistence_parity`,
|
|
31
31
|
# `adapter_parity_sqlite`) owns a throwaway schema for the exact
|
|
32
32
|
# reason it must never look at what a real deployment actually holds;
|
|
33
|
-
# this check exists
|
|
33
|
+
# this check exists for what a real deployment actually holds, so it
|
|
34
34
|
# connects the same way `bin/merge_tail` itself does — the target's
|
|
35
35
|
# own `.world` binding — and never writes anything: `eras`/
|
|
36
36
|
# `diverged_count` are both plain `SELECT`s, and `ensure_base!` only
|
|
37
|
-
# ever provisions (`CREATE TABLE IF NOT EXISTS`) when
|
|
37
|
+
# ever provisions (`CREATE TABLE IF NOT EXISTS`) when this connection
|
|
38
38
|
# is the database's own provisioning owner, the identical idempotent
|
|
39
39
|
# call every ordinary boot already makes. A target with no real,
|
|
40
40
|
# reachable database configured (nothing provisioned locally, say) is
|
|
@@ -45,20 +45,30 @@ module Hecks
|
|
|
45
45
|
module_function
|
|
46
46
|
|
|
47
47
|
# `{ checked: true, diverged_total:, breakdown: [{ordinal:, diverged:}] }`
|
|
48
|
-
# or `{ checked: false, reason: "..." }
|
|
49
|
-
# `false` means (an operational note, not a finding); a `true` with
|
|
48
|
+
# or `{ checked: false, kind:, reason: "..." }`. A `true` with
|
|
50
49
|
# `diverged_total.positive?` is the finding this module exists to
|
|
51
50
|
# surface: real post-cut writes an ancestor era is still holding.
|
|
51
|
+
#
|
|
52
|
+
# `kind:` splits the two things `checked: false` used to mean. Both
|
|
53
|
+
# answered the same shape, and `bin/qa_sweep` logged that shape as a
|
|
54
|
+
# held Check either way — so a refused connection or a `Lineage`
|
|
55
|
+
# defect read, in the ledger, exactly like an audit that ran and
|
|
56
|
+
# found nothing: counted toward `sweep.made` and the target's clean
|
|
57
|
+
# streak. `:not_applicable` is a target with no lineage to audit at
|
|
58
|
+
# all; `:error` is the audit failing to run, which the caller
|
|
59
|
+
# surfaces as a finding rather than holding.
|
|
52
60
|
def diverged_ancestor_writes(domain_path)
|
|
53
61
|
registry, directory = load_registry(domain_path)
|
|
54
62
|
bluebook = registry.bluebooks.values.first
|
|
55
|
-
return
|
|
63
|
+
return failed("no bluebook in #{directory}") unless bluebook
|
|
56
64
|
|
|
57
65
|
first = bluebook.aggregates.first
|
|
58
|
-
return
|
|
66
|
+
return not_applicable("#{bluebook.name} declares no aggregates") unless first
|
|
59
67
|
|
|
60
68
|
adapter_name = Hecks::Ports::Persistence::BindingPolicy.resolve(registry, bluebook.name, first).adapter
|
|
61
|
-
|
|
69
|
+
unless adapter_name == "PostgresEra"
|
|
70
|
+
return not_applicable("#{bluebook.name} is bound to #{adapter_name}, not PostgresEra")
|
|
71
|
+
end
|
|
62
72
|
|
|
63
73
|
settings = registry.world(bluebook.name)&.for_binding(Hecks::Ports::Persistence::VERB, adapter_name) || {}
|
|
64
74
|
db = Hecks::Adapters::PostgresEra.connect_for(bluebook.name, settings)
|
|
@@ -81,14 +91,23 @@ module Hecks
|
|
|
81
91
|
db.close
|
|
82
92
|
end
|
|
83
93
|
rescue StandardError => e
|
|
84
|
-
|
|
94
|
+
# Could not audit is not the same as nothing to audit — a refused
|
|
95
|
+
# connection, a `Lineage` defect, malformed `.world` settings. Every
|
|
96
|
+
# one of these used to answer the benign shape and be held.
|
|
97
|
+
failed("#{e.class}: #{e.message}")
|
|
85
98
|
end
|
|
86
99
|
|
|
87
|
-
|
|
100
|
+
# Nothing to audit — this target holds no PostgresEra lineage, so no
|
|
101
|
+
# ancestor era can carry post-cut writes. Not a finding, and not
|
|
102
|
+
# evidence of anything either: the caller logs no Check at all.
|
|
103
|
+
def not_applicable(reason) = { checked: false, kind: :not_applicable, reason: reason }
|
|
104
|
+
|
|
105
|
+
# The audit itself could not run — reported as a finding, never held.
|
|
106
|
+
def failed(reason) = { checked: false, kind: :error, reason: reason }
|
|
88
107
|
|
|
89
|
-
#
|
|
108
|
+
# The same load `bin/merge_tail` itself performs (that script's own
|
|
90
109
|
# top half) — a fresh `Registry`, never the ledger's own (this asks
|
|
91
|
-
# about the
|
|
110
|
+
# about the swept target's lineage, not QualityControl's own).
|
|
92
111
|
def load_registry(domain_path)
|
|
93
112
|
loading = Hecks::Ports::Loading.bootstrap
|
|
94
113
|
directory = loading.bluebook_directory(domain_path)
|
|
@@ -1,27 +1,28 @@
|
|
|
1
1
|
require "json"
|
|
2
|
+
require_relative "../corpus"
|
|
2
3
|
|
|
3
4
|
module Hecks
|
|
4
5
|
module Fuzzing
|
|
5
|
-
#
|
|
6
|
+
# What an aggregate can exhibit, and which pairs it puts together.
|
|
6
7
|
#
|
|
7
8
|
# Extracted from `spec/combination_coverage_spec.rb`'s own pairwise
|
|
8
9
|
# table so it has exactly two consumers that can never drift: that
|
|
9
10
|
# spec (the golden corpus, held to every pair) and `bin/qa_domain_
|
|
10
|
-
# novelty` (a
|
|
11
|
+
# novelty` (a candidate stress domain, measured against every path
|
|
11
12
|
# the QA ledger already sweeps — see that script's own header for why
|
|
12
13
|
# a new domain has to name the pair no existing target meets before
|
|
13
14
|
# it earns a place in the rotation).
|
|
14
15
|
#
|
|
15
|
-
#
|
|
16
|
+
# **The unit is one aggregate**. Two forms in the same chapter but
|
|
16
17
|
# different heads never meet at dispatch; two forms on one head do
|
|
17
18
|
# — that spec's own header has the four defects that argument came
|
|
18
19
|
# from. Each entry below is a form the language declares and a
|
|
19
20
|
# runtime has to handle, chosen because it has produced a defect or
|
|
20
21
|
# sits one step from one; adding one here is how a new form joins
|
|
21
|
-
#
|
|
22
|
+
# both gates at once, and it will name its own uncovered pairs on the
|
|
22
23
|
# first run of each.
|
|
23
24
|
#
|
|
24
|
-
#
|
|
25
|
+
# **One flat table, on purpose** — each entry is an independent boolean
|
|
25
26
|
# check against the same string-keyed aggregate IR hash (the shape
|
|
26
27
|
# `spec/golden/ir/*.json` carries and `Projector::Exporter.call`
|
|
27
28
|
# round-trips to through JSON), laid out so every declared form can
|
|
@@ -48,7 +49,7 @@ module Hecks
|
|
|
48
49
|
"closed_set" => ->(a) { (a["value_objects"] || []).any? { |shape| shape["closed_set"] } },
|
|
49
50
|
"has_default" => ->(a) { attributes(a).any? { |held| !held["default"].nil? } },
|
|
50
51
|
"has_optional" => ->(a) { commands(a).any? { |verb| (verb["attributes"] || []).any? { |held| held["optional"] } } },
|
|
51
|
-
#
|
|
52
|
+
# The reference-hop family (angle-2, qa/bluebook ledger) — the
|
|
52
53
|
# forms `qa/stress_domains/referral_chain` exists for, absent
|
|
53
54
|
# from the census until that domain named them. Each is one
|
|
54
55
|
# step from a catalogued gap: `two_hop_given` is `dereference`'s
|
|
@@ -59,6 +60,19 @@ module Hecks
|
|
|
59
60
|
# command redeclaring the aggregate's own reference field under
|
|
60
61
|
# a non-reference type, so only `resolve_state_references` (never
|
|
61
62
|
# ported) can catch a dangling id.
|
|
63
|
+
# A retroactive correction, and a role-gated command — both are
|
|
64
|
+
# declared forms this census could not see, on aggregates it was
|
|
65
|
+
# already measuring: `examples/banking` has carried `corrects`
|
|
66
|
+
# mutations and `role`-bearing commands the whole time, and
|
|
67
|
+
# `qa/stress_domains/corrections` exists for the first. Three
|
|
68
|
+
# stress domains' own NOTES.md record `bin/qa_domain_novelty`
|
|
69
|
+
# answering "no new pair" for a domain whose whole point was a
|
|
70
|
+
# shape this table did not name (case_escalation's is the
|
|
71
|
+
# bluntest: "read that as a gap in the census, not in this
|
|
72
|
+
# domain"). Entity commands count for both — BUG#30-33 and
|
|
73
|
+
# BUG#31 were all entity-level `corrects`.
|
|
74
|
+
"corrects" => ->(a) { every_command(a).any? { |verb| corrects?(verb) } },
|
|
75
|
+
"role_gated" => ->(a) { every_command(a).any? { |verb| !verb["role"].to_s.empty? } },
|
|
62
76
|
"two_hop_given" => ->(a) { two_hop_given?(a) },
|
|
63
77
|
"multi_hop_where" => ->(a) { multi_hop_where?(a) },
|
|
64
78
|
"revalued_reference" => ->(a) { revalued_reference?(a) }
|
|
@@ -68,6 +82,13 @@ module Hecks
|
|
|
68
82
|
|
|
69
83
|
def entities(aggregate) = aggregate["entities"] || []
|
|
70
84
|
def commands(aggregate) = aggregate["commands"] || []
|
|
85
|
+
|
|
86
|
+
# One aggregate's commands, its pieces' included — a form carried by
|
|
87
|
+
# an entity command is carried by the aggregate that owns it, the
|
|
88
|
+
# same way `composite_piece`/`piece_lifecycle` already read pieces.
|
|
89
|
+
def every_command(aggregate) = commands(aggregate) + entities(aggregate).flat_map { |piece| commands(piece) }
|
|
90
|
+
|
|
91
|
+
def corrects?(verb) = (verb["mutations"] || []).any? { |change| change["op"].to_s == "corrects" }
|
|
71
92
|
def attributes(aggregate) = aggregate["attributes"] || []
|
|
72
93
|
def queries(aggregate) = aggregate["queries"] || []
|
|
73
94
|
def reference?(attribute) = attribute["type"].to_s.start_with?("Reference<")
|
|
@@ -82,7 +103,7 @@ module Hecks
|
|
|
82
103
|
end
|
|
83
104
|
|
|
84
105
|
# A command attribute sharing a name with one of the aggregate's
|
|
85
|
-
# own reference-typed attributes while carrying a
|
|
106
|
+
# own reference-typed attributes while carrying a different, non-
|
|
86
107
|
# reference type — `attribute :member, Handle` against
|
|
87
108
|
# `reference_to Member`.
|
|
88
109
|
def revalued_reference?(aggregate)
|
|
@@ -121,7 +142,7 @@ module Hecks
|
|
|
121
142
|
def pair_key(left, right) = [left, right].sort.join(" + ")
|
|
122
143
|
|
|
123
144
|
# `held` is `[[aggregate_name, properties], ...]`. Answers which
|
|
124
|
-
# pairs are met on
|
|
145
|
+
# pairs are met on one aggregate, and by which — a Hash from pair
|
|
125
146
|
# key to the names carrying it, so a caller can say who.
|
|
126
147
|
def covered_pairs(held)
|
|
127
148
|
held.each_with_object(Hash.new { |h, k| h[k] = [] }) do |(name, shows), covered|
|
|
@@ -140,23 +161,17 @@ module Hecks
|
|
|
140
161
|
end
|
|
141
162
|
end
|
|
142
163
|
|
|
143
|
-
#
|
|
144
|
-
#
|
|
145
|
-
#
|
|
146
|
-
# two shapes `bin/model_check`'s `bluebook_in` reads. `nil` when
|
|
147
|
-
# neither holds a bluebook.
|
|
164
|
+
# Where a domain path keeps its bluebooks — see
|
|
165
|
+
# `Hecks::Corpus.bluebook_files`, the one definition every corpus
|
|
166
|
+
# walk shares. `nil` when neither shape holds a bluebook.
|
|
148
167
|
def bluebook_files(domain_path)
|
|
149
|
-
|
|
150
|
-
files = Dir[File.join(dir, "*.bluebook")]
|
|
151
|
-
return files unless files.empty?
|
|
152
|
-
end
|
|
153
|
-
nil
|
|
168
|
+
Hecks::Corpus.bluebook_files(domain_path)
|
|
154
169
|
end
|
|
155
170
|
|
|
156
|
-
#
|
|
171
|
+
# The same census over a domain on disk, booted the lightweight
|
|
157
172
|
# way `bin/model_check` and `Hecks::Codemod.load_bluebook` already
|
|
158
173
|
# do (ports and the two in-process adapters, no `Hecks.boot`, no
|
|
159
|
-
# live database, no `.hecksagon`: the census reads declared
|
|
174
|
+
# live database, no `.hecksagon`: the census reads declared shape,
|
|
160
175
|
# and a framework chapter a `.hecksagon` would attach is not this
|
|
161
176
|
# domain's own). Only the domain's own chapter is measured — the
|
|
162
177
|
# first bluebook loaded, the same "target chapter is always first"
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require_relative "isolated_boot"
|
|
3
|
+
require_relative "sequence_generator"
|
|
4
|
+
require_relative "replay"
|
|
5
|
+
require_relative "shrinker"
|
|
6
|
+
require_relative "differential"
|
|
7
|
+
|
|
8
|
+
module Hecks
|
|
9
|
+
module Fuzzing
|
|
10
|
+
# **One generated domain, checked** — the child-process half of
|
|
11
|
+
# `bin/qa_generated_domains` (one domain per process: every generated
|
|
12
|
+
# domain is named `QaGenerated`, see DomainGenerator's header).
|
|
13
|
+
#
|
|
14
|
+
# The same comparisons `bin/qa_sweep` runs on a rotation target, over
|
|
15
|
+
# sequences generated against a domain nobody wrote: with a compiled
|
|
16
|
+
# binary, `Differential.diff` (Ruby vs Rust, self-consistency, declared
|
|
17
|
+
# properties); without one, a Ruby-only replay (properties,
|
|
18
|
+
# self-consistency, an interpreter crash). A domain that does not even
|
|
19
|
+
# boot is `invalid` — a generator defect, not a finding, reported apart.
|
|
20
|
+
#
|
|
21
|
+
# `match:` is how domain-level shrinking asks "does this smaller domain
|
|
22
|
+
# still show the same finding?": only a divergence in the same mode
|
|
23
|
+
# whose `Shrinker.signature` contains the original's counts. Verb names
|
|
24
|
+
# survive a removal that did not touch them, so the signature does too.
|
|
25
|
+
module GeneratedDomainCheck
|
|
26
|
+
DIFFERENTIAL_MODES = %i[differential self_consistency properties_in_differential].freeze
|
|
27
|
+
|
|
28
|
+
module_function
|
|
29
|
+
|
|
30
|
+
def run(domain_path, seeds:, steps:, adversarial:, binary: nil, differ: nil, match: nil, shrink_budget: 0)
|
|
31
|
+
error = boot_error(domain_path)
|
|
32
|
+
return { "status" => "invalid", "error" => error } if error
|
|
33
|
+
|
|
34
|
+
(1..seeds).each do |seed|
|
|
35
|
+
finding = check_seed(domain_path, seed, steps, adversarial, binary, differ, match)
|
|
36
|
+
next unless finding
|
|
37
|
+
|
|
38
|
+
finding["shrunk_steps"] = shrink(domain_path, finding, binary, differ, shrink_budget) if shrink_budget.positive?
|
|
39
|
+
return finding.merge("status" => "found", "seeds_run" => seed)
|
|
40
|
+
end
|
|
41
|
+
{ "status" => "clean", "seeds_run" => seeds }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def boot_error(domain_path)
|
|
45
|
+
IsolatedBoot.call(domain_path) { |copy| Hecks.boot(copy) }
|
|
46
|
+
nil
|
|
47
|
+
rescue StandardError, ScriptError => e
|
|
48
|
+
"#{e.class}: #{e.message.lines.first&.strip}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def check_seed(domain_path, seed, steps, adversarial, binary, differ, match)
|
|
52
|
+
sequence = SequenceGenerator.generate(domain_path, seed: seed, steps: steps, adversarial: adversarial)
|
|
53
|
+
rescue StandardError => e
|
|
54
|
+
finding(seed, :generator, [{ field: "generator_crash", detail: "#{e.class}: #{e.message}" }], [], match)
|
|
55
|
+
else
|
|
56
|
+
outcomes(domain_path, sequence, binary, differ).each do |mode, divergences|
|
|
57
|
+
found = finding(seed, mode, divergences, sequence, match)
|
|
58
|
+
return found if found
|
|
59
|
+
end
|
|
60
|
+
nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def outcomes(domain_path, sequence, binary, differ)
|
|
64
|
+
return Differential.diff(differ, domain_path, sequence, binary, modes: DIFFERENTIAL_MODES) if binary
|
|
65
|
+
|
|
66
|
+
history = Replay.call(domain_path, sequence, self_consistency: true)
|
|
67
|
+
{ ruby_only: Differential.property_divergences(history),
|
|
68
|
+
self_consistency: Differential.self_consistency_divergences(history) }
|
|
69
|
+
rescue StandardError => e
|
|
70
|
+
{ ruby_only: [{ field: "crash", detail: "#{e.class}: #{e.message}" }] }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def finding(seed, mode, divergences, sequence, match)
|
|
74
|
+
return nil if divergences.empty?
|
|
75
|
+
|
|
76
|
+
signature = Shrinker.signature(divergences)
|
|
77
|
+
return nil if match && !(match["mode"] == mode.to_s && Set.new(match["signature"]).subset?(signature))
|
|
78
|
+
|
|
79
|
+
{ "seed" => seed, "mode" => mode.to_s, "signature" => signature.to_a.sort, "steps" => sequence,
|
|
80
|
+
"divergences" => JSON.parse(JSON.generate(divergences)) }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# The step-level half of shrinking, in this same process: the domain
|
|
84
|
+
# is already as small as the parent could make it.
|
|
85
|
+
def shrink(domain_path, found, binary, differ, budget)
|
|
86
|
+
original = Set.new(found["signature"])
|
|
87
|
+
mode = found["mode"].to_sym
|
|
88
|
+
Shrinker.call(found["steps"], budget: budget) do |candidate|
|
|
89
|
+
divergences = outcomes(domain_path, candidate, binary, differ)[mode] || []
|
|
90
|
+
Shrinker.reproduces?(original, divergences)
|
|
91
|
+
end.steps
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Fuzzing
|
|
3
|
-
# A value that is
|
|
3
|
+
# A value that is deliberately the wrong shape for the attribute it is handed
|
|
4
4
|
# to — the sibling of ValueGenerator, which only ever builds values that satisfy
|
|
5
5
|
# a declared type.
|
|
6
6
|
#
|
|
@@ -14,7 +14,7 @@ module Hecks
|
|
|
14
14
|
# Rendering's whole story), and no generated value
|
|
15
15
|
# could ever have produced it.
|
|
16
16
|
#
|
|
17
|
-
# These are expected to be
|
|
17
|
+
# These are expected to be refused, and that is the point : a refusal is an
|
|
18
18
|
# answer, and its wording is pinned byte-for-byte. The bugs live in
|
|
19
19
|
# the sentence, not in the happy path.
|
|
20
20
|
module InvalidValueGenerator
|
|
@@ -37,7 +37,7 @@ module Hecks
|
|
|
37
37
|
build(kind, attribute, aggregate, random: random)
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
# Only the confusions that
|
|
40
|
+
# Only the confusions that mean anything for this attribute. Offering
|
|
41
41
|
# `scalar_for_object` for a plain Integer would just be a second spelling of
|
|
42
42
|
# `numeral_string`, and a kind that cannot be wrong for the attribute it is
|
|
43
43
|
# handed teaches the corpus nothing.
|
|
@@ -60,16 +60,16 @@ module Hecks
|
|
|
60
60
|
end
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
-
# A bare scalar where a value object is declared. A
|
|
63
|
+
# A bare scalar where a value object is declared. A single-field value object
|
|
64
64
|
# legitimately accepts one (that is the standing-in rule every domain relies
|
|
65
|
-
# on), so the interesting case is a value object with
|
|
65
|
+
# on), so the interesting case is a value object with several fields, where a
|
|
66
66
|
# scalar cannot stand for anything and the refusal has to say so.
|
|
67
67
|
def scalar_for(attribute, aggregate, random:)
|
|
68
68
|
value_object = aggregate.value_object(attribute.type.to_s)
|
|
69
69
|
sole = value_object&.sole_attribute
|
|
70
70
|
return "a bare scalar" unless sole
|
|
71
71
|
|
|
72
|
-
# One field, so a scalar is legal — corrupt the
|
|
72
|
+
# One field, so a scalar is legal — corrupt the field's own type instead,
|
|
73
73
|
# which is still a shape the attribute cannot accept.
|
|
74
74
|
{ sole.name.to_s => ["nested", "array"] }
|
|
75
75
|
end
|