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
data/lib/hecks/runtime/event.rb
CHANGED
|
@@ -2,32 +2,10 @@ require "time"
|
|
|
2
2
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Runtime
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# bookkeeping stamped by `Dispatcher#dispatch` when a saga leg's own
|
|
8
|
-
# dispatch causes this event (see `SagaInterpreter#deliver_saga_dispatch`
|
|
9
|
-
# and `#saga_correlation`) : a Hash of `correlation_head` -> correlation
|
|
10
|
-
# value, so an event caused by one saga cannot be misread by an unrelated
|
|
11
|
-
# one correlating on a different field. Absent for any event no saga
|
|
12
|
-
# dispatch caused, which is most of them.
|
|
5
|
+
# An emitted domain event. `correlation` (saga head -> value) is runtime bookkeeping
|
|
6
|
+
# and deliberately absent from `to_h`.
|
|
13
7
|
Event = Struct.new(:name, :aggregate, :id, :payload, :occurred_at, :correlation, keyword_init: true) do
|
|
14
|
-
# Freezes the event deep,
|
|
15
|
-
#
|
|
16
|
-
# An emitted event is a record of something that happened, and a
|
|
17
|
-
# mutable audit trail is not one. The payload — the domain fact the
|
|
18
|
-
# event carries — is frozen through on emission: freezing the Hash
|
|
19
|
-
# alone would leave every value in it editable in place, which is
|
|
20
|
-
# the shape all four previous freezing bugs had.
|
|
21
|
-
#
|
|
22
|
-
# The whole event, not just its payload. Correlation is set at
|
|
23
|
-
# construction rather than merged in here by `Dispatcher#dispatch`
|
|
24
|
-
# after the event already exists, because it is part of the
|
|
25
|
-
# transaction and known from `dispatch`'s own argument before
|
|
26
|
-
# anything is emitted — that is what keeps an event immutable once
|
|
27
|
-
# it exists.
|
|
28
|
-
#
|
|
29
|
-
# The log stays appendable: new events are still recorded. It is
|
|
30
|
-
# each event that stops changing once it exists.
|
|
8
|
+
# Freezes the event deep, payload and correlation included.
|
|
31
9
|
#
|
|
32
10
|
# @return [void]
|
|
33
11
|
def emit!
|
|
@@ -3,58 +3,21 @@ require_relative "value"
|
|
|
3
3
|
|
|
4
4
|
module Hecks
|
|
5
5
|
module Runtime
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# An identity is declared as a path — `identified_by :number` — and
|
|
9
|
-
# this is the one place that reads one. It follows the path and nothing else.
|
|
10
|
-
#
|
|
11
|
-
# What it replaced was `Value.identifier`, which opened a one-field value
|
|
12
|
-
# object and took whatever was inside : that let `identified_by :number` pass
|
|
13
|
-
# for an identity, with the runtime guessing which field had been meant. The
|
|
14
|
-
# guess is gone. A declaration that names no field is now refused when the
|
|
15
|
-
# bluebook loads (`an aggregate that is identified names a field`, `an entity
|
|
16
|
-
# is known by a field`), so by the time anything is dispatched there is
|
|
17
|
-
# always a path here to follow.
|
|
18
|
-
#
|
|
19
|
-
# Usage:
|
|
20
|
-
#
|
|
21
|
-
# Identity.scalar("number.value", account_number_value_object) # => "acct-1"
|
|
6
|
+
# Resolves an aggregate's or entity's identity string from a command payload.
|
|
22
7
|
#
|
|
8
|
+
# Identity.scalar("number.value", account_number_value_object) # => "acct-1"
|
|
23
9
|
module Identity
|
|
24
10
|
module_function
|
|
25
11
|
|
|
26
|
-
# Reads
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
# A hash read that decides which spelling of a key answers by
|
|
30
|
-
# presence, never by `||` — a bare `||` treats a genuinely-held
|
|
31
|
-
# `false` the same as an absent key and falls through to the other
|
|
32
|
-
# spelling, landing on `nil` instead of the real, stored answer.
|
|
33
|
-
#
|
|
34
|
-
# @param hash [Hash] the hash to read, potentially keyed by either spelling
|
|
35
|
-
# @param key [String, Symbol] the key to look up
|
|
36
|
-
# @return [Object, nil] the value under `key.to_sym` if that key is present,
|
|
37
|
-
# otherwise the value under `key` as given; nil if neither is a key of `hash`
|
|
12
|
+
# Reads `key` from `hash` by whichever Symbol/String spelling is present.
|
|
13
|
+
# Presence decides, not `||`, so a held `false` is not mistaken for absent.
|
|
38
14
|
def hash_lookup(hash, key)
|
|
39
15
|
sym = key.to_sym
|
|
40
16
|
hash.key?(sym) ? hash[sym] : hash[key]
|
|
41
17
|
end
|
|
42
18
|
|
|
43
|
-
# Digs `path`
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
# The head names the attribute and is consumed by whoever looked the value
|
|
47
|
-
# up; what is left is the walk down into it. A path with no fields to walk
|
|
48
|
-
# — an aggregate that declares no identity and falls back to `id` — hands
|
|
49
|
-
# back what it was given, because there is nothing declared to dig for.
|
|
50
|
-
#
|
|
51
|
-
# @param path [String, Symbol] the dotted identity path (`"number.value"`) or a
|
|
52
|
-
# bare head (`:id`); only the segments after the head are walked
|
|
53
|
-
# @param held [Object] the value already looked up for the path's head — a
|
|
54
|
-
# `Runtime::Value`, a Hash, or a plain scalar
|
|
55
|
-
# @return [Object, nil] `held` unchanged when `path` has no fields past its head;
|
|
56
|
-
# otherwise the value found by walking those fields, or nil when a segment
|
|
57
|
-
# is missing or the value being dug into is not a Hash
|
|
19
|
+
# Digs the fields of `path` out of `held`, past the head the caller consumed.
|
|
20
|
+
# A path with no fields past the head returns `held` unchanged.
|
|
58
21
|
def scalar(path, held)
|
|
59
22
|
_head, *fields = path.to_s.split(".")
|
|
60
23
|
return held if fields.empty?
|
|
@@ -64,66 +27,22 @@ module Hecks
|
|
|
64
27
|
end
|
|
65
28
|
end
|
|
66
29
|
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
# The identity is the join of its parts, in declaration order. Shared by
|
|
71
|
-
# `CommandInterpreter` (an aggregate acting on itself) and
|
|
72
|
-
# `EntityInterpreter` (a piece addressed through its aggregate) — a piece
|
|
73
|
-
# declares an identity the same shape a head does, so it derives one the
|
|
74
|
-
# same way. `construct` answers `identity_paths` / `identity_heads` /
|
|
75
|
-
# `attribute` (an Aggregate or an Entity, either one) ; `value_owner`
|
|
76
|
-
# answers for coercion (`Value.for_attribute`'s first argument), which for
|
|
77
|
-
# an entity is its owning aggregate — an entity's value objects resolve
|
|
78
|
-
# through the aggregate's namespace, not its own.
|
|
79
|
-
#
|
|
80
|
-
# A part the payload does not carry makes the whole identity unresolvable,
|
|
81
|
-
# rather than half of one. Half an identity names nothing, and joining what
|
|
82
|
-
# did arrive would silently name a different record on every dispatch — the
|
|
83
|
-
# precise failure that minting an id caused, arrived at by another road.
|
|
84
|
-
#
|
|
85
|
-
# @param construct [Bluebook::Aggregate, Bluebook::Entity] the construct whose
|
|
86
|
-
# `identity_paths` are resolved
|
|
87
|
-
# @param args [Hash{Symbol => Object}] the offered payload to resolve each
|
|
88
|
-
# identity path against
|
|
89
|
-
# @param value_owner [Bluebook::Aggregate, Bluebook::Entity] the construct whose
|
|
90
|
-
# namespace a value-object identity part is coerced against; `construct` itself
|
|
91
|
-
# unless the caller passes the owning aggregate for an entity
|
|
92
|
-
# @return [String, nil] the joined identity string, or nil when `construct`
|
|
93
|
-
# declares no identity path or any resolved part is nil or blank
|
|
30
|
+
# Joins every declared identity part of `construct` from `args`, in declaration order.
|
|
31
|
+
# `value_owner` is the aggregate that coerces value-object parts (an entity's owner).
|
|
32
|
+
# Returns nil unless every part resolves: half an identity would name a different record.
|
|
94
33
|
def of(construct, args, value_owner: construct)
|
|
95
34
|
paths = construct.identity_paths
|
|
96
35
|
return nil if paths.empty?
|
|
97
36
|
|
|
98
37
|
parts = paths.map { |path| from(construct, args, path, value_owner: value_owner) }
|
|
99
|
-
# A blank part names nothing, the same as an absent one
|
|
100
|
-
# scalar, and "" is not a fact about anything. Checking only `nil?`
|
|
101
|
-
# would let a canonical text extracted as "" (an expression whose
|
|
102
|
-
# source did not survive extraction) resolve to a real, empty-string
|
|
103
|
-
# identity — a record addressable by an id no caller could have meant.
|
|
38
|
+
# A blank part names nothing, the same as an absent one.
|
|
104
39
|
return nil if parts.any? { |part| part.nil? || (part.respond_to?(:empty?) && part.empty?) }
|
|
105
40
|
|
|
106
41
|
Naming.identity(parts)
|
|
107
42
|
end
|
|
108
43
|
|
|
109
|
-
# Resolves one identity path
|
|
110
|
-
#
|
|
111
|
-
# A path digs into the value object that carries the identity, so what is
|
|
112
|
-
# stored is the scalar inside it rather than the object serialised whole.
|
|
113
|
-
#
|
|
114
|
-
# @param construct [Bluebook::Aggregate, Bluebook::Entity] the construct `key` is
|
|
115
|
-
# checked against when it is a bare head
|
|
116
|
-
# @param args [Hash{Symbol => Object}] the offered payload to resolve `key` against
|
|
117
|
-
# @param key [String, Symbol, nil] the identity path to resolve, dotted
|
|
118
|
-
# (`"number.value"`) or bare (`:id`); nil resolves to nil
|
|
119
|
-
# @param value_owner [Bluebook::Aggregate, Bluebook::Entity] the construct whose
|
|
120
|
-
# namespace a value-object head is coerced against; `construct` itself unless
|
|
121
|
-
# the caller passes the owning aggregate for an entity
|
|
122
|
-
# @return [String, Object, nil] the resolved identity text — a String once dug
|
|
123
|
-
# through a dotted path or coerced through a declared value-object attribute;
|
|
124
|
-
# the raw `args[key]` value, unconverted, when the bare head names no declared
|
|
125
|
-
# identity attribute of `construct`; nil when `key` is nil, absent from `args`,
|
|
126
|
-
# or a dotted walk finds nothing
|
|
44
|
+
# Resolves one identity path, dotted or a bare head such as `:id`, against `args`.
|
|
45
|
+
# A dotted path yields the scalar inside the value object, never the object serialised.
|
|
127
46
|
def from(construct, args, key, value_owner: construct)
|
|
128
47
|
return nil unless key
|
|
129
48
|
|
|
@@ -134,83 +53,28 @@ module Hecks
|
|
|
134
53
|
unless rest.empty?
|
|
135
54
|
held = args[head]
|
|
136
55
|
held = held.to_h if held.respond_to?(:to_h)
|
|
137
|
-
#
|
|
138
|
-
# caller may hand that field's value straight over — a string or a
|
|
139
|
-
# number, never a serialised object. Only a value object that actually
|
|
140
|
-
# arrived whole has to be opened.
|
|
56
|
+
# An ID is always a scalar: a caller may pass the carrying field's value directly.
|
|
141
57
|
return held.to_s unless held.is_a?(Hash)
|
|
142
58
|
|
|
143
59
|
return rest.reduce(held) { |h, f| h.is_a?(Hash) ? hash_lookup(h, f) : nil }&.to_s
|
|
144
60
|
end
|
|
145
61
|
|
|
146
|
-
#
|
|
147
|
-
# named it. A saga addresses an aggregate by its correlation key, and
|
|
148
|
-
# that key carries the id already resolved — coercing "w1" against a
|
|
149
|
-
# WireReference asked the caller to pass fields for a value object they
|
|
150
|
-
# never mentioned.
|
|
62
|
+
# Coerce only when the caller named the attribute; a saga's key is already resolved.
|
|
151
63
|
attribute = construct.identity_heads.include?(head) ? construct.attribute(head) : nil
|
|
152
64
|
raw = args[head]
|
|
153
65
|
return raw unless attribute
|
|
154
66
|
|
|
155
|
-
#
|
|
156
|
-
# already keeps, just reached a different way here: a bare
|
|
157
|
-
# (undotted) identity path names one of this construct's own
|
|
158
|
-
# declared attributes directly, and when that attribute's type is
|
|
159
|
-
# a value object (Translation's own compound `identified_by
|
|
160
|
-
# :domain, :from, :to`, each typed `TranslationDomainName`/
|
|
161
|
-
# `TranslationEraName`), `Value.for_attribute` coerces it into a
|
|
162
|
-
# real single-field Value wrapper — never unwrapped before this,
|
|
163
|
-
# so `Naming.identity`'s own plain `Array#join` (`Naming.identity`'s
|
|
164
|
-
# own header: parts must already be scalars) fell through to
|
|
165
|
-
# Ruby's default `Object#to_s`, leaking a raw, run-to-run-random
|
|
166
|
-
# memory address (`#<Hecks::Runtime::Value:0x...>`) into
|
|
167
|
-
# every refusal quoting this identity — found live via bin/fuzz on
|
|
168
|
-
# the self-hosted "translation" domain (replay_is_deterministic:
|
|
169
|
-
# the same address never repeats, so two replays of the
|
|
170
|
-
# identical steps produced different histories the moment a
|
|
171
|
-
# Translation went missing). `materialize_unwrapped` is the
|
|
172
|
-
# same single-field-VO-recurses-to-its-bare-scalar helper
|
|
173
|
-
# `read_model_interpreter.rb` already uses for exactly this
|
|
174
|
-
# unwrap; passthrough for anything that isn't a Value at all.
|
|
67
|
+
# Unwrap the coerced value object so `to_s` never leaks an object address into an id.
|
|
175
68
|
Value.materialize_unwrapped(Value.for_attribute(value_owner, attribute, raw)).to_s
|
|
176
69
|
end
|
|
177
70
|
|
|
178
|
-
# Renders
|
|
179
|
-
#
|
|
180
|
-
# How an identity reads when the runtime has to name it in a refusal — the
|
|
181
|
-
# paths as they were declared, so the message quotes the bluebook back.
|
|
182
|
-
#
|
|
183
|
-
# @param construct [Bluebook::Aggregate, Bluebook::Entity] the construct whose
|
|
184
|
-
# declared identity paths are rendered
|
|
185
|
-
# @return [String] `construct`'s identity paths, comma-separated, exactly as declared
|
|
71
|
+
# Renders the declared identity paths of `construct` for a refusal message.
|
|
186
72
|
def reading(construct)
|
|
187
73
|
construct.identity_paths.join(", ")
|
|
188
74
|
end
|
|
189
75
|
|
|
190
|
-
# Resolves
|
|
191
|
-
#
|
|
192
|
-
#
|
|
193
|
-
# Best-effort, for a lock key only — `Runtime::AggregateLock`'s own
|
|
194
|
-
# per-record striping needs some id to key on before dispatch has run
|
|
195
|
-
# far enough to hydrate for real, so this walks the identical chain
|
|
196
|
-
# `CommandInterpreter#hydrate_existing`/`#hydrate_prior_or_initial`
|
|
197
|
-
# and `EntityInterpreter#parent` already use to locate the real
|
|
198
|
-
# record — but wrapped to never raise. Choosing which Mutex to hold
|
|
199
|
-
# must never itself become a crash. `nil` means "could not resolve
|
|
200
|
-
# from the raw, pre-normalized payload this runs against" — the
|
|
201
|
-
# caller locks by aggregate type alone in that case (coarser, still
|
|
202
|
-
# correct, just less concurrent).
|
|
203
|
-
#
|
|
204
|
-
# @param construct [Bluebook::Aggregate, Bluebook::Entity] the construct being
|
|
205
|
-
# located for the lock key
|
|
206
|
-
# @param args [Hash{Symbol => Object}] the raw, pre-normalized payload to resolve
|
|
207
|
-
# an identity from
|
|
208
|
-
# @param route [Runtime::Routing::Envelope, nil] the call's resolved routing
|
|
209
|
-
# envelope, if any; its own `aggregate` identity is tried first
|
|
210
|
-
# @param reference_key [Symbol, nil] the command's reference-key fallback,
|
|
211
|
-
# tried last if given
|
|
212
|
-
# @return [String, Object, nil] the best-effort identity to lock by, or nil
|
|
213
|
-
# when nothing resolves (the caller then locks by aggregate type alone)
|
|
76
|
+
# Resolves a best-effort identity for `construct` to use as a lock key; never raises.
|
|
77
|
+
# Returns nil when nothing resolves, and the caller then locks by aggregate type alone.
|
|
214
78
|
def best_effort(construct, args, route = nil, reference_key: nil)
|
|
215
79
|
route&.aggregate ||
|
|
216
80
|
of(construct, args) ||
|
|
@@ -4,66 +4,22 @@ require_relative "../ports/persistence/codec_boundary"
|
|
|
4
4
|
|
|
5
5
|
module Hecks
|
|
6
6
|
module Runtime
|
|
7
|
-
# One aggregate
|
|
8
|
-
#
|
|
9
|
-
# for a CAS-capable adapter, the optimistic-concurrency `version`
|
|
10
|
-
# stamped on it. `[]`/`[]=`/method_missing give state-field access;
|
|
11
|
-
# `to_h` is the wire/storage shape with `id` merged in last so a
|
|
12
|
-
# same-named declared attribute can never clobber it.
|
|
7
|
+
# One aggregate or entity record's runtime state: declared attributes plus `id` and `version`.
|
|
8
|
+
# `to_h` is the wire/storage shape, with `id` merged last so an attribute named `id` loses.
|
|
13
9
|
class Instance
|
|
14
10
|
attr_reader :aggregate, :id
|
|
15
11
|
attr_accessor :state
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
# on a record it reads/writes, so a later `save` can assert "commit
|
|
19
|
-
# only if nobody has written since". Deliberately absent from
|
|
20
|
-
# `to_h`/`[]`/`[]=`/`method_missing` : a domain author never declares
|
|
21
|
-
# this, a `given`/`ensures`/`invariant` can never read it, and no
|
|
22
|
-
# adapter that doesn't understand it (Memory, Heki) ever sets it —
|
|
23
|
-
# `nil` there just means "no CAS attempted", which is exactly what a
|
|
24
|
-
# plain `save` already does. See docs/decisions/ (concurrency-control
|
|
25
|
-
# ADR) for the full mechanism.
|
|
12
|
+
# Optimistic-concurrency version stamped by a CAS-capable adapter; not domain state.
|
|
13
|
+
# Left out of `to_h` and `[]` on purpose; nil means no CAS was attempted.
|
|
26
14
|
attr_accessor :version
|
|
27
15
|
|
|
28
|
-
# `args
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
# holding it when they mint a brand-new record). See
|
|
32
|
-
# `materialize_identity!` for why a composite identity needs it.
|
|
33
|
-
#
|
|
34
|
-
# `hydrate:` — on by default, and every existing caller keeps getting
|
|
35
|
-
# exactly what it always got: `state` re-walked through
|
|
36
|
-
# `hydrate_with_defaults` (declared defaults filled, every attribute
|
|
37
|
-
# re-coerced through `Value.for_attribute`, an entity list's every
|
|
38
|
-
# element rebuilt and re-validated). `false` is for exactly one
|
|
39
|
-
# caller (`Adapters::Memory#build_instance`, judge-bootstrapping
|
|
40
|
-
# only — see its own header) that already knows `state` needs none
|
|
41
|
-
# of that: it is a shallow dup of an already-hydrated, already-
|
|
42
|
-
# validated live `Instance`'s own state, not a raw value pulled off
|
|
43
|
-
# a wire. Skipping the re-walk is what turns a `list_of` entity's Nth
|
|
44
|
-
# save from O(N) (re-hydrating every element saved so far, for every
|
|
45
|
-
# save) into O(1) — the quadratic cost `Adapters::Memory`'s own
|
|
46
|
-
# header traces start to finish. `CodecBoundary.check_state!` still
|
|
47
|
-
# runs either way ; only the re-hydration is skipped.
|
|
48
|
-
#
|
|
49
|
-
# @param aggregate [Bluebook::Aggregate, Bluebook::Entity] the construct this
|
|
50
|
-
# record's state is declared by
|
|
51
|
-
# @param id [String, nil] this record's identity; nil for a record whose identity
|
|
52
|
-
# is not yet resolvable (e.g. an entity element hydrated before its own view exists)
|
|
53
|
-
# @param state [Hash{Symbol => Object}, nil] the record's stored (or partial)
|
|
54
|
-
# attribute values; nil for a brand-new record, hydrated entirely from declared
|
|
55
|
-
# defaults
|
|
56
|
-
# @param args [Hash{Symbol => Object}, nil] the original command payload, offered
|
|
57
|
-
# only by a fresh creation; materializes a composite identity's own head
|
|
58
|
-
# attributes
|
|
59
|
-
# @param hydrate [Boolean] whether `state` is re-walked through
|
|
60
|
-
# `hydrate_with_defaults`; false only for a caller that already knows `state` is
|
|
61
|
-
# already hydrated and validated
|
|
16
|
+
# `args` is the creation payload, used only to fill a composite identity's heads.
|
|
17
|
+
# `hydrate: false` skips the default re-walk for an already-hydrated, validated state,
|
|
18
|
+
# keeping a `list_of` entity's Nth save O(1) rather than O(N).
|
|
62
19
|
def initialize(aggregate:, id:, state: nil, args: nil, hydrate: true)
|
|
63
20
|
@aggregate = aggregate
|
|
64
21
|
@id = id
|
|
65
|
-
# Inside a persistence adapter call this refuses undecoded stored
|
|
66
|
-
# state (Ports::Persistence::CodecBoundary); everywhere else, no-op.
|
|
22
|
+
# Inside a persistence adapter call this refuses undecoded stored state.
|
|
67
23
|
Ports::Persistence::CodecBoundary.check_state!(aggregate, state) if state
|
|
68
24
|
@state = if !hydrate
|
|
69
25
|
state || self.class.defaults(aggregate)
|
|
@@ -76,18 +32,7 @@ module Hecks
|
|
|
76
32
|
materialize_identity!(args)
|
|
77
33
|
end
|
|
78
34
|
|
|
79
|
-
#
|
|
80
|
-
# gets: an attribute the record predates — a newly-required field
|
|
81
|
-
# with a declared default:, a list added since the record was
|
|
82
|
-
# written — arrives filled instead of nil. Only declared defaults
|
|
83
|
-
# fill in; an attribute with no default stays absent, exactly as
|
|
84
|
-
# stored.
|
|
85
|
-
#
|
|
86
|
-
# @param aggregate [Bluebook::Aggregate, Bluebook::Entity] the construct whose
|
|
87
|
-
# declared attributes and defaults `state` is hydrated against
|
|
88
|
-
# @param state [Hash{Symbol => Object}] the raw stored (or partial) attribute values
|
|
89
|
-
# @return [Hash{Symbol => Object}] `state` coerced through every declared attribute,
|
|
90
|
-
# with any missing declared-default attribute filled in
|
|
35
|
+
# Hydrates `state` and fills declared defaults for attributes the record predates.
|
|
91
36
|
def self.hydrate_with_defaults(aggregate, state)
|
|
92
37
|
hydrated = Value.hydrate(aggregate, state)
|
|
93
38
|
defaults(aggregate).each do |name, value|
|
|
@@ -96,39 +41,20 @@ module Hecks
|
|
|
96
41
|
hydrated
|
|
97
42
|
end
|
|
98
43
|
|
|
99
|
-
# Builds a fresh record's starting state:
|
|
100
|
-
# default value, an empty frozen Array for a `list_of` attribute, and
|
|
101
|
-
# the lifecycle field's declared starting value when `aggregate` has one.
|
|
102
|
-
#
|
|
103
|
-
# @param aggregate [Bluebook::Aggregate, Bluebook::Entity] the construct whose
|
|
104
|
-
# declared attributes and lifecycle are read
|
|
105
|
-
# @return [Hash{Symbol => Object}] the default state, keyed by attribute name
|
|
44
|
+
# Builds a fresh record's starting state: declared defaults, empty lists, lifecycle start.
|
|
106
45
|
def self.defaults(aggregate)
|
|
107
46
|
state = aggregate.attributes.to_h do |attr|
|
|
108
|
-
# Frozen
|
|
109
|
-
# An untouched list is the easiest one to miss and the easiest
|
|
110
|
-
# to mutate: nothing has replaced it yet, so a caller pushing
|
|
111
|
-
# into it writes straight into the aggregate's own state.
|
|
47
|
+
# Frozen so pushing into an untouched list cannot write into the aggregate's state.
|
|
112
48
|
[attr.name, attr.list? ? Freezer.deep([]) : default_for(aggregate, attr)]
|
|
113
49
|
end
|
|
114
50
|
state[aggregate.lifecycle.field.to_sym] = aggregate.lifecycle.default if aggregate.lifecycle
|
|
115
51
|
state
|
|
116
52
|
end
|
|
117
53
|
|
|
118
|
-
# Resolves
|
|
119
|
-
# if it has one, otherwise a value object built entirely from its own
|
|
120
|
-
# members' defaults, when every member declares one.
|
|
121
|
-
#
|
|
122
|
-
# @param aggregate [Bluebook::Aggregate, Bluebook::Entity] the construct
|
|
123
|
-
# `attribute` belongs to
|
|
124
|
-
# @param attribute [Bluebook::Attribute] the attribute to resolve a default for
|
|
125
|
-
# @return [Runtime::Value, Object, nil] the coerced default value; nil when
|
|
126
|
-
# `attribute` declares no default and either names no value object or names
|
|
127
|
-
# one with a member that itself declares no default
|
|
54
|
+
# Resolves an attribute's default, building a value object from its members' defaults.
|
|
128
55
|
def self.default_for(aggregate, attribute)
|
|
129
56
|
return Value.for_attribute(aggregate, attribute, attribute.default) unless attribute.default.nil?
|
|
130
|
-
# An entity
|
|
131
|
-
# declares no value objects of its own — nothing to default-build.
|
|
57
|
+
# An entity declares no value objects of its own.
|
|
132
58
|
return nil unless aggregate.respond_to?(:value_object)
|
|
133
59
|
|
|
134
60
|
value_object = aggregate.value_object(attribute.type)
|
|
@@ -138,23 +64,12 @@ module Hecks
|
|
|
138
64
|
end
|
|
139
65
|
|
|
140
66
|
# Reads one state field by name.
|
|
141
|
-
#
|
|
142
|
-
# @param name [String, Symbol] the declared attribute name to read
|
|
143
|
-
# @return [Object, nil] the field's current value; nil if `name` is not a key
|
|
144
|
-
# of `state`
|
|
145
67
|
def [](name) = @state[name.to_sym]
|
|
146
68
|
|
|
147
69
|
# Reports whether `state` holds a value for `name`.
|
|
148
|
-
#
|
|
149
|
-
# @param name [String, Symbol] the declared attribute name to check
|
|
150
|
-
# @return [Boolean] true if `state` has a key for `name`
|
|
151
70
|
def key?(name) = @state.key?(name.to_sym)
|
|
152
71
|
|
|
153
72
|
# Writes one state field by name.
|
|
154
|
-
#
|
|
155
|
-
# @param name [String, Symbol] the declared attribute name to write
|
|
156
|
-
# @param value [Object] the value to store
|
|
157
|
-
# @return [Object] `value`, unchanged
|
|
158
73
|
def []=(name, value)
|
|
159
74
|
@state[name.to_sym] = value
|
|
160
75
|
end
|
|
@@ -169,36 +84,10 @@ module Hecks
|
|
|
169
84
|
@state.key?(name) || super
|
|
170
85
|
end
|
|
171
86
|
|
|
172
|
-
#
|
|
173
|
-
#
|
|
174
|
-
# `id: @id` last, not first — see Facade::Handle#to_h's own comment
|
|
175
|
-
# for the full story (the same fix, landed there first): an
|
|
176
|
-
# aggregate free to declare its own attribute literally named `id`
|
|
177
|
-
# (BurningManPrep's `Item`, `attribute :id, ItemId`) has that
|
|
178
|
-
# attribute's own wrapped value object sitting in `@state[:id]` —
|
|
179
|
-
# merging `@state` on top of `{ id: @id }` let it silently clobber
|
|
180
|
-
# the correct bare identity. `@id` merged last always wins.
|
|
181
|
-
#
|
|
182
|
-
# @return [Hash{Symbol => Object}] `state`, with `id` merged in last
|
|
87
|
+
# State plus identity, `id` merged last so a declared attribute named `id` cannot clobber it.
|
|
183
88
|
def to_h = @state.merge(id: @id)
|
|
184
89
|
|
|
185
|
-
# Copies this record
|
|
186
|
-
# reach the original's own state.
|
|
187
|
-
#
|
|
188
|
-
# A copy a mutation may touch. Every adapter but Memory hands `find`
|
|
189
|
-
# a freshly-decoded Instance already; Memory's holds the record it
|
|
190
|
-
# eventually saves — the same state Hash, aliased. Before `ensures`
|
|
191
|
-
# existed, nothing could refuse between apply_mutations and save, so
|
|
192
|
-
# that aliasing was invisible: a dispatch either ran to completion or
|
|
193
|
-
# raised before touching state at all. `ensures` is the first refusal
|
|
194
|
-
# to sit after mutation, and it found the bug the moment it did — an
|
|
195
|
-
# in-memory record left half-mutated by a dispatch that then refused.
|
|
196
|
-
# `command_interpreter`/`entity_interpreter` hydrate an existing
|
|
197
|
-
# record through this, never through the adapter's own return value
|
|
198
|
-
# directly, so a refused ensures leaves the stored record untouched
|
|
199
|
-
# regardless of which adapter is holding it.
|
|
200
|
-
#
|
|
201
|
-
# @return [Runtime::Instance] a copy of this record, with its own state Hash
|
|
90
|
+
# Copies this record with its own state Hash, so a refused dispatch spares the original.
|
|
202
91
|
def dup
|
|
203
92
|
copy = super
|
|
204
93
|
copy.state = @state.dup
|
|
@@ -212,36 +101,15 @@ module Hecks
|
|
|
212
101
|
|
|
213
102
|
private
|
|
214
103
|
|
|
215
|
-
#
|
|
216
|
-
#
|
|
217
|
-
# `identified_by` to fall back to `:id` for — `@aggregate.identified_by`
|
|
218
|
-
# is nil the moment there is more than one head (`Behaviour::Identified
|
|
219
|
-
# #derive_identity`), so the single-head branch below never runs for
|
|
220
|
-
# it at all. Without this, a creating command that declares those heads
|
|
221
|
-
# as ordinary attributes but doesn't also `sets` them (redundant with
|
|
222
|
-
# the identity the command's own args already named) would persist
|
|
223
|
-
# every head as nil — the id correctly naming the record, but the
|
|
224
|
-
# record's own attributes forgetting what named it.
|
|
225
|
-
#
|
|
226
|
-
# Filled from `args`, never from splitting `@id` back apart — the
|
|
227
|
-
# same reason the single-head branch below won't guess a multi-path
|
|
228
|
-
# identifier from its joined string: `@id` is a display key, not a
|
|
229
|
-
# reversible serialization, and a composite's own separator can
|
|
230
|
-
# collide with a part's own text. `args` is only offered by a fresh
|
|
231
|
-
# creation (`Instance.new`'s own `args:` comment); an existing record
|
|
232
|
-
# read back from storage has no args to lean on, and doesn't need
|
|
233
|
-
# one since a correctly-persisted record already carries its own
|
|
234
|
-
# heads.
|
|
104
|
+
# A composite identity has no single head, so each head is filled from `args`.
|
|
105
|
+
# Never split `@id`: it is a display key and a part's text can contain the separator.
|
|
235
106
|
def materialize_identity!(args = nil)
|
|
236
107
|
return materialize_composite_identity!(args) if @aggregate.identity_heads.size > 1
|
|
237
108
|
|
|
238
109
|
identity = @aggregate.identified_by || :id
|
|
239
110
|
attribute = @aggregate.attribute(identity)
|
|
240
111
|
return unless attribute && @state[identity].nil?
|
|
241
|
-
# Several paths under one head
|
|
242
|
-
# not a reversible serialization of the structured value object. A
|
|
243
|
-
# creating command supplies that object explicitly; persisted state
|
|
244
|
-
# hydrates it from storage. Never guess by splitting the joined id.
|
|
112
|
+
# Several paths under one head make the id a display key, not reversible; never split it.
|
|
245
113
|
return if @aggregate.identity_heads.one? && @aggregate.identity_paths.size > 1
|
|
246
114
|
|
|
247
115
|
@state[identity] = Value.from_identifier(@aggregate, attribute, @id)
|
|
@@ -3,21 +3,9 @@ require_relative "aggregate_lock"
|
|
|
3
3
|
|
|
4
4
|
module Hecks
|
|
5
5
|
module Runtime
|
|
6
|
-
#
|
|
7
|
-
# coercion of a command's declared arguments. Two copies of each lived
|
|
8
|
-
# in CommandInterpreter and EntityInterpreter, where they could only
|
|
9
|
-
# ever drift.
|
|
6
|
+
# Shared by the stepwise interpreters: the traced step and coercion of declared arguments.
|
|
10
7
|
module Interpreting
|
|
11
|
-
#
|
|
12
|
-
# accessor, since a class including this module needs its own copy
|
|
13
|
-
# rather than one shared across every interpreter.
|
|
14
|
-
#
|
|
15
|
-
# Each including interpreter gets its own `trace` — set by a spec to
|
|
16
|
-
# observe dispatch order (Vocabulary::AggregateDispatchOrder and
|
|
17
|
-
# Vocabulary::EntityDispatchOrder in language/bluebook/vocabulary.bluebook);
|
|
18
|
-
# nil in production, always — one array push and a nil check per step
|
|
19
|
-
# is the entire cost of leaving this in.
|
|
20
|
-
#
|
|
8
|
+
# Gives each including interpreter its own `trace`, set by specs to observe dispatch order.
|
|
21
9
|
# @param interpreter [Class] the class (`CommandInterpreter`, `EntityInterpreter`)
|
|
22
10
|
# including this module
|
|
23
11
|
# @return [void]
|
|
@@ -27,37 +15,15 @@ module Hecks
|
|
|
27
15
|
|
|
28
16
|
private
|
|
29
17
|
|
|
30
|
-
# Logged after the step's
|
|
31
|
-
# itself once everything inside it has already logged — trace order is
|
|
32
|
-
# completion order, which is dispatch order.
|
|
18
|
+
# Logged after the step's work, so trace order is completion order.
|
|
33
19
|
def step(name)
|
|
34
20
|
result = yield
|
|
35
21
|
self.class.trace << name if self.class.trace
|
|
36
22
|
result
|
|
37
23
|
end
|
|
38
24
|
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
# vocabulary.bluebook, via lib/hecks/vocabulary.rb) by `send`ing
|
|
42
|
-
# each declared step name against the including interpreter's own
|
|
43
|
-
# `step_<name>` handler, in declared order. Data drives that sequence,
|
|
44
|
-
# rather than `call` spelling it out as a literal sequence of method
|
|
45
|
-
# calls — tracing a real dispatch and comparing it to the declaration is
|
|
46
|
-
# tautological once `call` mechanically follows the declaration; a
|
|
47
|
-
# conditional step (assign_creation_attributes, advance_lifecycle) still
|
|
48
|
-
# has to guard itself at the top of its own handler and skip tracing
|
|
49
|
-
# when it does not fire, rather than the caller branching around it —
|
|
50
|
-
# see CommandInterpreter#step_assign_creation_attributes.
|
|
51
|
-
# The commit boundary. Every step up to `save` runs as before;
|
|
52
|
-
# `save`, `emit`, and the outbox enqueue that follows them run
|
|
53
|
-
# inside one `repository.transaction` — so the aggregate row, its
|
|
54
|
-
# journal entry, the recorded event, and the outbox rows naming
|
|
55
|
-
# who is owed a reaction commit together or not at all. No new
|
|
56
|
-
# step is added to the vocabulary's dispatch order (the step list
|
|
57
|
-
# is a pinned contract, `spec/vocabulary_conformance_spec.rb`);
|
|
58
|
-
# the enqueue is a consequence of `emit`, not a step of its own.
|
|
59
|
-
# A context without a repository (port operations — nothing to
|
|
60
|
-
# save) or with no `:save` in its order runs plainly.
|
|
25
|
+
# Runs `order` by sending each `step_<name>` handler; `save` onward shares one transaction.
|
|
26
|
+
# The outbox enqueue rides on `emit`; the vocabulary's step list is a pinned contract.
|
|
61
27
|
def run_dispatch_order(order, ctx)
|
|
62
28
|
split = order.index(:save)
|
|
63
29
|
return order.each { |name| send(:"step_#{name}", ctx) } unless split && ctx.respond_to?(:repository) && ctx.repository
|
|
@@ -69,34 +35,16 @@ module Hecks
|
|
|
69
35
|
end
|
|
70
36
|
end
|
|
71
37
|
|
|
72
|
-
#
|
|
73
|
-
# the save's transaction is still open. `nil` (no outbox on this
|
|
74
|
-
# repository) tells the dispatcher to react directly, the
|
|
75
|
-
# pre-outbox path; a dry run emits nothing and enqueues nothing.
|
|
38
|
+
# Enqueues outbox rows for the just-emitted events inside the save's transaction.
|
|
76
39
|
def enqueue_outbox(ctx)
|
|
77
40
|
return if ctx.dry_run || !ctx.respond_to?(:outbox_rows=)
|
|
78
41
|
|
|
79
42
|
ctx.outbox_rows = @registry.outbox.enqueue(ctx.repository, Array(ctx.result), ctx.domain)
|
|
80
43
|
end
|
|
81
44
|
|
|
82
|
-
#
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
# lost-update gap itself, via `step_save`'s CAS + `#call`'s own
|
|
86
|
-
# `StaleWrite` retry loop — an extra in-process lock here would be
|
|
87
|
-
# pointless overhead, not incorrect, so it's skipped for clarity.
|
|
88
|
-
# A repository that declares `:cross_process_lock` (PostgresEra —
|
|
89
|
-
# ADR 0036) holds a real Postgres advisory lock for the whole
|
|
90
|
-
# dispatch order instead: unlike Heki/Memory (confirmed
|
|
91
|
-
# process-local, never a second process writing the same store),
|
|
92
|
-
# PostgresEra's own tables can be dispatched against concurrently
|
|
93
|
-
# by `rust/host` from a separate OS process, and an in-process
|
|
94
|
-
# `Mutex` is invisible to that. Every other repository gets the
|
|
95
|
-
# striped `Mutex` below, held for the whole dispatch-order run, so
|
|
96
|
-
# a second thread's own hydrate can't start until the first
|
|
97
|
-
# thread's save has landed. `lock_key_id` is best-effort
|
|
98
|
-
# (`Identity.best_effort`) — `nil` still locks correctly, just
|
|
99
|
-
# coarser (by aggregate type).
|
|
45
|
+
# Picks the isolation by capability: CAS needs none, a cross-process lock uses the
|
|
46
|
+
# repository's write lock, and the rest get a striped in-process Mutex (ADR 0036).
|
|
47
|
+
# A nil `lock_key_id` still locks correctly, just by aggregate type.
|
|
100
48
|
def run_dispatch_order_with_isolation(order, ctx, lock_key_id:)
|
|
101
49
|
capabilities = ctx.repository.capabilities
|
|
102
50
|
if capabilities.include?(:optimistic_concurrency)
|
|
@@ -108,9 +56,7 @@ module Hecks
|
|
|
108
56
|
end
|
|
109
57
|
end
|
|
110
58
|
|
|
111
|
-
#
|
|
112
|
-
# gate, then coercion — the same walk whether the command acts on an
|
|
113
|
-
# aggregate or on one of its entity's elements.
|
|
59
|
+
# Passes each declared payload attribute through the reference gate, then coercion.
|
|
114
60
|
def coerce_declared_arguments(aggregate, command, args)
|
|
115
61
|
command.attributes.each_with_object(args.dup) do |attribute, normalized|
|
|
116
62
|
next unless normalized.key?(attribute.name)
|
|
@@ -120,12 +66,7 @@ module Hecks
|
|
|
120
66
|
end
|
|
121
67
|
end
|
|
122
68
|
|
|
123
|
-
# Coercion only
|
|
124
|
-
# refuse_absent_arguments are separate DISPATCH_ORDER steps now (the
|
|
125
|
-
# declared vocabulary lists all three as flat, sequential members, not
|
|
126
|
-
# one nesting the other two), and EntityInterpreter never had them here
|
|
127
|
-
# at all (an entity inherits its aggregate's own gate). One shared
|
|
128
|
-
# copy, rather than two identical ones that can only ever drift apart.
|
|
69
|
+
# Coercion only; the unknown/absent-argument refusals are separate dispatch steps.
|
|
129
70
|
def normalize_args(aggregate, command, args)
|
|
130
71
|
coerce_declared_arguments(aggregate, command, args)
|
|
131
72
|
end
|