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