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
|
@@ -14,11 +14,11 @@ module Hecks
|
|
|
14
14
|
# (#check_piece_invariants, #eligible_rows, #nest_rows,
|
|
15
15
|
# #recompute_median) each leans on.
|
|
16
16
|
module InvariantsAndAggregation
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# admissibility.rb) checks these
|
|
20
|
-
#
|
|
21
|
-
# now re-checked a record
|
|
17
|
+
# Every stored record still satisfies its own aggregate's declared
|
|
18
|
+
# invariants — Admissibility#enforce_invariants (command_rules/
|
|
19
|
+
# admissibility.rb) checks these after every command's mutations,
|
|
20
|
+
# before save, the same point `ensures` is checked. Nothing until
|
|
21
|
+
# now re-checked a record after a whole replay finished, independent
|
|
22
22
|
# of whichever call site was supposed to have refused a violation
|
|
23
23
|
# in the first place — a record failing its own declared invariant
|
|
24
24
|
# here is proof a violating write landed anyway: the call site
|
|
@@ -27,9 +27,9 @@ module Hecks
|
|
|
27
27
|
#
|
|
28
28
|
# `history[:instances]` entries are already plain, symbol-keyed
|
|
29
29
|
# state Hashes (Replay.call's own `record.state`) — called against
|
|
30
|
-
# Evaluator.call the
|
|
30
|
+
# Evaluator.call the same way ValueObject::Builder#build already
|
|
31
31
|
# does for a VO's own invariants (value/coercion.rb), no GuardState
|
|
32
|
-
# wrapper needed the way enforce_invariants' own
|
|
32
|
+
# wrapper needed the way enforce_invariants' own live call uses one
|
|
33
33
|
# (GuardState exists for `parent.`/projected-field dereferencing
|
|
34
34
|
# mid-dispatch; a stored record's own scalar fields need none of
|
|
35
35
|
# that to re-check a same-aggregate invariant against itself).
|
|
@@ -39,8 +39,8 @@ module Hecks
|
|
|
39
39
|
#
|
|
40
40
|
# Entity#invariants (round 7) closes here too, not for free —
|
|
41
41
|
# `stored_records_satisfy_declared_invariants` only ever checked
|
|
42
|
-
# the
|
|
43
|
-
# checked against every
|
|
42
|
+
# the aggregate's own flat state; a piece's own invariant is
|
|
43
|
+
# checked against every element of a `list_of` field, a genuinely
|
|
44
44
|
# different walk `check_piece_invariants` below makes,
|
|
45
45
|
# independently of `Admissibility#check_entity_invariants` (the
|
|
46
46
|
# live enforcement path this property exists to catch drifting
|
|
@@ -70,11 +70,11 @@ module Hecks
|
|
|
70
70
|
offenders.empty? || offenders.join("; ")
|
|
71
71
|
end
|
|
72
72
|
|
|
73
|
-
# A
|
|
74
|
-
# `list_of` field holds — the
|
|
73
|
+
# A piece's own invariant, checked against every element a
|
|
74
|
+
# `list_of` field holds — the same lookup `Admissibility#
|
|
75
75
|
# check_entity_invariants` makes (`owner.attributes.find { |a|
|
|
76
76
|
# a.list? && a.type.to_s == entity.hecks_name }`), independently
|
|
77
|
-
# reapplied here against a
|
|
77
|
+
# reapplied here against a stored record's own plain Hash state
|
|
78
78
|
# rather than a live `Instance`.
|
|
79
79
|
def check_piece_invariants(owner_construct, owner_state, key)
|
|
80
80
|
owner_construct.entities.each do |entity|
|
|
@@ -99,23 +99,23 @@ module Hecks
|
|
|
99
99
|
nil
|
|
100
100
|
end
|
|
101
101
|
|
|
102
|
-
# A
|
|
103
|
-
#
|
|
102
|
+
# A saga instance's own checkpoint survives being written and read
|
|
103
|
+
# back — the durability contract `SagaInterpreter#checkpoint` makes
|
|
104
104
|
# (`state:` plus a `deep_copy`d `memory:`, handed to whatever
|
|
105
105
|
# adapter answers `save_saga`) and `Registry#rehydrate_sagas!`
|
|
106
106
|
# promises to restore on the next boot (`each_saga` yielding
|
|
107
107
|
# `[pm, correlation, state, memory]` back into `saga_instances`).
|
|
108
|
-
# `Replay` captures the
|
|
108
|
+
# `Replay` captures the live store already materialised the same
|
|
109
109
|
# way `checkpoint` itself does (`Value.materialize`, not raw
|
|
110
110
|
# `Runtime::Value`s — see its own comment); this property pushes
|
|
111
|
-
# that captured memory through the
|
|
111
|
+
# that captured memory through the same `JSON.generate` then
|
|
112
112
|
# `JSON.parse(symbolize_names: true)` round-trip `checkpoint`'s own
|
|
113
113
|
# `deep_copy` performs (mirrored here rather than called — a
|
|
114
114
|
# private instance method with no registry to hand it) and checks
|
|
115
115
|
# it comes back byte-identical. A memory holding anything that
|
|
116
116
|
# round-trip cannot carry faithfully — a bare Symbol leaf, a
|
|
117
117
|
# non-JSON type a future field introduces — is corruption the
|
|
118
|
-
# durable path would introduce on a
|
|
118
|
+
# durable path would introduce on a real restart, caught here
|
|
119
119
|
# without needing one.
|
|
120
120
|
#
|
|
121
121
|
# `declares_state?` (Behaviour::ProcessManager) is the other half:
|
|
@@ -150,11 +150,11 @@ module Hecks
|
|
|
150
150
|
offenders.empty? || offenders.join("; ")
|
|
151
151
|
end
|
|
152
152
|
|
|
153
|
-
# A `for_each`
|
|
154
|
-
#
|
|
153
|
+
# A `for_each` policy dispatches exactly once per row its declared
|
|
154
|
+
# query answers — never once for the triggering event regardless of
|
|
155
155
|
# row count, never skipping a matched row, never firing on a row a
|
|
156
|
-
# concurrent mutation only made match
|
|
157
|
-
# computes the expected row-id set
|
|
156
|
+
# concurrent mutation only made match after the fact. `Replay`
|
|
157
|
+
# computes the expected row-id set independently, at the same
|
|
158
158
|
# instant the real dispatch runs (`Replay.expected_fan_out_rows`,
|
|
159
159
|
# the query oracle's own shape aimed at fan-out: two engines
|
|
160
160
|
# compared, never one graded against itself), and records it
|
|
@@ -183,21 +183,21 @@ module Hecks
|
|
|
183
183
|
offenders.empty? || offenders.join("; ")
|
|
184
184
|
end
|
|
185
185
|
|
|
186
|
-
# A `count`/`median`
|
|
187
|
-
#
|
|
186
|
+
# A `count`/`median` report's reduced scalar matches the same
|
|
187
|
+
# reduction done independently, over the same eligible rows —
|
|
188
188
|
# `ReadModelInterpreter#project`'s own FK-join (root first, then
|
|
189
189
|
# each many-side head matched against it) and `#median` (odd →
|
|
190
190
|
# the true middle, even → the average of the two middles as a
|
|
191
191
|
# Float, empty → `nil`; `count` is the filtered length, empty →
|
|
192
192
|
# `0`), reproduced here in plain Ruby against `history[:instances]`
|
|
193
193
|
# rather than a live registry — `FieldPath.dig` +
|
|
194
|
-
# `Ports::Query::InMemory.comparable`/`.holds?` are the
|
|
194
|
+
# `Ports::Query::InMemory.comparable`/`.holds?` are the same two
|
|
195
195
|
# calls the interpreter itself makes to read a field and judge a
|
|
196
196
|
# `where`, called here rather than re-derived, so this oracle
|
|
197
197
|
# cannot drift from what "read a field" or "a clause holds" mean
|
|
198
198
|
# without the interpreter drifting the identical way.
|
|
199
199
|
#
|
|
200
|
-
# Only a report whose `:query` is answered by the
|
|
200
|
+
# Only a report whose `:query` is answered by the same bluebook
|
|
201
201
|
# `history[:bluebook]` carries (the bare `Domain.report_name`
|
|
202
202
|
# form, `domain == bluebook.name`) is checked — the same "only
|
|
203
203
|
# what we have the grammar for" scope `lifecycle_values_are_declared`
|
|
@@ -206,7 +206,7 @@ module Hecks
|
|
|
206
206
|
# ("only a report answered by this bluebook, only a count/median
|
|
207
207
|
# report, only one with a reduced many-side head") each gating the
|
|
208
208
|
# next, ending in one independent recomputation compared against
|
|
209
|
-
# the live answer. The guards are what make this oracle
|
|
209
|
+
# the live answer. The guards are what make this oracle scoped
|
|
210
210
|
# correctly, not incidental complexity — narrower than "every
|
|
211
211
|
# branch reads as its own precondition."
|
|
212
212
|
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
@@ -239,14 +239,14 @@ module Hecks
|
|
|
239
239
|
end
|
|
240
240
|
|
|
241
241
|
# `aggregation_matches_recompute`'s own shape, extended from
|
|
242
|
-
# reducing a many-side head to a scalar (count/median) to
|
|
242
|
+
# reducing a many-side head to a scalar (count/median) to nesting
|
|
243
243
|
# it — `ReadModelInterpreter#group_by_target`/`#nest`, reproduced
|
|
244
244
|
# here in plain Ruby against `history[:instances]` the same way
|
|
245
245
|
# `eligible_rows` already reproduces the FK-join and `where`
|
|
246
246
|
# narrowing count/median share. `Value.materialize_unwrapped` is
|
|
247
|
-
# the
|
|
247
|
+
# the same call `#project` makes before nesting (a single-field
|
|
248
248
|
# value object recurses to its bare scalar — a real grouping key
|
|
249
|
-
# has to
|
|
249
|
+
# has to be one) — called here rather than re-derived, so this
|
|
250
250
|
# oracle cannot drift from what "the group key" means without the
|
|
251
251
|
# interpreter drifting the identical way.
|
|
252
252
|
#
|
|
@@ -298,17 +298,17 @@ module Hecks
|
|
|
298
298
|
end
|
|
299
299
|
end
|
|
300
300
|
|
|
301
|
-
#
|
|
301
|
+
# The eligible rows a `count`/`median` head reduces — every
|
|
302
302
|
# instance of the reduced head's own aggregate, FK-matched against
|
|
303
303
|
# the report's root reference (if it has one; a rootless report has
|
|
304
304
|
# none to match) exactly the way `ReadModelInterpreter#reference_fields`
|
|
305
305
|
# finds the matching attribute, then narrowed by the report's own
|
|
306
|
-
# `where` clauses via the
|
|
306
|
+
# `where` clauses via the same `InMemory.holds?` the interpreter's
|
|
307
307
|
# `execute` calls.
|
|
308
308
|
def eligible_rows(bluebook, instances, domain, model, reduced_head, args)
|
|
309
309
|
aggregate = bluebook.aggregate(reduced_head[:aggregate])
|
|
310
310
|
prefix = "#{domain}::#{reduced_head[:aggregate]}#"
|
|
311
|
-
# `id:`
|
|
311
|
+
# `id:` merged in, the same `record.to_h` (`@state.merge(id:
|
|
312
312
|
# @id)`) every live head row carries — count/median never read
|
|
313
313
|
# it, but group_by_matches_recompute's own independent nesting
|
|
314
314
|
# does, the same way ReadModelInterpreter#row(record) = record.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require_relative "../nondeterministic"
|
|
2
|
+
|
|
1
3
|
module Hecks
|
|
2
4
|
module Fuzzing
|
|
3
5
|
module Properties
|
|
@@ -12,11 +14,11 @@ module Hecks
|
|
|
12
14
|
# trustworthy in the first place.
|
|
13
15
|
module LifecycleAndReplay
|
|
14
16
|
# Every lifecycle field a replay leaves an instance holding is one
|
|
15
|
-
# of the aggregate's
|
|
17
|
+
# of the aggregate's own declared states — the full set, not just
|
|
16
18
|
# `Lifecycle#states`' default+targets (see ModelCheck.full_states'
|
|
17
19
|
# own comment on that hole). The tie to M2 is direct: the model
|
|
18
|
-
# checker proves which states a domain's
|
|
19
|
-
# produce ; this proves a
|
|
20
|
+
# checker proves which states a domain's own declarations can ever
|
|
21
|
+
# produce ; this proves a real run never produced anything else —
|
|
20
22
|
# a coercion bug, a stale string surviving a rename, a default
|
|
21
23
|
# that drifted from the declared set, would all show up here as a
|
|
22
24
|
# value nothing upstream would have predicted.
|
|
@@ -44,10 +46,10 @@ module Hecks
|
|
|
44
46
|
end
|
|
45
47
|
|
|
46
48
|
# Every saga advance a replay actually logged moved along an edge
|
|
47
|
-
# the process manager
|
|
49
|
+
# the process manager declared — `(from, to)` pairs that appear in
|
|
48
50
|
# `saga_log` with `advanced: true` must be a `(handler.from_state,
|
|
49
51
|
# handler.to_state)` pair some handler on that PM declares
|
|
50
|
-
# (compensation edges included ; a
|
|
52
|
+
# (compensation edges included ; a refused-triggered advance is a
|
|
51
53
|
# handler like any other). A saga that advanced along a pair no
|
|
52
54
|
# handler names would mean the runtime moved state the language
|
|
53
55
|
# never authorized — the same trust ModelCheck's static reachability
|
|
@@ -72,56 +74,41 @@ module Hecks
|
|
|
72
74
|
offenders.empty? || offenders.join("; ")
|
|
73
75
|
end
|
|
74
76
|
|
|
75
|
-
#
|
|
77
|
+
# The foundational one. `Hecks::Runtime` mints nothing — every
|
|
76
78
|
# identity is declared and derived, never invented (see
|
|
77
|
-
# command_interpreter.rb's own "
|
|
79
|
+
# command_interpreter.rb's own "nothing is minted" — a random hex,
|
|
78
80
|
# a counter, anything not reproducible from the payload, was
|
|
79
81
|
# refused out of the runtime specifically because it broke this).
|
|
80
|
-
# So the
|
|
81
|
-
#
|
|
82
|
+
# So the same steps, replayed against a fresh boot, must produce
|
|
83
|
+
# byte-identical events, refusals, and instances — any drift here
|
|
82
84
|
# is nondeterminism the runtime promised not to have: a wall-clock
|
|
83
85
|
# read that leaked into compared state, a Hash iteration order a
|
|
84
86
|
# comparison depended on, anything. Two independent replays, not a
|
|
85
|
-
# cached one compared to itself, so a bug that corrupts the
|
|
87
|
+
# cached one compared to itself, so a bug that corrupts the first
|
|
86
88
|
# run's own bookkeeping cannot pass by agreeing with itself.
|
|
87
89
|
def replay_is_deterministic(domain_path, steps, adapter: :memory)
|
|
88
90
|
first = Replay.call(domain_path, steps, adapter: adapter)
|
|
89
91
|
second = Replay.call(domain_path, steps, adapter: adapter)
|
|
90
92
|
|
|
91
|
-
#
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
# `row[:event][:occurred_at]` — the outbox row's own `event:` field
|
|
101
|
-
# is `Outbox.serialize_event`'s `event.to_h.merge(correlation:...)`,
|
|
102
|
-
# the FULL `Event#to_h`, unlike `history[:events]` (this file's own
|
|
103
|
-
# `events = runtime.events.map { {name:, aggregate:, id:, payload:}
|
|
104
|
-
# }`, above) which has ALWAYS deliberately left `occurred_at` OFF
|
|
105
|
-
# the compared surface for exactly this reason: a wall-clock read,
|
|
106
|
-
# never reproducible byte-for-byte between two independent
|
|
107
|
-
# replaying processes a second apart. Stripped here the same way,
|
|
108
|
-
# for the one place it newly reappears.
|
|
109
|
-
#
|
|
110
|
-
# Both stripped from each `outbox_traces` row before comparing
|
|
111
|
-
# rather than dropping `outbox_traces` wholesale: everything else
|
|
112
|
-
# on a row (status, consumer, kind, the event's own name/
|
|
113
|
-
# aggregate/id/payload) IS reproducible from the steps alone and
|
|
93
|
+
# What leaves this comparison, and why, is declared once in
|
|
94
|
+
# `Nondeterministic::FIELDS`: the `outbox_row` group (per-enqueue
|
|
95
|
+
# uuids), the `event` group on each row's full `Event#to_h`
|
|
96
|
+
# (`occurred_at`, which `history[:events]` never carried), and the
|
|
97
|
+
# `history` group (live IR objects). Stripped per `outbox_traces`
|
|
98
|
+
# row rather than dropping `outbox_traces` wholesale: everything
|
|
99
|
+
# else on a row (status, consumer, kind, the event's own name/
|
|
100
|
+
# aggregate/id/payload) is reproducible from the steps alone and
|
|
114
101
|
# stays checked.
|
|
115
102
|
strip_outbox_nondeterminism = lambda do |history|
|
|
116
103
|
traces = Array(history[:outbox_traces]).map do |trace|
|
|
117
104
|
trace.merge(rows: trace[:rows].map do |row|
|
|
118
|
-
|
|
105
|
+
Nondeterministic.strip(row, :outbox_row).merge(event: Nondeterministic.strip(row[:event], :event))
|
|
119
106
|
end)
|
|
120
107
|
end
|
|
121
108
|
history.merge(outbox_traces: traces)
|
|
122
109
|
end
|
|
123
110
|
|
|
124
|
-
comparable = ->(history) { strip_outbox_nondeterminism.call(history)
|
|
111
|
+
comparable = ->(history) { Nondeterministic.strip(strip_outbox_nondeterminism.call(history), :history) }
|
|
125
112
|
return true if comparable.call(first) == comparable.call(second)
|
|
126
113
|
|
|
127
114
|
"two replays of the same #{steps.length} steps produced different histories"
|
|
@@ -3,61 +3,61 @@ require_relative "../../bluebook/expression/evaluator"
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Fuzzing
|
|
5
5
|
module Properties
|
|
6
|
-
#
|
|
6
|
+
# The transactional outbox's own contract (`Runtime::Outbox`, that
|
|
7
7
|
# file's own header), held to the history a replay actually produced
|
|
8
8
|
# rather than trusted. Two facts, each independently checkable from
|
|
9
9
|
# `history[:outbox_traces]` (`Replay#call`'s own before/after
|
|
10
10
|
# capture, one entry per step whose dispatch enqueued at least one
|
|
11
11
|
# row):
|
|
12
12
|
#
|
|
13
|
-
# 1. "
|
|
13
|
+
# 1. "delivery is inline by default" — a row this replay's own
|
|
14
14
|
# dispatch enqueued must not still be `pending`/`claimed` once
|
|
15
|
-
# that
|
|
15
|
+
# that same call returns (nothing here ever simulates a
|
|
16
16
|
# crash), and must not be `failed` either — `deliver_row`'s
|
|
17
17
|
# own rescue only reaches `failed` for a genuine defect in the
|
|
18
|
-
#
|
|
18
|
+
# relay's own consumer resolution (a row naming a policy/
|
|
19
19
|
# process_manager `run_consumer`'s own independent registry
|
|
20
20
|
# lookup cannot find — `WiringError`), never an ordinary
|
|
21
21
|
# domain refusal (`PolicyInterpreter#deliver`/`SagaInterpreter#
|
|
22
|
-
# advance` both rescue those
|
|
22
|
+
# advance` both rescue those themselves, recording `delivered:
|
|
23
23
|
# false` on the reaction/saga log and letting `run_consumer`
|
|
24
24
|
# return normally). Both checked for every row, `saga:` and
|
|
25
25
|
# `policy:` alike.
|
|
26
26
|
#
|
|
27
|
-
# 2. A `policy:`
|
|
27
|
+
# 2. A `policy:` row specifically — `PolicyInterpreter#deliver`
|
|
28
28
|
# returns `nil` (no `reaction_log` entry appended at all)
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
# every
|
|
29
|
+
# exactly when its own `where` (or, for a fan-out policy, the
|
|
30
|
+
# same `where`, gating the whole `for_each`) does not hold;
|
|
31
|
+
# every other outcome (delivered, refused, a defect,
|
|
32
32
|
# reaction-depth-reached) is still a non-nil record `#react`
|
|
33
|
-
# appends. So a `delivered` policy row with
|
|
34
|
-
# `reaction_log` entry is legitimate
|
|
35
|
-
# own `where`, independently
|
|
33
|
+
# appends. So a `delivered` policy row with no matching
|
|
34
|
+
# `reaction_log` entry is legitimate only when that policy's
|
|
35
|
+
# own `where`, independently re-evaluated here against the
|
|
36
36
|
# row's own recorded event, genuinely does not hold. A
|
|
37
|
-
# `for_each` policy's own fan-out correctness (how
|
|
37
|
+
# `for_each` policy's own fan-out correctness (how many rows
|
|
38
38
|
# it should have dispatched to) is `fanout_dispatches_once_
|
|
39
39
|
# per_matching_row`'s job, not this one's.
|
|
40
40
|
#
|
|
41
|
-
# A `saga:`
|
|
42
|
-
# was the first shape this property shipped with, and it was
|
|
41
|
+
# A `saga:` row has no equivalent second check, deliberately — this
|
|
42
|
+
# was the first shape this property shipped with, and it was wrong,
|
|
43
43
|
# caught live against `examples/banking` before this comment
|
|
44
44
|
# existed: `Fanout.sagas`' own `listens?` (starts_on/ends_on/
|
|
45
|
-
# handler_for matching the event
|
|
46
|
-
# whether a
|
|
45
|
+
# handler_for matching the event name alone) says nothing about
|
|
46
|
+
# whether a correlation resolves or a live instance exists, and
|
|
47
47
|
# `begin_saga`/`end_saga` (saga_interpreter.rb) both have silent,
|
|
48
|
-
# perfectly ordinary no-op paths that append
|
|
48
|
+
# perfectly ordinary no-op paths that append nothing to `saga_log`
|
|
49
49
|
# — `begin_saga` when an instance under that correlation already
|
|
50
|
-
# exists, `end_saga` when
|
|
50
|
+
# exists, `end_saga` when no live instance exists to end (an
|
|
51
51
|
# `AccountOpened` fired by opening an account directly, bypassing
|
|
52
52
|
# the onboarding flow whose `ends_on` names that same event,
|
|
53
53
|
# reproduces this exactly: `Fanout.listens?` enqueues the row
|
|
54
|
-
# because the event
|
|
54
|
+
# because the event name matches `ends_on`, `end_saga` finds
|
|
55
55
|
# nothing under that correlation to delete, and neither logs a
|
|
56
56
|
# word). A `saga:` row draining to `delivered` with zero matching
|
|
57
|
-
# `saga_log` entries is therefore
|
|
57
|
+
# `saga_log` entries is therefore not a finding — only check 1
|
|
58
58
|
# applies to it.
|
|
59
59
|
#
|
|
60
|
-
#
|
|
60
|
+
# **Not a grammar construct** — `FEATURE_COVERAGE`'s own `dry_runs_
|
|
61
61
|
# leave_no_trace` precedent: the outbox is a runtime door
|
|
62
62
|
# (`Runtime::Outbox`), not a word a bluebook declares, so there is
|
|
63
63
|
# no feature string here to claim.
|
|
@@ -117,14 +117,14 @@ module Hecks
|
|
|
117
117
|
# with itself (the same rule `resolve_dispatch_binding`'s own
|
|
118
118
|
# comment states). `Evaluator.call` (the raw-string entry, parsed
|
|
119
119
|
# and cached — never `call_rule`, which needs the policy's own
|
|
120
|
-
#
|
|
120
|
+
# build-time `where_rule` AST, an object this history has no
|
|
121
121
|
# reason to carry) is the exact same call `Replay#fan_out_finding`
|
|
122
122
|
# already makes for the identical fact one property over
|
|
123
123
|
# (`policy.where.to_s.empty? || Evaluator.call(policy.where, {},
|
|
124
124
|
# payload)`), reused rather than re-derived a second, slightly
|
|
125
125
|
# different way. `rescue`d to `nil`, not `false`: a where clause
|
|
126
126
|
# that cannot be re-evaluated from the row's own recorded payload
|
|
127
|
-
# alone is
|
|
127
|
+
# alone is inconclusive, not proof either way — the same "never a
|
|
128
128
|
# claimed pass or a claimed mismatch from a resolution this replay
|
|
129
129
|
# cannot actually reproduce" discipline `build_guard_check`'s own
|
|
130
130
|
# rescue clause already follows.
|
|
@@ -10,7 +10,7 @@ module Hecks
|
|
|
10
10
|
# (#query_for_verb, #query_eligible_rows, #resolve_hop_clause) other
|
|
11
11
|
# property modules in this directory also call.
|
|
12
12
|
module Querying
|
|
13
|
-
#
|
|
13
|
+
# The query oracle — differential testing within the one runtime,
|
|
14
14
|
# the shape the retired cross-runtime harness should always have
|
|
15
15
|
# been. Every generated ask was answered twice at the same instant
|
|
16
16
|
# (Replay records both): once through whatever the aggregate is
|
|
@@ -18,17 +18,17 @@ module Hecks
|
|
|
18
18
|
# a SQL binding would compile it), once through the reference
|
|
19
19
|
# interpreter's own evaluation. The two are separate, live
|
|
20
20
|
# implementations of the same comparator vocabulary, and they have
|
|
21
|
-
# drifted before — an adapter that
|
|
21
|
+
# drifted before — an adapter that accepts what the reference says
|
|
22
22
|
# matches nothing, or orders what it refuses to order, shows up
|
|
23
23
|
# here as a finding no self-referential adapter spec could see.
|
|
24
24
|
# M23 — `Replay` now runs the native and reference engines
|
|
25
|
-
#
|
|
25
|
+
# independently (each in its own begin/rescue — see that file's own
|
|
26
26
|
# comment at the capture site), so this property can tell apart what
|
|
27
27
|
# used to be indistinguishable: "both engines refused" (fine — the
|
|
28
28
|
# ask was genuinely bad, nothing to compare) from "one refused and
|
|
29
29
|
# the other did not" (a real divergence — the two engines disagree
|
|
30
|
-
# about whether the ask was even
|
|
31
|
-
# `native_refused`/`reference_refused` are read by
|
|
30
|
+
# about whether the ask was even valid, never mind what it answers).
|
|
31
|
+
# `native_refused`/`reference_refused` are read by key presence, not
|
|
32
32
|
# truthiness — `Replay` only ever adds `:error`/`:reference_error`
|
|
33
33
|
# to an entry when that side actually raised, so an absent key is an
|
|
34
34
|
# unambiguous "this side answered." A read-model ask (no reference
|
|
@@ -59,16 +59,16 @@ module Hecks
|
|
|
59
59
|
offenders.empty? || offenders.join("; ")
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
-
#
|
|
62
|
+
# The same "two engines, compared" shape query_answers_match_reference
|
|
63
63
|
# already uses, aimed squarely at Query#options' offset/limit pair —
|
|
64
|
-
# but recomputed from history[:instances] directly, a
|
|
64
|
+
# but recomputed from history[:instances] directly, a third,
|
|
65
65
|
# independent computation, rather than comparing QueryInterpreter's
|
|
66
66
|
# own native and reference paths against each other (which could
|
|
67
67
|
# share the identical bug neither implementation happened to hit —
|
|
68
|
-
# see #4's own fix, which touched
|
|
68
|
+
# see #4's own fix, which touched both #interpret and
|
|
69
69
|
# #reference_interpret at once). `order_by` declared alongside
|
|
70
70
|
# `offset` or `limit` names a genuinely paged query. Ports::Query::
|
|
71
|
-
# Ordering.apply is the
|
|
71
|
+
# Ordering.apply is the same engine QueryInterpreter#ordered calls,
|
|
72
72
|
# reused here rather than re-derived, so this oracle cannot drift
|
|
73
73
|
# from what "in order" means without the interpreter drifting the
|
|
74
74
|
# identical way — only the offset-then-limit .drop/.first slice
|
|
@@ -112,7 +112,7 @@ module Hecks
|
|
|
112
112
|
offenders.empty? || offenders.join("; ")
|
|
113
113
|
end
|
|
114
114
|
|
|
115
|
-
#
|
|
115
|
+
# The declared Query itself, resolved from a replayed verb — the
|
|
116
116
|
# same shape #command_for_verb resolves a command by, one
|
|
117
117
|
# construct over. Entity-level queries (a dotted query_path) are
|
|
118
118
|
# out of scope here — paging on an entity's own list has no real
|
|
@@ -128,24 +128,24 @@ module Hecks
|
|
|
128
128
|
aggregate&.query(query_path)
|
|
129
129
|
end
|
|
130
130
|
|
|
131
|
-
# A
|
|
131
|
+
# A query's own rows — unlike #eligible_rows (a ReadModel's
|
|
132
132
|
# reduced/grouped many-side head, possibly FK-joined against a
|
|
133
|
-
# root), a Query always asks about its
|
|
133
|
+
# root), a Query always asks about its own owning aggregate
|
|
134
134
|
# directly ; no join, no reference_target. `id:` merged in the
|
|
135
135
|
# same way #eligible_rows' own rows are, since a stable sort
|
|
136
136
|
# (Ordering.apply's own `identity:`) and the real answer's own
|
|
137
137
|
# `record.state.merge(id: record.id)` both need it.
|
|
138
|
-
# `bluebooks:` — needed
|
|
138
|
+
# `bluebooks:` — needed only to recognise and resolve a `/` hop
|
|
139
139
|
# clause (`engagement/client/status`, hop_chain.bluebook's own
|
|
140
|
-
# PricedAboveViaEngagement): a hop's head names one of the
|
|
140
|
+
# PricedAboveViaEngagement): a hop's head names one of the owning
|
|
141
141
|
# aggregate's declared references, and only the declaration graph
|
|
142
142
|
# can say which attribute that is and which aggregate it targets.
|
|
143
143
|
# A local clause never consults it. Latent gap this closed, found
|
|
144
144
|
# by the fuzzer itself the first time a generated sequence ever
|
|
145
|
-
# built a full hop chain
|
|
145
|
+
# built a full hop chain and had its paged query answer a row
|
|
146
146
|
# (seed 1, the moment scalar_value_objects.bluebook joined the
|
|
147
147
|
# fixtures corpus and shifted every seeded draw): the recompute
|
|
148
|
-
# dug `engagement/client/status` as a
|
|
148
|
+
# dug `engagement/client/status` as a local dotted path, found
|
|
149
149
|
# nil, and declared every genuinely-eligible row ineligible — a
|
|
150
150
|
# false property violation against a correct runtime answer,
|
|
151
151
|
# reproducible on an untouched main with this same 4-step script.
|
|
@@ -173,7 +173,7 @@ module Hecks
|
|
|
173
173
|
# would be checking the runtime against itself). One hop peels
|
|
174
174
|
# off the head (`HopPath.next_hop`, the identical one-step
|
|
175
175
|
# primitive the live fold uses), the inner clause recurses
|
|
176
|
-
# through `query_eligible_rows` against the
|
|
176
|
+
# through `query_eligible_rows` against the target's own
|
|
177
177
|
# snapshot rows (so a multi-hop tail resolves hop by hop, exactly
|
|
178
178
|
# as the live path's own recursion does), and the ids that
|
|
179
179
|
# answered fold back as the same local `in` membership clause the
|