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
|
@@ -8,10 +8,10 @@ require_relative "instance"
|
|
|
8
8
|
|
|
9
9
|
module Hecks
|
|
10
10
|
module Runtime
|
|
11
|
-
#
|
|
11
|
+
# **One entity element, located and mutated** — the walk-and-write half of
|
|
12
12
|
# dispatching into a piece an aggregate holds, factored out of
|
|
13
|
-
# `EntityInterpreter` so a
|
|
14
|
-
# `delegate_to_entity` step) can locate and mutate the
|
|
13
|
+
# `EntityInterpreter` so a second caller (`CommandInterpreter`'s own
|
|
14
|
+
# `delegate_to_entity` step) can locate and mutate the same element the
|
|
15
15
|
# same way, against an aggregate record it already holds in memory
|
|
16
16
|
# rather than one freshly loaded from a repository. `EntityInterpreter`
|
|
17
17
|
# keeps its own dispatch order and Context; this is the part underneath
|
|
@@ -27,19 +27,19 @@ module Hecks
|
|
|
27
27
|
|
|
28
28
|
# BUG#3 — an addressing value that can never match a stored element,
|
|
29
29
|
# returned from `element_of`'s own `wants` coercion in place of a real
|
|
30
|
-
# `Value` (below). A unique object, never `nil`: a stored,
|
|
31
|
-
# element field really can hold `nil`, and comparing
|
|
30
|
+
# `Value` (below). A unique object, never `nil`: a stored, optional
|
|
31
|
+
# element field really can hold `nil`, and comparing that against a
|
|
32
32
|
# bare `nil` sentinel would accidentally "match" it.
|
|
33
33
|
UNMATCHABLE = Object.new.freeze
|
|
34
34
|
private_constant :UNMATCHABLE
|
|
35
35
|
|
|
36
|
-
#
|
|
36
|
+
# **One hop per chain entry**. `container` starts as `instance` (the root
|
|
37
37
|
# aggregate record) and becomes each just-located element in turn —
|
|
38
|
-
# Dispatch's own element is found
|
|
38
|
+
# Dispatch's own element is found inside the Handler element
|
|
39
39
|
# `locate_chain` located the step before, never inside `instance`
|
|
40
|
-
# directly. `owner` is whichever construct's
|
|
40
|
+
# directly. `owner` is whichever construct's own attribute declares
|
|
41
41
|
# the list being searched (Handler declares `dispatches` ; the root
|
|
42
|
-
# aggregate declares `handlers`) — `root_aggregate` stays the
|
|
42
|
+
# aggregate declares `handlers`) — `root_aggregate` stays the root
|
|
43
43
|
# the whole way through instead, passed to `element_of` separately,
|
|
44
44
|
# because coercion (`Value.for_attribute`) resolves value objects
|
|
45
45
|
# against the root's own namespace only ; an entity must never
|
|
@@ -63,7 +63,7 @@ module Hecks
|
|
|
63
63
|
container
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
-
#
|
|
66
|
+
# One element, matched on every part of its identity — not just the first.
|
|
67
67
|
# A piece's identity may be several paths, the same shape a head's can be,
|
|
68
68
|
# so a dispatch that names the element has to supply every part and every
|
|
69
69
|
# part has to agree with the stored one. `routed_identity`, when given,
|
|
@@ -72,8 +72,8 @@ module Hecks
|
|
|
72
72
|
# which element it means, so re-deriving `wants` from `args` would be
|
|
73
73
|
# redundant at best and wrong if `args` no longer carries that identity
|
|
74
74
|
# at all.
|
|
75
|
-
# Locate, then copy-before-mutate, in that order — see the "
|
|
76
|
-
#
|
|
75
|
+
# Locate, then copy-before-mutate, in that order — see the "one
|
|
76
|
+
# level deeper" comment below on why the copy has to happen exactly
|
|
77
77
|
# where it does (aliasing the adapter's own record otherwise).
|
|
78
78
|
# Splitting resolution from the copy/write-back would separate two
|
|
79
79
|
# halves of one aliasing-safety invariant across method boundaries.
|
|
@@ -84,27 +84,27 @@ module Hecks
|
|
|
84
84
|
def element_of(root_aggregate, owner, entity, command_name, container, args, routed_identity = nil)
|
|
85
85
|
entity_name = entity.hecks_name
|
|
86
86
|
list_attr = owner.attributes.find { |a| a.list? && a.type.to_s == entity_name } ||
|
|
87
|
-
raise(UnknownVerb, RefusalWording.
|
|
88
|
-
|
|
87
|
+
raise(UnknownVerb, RefusalWording.render_site("UnknownVerb", "entity_holds_no_list",
|
|
88
|
+
aggregate: owner.hecks_name, entity: entity_name))
|
|
89
89
|
|
|
90
90
|
wants = unless routed_identity
|
|
91
91
|
entity.identity_paths.map do |path|
|
|
92
92
|
head = path.to_s.split(".").first.to_sym
|
|
93
93
|
raw = args[head] ||
|
|
94
|
-
raise(NotFound, RefusalWording.
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
raise(NotFound, RefusalWording.render_site("NotFound", "entity_element_no_identity",
|
|
95
|
+
command: command_name, entity: entity_name,
|
|
96
|
+
identity: Identity.reading(entity)))
|
|
97
97
|
|
|
98
|
-
#
|
|
98
|
+
# An identity offered for addressing, not for storage
|
|
99
99
|
# (BUG#3, found live by `bin/qa_sweep` — banking fuzz seed
|
|
100
100
|
# 23, `LedgerEntry.Amend sequence: { value: 0 }` against an
|
|
101
101
|
# entry-less ledger). Coercing it all the way to a typed
|
|
102
|
-
# `Value` here ran that type's own invariant
|
|
102
|
+
# `Value` here ran that type's own invariant before this
|
|
103
103
|
# method ever checks whether any element matches — a
|
|
104
104
|
# `sequence: 0` against `LedgerSequence`'s own "a ledger
|
|
105
105
|
# sequence is positive" invariant raised InvariantViolation,
|
|
106
106
|
# not NotFound, even when (as here) nothing was ever posted
|
|
107
|
-
# at all. Every element actually
|
|
107
|
+
# at all. Every element actually in the list already
|
|
108
108
|
# satisfied its own type's invariant the moment it was
|
|
109
109
|
# created, so a value that fails it can never equal one —
|
|
110
110
|
# degrading to `UNMATCHABLE` here, instead of propagating,
|
|
@@ -114,7 +114,7 @@ module Hecks
|
|
|
114
114
|
# `extract_id`/`extract_wants` (a raw scalar read, never a
|
|
115
115
|
# typed rebuild — rust/src/generated/*/*.rs) and
|
|
116
116
|
# `Identity.from`'s own raw-comparison convention for a
|
|
117
|
-
#
|
|
117
|
+
# root aggregate's identity (this file's sibling,
|
|
118
118
|
# `identity.rb`). `raw` rides alongside `want` so the
|
|
119
119
|
# eventual NotFound below can still quote what was offered.
|
|
120
120
|
want = begin
|
|
@@ -136,7 +136,7 @@ module Hecks
|
|
|
136
136
|
end
|
|
137
137
|
end
|
|
138
138
|
unless position
|
|
139
|
-
raise NotFound, RefusalWording.
|
|
139
|
+
raise NotFound, RefusalWording.render_site(
|
|
140
140
|
"NotFound", "entity_element_missing",
|
|
141
141
|
entity: entity_name, identity: Identity.reading(entity),
|
|
142
142
|
wants: wants&.map { |_h, path, _want, raw| Identity.scalar(path, raw) }&.join(", "),
|
|
@@ -145,17 +145,17 @@ module Hecks
|
|
|
145
145
|
)
|
|
146
146
|
end
|
|
147
147
|
|
|
148
|
-
#
|
|
148
|
+
# One level deeper than Instance#dup, for the same reason: a list
|
|
149
149
|
# attribute holds Hashes, and `apply_to_element` mutates the found
|
|
150
|
-
# one
|
|
150
|
+
# one in place — the update mechanism for an entity, not a bug. But
|
|
151
151
|
# in place means aliased with the adapter's own record until this
|
|
152
152
|
# copies the array and the target element before handing either
|
|
153
153
|
# back, and writes the fresh array into `container` so the copy is
|
|
154
|
-
# what persists on success and
|
|
154
|
+
# what persists on success and nothing aliased survives a refusal.
|
|
155
155
|
# `container[list_attr.name] = copied` reaches `instance` itself
|
|
156
|
-
# when this is the
|
|
157
|
-
#
|
|
158
|
-
#
|
|
156
|
+
# when this is the first hop, and reaches the (already copied)
|
|
157
|
+
# parent element when it is a later one — either way it is the
|
|
158
|
+
# same already-fresh object `locate_chain` is about to hand back
|
|
159
159
|
# as `container` for the next hop, so nothing further has to
|
|
160
160
|
# propagate a write back up the chain by hand.
|
|
161
161
|
copied = original.dup
|
|
@@ -165,9 +165,9 @@ module Hecks
|
|
|
165
165
|
element
|
|
166
166
|
end
|
|
167
167
|
|
|
168
|
-
#
|
|
169
|
-
# twin of `Identity.of`, reading off the
|
|
170
|
-
# than a dispatch payload. An id is a
|
|
168
|
+
# The element's own identity, joined from its parts — the entity-level
|
|
169
|
+
# twin of `Identity.of`, reading off the stored element (a Hash) rather
|
|
170
|
+
# than a dispatch payload. An id is a scalar, and the path is how it is
|
|
171
171
|
# reached — never by opening a value object and taking whatever single
|
|
172
172
|
# field is inside. That unwrapping is gone from the language: a piece
|
|
173
173
|
# that does not name its fields is refused when the bluebook loads ("an
|
|
@@ -191,10 +191,10 @@ module Hecks
|
|
|
191
191
|
# refuses what it cannot check applying nothing instead. `Member`/
|
|
192
192
|
# `Dispatch` (S17) are the first real callers: both need to
|
|
193
193
|
# append a value-object-typed element (`Pair`/`Binding`) onto a
|
|
194
|
-
# list attribute
|
|
194
|
+
# list attribute they own, once they become entities of
|
|
195
195
|
# `ValueObject`/`ProcessManager` rather than separate aggregates.
|
|
196
196
|
#
|
|
197
|
-
# `:increment`/`:decrement`/`:multiply`
|
|
197
|
+
# `:increment`/`:decrement`/`:multiply` also fixed here, found
|
|
198
198
|
# while proving this method against a real fixture: they wrapped
|
|
199
199
|
# `amount` unconditionally whenever `attribute` existed, the same
|
|
200
200
|
# asymmetric-wrapping shape `MutationApplier#rewrap_arithmetic_
|
|
@@ -246,13 +246,13 @@ module Hecks
|
|
|
246
246
|
# `corrects` — BUG#30. `MutationApplier#apply`'s own aggregate-
|
|
247
247
|
# level `:corrects` branch's own comment gives the full reasoning;
|
|
248
248
|
# the same one applies here unchanged: this mutation targets no
|
|
249
|
-
# field on
|
|
250
|
-
# the
|
|
249
|
+
# field on this element at all — its own event name, and whether
|
|
250
|
+
# the owning record has actually emitted it, was already checked
|
|
251
251
|
# once, up front, by `EntityInterpreter#step_enforce_givens`
|
|
252
252
|
# (`CommandRules::Admissibility#enforce_correction_target`, called
|
|
253
|
-
# there against the
|
|
253
|
+
# there against the parent record/root aggregate — see that
|
|
254
254
|
# step's own comment for exactly why). Whatever field a correction
|
|
255
|
-
# actually changes is an
|
|
255
|
+
# actually changes is an ordinary declared `sets`/`increment`/etc.
|
|
256
256
|
# mutation of its own, applied by one of the branches above like
|
|
257
257
|
# any other — `qa/stress_domains/corrections`' own `Entry.Amend`
|
|
258
258
|
# pairs `corrects "EntryRecorded", ...` with a separate `sets
|
|
@@ -282,8 +282,8 @@ module Hecks
|
|
|
282
282
|
end
|
|
283
283
|
|
|
284
284
|
# `MutationApplier#resolve_append_source`'s own entity-scoped
|
|
285
|
-
# twin — a caller-supplied
|
|
286
|
-
#
|
|
285
|
+
# twin — a caller-supplied arg first, falling back to the
|
|
286
|
+
# element's own current field (never the parent instance's) when
|
|
287
287
|
# it isn't one.
|
|
288
288
|
def resolve_element_append_source(source, element, args)
|
|
289
289
|
return source unless source.is_a?(Symbol)
|
|
@@ -293,25 +293,27 @@ module Hecks
|
|
|
293
293
|
end
|
|
294
294
|
|
|
295
295
|
# `MutationApplier#appended`'s own entity-scoped twin. Usually a
|
|
296
|
-
#
|
|
296
|
+
# value object element — an entity's own list, appended to by an
|
|
297
297
|
# entity-owned command, holds a value object (`Member.pairs`'
|
|
298
298
|
# own `Pair`, `Dispatch.with_spec`'s own `Binding`) the same way
|
|
299
299
|
# most real corpus appends do — but entity-in-entity nesting (a
|
|
300
|
-
# list of
|
|
300
|
+
# list of another entity, owned by this one) is real now too:
|
|
301
301
|
# `qa/stress_domains/nested_pieces` (`Board.AddCard`, appending a
|
|
302
302
|
# `Card` onto `Board`'s own `cards`) is the first corpus member to
|
|
303
303
|
# do it, the comment this replaces having been written before that
|
|
304
304
|
# domain existed. `element_type` naming an entity rather than a
|
|
305
305
|
# value object falls through to `fields` unchanged, same as
|
|
306
|
-
# before — `MutationApplier#entity_element`'s own identity-minting
|
|
307
|
-
#
|
|
308
|
-
#
|
|
309
|
-
#
|
|
310
|
-
#
|
|
311
|
-
#
|
|
312
|
-
#
|
|
313
|
-
#
|
|
314
|
-
#
|
|
306
|
+
# before — `MutationApplier#entity_element`'s own identity-minting
|
|
307
|
+
# fallback still isn't mirrored here (nothing in this corpus needs
|
|
308
|
+
# auto-minting at this depth — Card supplies its own identity in
|
|
309
|
+
# the append mapping) — but its collision-checking fallback now is
|
|
310
|
+
# (BUG#145, `check_entity_collision`, below — see its own call
|
|
311
|
+
# site's comment for why "collision-checking a nested entity is its
|
|
312
|
+
# own separate, unfixed question," this comment's own prior wording,
|
|
313
|
+
# stopped being true). BUG#12's fix (below) is likewise shared:
|
|
314
|
+
# every declared attribute the append mapping doesn't name gets its
|
|
315
|
+
# own default the same way a fresh aggregate's own attributes
|
|
316
|
+
# already do (`Instance.defaults`), whichever branch built `fields`.
|
|
315
317
|
def appended_to_element(aggregate, entity, element, mutation, args)
|
|
316
318
|
fields = mutation.source.transform_values { |source| resolve_element_append_source(source, element, args) }
|
|
317
319
|
element_type = entity.attribute(mutation.target)&.type
|
|
@@ -323,22 +325,53 @@ module Hecks
|
|
|
323
325
|
if value_object
|
|
324
326
|
Value.build(value_object, fields, aggregate)
|
|
325
327
|
else
|
|
326
|
-
# `entity.entities`,
|
|
327
|
-
# nested inside a piece is a child of the
|
|
328
|
+
# `entity.entities`, not `aggregate.entities` — a piece
|
|
329
|
+
# nested inside a piece is a child of the owning entity
|
|
328
330
|
# (`Card` is `Board.entities`, never `Workspace.entities`;
|
|
329
331
|
# `Behaviour::Entity#entities` answers direct children only,
|
|
330
332
|
# by design — see its own comment), the same lexical-nesting
|
|
331
333
|
# rule `EntityBuilder#entity_impl` builds the tree with in
|
|
332
334
|
# the first place.
|
|
333
335
|
nested_entity = entity.entities.find { |piece| piece.hecks_name == element_type.to_s }
|
|
334
|
-
|
|
336
|
+
if nested_entity
|
|
337
|
+
# BUG#145 — `MutationApplier#entity_element`'s own
|
|
338
|
+
# `check_entity_collision` call (one hop up, an aggregate's
|
|
339
|
+
# own entity list) never had a twin here: a nested entity's
|
|
340
|
+
# own identity is always caller-supplied at this depth
|
|
341
|
+
# (`Card.sequence` rides the append mapping directly — no
|
|
342
|
+
# nested-entity auto-mint exists anywhere in this corpus,
|
|
343
|
+
# see this method's own header), so this is unconditional,
|
|
344
|
+
# unlike `#entity_element`'s own auto-mint/collision `if`/
|
|
345
|
+
# `else` split — there is no auto-mint branch here to skip.
|
|
346
|
+
# Found live: `qa/stress_domains/nested_pieces`'s own
|
|
347
|
+
# differential sweep, `Board.AddCard` dispatched twice under
|
|
348
|
+
# the same `sequence` — Ruby silently appended a second
|
|
349
|
+
# `Card`, Rust's generated code (`rust/project/mutations.rb`'s
|
|
350
|
+
# `emit_mutation_line_body`, which never splits an
|
|
351
|
+
# aggregate-owned append from an entity-owned one the way
|
|
352
|
+
# this runtime's two separate methods do) already refused
|
|
353
|
+
# `AlreadyExists` for both depths.
|
|
354
|
+
# `entity` (the owner — `Board`), not `aggregate` (the root
|
|
355
|
+
# — `Workspace`), is what the refusal names as "on {…}" —
|
|
356
|
+
# `check_entity_collision`'s first argument is only ever
|
|
357
|
+
# used for that one naming purpose (`owner.hecks_name`,
|
|
358
|
+
# below), matching Rust's own generated wording exactly
|
|
359
|
+
# (`rust/project/mutations.rb`'s own `collision_guard`
|
|
360
|
+
# passes the entity's declaring construct's name the same
|
|
361
|
+
# way — "a Card already exists on Board", never "…on
|
|
362
|
+
# Workspace").
|
|
363
|
+
check_entity_collision(entity, nested_entity, element[mutation.target], fields)
|
|
364
|
+
fill_declared_defaults(aggregate, nested_entity, fields)
|
|
365
|
+
else
|
|
366
|
+
fields
|
|
367
|
+
end
|
|
335
368
|
end
|
|
336
369
|
Freezer.deep(Array(element[mutation.target]) + [appended])
|
|
337
370
|
end
|
|
338
371
|
|
|
339
372
|
# BUG#12 — an entity created via `sets :list, append: {...}` used
|
|
340
|
-
# to leave any of its
|
|
341
|
-
# simply didn't name (an optional field a
|
|
373
|
+
# to leave any of its own declared attributes the append mapping
|
|
374
|
+
# simply didn't name (an optional field a later, separate command
|
|
342
375
|
# sets — `Board.label`, `Card.note`) absent from the stored hash
|
|
343
376
|
# entirely, not even a `nil` placeholder, until that later command
|
|
344
377
|
# actually ran. `rust/project/json_codec.rb#emit_to_json_flat`'s
|
|
@@ -346,12 +379,12 @@ module Hecks
|
|
|
346
379
|
# contract for a persisted record: every declared field present,
|
|
347
380
|
# `null` when unset, "because Ruby's own `JSON.generate(state)`
|
|
348
381
|
# round-trip this mirrors does the same" — true for a freshly
|
|
349
|
-
# created
|
|
382
|
+
# created aggregate (`Instance.defaults` already fills one key per
|
|
350
383
|
# declared attribute, `default_for` per attribute), never true for
|
|
351
384
|
# an entity minted by an append. This closes that gap the same
|
|
352
|
-
# way: `Instance.default_for` is the
|
|
385
|
+
# way: `Instance.default_for` is the same per-attribute default
|
|
353
386
|
# rule (nil with no declared `default:`, a fully-defaulted value
|
|
354
|
-
# object when every one of
|
|
387
|
+
# object when every one of its own fields has one), reused rather
|
|
355
388
|
# than reimplemented so the two creation paths can never drift on
|
|
356
389
|
# what "the default" means. Additive only — a key `fields` already
|
|
357
390
|
# holds (the append mapping, an auto-minted identity, a lifecycle
|
|
@@ -366,8 +399,8 @@ module Hecks
|
|
|
366
399
|
end
|
|
367
400
|
|
|
368
401
|
# `MutationApplier#removed`'s own entity-scoped twin — matches by
|
|
369
|
-
#
|
|
370
|
-
# never be lost" reasoning that method's own comment gives,
|
|
402
|
+
# value equality, element-wise, the same "so a concurrent Add can
|
|
403
|
+
# never be lost" reasoning that method's own comment gives, unless
|
|
371
404
|
# the list this targets is itself entity-typed — see
|
|
372
405
|
# `list_element_match?`, below, which both this and
|
|
373
406
|
# `MutationApplier#removed` now share.
|
|
@@ -378,23 +411,23 @@ module Hecks
|
|
|
378
411
|
Array(element[mutation.target]).reject { |candidate| list_element_match?(aggregate, attribute, candidate, value) }
|
|
379
412
|
end
|
|
380
413
|
|
|
381
|
-
# BUG#32 (QualityControl ledger) —
|
|
382
|
-
#
|
|
383
|
-
#
|
|
414
|
+
# BUG#32 (QualityControl ledger) — the match rule `remove:` uses
|
|
415
|
+
# against one stored list element. Value equality for a
|
|
416
|
+
# value-object-typed list stays exactly what it always was — an
|
|
384
417
|
# element and `value` are both real `Value`s there, so `==` already
|
|
385
418
|
# compares every field, the "concurrent Add can never be lost"
|
|
386
419
|
# shape `removed`/`removed_from_element`'s own headers describe.
|
|
387
|
-
# An
|
|
420
|
+
# An entity-typed list is different in kind, not just in type: a
|
|
388
421
|
# stored element is a plain Hash, never a `Value` (`Entity`'s own
|
|
389
422
|
# header — "an entity must never answer .value_object"), so there
|
|
390
423
|
# is no whole-value shape to compare against at all — only the
|
|
391
|
-
# entity's own
|
|
424
|
+
# entity's own identity field, the same field a caller already has
|
|
392
425
|
# to name to address that element any other way
|
|
393
426
|
# (`element_of`'s own `wants`, above). `value` arrives here already
|
|
394
427
|
# coerced against that identity field's declared type
|
|
395
428
|
# (`Coercion#hydrate_entity_identity`, run underneath
|
|
396
429
|
# `Value.for_attribute` before either caller above ever sees it),
|
|
397
|
-
# so this only has to know
|
|
430
|
+
# so this only has to know which field to read off the stored
|
|
398
431
|
# element — `entity.identity_heads`'s own single head, when there
|
|
399
432
|
# is exactly one. A composite identity (more than one head, or
|
|
400
433
|
# none) has no single field a bare `remove:` target could mean —
|
|
@@ -405,7 +438,7 @@ module Hecks
|
|
|
405
438
|
# entity identity elsewhere in this runtime.
|
|
406
439
|
#
|
|
407
440
|
# Shared by `MutationApplier#removed` (an aggregate's own list) and
|
|
408
|
-
# `#removed_from_element` (a list an
|
|
441
|
+
# `#removed_from_element` (a list an entity owns), so the two
|
|
409
442
|
# `remove:` call sites can never quietly disagree on what
|
|
410
443
|
# "matches" means — the same reasoning this file's own header
|
|
411
444
|
# gives for centralizing `locate_chain`/`element_of` once rather
|
|
@@ -419,6 +452,57 @@ module Hecks
|
|
|
419
452
|
|
|
420
453
|
element.is_a?(Hash) && element[head] == value
|
|
421
454
|
end
|
|
455
|
+
|
|
456
|
+
# BUG#13 (PR #549) — the same check #hydrate gives every creating
|
|
457
|
+
# aggregate command (`repository.find(id)`,
|
|
458
|
+
# `command_interpreter.rb`), one level down. Originally lived in
|
|
459
|
+
# `MutationApplier` (mutation_applier.rb), called only from
|
|
460
|
+
# `#entity_element` — an aggregate's own entity list (`Workspace.
|
|
461
|
+
# boards`, `Ledger.entries`). Moved here (BUG#145) so `#appended_
|
|
462
|
+
# to_element`, above — an entity's own nested entity list one hop
|
|
463
|
+
# further in (`Board.cards`) — can share it too, rather than
|
|
464
|
+
# reimplementing it a second time the same way `#list_element_
|
|
465
|
+
# match?` already avoids that split for `remove:`.
|
|
466
|
+
#
|
|
467
|
+
# Reached, at the aggregate-owned call site, only on the two
|
|
468
|
+
# branches that do not auto-mint: a caller-supplied identity (the
|
|
469
|
+
# field is already in the append's own field map) or a composite
|
|
470
|
+
# one (`entity.identified_by` is nil for those — Runtime::
|
|
471
|
+
# Identified#derive_identity). Neither used to check the sibling
|
|
472
|
+
# list at all: a second LogVisit with the same date+sequence, or a
|
|
473
|
+
# second IssueKey with the same serial, appended a silent
|
|
474
|
+
# duplicate — worse than an ordinary duplicate row, because
|
|
475
|
+
# `EntityElement#element_of`'s own `find_index` always matches the
|
|
476
|
+
# first match, so the second becomes permanently unaddressable by
|
|
477
|
+
# any later command. At the entity-owned call site (`#appended_to_
|
|
478
|
+
# element`), there is no auto-mint branch at all — every caller
|
|
479
|
+
# reaches this unconditionally, since a nested entity's own
|
|
480
|
+
# identity is always caller-supplied in this corpus.
|
|
481
|
+
#
|
|
482
|
+
# Auto-minted (aggregate-owned) entities never reach here —
|
|
483
|
+
# `identity_heads` for them is still checked at mint time by
|
|
484
|
+
# construction (`current.size + 1` can only repeat if something
|
|
485
|
+
# `remove:`s from the list between mints, which no real domain does
|
|
486
|
+
# today), so they can't be flagged by mistake.
|
|
487
|
+
# `owner` — the declaring construct named in the "…already exists on
|
|
488
|
+
# {owner}" wording: the root aggregate at the aggregate-owned call
|
|
489
|
+
# site (`Workspace`, `Ledger`), the immediately-enclosing entity at
|
|
490
|
+
# the entity-owned one (`Board` — never the root `Workspace` two
|
|
491
|
+
# hops up). Used for that naming purpose only (`owner.hecks_name`) —
|
|
492
|
+
# never for `Value`/namespace resolution, which is why an `Entity`
|
|
493
|
+
# (not just an `Aggregate`) is a valid thing to pass here.
|
|
494
|
+
def check_entity_collision(owner, entity, current, fields)
|
|
495
|
+
heads = entity.identity_heads
|
|
496
|
+
return if heads.empty?
|
|
497
|
+
|
|
498
|
+
collision = Array(current).find { |element| heads.all? { |head| element[head] == fields[head] } }
|
|
499
|
+
return unless collision
|
|
500
|
+
|
|
501
|
+
raise(AlreadyExists, RefusalWording.render_site("AlreadyExists", "entity_duplicate",
|
|
502
|
+
entity: entity.hecks_name, aggregate: owner.hecks_name,
|
|
503
|
+
identity: Identity.reading(entity),
|
|
504
|
+
offered: heads.map { |head| Rendering.describe(fields[head]) }))
|
|
505
|
+
end
|
|
422
506
|
end
|
|
423
507
|
end
|
|
424
508
|
end
|