hecks 1.2.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 +60 -2
- 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/postgres_era.adapter +5 -0
- 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 +145 -0
- data/lib/hecks/behaviors/dsl.rb +2 -2
- data/lib/hecks/behaviors/expectations.rb +51 -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 +27 -3
- 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 +122 -86
- 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 +301 -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 +183 -20
- 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 +361 -0
- 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 +124 -0
- data/lib/hecks/fuzzing/form_census.rb +199 -0
- 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 +226 -38
- data/lib/hecks/fuzzing/nondeterministic.rb +67 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +161 -0
- data/lib/hecks/fuzzing/properties/corrections.rb +100 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +321 -41
- data/lib/hecks/fuzzing/properties/guards.rb +129 -26
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +32 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +31 -11
- data/lib/hecks/fuzzing/properties/outbox.rb +142 -0
- data/lib/hecks/fuzzing/properties/querying.rb +17 -17
- data/lib/hecks/fuzzing/properties.rb +84 -39
- data/lib/hecks/fuzzing/qa_settings.rb +152 -0
- data/lib/hecks/fuzzing/replay.rb +224 -114
- data/lib/hecks/fuzzing/rotation_priority.rb +94 -0
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +113 -0
- data/lib/hecks/fuzzing/self_consistency.rb +676 -0
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +526 -0
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +101 -30
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +55 -8
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +31 -11
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +123 -32
- data/lib/hecks/fuzzing/sequence_generator.rb +152 -31
- data/lib/hecks/fuzzing/shrinker.rb +197 -0
- data/lib/hecks/fuzzing/structural_skips.rb +39 -0
- data/lib/hecks/fuzzing/sweep_depth.rb +53 -0
- data/lib/hecks/fuzzing/target_capabilities.rb +180 -0
- data/lib/hecks/fuzzing/value_generator.rb +66 -14
- data/lib/hecks/fuzzing.rb +11 -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 +365 -15
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/literal.rb +9 -9
- data/lib/hecks/naming.rb +89 -21
- 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 +160 -58
- 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 +44 -6
- 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 +338 -83
- 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 +29 -6
- 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 +250 -0
- data/lib/hecks/projections/glossary/markdown.rb +105 -0
- data/lib/hecks/projections/glossary/mermaid.rb +110 -0
- data/lib/hecks/projections/glossary/page.css +271 -0
- data/lib/hecks/projections/glossary/page.js +72 -0
- data/lib/hecks/projections/glossary/sections.rb +17 -0
- data/lib/hecks/projections/glossary/sentences.rb +205 -0
- data/lib/hecks/projections/glossary.rb +214 -286
- 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 +15 -22
- 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 +132 -24
- 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 +64 -67
- 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 +94 -19
- 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 +282 -46
- data/lib/hecks/runtime/entity_interpreter.rb +160 -69
- 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 +111 -56
- data/lib/hecks/runtime/reaction_invocation.rb +76 -9
- 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 -112
- 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 +56 -27
- data/lib/hecks/runtime/remote_dispatcher.rb +38 -23
- data/lib/hecks/runtime/routing.rb +10 -88
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +17 -17
- data/lib/hecks/runtime/saga_interpreter.rb +93 -56
- 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 +379 -226
- data/lib/hecks/runtime/value/entity_list_coercion.rb +248 -0
- data/lib/hecks/runtime/value.rb +28 -23
- 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 +207 -5
- 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 +43 -2
data/lib/hecks/fuzzing/replay.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require "fileutils"
|
|
2
2
|
require "tmpdir"
|
|
3
3
|
require_relative "isolated_boot"
|
|
4
|
+
require_relative "self_consistency"
|
|
4
5
|
require_relative "../query_specification/common/comparators"
|
|
5
6
|
require_relative "../query_specification/common/where_clause"
|
|
6
7
|
require_relative "../query_specification/field_path"
|
|
@@ -8,17 +9,17 @@ require_relative "../ports/query/in_memory"
|
|
|
8
9
|
|
|
9
10
|
module Hecks
|
|
10
11
|
module Fuzzing
|
|
11
|
-
# A step list, replayed
|
|
12
|
+
# A step list, replayed in-process against a fresh boot — the same
|
|
12
13
|
# copy-to-tmp-and-reset preamble SequenceGenerator#call uses, and the
|
|
13
14
|
# same observable surface bin/run prints (instances, events,
|
|
14
15
|
# refusals, reactions, sagas, queries), but returned as data rather
|
|
15
16
|
# than JSON on stdout.
|
|
16
17
|
#
|
|
17
|
-
#
|
|
18
|
+
# Not what SequenceGenerator itself dispatches through while
|
|
18
19
|
# generating — that inline execution feeds the picker's own
|
|
19
20
|
# known_ids tracking and stays exactly as it is. This exists for
|
|
20
|
-
# everything
|
|
21
|
-
# me what happened": bin/fuzz recomputing a shrink candidate's
|
|
21
|
+
# everything else that needs "given a step list, boot fresh and tell
|
|
22
|
+
# me what happened": bin/fuzz recomputing a shrink candidate's true
|
|
22
23
|
# event count (removing a step changes what the sequence actually
|
|
23
24
|
# produces, so a shrunk candidate cannot reuse the original claim),
|
|
24
25
|
# and the declared-property checks in properties.rb. Both want it
|
|
@@ -34,32 +35,32 @@ module Hecks
|
|
|
34
35
|
module Replay
|
|
35
36
|
module_function
|
|
36
37
|
|
|
37
|
-
#
|
|
38
|
+
# The ad hoc filter's own comparator roster — read directly from
|
|
38
39
|
# QuerySpecification::Common::COMPARATORS (the same nine names
|
|
39
40
|
# Vocabulary::QueryComparator declares), never re-typed. A
|
|
40
41
|
# declared bluebook query never sees an `op:` outside this set —
|
|
41
|
-
# `admits: "Vocabulary::QueryComparator"` refuses one at
|
|
42
|
+
# `admits: "Vocabulary::QueryComparator"` refuses one at declare
|
|
42
43
|
# time — but a `"filter"`-shaped query step (below) has no
|
|
43
44
|
# declare-time gate at all, so this method gates it here instead.
|
|
44
45
|
#
|
|
45
|
-
#
|
|
46
|
+
# Not rust/src/kernel/query_comparators.rs's own ground truth —
|
|
46
47
|
# that hand-maintained Rust enum is missing `none_in_state` (the
|
|
47
48
|
# 9th comparator, added after the enum was written) and has
|
|
48
49
|
# already drifted; do not treat it as authoritative until item #9
|
|
49
50
|
# of the whole-project table-unification survey closes that gap.
|
|
50
51
|
FILTER_COMPARATORS = Hecks::QuerySpecification::Common::COMPARATORS.map(&:to_s).freeze
|
|
51
52
|
|
|
52
|
-
#
|
|
53
|
+
# The two classes `#enforce_givens`/`#enforce_lifecycle_guard`
|
|
53
54
|
# themselves ever raise — see Admissibility's own doc comment,
|
|
54
|
-
# `command_rules/admissibility.rb`. Any
|
|
55
|
+
# `command_rules/admissibility.rb`. Any other DOMAIN_REFUSAL a step
|
|
55
56
|
# raises (TypeMismatch, EnsuresNotMet, InvariantViolation, ...)
|
|
56
|
-
# proves the guard itself did
|
|
57
|
+
# proves the guard itself did not fire, since it runs first in
|
|
57
58
|
# DISPATCH_ORDER.
|
|
58
59
|
GUARD_REFUSAL_CLASSES = [Runtime::GivenNotMet, Runtime::LifecycleRefused].freeze
|
|
59
60
|
|
|
60
61
|
# One tightly ordered loop over steps, with several "oracle" snapshots
|
|
61
62
|
# (reaction_mark, fan_out_snapshot, guard_check, mutation_trace) that
|
|
62
|
-
# must be taken at very specific points
|
|
63
|
+
# must be taken at very specific points relative to dispatch — see
|
|
63
64
|
# fan_out_snapshot's own comment above for the real, previously-
|
|
64
65
|
# shipped bug this exact before/after ordering fixes. Splitting this
|
|
65
66
|
# into smaller methods would mean threading five-plus oracle-state
|
|
@@ -71,22 +72,43 @@ module Hecks
|
|
|
71
72
|
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
72
73
|
# rubocop:disable-next Metrics/MethodLength
|
|
73
74
|
# rubocop:disable-next Metrics/PerceivedComplexity
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
# `self_consistency:` — off by default, same "existing callers see no
|
|
76
|
+
# change" contract `adapter:` already has. `bin/qa_sweep` is the one
|
|
77
|
+
# real caller that opts in (gated by `QualityControlDials::
|
|
78
|
+
# SELF_CONSISTENCY_CHECKS`/`--self-consistency`): `bin/fuzz`,
|
|
79
|
+
# `Properties.check`'s own callers, and every existing spec keep
|
|
80
|
+
# calling this with no second axis of comparison at all, exactly as
|
|
81
|
+
# before. Computed here, not by a caller reading `runtime` back out
|
|
82
|
+
# afterward — `runtime` and the whole `IsolatedBoot` tmp directory
|
|
83
|
+
# go out of scope the moment this method returns (see this file's
|
|
84
|
+
# own header), so `Hecks::Fuzzing::SelfConsistency.check` has to run
|
|
85
|
+
# while both are still alive, against the exact same repositories
|
|
86
|
+
# this replay's own dispatch loop just wrote to.
|
|
87
|
+
#
|
|
88
|
+
# `database:`/`schema:` — only meaningful, and required, for
|
|
89
|
+
# `adapter: :postgres_era` — see `IsolatedBoot#rebind_to_postgres_era!`'s
|
|
90
|
+
# own header for why that one mode takes caller-owned connection
|
|
91
|
+
# identity rather than a shared default the way `:postgres` does.
|
|
92
|
+
# Forwarded straight through, unchanged, exactly like `adapter:`
|
|
93
|
+
# itself already was.
|
|
94
|
+
def call(domain_path, steps, adapter: :memory, database: nil, schema: nil, self_consistency: false)
|
|
95
|
+
# See isolated_boot.rb's own header: resets data/ and rebinds
|
|
76
96
|
# persistence to the chosen adapter (Memory by default), since a
|
|
77
97
|
# Postgres-bound domain's real store lives outside the copied
|
|
78
98
|
# directory and cannot be reached by resetting data/ alone.
|
|
79
|
-
IsolatedBoot.call(domain_path, adapter: adapter) do |copy|
|
|
99
|
+
IsolatedBoot.call(domain_path, adapter: adapter, database: database, schema: schema) do |copy|
|
|
80
100
|
runtime = Hecks.boot(copy)
|
|
81
101
|
|
|
82
102
|
refusals = []
|
|
83
103
|
queries = []
|
|
84
104
|
dry_runs = []
|
|
105
|
+
dry_run_traces = []
|
|
85
106
|
fan_outs = []
|
|
86
107
|
guard_checks = []
|
|
87
108
|
mutation_traces = []
|
|
109
|
+
outbox_traces = []
|
|
88
110
|
|
|
89
|
-
#
|
|
111
|
+
# Every aggregate a `for_each` could ever query, resolved once —
|
|
90
112
|
# `[domain, aggregate_name]` pairs, gleaned from every loaded
|
|
91
113
|
# bluebook's own fanning-out policies. Empty for every domain
|
|
92
114
|
# with no `for_each` at all (every example this corpus ships
|
|
@@ -104,17 +126,17 @@ module Hecks
|
|
|
104
126
|
args = (step["args"] || {}).transform_keys(&:to_sym)
|
|
105
127
|
|
|
106
128
|
if (question = step["query"])
|
|
107
|
-
#
|
|
129
|
+
# **The ad hoc, single-comparator filter** — a "query" step whose
|
|
108
130
|
# own value is a Hash, not a name: `{aggregate:, field:, op:,
|
|
109
|
-
# value:}`, the
|
|
131
|
+
# value:}`, the same wire shape kernel/cli.rs's new object-
|
|
110
132
|
# form "query" step reads on the Rust side (that file's own
|
|
111
133
|
# header explains why this shape exists at all: it bypasses
|
|
112
134
|
# the bluebook query DSL entirely, so it needs no generated
|
|
113
135
|
# per-domain codegen to prove for real). Answered here by
|
|
114
|
-
# calling `Ports::Query::InMemory`
|
|
136
|
+
# calling `Ports::Query::InMemory` directly — the real
|
|
115
137
|
# production comparator engine, not a second, hand-rewritten
|
|
116
138
|
# copy of it — against the raw repository, never through
|
|
117
|
-
# `runtime.query`, which only ever resolves a
|
|
139
|
+
# `runtime.query`, which only ever resolves a named, declared
|
|
118
140
|
# ask.
|
|
119
141
|
if question.is_a?(Hash)
|
|
120
142
|
begin
|
|
@@ -126,16 +148,16 @@ module Hecks
|
|
|
126
148
|
next
|
|
127
149
|
end
|
|
128
150
|
|
|
129
|
-
#
|
|
130
|
-
#
|
|
151
|
+
# **The query oracle** — two independent engines, each run and
|
|
152
|
+
# caught on its own, never a single shared `begin`/`rescue`
|
|
131
153
|
# wrapping both calls. A shared begin/rescue meant `runtime.
|
|
132
|
-
# query` raising (native refuses) short-circuited
|
|
154
|
+
# query` raising (native refuses) short-circuited before
|
|
133
155
|
# `runtime.reference_query` ever ran at all — the entry
|
|
134
156
|
# recorded only `error:`, with no `reference_rows` and no
|
|
135
157
|
# record of what the reference interpreter would have
|
|
136
158
|
# answered — and `runtime.reference_query` raising instead
|
|
137
159
|
# (reference refuses, native already succeeded) landed in the
|
|
138
|
-
#
|
|
160
|
+
# same rescue, discarding the native `rows` this begin block
|
|
139
161
|
# had already computed and recording the whole ask as an
|
|
140
162
|
# ordinary refusal. Either way, "one engine refused and the
|
|
141
163
|
# other did not" — a real divergence, exactly the shape a
|
|
@@ -180,35 +202,86 @@ module Hecks
|
|
|
180
202
|
|
|
181
203
|
# `{"dry_run": verb, "args": …}` — `Dispatcher#dry_run?`: the command
|
|
182
204
|
# evaluated hypothetically, nothing saved or emitted, no reaction.
|
|
183
|
-
# Recorded, never a refusal: a refused dry run is an
|
|
205
|
+
# Recorded, never a refusal: a refused dry run is an answer.
|
|
206
|
+
#
|
|
207
|
+
# `dry_runs` stays exactly `{verb:, ok:, error?:}` — the same
|
|
208
|
+
# shape it always had, and the same shape `kernel/cli.rs`'s own
|
|
209
|
+
# `dry_run` answers (`{"verb", "ok"}` or `{"verb", "ok": false,
|
|
210
|
+
# "error"}`, that function's own doc comment) — `spec/rust_
|
|
211
|
+
# conformance_spec.rb` compares this array against the compiled
|
|
212
|
+
# binary's own verbatim, so it can never carry a key Rust's own
|
|
213
|
+
# answer does not. The role-gated binding (`as_step_caller`,
|
|
214
|
+
# the same `role:`/`actor_id:` a real dispatch gets below) still
|
|
215
|
+
# applies to the dry-run call itself — only what gets recorded
|
|
216
|
+
# about it is unchanged.
|
|
217
|
+
#
|
|
218
|
+
# `dry_run_traces` — a separate, parallel array (same order,
|
|
219
|
+
# not merged into `dry_runs` above) carrying `before:`/`after:`
|
|
220
|
+
# snapshots of the whole observable store (every instance, the
|
|
221
|
+
# event count) on either side of the hypothetical call, so
|
|
222
|
+
# `Properties.dry_runs_leave_no_trace` can hold `Dispatcher
|
|
223
|
+
# #dry_run?`'s own contract to the store rather than trusting
|
|
224
|
+
# it. Ruby's own oracle data — Rust has nothing to compare it
|
|
225
|
+
# against, so it stays out of the compared surface entirely.
|
|
184
226
|
if (hypothetical = step["dry_run"])
|
|
227
|
+
before = { instances: snapshot_instances(runtime), events: runtime.events.size }
|
|
228
|
+
entry = { verb: hypothetical }
|
|
185
229
|
begin
|
|
186
|
-
runtime.dry_run?(hypothetical, **args)
|
|
187
|
-
|
|
230
|
+
as_step_caller(step) { runtime.dry_run?(hypothetical, **args) }
|
|
231
|
+
entry[:ok] = true
|
|
188
232
|
rescue *Runtime::DOMAIN_REFUSALS, Bluebook::Expression::EvaluationError => e
|
|
189
|
-
|
|
233
|
+
entry.merge!(ok: false, error: e.message)
|
|
190
234
|
end
|
|
235
|
+
after = { instances: snapshot_instances(runtime), events: runtime.events.size }
|
|
236
|
+
dry_runs << entry
|
|
237
|
+
dry_run_traces << entry.merge(before: before, after: after)
|
|
191
238
|
next
|
|
192
239
|
end
|
|
193
240
|
|
|
194
241
|
begin
|
|
195
|
-
#
|
|
196
|
-
# dispatch, so any reaction this
|
|
242
|
+
# **The fan-out oracle's own low-water mark** — taken before
|
|
243
|
+
# dispatch, so any reaction this one step's own announced
|
|
197
244
|
# events produce (`reaction_log` grows in place, the same
|
|
198
245
|
# Array `runtime.reactions` already exposes) can be sliced
|
|
199
|
-
# out after, and matched against an
|
|
246
|
+
# out after, and matched against an independent recomputation
|
|
200
247
|
# of what a `for_each` policy should have fanned out over —
|
|
201
248
|
# the query oracle's own shape (two engines, compared, never
|
|
202
249
|
# one graded against itself), aimed at fan-out instead of a
|
|
203
250
|
# named ask.
|
|
204
251
|
reaction_mark = runtime.reactions.size
|
|
205
252
|
|
|
206
|
-
#
|
|
207
|
-
#
|
|
208
|
-
# `
|
|
209
|
-
#
|
|
210
|
-
#
|
|
211
|
-
#
|
|
253
|
+
# **The outbox oracle's own low-water marks** — taken before
|
|
254
|
+
# dispatch, same idiom as `reaction_mark` right above:
|
|
255
|
+
# `saga_log_mark` slices `runtime.sagas` (a single flat
|
|
256
|
+
# array, safe to index into directly) the identical way
|
|
257
|
+
# `reaction_mark` already slices `runtime.reactions`.
|
|
258
|
+
# `outbox_before_ids` is a set of delivery_ids, not a
|
|
259
|
+
# size — `runtime.outbox.rows` concatenates every bound
|
|
260
|
+
# repository's own array in a fixed per-store order
|
|
261
|
+
# (`Outbox::Relay#rows`, `stores.flat_map`), so a row a
|
|
262
|
+
# different step's dispatch enqueues into an
|
|
263
|
+
# earlier-iterated store would land in the middle of
|
|
264
|
+
# that concatenated list, not at its tail — a plain
|
|
265
|
+
# "grew from N to M, take the tail" slice (the shape
|
|
266
|
+
# `reaction_mark`/`saga_log_mark` both get away with,
|
|
267
|
+
# since `reaction_log`/`saga_log` are each already one
|
|
268
|
+
# flat array irrespective of domain) would silently miss
|
|
269
|
+
# or misattribute rows the moment more than one
|
|
270
|
+
# repository has an outbox. `delivery_id` is unique per
|
|
271
|
+
# store by construction (`Row#to_h`'s own header;
|
|
272
|
+
# `hecks_outbox`'s `UNIQUE` column in the Postgres/Sqlite
|
|
273
|
+
# DDL, `outbox_enqueue`'s own de-dup check in Memory), so
|
|
274
|
+
# membership in this set is exactly "existed before this
|
|
275
|
+
# step's own dispatch ran."
|
|
276
|
+
saga_log_mark = runtime.sagas.size
|
|
277
|
+
outbox_before_ids = runtime.outbox.rows.map(&:delivery_id)
|
|
278
|
+
|
|
279
|
+
# The snapshot a `for_each` query would have seen — taken
|
|
280
|
+
# before this step's own dispatch, not after. The real
|
|
281
|
+
# `deliver_for_each` runs its query synchronously, inside
|
|
282
|
+
# this same dispatch, before this call even returns — so an
|
|
283
|
+
# oracle that re-reads the live repository after `dispatch`
|
|
284
|
+
# answers sees whatever the fan-out's own dispatched
|
|
212
285
|
# commands already mutated (an Account a `Review` leg just
|
|
213
286
|
# moved out of "open," say), not what the query actually
|
|
214
287
|
# matched. A measured bug, not a hypothetical one — this
|
|
@@ -222,59 +295,78 @@ module Hecks
|
|
|
222
295
|
runtime.registry.repository(fdomain, aggregate).all.to_h { |record| [record.id, record.state.dup] }
|
|
223
296
|
end
|
|
224
297
|
|
|
225
|
-
#
|
|
298
|
+
# **The guard oracle's own pre-dispatch read** — same idiom,
|
|
226
299
|
# same placement, same reason as fan_out_snapshot right
|
|
227
300
|
# above: `Admissibility#enforce_givens` (which itself calls
|
|
228
301
|
# `#enforce_lifecycle_guard` when `declaring:` is passed)
|
|
229
|
-
# is called a
|
|
302
|
+
# is called a second time here, independently, against the
|
|
230
303
|
# record exactly as CommandInterpreter#hydrate's own acting
|
|
231
304
|
# branch would find it (`repository.find(id).dup` — the
|
|
232
305
|
# identical three-tier id fallback, Identity.of/.from,
|
|
233
|
-
# reproduced read-only) —
|
|
306
|
+
# reproduced read-only) — before this step's real dispatch
|
|
234
307
|
# can mutate anything a cross-aggregate given dereferences
|
|
235
308
|
# (`customer.status`). A pure predicate read, side-effect
|
|
236
309
|
# free, so calling it twice changes nothing this step
|
|
237
310
|
# itself observes.
|
|
238
311
|
guard_check = build_guard_check(runtime, step["verb"], args)
|
|
239
312
|
|
|
240
|
-
#
|
|
241
|
-
# idiom again: an
|
|
313
|
+
# **The mutation oracle's own pre-dispatch read** — same
|
|
314
|
+
# idiom again: an entity-dispatched command's own
|
|
242
315
|
# `append`/`remove`/`multiply`/`clamp` mutations (S17's
|
|
243
316
|
# fixture, spec/fixtures/entity_list_mutations, now a real
|
|
244
|
-
# bootable domain) act on the entity's
|
|
317
|
+
# bootable domain) act on the entity's own attributes, so
|
|
245
318
|
# the element addressed by this step's own identity args
|
|
246
|
-
# is snapshotted
|
|
319
|
+
# is snapshotted before dispatch, materialized to plain
|
|
247
320
|
# data — `nil` for anything out of scope (an aggregate-
|
|
248
321
|
# level command, an entity command with no mutations at
|
|
249
322
|
# all, or one whose identity args don't resolve).
|
|
250
323
|
mutation_trace = build_mutation_trace(runtime, step["verb"], args)
|
|
251
324
|
|
|
252
|
-
# `role:` —
|
|
253
|
-
# the 231 existing `spec/corpus/*.json` steps (their own
|
|
325
|
+
# `role:`/`actor_id:` — optional per-step keys, absent on every
|
|
326
|
+
# one of the 231 existing `spec/corpus/*.json` steps (their own
|
|
254
327
|
# unwrapped `runtime.dispatch` call, unchanged, so nothing
|
|
255
|
-
# already pinned changes behavior). Binds the
|
|
328
|
+
# already pinned changes behavior). Binds the same ambient
|
|
256
329
|
# caller `refuse_role_mismatch` reads (`Hecks.as_caller`,
|
|
257
330
|
# `Runtime::Caller.as`) for exactly the one dispatch this
|
|
258
331
|
# step makes, then unbinds — mirrors `Caller.as`'s own
|
|
259
332
|
# `ensure`-restore, so back-to-back steps with different (or
|
|
260
|
-
# no) `role:` never leak into each other.
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
end
|
|
333
|
+
# no) `role:` never leak into each other. `actor_id:` is the
|
|
334
|
+
# sibling `kernel/cli.rs` already read (its own comment on
|
|
335
|
+
# the key): with it, a Governance-attached domain runs the
|
|
336
|
+
# real `holds_role?` lookup instead of the string fallback.
|
|
337
|
+
result = as_step_caller(step) { runtime.dispatch_flat(step["verb"], args) }
|
|
266
338
|
|
|
267
339
|
fan_outs.concat(fan_out_findings(runtime, fan_out_snapshot, result.events, runtime.reactions[reaction_mark..]))
|
|
340
|
+
|
|
341
|
+
# **The outbox oracle's own capture** — every outbox row this
|
|
342
|
+
# step's own dispatch newly wrote (across every bound
|
|
343
|
+
# repository, including any a reaction cascade touched —
|
|
344
|
+
# `outbox_before_ids` was taken before `dispatch`, which
|
|
345
|
+
# is the same call that runs the whole cascade
|
|
346
|
+
# synchronously, `reenter` included), paired with the
|
|
347
|
+
# `reaction_log`/`saga_log` rows that same dispatch
|
|
348
|
+
# produced. Skipped entirely when empty — a step whose
|
|
349
|
+
# own aggregate has no outbox enqueues nothing here, and
|
|
350
|
+
# there is nothing for `Properties.outbox_rows_match_
|
|
351
|
+
# reactions` to check for it (its own reactions, if any,
|
|
352
|
+
# went through the direct, pre-outbox path instead).
|
|
353
|
+
outbox_new_rows = runtime.outbox.rows.reject { |row| outbox_before_ids.include?(row.delivery_id) }
|
|
354
|
+
if outbox_new_rows.any?
|
|
355
|
+
outbox_traces << { verb: step["verb"], rows: outbox_new_rows.map(&:to_h),
|
|
356
|
+
reactions: runtime.reactions[reaction_mark..].dup,
|
|
357
|
+
sagas: runtime.sagas[saga_log_mark..].dup }
|
|
358
|
+
end
|
|
359
|
+
|
|
268
360
|
guard_checks << guard_check.merge(actual_refused: false, actual_kind: nil) if guard_check
|
|
269
|
-
#
|
|
361
|
+
# After — only on success ; a refused step mutated nothing,
|
|
270
362
|
# so there is no "after" to compare (and #build_mutation_
|
|
271
363
|
# trace already skipped anything with no mutations to
|
|
272
364
|
# trace in the first place).
|
|
273
365
|
mutation_traces << mutation_trace.merge(after: read_mutation_after(runtime, mutation_trace)) if mutation_trace
|
|
274
366
|
rescue *Runtime::DOMAIN_REFUSALS, Bluebook::Expression::EvaluationError => e
|
|
275
|
-
# `kind:` — the
|
|
367
|
+
# `kind:` — the raised class, not re-derived from the message.
|
|
276
368
|
# `GivenNotMet`/`EnsuresNotMet` share their exact wording
|
|
277
|
-
# ("<command> refused — <description>") with
|
|
369
|
+
# ("<command> refused — <description>") with four other
|
|
278
370
|
# refusal templates (Vocabulary's own LifecycleRefused/
|
|
279
371
|
# TypeMismatch/Unauthorized entries) — a property that told
|
|
280
372
|
# a guard refusal apart by pattern-matching the string alone
|
|
@@ -282,21 +374,21 @@ module Hecks
|
|
|
282
374
|
# one of those. The class is unambiguous where the string
|
|
283
375
|
# is not.
|
|
284
376
|
refusals << { verb: step["verb"], error: e.message, kind: refusal_kind(e) }
|
|
285
|
-
#
|
|
377
|
+
# Only a refusal raised by the guard itself counts here —
|
|
286
378
|
# measured, not assumed: a step whose args were simply
|
|
287
379
|
# incomplete (AbsentArgument, from normalize_args — which
|
|
288
|
-
# runs
|
|
380
|
+
# runs before enforce_givens in DISPATCH_ORDER) never
|
|
289
381
|
# reached the guard at all, and this oracle's own first
|
|
290
382
|
# live run against real generated pizzas data caught
|
|
291
383
|
# exactly that case as a false positive (a malformed-args
|
|
292
384
|
# step the generator deliberately produces, `amount:`
|
|
293
385
|
# dropped entirely) before this comment existed. Whether a
|
|
294
|
-
# refusal from a stage
|
|
386
|
+
# refusal from a stage after enforce_givens (TypeMismatch
|
|
295
387
|
# on a mutation, EnsuresNotMet, InvariantViolation) proves
|
|
296
388
|
# the guard passed can't be told apart from a same-shaped
|
|
297
|
-
# refusal from a stage
|
|
389
|
+
# refusal from a stage before it by class alone (TypeMismatch
|
|
298
390
|
# can come from either), so anything that isn't one of the
|
|
299
|
-
# two guard classes is left
|
|
391
|
+
# two guard classes is left out of guard_checks entirely —
|
|
300
392
|
# inconclusive, not a claimed pass.
|
|
301
393
|
if guard_check && GUARD_REFUSAL_CLASSES.include?(e.class)
|
|
302
394
|
guard_checks << guard_check.merge(actual_refused: true,
|
|
@@ -311,9 +403,9 @@ module Hecks
|
|
|
311
403
|
{ name: event.name, aggregate: event.aggregate, id: event.id, payload: event.payload }
|
|
312
404
|
end
|
|
313
405
|
|
|
314
|
-
#
|
|
406
|
+
# The live process-manager store, materialised to inert data —
|
|
315
407
|
# `{ pm_name => { correlation => { state:, memory: } } }`, the
|
|
316
|
-
#
|
|
408
|
+
# same shape SagaInterpreter#checkpoint hands its persistence
|
|
317
409
|
# adapter (state plus a `Value.materialize`d memory, which is
|
|
318
410
|
# exactly what `deep_copy` there serialises). Captured here
|
|
319
411
|
# because Replay returns the history, not the runtime, and the
|
|
@@ -321,7 +413,7 @@ module Hecks
|
|
|
321
413
|
# property (Properties.sagas_rehydrate_cleanly) reads this rather
|
|
322
414
|
# than reaching into a store the Memory rebind leaves as the
|
|
323
415
|
# no-op NULL_SAGA_STORE. Materialised, not raw, so the history
|
|
324
|
-
# stays plain data
|
|
416
|
+
# stays plain data and the round-trip check sees exactly the
|
|
325
417
|
# bytes a real adapter would have persisted.
|
|
326
418
|
saga_instances = runtime.registry.saga_instances.each_with_object({}) do |(pm_name, conversations), out|
|
|
327
419
|
out[pm_name] = conversations.each_with_object({}) do |(correlation, instance), rows|
|
|
@@ -335,34 +427,52 @@ module Hecks
|
|
|
335
427
|
# Free: no second boot, just the object the first one already
|
|
336
428
|
# built.
|
|
337
429
|
#
|
|
338
|
-
# `bluebook:` (singular) stays the
|
|
430
|
+
# `bluebook:` (singular) stays the first-loaded chapter — every
|
|
339
431
|
# existing property scopes itself to "only what we have the
|
|
340
432
|
# grammar for" against exactly this one, deliberately (see
|
|
341
433
|
# lifecycle_values_are_declared's own comment). `bluebooks:`
|
|
342
|
-
# (plural) is the
|
|
434
|
+
# (plural) is the full map, keyed by domain name — a domain
|
|
343
435
|
# under fuzz commonly composes more than one bluebook (banking
|
|
344
436
|
# alone loads Banking + Governance + Identity), and a refusal
|
|
345
|
-
# or an event can legitimately come from
|
|
437
|
+
# or an event can legitimately come from any of them, not only
|
|
346
438
|
# whichever one happened to load first. A property that needs
|
|
347
|
-
# to resolve a verb back to its
|
|
439
|
+
# to resolve a verb back to its own declaring bluebook — not
|
|
348
440
|
# "the" bluebook — reads this instead.
|
|
349
|
-
{ instances: instances, events: events, refusals: refusals,
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
441
|
+
history = { instances: instances, events: events, refusals: refusals,
|
|
442
|
+
reactions: runtime.reactions, sagas: runtime.sagas, saga_instances: saga_instances,
|
|
443
|
+
queries: queries, dry_runs: dry_runs, dry_run_traces: dry_run_traces,
|
|
444
|
+
fan_outs: fan_outs, guard_checks: guard_checks,
|
|
445
|
+
mutation_traces: mutation_traces, outbox_traces: outbox_traces,
|
|
446
|
+
saga_dispatches: runtime.saga_dispatches, policy_dispatches: runtime.policy_dispatches,
|
|
447
|
+
bluebook: runtime.registry.bluebooks.values.first,
|
|
448
|
+
bluebooks: runtime.registry.bluebooks.dup }
|
|
449
|
+
|
|
450
|
+
# `runtime` is still live here — this is the one and only place
|
|
451
|
+
# it is. See `SelfConsistency`'s own header for why this needs
|
|
452
|
+
# to happen now, against the same registry/repositories this
|
|
453
|
+
# replay's own dispatch loop just populated, not a second boot.
|
|
454
|
+
history[:self_consistency] = SelfConsistency.check(runtime, history) if self_consistency
|
|
455
|
+
|
|
456
|
+
history
|
|
356
457
|
end
|
|
357
458
|
end
|
|
358
459
|
|
|
359
|
-
#
|
|
460
|
+
# A step with no `role:` dispatches exactly as every corpus step
|
|
461
|
+
# always has — bare, no caller bound at all (`Caller.current` nil,
|
|
462
|
+
# so `refuse_role_mismatch` returns before checking anything).
|
|
463
|
+
def as_step_caller(step, &)
|
|
464
|
+
return yield unless step["role"]
|
|
465
|
+
|
|
466
|
+
Hecks.as_caller(role: step["role"], actor_id: step["actor_id"], &)
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
# The guard oracle's own resolution — "which record, if any, is
|
|
360
470
|
# this step about, and would enforce_givens/enforce_lifecycle_guard
|
|
361
|
-
# have refused it against that record's
|
|
471
|
+
# have refused it against that record's pre-dispatch state" —
|
|
362
472
|
# reproduced read-only from already-public pieces
|
|
363
473
|
# (Naming.split_verb, registry.bluebook/.aggregate/.command,
|
|
364
474
|
# Runtime::Identity.of/.from, repository.find), the exact same
|
|
365
|
-
# three-tier fallback CommandInterpreter#hydrate's
|
|
475
|
+
# three-tier fallback CommandInterpreter#hydrate's own acting
|
|
366
476
|
# branch uses, minus its creating/duplicate-checking logic (a
|
|
367
477
|
# creating command has no pre-existing record to snapshot, and
|
|
368
478
|
# every real target this closes — Debit/CloseAccount/Credit/
|
|
@@ -379,21 +489,21 @@ module Hecks
|
|
|
379
489
|
#
|
|
380
490
|
# Never lets a resolution surprise (a malformed verb, a dangling
|
|
381
491
|
# reference) become the step's own real dispatch outcome — this
|
|
382
|
-
# is a
|
|
492
|
+
# is a separate, best-effort read, not part of the step's own
|
|
383
493
|
# control flow.
|
|
384
|
-
#
|
|
494
|
+
# The same shape `call`'s own end-of-replay block used to build
|
|
385
495
|
# inline — every persisted record, keyed the way `query_eligible_rows`/
|
|
386
|
-
# `#eligible_rows` (properties.rb) already expect. Now
|
|
387
|
-
# once
|
|
388
|
-
# end: a query asked at step 1 of a script whose
|
|
496
|
+
# `#eligible_rows` (properties.rb) already expect. Now also called
|
|
497
|
+
# once per query step (see `call`, above), not only once at the very
|
|
498
|
+
# end: a query asked at step 1 of a script whose later steps go on
|
|
389
499
|
# to create more records was being checked, by every property that
|
|
390
|
-
# independently recomputes "the eligible rows," against the
|
|
391
|
-
# snapshot — the records that existed
|
|
500
|
+
# independently recomputes "the eligible rows," against the final
|
|
501
|
+
# snapshot — the records that existed after the whole replay, not
|
|
392
502
|
# the ones that existed when the query actually ran. Found live:
|
|
393
503
|
# `Banking.accounts_by_kind`, asked as literally the first step of a
|
|
394
504
|
# 3-step script, correctly answered against zero accounts (none
|
|
395
505
|
# existed yet) while `group_by_matches_recompute`'s own independent
|
|
396
|
-
# recompute claimed "1 eligible row" — the
|
|
506
|
+
# recompute claimed "1 eligible row" — the one account the script's
|
|
397
507
|
# later two steps went on to create. Each query step now carries
|
|
398
508
|
# its own `instances_at:` snapshot, taken at the moment it ran, so
|
|
399
509
|
# every property that recomputes against "the eligible rows" reads
|
|
@@ -428,7 +538,7 @@ module Hecks
|
|
|
428
538
|
command = aggregate&.command(command_name)
|
|
429
539
|
return nil unless aggregate && command && !command.creates?
|
|
430
540
|
|
|
431
|
-
#
|
|
541
|
+
# Nothing to check, genuinely — not "nothing THIS reproduces yet".
|
|
432
542
|
# A transition-only guard (no per-command `from:`, no `given`,
|
|
433
543
|
# only an aggregate `lifecycle do transition ... end` block
|
|
434
544
|
# naming this command — `Admit`/`Reject`'s own shape) still
|
|
@@ -451,20 +561,20 @@ module Hecks
|
|
|
451
561
|
recomputed_kind = begin
|
|
452
562
|
rules.enforce_givens(record.dup, command, args, domain: domain_name, declaring: aggregate)
|
|
453
563
|
|
|
454
|
-
# A
|
|
455
|
-
# (just above) only ever checks a per-
|
|
564
|
+
# A second, separate DISPATCH_ORDER step — `enforce_givens`
|
|
565
|
+
# (just above) only ever checks a per-command `from:` clause
|
|
456
566
|
# (its own trailing `enforce_lifecycle_guard(declaring, ...)
|
|
457
567
|
# if declaring` call) — the aggregate's own `lifecycle do
|
|
458
|
-
# transition "X" => Y, from: Z end` block is a
|
|
568
|
+
# transition "X" => Y, from: Z end` block is a wholly separate
|
|
459
569
|
# method (`admissible_transition`), called as its own later
|
|
460
570
|
# DISPATCH_ORDER step (`:enforce_givens` then
|
|
461
571
|
# `:admissible_transition` — Vocabulary.symbols
|
|
462
572
|
# ("AggregateDispatchOrder")), not reached from inside
|
|
463
573
|
# `enforce_givens` at all. Missing this call meant a command
|
|
464
|
-
# declared with
|
|
574
|
+
# declared with no per-command `from:` of its own — every real
|
|
465
575
|
# transition-guarded command in this corpus, `Admit`/`Reject`
|
|
466
576
|
# included — always recomputed "admitted" no matter the
|
|
467
|
-
# record's actual state, because the
|
|
577
|
+
# record's actual state, because the one check that would
|
|
468
578
|
# have refused it was never run. Found live: `Expression::
|
|
469
579
|
# Expression.Admit`, fuzzed against `lib/hecks/grammar`
|
|
470
580
|
# (a domain the property's own hand-verification — Banking,
|
|
@@ -484,22 +594,22 @@ module Hecks
|
|
|
484
594
|
nil
|
|
485
595
|
end
|
|
486
596
|
|
|
487
|
-
#
|
|
488
|
-
# purpose, to
|
|
597
|
+
# The mutation oracle's own pre-dispatch read — scoped, on
|
|
598
|
+
# purpose, to entity-dispatched commands only (a dotted
|
|
489
599
|
# command_name): the one place `append`/`remove`/`multiply`/
|
|
490
|
-
# `clamp` are known to act on an entity's
|
|
600
|
+
# `clamp` are known to act on an entity's own attributes
|
|
491
601
|
# (spec/fixtures/entity_list_mutations' own TaggedList — `tags`
|
|
492
602
|
# a value-object list, `count` a VO-typed scalar), never on
|
|
493
|
-
#
|
|
603
|
+
# another nested entity list — so this never needs to reproduce
|
|
494
604
|
# `MutationApplier#entity_element`'s own auto-mint/collision logic
|
|
495
|
-
# (item 1's own fix) at all. An aggregate-level command whose
|
|
496
|
-
# mutation appends an
|
|
497
|
-
# LogVisit`) is a
|
|
605
|
+
# (item 1's own fix) at all. An aggregate-level command whose own
|
|
606
|
+
# mutation appends an entity (`Board.AddList`, `SafeDepositBox.
|
|
607
|
+
# LogVisit`) is a different, already-covered case — item 1's own
|
|
498
608
|
# collision property, not this one.
|
|
499
609
|
#
|
|
500
610
|
# `nil` for anything out of scope: an aggregate-level command, an
|
|
501
611
|
# entity command with no mutations at all, or one whose identity
|
|
502
|
-
# args (parent
|
|
612
|
+
# args (parent or element) don't resolve.
|
|
503
613
|
# Same shape as build_guard_check just above: one early-return chain
|
|
504
614
|
# resolving the entity/element this step's args address (see the
|
|
505
615
|
# comment above), each step depending on the previous one's
|
|
@@ -550,7 +660,7 @@ module Hecks
|
|
|
550
660
|
nil
|
|
551
661
|
end
|
|
552
662
|
|
|
553
|
-
#
|
|
663
|
+
# The same element, re-located, after dispatch — by identity, not
|
|
554
664
|
# position (an append could have changed the array's own length
|
|
555
665
|
# or order relative to it). `nil` if it somehow vanished (not
|
|
556
666
|
# expected for any op this fixture declares — none of them
|
|
@@ -571,10 +681,10 @@ module Hecks
|
|
|
571
681
|
nil
|
|
572
682
|
end
|
|
573
683
|
|
|
574
|
-
#
|
|
684
|
+
# The fan-out oracle — one finding per (event, for_each policy) this
|
|
575
685
|
# step's own announced events could have triggered, independent of
|
|
576
|
-
# `PolicyInterpreter#deliver_for_each`: the
|
|
577
|
-
# every given/ensures already runs through, but the
|
|
686
|
+
# `PolicyInterpreter#deliver_for_each`: the same `where` evaluator
|
|
687
|
+
# every given/ensures already runs through, but the query answered
|
|
578
688
|
# by `Ports::Query::InMemory.holds?` directly against the live
|
|
579
689
|
# repository (`Replay.run_filter`'s own idiom), never by calling
|
|
580
690
|
# `QueryInterpreter` — sharing that call would make this oracle
|
|
@@ -589,7 +699,7 @@ module Hecks
|
|
|
589
699
|
def fan_out_findings(runtime, snapshot, announced, reactions_since)
|
|
590
700
|
announced.each_with_object([]) do |event, findings|
|
|
591
701
|
# `event.aggregate` is domain-qualified ("Banking::Account" —
|
|
592
|
-
# see command_rules/emission.rb's own Event.new) — the
|
|
702
|
+
# see command_rules/emission.rb's own Event.new) — the same
|
|
593
703
|
# source `PolicyInterpreter#policies_for` reads, split the
|
|
594
704
|
# same two ways: `Naming.demodulise` for the emitting
|
|
595
705
|
# aggregate's bare name, plain `split("::")` for the domain.
|
|
@@ -621,8 +731,8 @@ module Hecks
|
|
|
621
731
|
{ policy: policy.name, on: event.name, expected_row_ids: expected, actual_row_ids: actual }
|
|
622
732
|
end
|
|
623
733
|
|
|
624
|
-
#
|
|
625
|
-
# answered against the
|
|
734
|
+
# The independent recomputation — `policy.for_each`'s declared query,
|
|
735
|
+
# answered against the pre-dispatch snapshot (see the snapshot's
|
|
626
736
|
# own comment at its capture site: the real fan-out's query runs
|
|
627
737
|
# synchronously, before its own dispatched commands can mutate
|
|
628
738
|
# anything the query would have matched, so this has to read the
|
|
@@ -650,24 +760,24 @@ module Hecks
|
|
|
650
760
|
matched.keys.map(&:to_s).sort
|
|
651
761
|
end
|
|
652
762
|
|
|
653
|
-
# Answers
|
|
763
|
+
# Answers one ad hoc filter step for real — the mirror image of
|
|
654
764
|
# kernel/cli.rs's own `run_filter`, deliberately calling the exact
|
|
655
|
-
#
|
|
765
|
+
# same production module that method's Rust port stands in for
|
|
656
766
|
# (`Ports::Query::InMemory`, lib/hecks/ports/query/in_memory.rb)
|
|
657
767
|
# rather than re-deriving comparator behavior by hand. `field` walks
|
|
658
768
|
# through `QuerySpecification::FieldPath.dig` (the same reading a
|
|
659
769
|
# declared where-clause gets), `comparable`/`holds?` are the same
|
|
660
770
|
# two calls `InMemory.execute` itself makes per candidate record —
|
|
661
771
|
# this is that method's own filter/select step, inlined, because
|
|
662
|
-
# there is no
|
|
772
|
+
# there is no declared `Query` object here to hand `execute` (an ad
|
|
663
773
|
# hoc filter has no `order_by`/`limit`/`offset` at all, so nothing
|
|
664
774
|
# about `execute`'s own ordering/paging logic even applies).
|
|
665
775
|
# Sorted by id ascending regardless — `Ports::Query::Ordering`'s own
|
|
666
776
|
# header explains why an ask with no declared order still needs
|
|
667
777
|
# this tier ("the identity tier is what makes an ask total").
|
|
668
|
-
#
|
|
778
|
+
# The outcome class a recorded refusal row names (C8.2/C8.3,
|
|
669
779
|
# docs/semantics/bluebook-semantics.md): a domain refusal is its own
|
|
670
|
-
# class; an evaluation
|
|
780
|
+
# class; an evaluation fault — the language refusing to interpret a
|
|
671
781
|
# broken rule or input — is `"Fault"`, the same word the Rust kernel
|
|
672
782
|
# emits (`Refusal::Fault`), never a refusal class and never a raw
|
|
673
783
|
# Ruby exception name.
|
|
@@ -681,7 +791,7 @@ module Hecks
|
|
|
681
791
|
op = filter["op"].to_s
|
|
682
792
|
value = filter["value"]
|
|
683
793
|
|
|
684
|
-
# A malformed ad-hoc ask is a
|
|
794
|
+
# A malformed ad-hoc ask is a fault (C8.3), not a bare RuntimeError.
|
|
685
795
|
unless FILTER_COMPARATORS.include?(op)
|
|
686
796
|
raise Bluebook::Expression::EvaluationError, "unknown query comparator #{op.inspect}"
|
|
687
797
|
end
|
|
@@ -700,9 +810,9 @@ module Hecks
|
|
|
700
810
|
matched.sort_by { |record| record.id.to_s }.map { |record| { id: record.id }.merge(record.state) }
|
|
701
811
|
end
|
|
702
812
|
|
|
703
|
-
# The `refusals` entry's own "verb" column for a
|
|
813
|
+
# The `refusals` entry's own "verb" column for a refused ad hoc
|
|
704
814
|
# filter — there is no real verb to report (a filter step carries
|
|
705
|
-
# none), so this builds the
|
|
815
|
+
# none), so this builds the same descriptive label kernel/cli.rs's
|
|
706
816
|
# own `filter_label` builds from the same three raw fields, tolerant
|
|
707
817
|
# of any of them being missing (Ruby's own nil-to-"" interpolation)
|
|
708
818
|
# the same way that Rust port is.
|