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
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Fuzzing
|
|
3
3
|
module Properties
|
|
4
|
-
# A
|
|
4
|
+
# **A dry run leaves no trace** — `Dispatcher#dry_run?`'s whole contract
|
|
5
5
|
# ("the command evaluated hypothetically, nothing saved or emitted,
|
|
6
6
|
# no reaction"), held to the store rather than trusted: `Replay`
|
|
7
7
|
# snapshots every instance and the event count on either side of
|
|
8
|
-
# each `{"dry_run": …}` step, in a
|
|
8
|
+
# each `{"dry_run": …}` step, in a separate `dry_run_traces` array
|
|
9
9
|
# (same order as `history[:dry_runs]`, never merged into it — that
|
|
10
10
|
# array stays the exact `{verb:, ok:, error?:}` shape the compiled
|
|
11
11
|
# Rust binary's own `dry_run` answers, so `spec/rust_conformance_
|
|
@@ -37,7 +37,7 @@ module Hecks
|
|
|
37
37
|
# Dispatch-binding and mutation-recomputation properties: a saga/
|
|
38
38
|
# policy dispatch is bound to the value its own with_spec names, and a
|
|
39
39
|
# command's append/remove/multiply/clamp mutations land on the same
|
|
40
|
-
# after-state an independent recomputation of the
|
|
40
|
+
# after-state an independent recomputation of the same rule produces.
|
|
41
41
|
|
|
42
42
|
# Holds dispatch_binding_fidelity and mutations_match_recompute, plus
|
|
43
43
|
# the independently-written re-derivations (#resolve_dispatch_binding,
|
|
@@ -49,17 +49,17 @@ module Hecks
|
|
|
49
49
|
# are not claimable feature strings at all — see FEATURE_COVERAGE's
|
|
50
50
|
# own comment on this entry). history[:saga_dispatches]/[:policy_dispatches]
|
|
51
51
|
# (Registry#saga_dispatch_log/#policy_dispatch_log — additive,
|
|
52
|
-
# Ruby-only,
|
|
52
|
+
# Ruby-only, never touching saga_log/reaction_log, the byte-for-
|
|
53
53
|
# byte shape spec/rust_conformance_spec.rb holds Rust to) each
|
|
54
|
-
# carry the
|
|
54
|
+
# carry the raw inputs a dispatch's own args were resolved from,
|
|
55
55
|
# captured live at the moment the resolution actually ran — a
|
|
56
56
|
# saga's own memory keeps changing across a run, so re-deriving
|
|
57
|
-
# from history[:saga_instances]'s
|
|
57
|
+
# from history[:saga_instances]'s final memory (the only other
|
|
58
58
|
# place it would be visible) would grade the wrong moment,
|
|
59
59
|
# lifecycle_guard_and_given_violations_are_refused's own false
|
|
60
60
|
# positive one item earlier, in a different shape.
|
|
61
61
|
#
|
|
62
|
-
# #resolve_dispatch_binding/#resolve_trigger_binding are
|
|
62
|
+
# #resolve_dispatch_binding/#resolve_trigger_binding are separate,
|
|
63
63
|
# independently-written re-derivations of SagaInterpreter#
|
|
64
64
|
# dispatch_args/PolicyInterpreter#trigger_args's own resolution —
|
|
65
65
|
# never calling either method again, which would only ever agree
|
|
@@ -99,8 +99,8 @@ module Hecks
|
|
|
99
99
|
|
|
100
100
|
# SagaInterpreter#dispatch_args's own 4-branch resolution,
|
|
101
101
|
# reproduced independently: a literal, the correlation key itself,
|
|
102
|
-
# the
|
|
103
|
-
# the saga's own carried memory (seeded from the
|
|
102
|
+
# the current triggering event's own payload, or — the fallback —
|
|
103
|
+
# the saga's own carried memory (seeded from the starting event's
|
|
104
104
|
# payload, at begin_saga).
|
|
105
105
|
def resolve_dispatch_binding(entry)
|
|
106
106
|
entry[:with_spec].to_h do |key, value|
|
|
@@ -116,7 +116,7 @@ module Hecks
|
|
|
116
116
|
# PolicyInterpreter#trigger_args's own 2-branch resolution — a
|
|
117
117
|
# policy holds no correlation and no memory, so `payload` (the
|
|
118
118
|
# triggering event's own payload, already merged with a fan-out
|
|
119
|
-
# row's id when there is one) is the
|
|
119
|
+
# row's id when there is one) is the whole source.
|
|
120
120
|
def resolve_trigger_binding(entry)
|
|
121
121
|
entry[:with_spec].to_h do |key, value|
|
|
122
122
|
resolved = value.is_a?(Symbol) ? entry[:payload][value] : value
|
|
@@ -137,32 +137,77 @@ module Hecks
|
|
|
137
137
|
#
|
|
138
138
|
# `history[:mutation_traces]` (Replay's own bounded, additive
|
|
139
139
|
# extension — see #build_mutation_trace's own comment) carries a
|
|
140
|
-
# per-step before/after snapshot of the
|
|
140
|
+
# per-step before/after snapshot of the entity element an
|
|
141
141
|
# entity-dispatched command's own mutations acted on, materialized
|
|
142
142
|
# to plain data, plus the step's own raw args — the delta
|
|
143
143
|
# `aggregation_matches_recompute` never had to ask for, because
|
|
144
|
-
# count/median are pure functions of
|
|
144
|
+
# count/median are pure functions of final state and a mutation
|
|
145
145
|
# is not (the same "captured live, not re-derived from final
|
|
146
146
|
# state" lesson item 8's own saga_dispatch_log already learned).
|
|
147
147
|
#
|
|
148
148
|
# #recompute_append/#recompute_remove/#recompute_multiply/
|
|
149
|
-
# #recompute_clamp are
|
|
150
|
-
# reproductions of
|
|
149
|
+
# #recompute_clamp/#recompute_set are separate, independently-
|
|
150
|
+
# written reproductions of EntityElement#appended_to_element/
|
|
151
|
+
# #removed_from_element/#apply_to_element's own `:set` branch and
|
|
151
152
|
# CommandRules::Arithmetic#multiply/#clamp — never calling either
|
|
152
|
-
# again, which would only ever agree with itself.
|
|
153
|
-
#
|
|
154
|
-
#
|
|
155
|
-
#
|
|
156
|
-
#
|
|
153
|
+
# again, which would only ever agree with itself. Every one of
|
|
154
|
+
# these five reproduces the entity-scoped applier specifically
|
|
155
|
+
# (`EntityElement#apply_to_element`, entity_element.rb), never
|
|
156
|
+
# the aggregate-level `MutationApplier#apply` (mutation_applier.rb)
|
|
157
|
+
# — `build_mutation_trace` (replay.rb) only ever captures an
|
|
158
|
+
# entity-owned command's own mutation in the first place (that
|
|
159
|
+
# method's own `command_name&.include?(".")` guard), so there is
|
|
160
|
+
# no aggregate-level trace for this property to ever compare
|
|
161
|
+
# against `owner_for_verb`'s own comment draws the identical
|
|
162
|
+
# aggregate/owner distinction for `:append`. `:increment`/
|
|
163
|
+
# `:decrement` remain out of scope (CommandRules::Arithmetic#
|
|
164
|
+
# arithmetic's own VO-vs-scalar branching and INT64_RANGE overflow
|
|
165
|
+
# checks are a materially larger reproduction than the other five
|
|
166
|
+
# ops combined — scoped out of this pass, not forgotten; see
|
|
167
|
+
# docs/decisions/0056); a command mixing them with a recomputable
|
|
168
|
+
# op still gets the recomputable one checked.
|
|
169
|
+
#
|
|
170
|
+
# `:set` closes a real, separate gap from the other four — this is
|
|
171
|
+
# not "the fifth op of a symmetrical set." `self_consistency.rb`'s
|
|
172
|
+
# own rehydration/idempotency checks (lib/hecks/fuzzing/
|
|
173
|
+
# self_consistency.rb) can never catch a bug in `EntityElement#
|
|
174
|
+
# apply_to_element`'s `:set` branch (entity_element.rb) no matter
|
|
175
|
+
# how much they run: both the "live" state they snapshot and the
|
|
176
|
+
# "rehydrated" state they fold from `Ports::Persistence::
|
|
177
|
+
# AppendOnly`'s own journal trace back to the same single
|
|
178
|
+
# `step_apply_mutations` call (entity_interpreter.rb) — the
|
|
179
|
+
# journal holds the full POST-mutation state, not a delta (that
|
|
180
|
+
# file's own header), so a wrong `:set` result is already baked
|
|
181
|
+
# into both sides of that comparison before either one runs. A
|
|
182
|
+
# Ruby/Rust differential check is subject to the identical
|
|
183
|
+
# structural blind spot whenever Rust's own generated `:set`
|
|
184
|
+
# handling was derived from — and so shares — the same
|
|
185
|
+
# misunderstanding Ruby's implementation has. `#recompute_set`
|
|
186
|
+
# is a genuinely third computation, independent of both: it
|
|
187
|
+
# re-derives the expected value from the mutation's own declared
|
|
188
|
+
# `source` (an argument or a literal — matching `apply_to_
|
|
189
|
+
# element`'s own `resolve_source`, which unlike the aggregate-
|
|
190
|
+
# level `apply` never special-cases a `StateRef` source at all)
|
|
191
|
+
# and the step's own captured `args`, then compares against what
|
|
192
|
+
# the real dispatch actually stored — closing exactly the class
|
|
193
|
+
# of bug the self-consistency and (whenever Rust's codegen shares
|
|
194
|
+
# a ruby-derived misunderstanding) differential checks cannot see.
|
|
195
|
+
# `spec/fuzzing/mutation_set_self_correctness_spec.rb` proves this
|
|
196
|
+
# concretely: a defect planted directly in `EntityElement#apply_
|
|
197
|
+
# to_element`'s `:set` branch leaves `SelfConsistency.check`
|
|
198
|
+
# completely clean while `mutations_match_recompute` names it.
|
|
157
199
|
#
|
|
158
200
|
# `:unrecomputable` (never compared, never a finding) covers the
|
|
159
201
|
# generator's own deliberate arg-malforming (`StepBuilder#malform`)
|
|
160
202
|
# landing a non-Numeric amount/non-2-element bounds where
|
|
161
|
-
# multiply/clamp need one — the
|
|
203
|
+
# multiply/clamp need one — the same shape `guard_check`'s own
|
|
162
204
|
# AbsentArgument false positive taught: a step whose raw material
|
|
163
205
|
# doesn't fit the op's own contract is inconclusive, not a claimed
|
|
164
|
-
# mismatch.
|
|
165
|
-
|
|
206
|
+
# mismatch. `#recompute_set` returns it for the same reason,
|
|
207
|
+
# whenever `Value.for_attribute`'s own coercion door raises on the
|
|
208
|
+
# resolved raw source (malformed fuzzer input, never a real
|
|
209
|
+
# corpus shape).
|
|
210
|
+
RECOMPUTABLE_MUTATION_OPS = %i[append remove multiply clamp set].freeze
|
|
166
211
|
|
|
167
212
|
def mutations_match_recompute(history)
|
|
168
213
|
bluebooks = history.fetch(:bluebooks)
|
|
@@ -196,12 +241,12 @@ module Hecks
|
|
|
196
241
|
|
|
197
242
|
# `command_for_verb`'s own root-aggregate half (Guards) —
|
|
198
243
|
# re-derived independently rather than read off `entry[:domain]`/
|
|
199
|
-
# `entry[:aggregate]` (present on a
|
|
244
|
+
# `entry[:aggregate]` (present on a real `build_mutation_trace`
|
|
200
245
|
# entry, but not on every hand-built fixture this property is
|
|
201
246
|
# tested against) so this works from `entry[:verb]` alone, the
|
|
202
247
|
# one field every entry always carries. BUG#5's fix needs the
|
|
203
|
-
#
|
|
204
|
-
# value-object
|
|
248
|
+
# root aggregate specifically — `Value.for_attribute` resolves a
|
|
249
|
+
# value-object type against the root's own namespace only, the
|
|
205
250
|
# same reason `EntityElement#locate_chain` threads `root_aggregate`
|
|
206
251
|
# through every hop separately from each hop's own `owner`.
|
|
207
252
|
def aggregate_for_verb(bluebooks, verb)
|
|
@@ -211,7 +256,7 @@ module Hecks
|
|
|
211
256
|
bluebooks[domain_name]&.aggregate(aggregate_name)
|
|
212
257
|
end
|
|
213
258
|
|
|
214
|
-
# `mutation.target`'s own
|
|
259
|
+
# `mutation.target`'s own declaring construct — the root
|
|
215
260
|
# aggregate for an aggregate-owned command (`AddSlot`'s own
|
|
216
261
|
# `:slots`), or the entity a dot-shaped command belongs to
|
|
217
262
|
# (`Board.AddCard`'s own `:cards`, declared on `Board`, not on
|
|
@@ -242,50 +287,110 @@ module Hecks
|
|
|
242
287
|
when :remove then recompute_remove(current, mutation.source, args)
|
|
243
288
|
when :multiply then recompute_multiply(current, resolve_mutation_source(mutation.source, args))
|
|
244
289
|
when :clamp then recompute_clamp(current, mutation.source)
|
|
290
|
+
when :set then recompute_set(mutation.source, args, aggregate, owner, mutation.target)
|
|
245
291
|
end
|
|
246
292
|
end
|
|
247
293
|
|
|
294
|
+
# `EntityElement#apply_to_element`'s own `:set` branch (entity_
|
|
295
|
+
# element.rb), reproduced independently — not `MutationApplier#
|
|
296
|
+
# apply`'s aggregate-level twin (mutation_applier.rb), a
|
|
297
|
+
# different method with a different shape: `build_mutation_trace`
|
|
298
|
+
# (replay.rb) only ever captures an entity-owned command's own
|
|
299
|
+
# mutation (`command_name&.include?(".")`, that method's own
|
|
300
|
+
# header), so `mutations_match_recompute` can only ever be
|
|
301
|
+
# checking `apply_to_element`'s branch, never `apply`'s — the
|
|
302
|
+
# same distinction `owner_for_verb`'s own comment already draws
|
|
303
|
+
# for `:append`. Confirmed by reading `apply_to_element` directly:
|
|
304
|
+
# its own `:set` branch resolves the source through `rules.
|
|
305
|
+
# resolve_source` unconditionally (no `StateRef` branch at all —
|
|
306
|
+
# unlike the aggregate-level `apply`, an entity-owned `sets` has
|
|
307
|
+
# no declared corpus site using `state(:x)` today, so this
|
|
308
|
+
# reproduces what ships, not a hypothetical), reads the
|
|
309
|
+
# attribute off `entity.attribute(mutation.target)` (the owning
|
|
310
|
+
# entity, `owner` here — `Board`, never `Workspace`), and coerces
|
|
311
|
+
# through `Value.for_attribute(aggregate, attribute, value)`
|
|
312
|
+
# (the root aggregate, for value-object namespace resolution
|
|
313
|
+
# only — `value_object_for(aggregate, attribute.type)` — the
|
|
314
|
+
# same aggregate/owner split `owner_for_verb`'s own comment
|
|
315
|
+
# explains for `recompute_append`). An earlier version of this
|
|
316
|
+
# method mirrored `MutationApplier#apply`'s `:set` branch instead
|
|
317
|
+
# (StateRef-aware, coerced against `aggregate.attribute` rather
|
|
318
|
+
# than `owner.attribute`) and false-positived on every real
|
|
319
|
+
# entity-owned `sets` in the corpus — `NestedPieces::Workspace.
|
|
320
|
+
# Board.Label` (`sets :label`) has no `:label` attribute on
|
|
321
|
+
# `Workspace` at all, so `Value.for(aggregate, :label, raw)`
|
|
322
|
+
# silently passed the raw, uncoerced String through instead of
|
|
323
|
+
# wrapping it as `BoardLabel`, and the comparison below then
|
|
324
|
+
# disagreed with the real, correctly-coerced `after` state on
|
|
325
|
+
# every single run — caught immediately by running this against
|
|
326
|
+
# `qa/stress_domains/nested_pieces` before this comment existed,
|
|
327
|
+
# not by inspection alone.
|
|
328
|
+
#
|
|
329
|
+
# Reusing `Value.for_attribute` for the coercion step, rather
|
|
330
|
+
# than re-deriving it, is the same "coercion is its own already-
|
|
331
|
+
# guaranteed door" reasoning `GUARANTEED_BY_CONSTRUCTION` states
|
|
332
|
+
# and `#coerce_recompute_append_arg` already leans on for BUG#5 —
|
|
333
|
+
# what this property exists to check is the source resolution and
|
|
334
|
+
# routing (did the right raw value, from the right source, land
|
|
335
|
+
# on the right target?), not whether `Value.for_attribute` itself
|
|
336
|
+
# coerces correctly (a separate, already-enforced concern).
|
|
337
|
+
# Rescued broadly: a `:set` mutation trace is only ever captured
|
|
338
|
+
# after a real, already-admitted dispatch (`Replay#build_
|
|
339
|
+
# mutation_trace`'s own header), so a raise here means this
|
|
340
|
+
# recomputation resolved the wrong raw material, not that the
|
|
341
|
+
# real dispatch was itself malformed — `:unrecomputable`, not a
|
|
342
|
+
# crash, the same discipline every other branch in this method
|
|
343
|
+
# already follows for the generator's own deliberate malforming.
|
|
344
|
+
def recompute_set(source, args, aggregate, owner, target)
|
|
345
|
+
raw = resolve_mutation_source(source, args)
|
|
346
|
+
attribute = owner&.attribute(target)
|
|
347
|
+
coerced = attribute ? Runtime::Value.for_attribute(aggregate, attribute, raw) : raw
|
|
348
|
+
Runtime::Value.materialize(coerced)
|
|
349
|
+
rescue StandardError
|
|
350
|
+
:unrecomputable
|
|
351
|
+
end
|
|
352
|
+
|
|
248
353
|
# `EntityElement#appended_to_element`'s own field-mapping half,
|
|
249
|
-
# reproduced: the field map resolved the
|
|
354
|
+
# reproduced: the field map resolved the same two-tier way
|
|
250
355
|
# (`MutationApplier#resolve_append_source` — a caller-supplied
|
|
251
356
|
# arg, or the entity's own current field), then appended. (An
|
|
252
|
-
# entity-dispatched command's own mutations
|
|
357
|
+
# entity-dispatched command's own mutations do reach here —
|
|
253
358
|
# `#build_mutation_trace`'s own comment describing them as never
|
|
254
359
|
# reaching "the entity_element branch" means `MutationApplier#
|
|
255
|
-
# appended`'s own
|
|
360
|
+
# appended`'s own aggregate-level entity_element fallback
|
|
256
361
|
# specifically, which really is unreached from here; entity-owned
|
|
257
362
|
# append dispatches go through `EntityElement#appended_to_element`
|
|
258
|
-
# instead, and
|
|
363
|
+
# instead, and do reach this method.)
|
|
259
364
|
#
|
|
260
365
|
# BUG#5 — an entity-owned `:append` whose target field is itself
|
|
261
366
|
# value-object-typed (`Board.AddCard`'s own `sets :cards, append:
|
|
262
367
|
# { sequence: :sequence }`, `CardSequence`-typed). A caller-
|
|
263
|
-
# supplied arg reaches the
|
|
368
|
+
# supplied arg reaches the real applier (`EntityElement#
|
|
264
369
|
# appended_to_element`) already coerced: `Interpreting#
|
|
265
370
|
# coerce_declared_arguments` runs `Value.for_attribute` over
|
|
266
|
-
#
|
|
371
|
+
# every arg the acting command itself declares, before dispatch
|
|
267
372
|
# ever reaches a mutation applier at all — independent of, and
|
|
268
373
|
# earlier than, anything `appended_to_element`'s own value_object
|
|
269
|
-
# check does. `before_scope[source]` (the entity's
|
|
374
|
+
# check does. `before_scope[source]` (the entity's own current
|
|
270
375
|
# field) needs no such re-coercion here: it's already the
|
|
271
|
-
#
|
|
376
|
+
# materialized shape `build_mutation_trace` snapshotted it in
|
|
272
377
|
# (`Value.materialize`, same as `entry[:after]`), not a raw value
|
|
273
378
|
# sitting behind a live `Value`.
|
|
274
379
|
#
|
|
275
|
-
# BUG#12 — `owner`/`target` (new here) let this
|
|
380
|
+
# BUG#12 — `owner`/`target` (new here) let this also reproduce
|
|
276
381
|
# `EntityElement#fill_declared_defaults`'s own entity-nested-in-
|
|
277
382
|
# entity fallback (`appended_to_element`'s `else` branch, when
|
|
278
383
|
# the appended element is itself an entity — `Card`, nested
|
|
279
384
|
# inside `Board` — not a value object): `owner.attribute(target)
|
|
280
385
|
# &.type` names the appended element's own type; when that names
|
|
281
386
|
# an entity of `aggregate` rather than a value object, every one
|
|
282
|
-
# of
|
|
387
|
+
# of its own declared attributes `fields` doesn't already hold
|
|
283
388
|
# gets `Instance.default_for`'s own default — reused, not
|
|
284
389
|
# reimplemented, for the identical "never agree with itself"
|
|
285
390
|
# reason BUG#5's own coercion re-derivation above already gives:
|
|
286
391
|
# `Instance.default_for` is pre-existing, independently-tested
|
|
287
392
|
# machinery (an ordinary aggregate's own creation already runs
|
|
288
|
-
# through it via `Instance.defaults`), not the
|
|
393
|
+
# through it via `Instance.defaults`), not the new glue
|
|
289
394
|
# (`fill_declared_defaults` itself) this property exists to
|
|
290
395
|
# catch a drift in.
|
|
291
396
|
def recompute_append(current, source_map, before_scope, args, aggregate, command, owner = aggregate, target = nil)
|
|
@@ -305,8 +410,8 @@ module Hecks
|
|
|
305
410
|
# isn't an entity nested directly under `owner` (a value object,
|
|
306
411
|
# or nothing declared at all — `owner.attribute` answering `nil`
|
|
307
412
|
# for a target the DSL itself would already have refused at
|
|
308
|
-
# build time). `owner.entities`,
|
|
309
|
-
# piece nested inside a piece is a child of the
|
|
413
|
+
# build time). `owner.entities`, not `aggregate.entities` — a
|
|
414
|
+
# piece nested inside a piece is a child of the owning entity
|
|
310
415
|
# (`Card` is `Board.entities`, never `Workspace.entities`), the
|
|
311
416
|
# same distinction `EntityElement#appended_to_element`'s own fix
|
|
312
417
|
# draws.
|
|
@@ -335,8 +440,8 @@ module Hecks
|
|
|
335
440
|
# `Interpreting#coerce_declared_arguments`'s own coercion,
|
|
336
441
|
# reproduced independently (never calling it again, the same
|
|
337
442
|
# "never agree with itself" rule this whole module's header
|
|
338
|
-
# comment gives) — a raw arg is coerced
|
|
339
|
-
# (`source`) is one of the
|
|
443
|
+
# comment gives) — a raw arg is coerced only when its own name
|
|
444
|
+
# (`source`) is one of the acting command's own declared
|
|
340
445
|
# attributes, exactly the condition that method checks before a
|
|
341
446
|
# real dispatch ever coerces it either. `command.attribute(source)`
|
|
342
447
|
# answering `nil` (a source that names no declared attribute —
|
|
@@ -367,7 +472,7 @@ module Hecks
|
|
|
367
472
|
# reproduced on plain materialized data instead of a real Value:
|
|
368
473
|
# a single-numeric-field Hash (the VO-typed case — ListCount, one
|
|
369
474
|
# Integer field) scales that field ; a bare Numeric scales itself.
|
|
370
|
-
# `current ||= 0` — the
|
|
475
|
+
# `current ||= 0` — the same phantom-field fallback #multiply
|
|
371
476
|
# itself already gives (unaffected by this session's #clamp fix,
|
|
372
477
|
# since #multiply never needed one).
|
|
373
478
|
def recompute_multiply(current, amount)
|
|
@@ -387,8 +492,8 @@ module Hecks
|
|
|
387
492
|
end
|
|
388
493
|
|
|
389
494
|
# `CommandRules::Arithmetic#clamp`'s own two branches, reproduced
|
|
390
|
-
# the same way #recompute_multiply is — including
|
|
391
|
-
#
|
|
495
|
+
# the same way #recompute_multiply is — including this session's
|
|
496
|
+
# own `current ||= 0` fix (command_rules/arithmetic.rb), the one
|
|
392
497
|
# arithmetic op that didn't have it until now. `mutation.source`
|
|
393
498
|
# is always a literal `[min, max]`, never an argument reference
|
|
394
499
|
# (MutationApplier's own comment on why `resolve_source` is
|
|
@@ -411,18 +516,18 @@ module Hecks
|
|
|
411
516
|
end
|
|
412
517
|
|
|
413
518
|
# `CommandRules::Arithmetic#resolve_source`, reproduced: a
|
|
414
|
-
# mutation's source is either the
|
|
415
|
-
#
|
|
519
|
+
# mutation's source is either the name of an argument or a
|
|
520
|
+
# literal, told apart by type.
|
|
416
521
|
def resolve_mutation_source(source, args)
|
|
417
522
|
source.is_a?(Symbol) ? args[source] : source
|
|
418
523
|
end
|
|
419
524
|
|
|
420
|
-
# A generated step's own `args` arrive with
|
|
525
|
+
# A generated step's own `args` arrive with string keys on every
|
|
421
526
|
# nested Hash (the wire/JSON shape `spec/corpus/*.json` already
|
|
422
527
|
# uses) while `history[:mutation_traces]`' own materialized
|
|
423
|
-
# before/after state carries
|
|
528
|
+
# before/after state carries symbol keys throughout (Runtime::
|
|
424
529
|
# Value.materialize's own convention) — two hashes holding the
|
|
425
|
-
# identical fact compare
|
|
530
|
+
# identical fact compare unequal by Ruby's own `Hash#==` unless
|
|
426
531
|
# both sides are normalized the same way first. Recursive, since
|
|
427
532
|
# an appended/removed element can itself nest a value object
|
|
428
533
|
# (RemoveTag's own `Tag` argument, `{"key"=>..., "value"=>...}`).
|
|
@@ -12,18 +12,18 @@ module Hecks
|
|
|
12
12
|
# verb-to-declaration lookups (#command_for_verb,
|
|
13
13
|
# #effective_guard_descriptions) each resolves a refusal against.
|
|
14
14
|
module Guards
|
|
15
|
-
# `Query#options`'
|
|
15
|
+
# `Query#options`' other half — TenantScope.apply's own contract
|
|
16
16
|
# (tenant_scope.rb), independently restated as a property rather
|
|
17
17
|
# than exercised only through whatever the generator happens to
|
|
18
|
-
# try.
|
|
18
|
+
# try. Not closed by the generator here on purpose: SafeDepositBox.
|
|
19
19
|
# Rented — the only real corpus query declaring `authorize` at
|
|
20
|
-
# all — declares
|
|
20
|
+
# all — declares zero attributes of its own, so StepBuilder#args_for
|
|
21
21
|
# always hands it `{}` and TenantScope.apply refuses every
|
|
22
22
|
# generated attempt, unconditionally (confirmed: no successful ask
|
|
23
23
|
# against an authorize-bearing query reaches this property via the
|
|
24
24
|
# standard battery today). Extending the generator to invent a
|
|
25
25
|
# `tenant:` value ran into a separate, real finding along the way —
|
|
26
|
-
# SafeDepositBox is
|
|
26
|
+
# SafeDepositBox is composite-identified (`identified_by` is nil
|
|
27
27
|
# for it — Runtime::Identified#derive_identity), so the generator's
|
|
28
28
|
# existing `known_ids` pool (keyed by `aggregate.identified_by ||
|
|
29
29
|
# "id"`) tracks a stray, never-real scalar for it rather than its
|
|
@@ -37,10 +37,10 @@ module Hecks
|
|
|
37
37
|
# where a `tenant:` arg came from.
|
|
38
38
|
#
|
|
39
39
|
# Two claims, matching TenantScope.apply's own two branches: every
|
|
40
|
-
#
|
|
40
|
+
# successful answer's own tenant field agrees with the tenant arg
|
|
41
41
|
# given (the WhereClause TenantScope injects is a Symbol reference
|
|
42
|
-
# into args, resolved dynamically — this checks the
|
|
43
|
-
# re-deriving that resolution) ; every ask
|
|
42
|
+
# into args, resolved dynamically — this checks the outcome, not
|
|
43
|
+
# re-deriving that resolution) ; every ask missing a required
|
|
44
44
|
# tenant: refuses with the declared wording, never succeeds. A
|
|
45
45
|
# refusal for an unrelated reason with the tenant arg present is
|
|
46
46
|
# not this property's claim either way — skipped, not graded.
|
|
@@ -92,14 +92,14 @@ module Hecks
|
|
|
92
92
|
offenders.empty? || offenders.join("; ")
|
|
93
93
|
end
|
|
94
94
|
|
|
95
|
-
#
|
|
96
|
-
#
|
|
95
|
+
# Every given/ensures refusal a run actually raised names a rule
|
|
96
|
+
# the command actually declares. `GivenNotMet`/`EnsuresNotMet` both
|
|
97
97
|
# quote their guard's own `description` verbatim
|
|
98
98
|
# (command_rules/admissibility.rb: `"#{command.hecks_name} refused
|
|
99
|
-
# — #{given.description}"`) — the
|
|
99
|
+
# — #{given.description}"`) — the same text `behavior.bluebook`'s
|
|
100
100
|
# own `Rule`/Command.Ensure hold as `Rule#description`, so a
|
|
101
101
|
# refusal whose quoted text is not among the refusing command's
|
|
102
|
-
#
|
|
102
|
+
# own `guard_descriptions` (Behaviour::Command, both givens and
|
|
103
103
|
# ensures) is either a stale message surviving a renamed rule, a
|
|
104
104
|
# rule firing against the wrong command's own guard set, or the
|
|
105
105
|
# wording drifting out from under the declaration it is supposed
|
|
@@ -107,7 +107,7 @@ module Hecks
|
|
|
107
107
|
# guards, some through a cross-aggregate dereference) are exactly
|
|
108
108
|
# the surface this exists to hold to its word.
|
|
109
109
|
#
|
|
110
|
-
# `kind:` is what tells a guard refusal apart from the
|
|
110
|
+
# `kind:` is what tells a guard refusal apart from the four other
|
|
111
111
|
# `RefusalWording` templates sharing the identical "X refused — Y"
|
|
112
112
|
# shape (LifecycleRefused/transition_blocked, both TypeMismatch
|
|
113
113
|
# object-reference templates, Unauthorized/role_mismatch) — see
|
|
@@ -138,27 +138,27 @@ module Hecks
|
|
|
138
138
|
offenders.empty? || offenders.join("; ")
|
|
139
139
|
end
|
|
140
140
|
|
|
141
|
-
#
|
|
141
|
+
# Angle-8's own write-side half. `authorize_scopes_or_refuses`
|
|
142
142
|
# (above) enforces `TenantScope.apply`'s boundary, and that
|
|
143
|
-
# boundary exists
|
|
143
|
+
# boundary exists only for queries/read models — `authorize
|
|
144
144
|
# policy, tenant: field` is a word `QuerySpecification::Common::
|
|
145
145
|
# DSL#authorize_impl` grants to `QueryBuilder`/`ReadModelBuilder`
|
|
146
146
|
# alone; `CommandBuilder` never includes that module, so no
|
|
147
147
|
# bluebook can declare it on a command at all (confirmed by
|
|
148
|
-
# reading the grammar directly, not inferred). A
|
|
148
|
+
# reading the grammar directly, not inferred). A write that
|
|
149
149
|
# carries a `reference_to` from one tenant-scoped record into
|
|
150
|
-
# another's is checked by
|
|
150
|
+
# another's is checked by nothing at dispatch time: `TenantScope`
|
|
151
151
|
# never runs for a command, and no runtime `given`/`ensures`
|
|
152
152
|
# anywhere in this corpus reads a cross-aggregate tenant field
|
|
153
153
|
# either. `qa/stress_domains/tenant_ledger` exists to give this
|
|
154
154
|
# property a real place to fire.
|
|
155
155
|
#
|
|
156
|
-
#
|
|
157
|
-
# field one of
|
|
156
|
+
# The rule: an aggregate's own declared tenant field is whichever
|
|
157
|
+
# field one of its own queries names in `authorize policy, tenant:
|
|
158
158
|
# :field` — the exact same declaration `authorize_scopes_or_
|
|
159
159
|
# refuses` reads off a query above, reused here to name a field
|
|
160
|
-
# on the
|
|
161
|
-
# For every
|
|
160
|
+
# on the aggregate itself that stores the tenant it belongs to.
|
|
161
|
+
# For every stored record (`history[:instances]` — a refused
|
|
162
162
|
# dispatch never writes one, so "a refusal is correct behaviour,
|
|
163
163
|
# not a finding" holds by construction, the same way `history
|
|
164
164
|
# [:instances]` already guarantees this for `stored_records_
|
|
@@ -166,25 +166,25 @@ module Hecks
|
|
|
166
166
|
# tenant field, walk every `reference_to`-typed attribute it
|
|
167
167
|
# carries (`Bluebook::Reference` — "a reference IS the id",
|
|
168
168
|
# value/coercion.rb's own header, so the stored value is always a
|
|
169
|
-
# plain id, never a nested payload) pointing at
|
|
170
|
-
# that
|
|
169
|
+
# plain id, never a nested payload) pointing at another aggregate
|
|
170
|
+
# that also declares a tenant field: if the referenced record's
|
|
171
171
|
# own tenant value disagrees with the referencing record's own
|
|
172
172
|
# tenant value, the write crossed a tenant boundary and nothing
|
|
173
173
|
# refused it — a finding.
|
|
174
174
|
#
|
|
175
|
-
# A
|
|
175
|
+
# A dangling/unresolvable reference is skipped — a different,
|
|
176
176
|
# existence-shaped property's claim, not this one's (the same
|
|
177
177
|
# "inconclusive, not a claimed pass" restraint `lifecycle_guard_
|
|
178
178
|
# and_given_violations_are_refused` already documents for a
|
|
179
179
|
# differently-shaped case). Comparison goes through `Ports::
|
|
180
|
-
# Query::InMemory.comparable` (the
|
|
180
|
+
# Query::InMemory.comparable` (the same normalization `authorize_
|
|
181
181
|
# scopes_or_refuses` already applies to a query row's own tenant
|
|
182
182
|
# field, just above) rather than `Runtime::Value#==` directly —
|
|
183
|
-
# two single-attribute value objects with the
|
|
184
|
-
#
|
|
183
|
+
# two single-attribute value objects with the same scalar but
|
|
184
|
+
# different declared names (`LedgerRegion`/`TransferRegion`, this
|
|
185
185
|
# domain's own pair — a value object is always declared inside
|
|
186
186
|
# the aggregate that owns it, so two independently tenant-scoped
|
|
187
|
-
# aggregates can never share one) compare
|
|
187
|
+
# aggregates can never share one) compare unequal under `Value#==`
|
|
188
188
|
# (`type_name` is part of that equality) despite meaning the
|
|
189
189
|
# identical tenant, which would make every same-tenant write a
|
|
190
190
|
# false positive.
|
|
@@ -230,7 +230,7 @@ module Hecks
|
|
|
230
230
|
offenders.empty? || offenders.join("; ")
|
|
231
231
|
end
|
|
232
232
|
|
|
233
|
-
#
|
|
233
|
+
# The field an aggregate's own query names as tenant-scoping —
|
|
234
234
|
# shared by `commands_respect_tenant_scope` above for both sides
|
|
235
235
|
# of a `reference_to`. `nil` for an aggregate with no `authorize
|
|
236
236
|
# ..., tenant:` on any of its own queries — not every aggregate
|
|
@@ -241,16 +241,16 @@ module Hecks
|
|
|
241
241
|
authorization&.tenant&.to_sym
|
|
242
242
|
end
|
|
243
243
|
|
|
244
|
-
# A
|
|
244
|
+
# A declared process manager's own command — `command.hecks_name`,
|
|
245
245
|
# or an entity's own if the verb's second component is itself
|
|
246
246
|
# dotted (`Aggregate.Entity.Command`, the same two shapes
|
|
247
247
|
# `Dispatcher#dispatch` itself branches on). Shared by the guard
|
|
248
248
|
# property above and available for anything else that needs to go
|
|
249
249
|
# from a replayed verb back to its declaration.
|
|
250
250
|
#
|
|
251
|
-
#
|
|
251
|
+
# Resolved against `bluebooks` (the full map, `history[:bluebooks]`
|
|
252
252
|
# — every loaded domain, keyed by name), never a single assumed
|
|
253
|
-
# bluebook: a verb names its
|
|
253
|
+
# bluebook: a verb names its own domain (`Naming.split_verb`'s
|
|
254
254
|
# first element), and that domain is not always the one Replay
|
|
255
255
|
# happens to expose as `history[:bluebook]`. A fuzz run against
|
|
256
256
|
# `lib/hecks/grammar` (Expression + Translation, in load
|
|
@@ -258,14 +258,14 @@ module Hecks
|
|
|
258
258
|
# refusal read as "no declared command resolves that verb" purely
|
|
259
259
|
# because `history[:bluebook]` was Expression, not Translation; the
|
|
260
260
|
# refusal was real, this property's own domain resolution was not.
|
|
261
|
-
# A
|
|
261
|
+
# A delegating door refuses with its target's own words. `delegates_to`
|
|
262
262
|
# (CommandBuilder#delegates_to_impl) hands the whole dispatch to one
|
|
263
263
|
# entity command, and that command's given is what refuses — raised
|
|
264
264
|
# back through the door, in the door's name (chess: `Game.MoveKnight
|
|
265
265
|
# refused — "it is that color's turn"`, a given Knight.Move declares
|
|
266
266
|
# and MoveKnight, a pure passthrough, never could). Read the door's
|
|
267
267
|
# own guards first, then every delegation target's; an offence is
|
|
268
|
-
# only a description
|
|
268
|
+
# only a description neither declares. Found live mining chess's
|
|
269
269
|
# history: every refused move through a door read as undeclared.
|
|
270
270
|
def effective_guard_descriptions(bluebooks, verb, command)
|
|
271
271
|
own = command.guard_descriptions
|
|
@@ -296,9 +296,9 @@ module Hecks
|
|
|
296
296
|
end
|
|
297
297
|
end
|
|
298
298
|
|
|
299
|
-
# `guard_refusals_are_declared`'s
|
|
299
|
+
# `guard_refusals_are_declared`'s own opposite direction. That
|
|
300
300
|
# property is passive and one-directional — for a refusal that
|
|
301
|
-
#
|
|
301
|
+
# already happened, is the quoted text real declared text? It says
|
|
302
302
|
# nothing about a guard that should have refused and silently did
|
|
303
303
|
# not — a call site that stopped calling enforce_givens/enforce_
|
|
304
304
|
# lifecycle_guard would never appear in history[:refusals] at all,
|
|
@@ -306,7 +306,7 @@ module Hecks
|
|
|
306
306
|
#
|
|
307
307
|
# This one calls Admissibility#enforce_givens (which itself folds
|
|
308
308
|
# in #enforce_lifecycle_guard whenever `declaring:` is passed)
|
|
309
|
-
#
|
|
309
|
+
# directly, against Replay's own pre-dispatch snapshot
|
|
310
310
|
# (history[:guard_checks], one bounded, additive extension — see
|
|
311
311
|
# that file's own comment at the capture site) — an independent
|
|
312
312
|
# recomputation, not grading production against itself, the same
|
|
@@ -316,27 +316,27 @@ module Hecks
|
|
|
316
316
|
# could mutate anything a cross-aggregate given dereferences) is
|
|
317
317
|
# compared against `actual_refused` (GivenNotMet/LifecycleRefused
|
|
318
318
|
# specifically — Replay's own comment on GUARD_REFUSAL_CLASSES
|
|
319
|
-
# explains why
|
|
319
|
+
# explains why any other refusal class, or an outright success,
|
|
320
320
|
# both count as "the guard did not fire," since enforce_givens
|
|
321
|
-
# runs
|
|
321
|
+
# runs first in DISPATCH_ORDER).
|
|
322
322
|
#
|
|
323
323
|
# Aggregate#preconditions closes for free alongside this — a
|
|
324
324
|
# no-block `given` reference (CommandBuilder#given) pushes the
|
|
325
|
-
#
|
|
325
|
+
# same Given struct object `enforce_givens` already iterates
|
|
326
326
|
# command.givens for, so there is no separate runtime path a
|
|
327
327
|
# property could exercise beyond what this already reaches.
|
|
328
328
|
# Entity#preconditions closes the identical way, one level down
|
|
329
329
|
# (ADR 0028) — a piece's own bare `given` reference pushes the
|
|
330
|
-
#
|
|
330
|
+
# same Given struct onto its own referencing command's givens,
|
|
331
331
|
# so LedgerEntry's own Amend/Reverse (banking) already exercise
|
|
332
332
|
# this through the exact mechanism above, no separate path.
|
|
333
333
|
#
|
|
334
334
|
# Real targets: Account.Debit/CloseAccount (`from:` guards),
|
|
335
335
|
# Credit/Debit (the named-once `given("customer is active")`
|
|
336
336
|
# precondition) — FreezeAccount deliberately references the
|
|
337
|
-
#
|
|
337
|
+
# different named precondition `"customer is not closed"` instead
|
|
338
338
|
# (a suspended customer must still be freezable), so it is not a
|
|
339
|
-
# `"customer is active"` example, just the same
|
|
339
|
+
# `"customer is active"` example, just the same mechanism.
|
|
340
340
|
def lifecycle_guard_and_given_violations_are_refused(history)
|
|
341
341
|
offenders = history.fetch(:guard_checks).filter_map do |check|
|
|
342
342
|
next if check[:recomputed_refused] == check[:actual_refused]
|