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
|
@@ -19,7 +19,7 @@ module Hecks
|
|
|
19
19
|
include Interpreting
|
|
20
20
|
include CommandInterpreter::ArgumentGate
|
|
21
21
|
|
|
22
|
-
Context = Struct.new(:domain, :aggregate, :operation, :args, :route, :instance, :result)
|
|
22
|
+
Context = Struct.new(:domain, :aggregate, :operation, :args, :route, :instance, :result, :invocation)
|
|
23
23
|
|
|
24
24
|
DISPATCH_ORDER = %i[
|
|
25
25
|
refuse_unknown_arguments refuse_absent_arguments normalize_args resolve_references resolve_route emit
|
|
@@ -30,9 +30,12 @@ module Hecks
|
|
|
30
30
|
@rules = rules
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
# `invocation` — the `Runtime::Invocation` `Dispatcher` built;
|
|
34
|
+
# `ctx.args` is its `to_args`, `ctx.route` its `target`.
|
|
35
|
+
def call(domain, aggregate, operation, invocation)
|
|
36
|
+
ctx = Context.new(domain, aggregate, operation, invocation.to_args)
|
|
37
|
+
ctx.invocation = invocation
|
|
38
|
+
ctx.route = invocation.target
|
|
36
39
|
run_dispatch_order(DISPATCH_ORDER, ctx)
|
|
37
40
|
ctx.result
|
|
38
41
|
end
|
|
@@ -66,13 +69,13 @@ module Hecks
|
|
|
66
69
|
ctx.result = step(:emit) { ctx.operation.outbound? ? ask(ctx) : emit(ctx) }
|
|
67
70
|
end
|
|
68
71
|
|
|
69
|
-
#
|
|
72
|
+
# The domain calling out, and both endings recorded.
|
|
70
73
|
#
|
|
71
74
|
# The adapter is found the same way every other port's is — by name,
|
|
72
75
|
# across whatever adapters this boot loaded — so an `asks` is bound by
|
|
73
76
|
# an adapter declaring `port "IssueTracker"` and nothing new to learn.
|
|
74
77
|
#
|
|
75
|
-
#
|
|
78
|
+
# Every failure is an answer. A raise from the far side of a boundary is
|
|
76
79
|
# not an exception in this domain's terms, it is the outside saying no,
|
|
77
80
|
# and the chapter already named the word for that. So the rescue is
|
|
78
81
|
# deliberately wide: a timeout, a bad credential, an adapter that does
|
|
@@ -80,22 +83,22 @@ module Hecks
|
|
|
80
83
|
# `refuses` event, carrying what was said. A policy reacts to it, a
|
|
81
84
|
# retry counter reads it, and nothing has to catch anything.
|
|
82
85
|
#
|
|
83
|
-
#
|
|
86
|
+
# An ask is handed the record it is about.
|
|
84
87
|
#
|
|
85
|
-
# An
|
|
88
|
+
# An inbound operation deliberately cannot read state — it is the
|
|
86
89
|
# anti-corruption boundary, translating a fact from outside, and letting
|
|
87
90
|
# it read the aggregate would make it a second place rules live. That
|
|
88
91
|
# rule was written for that direction and does not survive the crossing.
|
|
89
92
|
#
|
|
90
93
|
# An outbound one almost always needs the record. `asks "File"` names
|
|
91
94
|
# `reference_to Ticket` and the adapter needs the ticket's repository,
|
|
92
|
-
# title and body — which are
|
|
95
|
+
# title and body — which are on the ticket, and which the policy that
|
|
93
96
|
# triggered this cannot supply because a command's event payload is its
|
|
94
|
-
#
|
|
97
|
+
# arguments, not its state. Without this, every ask would have to have
|
|
95
98
|
# its data re-passed through the command that fired it, so the same text
|
|
96
99
|
# would live in two places and could differ.
|
|
97
100
|
#
|
|
98
|
-
#
|
|
101
|
+
# Arguments win over state, because an argument is what this call said
|
|
99
102
|
# and state is what the record happens to hold.
|
|
100
103
|
def ask(ctx)
|
|
101
104
|
payload = held_state(ctx).merge(materialise(ctx.args))
|
|
@@ -105,13 +108,13 @@ module Hecks
|
|
|
105
108
|
announce(ctx, ctx.operation.refuses, ctx.args.merge(refusal: { value: "#{e.class}: #{e.message}" }))
|
|
106
109
|
end
|
|
107
110
|
|
|
108
|
-
#
|
|
109
|
-
# close. A policy re-enters its target with the event payload
|
|
111
|
+
# The answer is spread, not nested — and that is what makes the loop
|
|
112
|
+
# close. A policy re-enters its target with the event payload verbatim;
|
|
110
113
|
# it cannot reach inside a key. So an answer tucked under `answered:`
|
|
111
114
|
# can be read by a human and by nothing else, and the command that
|
|
112
115
|
# should record the issue number never gets one.
|
|
113
116
|
#
|
|
114
|
-
# Spread, the adapter's own keys
|
|
117
|
+
# Spread, the adapter's own keys are the arguments of whatever command
|
|
115
118
|
# reacts to the answering event. Which is a real contract on the adapter
|
|
116
119
|
# — it must return what that command takes, in the shape the runtime
|
|
117
120
|
# coerces (`{ number: { value: 43 } }`, not `43`) — and naming it here
|
|
@@ -134,7 +137,7 @@ module Hecks
|
|
|
134
137
|
end
|
|
135
138
|
end
|
|
136
139
|
|
|
137
|
-
#
|
|
140
|
+
# The record, if there is one. A record that does not exist yet is not
|
|
138
141
|
# an error here — the ask still goes, carrying only its arguments, and
|
|
139
142
|
# whatever the adapter makes of that is its own business. Refusing
|
|
140
143
|
# would put a second existence check behind the one `resolve_references`
|
|
@@ -145,10 +148,10 @@ module Hecks
|
|
|
145
148
|
{}
|
|
146
149
|
end
|
|
147
150
|
|
|
148
|
-
#
|
|
151
|
+
# The port this operation belongs to, found by asking the aggregate
|
|
149
152
|
# rather than threading it through the call — the dispatcher already
|
|
150
153
|
# resolved it once to get here, and a second parameter carried purely so
|
|
151
|
-
# this method can read it would be a parameter every
|
|
154
|
+
# this method can read it would be a parameter every other step ignores.
|
|
152
155
|
def port_name_for(ctx)
|
|
153
156
|
owning = ctx.aggregate.ports.find { |port| port.operations.any? { |op| op.equal?(ctx.operation) } }
|
|
154
157
|
owning&.name or raise WiringError,
|
|
@@ -185,9 +188,9 @@ module Hecks
|
|
|
185
188
|
[event]
|
|
186
189
|
end
|
|
187
190
|
|
|
188
|
-
#
|
|
191
|
+
# The one place this differs from CommandRules::Emission — there is no
|
|
189
192
|
# mutated instance to read an id off, because nothing was hydrated or
|
|
190
|
-
# saved. The record this event is
|
|
193
|
+
# saved. The record this event is about is named by whichever attribute
|
|
191
194
|
# is a reference to the owning aggregate (PortOperationBuilder#build
|
|
192
195
|
# already refused to build an operation with none), so its coerced
|
|
193
196
|
# value — already a plain id, never an object, per
|
|
@@ -15,7 +15,7 @@ module Hecks
|
|
|
15
15
|
# repository: prefers a native adapter hook (Ports::Query.execute)
|
|
16
16
|
# when the store can answer directly, falling back to interpreting
|
|
17
17
|
# wheres/order_by/limit/offset over every loaded record itself.
|
|
18
|
-
# #reference_call/#reference_interpret are a
|
|
18
|
+
# #reference_call/#reference_interpret are a separate, deliberately
|
|
19
19
|
# naive re-implementation of the same evaluation, used only as the
|
|
20
20
|
# fuzzer's oracle to catch divergence between adapters and this
|
|
21
21
|
# interpreter's own native path.
|
|
@@ -32,10 +32,10 @@ module Hecks
|
|
|
32
32
|
declared = declared_query(aggregate, query_name)
|
|
33
33
|
args = normalize_args(aggregate, declared, args)
|
|
34
34
|
declared = TenantScope.apply(declared, args)
|
|
35
|
-
#
|
|
36
|
-
# in `.wheres` and rides through as an ordinary
|
|
37
|
-
# the
|
|
38
|
-
# sub-query against the
|
|
35
|
+
# After TenantScope, so its synthetic clause is already present
|
|
36
|
+
# in `.wheres` and rides through as an ordinary local clause on
|
|
37
|
+
# the outer query. It does not reach the hop's own inner
|
|
38
|
+
# sub-query against the target aggregate — a hop's target may
|
|
39
39
|
# not even declare the same tenant boundary, and propagating one
|
|
40
40
|
# aggregate's tenant scope onto an unrelated aggregate's own
|
|
41
41
|
# query is a real design question of its own, not answered here.
|
|
@@ -51,14 +51,14 @@ module Hecks
|
|
|
51
51
|
if (native = Ports::Query.execute(repository, declared, args,
|
|
52
52
|
context: { domain: domain, aggregate: aggregate, registry: @registry }))
|
|
53
53
|
records = native
|
|
54
|
-
# `record.state.merge(id: record.id)` — id
|
|
54
|
+
# `record.state.merge(id: record.id)` — id last, not first. See
|
|
55
55
|
# Instance#to_h's own comment: an aggregate free to declare its
|
|
56
56
|
# own attribute literally named `id` has that attribute's own
|
|
57
57
|
# wrapped value sitting in `record.state[:id]` already; merging
|
|
58
|
-
# it
|
|
58
|
+
# it over a `{id:}.merge(state)` used to let it silently
|
|
59
59
|
# clobber the correct bare identity this row is supposed to
|
|
60
60
|
# carry.
|
|
61
|
-
# A
|
|
61
|
+
# A query row is an answer, not a handle. Mutating one edits
|
|
62
62
|
# nobody's state and silently disagrees with the store.
|
|
63
63
|
return Freezer.deep(records.map { |record| record.state.merge(id: record.id) })
|
|
64
64
|
end
|
|
@@ -66,18 +66,18 @@ module Hecks
|
|
|
66
66
|
Freezer.deep(interpret(repository.all, declared, args, domain: domain))
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
-
# The
|
|
69
|
+
# The reference answer — this interpreter's own evaluation, never an
|
|
70
70
|
# adapter's native hook. The fuzzer's query oracle replays every
|
|
71
71
|
# generated ask through both paths and treats a difference as a
|
|
72
72
|
# finding: the differential gate the retired cross-runtime harness
|
|
73
73
|
# should always have been, aimed where the divergence actually
|
|
74
74
|
# lives — between the engines inside this one runtime.
|
|
75
75
|
#
|
|
76
|
-
# A hop clause is answered here by its
|
|
76
|
+
# A hop clause is answered here by its own, deliberately naive
|
|
77
77
|
# walk (reference_where_holds?) — never Runtime::ReferenceHop's
|
|
78
|
-
# partition/fold/
|
|
78
|
+
# partition/fold/in-clause. Sharing that algorithm would have made
|
|
79
79
|
# this oracle blind to exactly the code the hop feature adds: every
|
|
80
|
-
#
|
|
80
|
+
# phase of a shared fold would still get diffed against the native
|
|
81
81
|
# adapters, but the fold itself — the empty candidate set, a
|
|
82
82
|
# duplicate id, a dangling reference, a chain's inside-out
|
|
83
83
|
# resolution order — would only ever be compared against itself.
|
|
@@ -95,14 +95,14 @@ module Hecks
|
|
|
95
95
|
|
|
96
96
|
def declared_query(aggregate, query_name)
|
|
97
97
|
aggregate.query(query_name) ||
|
|
98
|
-
raise(UnknownVerb, RefusalWording.
|
|
99
|
-
|
|
98
|
+
raise(UnknownVerb, RefusalWording.render_site("UnknownVerb", "no_query",
|
|
99
|
+
aggregate: aggregate.hecks_name, query: query_name))
|
|
100
100
|
end
|
|
101
101
|
|
|
102
102
|
def interpret(records, declared, args, domain: nil)
|
|
103
103
|
matched = records.select { |r| declared.wheres.all? { |w| where_holds?(w, r, args, domain: domain) } }
|
|
104
104
|
ordered = ordered(matched, declared.order_by, declared.null_semantics)
|
|
105
|
-
#
|
|
105
|
+
# **Offset first, then limit** — the order SQL means by `LIMIT n
|
|
106
106
|
# OFFSET m`, and the order Ports::Query::InMemory#execute already
|
|
107
107
|
# applies (see that file's own comment). This interpreter used to
|
|
108
108
|
# never read declared.offset at all — offset silently vanished for
|
|
@@ -110,7 +110,7 @@ module Hecks
|
|
|
110
110
|
skipped = declared.offset ? ordered.drop(resolve_query_value(declared.offset.value, args).to_i) : ordered
|
|
111
111
|
capped = declared.limit ? skipped.first(resolve_query_value(declared.limit.value, args).to_i) : skipped
|
|
112
112
|
|
|
113
|
-
# id
|
|
113
|
+
# id last — see the native-path comment above; same clobbering
|
|
114
114
|
# risk for the in-memory reference interpreter's own rows.
|
|
115
115
|
capped.map { |r| r.state.merge(id: r.id) }
|
|
116
116
|
end
|
|
@@ -120,7 +120,7 @@ module Hecks
|
|
|
120
120
|
# reference is answered by reference_where_holds? instead of the
|
|
121
121
|
# plain FieldPath.dig(record, field) `where_holds?` uses (which
|
|
122
122
|
# has no concept of a reference at all — it would just read the
|
|
123
|
-
# raw id straight off the record and compare
|
|
123
|
+
# raw id straight off the record and compare that).
|
|
124
124
|
def reference_interpret(records, declared, args, domain:, shape:)
|
|
125
125
|
matched = records.select do |r|
|
|
126
126
|
declared.wheres.all? do |w|
|
|
@@ -128,20 +128,20 @@ module Hecks
|
|
|
128
128
|
end
|
|
129
129
|
end
|
|
130
130
|
ordered = ordered(matched, declared.order_by, declared.null_semantics)
|
|
131
|
-
#
|
|
131
|
+
# **Offset first, then limit** — same fix, same reasoning, as
|
|
132
132
|
# #interpret's own rows above.
|
|
133
133
|
skipped = declared.offset ? ordered.drop(resolve_query_value(declared.offset.value, args).to_i) : ordered
|
|
134
134
|
capped = declared.limit ? skipped.first(resolve_query_value(declared.limit.value, args).to_i) : skipped
|
|
135
135
|
|
|
136
|
-
# id
|
|
136
|
+
# id last — same reasoning, same fix, as interpret's own rows.
|
|
137
137
|
capped.map { |r| r.state.merge(id: r.id) }
|
|
138
138
|
end
|
|
139
139
|
|
|
140
|
-
#
|
|
140
|
+
# **The naive reading of a hop**: not a fold, not an id set — for
|
|
141
141
|
# each candidate row, walk the reference by hand and dig the
|
|
142
142
|
# field out of whatever it actually points at. A nil reference,
|
|
143
143
|
# or one that resolves to nothing (a dangling id), makes the
|
|
144
|
-
#
|
|
144
|
+
# whole clause false outright, whatever the comparator — "points
|
|
145
145
|
# at a client that is not active" is false for a proposal with no
|
|
146
146
|
# client at all, the same way it is false for one whose client
|
|
147
147
|
# really is active; falling through to holds?(clause, nil, args)
|
|
@@ -177,8 +177,8 @@ module Hecks
|
|
|
177
177
|
|
|
178
178
|
ordered = ordered_elements(rows, declared.order_by, declared.null_semantics,
|
|
179
179
|
parent_key, entity.identity_heads)
|
|
180
|
-
#
|
|
181
|
-
# #interpret's own rows above. `entity_rows` is the
|
|
180
|
+
# **Offset first, then limit** — same fix, same reasoning, as
|
|
181
|
+
# #interpret's own rows above. `entity_rows` is the only engine
|
|
182
182
|
# for entity/sub-list queries, so a declared offset here silently
|
|
183
183
|
# vanished for every entity query, not merely one path among
|
|
184
184
|
# several.
|
|
@@ -186,31 +186,28 @@ module Hecks
|
|
|
186
186
|
declared.limit ? skipped.first(resolve_query_value(declared.limit.value, args).to_i) : skipped
|
|
187
187
|
end
|
|
188
188
|
|
|
189
|
-
#
|
|
190
|
-
#
|
|
189
|
+
# The three declarations `entity_rows` needs before it can read a
|
|
190
|
+
# single record — the entity itself, its declared query, and the
|
|
191
191
|
# list attribute that holds it on the aggregate. Extracted from
|
|
192
192
|
# `entity_rows` (pure extraction, same lookups, same order, same
|
|
193
193
|
# UnknownVerb refusals) purely to separate "which declarations does
|
|
194
194
|
# this dotted name resolve to" from the row-computation that follows.
|
|
195
195
|
def resolve_entity_query(aggregate, entity_name, query_name)
|
|
196
196
|
entity = aggregate.entities.find { |piece| piece.hecks_name == entity_name } ||
|
|
197
|
-
raise(UnknownVerb, RefusalWording.
|
|
198
|
-
|
|
197
|
+
raise(UnknownVerb, RefusalWording.render_site("UnknownVerb", "entity_unknown",
|
|
198
|
+
aggregate: aggregate.hecks_name, entity: entity_name))
|
|
199
199
|
declared = entity.query(query_name) ||
|
|
200
|
-
raise(UnknownVerb, RefusalWording.
|
|
201
|
-
|
|
200
|
+
raise(UnknownVerb, RefusalWording.render_site("UnknownVerb", "entity_query_missing",
|
|
201
|
+
entity: entity_name, query: query_name))
|
|
202
202
|
list_attr = aggregate.attributes.find { |a| a.list? && a.type.to_s == entity_name } ||
|
|
203
|
-
raise(UnknownVerb, RefusalWording.
|
|
204
|
-
|
|
203
|
+
raise(UnknownVerb, RefusalWording.render_site("UnknownVerb", "entity_holds_no_list",
|
|
204
|
+
aggregate: aggregate.hecks_name, entity: entity_name))
|
|
205
205
|
[entity, declared, list_attr]
|
|
206
206
|
end
|
|
207
207
|
|
|
208
|
-
# FieldPath.dig, not a raw `element[clause.field.to_sym]` —
|
|
209
|
-
#
|
|
210
|
-
#
|
|
211
|
-
# comment below), and a dotted `where` (`where "price.cents" < 100`)
|
|
212
|
-
# needs the same segment-by-segment walk every other query path
|
|
213
|
-
# already gets. Reading only the symbol spelling of the WHOLE
|
|
208
|
+
# FieldPath.dig, not a raw `element[clause.field.to_sym]` — a dotted
|
|
209
|
+
# `where` (`where "price.cents" < 100`) needs the same segment-by-
|
|
210
|
+
# segment walk every other query path already gets. Reading the whole
|
|
214
211
|
# dotted string as one key always missed — `element[:"price.cents"]`
|
|
215
212
|
# is never a real key — so a dotted where on an entity query
|
|
216
213
|
# silently matched nothing, on the only engine entity queries have.
|
|
@@ -218,28 +215,22 @@ module Hecks
|
|
|
218
215
|
holds?(clause, QuerySpecification::FieldPath.dig(element, clause.field), args)
|
|
219
216
|
end
|
|
220
217
|
|
|
221
|
-
# A row's own key
|
|
222
|
-
#
|
|
223
|
-
#
|
|
224
|
-
#
|
|
225
|
-
#
|
|
226
|
-
# identity is a value object, and `to_s` on one is an
|
|
218
|
+
# A row's own key. A sub-list row is a hydrated entity element (symbol-keyed
|
|
219
|
+
# since every adapter decodes through `Ports::Persistence::StateCodec` and
|
|
220
|
+
# `EntityListCoercion#hydrate_entity_list` symbolizes each element — PR A4
|
|
221
|
+
# removed the string-spelling fallback that coped with the old per-adapter
|
|
222
|
+
# shapes). It rides `comparable` for the same reason a where-clause does : an
|
|
223
|
+
# identity is a value object, and `to_s` on one is an object address — a sort key
|
|
227
224
|
# that differs run to run, which is worse than the store order it replaced.
|
|
228
|
-
|
|
229
|
-
# value is a genuinely-held `false` must not fall through to the
|
|
230
|
-
# other spelling (usually absent) and land on `nil`.
|
|
231
|
-
def cell(row, key)
|
|
232
|
-
sym = key.to_sym
|
|
233
|
-
row.key?(sym) ? row[sym] : row[key.to_s]
|
|
234
|
-
end
|
|
225
|
+
def cell(row, key) = row[key.to_sym]
|
|
235
226
|
|
|
236
|
-
# A sub-list row is identified by its
|
|
227
|
+
# A sub-list row is identified by its parent and then its own key : two
|
|
237
228
|
# entities under different parents can share a sequence, so the parent has
|
|
238
229
|
# to lead or the tie is not broken at all.
|
|
239
230
|
#
|
|
240
|
-
#
|
|
231
|
+
# Every key the piece is known by, in declaration order, for the same
|
|
241
232
|
# reason the parent leads: a part that ties is a part that breaks no tie.
|
|
242
|
-
# This took `identified_by`, which is the
|
|
233
|
+
# This took `identified_by`, which is the single head and is nil the
|
|
243
234
|
# moment an identity has two parts — and `cell(row, nil)` calls
|
|
244
235
|
# `nil.to_sym`, so a query against a composite piece did not sort wrongly,
|
|
245
236
|
# it raised. A piece known by one key sorts exactly as it did.
|
|
@@ -262,7 +253,7 @@ module Hecks
|
|
|
262
253
|
# Ports::Query::InMemory#holds? used to carry a copy each and the
|
|
263
254
|
# two drifted — `none_in_state` reached only one of them, and
|
|
264
255
|
# `comparable` disagreed about value objects with two numeric
|
|
265
|
-
# members. What stays here is how a value is
|
|
256
|
+
# members. What stays here is how a value is reached for this
|
|
266
257
|
# path: the registry is instance state rather than an argument.
|
|
267
258
|
def holds?(clause, held, args, record: nil, domain: nil)
|
|
268
259
|
QuerySpecification::Common::Comparison.holds?(
|
|
@@ -276,8 +267,8 @@ module Hecks
|
|
|
276
267
|
|
|
277
268
|
# `boundary: false` always (C3.8 — a query's declared argument types
|
|
278
269
|
# name the argument for callers and generators, never a runtime
|
|
279
|
-
# shape checked here). A null required
|
|
280
|
-
# argument, though, is
|
|
270
|
+
# shape checked here). A null required value-object-typed query
|
|
271
|
+
# argument, though, is not a runtime-shape question at all: C3.7
|
|
281
272
|
# says a named query's declared value-object arguments are checked
|
|
282
273
|
# the same way a command argument's own is, so a `nil` offered for
|
|
283
274
|
# a non-optional value-object-typed query attribute
|
|
@@ -286,30 +277,30 @@ module Hecks
|
|
|
286
277
|
# let it through as a silent, unfiltered query instead, a real
|
|
287
278
|
# Ruby/Rust divergence the fuzzer caught (QualityControl BUG#2).
|
|
288
279
|
#
|
|
289
|
-
# `checked_vo?` true is handled by `null_vo_argument!`
|
|
280
|
+
# `checked_vo?` true is handled by `null_vo_argument!` directly,
|
|
290
281
|
# never by routing through `Value.for_attribute(argument: true)`
|
|
291
|
-
# into the shared `Value::Coercion#nil_argument` the
|
|
282
|
+
# into the shared `Value::Coercion#nil_argument` the command door
|
|
292
283
|
# (`Interpreting#coerce_declared_arguments`) still uses — that
|
|
293
|
-
# method builds a null value object from
|
|
294
|
-
#
|
|
284
|
+
# method builds a null value object from zero fields, which
|
|
285
|
+
# succeeds (silently absorbing the null via the type's own field
|
|
295
286
|
# defaults) whenever every field happens to have one
|
|
296
287
|
# (`Lease.Expired`'s `now`, a `LeaseInstant` with a `default: 0`
|
|
297
288
|
# field; `Account.Overdrawn`/`HighBalance`/`StrictlyAbove`/
|
|
298
289
|
# `AtMost`'s `floor`/`cap`, a two-defaulted-field `Money`) and only
|
|
299
290
|
# refuses when a field has none (`Order.CostingLessThan`'s
|
|
300
|
-
# `ceiling`, a defaultless `Price`) — a real
|
|
291
|
+
# `ceiling`, a defaultless `Price`) — a real query-side divergence
|
|
301
292
|
# from Rust, which always refuses `TypeMismatch` on an explicit
|
|
302
293
|
# null argument regardless of any default (QualityControl BUG#36).
|
|
303
294
|
# `null_vo_argument!` instead treats an explicit null exactly the
|
|
304
|
-
# way `Value.fields_for` already treats any other
|
|
295
|
+
# way `Value.fields_for` already treats any other wrong-shaped
|
|
305
296
|
# (non-Hash, non-Value) value offered for that same attribute — a
|
|
306
297
|
# single-field value object auto-wraps into `{field: nil}` (whose
|
|
307
|
-
#
|
|
298
|
+
# own `nil` is a present key, so `Value.build`'s `apply_defaults`
|
|
308
299
|
# never fills it, and `check_required_fields` refuses it exactly
|
|
309
300
|
# as any other missing required field would); a multi-field value
|
|
310
301
|
# object refuses immediately with the same `value_object_shape`
|
|
311
302
|
# wording an ordinary wrong-shaped scalar already gets. Command
|
|
312
|
-
# arguments are deliberately
|
|
303
|
+
# arguments are deliberately untouched — this is a query-only
|
|
313
304
|
# door; `nil_argument`'s own default-absorbing fallback still
|
|
314
305
|
# governs a null command argument exactly as it always has.
|
|
315
306
|
#
|
|
@@ -49,7 +49,7 @@ module Hecks
|
|
|
49
49
|
end
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
-
#
|
|
52
|
+
# One `with:` source, resolved — pulled out of resolve_mapping
|
|
53
53
|
# because it is a pure function of its own arguments (a literal, a
|
|
54
54
|
# binding, or a name visible in some scope), with no dependency on
|
|
55
55
|
# anything else resolve_mapping's own to_h block is doing.
|
|
@@ -60,10 +60,10 @@ module Hecks
|
|
|
60
60
|
visible = scopes.find { |scope| scope.facts.key?(source) }
|
|
61
61
|
unless visible
|
|
62
62
|
names = scopes.map(&:name).join(" then ")
|
|
63
|
-
#
|
|
63
|
+
# **What is visible, named**. A refusal that only says which name is
|
|
64
64
|
# missing sent a modeler guessing field after field
|
|
65
65
|
# (`number`, `reference`…) at a fan-out row that is addressed
|
|
66
|
-
# by
|
|
66
|
+
# by one key — `account`, the lowercase aggregate — which
|
|
67
67
|
# nothing else in the domain spells out. The names each scope
|
|
68
68
|
# actually offers are the whole diagnosis; the refusal now
|
|
69
69
|
# lists them, scope by scope.
|
|
@@ -80,10 +80,10 @@ module Hecks
|
|
|
80
80
|
# command attributes enter `with:`, while identities become `to:`.
|
|
81
81
|
# `consumed` accumulates across the aggregate-identity and entity-
|
|
82
82
|
# identity steps below, and refuse_unconsumed! at the end reads the
|
|
83
|
-
#
|
|
83
|
+
# final list — an ordering dependency threaded through one shared
|
|
84
84
|
# local. Already leans on private helpers (identity_for,
|
|
85
85
|
# source_receiver_for, command_facts, refuse_unconsumed!) for every
|
|
86
|
-
# piece that
|
|
86
|
+
# piece that is self-contained; what remains is the sequencing
|
|
87
87
|
# itself, which further splitting would only relocate, not remove.
|
|
88
88
|
# rubocop:disable-next Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
89
89
|
def build(registry:, verb:, projected:, explicit:, passthrough: [], source_receiver: nil)
|
|
@@ -99,7 +99,7 @@ module Hecks
|
|
|
99
99
|
rescue UnknownVerb
|
|
100
100
|
return args
|
|
101
101
|
end
|
|
102
|
-
#
|
|
102
|
+
# An entity target has no shape this branch can build — an
|
|
103
103
|
# implicit (unprojected) `to:` is always a bare scalar, the
|
|
104
104
|
# aggregate's own identity alone; an entity command's receiver is
|
|
105
105
|
# `{aggregate:, entities:}`, which needs the entity's own
|
|
@@ -167,13 +167,13 @@ module Hecks
|
|
|
167
167
|
|
|
168
168
|
*entity_names, command_name = command_path.split(".")
|
|
169
169
|
|
|
170
|
-
# A
|
|
171
|
-
# entity command uses ("Head.Rest") — checked
|
|
170
|
+
# A port operation, reached by the same two-segment tail shape an
|
|
171
|
+
# entity command uses ("Head.Rest") — checked first, same order
|
|
172
172
|
# `Dispatcher#dispatch` already resolves a live verb in (an
|
|
173
173
|
# aggregate that declared both a port and an entity of the same
|
|
174
174
|
# name would resolve to the port there too; no domain in this
|
|
175
175
|
# corpus does). A port has no `entities` of its own — the
|
|
176
|
-
#
|
|
176
|
+
# receiver is always the aggregate itself — so `Target#entities`
|
|
177
177
|
# stays empty and `#command` holds the `PortOperation`, which
|
|
178
178
|
# answers `#creates?` (always false) the same way an ordinary
|
|
179
179
|
# `Command` does, letting `source_receiver_for` lift a same-
|
|
@@ -215,37 +215,37 @@ module Hecks
|
|
|
215
215
|
private_class_method :aggregate_aliases
|
|
216
216
|
|
|
217
217
|
# Event.id names the aggregate that emitted the event. It can therefore
|
|
218
|
-
# supply only the receiver of a non-creating command on that
|
|
218
|
+
# supply only the receiver of a non-creating command on that same root;
|
|
219
219
|
# it cannot address another aggregate, invent an entity identity, or turn
|
|
220
220
|
# a creation into a mutation. An explicit projected receiver is resolved
|
|
221
221
|
# first and remains authoritative.
|
|
222
222
|
#
|
|
223
|
-
#
|
|
224
|
-
# back is
|
|
223
|
+
# Answers regardless of `target.entities` — the value this method hands
|
|
224
|
+
# back is only ever the root aggregate's own identity (`source_receiver`
|
|
225
225
|
# carries nothing else: `event.aggregate`/`event.id` name the emitting
|
|
226
|
-
#
|
|
226
|
+
# aggregate, never one of its entities). An entity target's own identity
|
|
227
227
|
# is resolved entirely separately, from `args` (`build`'s own `entity_
|
|
228
228
|
# identities` loop) — this method is never consulted for it, so
|
|
229
229
|
# answering for an entity target does not "invent an entity identity"
|
|
230
|
-
# any more than answering for a plain one invents
|
|
230
|
+
# any more than answering for a plain one invents that identity; it was
|
|
231
231
|
# already the one thing this method has ever supplied. `build`'s own
|
|
232
|
-
#
|
|
232
|
+
# explicit branch (below) already applies this correctly either way
|
|
233
233
|
# (`aggregate_identity ||= inherited_receiver`, entity identities read
|
|
234
234
|
# from `args` regardless) — the caller that actually needed a guard
|
|
235
|
-
# here is the
|
|
235
|
+
# here is the implicit one, above, which has no shape to build a
|
|
236
236
|
# `{aggregate:, entities:}` receiver from an inherited scalar alone,
|
|
237
237
|
# and now guards itself for exactly that reason instead of this method
|
|
238
238
|
# doing it on that caller's behalf and, as a side effect, also refusing
|
|
239
|
-
# the
|
|
239
|
+
# the explicit caller's own legitimate case — confirmed missing until
|
|
240
240
|
# `qa/stress_domains/waybill` (BUG#6) exercised it for the first time
|
|
241
241
|
# anywhere in the corpus: a saga dispatching into a nested entity's own
|
|
242
242
|
# command, inheriting its aggregate receiver from the event that
|
|
243
243
|
# triggered it.
|
|
244
244
|
def source_receiver_for(target, source_receiver)
|
|
245
245
|
return nil unless source_receiver
|
|
246
|
-
# `target.command.creates?`
|
|
246
|
+
# `target.command.creates?` alone misreads every entity command —
|
|
247
247
|
# `Behaviour::Command#creates?`'s own comment: "a verb declared on
|
|
248
|
-
# an
|
|
248
|
+
# an entity always acts on that piece... which means `creates?`
|
|
249
249
|
# answers true for every one of them" (it never sets `@references`
|
|
250
250
|
# the way an aggregate-level command's own `reference_to` does,
|
|
251
251
|
# not because it brings anything new into being). `build`'s own
|
|
@@ -253,7 +253,7 @@ module Hecks
|
|
|
253
253
|
# 20-odd lines below, already reads `creates?` correctly for
|
|
254
254
|
# exactly this reason — an entity command is never a genuine
|
|
255
255
|
# creation, whatever `creates?` alone answers — so this checks the
|
|
256
|
-
#
|
|
256
|
+
# same compound condition instead of the bare, misleading half of
|
|
257
257
|
# it. Left unfixed, an entity target's own receiver was refused
|
|
258
258
|
# here even after this method stopped refusing on `target.entities`
|
|
259
259
|
# alone — the second half of BUG#6's own fix.
|