hecks 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/hecks/adapters/driven/claude_code.rb +72 -7
- data/lib/hecks/adapters/driven/d1.rb +187 -23
- data/lib/hecks/adapters/driven/folder.rb +83 -10
- data/lib/hecks/adapters/driven/google_authentication.rb +33 -12
- data/lib/hecks/adapters/driven/governance_authorization.rb +87 -18
- data/lib/hecks/adapters/driven/heki/journal.rb +13 -4
- data/lib/hecks/adapters/driven/heki/saga_store.rb +56 -10
- data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
- data/lib/hecks/adapters/driven/heki.rb +106 -9
- data/lib/hecks/adapters/driven/identity_registry.rb +12 -2
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +25 -3
- data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
- data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +67 -14
- data/lib/hecks/adapters/driven/lambda.rb +82 -35
- data/lib/hecks/adapters/driven/local_storage.rb +83 -10
- data/lib/hecks/adapters/driven/memory.rb +205 -9
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +21 -1
- data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
- data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
- data/lib/hecks/adapters/driven/postgres.rb +175 -28
- data/lib/hecks/adapters/driven/prism.rb +50 -11
- data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
- data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
- data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
- data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
- data/lib/hecks/adapters/driven/sqlite.rb +181 -21
- data/lib/hecks/adapters/driven/system_clock.rb +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
- data/lib/hecks/adapters/driven.rb +6 -4
- data/lib/hecks/adapters/driving/github_webhook.rb +31 -18
- data/lib/hecks/behaviors/dsl.rb +60 -2
- data/lib/hecks/behaviors/expectations.rb +190 -29
- data/lib/hecks/behaviors/ir.rb +12 -1
- data/lib/hecks/behaviors/rspec.rb +9 -1
- data/lib/hecks/behaviors/runner.rb +21 -2
- data/lib/hecks/behaviors.rb +9 -1
- data/lib/hecks/bluebook/aggregate.rb +43 -13
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +17 -10
- data/lib/hecks/bluebook/assembly/build.rb +26 -5
- data/lib/hecks/bluebook/assembly/contract.rb +98 -23
- data/lib/hecks/bluebook/assembly/contracts.rb +59 -52
- data/lib/hecks/bluebook/assembly/marks.rb +159 -30
- data/lib/hecks/bluebook/assembly/specializer.rb +38 -21
- data/lib/hecks/bluebook/assembly.rb +32 -14
- data/lib/hecks/bluebook/attribute.rb +26 -12
- data/lib/hecks/bluebook/behaviour/aggregate.rb +43 -11
- data/lib/hecks/bluebook/behaviour/attribute.rb +18 -5
- data/lib/hecks/bluebook/behaviour/chapter.rb +76 -5
- data/lib/hecks/bluebook/behaviour/command.rb +55 -25
- data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -7
- data/lib/hecks/bluebook/behaviour/entity.rb +20 -8
- data/lib/hecks/bluebook/behaviour/hexagon.rb +30 -4
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +27 -6
- data/lib/hecks/bluebook/behaviour/policy.rb +42 -17
- data/lib/hecks/bluebook/behaviour/process_manager.rb +39 -8
- data/lib/hecks/bluebook/behaviour/query.rb +6 -1
- data/lib/hecks/bluebook/behaviour/read_model.rb +29 -8
- data/lib/hecks/bluebook/behaviour/traits.rb +48 -12
- data/lib/hecks/bluebook/behaviour/value_object.rb +21 -9
- data/lib/hecks/bluebook/capabilities.rb +27 -0
- data/lib/hecks/bluebook/chapter.rb +51 -9
- data/lib/hecks/bluebook/command.rb +62 -17
- data/lib/hecks/bluebook/domain_port.rb +34 -9
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +282 -123
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +112 -75
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +81 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +486 -117
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +179 -47
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +284 -122
- data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
- data/lib/hecks/bluebook/dsl/entity_builder.rb +191 -61
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +130 -30
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +38 -21
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
- data/lib/hecks/bluebook/dsl/policy_builder.rb +86 -36
- data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +111 -47
- data/lib/hecks/bluebook/dsl/query_builder.rb +37 -8
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +127 -52
- data/lib/hecks/bluebook/dsl/rule_reference.rb +97 -43
- data/lib/hecks/bluebook/dsl/translation_builder.rb +150 -44
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +68 -20
- data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
- data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
- data/lib/hecks/bluebook/entity.rb +40 -11
- data/lib/hecks/bluebook/expression/ast_json.rb +128 -36
- data/lib/hecks/bluebook/expression/ast_reader.rb +32 -3
- data/lib/hecks/bluebook/expression/canonical_form.rb +55 -16
- data/lib/hecks/bluebook/expression/evaluator.rb +221 -43
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +54 -18
- data/lib/hecks/bluebook/expression/resolver.rb +369 -128
- data/lib/hecks/bluebook/hexagon.rb +35 -1
- data/lib/hecks/bluebook/lifecycle.rb +12 -1
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/judge.rb +126 -108
- data/lib/hecks/bluebook/meta_validator/plan.rb +81 -46
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +3 -2
- data/lib/hecks/bluebook/meta_validator/readings.rb +200 -50
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +68 -41
- data/lib/hecks/bluebook/meta_validator/shapes.rb +166 -21
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +286 -46
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +11 -10
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -5
- data/lib/hecks/bluebook/meta_validator.rb +235 -139
- data/lib/hecks/bluebook/model_check.rb +434 -104
- data/lib/hecks/bluebook/pattern_subset.rb +32 -10
- data/lib/hecks/bluebook/policy.rb +15 -13
- data/lib/hecks/bluebook/process_manager.rb +27 -14
- data/lib/hecks/bluebook/project_discovery.rb +5 -0
- data/lib/hecks/bluebook/project_loader.rb +40 -0
- data/lib/hecks/bluebook/project_register.rb +50 -6
- data/lib/hecks/bluebook/query.rb +31 -4
- data/lib/hecks/bluebook/read_model.rb +35 -15
- data/lib/hecks/bluebook/reference.rb +26 -13
- data/lib/hecks/bluebook/smoke_test.rb +46 -23
- data/lib/hecks/bluebook/synthesizer.rb +46 -12
- data/lib/hecks/bluebook/translation.rb +34 -5
- data/lib/hecks/bluebook/value_object.rb +29 -11
- data/lib/hecks/bluebook.rb +5 -6
- data/lib/hecks/codemod.rb +138 -50
- data/lib/hecks/construct.rb +21 -7
- data/lib/hecks/corpus.rb +438 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
- data/lib/hecks/doc/reference.rb +200 -31
- data/lib/hecks/embryonaut_bluebook.rb +38 -15
- data/lib/hecks/facade/cli_door.rb +69 -10
- data/lib/hecks/facade/cli_runner.rb +105 -24
- data/lib/hecks/facade/command_request.rb +23 -0
- data/lib/hecks/facade/handle.rb +155 -35
- data/lib/hecks/facade/json_door.rb +106 -25
- data/lib/hecks/facade/surface/aggregate_door.rb +50 -27
- data/lib/hecks/facade/surface/chapter.rb +26 -17
- data/lib/hecks/facade/surface.rb +16 -3
- data/lib/hecks/facade.rb +15 -4
- data/lib/hecks/forms/app.rb +46 -30
- data/lib/hecks/forms/command_form_renderer.rb +70 -9
- data/lib/hecks/forms/field_renderer.rb +142 -6
- data/lib/hecks/forms/field_shape.rb +183 -19
- data/lib/hecks/forms/html.rb +51 -7
- data/lib/hecks/forms/index_renderer.rb +14 -2
- data/lib/hecks/forms/page.rb +14 -0
- data/lib/hecks/forms/params.rb +120 -23
- data/lib/hecks/forms/port_argument.rb +14 -2
- data/lib/hecks/forms/query_form_renderer.rb +65 -2
- data/lib/hecks/forms/record_renderer.rb +60 -2
- data/lib/hecks/forms/record_table.rb +28 -1
- data/lib/hecks/forms/reference_options.rb +24 -0
- data/lib/hecks/forms/value_object_shape.rb +13 -3
- data/lib/hecks/forms.rb +24 -4
- data/lib/hecks/fqn.rb +59 -1
- data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
- data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
- data/lib/hecks/framework/oidc.json +15 -0
- data/lib/hecks/framework.rb +79 -25
- data/lib/hecks/freezer.rb +27 -11
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +241 -106
- data/lib/hecks/fuzzing/combination_miner.rb +178 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +241 -45
- data/lib/hecks/fuzzing/coverage_campaign.rb +161 -0
- data/lib/hecks/fuzzing/differential.rb +192 -0
- data/lib/hecks/fuzzing/domain_generator.rb +871 -0
- data/lib/hecks/fuzzing/era_boundary.rb +68 -19
- data/lib/hecks/fuzzing/form_census.rb +121 -20
- data/lib/hecks/fuzzing/generated_domain_check.rb +171 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +45 -6
- data/lib/hecks/fuzzing/isolated_boot.rb +137 -80
- data/lib/hecks/fuzzing/nondeterministic.rb +79 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +111 -21
- data/lib/hecks/fuzzing/properties/corrections.rb +34 -9
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +300 -51
- data/lib/hecks/fuzzing/properties/guards.rb +86 -42
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +80 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +40 -35
- data/lib/hecks/fuzzing/properties/outbox.rb +70 -32
- data/lib/hecks/fuzzing/properties/querying.rb +82 -28
- data/lib/hecks/fuzzing/properties.rb +84 -51
- data/lib/hecks/fuzzing/qa_settings.rb +164 -0
- data/lib/hecks/fuzzing/replay.rb +241 -133
- data/lib/hecks/fuzzing/rotation_priority.rb +48 -28
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +139 -0
- data/lib/hecks/fuzzing/self_consistency.rb +315 -137
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +46 -40
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +18 -11
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +13 -12
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +21 -12
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +59 -32
- data/lib/hecks/fuzzing/sequence_generator.rb +158 -23
- data/lib/hecks/fuzzing/shrinker.rb +309 -0
- data/lib/hecks/fuzzing/structural_skips.rb +37 -130
- data/lib/hecks/fuzzing/sweep_depth.rb +12 -4
- data/lib/hecks/fuzzing/target_capabilities.rb +107 -29
- data/lib/hecks/fuzzing/value_generator.rb +110 -22
- data/lib/hecks/fuzzing.rb +5 -0
- data/lib/hecks/grammar/evolve.rb +188 -12
- data/lib/hecks/grammar.rb +53 -7
- data/lib/hecks/ir.rb +51 -20
- data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
- data/lib/hecks/language/bluebook/policy.bluebook +11 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +348 -13
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/literal.rb +41 -9
- data/lib/hecks/naming.rb +112 -31
- data/lib/hecks/ports/access_control.rb +53 -2
- data/lib/hecks/ports/agent/answers.rb +83 -6
- data/lib/hecks/ports/agent.rb +119 -35
- data/lib/hecks/ports/authentication.rb +44 -4
- data/lib/hecks/ports/authorization.rb +50 -11
- data/lib/hecks/ports/clock.rb +42 -23
- data/lib/hecks/ports/extraction.rb +16 -0
- data/lib/hecks/ports/identity_assignment.rb +23 -2
- data/lib/hecks/ports/identity_generation.rb +17 -3
- data/lib/hecks/ports/identity_resolution.rb +17 -1
- data/lib/hecks/ports/key_vault.port +6 -0
- data/lib/hecks/ports/key_vault.rb +58 -0
- data/lib/hecks/ports/loading.rb +4 -0
- data/lib/hecks/ports/persistence/append_only.rb +172 -8
- data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
- data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
- data/lib/hecks/ports/persistence/execution.rb +4 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
- data/lib/hecks/ports/persistence/plugin.rb +42 -4
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +118 -66
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +34 -5
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +33 -9
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +95 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +314 -90
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +48 -8
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +58 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +96 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +9 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
- data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
- data/lib/hecks/ports/persistence/repository_factory.rb +23 -3
- data/lib/hecks/ports/persistence/state_codec.rb +319 -0
- data/lib/hecks/ports/persistence.rb +36 -1
- data/lib/hecks/ports/projection.rb +61 -7
- data/lib/hecks/ports/query/in_memory.rb +41 -8
- data/lib/hecks/ports/query/ordering.rb +21 -6
- data/lib/hecks/ports/query.rb +35 -0
- data/lib/hecks/ports.rb +1 -0
- data/lib/hecks/projections/bootstrap_table.rb +147 -0
- data/lib/hecks/projections/diagrams.rb +315 -79
- data/lib/hecks/projections/glossary/html.rb +91 -3
- data/lib/hecks/projections/glossary/markdown.rb +50 -3
- data/lib/hecks/projections/glossary/mermaid.rb +49 -1
- data/lib/hecks/projections/glossary/sections.rb +1 -1
- data/lib/hecks/projections/glossary/sentences.rb +110 -5
- data/lib/hecks/projections/glossary.rb +173 -31
- data/lib/hecks/projections/ir.rb +1 -1
- data/lib/hecks/projections/model/deviations.rb +62 -17
- data/lib/hecks/projections/model.rb +75 -21
- data/lib/hecks/projections/oidc.rb +45 -8
- data/lib/hecks/projections/parser_table.rb +53 -12
- data/lib/hecks/projections/reference.rb +15 -3
- data/lib/hecks/projections/rust_vocabulary.rb +646 -0
- data/lib/hecks/projections/shape.rb +10 -2
- data/lib/hecks/projections/statements.rb +69 -22
- data/lib/hecks/projections/vocabulary.rb +26 -9
- data/lib/hecks/projections.rb +5 -3
- data/lib/hecks/projector/cli_projector.rb +245 -37
- data/lib/hecks/projector/docs_projector.rb +154 -28
- data/lib/hecks/projector/exporter.rb +104 -29
- data/lib/hecks/projector/ir_projector.rb +7 -1
- data/lib/hecks/projector/narrate_projector.rb +144 -23
- data/lib/hecks/projector/target.rb +42 -18
- data/lib/hecks/projector.rb +86 -18
- data/lib/hecks/query_ir.rb +94 -47
- data/lib/hecks/query_specification/common/comparators.rb +19 -3
- data/lib/hecks/query_specification/common/comparison.rb +109 -27
- data/lib/hecks/query_specification/common/dsl.rb +65 -9
- data/lib/hecks/query_specification/common/null_policy.rb +61 -15
- data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
- data/lib/hecks/query_specification/common/options.rb +25 -0
- data/lib/hecks/query_specification/field_path.rb +69 -15
- data/lib/hecks/query_specification/hop_path.rb +57 -20
- data/lib/hecks/query_specification/read_model/specification.rb +4 -0
- data/lib/hecks/rendering.rb +9 -3
- data/lib/hecks/router/namespace_installer.rb +16 -3
- data/lib/hecks/router.rb +56 -1
- data/lib/hecks/runtime/aggregate_lock.rb +20 -11
- data/lib/hecks/runtime/boot_gates.rb +21 -3
- data/lib/hecks/runtime/caller.rb +40 -8
- data/lib/hecks/runtime/capability_graph.rb +13 -2
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +40 -41
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +45 -67
- data/lib/hecks/runtime/command_interpreter.rb +195 -113
- data/lib/hecks/runtime/command_rules/admissibility.rb +231 -80
- data/lib/hecks/runtime/command_rules/arithmetic.rb +154 -96
- data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
- data/lib/hecks/runtime/command_rules/emission.rb +18 -1
- data/lib/hecks/runtime/command_rules/references.rb +145 -55
- data/lib/hecks/runtime/command_rules.rb +3 -0
- data/lib/hecks/runtime/dependency_planning.rb +56 -11
- data/lib/hecks/runtime/dispatcher.rb +263 -92
- data/lib/hecks/runtime/entity_element.rb +304 -67
- data/lib/hecks/runtime/entity_interpreter.rb +149 -88
- data/lib/hecks/runtime/errors.rb +37 -23
- data/lib/hecks/runtime/event.rb +15 -10
- data/lib/hecks/runtime/identity.rb +91 -23
- data/lib/hecks/runtime/instance.rb +103 -18
- data/lib/hecks/runtime/interpreting.rb +24 -16
- data/lib/hecks/runtime/invocation.rb +358 -0
- data/lib/hecks/runtime/loader.rb +105 -19
- data/lib/hecks/runtime/outbox.rb +164 -26
- data/lib/hecks/runtime/policy_interpreter.rb +73 -60
- data/lib/hecks/runtime/port_operation_interpreter.rb +42 -19
- data/lib/hecks/runtime/query_interpreter.rb +93 -74
- data/lib/hecks/runtime/reaction_invocation.rb +73 -28
- data/lib/hecks/runtime/read_model_interpreter.rb +60 -44
- data/lib/hecks/runtime/rebuild_sweep.rb +32 -4
- data/lib/hecks/runtime/reference_hop.rb +48 -6
- data/lib/hecks/runtime/refusal_wording.rb +142 -115
- data/lib/hecks/runtime/registry/saga_persistence.rb +32 -21
- data/lib/hecks/runtime/registry/verification.rb +153 -28
- data/lib/hecks/runtime/registry.rb +202 -32
- data/lib/hecks/runtime/remote_dispatcher.rb +125 -24
- data/lib/hecks/runtime/routing.rb +36 -154
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +25 -27
- data/lib/hecks/runtime/saga_interpreter.rb +90 -76
- data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
- data/lib/hecks/runtime/tenant_check.rb +33 -13
- data/lib/hecks/runtime/tenant_scope.rb +23 -5
- data/lib/hecks/runtime/value/admission.rb +75 -30
- data/lib/hecks/runtime/value/coercion.rb +555 -142
- data/lib/hecks/runtime/value/entity_list_coercion.rb +132 -60
- data/lib/hecks/runtime/value.rb +71 -21
- data/lib/hecks/runtime.rb +39 -7
- data/lib/hecks/storehouse.rb +368 -72
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
- data/lib/hecks/version.rb +3 -3
- data/lib/hecks/vocabulary.rb +205 -4
- data/lib/hecks.rb +91 -11
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +19 -11
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +24 -12
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +38 -14
- metadata +28 -2
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Fuzzing
|
|
3
3
|
module Properties
|
|
4
|
-
# A
|
|
4
|
+
# **A dry run leaves no trace** — `Dispatcher#dry_run?`'s whole contract
|
|
5
5
|
# ("the command evaluated hypothetically, nothing saved or emitted,
|
|
6
6
|
# no reaction"), held to the store rather than trusted: `Replay`
|
|
7
7
|
# snapshots every instance and the event count on either side of
|
|
8
|
-
# each `{"dry_run": …}` step, in a
|
|
8
|
+
# each `{"dry_run": …}` step, in a separate `dry_run_traces` array
|
|
9
9
|
# (same order as `history[:dry_runs]`, never merged into it — that
|
|
10
10
|
# array stays the exact `{verb:, ok:, error?:}` shape the compiled
|
|
11
11
|
# Rust binary's own `dry_run` answers, so `spec/rust_conformance_
|
|
@@ -16,6 +16,12 @@ module Hecks
|
|
|
16
16
|
# hand-built history, an older corpus) are skipped, not failed: no
|
|
17
17
|
# claim, no finding.
|
|
18
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
|
|
19
25
|
def dry_runs_leave_no_trace(history)
|
|
20
26
|
offenders = Array(history[:dry_run_traces]).filter_map do |entry|
|
|
21
27
|
before = entry[:before]
|
|
@@ -37,7 +43,7 @@ module Hecks
|
|
|
37
43
|
# Dispatch-binding and mutation-recomputation properties: a saga/
|
|
38
44
|
# policy dispatch is bound to the value its own with_spec names, and a
|
|
39
45
|
# command's append/remove/multiply/clamp mutations land on the same
|
|
40
|
-
# after-state an independent recomputation of the
|
|
46
|
+
# after-state an independent recomputation of the same rule produces.
|
|
41
47
|
|
|
42
48
|
# Holds dispatch_binding_fidelity and mutations_match_recompute, plus
|
|
43
49
|
# the independently-written re-derivations (#resolve_dispatch_binding,
|
|
@@ -49,17 +55,17 @@ module Hecks
|
|
|
49
55
|
# are not claimable feature strings at all — see FEATURE_COVERAGE's
|
|
50
56
|
# own comment on this entry). history[:saga_dispatches]/[:policy_dispatches]
|
|
51
57
|
# (Registry#saga_dispatch_log/#policy_dispatch_log — additive,
|
|
52
|
-
# Ruby-only,
|
|
58
|
+
# Ruby-only, never touching saga_log/reaction_log, the byte-for-
|
|
53
59
|
# byte shape spec/rust_conformance_spec.rb holds Rust to) each
|
|
54
|
-
# carry the
|
|
60
|
+
# carry the raw inputs a dispatch's own args were resolved from,
|
|
55
61
|
# captured live at the moment the resolution actually ran — a
|
|
56
62
|
# saga's own memory keeps changing across a run, so re-deriving
|
|
57
|
-
# from history[:saga_instances]'s
|
|
63
|
+
# from history[:saga_instances]'s final memory (the only other
|
|
58
64
|
# place it would be visible) would grade the wrong moment,
|
|
59
65
|
# lifecycle_guard_and_given_violations_are_refused's own false
|
|
60
66
|
# positive one item earlier, in a different shape.
|
|
61
67
|
#
|
|
62
|
-
# #resolve_dispatch_binding/#resolve_trigger_binding are
|
|
68
|
+
# #resolve_dispatch_binding/#resolve_trigger_binding are separate,
|
|
63
69
|
# independently-written re-derivations of SagaInterpreter#
|
|
64
70
|
# dispatch_args/PolicyInterpreter#trigger_args's own resolution —
|
|
65
71
|
# never calling either method again, which would only ever agree
|
|
@@ -67,7 +73,8 @@ module Hecks
|
|
|
67
73
|
# argument binding on a fan-out dispatch that produces a perfectly
|
|
68
74
|
# normal-looking log entry (`delivered: true`) and would only ever
|
|
69
75
|
# surface as a downstream assertion failure, if it surfaces at
|
|
70
|
-
# all" —
|
|
76
|
+
# all" — a defect class one level over from what
|
|
77
|
+
# `fanout_dispatches_once_per_matching_row` already catches.
|
|
71
78
|
#
|
|
72
79
|
# Real targets: Settlement (mixed literal/correlation-head/event-
|
|
73
80
|
# payload/memory-fallback bindings across three legs, plus a
|
|
@@ -75,6 +82,11 @@ module Hecks
|
|
|
75
82
|
# the forward Credit leg carries), ExternalSettlement, Onboarding
|
|
76
83
|
# (no compensation leg, by design — nothing to check there beyond
|
|
77
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
|
|
78
90
|
def dispatch_binding_fidelity(history)
|
|
79
91
|
saga_offenders = history.fetch(:saga_dispatches, []).filter_map do |entry|
|
|
80
92
|
expected = resolve_dispatch_binding(entry)
|
|
@@ -99,9 +111,14 @@ module Hecks
|
|
|
99
111
|
|
|
100
112
|
# SagaInterpreter#dispatch_args's own 4-branch resolution,
|
|
101
113
|
# reproduced independently: a literal, the correlation key itself,
|
|
102
|
-
# the
|
|
103
|
-
# the saga's own carried memory (seeded from the
|
|
114
|
+
# the current triggering event's own payload, or — the fallback —
|
|
115
|
+
# the saga's own carried memory (seeded from the starting event's
|
|
104
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
|
|
105
122
|
def resolve_dispatch_binding(entry)
|
|
106
123
|
entry[:with_spec].to_h do |key, value|
|
|
107
124
|
resolved = if !value.is_a?(Symbol) then value
|
|
@@ -116,7 +133,12 @@ module Hecks
|
|
|
116
133
|
# PolicyInterpreter#trigger_args's own 2-branch resolution — a
|
|
117
134
|
# policy holds no correlation and no memory, so `payload` (the
|
|
118
135
|
# triggering event's own payload, already merged with a fan-out
|
|
119
|
-
# row's id when there is one) is the
|
|
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
|
|
120
142
|
def resolve_trigger_binding(entry)
|
|
121
143
|
entry[:with_spec].to_h do |key, value|
|
|
122
144
|
resolved = value.is_a?(Symbol) ? entry[:payload][value] : value
|
|
@@ -137,33 +159,86 @@ module Hecks
|
|
|
137
159
|
#
|
|
138
160
|
# `history[:mutation_traces]` (Replay's own bounded, additive
|
|
139
161
|
# extension — see #build_mutation_trace's own comment) carries a
|
|
140
|
-
# per-step before/after snapshot of the
|
|
162
|
+
# per-step before/after snapshot of the entity element an
|
|
141
163
|
# entity-dispatched command's own mutations acted on, materialized
|
|
142
164
|
# to plain data, plus the step's own raw args — the delta
|
|
143
165
|
# `aggregation_matches_recompute` never had to ask for, because
|
|
144
|
-
# count/median are pure functions of
|
|
166
|
+
# count/median are pure functions of final state and a mutation
|
|
145
167
|
# is not (the same "captured live, not re-derived from final
|
|
146
168
|
# state" lesson item 8's own saga_dispatch_log already learned).
|
|
147
169
|
#
|
|
148
170
|
# #recompute_append/#recompute_remove/#recompute_multiply/
|
|
149
|
-
# #recompute_clamp are
|
|
150
|
-
# reproductions of
|
|
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
|
|
151
174
|
# CommandRules::Arithmetic#multiply/#clamp — never calling either
|
|
152
|
-
# again, which would only ever agree with itself.
|
|
153
|
-
#
|
|
154
|
-
#
|
|
155
|
-
#
|
|
156
|
-
#
|
|
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.
|
|
157
221
|
#
|
|
158
222
|
# `:unrecomputable` (never compared, never a finding) covers the
|
|
159
223
|
# generator's own deliberate arg-malforming (`StepBuilder#malform`)
|
|
160
224
|
# landing a non-Numeric amount/non-2-element bounds where
|
|
161
|
-
# multiply/clamp need one — the
|
|
225
|
+
# multiply/clamp need one — the same shape `guard_check`'s own
|
|
162
226
|
# AbsentArgument false positive taught: a step whose raw material
|
|
163
227
|
# doesn't fit the op's own contract is inconclusive, not a claimed
|
|
164
|
-
# mismatch.
|
|
165
|
-
|
|
166
|
-
|
|
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
|
|
167
242
|
def mutations_match_recompute(history)
|
|
168
243
|
bluebooks = history.fetch(:bluebooks)
|
|
169
244
|
|
|
@@ -196,14 +271,20 @@ module Hecks
|
|
|
196
271
|
|
|
197
272
|
# `command_for_verb`'s own root-aggregate half (Guards) —
|
|
198
273
|
# re-derived independently rather than read off `entry[:domain]`/
|
|
199
|
-
# `entry[:aggregate]` (present on a
|
|
274
|
+
# `entry[:aggregate]` (present on a real `build_mutation_trace`
|
|
200
275
|
# entry, but not on every hand-built fixture this property is
|
|
201
276
|
# tested against) so this works from `entry[:verb]` alone, the
|
|
202
277
|
# one field every entry always carries. BUG#5's fix needs the
|
|
203
|
-
#
|
|
204
|
-
# value-object
|
|
278
|
+
# root aggregate specifically — `Value.for_attribute` resolves a
|
|
279
|
+
# value-object type against the root's own namespace only, the
|
|
205
280
|
# same reason `EntityElement#locate_chain` threads `root_aggregate`
|
|
206
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
|
|
207
288
|
def aggregate_for_verb(bluebooks, verb)
|
|
208
289
|
domain_name, aggregate_name, = Naming.split_verb(verb)
|
|
209
290
|
return nil unless domain_name
|
|
@@ -211,7 +292,7 @@ module Hecks
|
|
|
211
292
|
bluebooks[domain_name]&.aggregate(aggregate_name)
|
|
212
293
|
end
|
|
213
294
|
|
|
214
|
-
# `mutation.target`'s own
|
|
295
|
+
# `mutation.target`'s own declaring construct — the root
|
|
215
296
|
# aggregate for an aggregate-owned command (`AddSlot`'s own
|
|
216
297
|
# `:slots`), or the entity a dot-shaped command belongs to
|
|
217
298
|
# (`Board.AddCard`'s own `:cards`, declared on `Board`, not on
|
|
@@ -223,6 +304,15 @@ module Hecks
|
|
|
223
304
|
# each hop's own `owner`. Re-derived independently from
|
|
224
305
|
# `entry[:verb]` alone, the same reasoning `aggregate_for_verb`'s
|
|
225
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
|
|
226
316
|
def owner_for_verb(bluebooks, verb)
|
|
227
317
|
domain_name, aggregate_name, command_path = Naming.split_verb(verb)
|
|
228
318
|
return nil unless command_path
|
|
@@ -235,6 +325,21 @@ module Hecks
|
|
|
235
325
|
aggregate.entities.find { |candidate| candidate.hecks_name == entity_name }
|
|
236
326
|
end
|
|
237
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
|
|
238
343
|
def recompute_mutation(mutation, current, args, before_scope, aggregate, command, owner = aggregate)
|
|
239
344
|
case mutation.op
|
|
240
345
|
when :append
|
|
@@ -242,52 +347,136 @@ module Hecks
|
|
|
242
347
|
when :remove then recompute_remove(current, mutation.source, args)
|
|
243
348
|
when :multiply then recompute_multiply(current, resolve_mutation_source(mutation.source, args))
|
|
244
349
|
when :clamp then recompute_clamp(current, mutation.source)
|
|
350
|
+
when :set then recompute_set(mutation.source, args, aggregate, owner, mutation.target)
|
|
245
351
|
end
|
|
246
352
|
end
|
|
247
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
|
+
|
|
248
421
|
# `EntityElement#appended_to_element`'s own field-mapping half,
|
|
249
|
-
# reproduced: the field map resolved the
|
|
422
|
+
# reproduced: the field map resolved the same two-tier way
|
|
250
423
|
# (`MutationApplier#resolve_append_source` — a caller-supplied
|
|
251
424
|
# arg, or the entity's own current field), then appended. (An
|
|
252
|
-
# entity-dispatched command's own mutations
|
|
425
|
+
# entity-dispatched command's own mutations do reach here —
|
|
253
426
|
# `#build_mutation_trace`'s own comment describing them as never
|
|
254
427
|
# reaching "the entity_element branch" means `MutationApplier#
|
|
255
|
-
# appended`'s own
|
|
428
|
+
# appended`'s own aggregate-level entity_element fallback
|
|
256
429
|
# specifically, which really is unreached from here; entity-owned
|
|
257
430
|
# append dispatches go through `EntityElement#appended_to_element`
|
|
258
|
-
# instead, and
|
|
431
|
+
# instead, and do reach this method.)
|
|
259
432
|
#
|
|
260
433
|
# BUG#5 — an entity-owned `:append` whose target field is itself
|
|
261
434
|
# value-object-typed (`Board.AddCard`'s own `sets :cards, append:
|
|
262
435
|
# { sequence: :sequence }`, `CardSequence`-typed). A caller-
|
|
263
|
-
# supplied arg reaches the
|
|
436
|
+
# supplied arg reaches the real applier (`EntityElement#
|
|
264
437
|
# appended_to_element`) already coerced: `Interpreting#
|
|
265
438
|
# coerce_declared_arguments` runs `Value.for_attribute` over
|
|
266
|
-
#
|
|
439
|
+
# every arg the acting command itself declares, before dispatch
|
|
267
440
|
# ever reaches a mutation applier at all — independent of, and
|
|
268
441
|
# earlier than, anything `appended_to_element`'s own value_object
|
|
269
|
-
# check does. `before_scope[source]` (the entity's
|
|
442
|
+
# check does. `before_scope[source]` (the entity's own current
|
|
270
443
|
# field) needs no such re-coercion here: it's already the
|
|
271
|
-
#
|
|
444
|
+
# materialized shape `build_mutation_trace` snapshotted it in
|
|
272
445
|
# (`Value.materialize`, same as `entry[:after]`), not a raw value
|
|
273
446
|
# sitting behind a live `Value`.
|
|
274
447
|
#
|
|
275
|
-
# BUG#12 — `owner`/`target` (new here) let this
|
|
448
|
+
# BUG#12 — `owner`/`target` (new here) let this also reproduce
|
|
276
449
|
# `EntityElement#fill_declared_defaults`'s own entity-nested-in-
|
|
277
450
|
# entity fallback (`appended_to_element`'s `else` branch, when
|
|
278
451
|
# the appended element is itself an entity — `Card`, nested
|
|
279
452
|
# inside `Board` — not a value object): `owner.attribute(target)
|
|
280
453
|
# &.type` names the appended element's own type; when that names
|
|
281
454
|
# an entity of `aggregate` rather than a value object, every one
|
|
282
|
-
# of
|
|
455
|
+
# of its own declared attributes `fields` doesn't already hold
|
|
283
456
|
# gets `Instance.default_for`'s own default — reused, not
|
|
284
457
|
# reimplemented, for the identical "never agree with itself"
|
|
285
458
|
# reason BUG#5's own coercion re-derivation above already gives:
|
|
286
459
|
# `Instance.default_for` is pre-existing, independently-tested
|
|
287
460
|
# machinery (an ordinary aggregate's own creation already runs
|
|
288
|
-
# through it via `Instance.defaults`), not the
|
|
461
|
+
# through it via `Instance.defaults`), not the new glue
|
|
289
462
|
# (`fill_declared_defaults` itself) this property exists to
|
|
290
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
|
|
291
480
|
def recompute_append(current, source_map, before_scope, args, aggregate, command, owner = aggregate, target = nil)
|
|
292
481
|
fields = source_map.transform_values do |source|
|
|
293
482
|
resolve_mutation_append_field(source, before_scope, args, aggregate, command)
|
|
@@ -305,11 +494,22 @@ module Hecks
|
|
|
305
494
|
# isn't an entity nested directly under `owner` (a value object,
|
|
306
495
|
# or nothing declared at all — `owner.attribute` answering `nil`
|
|
307
496
|
# for a target the DSL itself would already have refused at
|
|
308
|
-
# build time). `owner.entities`,
|
|
309
|
-
# piece nested inside a piece is a child of the
|
|
497
|
+
# build time). `owner.entities`, not `aggregate.entities` — a
|
|
498
|
+
# piece nested inside a piece is a child of the owning entity
|
|
310
499
|
# (`Card` is `Board.entities`, never `Workspace.entities`), the
|
|
311
500
|
# same distinction `EntityElement#appended_to_element`'s own fix
|
|
312
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
|
|
313
513
|
def fill_recompute_declared_defaults(aggregate, owner, target, fields)
|
|
314
514
|
return fields unless target
|
|
315
515
|
|
|
@@ -325,6 +525,18 @@ module Hecks
|
|
|
325
525
|
fields
|
|
326
526
|
end
|
|
327
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
|
|
328
540
|
def resolve_mutation_append_field(source, before_scope, args, aggregate, command)
|
|
329
541
|
return source unless source.is_a?(Symbol)
|
|
330
542
|
return before_scope[source] unless args.key?(source)
|
|
@@ -335,8 +547,8 @@ module Hecks
|
|
|
335
547
|
# `Interpreting#coerce_declared_arguments`'s own coercion,
|
|
336
548
|
# reproduced independently (never calling it again, the same
|
|
337
549
|
# "never agree with itself" rule this whole module's header
|
|
338
|
-
# comment gives) — a raw arg is coerced
|
|
339
|
-
# (`source`) is one of the
|
|
550
|
+
# comment gives) — a raw arg is coerced only when its own name
|
|
551
|
+
# (`source`) is one of the acting command's own declared
|
|
340
552
|
# attributes, exactly the condition that method checks before a
|
|
341
553
|
# real dispatch ever coerces it either. `command.attribute(source)`
|
|
342
554
|
# answering `nil` (a source that names no declared attribute —
|
|
@@ -350,6 +562,15 @@ module Hecks
|
|
|
350
562
|
# throughout this whole property, so the eventual `symbolize_deep`
|
|
351
563
|
# comparison is always materialized-against-materialized, never a
|
|
352
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
|
|
353
574
|
def coerce_recompute_append_arg(aggregate, command, source, raw)
|
|
354
575
|
attribute = command.attribute(source)
|
|
355
576
|
return raw unless attribute
|
|
@@ -358,6 +579,13 @@ module Hecks
|
|
|
358
579
|
end
|
|
359
580
|
|
|
360
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
|
|
361
589
|
def recompute_remove(current, source, args)
|
|
362
590
|
target = symbolize_deep(resolve_mutation_source(source, args))
|
|
363
591
|
Array(current).reject { |element| symbolize_deep(element) == target }
|
|
@@ -367,9 +595,15 @@ module Hecks
|
|
|
367
595
|
# reproduced on plain materialized data instead of a real Value:
|
|
368
596
|
# a single-numeric-field Hash (the VO-typed case — ListCount, one
|
|
369
597
|
# Integer field) scales that field ; a bare Numeric scales itself.
|
|
370
|
-
# `current ||= 0` — the
|
|
598
|
+
# `current ||= 0` — the same phantom-field fallback #multiply
|
|
371
599
|
# itself already gives (unaffected by this session's #clamp fix,
|
|
372
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
|
|
373
607
|
def recompute_multiply(current, amount)
|
|
374
608
|
return :unrecomputable unless amount.is_a?(Numeric)
|
|
375
609
|
|
|
@@ -387,12 +621,18 @@ module Hecks
|
|
|
387
621
|
end
|
|
388
622
|
|
|
389
623
|
# `CommandRules::Arithmetic#clamp`'s own two branches, reproduced
|
|
390
|
-
# the same way #recompute_multiply is — including
|
|
391
|
-
#
|
|
624
|
+
# the same way #recompute_multiply is — including this session's
|
|
625
|
+
# own `current ||= 0` fix (command_rules/arithmetic.rb), the one
|
|
392
626
|
# arithmetic op that didn't have it until now. `mutation.source`
|
|
393
627
|
# is always a literal `[min, max]`, never an argument reference
|
|
394
628
|
# (MutationApplier's own comment on why `resolve_source` is
|
|
395
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
|
|
396
636
|
def recompute_clamp(current, bounds)
|
|
397
637
|
return :unrecomputable unless bounds.is_a?(Array) && bounds.size == 2
|
|
398
638
|
|
|
@@ -411,21 +651,30 @@ module Hecks
|
|
|
411
651
|
end
|
|
412
652
|
|
|
413
653
|
# `CommandRules::Arithmetic#resolve_source`, reproduced: a
|
|
414
|
-
# mutation's source is either the
|
|
415
|
-
#
|
|
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
|
|
416
661
|
def resolve_mutation_source(source, args)
|
|
417
662
|
source.is_a?(Symbol) ? args[source] : source
|
|
418
663
|
end
|
|
419
664
|
|
|
420
|
-
# A generated step's own `args` arrive with
|
|
665
|
+
# A generated step's own `args` arrive with string keys on every
|
|
421
666
|
# nested Hash (the wire/JSON shape `spec/corpus/*.json` already
|
|
422
667
|
# uses) while `history[:mutation_traces]`' own materialized
|
|
423
|
-
# before/after state carries
|
|
668
|
+
# before/after state carries symbol keys throughout (Runtime::
|
|
424
669
|
# Value.materialize's own convention) — two hashes holding the
|
|
425
|
-
# identical fact compare
|
|
670
|
+
# identical fact compare unequal by Ruby's own `Hash#==` unless
|
|
426
671
|
# both sides are normalized the same way first. Recursive, since
|
|
427
672
|
# an appended/removed element can itself nest a value object
|
|
428
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
|
|
429
678
|
def symbolize_deep(value)
|
|
430
679
|
case value
|
|
431
680
|
when Hash then value.to_h { |key, val| [key.to_sym, symbolize_deep(val)] }
|