hecks 2.7.0 → 2.9.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/exe/hecks +11 -0
- data/lib/hecks/adapters/driven/claude_code.rb +4 -104
- data/lib/hecks/adapters/driven/d1.rb +44 -237
- data/lib/hecks/adapters/driven/folder.rb +17 -76
- data/lib/hecks/adapters/driven/google_authentication.rb +15 -49
- data/lib/hecks/adapters/driven/governance_authorization.rb +23 -70
- data/lib/hecks/adapters/driven/heki/journal.rb +8 -41
- data/lib/hecks/adapters/driven/heki/saga_store.rb +12 -54
- data/lib/hecks/adapters/driven/heki/snapshot.rb +4 -12
- data/lib/hecks/adapters/driven/heki.rb +13 -42
- data/lib/hecks/adapters/driven/identity_registry.rb +3 -14
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +9 -26
- data/lib/hecks/adapters/driven/in_process_key_vault.rb +2 -14
- data/lib/hecks/adapters/driven/lambda/client.rb +10 -51
- data/lib/hecks/adapters/driven/lambda.rb +10 -84
- data/lib/hecks/adapters/driven/local_storage.rb +5 -40
- data/lib/hecks/adapters/driven/memory.rb +18 -99
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +5 -21
- data/lib/hecks/adapters/driven/postgres/codec.rb +6 -28
- data/lib/hecks/adapters/driven/postgres/outbox.rb +3 -12
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +4 -30
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +34 -108
- data/lib/hecks/adapters/driven/postgres.rb +145 -134
- data/lib/hecks/adapters/driven/postgres_era.adapter +4 -15
- data/lib/hecks/adapters/driven/prism.rb +7 -30
- data/lib/hecks/adapters/driven/sql_query_builder.rb +21 -121
- data/lib/hecks/adapters/driven/sqlite/codec.rb +8 -40
- data/lib/hecks/adapters/driven/sqlite/projection.rb +26 -82
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +40 -84
- data/lib/hecks/adapters/driven/sqlite.rb +128 -65
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +15 -37
- data/lib/hecks/adapters/driven.rb +5 -36
- data/lib/hecks/adapters/driving/github_webhook.rb +8 -76
- data/lib/hecks/behaviors/dsl.rb +7 -25
- data/lib/hecks/behaviors/expectations.rb +32 -142
- data/lib/hecks/behaviors/ir.rb +7 -20
- data/lib/hecks/behaviors/rspec.rb +6 -17
- data/lib/hecks/behaviors/runner.rb +10 -34
- data/lib/hecks/behaviors.rb +4 -8
- data/lib/hecks/bluebook/aggregate.rb +9 -75
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +9 -38
- data/lib/hecks/bluebook/assembly/build.rb +4 -15
- data/lib/hecks/bluebook/assembly/contract.rb +15 -136
- data/lib/hecks/bluebook/assembly/contracts.rb +54 -174
- data/lib/hecks/bluebook/assembly/marks.rb +31 -223
- data/lib/hecks/bluebook/assembly/specializer.rb +6 -52
- data/lib/hecks/bluebook/assembly.rb +6 -45
- data/lib/hecks/bluebook/attribute.rb +12 -61
- data/lib/hecks/bluebook/behaviour/aggregate.rb +10 -39
- data/lib/hecks/bluebook/behaviour/attribute.rb +3 -14
- data/lib/hecks/bluebook/behaviour/chapter.rb +17 -42
- data/lib/hecks/bluebook/behaviour/command.rb +14 -66
- data/lib/hecks/bluebook/behaviour/domain_port.rb +4 -20
- data/lib/hecks/bluebook/behaviour/entity.rb +7 -27
- data/lib/hecks/bluebook/behaviour/hexagon.rb +23 -41
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +15 -40
- data/lib/hecks/bluebook/behaviour/policy.rb +23 -52
- data/lib/hecks/bluebook/behaviour/process_manager.rb +30 -57
- data/lib/hecks/bluebook/behaviour/read_model.rb +38 -26
- data/lib/hecks/bluebook/behaviour/traits.rb +27 -64
- data/lib/hecks/bluebook/behaviour/value_object.rb +5 -24
- data/lib/hecks/bluebook/capabilities.rb +17 -49
- data/lib/hecks/bluebook/chapter.rb +10 -48
- data/lib/hecks/bluebook/command.rb +23 -81
- data/lib/hecks/bluebook/domain_port.rb +14 -63
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +50 -229
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +50 -377
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +33 -204
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +17 -65
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +86 -708
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +56 -170
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +74 -546
- data/lib/hecks/bluebook/dsl/const_shim.rb +8 -41
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +12 -87
- data/lib/hecks/bluebook/dsl/entity_builder.rb +44 -330
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +20 -269
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +26 -189
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +13 -88
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +6 -20
- data/lib/hecks/bluebook/dsl/policy_builder.rb +16 -88
- data/lib/hecks/bluebook/dsl/port_builder.rb +2 -6
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +22 -96
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +31 -184
- data/lib/hecks/bluebook/dsl/query_builder.rb +11 -42
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +75 -201
- data/lib/hecks/bluebook/dsl/rule_reference.rb +22 -163
- data/lib/hecks/bluebook/dsl/translation_builder.rb +28 -114
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +10 -124
- data/lib/hecks/bluebook/dsl/word_gate.rb +16 -162
- data/lib/hecks/bluebook/dsl/world_builder.rb +19 -106
- data/lib/hecks/bluebook/entity.rb +13 -71
- data/lib/hecks/bluebook/expression/ast_json.rb +24 -190
- data/lib/hecks/bluebook/expression/ast_reader.rb +12 -54
- data/lib/hecks/bluebook/expression/canonical_form.rb +11 -88
- data/lib/hecks/bluebook/expression/evaluator.rb +47 -295
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +30 -147
- data/lib/hecks/bluebook/expression/resolver.rb +37 -660
- data/lib/hecks/bluebook/hexagon.rb +5 -28
- data/lib/hecks/bluebook/lifecycle.rb +5 -16
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -7
- data/lib/hecks/bluebook/meta_validator/judge.rb +111 -361
- data/lib/hecks/bluebook/meta_validator/plan.rb +33 -162
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -7
- data/lib/hecks/bluebook/meta_validator/readings.rb +56 -342
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -176
- data/lib/hecks/bluebook/meta_validator/shapes.rb +54 -268
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +47 -308
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +7 -43
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -18
- data/lib/hecks/bluebook/meta_validator.rb +79 -349
- data/lib/hecks/bluebook/model_check/client_profile.rb +10 -91
- data/lib/hecks/bluebook/model_check.rb +102 -600
- data/lib/hecks/bluebook/pattern_subset.rb +11 -69
- data/lib/hecks/bluebook/process_manager.rb +13 -83
- data/lib/hecks/bluebook/project_loader.rb +3 -39
- data/lib/hecks/bluebook/project_register.rb +12 -60
- data/lib/hecks/bluebook/query.rb +9 -48
- data/lib/hecks/bluebook/read_model.rb +57 -81
- data/lib/hecks/bluebook/reference.rb +7 -41
- data/lib/hecks/bluebook/smoke_test.rb +8 -98
- data/lib/hecks/bluebook/synthesizer.rb +13 -62
- data/lib/hecks/bluebook/translation.rb +26 -62
- data/lib/hecks/bluebook/value_object.rb +11 -36
- data/lib/hecks/bluebook.rb +5 -22
- data/lib/hecks/cache_dir.rb +84 -0
- data/lib/hecks/cli/document.rb +86 -0
- data/lib/hecks/cli/ir.rb +45 -0
- data/lib/hecks/cli/mcp.rb +31 -0
- data/lib/hecks/cli/mcp_door.rb +366 -0
- data/lib/hecks/cli/model_check.rb +203 -0
- data/lib/hecks/cli/project_cli.rb +90 -0
- data/lib/hecks/cli/project_diagrams.rb +36 -0
- data/lib/hecks/cli/run.rb +173 -0
- data/lib/hecks/cli/smoke_test.rb +45 -0
- data/lib/hecks/cli/stores.rb +53 -0
- data/lib/hecks/cli.rb +160 -0
- data/lib/hecks/construct.rb +8 -34
- data/lib/hecks/deploy/bluebook/deploy.bluebook +33 -142
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +2 -9
- data/lib/hecks/embryonaut_bluebook/lock.rb +3 -35
- data/lib/hecks/embryonaut_bluebook/shape.rb +1 -17
- data/lib/hecks/embryonaut_bluebook/vendor.rb +8 -53
- data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +4 -19
- data/lib/hecks/embryonaut_bluebook.rb +4 -71
- data/lib/hecks/facade/cli_door.rb +24 -106
- data/lib/hecks/facade/cli_runner.rb +28 -172
- data/lib/hecks/facade/command_request.rb +13 -33
- data/lib/hecks/facade/handle.rb +34 -121
- data/lib/hecks/facade/json_door.rb +31 -146
- data/lib/hecks/facade/surface/aggregate_door.rb +39 -104
- data/lib/hecks/facade/surface/chapter.rb +10 -54
- data/lib/hecks/facade/surface.rb +8 -23
- data/lib/hecks/facade.rb +10 -25
- data/lib/hecks/forms/app.rb +15 -70
- data/lib/hecks/forms/command_form_renderer.rb +10 -29
- data/lib/hecks/forms/field_renderer.rb +17 -156
- data/lib/hecks/forms/field_shape.rb +30 -222
- data/lib/hecks/forms/html.rb +12 -47
- data/lib/hecks/forms/index_renderer.rb +1 -4
- data/lib/hecks/forms/page.rb +3 -9
- data/lib/hecks/forms/params.rb +14 -60
- data/lib/hecks/forms/port_argument.rb +2 -19
- data/lib/hecks/forms/query_form_renderer.rb +4 -79
- data/lib/hecks/forms/record_renderer.rb +8 -75
- data/lib/hecks/forms/record_table.rb +7 -50
- data/lib/hecks/forms/reference_options.rb +3 -30
- data/lib/hecks/forms/value_object_shape.rb +8 -35
- data/lib/hecks/forms.rb +5 -33
- data/lib/hecks/fqn.rb +5 -12
- data/lib/hecks/framework/bluebook/compliance.bluebook +16 -65
- data/lib/hecks/framework/bluebook/console_settings.bluebook +47 -134
- data/lib/hecks/framework/bluebook/framework.hecksagon +5 -14
- data/lib/hecks/framework/bluebook/governance.bluebook +13 -34
- data/lib/hecks/framework/bluebook/identity.bluebook +11 -26
- data/lib/hecks/framework/bluebook/privacy.bluebook +8 -33
- data/lib/hecks/framework.rb +11 -80
- data/lib/hecks/freezer.rb +5 -39
- data/lib/hecks/grammar/expression.bluebook +15 -40
- data/lib/hecks/grammar/translation.bluebook +3 -6
- data/lib/hecks/grammar.rb +16 -41
- data/lib/hecks/ir.rb +14 -83
- data/lib/hecks/language/adapter.bluebook +2 -7
- data/lib/hecks/language/bluebook/aggregate.bluebook +56 -221
- data/lib/hecks/language/bluebook/attaches/paging.bluebook +6 -15
- data/lib/hecks/language/bluebook/bluebook.bluebook +15 -83
- data/lib/hecks/language/bluebook/bluebook.hecksagon +3 -15
- data/lib/hecks/language/bluebook/command.bluebook +27 -144
- data/lib/hecks/language/bluebook/entity.bluebook +24 -97
- data/lib/hecks/language/bluebook/policy.bluebook +16 -67
- data/lib/hecks/language/bluebook/process_manager.bluebook +28 -151
- data/lib/hecks/language/bluebook/projection.bluebook +126 -60
- data/lib/hecks/language/bluebook/query.bluebook +12 -43
- data/lib/hecks/language/bluebook/shape.bluebook +23 -73
- data/lib/hecks/language/bluebook/syntax.bluebook +51 -262
- data/lib/hecks/language/bluebook/vocabulary.bluebook +98 -197
- data/lib/hecks/language/hecksagon/adapter_binding.bluebook +3 -10
- data/lib/hecks/language/hecksagon/domain_port.bluebook +4 -19
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +6 -25
- data/lib/hecks/language/hecksagon/port_operation.bluebook +3 -14
- data/lib/hecks/language/oidc.json +35 -0
- data/lib/hecks/language/port.bluebook +4 -17
- data/lib/hecks/language/translation/translation.bluebook +3 -9
- data/lib/hecks/language/translation/translation_aggregate.bluebook +5 -24
- data/lib/hecks/language/world/wiring.bluebook +1 -3
- data/lib/hecks/language/world/world.bluebook +5 -7
- data/lib/hecks/literal.rb +8 -50
- data/lib/hecks/mcp_door_scope.rb +95 -0
- data/lib/hecks/mcp_stdio_guard.rb +4 -41
- data/lib/hecks/naming.rb +29 -137
- data/lib/hecks/ports/access_control.rb +7 -23
- data/lib/hecks/ports/agent/answers.rb +5 -23
- data/lib/hecks/ports/agent.rb +47 -172
- data/lib/hecks/ports/authentication.rb +12 -39
- data/lib/hecks/ports/authorization.rb +16 -43
- data/lib/hecks/ports/clock.rb +5 -45
- data/lib/hecks/ports/extraction.rb +7 -12
- data/lib/hecks/ports/identity_assignment.rb +8 -29
- data/lib/hecks/ports/identity_generation.rb +6 -29
- data/lib/hecks/ports/identity_resolution.rb +2 -10
- data/lib/hecks/ports/key_vault.rb +9 -45
- data/lib/hecks/ports/persistence/append_only.rb +62 -65
- data/lib/hecks/ports/persistence/binding_policy.rb +4 -14
- data/lib/hecks/ports/persistence/codec_boundary.rb +20 -80
- data/lib/hecks/ports/persistence/null_saga_store.rb +4 -20
- data/lib/hecks/ports/persistence/plugin.rb +13 -42
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +38 -141
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +15 -115
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +16 -84
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +7 -26
- data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -22
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +55 -181
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/events.rb +77 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +12 -122
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +28 -128
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +73 -474
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +31 -124
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +45 -219
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +13 -108
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +18 -75
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +9 -49
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +6 -123
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +23 -55
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +14 -72
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +7 -16
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +14 -115
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +13 -45
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +127 -554
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +13 -63
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +2 -17
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +3 -14
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +13 -58
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -17
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -26
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +9 -25
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +22 -122
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +34 -123
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +5 -12
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +3 -9
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -14
- data/lib/hecks/ports/persistence/plugins/era.rb +4 -23
- data/lib/hecks/ports/persistence/postgres_dump/connection.rb +7 -23
- data/lib/hecks/ports/persistence/postgres_dump.rb +4 -35
- data/lib/hecks/ports/persistence/remote_runtime.rb +2 -24
- data/lib/hecks/ports/persistence/repository_factory.rb +8 -27
- data/lib/hecks/ports/persistence/state_codec.rb +23 -103
- data/lib/hecks/ports/persistence.rb +7 -14
- data/lib/hecks/ports/projection.rb +42 -84
- data/lib/hecks/ports/query/in_memory.rb +10 -58
- data/lib/hecks/ports/query/ordering.rb +11 -32
- data/lib/hecks/ports/query.rb +14 -37
- data/lib/hecks/ports.rb +2 -8
- data/lib/hecks/projections/bootstrap_table.rb +11 -43
- data/lib/hecks/projections/deploy/fargate/assembly.rb +2 -15
- data/lib/hecks/projections/deploy/fargate/cdn.rb +1 -48
- data/lib/hecks/projections/deploy/fargate/cdn_default.rb +2 -11
- data/lib/hecks/projections/deploy/fargate/check.rb +5 -14
- data/lib/hecks/projections/deploy/fargate/containers.rb +3 -78
- data/lib/hecks/projections/deploy/fargate/extras.rb +3 -44
- data/lib/hecks/projections/deploy/fargate/monitoring.rb +0 -44
- data/lib/hecks/projections/deploy/fargate/settings.rb +2 -54
- data/lib/hecks/projections/deploy/fargate/yaml.rb +13 -58
- data/lib/hecks/projections/deploy/fargate.rb +35 -202
- data/lib/hecks/projections/deploy/lambda.rb +151 -679
- data/lib/hecks/projections/deploy/preview/access_yaml.rb +1 -33
- data/lib/hecks/projections/deploy/preview/containers.rb +6 -111
- data/lib/hecks/projections/deploy/preview/routing_yaml.rb +4 -56
- data/lib/hecks/projections/deploy/preview/script.rb +2 -14
- data/lib/hecks/projections/deploy/preview/settings.rb +3 -33
- data/lib/hecks/projections/deploy/preview/task_yaml.rb +2 -38
- data/lib/hecks/projections/deploy/preview/template.rb +2 -58
- data/lib/hecks/projections/deploy/preview.rb +2 -67
- data/lib/hecks/projections/deploy/scripts/settings.rb +3 -25
- data/lib/hecks/projections/deploy/scripts.rb +4 -57
- data/lib/hecks/projections/deploy/shared.rb +47 -146
- data/lib/hecks/projections/deploy/smoke.rb +4 -61
- data/lib/hecks/projections/deploy/template_diff/comparer.rb +3 -25
- data/lib/hecks/projections/deploy/template_diff/loader.rb +2 -11
- data/lib/hecks/projections/deploy/template_diff.rb +5 -57
- data/lib/hecks/projections/diagrams.rb +20 -582
- data/lib/hecks/projections/glossary/html.rb +14 -130
- data/lib/hecks/projections/glossary/markdown.rb +8 -69
- data/lib/hecks/projections/glossary/mermaid.rb +9 -66
- data/lib/hecks/projections/glossary/sensitivity.rb +5 -34
- data/lib/hecks/projections/glossary/sentences.rb +14 -159
- data/lib/hecks/projections/glossary.rb +30 -222
- data/lib/hecks/projections/ir.rb +3 -10
- data/lib/hecks/projections/model/deviations.rb +15 -42
- data/lib/hecks/projections/model.rb +12 -51
- data/lib/hecks/projections/oidc.rb +12 -56
- data/lib/hecks/projections/parser_table.rb +9 -28
- data/lib/hecks/projections/reference.rb +9 -16
- data/lib/hecks/projections/rust_vocabulary.rb +12 -250
- data/lib/hecks/projections/shape.rb +3 -29
- data/lib/hecks/projections/statements.rb +7 -86
- data/lib/hecks/projections/vocabulary.rb +6 -22
- data/lib/hecks/projections.rb +2 -16
- data/lib/hecks/projector/cli_projector.rb +60 -375
- data/lib/hecks/projector/docs_projector.rb +10 -201
- data/lib/hecks/projector/exporter.rb +46 -138
- data/lib/hecks/projector/ir_projector.rb +2 -9
- data/lib/hecks/projector/narrate_projector.rb +15 -189
- data/lib/hecks/projector/target.rb +13 -76
- data/lib/hecks/projector.rb +30 -121
- data/lib/hecks/query_specification/common/comparators.rb +5 -26
- data/lib/hecks/query_specification/common/comparison.rb +35 -143
- data/lib/hecks/query_specification/common/dsl.rb +8 -24
- data/lib/hecks/query_specification/common/null_policy.rb +20 -70
- data/lib/hecks/query_specification/common/options.rb +2 -6
- data/lib/hecks/query_specification/common/where_clause.rb +2 -10
- data/lib/hecks/query_specification/field_path.rb +37 -77
- data/lib/hecks/query_specification/hop_path.rb +12 -76
- data/lib/hecks/release/gem_pin.rb +2 -26
- data/lib/hecks/release/runner/ci_publisher.rb +93 -0
- data/lib/hecks/release/runner/commands.rb +66 -0
- data/lib/hecks/release/runner/console.rb +52 -0
- data/lib/hecks/release/runner/gem_publisher.rb +43 -0
- data/lib/hecks/release/runner/git.rb +48 -0
- data/lib/hecks/release/runner/npm_publisher.rb +75 -0
- data/lib/hecks/release/runner/preflight.rb +104 -0
- data/lib/hecks/release/runner/published.rb +55 -0
- data/lib/hecks/release/runner/tagger.rb +80 -0
- data/lib/hecks/release/runner.rb +215 -0
- data/lib/hecks/rendering.rb +4 -26
- data/lib/hecks/router/namespace_installer.rb +12 -36
- data/lib/hecks/router.rb +4 -8
- data/lib/hecks/runtime/aggregate_lock.rb +8 -33
- data/lib/hecks/runtime/boot_gates.rb +3 -18
- data/lib/hecks/runtime/caller.rb +19 -59
- data/lib/hecks/runtime/capability_graph.rb +5 -21
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +23 -160
- data/lib/hecks/runtime/command_interpreter.rb +125 -307
- data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
- data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
- data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
- data/lib/hecks/runtime/command_rules/emission.rb +8 -21
- data/lib/hecks/runtime/command_rules/references.rb +23 -202
- data/lib/hecks/runtime/dependency_planning.rb +30 -102
- data/lib/hecks/runtime/dispatcher.rb +82 -241
- data/lib/hecks/runtime/entity_element.rb +40 -462
- data/lib/hecks/runtime/entity_interpreter.rb +65 -220
- data/lib/hecks/runtime/errors.rb +32 -115
- data/lib/hecks/runtime/event.rb +3 -25
- data/lib/hecks/runtime/identity.rb +18 -154
- data/lib/hecks/runtime/instance.rb +18 -150
- data/lib/hecks/runtime/interpreting.rb +11 -70
- data/lib/hecks/runtime/invocation.rb +28 -164
- data/lib/hecks/runtime/loader.rb +48 -186
- data/lib/hecks/runtime/outbox.rb +38 -261
- data/lib/hecks/runtime/policy_interpreter.rb +38 -200
- data/lib/hecks/runtime/port_operation_interpreter.rb +26 -87
- data/lib/hecks/runtime/query_interpreter.rb +45 -187
- data/lib/hecks/runtime/reaction_invocation.rb +36 -138
- data/lib/hecks/runtime/read_model_interpreter.rb +139 -208
- data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
- data/lib/hecks/runtime/reference_hop.rb +8 -81
- data/lib/hecks/runtime/refusal_wording.rb +8 -89
- data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
- data/lib/hecks/runtime/registry/verification.rb +52 -225
- data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
- data/lib/hecks/runtime/registry.rb +103 -244
- data/lib/hecks/runtime/remote_dispatcher.rb +37 -152
- data/lib/hecks/runtime/routing.rb +4 -15
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
- data/lib/hecks/runtime/saga_interpreter.rb +79 -263
- data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
- data/lib/hecks/runtime/tenant_check.rb +6 -68
- data/lib/hecks/runtime/tenant_scope.rb +3 -28
- data/lib/hecks/runtime/value/admission.rb +16 -67
- data/lib/hecks/runtime/value/coercion.rb +155 -641
- data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
- data/lib/hecks/runtime/value.rb +12 -84
- data/lib/hecks/runtime.rb +17 -65
- data/lib/hecks/storehouse.rb +146 -567
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +8 -32
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
- data/lib/hecks/vendoring/git_environment.rb +2 -8
- data/lib/hecks/vendoring/git_source.rb +4 -10
- data/lib/hecks/vendoring.rb +7 -46
- data/lib/hecks/version.rb +3 -14
- data/lib/hecks/vocabulary.rb +5 -0
- data/lib/hecks.rb +32 -82
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
- metadata +30 -59
- data/lib/hecks/bench/cli.rb +0 -74
- data/lib/hecks/bench/environment.rb +0 -100
- data/lib/hecks/bench/postgres_probe.rb +0 -41
- data/lib/hecks/bench/report.rb +0 -116
- data/lib/hecks/bench/ruby_runner.rb +0 -151
- data/lib/hecks/bench/run.rb +0 -55
- data/lib/hecks/bench/rust_runner.rb +0 -165
- data/lib/hecks/bench/stats.rb +0 -75
- data/lib/hecks/bench/suite.rb +0 -172
- data/lib/hecks/bench/workload.rb +0 -137
- data/lib/hecks/bench.rb +0 -28
- data/lib/hecks/codemod.rb +0 -443
- data/lib/hecks/corpus.rb +0 -479
- data/lib/hecks/doc/reference.rb +0 -585
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -684
- data/lib/hecks/fuzzing/combination_miner.rb +0 -178
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +0 -469
- data/lib/hecks/fuzzing/coverage_campaign.rb +0 -161
- data/lib/hecks/fuzzing/differential.rb +0 -192
- data/lib/hecks/fuzzing/domain_generator.rb +0 -871
- data/lib/hecks/fuzzing/era_boundary.rb +0 -154
- data/lib/hecks/fuzzing/form_census.rb +0 -285
- data/lib/hecks/fuzzing/generated_domain_check.rb +0 -171
- data/lib/hecks/fuzzing/invalid_value_generator.rb +0 -125
- data/lib/hecks/fuzzing/isolated_boot.rb +0 -658
- data/lib/hecks/fuzzing/nondeterministic.rb +0 -79
- data/lib/hecks/fuzzing/persistence_parity.rb +0 -253
- data/lib/hecks/fuzzing/properties/corrections.rb +0 -125
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +0 -688
- data/lib/hecks/fuzzing/properties/guards.rb +0 -398
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +0 -399
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +0 -137
- data/lib/hecks/fuzzing/properties/outbox.rb +0 -180
- data/lib/hecks/fuzzing/properties/querying.rb +0 -260
- data/lib/hecks/fuzzing/properties.rb +0 -267
- data/lib/hecks/fuzzing/qa_settings.rb +0 -164
- data/lib/hecks/fuzzing/replay.rb +0 -930
- data/lib/hecks/fuzzing/rotation_priority.rb +0 -114
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +0 -139
- data/lib/hecks/fuzzing/self_consistency.rb +0 -825
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +0 -532
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +0 -171
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +0 -127
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +0 -116
- data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +0 -138
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +0 -344
- data/lib/hecks/fuzzing/sequence_generator.rb +0 -311
- data/lib/hecks/fuzzing/shrinker.rb +0 -309
- data/lib/hecks/fuzzing/structural_skips.rb +0 -53
- data/lib/hecks/fuzzing/sweep_depth.rb +0 -61
- data/lib/hecks/fuzzing/target_capabilities.rb +0 -255
- data/lib/hecks/fuzzing/value_generator.rb +0 -340
- data/lib/hecks/fuzzing.rb +0 -23
- data/lib/hecks/grammar/evolve.rb +0 -524
- data/lib/hecks/query_ir.rb +0 -482
|
@@ -11,36 +11,22 @@ require_relative "value"
|
|
|
11
11
|
|
|
12
12
|
module Hecks
|
|
13
13
|
module Runtime
|
|
14
|
-
# Answers one declared aggregate or entity query against a
|
|
15
|
-
#
|
|
16
|
-
# when the store can answer directly, falling back to interpreting
|
|
17
|
-
# wheres/order_by/limit/offset over every loaded record itself.
|
|
18
|
-
# #reference_call/#reference_interpret are a separate, deliberately
|
|
19
|
-
# naive re-implementation of the same evaluation, used only as the
|
|
20
|
-
# fuzzer's oracle to catch divergence between adapters and this
|
|
21
|
-
# interpreter's own native path.
|
|
14
|
+
# Answers one declared aggregate or entity query against a repository, using a
|
|
15
|
+
# native adapter hook when the store has one and interpreting the query otherwise.
|
|
22
16
|
class QueryInterpreter
|
|
23
17
|
attr_reader :registry
|
|
24
18
|
|
|
25
|
-
# @param registry [Runtime::Registry] the booted registry queries are answered
|
|
26
|
-
# against
|
|
27
19
|
def initialize(registry)
|
|
28
20
|
@registry = registry
|
|
29
21
|
end
|
|
30
22
|
|
|
31
23
|
# Answers one declared aggregate or entity query, preferring a native adapter
|
|
32
|
-
# hook and falling back to interpreting
|
|
24
|
+
# hook and falling back to interpreting it over every loaded record.
|
|
33
25
|
#
|
|
34
|
-
# @param
|
|
35
|
-
# @
|
|
36
|
-
# @param query_name [String] the query's declared name, or an entity query's
|
|
37
|
-
# dotted `"Entity.Query"` name
|
|
38
|
-
# @param args [Hash{Symbol => Object}] the query's declared arguments
|
|
39
|
-
# @return [Array<Hash>] one frozen Hash per matching record, its state with
|
|
40
|
-
# `:id` merged in last
|
|
26
|
+
# @param query_name [String] a declared name, or an entity query's `"Entity.Query"`
|
|
27
|
+
# @return [Array<Hash>] one frozen Hash per matching record, `:id` merged in last
|
|
41
28
|
# @raise [Runtime::UnknownVerb] if `query_name` names no declared query
|
|
42
|
-
# @raise [Runtime::TypeMismatch] if an argument cannot be coerced to its
|
|
43
|
-
# declared type
|
|
29
|
+
# @raise [Runtime::TypeMismatch] if an argument cannot be coerced to its type
|
|
44
30
|
# @raise [Runtime::WiringError] if the aggregate's repository cannot be resolved
|
|
45
31
|
def call(domain, aggregate, query_name, args)
|
|
46
32
|
return entity_rows(domain, aggregate, query_name, args) if query_name.include?(".")
|
|
@@ -48,67 +34,33 @@ module Hecks
|
|
|
48
34
|
declared = declared_query(aggregate, query_name)
|
|
49
35
|
args = normalize_args(aggregate, declared, args)
|
|
50
36
|
declared = TenantScope.apply(declared, args)
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
# the outer query. It does not reach the hop's own inner
|
|
54
|
-
# sub-query against the target aggregate — a hop's target may
|
|
55
|
-
# not even declare the same tenant boundary, and propagating one
|
|
56
|
-
# aggregate's tenant scope onto an unrelated aggregate's own
|
|
57
|
-
# query is a real design question of its own, not answered here.
|
|
37
|
+
# Applied after TenantScope so its tenant clause rides through as an ordinary
|
|
38
|
+
# local clause; it is deliberately not propagated into the hop's inner query.
|
|
58
39
|
declared = ReferenceHop.apply(declared, args, registry: @registry, domain: domain, aggregate: aggregate)
|
|
59
40
|
|
|
60
41
|
repository = @registry.repository(domain, aggregate)
|
|
61
|
-
# `registry:`
|
|
62
|
-
# can look its target aggregate up — see
|
|
63
|
-
# QuerySpecification::Common::Comparison#none_in_state? for the
|
|
64
|
-
# comparator itself, and for why an ordinary aggregate-level
|
|
65
|
-
# Memory query needs the registry passed explicitly rather than
|
|
66
|
-
# closing over an instance variable.
|
|
42
|
+
# `registry:` lets a `none_in_state` where-clause look up its target aggregate.
|
|
67
43
|
if (native = Ports::Query.execute(repository, declared, args,
|
|
68
44
|
context: { domain: domain, aggregate: aggregate, registry: @registry }))
|
|
69
45
|
records = native
|
|
70
|
-
# `
|
|
71
|
-
# Instance#to_h
|
|
72
|
-
#
|
|
73
|
-
# wrapped value sitting in `record.state[:id]` already; merging
|
|
74
|
-
# it over a `{id:}.merge(state)` would let it silently
|
|
75
|
-
# clobber the correct bare identity this row is supposed to
|
|
76
|
-
# carry.
|
|
77
|
-
# A query row is an answer, not a handle. Mutating one edits
|
|
78
|
-
# nobody's state and silently disagrees with the store.
|
|
46
|
+
# `id` merges last so an aggregate attribute named `id` cannot clobber the
|
|
47
|
+
# bare identity (see Instance#to_h). Rows are frozen: a mutated row would
|
|
48
|
+
# edit nobody's state and disagree with the store.
|
|
79
49
|
return Freezer.deep(records.map { |record| record.state.merge(id: record.id) })
|
|
80
50
|
end
|
|
81
51
|
|
|
82
52
|
Freezer.deep(interpret(repository.all, declared, args, domain: domain))
|
|
83
53
|
end
|
|
84
54
|
|
|
85
|
-
# The reference answer
|
|
86
|
-
#
|
|
87
|
-
# generated ask through both paths and treats a difference as a
|
|
88
|
-
# finding: the differential gate the retired cross-runtime harness
|
|
89
|
-
# should always have been, aimed where the divergence actually
|
|
90
|
-
# lives — between the engines inside this one runtime.
|
|
55
|
+
# The reference answer: this interpreter's own evaluation, never a native hook.
|
|
56
|
+
# The fuzzer's query oracle diffs it against #call.
|
|
91
57
|
#
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
# partition/fold/in-clause. Sharing that algorithm would have made
|
|
95
|
-
# this oracle blind to exactly the code the hop feature adds: every
|
|
96
|
-
# phase of a shared fold would still get diffed against the native
|
|
97
|
-
# adapters, but the fold itself — the empty candidate set, a
|
|
98
|
-
# duplicate id, a dangling reference, a chain's inside-out
|
|
99
|
-
# resolution order — would only ever be compared against itself.
|
|
58
|
+
# Hop clauses use a deliberately naive walk (reference_where_holds?), not
|
|
59
|
+
# ReferenceHop's fold; sharing that would blind the oracle to it.
|
|
100
60
|
#
|
|
101
|
-
# @
|
|
102
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate the query is declared on
|
|
103
|
-
# @param query_name [String] the query's declared name, or an entity query's
|
|
104
|
-
# dotted `"Entity.Query"` name
|
|
105
|
-
# @param args [Hash{Symbol => Object}] the query's declared arguments
|
|
106
|
-
# @return [Array<Hash>] one Hash per matching record, its state with `:id`
|
|
107
|
-
# merged in last
|
|
61
|
+
# @return [Array<Hash>] one Hash per matching record, `:id` merged in last
|
|
108
62
|
# @raise [Runtime::UnknownVerb] if `query_name` names no declared query
|
|
109
|
-
# @raise [Runtime::TypeMismatch] if an argument cannot be coerced to its
|
|
110
|
-
# declared type
|
|
111
|
-
# @raise [Runtime::WiringError] if the aggregate's repository cannot be resolved
|
|
63
|
+
# @raise [Runtime::TypeMismatch] if an argument cannot be coerced to its type
|
|
112
64
|
def reference_call(domain, aggregate, query_name, args)
|
|
113
65
|
return entity_rows(domain, aggregate, query_name, args) if query_name.include?(".")
|
|
114
66
|
|
|
@@ -130,25 +82,15 @@ module Hecks
|
|
|
130
82
|
def interpret(records, declared, args, domain: nil)
|
|
131
83
|
matched = records.select { |r| declared.wheres.all? { |w| where_holds?(w, r, args, domain: domain) } }
|
|
132
84
|
ordered = ordered(matched, declared.order_by, declared.null_semantics)
|
|
133
|
-
#
|
|
134
|
-
# OFFSET m`, and the order Ports::Query::InMemory#execute already
|
|
135
|
-
# applies (see that file's own comment). Without reading
|
|
136
|
-
# `declared.offset`, offset would silently vanish for any query
|
|
137
|
-
# answered here, not just come out reversed.
|
|
85
|
+
# Offset before limit, as SQL's `LIMIT n OFFSET m` and Ports::Query::InMemory do.
|
|
138
86
|
skipped = declared.offset ? ordered.drop(resolve_query_value(declared.offset.value, args).to_i) : ordered
|
|
139
87
|
capped = declared.limit ? skipped.first(resolve_query_value(declared.limit.value, args).to_i) : skipped
|
|
140
88
|
|
|
141
|
-
# id last — see the native-path comment above; same clobbering
|
|
142
|
-
# risk for the in-memory reference interpreter's own rows.
|
|
143
89
|
capped.map { |r| r.state.merge(id: r.id) }
|
|
144
90
|
end
|
|
145
91
|
|
|
146
|
-
# `interpret`
|
|
147
|
-
#
|
|
148
|
-
# reference is answered by reference_where_holds? instead of the
|
|
149
|
-
# plain FieldPath.dig(record, field) `where_holds?` uses (which
|
|
150
|
-
# has no concept of a reference at all — it would just read the
|
|
151
|
-
# raw id straight off the record and compare that).
|
|
92
|
+
# Twin of `interpret` for reference_call: hop clauses go through
|
|
93
|
+
# reference_where_holds?, which `where_holds?` cannot follow.
|
|
152
94
|
def reference_interpret(records, declared, args, domain:, shape:)
|
|
153
95
|
matched = records.select do |r|
|
|
154
96
|
declared.wheres.all? do |w|
|
|
@@ -156,24 +98,14 @@ module Hecks
|
|
|
156
98
|
end
|
|
157
99
|
end
|
|
158
100
|
ordered = ordered(matched, declared.order_by, declared.null_semantics)
|
|
159
|
-
# **Offset first, then limit** — same fix, same reasoning, as
|
|
160
|
-
# #interpret's own rows above.
|
|
161
101
|
skipped = declared.offset ? ordered.drop(resolve_query_value(declared.offset.value, args).to_i) : ordered
|
|
162
102
|
capped = declared.limit ? skipped.first(resolve_query_value(declared.limit.value, args).to_i) : skipped
|
|
163
103
|
|
|
164
|
-
# id last — same reasoning, same fix, as interpret's own rows.
|
|
165
104
|
capped.map { |r| r.state.merge(id: r.id) }
|
|
166
105
|
end
|
|
167
106
|
|
|
168
|
-
#
|
|
169
|
-
#
|
|
170
|
-
# field out of whatever it actually points at. A nil reference,
|
|
171
|
-
# or one that resolves to nothing (a dangling id), makes the
|
|
172
|
-
# whole clause false outright, whatever the comparator — "points
|
|
173
|
-
# at a client that is not active" is false for a proposal with no
|
|
174
|
-
# client at all, the same way it is false for one whose client
|
|
175
|
-
# really is active; falling through to holds?(clause, nil, args)
|
|
176
|
-
# instead would answer `ne` wrong (nil != "active" is true).
|
|
107
|
+
# Walks each reference by hand. A nil or dangling reference makes the whole clause
|
|
108
|
+
# false whatever the comparator; comparing nil directly would answer `ne` true.
|
|
177
109
|
def reference_where_holds?(clause, record, args, domain:, shape:)
|
|
178
110
|
step = QuerySpecification::HopPath.next_hop(clause.field, shape.attributes)
|
|
179
111
|
return where_holds?(clause, record, args) unless step
|
|
@@ -205,21 +137,13 @@ module Hecks
|
|
|
205
137
|
|
|
206
138
|
ordered = ordered_elements(rows, declared.order_by, declared.null_semantics,
|
|
207
139
|
parent_key, entity.identity_heads)
|
|
208
|
-
#
|
|
209
|
-
# #interpret's own rows above. `entity_rows` is the only engine
|
|
210
|
-
# for entity/sub-list queries, so a declared offset here silently
|
|
211
|
-
# vanished for every entity query, not merely one path among
|
|
212
|
-
# several.
|
|
140
|
+
# Entity queries have no native path, so offset must be applied here.
|
|
213
141
|
skipped = declared.offset ? ordered.drop(resolve_query_value(declared.offset.value, args).to_i) : ordered
|
|
214
142
|
declared.limit ? skipped.first(resolve_query_value(declared.limit.value, args).to_i) : skipped
|
|
215
143
|
end
|
|
216
144
|
|
|
217
|
-
#
|
|
218
|
-
#
|
|
219
|
-
# list attribute that holds it on the aggregate. Extracted from
|
|
220
|
-
# `entity_rows` (pure extraction, same lookups, same order, same
|
|
221
|
-
# UnknownVerb refusals) purely to separate "which declarations does
|
|
222
|
-
# this dotted name resolve to" from the row-computation that follows.
|
|
145
|
+
# Resolves a dotted name to the entity, its declared query and the list attribute
|
|
146
|
+
# holding it, refusing with UnknownVerb when any is missing.
|
|
223
147
|
def resolve_entity_query(aggregate, entity_name, query_name)
|
|
224
148
|
entity = aggregate.entities.find { |piece| piece.hecks_name == entity_name } ||
|
|
225
149
|
raise(UnknownVerb, RefusalWording.render_site("UnknownVerb", "entity_unknown",
|
|
@@ -233,35 +157,19 @@ module Hecks
|
|
|
233
157
|
[entity, declared, list_attr]
|
|
234
158
|
end
|
|
235
159
|
|
|
236
|
-
# FieldPath.dig, not
|
|
237
|
-
#
|
|
238
|
-
# segment walk every other query path already gets. Reading the whole
|
|
239
|
-
# dotted string as one key always missed — `element[:"price.cents"]`
|
|
240
|
-
# is never a real key — so a dotted where on an entity query
|
|
241
|
-
# silently matched nothing, on the only engine entity queries have.
|
|
160
|
+
# FieldPath.dig, not `element[clause.field.to_sym]`: a dotted field such as
|
|
161
|
+
# "price.cents" is never a single key.
|
|
242
162
|
def element_where_holds?(clause, element, args)
|
|
243
163
|
holds?(clause, QuerySpecification::FieldPath.dig(element, clause.field), args)
|
|
244
164
|
end
|
|
245
165
|
|
|
246
|
-
#
|
|
247
|
-
#
|
|
248
|
-
# `EntityListCoercion#hydrate_entity_list` symbolizes each element — PR A4
|
|
249
|
-
# removed the string-spelling fallback that coped with the old per-adapter
|
|
250
|
-
# shapes). It rides `comparable` for the same reason a where-clause does : an
|
|
251
|
-
# identity is a value object, and `to_s` on one is an object address — a sort key
|
|
252
|
-
# that differs run to run, which is worse than the store order it replaced.
|
|
166
|
+
# Sub-list rows are symbol-keyed because every adapter decodes through
|
|
167
|
+
# `Ports::Persistence::StateCodec`.
|
|
253
168
|
def cell(row, key) = row[key.to_sym]
|
|
254
169
|
|
|
255
|
-
#
|
|
256
|
-
# entities under different parents can share a sequence,
|
|
257
|
-
#
|
|
258
|
-
#
|
|
259
|
-
# Every key the piece is known by, in declaration order, for the same
|
|
260
|
-
# reason the parent leads: a part that ties is a part that breaks no tie.
|
|
261
|
-
# This took `identified_by`, which is the single head and is nil the
|
|
262
|
-
# moment an identity has two parts — and `cell(row, nil)` calls
|
|
263
|
-
# `nil.to_sym`, so a query against a composite piece did not sort wrongly,
|
|
264
|
-
# it raised. A piece known by one key sorts exactly as it did.
|
|
170
|
+
# Orders by parent first, then every key of the piece in declaration order: two
|
|
171
|
+
# entities under different parents can share a sequence, and a composite identity
|
|
172
|
+
# has no single head (`cell(row, nil)` would raise).
|
|
265
173
|
def ordered_elements(rows, order_by, null_semantics, parent_key, entity_keys)
|
|
266
174
|
field = order_by&.field
|
|
267
175
|
Ports::Query::Ordering.apply(
|
|
@@ -276,13 +184,8 @@ module Hecks
|
|
|
276
184
|
holds?(clause, QuerySpecification::FieldPath.dig(record, clause.field), args, record: record, domain: domain)
|
|
277
185
|
end
|
|
278
186
|
|
|
279
|
-
# The comparator table
|
|
280
|
-
# QuerySpecification::Common::Comparison
|
|
281
|
-
# Ports::Query::InMemory#holds? rather than each carrying its own
|
|
282
|
-
# copy — two copies once drifted: `none_in_state` reached only one
|
|
283
|
-
# of them, and `comparable` disagreed about value objects with two
|
|
284
|
-
# numeric members. What stays here is how a value is reached for
|
|
285
|
-
# this path: the registry is instance state rather than an argument.
|
|
187
|
+
# The comparator table is shared with Ports::Query::InMemory#holds? through
|
|
188
|
+
# QuerySpecification::Common::Comparison so the two cannot drift.
|
|
286
189
|
def holds?(clause, held, args, record: nil, domain: nil)
|
|
287
190
|
QuerySpecification::Common::Comparison.holds?(
|
|
288
191
|
clause.op, comparable(held), comparable(resolve_query_value(clause.value, args)), registry: @registry
|
|
@@ -293,49 +196,14 @@ module Hecks
|
|
|
293
196
|
value.is_a?(Symbol) ? args[value] : value
|
|
294
197
|
end
|
|
295
198
|
|
|
296
|
-
# `boundary: false
|
|
297
|
-
#
|
|
298
|
-
#
|
|
299
|
-
#
|
|
300
|
-
# says a named query's declared value-object arguments are checked
|
|
301
|
-
# the same way a command argument's own is, so a `nil` offered for
|
|
302
|
-
# a non-optional value-object-typed query attribute
|
|
303
|
-
# (Governance::RoleAssignment.AssignmentsForActor's `actor_id`, say)
|
|
304
|
-
# has to refuse — passing it through unchecked would let it through
|
|
305
|
-
# as a silent, unfiltered query instead, a real
|
|
306
|
-
# Ruby/Rust divergence the fuzzer caught (QualityControl BUG#2).
|
|
307
|
-
#
|
|
308
|
-
# `checked_vo?` true is handled by `null_vo_argument!` directly,
|
|
309
|
-
# never by routing through `Value.for_attribute(argument: true)`
|
|
310
|
-
# into the shared `Value::Coercion#nil_argument` the command door
|
|
311
|
-
# (`Interpreting#coerce_declared_arguments`) still uses — that
|
|
312
|
-
# method builds a null value object from zero fields, which
|
|
313
|
-
# succeeds (silently absorbing the null via the type's own field
|
|
314
|
-
# defaults) whenever every field happens to have one
|
|
315
|
-
# (`Lease.Expired`'s `now`, a `LeaseInstant` with a `default: 0`
|
|
316
|
-
# field; `Account.Overdrawn`/`HighBalance`/`StrictlyAbove`/
|
|
317
|
-
# `AtMost`'s `floor`/`cap`, a two-defaulted-field `Money`) and only
|
|
318
|
-
# refuses when a field has none (`Order.CostingLessThan`'s
|
|
319
|
-
# `ceiling`, a defaultless `Price`) — a real query-side divergence
|
|
320
|
-
# from Rust, which always refuses `TypeMismatch` on an explicit
|
|
321
|
-
# null argument regardless of any default (QualityControl BUG#36).
|
|
322
|
-
# `null_vo_argument!` instead treats an explicit null exactly the
|
|
323
|
-
# way `Value.fields_for` already treats any other wrong-shaped
|
|
324
|
-
# (non-Hash, non-Value) value offered for that same attribute — a
|
|
325
|
-
# single-field value object auto-wraps into `{field: nil}` (whose
|
|
326
|
-
# own `nil` is a present key, so `Value.build`'s `apply_defaults`
|
|
327
|
-
# never fills it, and `check_required_fields` refuses it exactly
|
|
328
|
-
# as any other missing required field would); a multi-field value
|
|
329
|
-
# object refuses immediately with the same `value_object_shape`
|
|
330
|
-
# wording an ordinary wrong-shaped scalar already gets. Command
|
|
331
|
-
# arguments are deliberately untouched — this is a query-only
|
|
332
|
-
# door; `nil_argument`'s own default-absorbing fallback still
|
|
333
|
-
# governs a null command argument exactly as it always has.
|
|
199
|
+
# Coerces with `boundary: false`: a query's declared argument types are not a
|
|
200
|
+
# runtime shape check. The exception is a nil for a required value-object-typed
|
|
201
|
+
# argument, which must refuse as a command argument does; passing it through
|
|
202
|
+
# would run an unfiltered query (a Ruby/Rust divergence).
|
|
334
203
|
#
|
|
335
|
-
#
|
|
336
|
-
# only
|
|
337
|
-
#
|
|
338
|
-
# through exactly as it always did.
|
|
204
|
+
# It does not use `Value::Coercion#nil_argument`, which builds a null value object
|
|
205
|
+
# from field defaults and only refuses when a field has none. `null_vo_argument!`
|
|
206
|
+
# refuses like any other wrong-shaped value. Command arguments are untouched.
|
|
339
207
|
def normalize_args(aggregate, declared, args)
|
|
340
208
|
declared.attributes.each_with_object(args.dup) do |attribute, normalized|
|
|
341
209
|
next unless normalized.key?(attribute.name)
|
|
@@ -357,10 +225,7 @@ module Hecks
|
|
|
357
225
|
!Value.value_object_for(aggregate, attribute.type).nil?
|
|
358
226
|
end
|
|
359
227
|
|
|
360
|
-
#
|
|
361
|
-
# attribute's type resolves to a real value object — see its own
|
|
362
|
-
# comment above for why this refuses unconditionally, never
|
|
363
|
-
# absorbing the null via the type's own field defaults.
|
|
228
|
+
# Refuses a nil for a value-object argument; never absorbs it via field defaults.
|
|
364
229
|
def null_vo_argument!(aggregate, attribute)
|
|
365
230
|
value_object = Value.value_object_for(aggregate, attribute.type)
|
|
366
231
|
Value.build(value_object, Value.fields_for(value_object, attribute.name, nil), aggregate)
|
|
@@ -368,15 +233,8 @@ module Hecks
|
|
|
368
233
|
|
|
369
234
|
def comparable(value) = QuerySpecification::Common::Comparison.comparable(value)
|
|
370
235
|
|
|
371
|
-
# FieldPath.dig, not
|
|
372
|
-
#
|
|
373
|
-
# single symbol key (`:"price.cents"`) that never matches anything
|
|
374
|
-
# `Instance#[]` actually holds, so a dotted order_by silently sorted
|
|
375
|
-
# by all-nil (the identity tier alone deciding every tie) on this,
|
|
376
|
-
# the reference/no-native-hook engine — the same bug already fixed
|
|
377
|
-
# for `where` (see `where_holds?` above) and for entity rows (see
|
|
378
|
-
# `ordered_elements` below), just not yet for this, the aggregate-
|
|
379
|
-
# level order_by.
|
|
236
|
+
# FieldPath.dig, not `record[field]`: a dotted order_by is one symbol key that
|
|
237
|
+
# `Instance#[]` never holds, so it would sort by all-nil.
|
|
380
238
|
def ordered(records, order_by, null_semantics = nil)
|
|
381
239
|
field = order_by&.field
|
|
382
240
|
Ports::Query::Ordering.apply(records, order_by, null_semantics,
|
|
@@ -5,27 +5,19 @@ require_relative "value"
|
|
|
5
5
|
|
|
6
6
|
module Hecks
|
|
7
7
|
module Runtime
|
|
8
|
-
# Turns facts
|
|
9
|
-
#
|
|
10
|
-
# both receiver identities and facts through one `with:` map, so this is
|
|
11
|
-
# the one compatibility seam that separates them after resolving the
|
|
12
|
-
# declaration and before re-entering the dispatcher.
|
|
8
|
+
# Turns facts a policy or process manager selected into the receiver/payload
|
|
9
|
+
# envelope an outside caller uses.
|
|
13
10
|
module ReactionInvocation
|
|
14
11
|
Target = Struct.new(:aggregate, :entities, :command, keyword_init: true)
|
|
15
12
|
# :facts, not :values — Struct.new already defines #values (every
|
|
16
|
-
# member's own value, in order); naming this field :values
|
|
17
|
-
#
|
|
13
|
+
# member's own value, in order); naming this field :values would
|
|
14
|
+
# silently shadow that with the single Hash it actually holds.
|
|
18
15
|
Scope = Struct.new(:name, :facts, keyword_init: true)
|
|
19
16
|
|
|
20
17
|
module_function
|
|
21
18
|
|
|
22
|
-
# Reports whether a reaction declared an explicit `with:` projection
|
|
23
|
-
#
|
|
24
|
-
# An omitted projection and an explicitly empty `with: {}` both hold
|
|
25
|
-
# as the same empty array on the holding IR. Builders preserve
|
|
26
|
-
# declaration presence off-wire, in `@projection_declared`;
|
|
27
|
-
# reconstructed/legacy IR without that ivar falls back to reading
|
|
28
|
-
# presence off whether `with_spec` is non-empty.
|
|
19
|
+
# Reports whether a reaction declared an explicit `with:` projection;
|
|
20
|
+
# an omitted projection and an explicit `with: {}` both count as none.
|
|
29
21
|
#
|
|
30
22
|
# @param declaration [Bluebook::Policy, Bluebook::DispatchSpec] the reacting
|
|
31
23
|
# declaration to check
|
|
@@ -38,27 +30,16 @@ module Hecks
|
|
|
38
30
|
end
|
|
39
31
|
end
|
|
40
32
|
|
|
41
|
-
# Resolves a declared `with:` projection against the scopes and bindings visible
|
|
42
|
-
#
|
|
43
|
-
# A reaction's source names resolve lexically, not globally. Policies
|
|
44
|
-
# supply one event/row scope. Process managers supply current event then
|
|
45
|
-
# opening-event memory, while correlation is an explicit binding ahead
|
|
46
|
-
# of both. Missing names are refused here rather than materialized as nil
|
|
47
|
-
# and accidentally presented as target command facts.
|
|
33
|
+
# Resolves a declared `with:` projection against the scopes and bindings visible
|
|
34
|
+
# to it, refusing a name resolved in neither rather than passing it as nil.
|
|
48
35
|
#
|
|
49
|
-
# @param with_spec [Hash
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
# @param
|
|
53
|
-
# Hash)>] the named fact scopes to resolve a Symbol source against, checked in order;
|
|
54
|
-
# a bare `[name, facts]` pair is wrapped into a `Scope`
|
|
55
|
-
# @param bindings [Hash] explicit bindings (such as a saga's correlation key), checked
|
|
56
|
-
# before any scope
|
|
36
|
+
# @param with_spec [Hash] fact name => source: a Symbol looked up in `bindings`/`scopes`,
|
|
37
|
+
# or any other value taken as a literal
|
|
38
|
+
# @param scopes [Array<Scope, Array(String, Hash)>] named fact scopes, checked in order
|
|
39
|
+
# @param bindings [Hash] explicit bindings (e.g. a saga's correlation key), checked first
|
|
57
40
|
# @param label [String] names this resolution in an `UnknownArgument` refusal
|
|
58
|
-
# @return [Hash
|
|
59
|
-
#
|
|
60
|
-
# @raise [Runtime::UnknownArgument] if a Symbol source names a fact visible in no
|
|
61
|
-
# binding and no scope
|
|
41
|
+
# @return [Hash] `with_spec`'s keys mapped to their resolved, materialized values
|
|
42
|
+
# @raise [Runtime::UnknownArgument] if a Symbol source is visible in no binding or scope
|
|
62
43
|
def resolve_mapping(with_spec:, scopes:, bindings: {}, label: "reaction")
|
|
63
44
|
normalized_bindings = bindings.transform_keys(&:to_sym)
|
|
64
45
|
normalized_scopes = scopes.map do |scope|
|
|
@@ -72,10 +53,6 @@ module Hecks
|
|
|
72
53
|
end
|
|
73
54
|
end
|
|
74
55
|
|
|
75
|
-
# One `with:` source, resolved — pulled out of resolve_mapping
|
|
76
|
-
# because it is a pure function of its own arguments (a literal, a
|
|
77
|
-
# binding, or a name visible in some scope), with no dependency on
|
|
78
|
-
# anything else resolve_mapping's own to_h block is doing.
|
|
79
56
|
def resolved_mapping_value(source, bindings, scopes, label)
|
|
80
57
|
return source unless source.is_a?(Symbol)
|
|
81
58
|
return bindings.fetch(source) if bindings.key?(source)
|
|
@@ -83,13 +60,7 @@ module Hecks
|
|
|
83
60
|
visible = scopes.find { |scope| scope.facts.key?(source) }
|
|
84
61
|
unless visible
|
|
85
62
|
names = scopes.map(&:name).join(" then ")
|
|
86
|
-
#
|
|
87
|
-
# missing sent a modeler guessing field after field
|
|
88
|
-
# (`number`, `reference`…) at a fan-out row that is addressed
|
|
89
|
-
# by one key — `account`, the lowercase aggregate — which
|
|
90
|
-
# nothing else in the domain spells out. The names each scope
|
|
91
|
-
# actually offers are the whole diagnosis; the refusal now
|
|
92
|
-
# lists them, scope by scope.
|
|
63
|
+
# Names every visible option so a caller isn't left guessing at fields.
|
|
93
64
|
offered = scopes.map { |scope| "#{scope.name}: #{scope.facts.keys.sort.join(', ')}" }.join("; ")
|
|
94
65
|
raise UnknownArgument,
|
|
95
66
|
"#{label}'s with: reads :#{source}, which is not visible in #{names} (visible — #{offered})"
|
|
@@ -98,39 +69,18 @@ module Hecks
|
|
|
98
69
|
end
|
|
99
70
|
private_class_method :resolved_mapping_value
|
|
100
71
|
|
|
101
|
-
# A reaction without an explicit projection
|
|
102
|
-
#
|
|
103
|
-
# command attributes enter `with:`, while identities become `to:`.
|
|
104
|
-
# `consumed` accumulates across the aggregate-identity and entity-
|
|
105
|
-
# identity steps below, and refuse_unconsumed! at the end reads the
|
|
106
|
-
# final list — an ordering dependency threaded through one shared
|
|
107
|
-
# local. Already leans on private helpers (identity_for,
|
|
108
|
-
# source_receiver_for, command_facts, refuse_unconsumed!) for every
|
|
109
|
-
# piece that is self-contained; what remains is the sequencing
|
|
110
|
-
# itself, which further splitting would only relocate, not remove.
|
|
72
|
+
# A reaction without an explicit `with:` projection forwards its payload
|
|
73
|
+
# wholesale; an explicit one builds a strict `{to:, with:}` envelope instead.
|
|
111
74
|
# rubocop:disable-next Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
112
|
-
#
|
|
113
|
-
# @param registry [Runtime::Registry] the booted registry to resolve `verb` against
|
|
114
75
|
# @param verb [String] the fully qualified target command verb
|
|
115
|
-
# @param projected [Hash]
|
|
116
|
-
#
|
|
117
|
-
# @param
|
|
118
|
-
#
|
|
119
|
-
# @
|
|
120
|
-
#
|
|
121
|
-
# @
|
|
122
|
-
#
|
|
123
|
-
# supplies one; nil when there is no such event to inherit from
|
|
124
|
-
# @return [Hash{Symbol => Object}] `{to:, with:}` for an explicit projection targeting
|
|
125
|
-
# a non-creating command (`with:` only for a creating command); otherwise `projected`
|
|
126
|
-
# (with `to:` merged in when a receiver could be inherited)
|
|
127
|
-
# @raise [Runtime::UnknownVerb] if `verb` does not resolve to a declared command,
|
|
128
|
-
# entity command, or port operation (only when `explicit` is true; a legacy call
|
|
129
|
-
# resolving `verb` only to check inheritance swallows this and forwards unchanged)
|
|
130
|
-
# @raise [Runtime::TypeMismatch] if an explicit projection resolves no receiver
|
|
131
|
-
# identity for the target aggregate or one of its entities
|
|
132
|
-
# @raise [Runtime::UnknownArgument] if an explicit projection's facts include a name
|
|
133
|
-
# that is neither a consumed receiver identity nor a declared command fact
|
|
76
|
+
# @param projected [Hash] resolved facts, or the raw payload when not `explicit`
|
|
77
|
+
# @param explicit [Boolean] whether the reaction declared its own `with:` projection
|
|
78
|
+
# @param passthrough [Array<String, Symbol>] extra fact names allowed unconsumed
|
|
79
|
+
# @param source_receiver [Hash, nil] an inheritable receiver from the triggering event
|
|
80
|
+
# @return [Hash] `{to:, with:}` when explicit, else `projected` merged with `to:`
|
|
81
|
+
# @raise [Runtime::UnknownVerb] if `verb` does not resolve (only when `explicit`)
|
|
82
|
+
# @raise [Runtime::TypeMismatch] if no receiver identity resolves for an explicit target
|
|
83
|
+
# @raise [Runtime::UnknownArgument] if an explicit projection has an undeclared fact
|
|
134
84
|
def build(registry:, verb:, projected:, explicit:, passthrough: [], source_receiver: nil)
|
|
135
85
|
args = projected.transform_keys(&:to_sym)
|
|
136
86
|
unless explicit
|
|
@@ -144,16 +94,9 @@ module Hecks
|
|
|
144
94
|
rescue UnknownVerb
|
|
145
95
|
return args
|
|
146
96
|
end
|
|
147
|
-
# An entity target
|
|
148
|
-
#
|
|
149
|
-
#
|
|
150
|
-
# `{aggregate:, entities:}`, which needs the entity's own
|
|
151
|
-
# identity too, and nothing here resolves one (the same "cannot
|
|
152
|
-
# invent an entity identity" limit `source_receiver_for`'s own
|
|
153
|
-
# comment already states). So this stays exactly the pre-BUG#6
|
|
154
|
-
# behavior for an entity target: no receiver is lifted, and the
|
|
155
|
-
# payload forwards wholesale, unchanged, same as when there is
|
|
156
|
-
# no `source_receiver` at all.
|
|
97
|
+
# An entity target's receiver needs `{aggregate:, entities:}`, but nothing
|
|
98
|
+
# here can invent the entity's own identity — so no receiver is lifted here;
|
|
99
|
+
# the payload forwards wholesale, same as with no `source_receiver` at all.
|
|
157
100
|
return args unless target.entities.empty?
|
|
158
101
|
|
|
159
102
|
inherited_receiver = source_receiver_for(target, source_receiver)
|
|
@@ -212,18 +155,9 @@ module Hecks
|
|
|
212
155
|
|
|
213
156
|
*entity_names, command_name = command_path.split(".")
|
|
214
157
|
|
|
215
|
-
# A port operation
|
|
216
|
-
#
|
|
217
|
-
# `Dispatcher#dispatch` already resolves a live verb in
|
|
218
|
-
# aggregate that declared both a port and an entity of the same
|
|
219
|
-
# name would resolve to the port there too; no domain in this
|
|
220
|
-
# corpus does). A port has no `entities` of its own — the
|
|
221
|
-
# receiver is always the aggregate itself — so `Target#entities`
|
|
222
|
-
# stays empty and `#command` holds the `PortOperation`, which
|
|
223
|
-
# answers `#creates?` (always false) the same way an ordinary
|
|
224
|
-
# `Command` does, letting `source_receiver_for` lift a same-
|
|
225
|
-
# aggregate policy's own Event.id as the operation's receiver
|
|
226
|
-
# exactly as it already does for a plain command.
|
|
158
|
+
# A port operation shares the two-segment tail shape an entity command
|
|
159
|
+
# uses ("Head.Rest"); checked first, matching the order
|
|
160
|
+
# `Dispatcher#dispatch` already resolves a live verb in.
|
|
227
161
|
if entity_names.one? && (port = aggregate.port(entity_names.first))
|
|
228
162
|
operation = port.operation(command_name)
|
|
229
163
|
raise UnknownVerb, "reaction target #{verb.inspect} does not resolve to a declared port operation" unless operation
|
|
@@ -259,49 +193,13 @@ module Hecks
|
|
|
259
193
|
end
|
|
260
194
|
private_class_method :aggregate_aliases
|
|
261
195
|
|
|
262
|
-
#
|
|
263
|
-
#
|
|
264
|
-
# it cannot address another aggregate, invent an entity identity, or turn
|
|
265
|
-
# a creation into a mutation. An explicit projected receiver is resolved
|
|
266
|
-
# first and remains authoritative.
|
|
267
|
-
#
|
|
268
|
-
# Answers regardless of `target.entities` — the value this method hands
|
|
269
|
-
# back is only ever the root aggregate's own identity (`source_receiver`
|
|
270
|
-
# carries nothing else: `event.aggregate`/`event.id` name the emitting
|
|
271
|
-
# aggregate, never one of its entities). An entity target's own identity
|
|
272
|
-
# is resolved entirely separately, from `args` (`build`'s own `entity_
|
|
273
|
-
# identities` loop) — this method is never consulted for it, so
|
|
274
|
-
# answering for an entity target does not "invent an entity identity"
|
|
275
|
-
# any more than answering for a plain one invents that identity; it was
|
|
276
|
-
# already the one thing this method has ever supplied. `build`'s own
|
|
277
|
-
# explicit branch (below) already applies this correctly either way
|
|
278
|
-
# (`aggregate_identity ||= inherited_receiver`, entity identities read
|
|
279
|
-
# from `args` regardless) — the caller that actually needed a guard
|
|
280
|
-
# here is the implicit one, above, which has no shape to build a
|
|
281
|
-
# `{aggregate:, entities:}` receiver from an inherited scalar alone,
|
|
282
|
-
# and now guards itself for exactly that reason instead of this method
|
|
283
|
-
# doing it on that caller's behalf and, as a side effect, also refusing
|
|
284
|
-
# the explicit caller's own legitimate case — confirmed missing until
|
|
285
|
-
# `qa/stress_domains/waybill` (BUG#6) exercised it for the first time
|
|
286
|
-
# anywhere in the corpus: a saga dispatching into a nested entity's own
|
|
287
|
-
# command, inheriting its aggregate receiver from the event that
|
|
288
|
-
# triggered it.
|
|
196
|
+
# An event's own identity can supply the receiver of a non-creating command on
|
|
197
|
+
# the same aggregate root; it never addresses another aggregate or an entity.
|
|
289
198
|
def source_receiver_for(target, source_receiver)
|
|
290
199
|
return nil unless source_receiver
|
|
291
|
-
# `target.command.creates?` alone misreads every entity command
|
|
292
|
-
# `
|
|
293
|
-
#
|
|
294
|
-
# answers true for every one of them" (it never sets `@references`
|
|
295
|
-
# the way an aggregate-level command's own `reference_to` does,
|
|
296
|
-
# not because it brings anything new into being). `build`'s own
|
|
297
|
-
# `if target.entities.empty? && target.command.creates?` guard,
|
|
298
|
-
# 20-odd lines below, already reads `creates?` correctly for
|
|
299
|
-
# exactly this reason — an entity command is never a genuine
|
|
300
|
-
# creation, whatever `creates?` alone answers — so this checks the
|
|
301
|
-
# same compound condition instead of the bare, misleading half of
|
|
302
|
-
# it. Left unfixed, an entity target's own receiver was refused
|
|
303
|
-
# here even after this method stopped refusing on `target.entities`
|
|
304
|
-
# alone — the second half of BUG#6's own fix.
|
|
200
|
+
# `target.command.creates?` alone misreads every entity command, which always
|
|
201
|
+
# answers true for `creates?` though it never creates anything (Behaviour::
|
|
202
|
+
# Command#creates?) — checked as the same compound condition `build` uses below.
|
|
305
203
|
return nil if target.entities.empty? && target.command.creates?
|
|
306
204
|
|
|
307
205
|
source = source_receiver.transform_keys(&:to_sym)
|