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
|
@@ -1,688 +0,0 @@
|
|
|
1
|
-
module Hecks
|
|
2
|
-
module Fuzzing
|
|
3
|
-
module Properties
|
|
4
|
-
# **A dry run leaves no trace** — `Dispatcher#dry_run?`'s whole contract
|
|
5
|
-
# ("the command evaluated hypothetically, nothing saved or emitted,
|
|
6
|
-
# no reaction"), held to the store rather than trusted: `Replay`
|
|
7
|
-
# snapshots every instance and the event count on either side of
|
|
8
|
-
# each `{"dry_run": …}` step, in a separate `dry_run_traces` array
|
|
9
|
-
# (same order as `history[:dry_runs]`, never merged into it — that
|
|
10
|
-
# array stays the exact `{verb:, ok:, error?:}` shape the compiled
|
|
11
|
-
# Rust binary's own `dry_run` answers, so `spec/rust_conformance_
|
|
12
|
-
# spec.rb`'s direct comparison against it never sees a key Rust
|
|
13
|
-
# doesn't have), and this is the comparison. Refused or accepted
|
|
14
|
-
# makes no difference — a hypothetical that was refused had even
|
|
15
|
-
# less business writing anything. Entries without the snapshots (a
|
|
16
|
-
# hand-built history, an older corpus) are skipped, not failed: no
|
|
17
|
-
# claim, no finding.
|
|
18
|
-
module DryRuns
|
|
19
|
-
# Checks that every dry-run step left neither the event count nor any
|
|
20
|
-
# instance's state changed.
|
|
21
|
-
#
|
|
22
|
-
# @param history [Hash] a replayed history, as returned by `Fuzzing::Replay.call`
|
|
23
|
-
# @return [true, String] true if every dry-run trace shows no change; otherwise
|
|
24
|
-
# a semicolon-joined message naming each offending dry run
|
|
25
|
-
def dry_runs_leave_no_trace(history)
|
|
26
|
-
offenders = Array(history[:dry_run_traces]).filter_map do |entry|
|
|
27
|
-
before = entry[:before]
|
|
28
|
-
after = entry[:after]
|
|
29
|
-
next unless before && after
|
|
30
|
-
|
|
31
|
-
traces = []
|
|
32
|
-
traces << "events #{before[:events]} -> #{after[:events]}" unless before[:events] == after[:events]
|
|
33
|
-
traces << "instances changed" unless before[:instances] == after[:instances]
|
|
34
|
-
next if traces.empty?
|
|
35
|
-
|
|
36
|
-
"dry run of #{entry[:verb]} (ok: #{entry[:ok]}) left a trace: #{traces.join(', ')}"
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
offenders.empty? || offenders.join("; ")
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# Dispatch-binding and mutation-recomputation properties: a saga/
|
|
44
|
-
# policy dispatch is bound to the value its own with_spec names, and a
|
|
45
|
-
# command's append/remove/multiply/clamp mutations land on the same
|
|
46
|
-
# after-state an independent recomputation of the same rule produces.
|
|
47
|
-
|
|
48
|
-
# Holds dispatch_binding_fidelity and mutations_match_recompute, plus
|
|
49
|
-
# the independently-written re-derivations (#resolve_dispatch_binding,
|
|
50
|
-
# #resolve_trigger_binding, #recompute_append/#remove/#multiply/#clamp)
|
|
51
|
-
# each checks the real dispatch/mutation against.
|
|
52
|
-
module DispatchAndMutations
|
|
53
|
-
# Closes Handler#dispatches and — the same shape, one construct
|
|
54
|
-
# over — Policy#with_spec (Dispatch#command_name/Dispatch#with_spec
|
|
55
|
-
# are not claimable feature strings at all — see FEATURE_COVERAGE's
|
|
56
|
-
# own comment on this entry). history[:saga_dispatches]/[:policy_dispatches]
|
|
57
|
-
# (Registry#saga_dispatch_log/#policy_dispatch_log — additive,
|
|
58
|
-
# Ruby-only, never touching saga_log/reaction_log, the byte-for-
|
|
59
|
-
# byte shape spec/rust_conformance_spec.rb holds Rust to) each
|
|
60
|
-
# carry the raw inputs a dispatch's own args were resolved from,
|
|
61
|
-
# captured live at the moment the resolution actually ran — a
|
|
62
|
-
# saga's own memory keeps changing across a run, so re-deriving
|
|
63
|
-
# from history[:saga_instances]'s final memory (the only other
|
|
64
|
-
# place it would be visible) would grade the wrong moment,
|
|
65
|
-
# lifecycle_guard_and_given_violations_are_refused's own false
|
|
66
|
-
# positive one item earlier, in a different shape.
|
|
67
|
-
#
|
|
68
|
-
# #resolve_dispatch_binding/#resolve_trigger_binding are separate,
|
|
69
|
-
# independently-written re-derivations of SagaInterpreter#
|
|
70
|
-
# dispatch_args/PolicyInterpreter#trigger_args's own resolution —
|
|
71
|
-
# never calling either method again, which would only ever agree
|
|
72
|
-
# with itself. Exactly the class of bug this closes: "a wrong
|
|
73
|
-
# argument binding on a fan-out dispatch that produces a perfectly
|
|
74
|
-
# normal-looking log entry (`delivered: true`) and would only ever
|
|
75
|
-
# surface as a downstream assertion failure, if it surfaces at
|
|
76
|
-
# all" — a defect class one level over from what
|
|
77
|
-
# `fanout_dispatches_once_per_matching_row` already catches.
|
|
78
|
-
#
|
|
79
|
-
# Real targets: Settlement (mixed literal/correlation-head/event-
|
|
80
|
-
# payload/memory-fallback bindings across three legs, plus a
|
|
81
|
-
# compensation leg that deliberately omits `reference:`, a field
|
|
82
|
-
# the forward Credit leg carries), ExternalSettlement, Onboarding
|
|
83
|
-
# (no compensation leg, by design — nothing to check there beyond
|
|
84
|
-
# the forward leg's own event-payload binding).
|
|
85
|
-
#
|
|
86
|
-
# @param history [Hash] a replayed history, as returned by `Fuzzing::Replay.call`
|
|
87
|
-
# @return [true, String] true if every saga and policy dispatch's own bound args
|
|
88
|
-
# match an independent re-derivation of their `with_spec`; otherwise a
|
|
89
|
-
# semicolon-joined message naming each offending dispatch
|
|
90
|
-
def dispatch_binding_fidelity(history)
|
|
91
|
-
saga_offenders = history.fetch(:saga_dispatches, []).filter_map do |entry|
|
|
92
|
-
expected = resolve_dispatch_binding(entry)
|
|
93
|
-
next if expected == entry[:args]
|
|
94
|
-
|
|
95
|
-
"#{entry[:process_manager]}##{entry[:instance]} dispatching #{entry[:dispatch]} on #{entry[:on]} — " \
|
|
96
|
-
"bound #{entry[:args].inspect}, but independently re-deriving with_spec's own resolution gives " \
|
|
97
|
-
"#{expected.inspect}"
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
policy_offenders = history.fetch(:policy_dispatches, []).filter_map do |entry|
|
|
101
|
-
expected = resolve_trigger_binding(entry)
|
|
102
|
-
next if expected == entry[:args]
|
|
103
|
-
|
|
104
|
-
"#{entry[:policy]} on #{entry[:on]} — bound #{entry[:args].inspect}, but independently re-deriving " \
|
|
105
|
-
"with_spec's own resolution gives #{expected.inspect}"
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
offenders = saga_offenders + policy_offenders
|
|
109
|
-
offenders.empty? || offenders.join("; ")
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
# SagaInterpreter#dispatch_args's own 4-branch resolution,
|
|
113
|
-
# reproduced independently: a literal, the correlation key itself,
|
|
114
|
-
# the current triggering event's own payload, or — the fallback —
|
|
115
|
-
# the saga's own carried memory (seeded from the starting event's
|
|
116
|
-
# payload, at begin_saga).
|
|
117
|
-
#
|
|
118
|
-
# @param entry [Hash] one `history[:saga_dispatches]` entry: at least
|
|
119
|
-
# `:with_spec`, `:correlation_head`, `:instance`, `:event_payload`, and `:memory`
|
|
120
|
-
# @return [Hash{Symbol => Object}] the resolved, materialized args `with_spec`
|
|
121
|
-
# should produce, keyed by symbol
|
|
122
|
-
def resolve_dispatch_binding(entry)
|
|
123
|
-
entry[:with_spec].to_h do |key, value|
|
|
124
|
-
resolved = if !value.is_a?(Symbol) then value
|
|
125
|
-
elsif value == entry[:correlation_head] then entry[:instance]
|
|
126
|
-
elsif entry[:event_payload].key?(value) then entry[:event_payload][value]
|
|
127
|
-
else entry[:memory][value]
|
|
128
|
-
end
|
|
129
|
-
[key.to_sym, Runtime::Value.materialize(resolved)]
|
|
130
|
-
end
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
# PolicyInterpreter#trigger_args's own 2-branch resolution — a
|
|
134
|
-
# policy holds no correlation and no memory, so `payload` (the
|
|
135
|
-
# triggering event's own payload, already merged with a fan-out
|
|
136
|
-
# row's id when there is one) is the whole source.
|
|
137
|
-
#
|
|
138
|
-
# @param entry [Hash] one `history[:policy_dispatches]` entry: at least
|
|
139
|
-
# `:with_spec` and `:payload`
|
|
140
|
-
# @return [Hash{Symbol => Object}] the resolved, materialized args `with_spec`
|
|
141
|
-
# should produce, keyed by symbol
|
|
142
|
-
def resolve_trigger_binding(entry)
|
|
143
|
-
entry[:with_spec].to_h do |key, value|
|
|
144
|
-
resolved = value.is_a?(Symbol) ? entry[:payload][value] : value
|
|
145
|
-
[key.to_sym, Runtime::Value.materialize(resolved)]
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
# Closes Command#mutations — the last of the five, and the
|
|
150
|
-
# largest: no real corpus entity anywhere uses `append`/`remove`/
|
|
151
|
-
# `multiply`/`clamp` (every real aggregate-owned mutation is
|
|
152
|
-
# aggregate-scoped — Account.Credit's :ledger, LogVisit's
|
|
153
|
-
# :visits); the only entity-owned use of these four ops in the
|
|
154
|
-
# whole repository is spec/fixtures/entity_list_mutations, now a
|
|
155
|
-
# real, bootable, Memory-default domain (no .hecksagon needed at
|
|
156
|
-
# all — a domain with none boots every aggregate against Memory
|
|
157
|
-
# by construction, confirmed live) rather than the raw-Kernel.
|
|
158
|
-
# load-only fixture it was.
|
|
159
|
-
#
|
|
160
|
-
# `history[:mutation_traces]` (Replay's own bounded, additive
|
|
161
|
-
# extension — see #build_mutation_trace's own comment) carries a
|
|
162
|
-
# per-step before/after snapshot of the entity element an
|
|
163
|
-
# entity-dispatched command's own mutations acted on, materialized
|
|
164
|
-
# to plain data, plus the step's own raw args — the delta
|
|
165
|
-
# `aggregation_matches_recompute` never had to ask for, because
|
|
166
|
-
# count/median are pure functions of final state and a mutation
|
|
167
|
-
# is not (the same "captured live, not re-derived from final
|
|
168
|
-
# state" lesson item 8's own saga_dispatch_log already learned).
|
|
169
|
-
#
|
|
170
|
-
# #recompute_append/#recompute_remove/#recompute_multiply/
|
|
171
|
-
# #recompute_clamp/#recompute_set are separate, independently-
|
|
172
|
-
# written reproductions of EntityElement#appended_to_element/
|
|
173
|
-
# #removed_from_element/#apply_to_element's own `:set` branch and
|
|
174
|
-
# CommandRules::Arithmetic#multiply/#clamp — never calling either
|
|
175
|
-
# again, which would only ever agree with itself. Every one of
|
|
176
|
-
# these five reproduces the entity-scoped applier specifically
|
|
177
|
-
# (`EntityElement#apply_to_element`, entity_element.rb), never
|
|
178
|
-
# the aggregate-level `MutationApplier#apply` (mutation_applier.rb)
|
|
179
|
-
# — `build_mutation_trace` (replay.rb) only ever captures an
|
|
180
|
-
# entity-owned command's own mutation in the first place (that
|
|
181
|
-
# method's own `command_name&.include?(".")` guard), so there is
|
|
182
|
-
# no aggregate-level trace for this property to ever compare
|
|
183
|
-
# against `owner_for_verb`'s own comment draws the identical
|
|
184
|
-
# aggregate/owner distinction for `:append`. `:increment`/
|
|
185
|
-
# `:decrement` remain out of scope (CommandRules::Arithmetic#
|
|
186
|
-
# arithmetic's own VO-vs-scalar branching and INT64_RANGE overflow
|
|
187
|
-
# checks are a materially larger reproduction than the other five
|
|
188
|
-
# ops combined — scoped out of this pass, not forgotten; see
|
|
189
|
-
# docs/decisions/0056); a command mixing them with a recomputable
|
|
190
|
-
# op still gets the recomputable one checked.
|
|
191
|
-
#
|
|
192
|
-
# `:set` closes a real, separate gap from the other four — this is
|
|
193
|
-
# not "the fifth op of a symmetrical set." `self_consistency.rb`'s
|
|
194
|
-
# own rehydration/idempotency checks (lib/hecks/fuzzing/
|
|
195
|
-
# self_consistency.rb) can never catch a bug in `EntityElement#
|
|
196
|
-
# apply_to_element`'s `:set` branch (entity_element.rb) no matter
|
|
197
|
-
# how much they run: both the "live" state they snapshot and the
|
|
198
|
-
# "rehydrated" state they fold from `Ports::Persistence::
|
|
199
|
-
# AppendOnly`'s own journal trace back to the same single
|
|
200
|
-
# `step_apply_mutations` call (entity_interpreter.rb) — the
|
|
201
|
-
# journal holds the full POST-mutation state, not a delta (that
|
|
202
|
-
# file's own header), so a wrong `:set` result is already baked
|
|
203
|
-
# into both sides of that comparison before either one runs. A
|
|
204
|
-
# Ruby/Rust differential check is subject to the identical
|
|
205
|
-
# structural blind spot whenever Rust's own generated `:set`
|
|
206
|
-
# handling was derived from — and so shares — the same
|
|
207
|
-
# misunderstanding Ruby's implementation has. `#recompute_set`
|
|
208
|
-
# is a genuinely third computation, independent of both: it
|
|
209
|
-
# re-derives the expected value from the mutation's own declared
|
|
210
|
-
# `source` (an argument or a literal — matching `apply_to_
|
|
211
|
-
# element`'s own `resolve_source`, which unlike the aggregate-
|
|
212
|
-
# level `apply` never special-cases a `StateRef` source at all)
|
|
213
|
-
# and the step's own captured `args`, then compares against what
|
|
214
|
-
# the real dispatch actually stored — closing exactly the class
|
|
215
|
-
# of bug the self-consistency and (whenever Rust's codegen shares
|
|
216
|
-
# a ruby-derived misunderstanding) differential checks cannot see.
|
|
217
|
-
# `spec/fuzzing/mutation_set_self_correctness_spec.rb` proves this
|
|
218
|
-
# concretely: a defect planted directly in `EntityElement#apply_
|
|
219
|
-
# to_element`'s `:set` branch leaves `SelfConsistency.check`
|
|
220
|
-
# completely clean while `mutations_match_recompute` names it.
|
|
221
|
-
#
|
|
222
|
-
# `:unrecomputable` (never compared, never a finding) covers the
|
|
223
|
-
# generator's own deliberate arg-malforming (`StepBuilder#malform`)
|
|
224
|
-
# landing a non-Numeric amount/non-2-element bounds where
|
|
225
|
-
# multiply/clamp need one — the same shape `guard_check`'s own
|
|
226
|
-
# AbsentArgument false positive taught: a step whose raw material
|
|
227
|
-
# doesn't fit the op's own contract is inconclusive, not a claimed
|
|
228
|
-
# mismatch. `#recompute_set` returns it for the same reason,
|
|
229
|
-
# whenever `Value.for_attribute`'s own coercion door raises on the
|
|
230
|
-
# resolved raw source (malformed fuzzer input, never a real
|
|
231
|
-
# corpus shape).
|
|
232
|
-
RECOMPUTABLE_MUTATION_OPS = %i[append remove multiply clamp set].freeze
|
|
233
|
-
|
|
234
|
-
# Checks that every recomputable mutation an entity-owned command
|
|
235
|
-
# applied landed on the same after-state an independent
|
|
236
|
-
# recomputation of the same rule produces.
|
|
237
|
-
#
|
|
238
|
-
# @param history [Hash] a replayed history, as returned by `Fuzzing::Replay.call`
|
|
239
|
-
# @return [true, String] true if every recomputable mutation's after-state
|
|
240
|
-
# matches an independent recomputation; otherwise a semicolon-joined message
|
|
241
|
-
# naming each offending mutation
|
|
242
|
-
def mutations_match_recompute(history)
|
|
243
|
-
bluebooks = history.fetch(:bluebooks)
|
|
244
|
-
|
|
245
|
-
offenders = history.fetch(:mutation_traces, []).flat_map do |entry|
|
|
246
|
-
next [] unless entry[:after]
|
|
247
|
-
|
|
248
|
-
command = command_for_verb(bluebooks, entry[:verb])
|
|
249
|
-
next [] unless command
|
|
250
|
-
|
|
251
|
-
aggregate = aggregate_for_verb(bluebooks, entry[:verb])
|
|
252
|
-
next [] unless aggregate
|
|
253
|
-
|
|
254
|
-
owner = owner_for_verb(bluebooks, entry[:verb]) || aggregate
|
|
255
|
-
|
|
256
|
-
command.mutations.select { |m| RECOMPUTABLE_MUTATION_OPS.include?(m.op) }.filter_map do |mutation|
|
|
257
|
-
expected = recompute_mutation(mutation, entry[:before][mutation.target], entry[:args], entry[:before],
|
|
258
|
-
aggregate, command, owner)
|
|
259
|
-
next if expected == :unrecomputable
|
|
260
|
-
|
|
261
|
-
actual = entry[:after][mutation.target]
|
|
262
|
-
next if symbolize_deep(expected) == symbolize_deep(actual)
|
|
263
|
-
|
|
264
|
-
"#{entry[:verb]} — #{mutation.op} on #{mutation.target} — recomputing independently gives " \
|
|
265
|
-
"#{expected.inspect}, but the real dispatch left #{actual.inspect}"
|
|
266
|
-
end
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
offenders.empty? || offenders.join("; ")
|
|
270
|
-
end
|
|
271
|
-
|
|
272
|
-
# `command_for_verb`'s own root-aggregate half (Guards) —
|
|
273
|
-
# re-derived independently rather than read off `entry[:domain]`/
|
|
274
|
-
# `entry[:aggregate]` (present on a real `build_mutation_trace`
|
|
275
|
-
# entry, but not on every hand-built fixture this property is
|
|
276
|
-
# tested against) so this works from `entry[:verb]` alone, the
|
|
277
|
-
# one field every entry always carries. BUG#5's fix needs the
|
|
278
|
-
# root aggregate specifically — `Value.for_attribute` resolves a
|
|
279
|
-
# value-object type against the root's own namespace only, the
|
|
280
|
-
# same reason `EntityElement#locate_chain` threads `root_aggregate`
|
|
281
|
-
# through every hop separately from each hop's own `owner`.
|
|
282
|
-
#
|
|
283
|
-
# @param bluebooks [Hash{String => Bluebook::Chapter}] every loaded domain,
|
|
284
|
-
# keyed by domain name
|
|
285
|
-
# @param verb [String] the dispatched verb to resolve
|
|
286
|
-
# @return [Bluebook::Aggregate, nil] the root aggregate `verb`'s domain and
|
|
287
|
-
# aggregate name, or nil if `verb` names no domain
|
|
288
|
-
def aggregate_for_verb(bluebooks, verb)
|
|
289
|
-
domain_name, aggregate_name, = Naming.split_verb(verb)
|
|
290
|
-
return nil unless domain_name
|
|
291
|
-
|
|
292
|
-
bluebooks[domain_name]&.aggregate(aggregate_name)
|
|
293
|
-
end
|
|
294
|
-
|
|
295
|
-
# `mutation.target`'s own declaring construct — the root
|
|
296
|
-
# aggregate for an aggregate-owned command (`AddSlot`'s own
|
|
297
|
-
# `:slots`), or the entity a dot-shaped command belongs to
|
|
298
|
-
# (`Board.AddCard`'s own `:cards`, declared on `Board`, not on
|
|
299
|
-
# `Workspace`) — needed only by BUG#12's own recompute-side fix
|
|
300
|
-
# (`recompute_append`, below): `entity.attribute(mutation.target)`
|
|
301
|
-
# has to be asked of whichever construct actually declares it,
|
|
302
|
-
# never the root aggregate unconditionally, the same distinction
|
|
303
|
-
# `EntityElement#locate_chain` draws between `root_aggregate` and
|
|
304
|
-
# each hop's own `owner`. Re-derived independently from
|
|
305
|
-
# `entry[:verb]` alone, the same reasoning `aggregate_for_verb`'s
|
|
306
|
-
# own comment gives.
|
|
307
|
-
#
|
|
308
|
-
# @param bluebooks [Hash{String => Bluebook::Chapter}] every loaded domain,
|
|
309
|
-
# keyed by domain name
|
|
310
|
-
# @param verb [String] the dispatched verb to resolve
|
|
311
|
-
# @return [Bluebook::Aggregate, Bluebook::Entity, nil] the construct that
|
|
312
|
-
# declares the mutation's own target: the root aggregate for an
|
|
313
|
-
# aggregate-owned command, or the owning entity for a dot-shaped
|
|
314
|
-
# (entity-owned) command; nil if `verb` names no command path or no
|
|
315
|
-
# resolvable aggregate
|
|
316
|
-
def owner_for_verb(bluebooks, verb)
|
|
317
|
-
domain_name, aggregate_name, command_path = Naming.split_verb(verb)
|
|
318
|
-
return nil unless command_path
|
|
319
|
-
|
|
320
|
-
aggregate = bluebooks[domain_name]&.aggregate(aggregate_name)
|
|
321
|
-
return nil unless aggregate
|
|
322
|
-
return aggregate unless command_path.include?(".")
|
|
323
|
-
|
|
324
|
-
entity_name, = command_path.split(".", 2)
|
|
325
|
-
aggregate.entities.find { |candidate| candidate.hecks_name == entity_name }
|
|
326
|
-
end
|
|
327
|
-
|
|
328
|
-
# Dispatches to the recompute rule for `mutation.op`.
|
|
329
|
-
#
|
|
330
|
-
# @param mutation [Bluebook::Mutation] the mutation to recompute
|
|
331
|
-
# @param current [Object] the target field's before-value, as captured in
|
|
332
|
-
# `entry[:before]`
|
|
333
|
-
# @param args [Hash] the step's own raw args, as captured in `entry[:args]`
|
|
334
|
-
# @param before_scope [Hash] the entity element's own before-state
|
|
335
|
-
# (`entry[:before]`), the source an append's caller-omitted field resolves against
|
|
336
|
-
# @param aggregate [Bluebook::Aggregate] the root aggregate, for value-object
|
|
337
|
-
# namespace resolution
|
|
338
|
-
# @param command [Bluebook::Command] the command that declared `mutation`
|
|
339
|
-
# @param owner [Bluebook::Aggregate, Bluebook::Entity] the construct that
|
|
340
|
-
# declares `mutation.target`; defaults to `aggregate`
|
|
341
|
-
# @return [Object, Symbol] the recomputed after-value for `mutation.op`, or
|
|
342
|
-
# `:unrecomputable` if the raw material doesn't fit the op's own contract
|
|
343
|
-
def recompute_mutation(mutation, current, args, before_scope, aggregate, command, owner = aggregate)
|
|
344
|
-
case mutation.op
|
|
345
|
-
when :append
|
|
346
|
-
recompute_append(current, mutation.source, before_scope, args, aggregate, command, owner, mutation.target)
|
|
347
|
-
when :remove then recompute_remove(current, mutation.source, args)
|
|
348
|
-
when :multiply then recompute_multiply(current, resolve_mutation_source(mutation.source, args))
|
|
349
|
-
when :clamp then recompute_clamp(current, mutation.source)
|
|
350
|
-
when :set then recompute_set(mutation.source, args, aggregate, owner, mutation.target)
|
|
351
|
-
end
|
|
352
|
-
end
|
|
353
|
-
|
|
354
|
-
# `EntityElement#apply_to_element`'s own `:set` branch (entity_
|
|
355
|
-
# element.rb), reproduced independently — not `MutationApplier#
|
|
356
|
-
# apply`'s aggregate-level twin (mutation_applier.rb), a
|
|
357
|
-
# different method with a different shape: `build_mutation_trace`
|
|
358
|
-
# (replay.rb) only ever captures an entity-owned command's own
|
|
359
|
-
# mutation (`command_name&.include?(".")`, that method's own
|
|
360
|
-
# header), so `mutations_match_recompute` can only ever be
|
|
361
|
-
# checking `apply_to_element`'s branch, never `apply`'s — the
|
|
362
|
-
# same distinction `owner_for_verb`'s own comment already draws
|
|
363
|
-
# for `:append`. Confirmed by reading `apply_to_element` directly:
|
|
364
|
-
# its own `:set` branch resolves the source through `rules.
|
|
365
|
-
# resolve_source` unconditionally (no `StateRef` branch at all —
|
|
366
|
-
# unlike the aggregate-level `apply`, an entity-owned `sets` has
|
|
367
|
-
# no declared corpus site using `state(:x)` today, so this
|
|
368
|
-
# reproduces what ships, not a hypothetical), reads the
|
|
369
|
-
# attribute off `entity.attribute(mutation.target)` (the owning
|
|
370
|
-
# entity, `owner` here — `Board`, never `Workspace`), and coerces
|
|
371
|
-
# through `Value.for_attribute(aggregate, attribute, value)`
|
|
372
|
-
# (the root aggregate, for value-object namespace resolution
|
|
373
|
-
# only — `value_object_for(aggregate, attribute.type)` — the
|
|
374
|
-
# same aggregate/owner split `owner_for_verb`'s own comment
|
|
375
|
-
# explains for `recompute_append`). Coercing against
|
|
376
|
-
# `owner.attribute` rather than `aggregate.attribute` matters
|
|
377
|
-
# concretely: `NestedPieces::Workspace.Board.Label` (`sets
|
|
378
|
-
# :label`) has no `:label` attribute on `Workspace` at all, so
|
|
379
|
-
# coercing against the aggregate would silently pass the raw,
|
|
380
|
-
# uncoerced String through instead of wrapping it as
|
|
381
|
-
# `BoardLabel`, and the comparison below would then disagree with
|
|
382
|
-
# the real, correctly-coerced `after` state on every single run —
|
|
383
|
-
# a real false positive, confirmed by running this against
|
|
384
|
-
# `qa/stress_domains/nested_pieces`, not by inspection alone.
|
|
385
|
-
#
|
|
386
|
-
# Reusing `Value.for_attribute` for the coercion step, rather
|
|
387
|
-
# than re-deriving it, is the same "coercion is its own already-
|
|
388
|
-
# guaranteed door" reasoning `GUARANTEED_BY_CONSTRUCTION` states
|
|
389
|
-
# and `#coerce_recompute_append_arg` already leans on for BUG#5 —
|
|
390
|
-
# what this property exists to check is the source resolution and
|
|
391
|
-
# routing (did the right raw value, from the right source, land
|
|
392
|
-
# on the right target?), not whether `Value.for_attribute` itself
|
|
393
|
-
# coerces correctly (a separate, already-enforced concern).
|
|
394
|
-
# Rescued broadly: a `:set` mutation trace is only ever captured
|
|
395
|
-
# after a real, already-admitted dispatch (`Replay#build_
|
|
396
|
-
# mutation_trace`'s own header), so a raise here means this
|
|
397
|
-
# recomputation resolved the wrong raw material, not that the
|
|
398
|
-
# real dispatch was itself malformed — `:unrecomputable`, not a
|
|
399
|
-
# crash, the same discipline every other branch in this method
|
|
400
|
-
# already follows for the generator's own deliberate malforming.
|
|
401
|
-
#
|
|
402
|
-
# @param source [Symbol, Object] the mutation's own declared source: an argument
|
|
403
|
-
# name to resolve from `args`, or a literal value already
|
|
404
|
-
# @param args [Hash] the step's own raw args
|
|
405
|
-
# @param aggregate [Bluebook::Aggregate] the root aggregate, for value-object
|
|
406
|
-
# namespace resolution
|
|
407
|
-
# @param owner [Bluebook::Aggregate, Bluebook::Entity, nil] the construct that
|
|
408
|
-
# declares `target`
|
|
409
|
-
# @param target [Symbol, nil] the mutation's own target field name
|
|
410
|
-
# @return [Object, Symbol] the recomputed, materialized value, or
|
|
411
|
-
# `:unrecomputable` if resolving or coercing the source raises
|
|
412
|
-
def recompute_set(source, args, aggregate, owner, target)
|
|
413
|
-
raw = resolve_mutation_source(source, args)
|
|
414
|
-
attribute = owner&.attribute(target)
|
|
415
|
-
coerced = attribute ? Runtime::Value.for_attribute(aggregate, attribute, raw) : raw
|
|
416
|
-
Runtime::Value.materialize(coerced)
|
|
417
|
-
rescue StandardError
|
|
418
|
-
:unrecomputable
|
|
419
|
-
end
|
|
420
|
-
|
|
421
|
-
# `EntityElement#appended_to_element`'s own field-mapping half,
|
|
422
|
-
# reproduced: the field map resolved the same two-tier way
|
|
423
|
-
# (`MutationApplier#resolve_append_source` — a caller-supplied
|
|
424
|
-
# arg, or the entity's own current field), then appended. (An
|
|
425
|
-
# entity-dispatched command's own mutations do reach here —
|
|
426
|
-
# `#build_mutation_trace`'s own comment describing them as never
|
|
427
|
-
# reaching "the entity_element branch" means `MutationApplier#
|
|
428
|
-
# appended`'s own aggregate-level entity_element fallback
|
|
429
|
-
# specifically, which really is unreached from here; entity-owned
|
|
430
|
-
# append dispatches go through `EntityElement#appended_to_element`
|
|
431
|
-
# instead, and do reach this method.)
|
|
432
|
-
#
|
|
433
|
-
# BUG#5 — an entity-owned `:append` whose target field is itself
|
|
434
|
-
# value-object-typed (`Board.AddCard`'s own `sets :cards, append:
|
|
435
|
-
# { sequence: :sequence }`, `CardSequence`-typed). A caller-
|
|
436
|
-
# supplied arg reaches the real applier (`EntityElement#
|
|
437
|
-
# appended_to_element`) already coerced: `Interpreting#
|
|
438
|
-
# coerce_declared_arguments` runs `Value.for_attribute` over
|
|
439
|
-
# every arg the acting command itself declares, before dispatch
|
|
440
|
-
# ever reaches a mutation applier at all — independent of, and
|
|
441
|
-
# earlier than, anything `appended_to_element`'s own value_object
|
|
442
|
-
# check does. `before_scope[source]` (the entity's own current
|
|
443
|
-
# field) needs no such re-coercion here: it's already the
|
|
444
|
-
# materialized shape `build_mutation_trace` snapshotted it in
|
|
445
|
-
# (`Value.materialize`, same as `entry[:after]`), not a raw value
|
|
446
|
-
# sitting behind a live `Value`.
|
|
447
|
-
#
|
|
448
|
-
# BUG#12 — `owner`/`target` (new here) let this also reproduce
|
|
449
|
-
# `EntityElement#fill_declared_defaults`'s own entity-nested-in-
|
|
450
|
-
# entity fallback (`appended_to_element`'s `else` branch, when
|
|
451
|
-
# the appended element is itself an entity — `Card`, nested
|
|
452
|
-
# inside `Board` — not a value object): `owner.attribute(target)
|
|
453
|
-
# &.type` names the appended element's own type; when that names
|
|
454
|
-
# an entity of `aggregate` rather than a value object, every one
|
|
455
|
-
# of its own declared attributes `fields` doesn't already hold
|
|
456
|
-
# gets `Instance.default_for`'s own default — reused, not
|
|
457
|
-
# reimplemented, for the identical "never agree with itself"
|
|
458
|
-
# reason BUG#5's own coercion re-derivation above already gives:
|
|
459
|
-
# `Instance.default_for` is pre-existing, independently-tested
|
|
460
|
-
# machinery (an ordinary aggregate's own creation already runs
|
|
461
|
-
# through it via `Instance.defaults`), not the new glue
|
|
462
|
-
# (`fill_declared_defaults` itself) this property exists to
|
|
463
|
-
# catch a drift in.
|
|
464
|
-
#
|
|
465
|
-
# @param current [Object] the target field's before-value (an Array of elements)
|
|
466
|
-
# @param source_map [Hash{Symbol => Object}] the append mutation's own field map:
|
|
467
|
-
# each appended field's name to its source (an argument name Symbol, or a literal)
|
|
468
|
-
# @param before_scope [Hash] the entity element's own before-state, the source a
|
|
469
|
-
# caller-omitted field resolves against
|
|
470
|
-
# @param args [Hash] the step's own raw args
|
|
471
|
-
# @param aggregate [Bluebook::Aggregate] the root aggregate, for value-object
|
|
472
|
-
# namespace resolution
|
|
473
|
-
# @param command [Bluebook::Command] the command that declared the mutation, for
|
|
474
|
-
# coercing a caller-supplied arg
|
|
475
|
-
# @param owner [Bluebook::Aggregate, Bluebook::Entity] the construct whose nested
|
|
476
|
-
# entity `target` may declare; defaults to `aggregate`
|
|
477
|
-
# @param target [Symbol, nil] the append mutation's own target field name
|
|
478
|
-
# @return [Array] `current` (coerced to an Array) with the newly built,
|
|
479
|
-
# symbol-keyed element appended
|
|
480
|
-
def recompute_append(current, source_map, before_scope, args, aggregate, command, owner = aggregate, target = nil)
|
|
481
|
-
fields = source_map.transform_values do |source|
|
|
482
|
-
resolve_mutation_append_field(source, before_scope, args, aggregate, command)
|
|
483
|
-
end
|
|
484
|
-
fill_recompute_declared_defaults(aggregate, owner, target, fields)
|
|
485
|
-
Array(current) + [symbolize_deep(fields)]
|
|
486
|
-
end
|
|
487
|
-
|
|
488
|
-
# BUG#12's own recompute-side half — see `#recompute_append`'s
|
|
489
|
-
# own comment above for why this exists and why it reuses
|
|
490
|
-
# `Instance.default_for` rather than calling `EntityElement#
|
|
491
|
-
# fill_declared_defaults` again. A no-op whenever `target` names
|
|
492
|
-
# no attribute at all (every RECOMPUTABLE_MUTATION_OPS caller but
|
|
493
|
-
# `:append` passes no `target`) or `target`'s own declared type
|
|
494
|
-
# isn't an entity nested directly under `owner` (a value object,
|
|
495
|
-
# or nothing declared at all — `owner.attribute` answering `nil`
|
|
496
|
-
# for a target the DSL itself would already have refused at
|
|
497
|
-
# build time). `owner.entities`, not `aggregate.entities` — a
|
|
498
|
-
# piece nested inside a piece is a child of the owning entity
|
|
499
|
-
# (`Card` is `Board.entities`, never `Workspace.entities`), the
|
|
500
|
-
# same distinction `EntityElement#appended_to_element`'s own fix
|
|
501
|
-
# draws.
|
|
502
|
-
#
|
|
503
|
-
# @param aggregate [Bluebook::Aggregate] the root aggregate, forwarded to
|
|
504
|
-
# `Instance.default_for`
|
|
505
|
-
# @param owner [Bluebook::Aggregate, Bluebook::Entity, nil] the construct whose
|
|
506
|
-
# nested entity `target` may declare
|
|
507
|
-
# @param target [Symbol, nil] the append mutation's own target field name
|
|
508
|
-
# @param fields [Hash{Symbol => Object}] the field map already resolved by
|
|
509
|
-
# `recompute_append`
|
|
510
|
-
# @return [Hash{Symbol => Object}] `fields` unchanged if `target` is nil or does
|
|
511
|
-
# not name a nested entity; otherwise `fields` with every declared attribute it
|
|
512
|
-
# doesn't already hold filled in with its own default
|
|
513
|
-
def fill_recompute_declared_defaults(aggregate, owner, target, fields)
|
|
514
|
-
return fields unless target
|
|
515
|
-
|
|
516
|
-
element_type = owner&.attribute(target)&.type
|
|
517
|
-
entity = element_type && owner.entities.find { |piece| piece.hecks_name == element_type.to_s }
|
|
518
|
-
return fields unless entity
|
|
519
|
-
|
|
520
|
-
entity.attributes.each do |attribute|
|
|
521
|
-
next if fields.key?(attribute.name)
|
|
522
|
-
|
|
523
|
-
fields[attribute.name] = attribute.list? ? [] : Runtime::Instance.default_for(aggregate, attribute)
|
|
524
|
-
end
|
|
525
|
-
fields
|
|
526
|
-
end
|
|
527
|
-
|
|
528
|
-
# Resolves one appended field's own value from its declared source.
|
|
529
|
-
#
|
|
530
|
-
# @param source [Symbol, Object] an append field's own source: an argument name,
|
|
531
|
-
# or a literal value
|
|
532
|
-
# @param before_scope [Hash] the entity element's own before-state
|
|
533
|
-
# @param args [Hash] the step's own raw args
|
|
534
|
-
# @param aggregate [Bluebook::Aggregate] the root aggregate, for value-object
|
|
535
|
-
# namespace resolution
|
|
536
|
-
# @param command [Bluebook::Command] the command that declared the mutation
|
|
537
|
-
# @return [Object] `before_scope[source]` if `source` names no caller-supplied
|
|
538
|
-
# arg; `source` unchanged if it is not a Symbol; otherwise the coerced,
|
|
539
|
-
# materialized arg value
|
|
540
|
-
def resolve_mutation_append_field(source, before_scope, args, aggregate, command)
|
|
541
|
-
return source unless source.is_a?(Symbol)
|
|
542
|
-
return before_scope[source] unless args.key?(source)
|
|
543
|
-
|
|
544
|
-
coerce_recompute_append_arg(aggregate, command, source, args[source])
|
|
545
|
-
end
|
|
546
|
-
|
|
547
|
-
# `Interpreting#coerce_declared_arguments`'s own coercion,
|
|
548
|
-
# reproduced independently (never calling it again, the same
|
|
549
|
-
# "never agree with itself" rule this whole module's header
|
|
550
|
-
# comment gives) — a raw arg is coerced only when its own name
|
|
551
|
-
# (`source`) is one of the acting command's own declared
|
|
552
|
-
# attributes, exactly the condition that method checks before a
|
|
553
|
-
# real dispatch ever coerces it either. `command.attribute(source)`
|
|
554
|
-
# answering `nil` (a source that names no declared attribute —
|
|
555
|
-
# never possible for `coerce_declared_arguments` to have touched
|
|
556
|
-
# it in the real dispatch either) leaves `raw` exactly as it
|
|
557
|
-
# arrived, the same as every bare-scalar append this already
|
|
558
|
-
# handled correctly before BUG#5's fix.
|
|
559
|
-
#
|
|
560
|
-
# `Value.materialize`d immediately after coercing — matching the
|
|
561
|
-
# plain-data shape `entry[:before]`/`entry[:after]` already carry
|
|
562
|
-
# throughout this whole property, so the eventual `symbolize_deep`
|
|
563
|
-
# comparison is always materialized-against-materialized, never a
|
|
564
|
-
# live `Value` against a Hash.
|
|
565
|
-
#
|
|
566
|
-
# @param aggregate [Bluebook::Aggregate] the root aggregate, for value-object
|
|
567
|
-
# namespace resolution
|
|
568
|
-
# @param command [Bluebook::Command] the command whose own declared attribute
|
|
569
|
-
# `source` may name
|
|
570
|
-
# @param source [Symbol] the argument name to coerce
|
|
571
|
-
# @param raw [Object] the raw arg value to coerce
|
|
572
|
-
# @return [Object] `raw`, coerced and materialized, if `source` names a declared
|
|
573
|
-
# attribute of `command`; `raw` unchanged otherwise
|
|
574
|
-
def coerce_recompute_append_arg(aggregate, command, source, raw)
|
|
575
|
-
attribute = command.attribute(source)
|
|
576
|
-
return raw unless attribute
|
|
577
|
-
|
|
578
|
-
Runtime::Value.materialize(Runtime::Value.for_attribute(aggregate, attribute, raw, argument: true))
|
|
579
|
-
end
|
|
580
|
-
|
|
581
|
-
# `MutationApplier#removed`'s own value-equality match, reproduced.
|
|
582
|
-
#
|
|
583
|
-
# @param current [Object] the target field's before-value (an Array of elements)
|
|
584
|
-
# @param source [Symbol, Object] the remove mutation's own source: an argument
|
|
585
|
-
# name, or a literal value already
|
|
586
|
-
# @param args [Hash] the step's own raw args
|
|
587
|
-
# @return [Array] `current` (coerced to an Array) with every element matching the
|
|
588
|
-
# resolved target value removed
|
|
589
|
-
def recompute_remove(current, source, args)
|
|
590
|
-
target = symbolize_deep(resolve_mutation_source(source, args))
|
|
591
|
-
Array(current).reject { |element| symbolize_deep(element) == target }
|
|
592
|
-
end
|
|
593
|
-
|
|
594
|
-
# `CommandRules::Arithmetic#multiply`'s own two branches,
|
|
595
|
-
# reproduced on plain materialized data instead of a real Value:
|
|
596
|
-
# a single-numeric-field Hash (the VO-typed case — ListCount, one
|
|
597
|
-
# Integer field) scales that field ; a bare Numeric scales itself.
|
|
598
|
-
# `current ||= 0` — the same phantom-field fallback #multiply
|
|
599
|
-
# itself already gives (unaffected by this session's #clamp fix,
|
|
600
|
-
# since #multiply never needed one).
|
|
601
|
-
#
|
|
602
|
-
# @param current [Hash, Numeric, nil] the target field's before-value
|
|
603
|
-
# @param amount [Object] the resolved multiplier
|
|
604
|
-
# @return [Hash, Numeric, Symbol] the scaled field/value, or `:unrecomputable` if
|
|
605
|
-
# `amount` is not Numeric or `current` is neither a Hash with a numeric field
|
|
606
|
-
# nor a Numeric
|
|
607
|
-
def recompute_multiply(current, amount)
|
|
608
|
-
return :unrecomputable unless amount.is_a?(Numeric)
|
|
609
|
-
|
|
610
|
-
current ||= 0
|
|
611
|
-
if current.is_a?(Hash)
|
|
612
|
-
field = current.keys.find { |f| current[f].is_a?(Numeric) }
|
|
613
|
-
return :unrecomputable unless field
|
|
614
|
-
|
|
615
|
-
current.merge(field => current[field] * amount)
|
|
616
|
-
elsif current.is_a?(Numeric)
|
|
617
|
-
current * amount
|
|
618
|
-
else
|
|
619
|
-
:unrecomputable
|
|
620
|
-
end
|
|
621
|
-
end
|
|
622
|
-
|
|
623
|
-
# `CommandRules::Arithmetic#clamp`'s own two branches, reproduced
|
|
624
|
-
# the same way #recompute_multiply is — including this session's
|
|
625
|
-
# own `current ||= 0` fix (command_rules/arithmetic.rb), the one
|
|
626
|
-
# arithmetic op that didn't have it until now. `mutation.source`
|
|
627
|
-
# is always a literal `[min, max]`, never an argument reference
|
|
628
|
-
# (MutationApplier's own comment on why `resolve_source` is
|
|
629
|
-
# skipped for clamp) — so nothing here reads `args` for it at all.
|
|
630
|
-
#
|
|
631
|
-
# @param current [Hash, Numeric, nil] the target field's before-value
|
|
632
|
-
# @param bounds [Object] the mutation's own declared `[min, max]` literal
|
|
633
|
-
# @return [Hash, Numeric, Symbol] the clamped field/value, or `:unrecomputable`
|
|
634
|
-
# if `bounds` is not a 2-element Array or `current` is neither a Hash with a
|
|
635
|
-
# numeric field nor a Numeric
|
|
636
|
-
def recompute_clamp(current, bounds)
|
|
637
|
-
return :unrecomputable unless bounds.is_a?(Array) && bounds.size == 2
|
|
638
|
-
|
|
639
|
-
min, max = bounds
|
|
640
|
-
current ||= 0
|
|
641
|
-
if current.is_a?(Hash)
|
|
642
|
-
field = current.keys.find { |f| current[f].is_a?(Numeric) }
|
|
643
|
-
return :unrecomputable unless field
|
|
644
|
-
|
|
645
|
-
current.merge(field => current[field].clamp(min, max))
|
|
646
|
-
elsif current.is_a?(Numeric)
|
|
647
|
-
current.clamp(min, max)
|
|
648
|
-
else
|
|
649
|
-
:unrecomputable
|
|
650
|
-
end
|
|
651
|
-
end
|
|
652
|
-
|
|
653
|
-
# `CommandRules::Arithmetic#resolve_source`, reproduced: a
|
|
654
|
-
# mutation's source is either the name of an argument or a
|
|
655
|
-
# literal, told apart by type.
|
|
656
|
-
#
|
|
657
|
-
# @param source [Symbol, Object] an argument name to resolve from `args`, or a
|
|
658
|
-
# literal value already
|
|
659
|
-
# @param args [Hash] the step's own raw args
|
|
660
|
-
# @return [Object] `args[source]` if `source` is a Symbol; `source` unchanged otherwise
|
|
661
|
-
def resolve_mutation_source(source, args)
|
|
662
|
-
source.is_a?(Symbol) ? args[source] : source
|
|
663
|
-
end
|
|
664
|
-
|
|
665
|
-
# A generated step's own `args` arrive with string keys on every
|
|
666
|
-
# nested Hash (the wire/JSON shape `spec/corpus/*.json` already
|
|
667
|
-
# uses) while `history[:mutation_traces]`' own materialized
|
|
668
|
-
# before/after state carries symbol keys throughout (Runtime::
|
|
669
|
-
# Value.materialize's own convention) — two hashes holding the
|
|
670
|
-
# identical fact compare unequal by Ruby's own `Hash#==` unless
|
|
671
|
-
# both sides are normalized the same way first. Recursive, since
|
|
672
|
-
# an appended/removed element can itself nest a value object
|
|
673
|
-
# (RemoveTag's own `Tag` argument, `{"key"=>..., "value"=>...}`).
|
|
674
|
-
#
|
|
675
|
-
# @param value [Object] a Hash, Array, or scalar to normalize
|
|
676
|
-
# @return [Object] `value` with every Hash key symbolized, recursively through
|
|
677
|
-
# nested Hashes and Arrays; any other value unchanged
|
|
678
|
-
def symbolize_deep(value)
|
|
679
|
-
case value
|
|
680
|
-
when Hash then value.to_h { |key, val| [key.to_sym, symbolize_deep(val)] }
|
|
681
|
-
when Array then value.map { |val| symbolize_deep(val) }
|
|
682
|
-
else value
|
|
683
|
-
end
|
|
684
|
-
end
|
|
685
|
-
end
|
|
686
|
-
end
|
|
687
|
-
end
|
|
688
|
-
end
|