hecks 2.8.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 +4 -6
- 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 +17 -98
- 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 +5 -48
- 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 +31 -141
- 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 +14 -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 +25 -31
- 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 +16 -69
- 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 -194
- 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 -111
- 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 +53 -267
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +43 -305
- 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 +6 -21
- data/lib/hecks/bluebook/model_check.rb +100 -597
- 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 +4 -23
- data/lib/hecks/cli/document.rb +3 -21
- data/lib/hecks/cli/ir.rb +3 -5
- data/lib/hecks/cli/mcp.rb +2 -3
- data/lib/hecks/cli/mcp_door.rb +19 -114
- data/lib/hecks/cli/model_check.rb +18 -85
- data/lib/hecks/cli/project_cli.rb +7 -40
- data/lib/hecks/cli/project_diagrams.rb +0 -7
- data/lib/hecks/cli/run.rb +12 -58
- data/lib/hecks/cli/smoke_test.rb +2 -16
- data/lib/hecks/cli/stores.rb +1 -2
- data/lib/hecks/cli.rb +18 -24
- 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 +82 -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 +9 -90
- 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 +61 -64
- 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 +6 -25
- 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 +18 -124
- 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 +150 -678
- 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 +2 -15
- 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 +42 -134
- 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 +3 -6
- data/lib/hecks/release/runner/commands.rb +2 -4
- data/lib/hecks/release/runner/gem_publisher.rb +2 -3
- data/lib/hecks/release/runner/npm_publisher.rb +6 -20
- data/lib/hecks/release/runner/preflight.rb +4 -19
- data/lib/hecks/release/runner/tagger.rb +2 -9
- data/lib/hecks/release/runner.rb +3 -32
- 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 +124 -306
- 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 +64 -219
- 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 +117 -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 +99 -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 +141 -568
- 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.rb +26 -81
- 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 +3 -2
|
@@ -8,69 +8,18 @@ require_relative "instance"
|
|
|
8
8
|
|
|
9
9
|
module Hecks
|
|
10
10
|
module Runtime
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# `EntityInterpreter` so a second caller (`CommandInterpreter`'s own
|
|
14
|
-
# `delegate_to_entity` step) can locate and mutate the same element the
|
|
15
|
-
# same way, against an aggregate record it already holds in memory
|
|
16
|
-
# rather than one freshly loaded from a repository. `EntityInterpreter`
|
|
17
|
-
# keeps its own dispatch order and Context; this is the part underneath
|
|
18
|
-
# that both now share, so it exists in exactly one place rather than two
|
|
19
|
-
# that could only ever drift (the same reasoning `Runtime::Identity`'s
|
|
20
|
-
# own header gives for the join/dig/reading trio it centralizes).
|
|
21
|
-
#
|
|
22
|
-
# `rules` (a `CommandRules` instance) is passed explicitly throughout
|
|
23
|
-
# rather than closed over, since this module has no instance of its own
|
|
24
|
-
# to hold one.
|
|
11
|
+
# Locates and mutates one entity element within an aggregate record — the shared
|
|
12
|
+
# implementation behind EntityInterpreter and CommandInterpreter's own delegation.
|
|
25
13
|
module EntityElement
|
|
26
14
|
module_function
|
|
27
15
|
|
|
28
|
-
#
|
|
29
|
-
# returned from `element_of`'s own `wants` coercion in place of a real
|
|
30
|
-
# `Value` (below). A unique object, never `nil`: a stored, optional
|
|
31
|
-
# element field really can hold `nil`, and comparing that against a
|
|
32
|
-
# bare `nil` sentinel would accidentally "match" it.
|
|
16
|
+
# A sentinel that never equals a stored element field, since a real field can hold nil.
|
|
33
17
|
UNMATCHABLE = Object.new.freeze
|
|
34
18
|
private_constant :UNMATCHABLE
|
|
35
19
|
|
|
36
|
-
# Walks `chain
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
# **One hop per chain entry**. `container` starts as `instance` (the root
|
|
40
|
-
# aggregate record) and becomes each just-located element in turn —
|
|
41
|
-
# Dispatch's own element is found inside the Handler element
|
|
42
|
-
# `locate_chain` located the step before, never inside `instance`
|
|
43
|
-
# directly. `owner` is whichever construct's own attribute declares
|
|
44
|
-
# the list being searched (Handler declares `dispatches` ; the root
|
|
45
|
-
# aggregate declares `handlers`) — `root_aggregate` stays the root
|
|
46
|
-
# the whole way through instead, passed to `element_of` separately,
|
|
47
|
-
# because coercion (`Value.for_attribute`) resolves value objects
|
|
48
|
-
# against the root's own namespace only ; an entity must never
|
|
49
|
-
# answer `.value_object` (Entity's own header comment) so handing
|
|
50
|
-
# it an intermediate owner instead would break every VO-typed
|
|
51
|
-
# identity field a nested entity declares.
|
|
52
|
-
#
|
|
53
|
-
# `route`, if given, is the routing envelope's own entity chain
|
|
54
|
-
# (`ctx.route.entities`, one entry per hop) — the routing/payload
|
|
55
|
-
# split's own consequence: an entity's own identity may arrive via
|
|
56
|
-
# `to:` rather than duplicated into `args`, so each hop is offered
|
|
57
|
-
# its routed identity ahead of falling back to `args`.
|
|
58
|
-
#
|
|
59
|
-
# @param root_aggregate [Bluebook::Aggregate] the root aggregate record's
|
|
60
|
-
# own construct, held constant across every hop for coercion
|
|
61
|
-
# @param chain [Array<Bluebook::Entity>] the entity chain to walk, root-first
|
|
62
|
-
# @param instance [Runtime::Instance] the root aggregate record to walk from
|
|
63
|
-
# @param args [Hash{String, Symbol => Object}] the offered command arguments,
|
|
64
|
-
# read for each hop's own identity when `route` does not supply it
|
|
65
|
-
# @param command_name [String] the command name, quoted in a refusal
|
|
66
|
-
# @param route [Runtime::Routing::Envelope, nil] the call's resolved routing
|
|
67
|
-
# envelope, if any; its own `entities` supply each hop's identity first
|
|
68
|
-
# @return [Runtime::Instance, Hash{Symbol => Object}] the located element;
|
|
69
|
-
# `instance` itself, unchanged, when `chain` is empty
|
|
70
|
-
# @raise [Runtime::UnknownVerb] if a hop's owner declares no list attribute
|
|
71
|
-
# for that entity
|
|
72
|
-
# @raise [Runtime::NotFound] if a hop's identity is absent from `args`, or no
|
|
73
|
-
# element matches it
|
|
20
|
+
# Walks `chain` one hop at a time and returns the located element (or `instance`
|
|
21
|
+
# itself when `chain` is empty). `route`, when given, offers each hop's identity
|
|
22
|
+
# before falling back to `args`.
|
|
74
23
|
def locate_chain(root_aggregate, chain, instance, args, command_name, route = nil)
|
|
75
24
|
container = instance
|
|
76
25
|
owner = root_aggregate
|
|
@@ -82,42 +31,14 @@ module Hecks
|
|
|
82
31
|
container
|
|
83
32
|
end
|
|
84
33
|
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
# matches by the element's own minted identity string directly instead
|
|
90
|
-
# (`element_identity`, below) — the routing envelope already resolved
|
|
91
|
-
# which element it means, so re-deriving `wants` from `args` would be
|
|
92
|
-
# redundant at best and wrong if `args` does not carry that identity
|
|
93
|
-
# at all.
|
|
94
|
-
# Locate, then copy-before-mutate, in that order — see the "one
|
|
95
|
-
# level deeper" comment below on why the copy has to happen exactly
|
|
96
|
-
# where it does (aliasing the adapter's own record otherwise).
|
|
97
|
-
# Splitting resolution from the copy/write-back would separate two
|
|
98
|
-
# halves of one aliasing-safety invariant across method boundaries.
|
|
34
|
+
# Locates one element, matching every part of its declared identity, and copies
|
|
35
|
+
# the owning list and the element before any write so nothing aliases the caller's
|
|
36
|
+
# record. `routed_identity`, when given, matches directly by the element's own
|
|
37
|
+
# identity string instead of re-deriving `wants` from `args`.
|
|
99
38
|
# rubocop:disable-next Metrics/AbcSize
|
|
100
39
|
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
101
40
|
# rubocop:disable-next Metrics/PerceivedComplexity
|
|
102
41
|
# rubocop:disable-next Metrics/MethodLength
|
|
103
|
-
#
|
|
104
|
-
# @param root_aggregate [Bluebook::Aggregate] the root aggregate record's own
|
|
105
|
-
# construct, used for value-object coercion
|
|
106
|
-
# @param owner [Bluebook::Aggregate, Bluebook::Entity] the construct whose own
|
|
107
|
-
# attribute declares the list `entity` is searched in
|
|
108
|
-
# @param entity [Bluebook::Entity] the entity type being located
|
|
109
|
-
# @param command_name [String] the command name, quoted in a refusal
|
|
110
|
-
# @param container [Runtime::Instance, Hash{Symbol => Object}] the record or
|
|
111
|
-
# element holding the list to search
|
|
112
|
-
# @param args [Hash{String, Symbol => Object}] the offered command arguments,
|
|
113
|
-
# read for the element's own identity when `routed_identity` is nil
|
|
114
|
-
# @param routed_identity [String, nil] the routing envelope's own identity
|
|
115
|
-
# string for this hop, matched directly instead of deriving one from `args`
|
|
116
|
-
# @return [Hash{Symbol => Object}] a fresh copy of the located element; the
|
|
117
|
-
# owning list inside `container` is replaced with a fresh copy too
|
|
118
|
-
# @raise [Runtime::UnknownVerb] if `owner` declares no list attribute for `entity`
|
|
119
|
-
# @raise [Runtime::NotFound] if an identity part is absent from `args`
|
|
120
|
-
# (`routed_identity` nil only), or no element matches
|
|
121
42
|
def element_of(root_aggregate, owner, entity, command_name, container, args, routed_identity = nil)
|
|
122
43
|
entity_name = entity.hecks_name
|
|
123
44
|
list_attr = owner.attributes.find { |a| a.list? && a.type.to_s == entity_name } ||
|
|
@@ -132,28 +53,9 @@ module Hecks
|
|
|
132
53
|
command: command_name, entity: entity_name,
|
|
133
54
|
identity: Identity.reading(entity)))
|
|
134
55
|
|
|
135
|
-
#
|
|
136
|
-
# (
|
|
137
|
-
#
|
|
138
|
-
# entry-less ledger). Coercing it all the way to a typed
|
|
139
|
-
# `Value` here ran that type's own invariant before this
|
|
140
|
-
# method ever checks whether any element matches — a
|
|
141
|
-
# `sequence: 0` against `LedgerSequence`'s own "a ledger
|
|
142
|
-
# sequence is positive" invariant raised InvariantViolation,
|
|
143
|
-
# not NotFound, even when (as here) nothing was ever posted
|
|
144
|
-
# at all. Every element actually in the list already
|
|
145
|
-
# satisfied its own type's invariant the moment it was
|
|
146
|
-
# created, so a value that fails it can never equal one —
|
|
147
|
-
# degrading to `UNMATCHABLE` here, instead of propagating,
|
|
148
|
-
# is exactly as safe as the ordinary "no match found" case
|
|
149
|
-
# below, and lines this addressing path up with the two
|
|
150
|
-
# conventions it already disagreed with: Rust's own
|
|
151
|
-
# `extract_id`/`extract_wants` (a raw scalar read, never a
|
|
152
|
-
# typed rebuild — rust/src/generated/*/*.rs) and
|
|
153
|
-
# `Identity.from`'s own raw-comparison convention for a
|
|
154
|
-
# root aggregate's identity (this file's sibling,
|
|
155
|
-
# `identity.rb`). `raw` rides alongside `want` so the
|
|
156
|
-
# eventual NotFound below can still quote what was offered.
|
|
56
|
+
# A value that fails its own type's invariant can never match a stored
|
|
57
|
+
# element (every stored one already satisfies it) — degrade to
|
|
58
|
+
# `UNMATCHABLE` here rather than letting InvariantViolation propagate.
|
|
157
59
|
want = begin
|
|
158
60
|
Value.for_attribute(root_aggregate, entity.attribute(head), raw)
|
|
159
61
|
rescue InvariantViolation
|
|
@@ -182,19 +84,9 @@ module Hecks
|
|
|
182
84
|
)
|
|
183
85
|
end
|
|
184
86
|
|
|
185
|
-
#
|
|
186
|
-
# attribute holds Hashes, and
|
|
187
|
-
#
|
|
188
|
-
# in place means aliased with the adapter's own record until this
|
|
189
|
-
# copies the array and the target element before handing either
|
|
190
|
-
# back, and writes the fresh array into `container` so the copy is
|
|
191
|
-
# what persists on success and nothing aliased survives a refusal.
|
|
192
|
-
# `container[list_attr.name] = copied` reaches `instance` itself
|
|
193
|
-
# when this is the first hop, and reaches the (already copied)
|
|
194
|
-
# parent element when it is a later one — either way it is the
|
|
195
|
-
# same already-fresh object `locate_chain` is about to hand back
|
|
196
|
-
# as `container` for the next hop, so nothing further has to
|
|
197
|
-
# propagate a write back up the chain by hand.
|
|
87
|
+
# Copies the list and the found element before handing either back — the list
|
|
88
|
+
# attribute holds Hashes, and the caller mutates the returned element in place,
|
|
89
|
+
# so this keeps that mutation off the adapter's own record until it commits.
|
|
198
90
|
copied = original.dup
|
|
199
91
|
element = copied[position].dup
|
|
200
92
|
copied[position] = element
|
|
@@ -202,20 +94,8 @@ module Hecks
|
|
|
202
94
|
element
|
|
203
95
|
end
|
|
204
96
|
|
|
205
|
-
#
|
|
206
|
-
#
|
|
207
|
-
# than a dispatch payload. An id is a scalar, and the path is how it is
|
|
208
|
-
# reached — never by opening a value object and taking whatever single
|
|
209
|
-
# field is inside. That unwrapping is gone from the language: a piece
|
|
210
|
-
# that does not name its fields is refused when the bluebook loads ("an
|
|
211
|
-
# entity says what it is known by", "an identity part names something"),
|
|
212
|
-
# so by the time a dispatch arrives here there is always a path to dig.
|
|
213
|
-
#
|
|
214
|
-
# @param entity [Bluebook::Entity] the entity type whose identity paths
|
|
215
|
-
# are read
|
|
216
|
-
# @param element [Hash{Symbol => Object}] the stored element to read an
|
|
217
|
-
# identity off
|
|
218
|
-
# @return [String] the element's identity, joined from its declared parts
|
|
97
|
+
# An element's identity, joined from its declared identity paths (read off the
|
|
98
|
+
# stored Hash, not a dispatch payload).
|
|
219
99
|
def element_identity(entity, element)
|
|
220
100
|
parts = entity.identity_paths.map do |path|
|
|
221
101
|
head = path.to_s.split(".").first.to_sym
|
|
@@ -225,65 +105,10 @@ module Hecks
|
|
|
225
105
|
Naming.identity(parts)
|
|
226
106
|
end
|
|
227
107
|
|
|
228
|
-
#
|
|
229
|
-
#
|
|
230
|
-
#
|
|
231
|
-
# origin). Missing until now — an entity-owned command declaring
|
|
232
|
-
# `sets :some_list, append: {...}` matched no `when` here and
|
|
233
|
-
# silently no-opped, the one place this language otherwise
|
|
234
|
-
# refuses what it cannot check applying nothing instead. `Member`/
|
|
235
|
-
# `Dispatch` (S17) are the first real callers: both need to
|
|
236
|
-
# append a value-object-typed element (`Pair`/`Binding`) onto a
|
|
237
|
-
# list attribute they own, once they become entities of
|
|
238
|
-
# `ValueObject`/`ProcessManager` rather than separate aggregates.
|
|
239
|
-
#
|
|
240
|
-
# `:increment`/`:decrement`/`:multiply` also fixed here, found
|
|
241
|
-
# while proving this method against a real fixture: they wrapped
|
|
242
|
-
# `amount` unconditionally whenever `attribute` existed, the same
|
|
243
|
-
# asymmetric-wrapping shape `MutationApplier#rewrap_arithmetic_
|
|
244
|
-
# result`'s own comment documents fixing at the aggregate level
|
|
245
|
-
# (migration plan task 9) — a phantom-created VO-typed field's
|
|
246
|
-
# `current` reads back a raw, unwrapped default, `amount` was
|
|
247
|
-
# wrapped anyway, and the two sides of one arithmetic call
|
|
248
|
-
# disagreed on Value-ness. Confirmed live, not theoretical: this
|
|
249
|
-
# method's own fixture (TaggedList.Bump, a VO-typed `count` with
|
|
250
|
-
# `default: 0`) raised exactly this TypeMismatch on its first
|
|
251
|
-
# real run.
|
|
252
|
-
# A case dispatching over a closed, declared set of mutation ops —
|
|
253
|
-
# deliberately kept byte-for-byte parallel to its aggregate-level
|
|
254
|
-
# twin, MutationApplier#apply (see this method's own comment
|
|
255
|
-
# above and each branch's "own entity-scoped twin" cross-reference):
|
|
256
|
-
# extracting the near-duplicate increment/decrement/multiply
|
|
257
|
-
# shape here without doing the same there would break that
|
|
258
|
-
# intentional mirroring, which is what lets the two be diffed
|
|
259
|
-
# against each other when one gets a fix the other needs too.
|
|
108
|
+
# Applies one declared mutation to an entity element, in place. `pre` is the
|
|
109
|
+
# element as it stood before this command: every read goes through it, every
|
|
110
|
+
# write lands on `element`.
|
|
260
111
|
# rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity
|
|
261
|
-
# Applies one declared mutation to an entity element, in place.
|
|
262
|
-
#
|
|
263
|
-
# `pre` — the element as it was before this command (C4.2): every
|
|
264
|
-
# read below goes through it, every write lands on `element`.
|
|
265
|
-
#
|
|
266
|
-
# @param rules [Runtime::CommandRules] the shared rules engine `resolve_source`/
|
|
267
|
-
# `arithmetic`/`multiply`/`clamp`/`sign_of` are read through
|
|
268
|
-
# @param aggregate [Bluebook::Aggregate] the root aggregate, for value-object
|
|
269
|
-
# coercion
|
|
270
|
-
# @param entity [Bluebook::Entity] the entity type `element` is an instance of
|
|
271
|
-
# @param element [Hash{Symbol => Object}] the element to mutate; written in place
|
|
272
|
-
# @param mutation [Bluebook::Mutation] the declared mutation to apply
|
|
273
|
-
# @param args [Hash{String, Symbol => Object}] the offered command arguments,
|
|
274
|
-
# a mutation's source may read from
|
|
275
|
-
# @param pre [Hash{Symbol => Object}] the element as it stood before this
|
|
276
|
-
# command; every read goes through this, every write lands on `element`
|
|
277
|
-
# @return [void]
|
|
278
|
-
# @raise [Runtime::TypeMismatch] if a mutation's own value cannot be coerced,
|
|
279
|
-
# or an arithmetic op's operands are not numeric or compatible value objects
|
|
280
|
-
# @raise [Runtime::InvariantViolation] if a coerced or arithmetic-derived value
|
|
281
|
-
# object breaks one of its own invariants
|
|
282
|
-
# @raise [Runtime::AlreadyExists] if an `:append` mints a nested entity whose
|
|
283
|
-
# identity collides with an existing element
|
|
284
|
-
# @raise [Bluebook::Expression::EvaluationError] if an arithmetic op's product
|
|
285
|
-
# does not fit a signed 64-bit Integer, or is a non-finite Float
|
|
286
|
-
# @raise [Runtime::WiringError] if `mutation.op` names no handled mutation kind
|
|
287
112
|
def apply_to_element(rules, aggregate, entity, element, mutation, args, pre = element)
|
|
288
113
|
case mutation.op
|
|
289
114
|
when :set
|
|
@@ -310,73 +135,26 @@ module Hecks
|
|
|
310
135
|
element[mutation.target] = rewrap_arithmetic_result(aggregate, attribute, current, result)
|
|
311
136
|
when :clamp
|
|
312
137
|
element[mutation.target] = rules.clamp(pre[mutation.target], mutation.source, mutation.target)
|
|
313
|
-
# `corrects
|
|
314
|
-
#
|
|
315
|
-
# the same one applies here unchanged: this mutation targets no
|
|
316
|
-
# field on this element at all — its own event name, and whether
|
|
317
|
-
# the owning record has actually emitted it, was already checked
|
|
318
|
-
# once, up front, by `EntityInterpreter#step_enforce_givens`
|
|
319
|
-
# (`CommandRules::Admissibility#enforce_correction_target`, called
|
|
320
|
-
# there against the parent record/root aggregate — see that
|
|
321
|
-
# step's own comment for exactly why). Whatever field a correction
|
|
322
|
-
# actually changes is an ordinary declared `sets`/`increment`/etc.
|
|
323
|
-
# mutation of its own, applied by one of the branches above like
|
|
324
|
-
# any other — `qa/stress_domains/corrections`' own `Entry.Amend`
|
|
325
|
-
# pairs `corrects "EntryRecorded", ...` with a separate `sets
|
|
326
|
-
# :amount`, exactly this shape.
|
|
138
|
+
# `corrects`: a no-op here; the target event was already checked by
|
|
139
|
+
# EntityInterpreter#step_enforce_givens.
|
|
327
140
|
when :corrects
|
|
328
141
|
nil
|
|
329
142
|
else
|
|
330
|
-
#
|
|
331
|
-
# (MutationApplier#apply), for the same reason: applying
|
|
332
|
-
# nothing and refusing nothing would be the one silent
|
|
333
|
-
# no-op in a language that otherwise refuses what it cannot
|
|
334
|
-
# check.
|
|
143
|
+
# Backstop: an unhandled op must refuse, not silently apply nothing.
|
|
335
144
|
raise WiringError, "no entity mutation applier handles :#{mutation.op} — add one before declaring it"
|
|
336
145
|
end
|
|
337
146
|
end
|
|
338
147
|
|
|
339
|
-
# Rewraps a plain-Numeric arithmetic result into
|
|
340
|
-
#
|
|
341
|
-
#
|
|
342
|
-
# `MutationApplier#rewrap_arithmetic_result`'s own entity-scoped
|
|
343
|
-
# twin, byte-for-byte the same fix — see that method's own
|
|
344
|
-
# comment for the full "phantom-field asymmetric wrapping" story.
|
|
345
|
-
# A no-op whenever `current` was already a Value (the arithmetic
|
|
346
|
-
# call already returned one) or the mutation targets no declared
|
|
347
|
-
# attribute at all.
|
|
348
|
-
#
|
|
349
|
-
# @param aggregate [Bluebook::Aggregate] the root aggregate, for value-object
|
|
350
|
-
# coercion
|
|
351
|
-
# @param attribute [Bluebook::Attribute, nil] the mutated field's own declared
|
|
352
|
-
# attribute; a no-op when nil (targets no declared attribute)
|
|
353
|
-
# @param current [Object] the field's pre-mutation value, as read off `pre`
|
|
354
|
-
# @param result [Object] the arithmetic op's own result
|
|
355
|
-
# @return [Runtime::Value, Object] `result` unchanged when already a `Value`,
|
|
356
|
-
# `current` is a `Value`, or `attribute` is nil; otherwise `result` coerced
|
|
357
|
-
# into `attribute`'s own declared type
|
|
148
|
+
# Rewraps a plain-Numeric arithmetic result into the attribute's own Value type.
|
|
149
|
+
# A no-op when `current` was already a Value, or the mutation targets no attribute.
|
|
358
150
|
def rewrap_arithmetic_result(aggregate, attribute, current, result)
|
|
359
151
|
return result if current.is_a?(Value) || attribute.nil? || result.is_a?(Value)
|
|
360
152
|
|
|
361
153
|
Value.for_attribute(aggregate, attribute, result)
|
|
362
154
|
end
|
|
363
155
|
|
|
364
|
-
#
|
|
365
|
-
#
|
|
366
|
-
#
|
|
367
|
-
# `MutationApplier#resolve_append_source`'s own entity-scoped
|
|
368
|
-
# twin — a caller-supplied arg first, falling back to the
|
|
369
|
-
# element's own current field (never the parent instance's) when
|
|
370
|
-
# it isn't one.
|
|
371
|
-
#
|
|
372
|
-
# @param source [Symbol, Object] the mutation's own field source: a Symbol
|
|
373
|
-
# names a command argument or, failing that, an element field; anything
|
|
374
|
-
# else is returned as is
|
|
375
|
-
# @param element [Hash{Symbol => Object}] the element to fall back to reading
|
|
376
|
-
# from
|
|
377
|
-
# @param args [Hash{String, Symbol => Object}] the offered command arguments
|
|
378
|
-
# @return [Object, nil] the named argument's value; failing that, the named
|
|
379
|
-
# element field's value; `source` itself when it is not a Symbol
|
|
156
|
+
# A caller-supplied arg wins; otherwise the field falls back to the element's own
|
|
157
|
+
# value. `args.key?` so an explicit nil still counts as supplied.
|
|
380
158
|
def resolve_element_append_source(source, element, args)
|
|
381
159
|
return source unless source.is_a?(Symbol)
|
|
382
160
|
return args[source] if args.key?(source)
|
|
@@ -384,42 +162,6 @@ module Hecks
|
|
|
384
162
|
element[source]
|
|
385
163
|
end
|
|
386
164
|
|
|
387
|
-
# `MutationApplier#appended`'s own entity-scoped twin. Usually a
|
|
388
|
-
# value object element — an entity's own list, appended to by an
|
|
389
|
-
# entity-owned command, holds a value object (`Member.pairs`'
|
|
390
|
-
# own `Pair`, `Dispatch.with_spec`'s own `Binding`) the same way
|
|
391
|
-
# most real corpus appends do — but entity-in-entity nesting (a
|
|
392
|
-
# list of another entity, owned by this one) is real now too:
|
|
393
|
-
# `qa/stress_domains/nested_pieces` (`Board.AddCard`, appending a
|
|
394
|
-
# `Card` onto `Board`'s own `cards`) is the first corpus member to
|
|
395
|
-
# do it, the comment this replaces having been written before that
|
|
396
|
-
# domain existed. `element_type` naming an entity rather than a
|
|
397
|
-
# value object falls through to `fields` unchanged, same as
|
|
398
|
-
# before — `MutationApplier#entity_element`'s own identity-minting
|
|
399
|
-
# fallback still isn't mirrored here (nothing in this corpus needs
|
|
400
|
-
# auto-minting at this depth — Card supplies its own identity in
|
|
401
|
-
# the append mapping) — but its collision-checking fallback now is
|
|
402
|
-
# (BUG#145, `check_entity_collision`, below — see its own call
|
|
403
|
-
# site's comment for why "collision-checking a nested entity is its
|
|
404
|
-
# own separate, unfixed question," this comment's own prior wording,
|
|
405
|
-
# stopped being true). BUG#12's fix (below) is likewise shared:
|
|
406
|
-
# every declared attribute the append mapping doesn't name gets its
|
|
407
|
-
# own default the same way a fresh aggregate's own attributes
|
|
408
|
-
# already do (`Instance.defaults`), whichever branch built `fields`.
|
|
409
|
-
#
|
|
410
|
-
# @param aggregate [Bluebook::Aggregate] the root aggregate, for value-object
|
|
411
|
-
# coercion
|
|
412
|
-
# @param entity [Bluebook::Entity] the entity type `element`'s mutated list
|
|
413
|
-
# attribute belongs to
|
|
414
|
-
# @param element [Hash{Symbol => Object}] the element being appended to, read
|
|
415
|
-
# as the source for a field the mutation's own map does not supply directly
|
|
416
|
-
# @param mutation [Bluebook::Mutation] the declared `:append` mutation
|
|
417
|
-
# @param args [Hash{String, Symbol => Object}] the offered command arguments
|
|
418
|
-
# @return [Array] `element[mutation.target]`'s existing elements, frozen deep,
|
|
419
|
-
# with the newly built element (a `Runtime::Value`, or a Hash for an entity
|
|
420
|
-
# or untyped element) appended last
|
|
421
|
-
# @raise [Runtime::AlreadyExists] if the appended element is a nested entity
|
|
422
|
-
# whose identity collides with an existing one
|
|
423
165
|
def appended_to_element(aggregate, entity, element, mutation, args)
|
|
424
166
|
fields = mutation.source.transform_values { |source| resolve_element_append_source(source, element, args) }
|
|
425
167
|
element_type = entity.attribute(mutation.target)&.type
|
|
@@ -431,41 +173,10 @@ module Hecks
|
|
|
431
173
|
if value_object
|
|
432
174
|
Value.build(value_object, fields, aggregate)
|
|
433
175
|
else
|
|
434
|
-
# `entity.entities`, not `aggregate.entities` — a piece
|
|
435
|
-
#
|
|
436
|
-
# (`Card` is `Board.entities`, never `Workspace.entities`;
|
|
437
|
-
# `Behaviour::Entity#entities` answers direct children only,
|
|
438
|
-
# by design — see its own comment), the same lexical-nesting
|
|
439
|
-
# rule `EntityBuilder#entity_impl` builds the tree with in
|
|
440
|
-
# the first place.
|
|
176
|
+
# `entity.entities`, not `aggregate.entities` — a nested piece is a child of
|
|
177
|
+
# the owning entity, not of the root aggregate.
|
|
441
178
|
nested_entity = entity.entities.find { |piece| piece.hecks_name == element_type.to_s }
|
|
442
179
|
if nested_entity
|
|
443
|
-
# BUG#145 — `MutationApplier#entity_element`'s own
|
|
444
|
-
# `check_entity_collision` call (one hop up, an aggregate's
|
|
445
|
-
# own entity list) never had a twin here: a nested entity's
|
|
446
|
-
# own identity is always caller-supplied at this depth
|
|
447
|
-
# (`Card.sequence` rides the append mapping directly — no
|
|
448
|
-
# nested-entity auto-mint exists anywhere in this corpus,
|
|
449
|
-
# see this method's own header), so this is unconditional,
|
|
450
|
-
# unlike `#entity_element`'s own auto-mint/collision `if`/
|
|
451
|
-
# `else` split — there is no auto-mint branch here to skip.
|
|
452
|
-
# Found live: `qa/stress_domains/nested_pieces`'s own
|
|
453
|
-
# differential sweep, `Board.AddCard` dispatched twice under
|
|
454
|
-
# the same `sequence` — Ruby silently appended a second
|
|
455
|
-
# `Card`, Rust's generated code (`rust/project/mutations.rb`'s
|
|
456
|
-
# `emit_mutation_line_body`, which never splits an
|
|
457
|
-
# aggregate-owned append from an entity-owned one the way
|
|
458
|
-
# this runtime's two separate methods do) already refused
|
|
459
|
-
# `AlreadyExists` for both depths.
|
|
460
|
-
# `entity` (the owner — `Board`), not `aggregate` (the root
|
|
461
|
-
# — `Workspace`), is what the refusal names as "on {…}" —
|
|
462
|
-
# `check_entity_collision`'s first argument is only ever
|
|
463
|
-
# used for that one naming purpose (`owner.hecks_name`,
|
|
464
|
-
# below), matching Rust's own generated wording exactly
|
|
465
|
-
# (`rust/project/mutations.rb`'s own `collision_guard`
|
|
466
|
-
# passes the entity's declaring construct's name the same
|
|
467
|
-
# way — "a Card already exists on Board", never "…on
|
|
468
|
-
# Workspace").
|
|
469
180
|
check_entity_collision(entity, nested_entity, element[mutation.target], fields)
|
|
470
181
|
fill_declared_defaults(aggregate, nested_entity, fields)
|
|
471
182
|
else
|
|
@@ -475,35 +186,9 @@ module Hecks
|
|
|
475
186
|
Freezer.deep(Array(element[mutation.target]) + [appended])
|
|
476
187
|
end
|
|
477
188
|
|
|
478
|
-
#
|
|
479
|
-
#
|
|
480
|
-
#
|
|
481
|
-
# sets — `Board.label`, `Card.note`) absent from the stored hash
|
|
482
|
-
# entirely, not even a `nil` placeholder, until that later command
|
|
483
|
-
# actually ran. `rust/project/json_codec.rb#emit_to_json_flat`'s
|
|
484
|
-
# own header comment documents the opposite as the intended
|
|
485
|
-
# contract for a persisted record: every declared field present,
|
|
486
|
-
# `null` when unset, "because Ruby's own `JSON.generate(state)`
|
|
487
|
-
# round-trip this mirrors does the same" — true for a freshly
|
|
488
|
-
# created aggregate (`Instance.defaults` already fills one key per
|
|
489
|
-
# declared attribute, `default_for` per attribute), never true for
|
|
490
|
-
# an entity minted by an append. This closes that gap the same
|
|
491
|
-
# way: `Instance.default_for` is the same per-attribute default
|
|
492
|
-
# rule (nil with no declared `default:`, a fully-defaulted value
|
|
493
|
-
# object when every one of its own fields has one), reused rather
|
|
494
|
-
# than reimplemented so the two creation paths can never drift on
|
|
495
|
-
# what "the default" means. Additive only — a key `fields` already
|
|
496
|
-
# holds (the append mapping, an auto-minted identity, a lifecycle
|
|
497
|
-
# default) is never overwritten.
|
|
498
|
-
#
|
|
499
|
-
# @param aggregate [Bluebook::Aggregate] the root aggregate, for value-object
|
|
500
|
-
# coercion
|
|
501
|
-
# @param entity [Bluebook::Entity] the entity type whose declared attributes
|
|
502
|
-
# are defaulted
|
|
503
|
-
# @param fields [Hash{Symbol => Object}] the element fields already resolved;
|
|
504
|
-
# written in place
|
|
505
|
-
# @return [Hash{Symbol => Object}] `fields`, with every declared attribute it
|
|
506
|
-
# did not already hold filled with its own default
|
|
189
|
+
# Fills every declared attribute `fields` does not already hold with its own
|
|
190
|
+
# default (Instance.default_for), matching a freshly created aggregate's own
|
|
191
|
+
# per-attribute defaults. Additive only: an existing key in `fields` is kept.
|
|
507
192
|
def fill_declared_defaults(aggregate, entity, fields)
|
|
508
193
|
entity.attributes.each do |attribute|
|
|
509
194
|
next if fields.key?(attribute.name)
|
|
@@ -513,24 +198,7 @@ module Hecks
|
|
|
513
198
|
fields
|
|
514
199
|
end
|
|
515
200
|
|
|
516
|
-
#
|
|
517
|
-
# value equality, element-wise, the same "so a concurrent Add can
|
|
518
|
-
# never be lost" reasoning that method's own comment gives, unless
|
|
519
|
-
# the list this targets is itself entity-typed — see
|
|
520
|
-
# `list_element_match?`, below, which both this and
|
|
521
|
-
# `MutationApplier#removed` now share.
|
|
522
|
-
#
|
|
523
|
-
# @param rules [Runtime::CommandRules] the shared rules engine `resolve_source`
|
|
524
|
-
# is read through
|
|
525
|
-
# @param aggregate [Bluebook::Aggregate] the root aggregate, for value-object
|
|
526
|
-
# coercion
|
|
527
|
-
# @param entity [Bluebook::Entity] the entity type `element`'s mutated list
|
|
528
|
-
# attribute belongs to
|
|
529
|
-
# @param element [Hash{Symbol => Object}] the element being removed from
|
|
530
|
-
# @param mutation [Bluebook::Mutation] the declared `:remove` mutation
|
|
531
|
-
# @param args [Hash{String, Symbol => Object}] the offered command arguments
|
|
532
|
-
# @return [Array] `element[mutation.target]`'s existing elements, with any
|
|
533
|
-
# matching the resolved remove target left out
|
|
201
|
+
# Removes the element matching by value, or by identity for entity-typed lists.
|
|
534
202
|
def removed_from_element(rules, aggregate, entity, element, mutation, args)
|
|
535
203
|
value = rules.resolve_source(mutation.source, args)
|
|
536
204
|
attribute = entity.attribute(mutation.target)
|
|
@@ -538,49 +206,9 @@ module Hecks
|
|
|
538
206
|
Array(element[mutation.target]).reject { |candidate| list_element_match?(aggregate, attribute, candidate, value) }
|
|
539
207
|
end
|
|
540
208
|
|
|
541
|
-
#
|
|
542
|
-
#
|
|
543
|
-
#
|
|
544
|
-
# element and `value` are both real `Value`s there, so `==` already
|
|
545
|
-
# compares every field, the "concurrent Add can never be lost"
|
|
546
|
-
# shape `removed`/`removed_from_element`'s own headers describe.
|
|
547
|
-
# An entity-typed list is different in kind, not just in type: a
|
|
548
|
-
# stored element is a plain Hash, never a `Value` (`Entity`'s own
|
|
549
|
-
# header — "an entity must never answer .value_object"), so there
|
|
550
|
-
# is no whole-value shape to compare against at all — only the
|
|
551
|
-
# entity's own identity field, the same field a caller already has
|
|
552
|
-
# to name to address that element any other way
|
|
553
|
-
# (`element_of`'s own `wants`, above). `value` arrives here already
|
|
554
|
-
# coerced against that identity field's declared type
|
|
555
|
-
# (`Coercion#hydrate_entity_identity`, run underneath
|
|
556
|
-
# `Value.for_attribute` before either caller above ever sees it),
|
|
557
|
-
# so this only has to know which field to read off the stored
|
|
558
|
-
# element — `entity.identity_heads`'s own single head, when there
|
|
559
|
-
# is exactly one. A composite identity (more than one head, or
|
|
560
|
-
# none) has no single field a bare `remove:` target could mean —
|
|
561
|
-
# this returns `false` (never a match, the same documented no-op
|
|
562
|
-
# `hydrate_entity_identity`'s own header already commits to) rather
|
|
563
|
-
# than guessing which head, matching the narrow, honest boundary
|
|
564
|
-
# `MutationApplier#check_entity_collision`'s own header draws for
|
|
565
|
-
# entity identity elsewhere in this runtime.
|
|
566
|
-
#
|
|
567
|
-
# Shared by `MutationApplier#removed` (an aggregate's own list) and
|
|
568
|
-
# `#removed_from_element` (a list an entity owns), so the two
|
|
569
|
-
# `remove:` call sites can never quietly disagree on what
|
|
570
|
-
# "matches" means — the same reasoning this file's own header
|
|
571
|
-
# gives for centralizing `locate_chain`/`element_of` once rather
|
|
572
|
-
# than twice.
|
|
573
|
-
#
|
|
574
|
-
# @param aggregate [Bluebook::Aggregate] the root aggregate, to resolve
|
|
575
|
-
# whether `attribute`'s own type is an entity
|
|
576
|
-
# @param attribute [Bluebook::Attribute, nil] the list attribute `element`
|
|
577
|
-
# belongs to
|
|
578
|
-
# @param element [Object] the stored list element to check: a Hash for an
|
|
579
|
-
# entity-typed list, a `Runtime::Value` otherwise
|
|
580
|
-
# @param value [Object] the `remove:` target to match `element` against
|
|
581
|
-
# @return [Boolean] whole-value equality for a non-entity-typed list;
|
|
582
|
-
# identity-field equality for an entity-typed one with a single identity
|
|
583
|
-
# head; false for a composite or absent identity
|
|
209
|
+
# The match rule `remove:` uses against one stored list element. Whole-value
|
|
210
|
+
# equality for a non-entity-typed list; for an entity-typed one, matches by its
|
|
211
|
+
# single identity head (a composite or absent identity never matches).
|
|
584
212
|
def list_element_match?(aggregate, attribute, element, value)
|
|
585
213
|
entity = attribute&.list? ? Value.find_entity(aggregate, attribute.type.to_s) : nil
|
|
586
214
|
return element == value unless entity
|
|
@@ -591,59 +219,9 @@ module Hecks
|
|
|
591
219
|
element.is_a?(Hash) && element[head] == value
|
|
592
220
|
end
|
|
593
221
|
|
|
594
|
-
# Refuses a caller-supplied or composite identity that already names an
|
|
595
|
-
#
|
|
596
|
-
#
|
|
597
|
-
# BUG#13 — the same check #hydrate gives every creating
|
|
598
|
-
# aggregate command (`repository.find(id)`,
|
|
599
|
-
# `command_interpreter.rb`), one level down. Shared with
|
|
600
|
-
# `MutationApplier` (mutation_applier.rb), called from
|
|
601
|
-
# `#entity_element` — an aggregate's own entity list (`Workspace.
|
|
602
|
-
# boards`, `Ledger.entries`) — and, moved here (BUG#145), from `#appended_
|
|
603
|
-
# to_element`, above — an entity's own nested entity list one hop
|
|
604
|
-
# further in (`Board.cards`) — so neither call site reimplements
|
|
605
|
-
# it a second time, the same way `#list_element_
|
|
606
|
-
# match?` already avoids that split for `remove:`.
|
|
607
|
-
#
|
|
608
|
-
# Reached, at the aggregate-owned call site, only on the two
|
|
609
|
-
# branches that do not auto-mint: a caller-supplied identity (the
|
|
610
|
-
# field is already in the append's own field map) or a composite
|
|
611
|
-
# one (`entity.identified_by` is nil for those — Runtime::
|
|
612
|
-
# Identified#derive_identity). Without this, neither checks the sibling
|
|
613
|
-
# list at all: a second LogVisit with the same date+sequence, or a
|
|
614
|
-
# second IssueKey with the same serial, would append a silent
|
|
615
|
-
# duplicate — worse than an ordinary duplicate row, because
|
|
616
|
-
# `EntityElement#element_of`'s own `find_index` always matches the
|
|
617
|
-
# first match, so the second becomes permanently unaddressable by
|
|
618
|
-
# any later command. At the entity-owned call site (`#appended_to_
|
|
619
|
-
# element`), there is no auto-mint branch at all — every caller
|
|
620
|
-
# reaches this unconditionally, since a nested entity's own
|
|
621
|
-
# identity is always caller-supplied in this corpus.
|
|
622
|
-
#
|
|
623
|
-
# Auto-minted (aggregate-owned) entities never reach here —
|
|
624
|
-
# `identity_heads` for them is still checked at mint time by
|
|
625
|
-
# construction (`current.size + 1` can only repeat if something
|
|
626
|
-
# `remove:`s from the list between mints, which no real domain does
|
|
627
|
-
# today), so they can't be flagged by mistake.
|
|
628
|
-
# `owner` — the declaring construct named in the "…already exists on
|
|
629
|
-
# {owner}" wording: the root aggregate at the aggregate-owned call
|
|
630
|
-
# site (`Workspace`, `Ledger`), the immediately-enclosing entity at
|
|
631
|
-
# the entity-owned one (`Board` — never the root `Workspace` two
|
|
632
|
-
# hops up). Used for that naming purpose only (`owner.hecks_name`) —
|
|
633
|
-
# never for `Value`/namespace resolution, which is why an `Entity`
|
|
634
|
-
# (not just an `Aggregate`) is a valid thing to pass here.
|
|
635
|
-
#
|
|
636
|
-
# @param owner [Bluebook::Aggregate, Bluebook::Entity] the construct named
|
|
637
|
-
# in a refusal as what the duplicate "already exists on"
|
|
638
|
-
# @param entity [Bluebook::Entity] the entity type being checked for a
|
|
639
|
-
# colliding identity
|
|
640
|
-
# @param current [Array<Hash>, Object] the entity's own existing elements;
|
|
641
|
-
# coerced through `Array()`, so a single element or nil is also accepted
|
|
642
|
-
# @param fields [Hash{Symbol => Object}] the new element's own fields, whose
|
|
643
|
-
# identity heads are checked against every element in `current`
|
|
644
|
-
# @return [void]
|
|
645
|
-
# @raise [Runtime::AlreadyExists] if `current` already holds an element
|
|
646
|
-
# whose identity heads match `fields`'s own
|
|
222
|
+
# Refuses a caller-supplied or composite identity that already names an element
|
|
223
|
+
# in `current`. Shared by the aggregate-owned and entity-owned append paths so
|
|
224
|
+
# neither reimplements the check. `owner` is named in the refusal only.
|
|
647
225
|
def check_entity_collision(owner, entity, current, fields)
|
|
648
226
|
heads = entity.identity_heads
|
|
649
227
|
return if heads.empty?
|