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
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Fuzzing
|
|
3
|
+
# The one declared set of `FIELDS` that leave a comparison — partition,
|
|
4
|
+
# not filter. Every comparison in lib/hecks/fuzzing that drops a field
|
|
5
|
+
# before comparing two histories names a group here instead of writing
|
|
6
|
+
# its own literal `except(...)` list, so the reason for dropping a field
|
|
7
|
+
# is written exactly once, beside the field.
|
|
8
|
+
#
|
|
9
|
+
# Two specs hold this set honest (spec/fuzzing/nondeterministic_spec.rb):
|
|
10
|
+
# a literal except-list naming any of these fields anywhere under
|
|
11
|
+
# lib/hecks/fuzzing fails the build (the tolerance must be declared
|
|
12
|
+
# here, not re-derived at a call site), and every declared field must
|
|
13
|
+
# actually be produced where its group says — a field nothing emits any
|
|
14
|
+
# more is a stale tolerance and fails too.
|
|
15
|
+
#
|
|
16
|
+
# Groups are keyed by the shape the field rides on, since the same name
|
|
17
|
+
# can be compared on one surface and dropped on another:
|
|
18
|
+
#
|
|
19
|
+
# - `query_row` — one entry of `Replay.call`'s `:queries`.
|
|
20
|
+
# - `outbox_row` — one row of an `:outbox_traces` entry's `:rows`.
|
|
21
|
+
# - `event` — an event hash in full `Event#to_h` form (an outbox
|
|
22
|
+
# row's `:event`, or a Rust binary's `events` entries,
|
|
23
|
+
# string-keyed there).
|
|
24
|
+
# - `history` — the top-level `Replay.call` history hash itself.
|
|
25
|
+
module Nondeterministic
|
|
26
|
+
FIELDS = {
|
|
27
|
+
query_row: {
|
|
28
|
+
instances_at: "a full state snapshot taken for the query oracle's own use " \
|
|
29
|
+
"(Properties::Querying); the same state is already compared as `instances`, " \
|
|
30
|
+
"so keeping it would re-report one instances divergence under every query step"
|
|
31
|
+
}.freeze,
|
|
32
|
+
outbox_row: {
|
|
33
|
+
event_uid: "`Runtime::Outbox::Fanout#rows_for`'s own SecureRandom.uuid, minted fresh per " \
|
|
34
|
+
"enqueue and kept off `Event#to_h` — relay bookkeeping, never something the " \
|
|
35
|
+
"domain produced, so two otherwise-identical replays carry two different uuids",
|
|
36
|
+
delivery_id: "`\"\#{event_uid}/\#{consumer}\"` — inherits event_uid's per-enqueue uuid"
|
|
37
|
+
}.freeze,
|
|
38
|
+
event: {
|
|
39
|
+
occurred_at: "a wall-clock read; never reproducible byte-for-byte between two independent " \
|
|
40
|
+
"runs (Replay's own projected `:events` leave it off for the same reason)"
|
|
41
|
+
}.freeze,
|
|
42
|
+
history: {
|
|
43
|
+
bluebook: "a live IR object (the first-loaded bluebook) handed to properties — an object " \
|
|
44
|
+
"identity, not a value, so two boots never compare equal on it",
|
|
45
|
+
bluebooks: "the full live IR map, keyed by domain — object identities, same as `bluebook`"
|
|
46
|
+
}.freeze
|
|
47
|
+
}.freeze
|
|
48
|
+
|
|
49
|
+
module_function
|
|
50
|
+
|
|
51
|
+
# Lists the nondeterministic field names declared for one comparison group.
|
|
52
|
+
#
|
|
53
|
+
# @param group [Symbol] a key of `FIELDS`, such as `:query_row` or `:event`
|
|
54
|
+
# @return [Array<Symbol>] the field names declared for `group`
|
|
55
|
+
# @raise [KeyError] if `group` names no declared group
|
|
56
|
+
def names(group)
|
|
57
|
+
FIELDS.fetch(group).keys
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Every declared name, across every group.
|
|
61
|
+
#
|
|
62
|
+
# @return [Array<Symbol>] the union of every group's field names, deduplicated
|
|
63
|
+
def all_names
|
|
64
|
+
FIELDS.values.flat_map(&:keys).uniq
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# `hash` without `group`'s fields — symbol keys, the shape every
|
|
68
|
+
# Ruby-side history carries.
|
|
69
|
+
#
|
|
70
|
+
# @param hash [Hash] a symbol-keyed row, event, or history hash to compare
|
|
71
|
+
# @param group [Symbol] a key of `FIELDS` naming which fields to drop
|
|
72
|
+
# @return [Hash] `hash` with `group`'s declared fields removed
|
|
73
|
+
# @raise [KeyError] if `group` names no declared group
|
|
74
|
+
def strip(hash, group)
|
|
75
|
+
hash.except(*names(group))
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
require "json"
|
|
2
2
|
require_relative "replay"
|
|
3
|
+
require_relative "nondeterministic"
|
|
3
4
|
|
|
4
5
|
module Hecks
|
|
5
6
|
module Fuzzing
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
7
|
+
# ## The second differential axis
|
|
8
|
+
#
|
|
9
|
+
# bin/qa_sweep's own `diff_ruby_vs_rust`
|
|
10
|
+
# compares two different engines (the Ruby interpreter vs the compiled
|
|
11
|
+
# Rust kernel) against the same persistence (Memory, always — see
|
|
12
|
+
# `SequenceGenerator`'s own header: sequence generation stays
|
|
10
13
|
# Memory-only, and `IsolatedBoot`'s own header explains why every
|
|
11
14
|
# existing fuzz/replay path structurally cannot reach a real Postgres-
|
|
12
15
|
# bound domain's own SQL compilation). This module compares the
|
|
13
|
-
# opposite pairing: the
|
|
16
|
+
# opposite pairing: the same one Ruby engine, against two different
|
|
14
17
|
# persistences — Memory (the reference, exactly as fast and as
|
|
15
18
|
# deterministic as every other fuzz check) and a real, disposable
|
|
16
19
|
# `PostgresEra` database (the actual SQL compute/rekey/query-pushdown
|
|
@@ -26,20 +29,24 @@ module Hecks
|
|
|
26
29
|
# Memory, unconditionally, no matter what `directory.world` itself
|
|
27
30
|
# declares.
|
|
28
31
|
#
|
|
29
|
-
#
|
|
32
|
+
# ## A separate axis, not a replacement
|
|
33
|
+
#
|
|
34
|
+
# Not a replacement for `bin/qa_sweep`'s own Ruby-vs-Rust differential
|
|
30
35
|
# mode — a genuinely separate axis, opt-in (`--persistence-parity`),
|
|
31
36
|
# because this one pays for a real `PG.connect` and real SQL per
|
|
32
37
|
# dispatch where Memory-vs-Rust pays for neither. See `bin/qa_sweep`'s
|
|
33
38
|
# own `--persistence-parity` handling for the seed-count dial that
|
|
34
39
|
# keeps that cost bounded.
|
|
35
40
|
#
|
|
36
|
-
#
|
|
41
|
+
# ## `left:`/`right:`
|
|
42
|
+
#
|
|
43
|
+
# Generalized — originally hardcoded to Memory vs
|
|
37
44
|
# PostgresEra (the only pairing that existed), now any two of
|
|
38
45
|
# `IsolatedBoot`'s own adapter symbols (`:memory`, `:sqlite`,
|
|
39
46
|
# `:postgres`, `:postgres_era`). Defaults preserve the original
|
|
40
47
|
# pairing exactly, so every existing caller (this file's own spec,
|
|
41
48
|
# `bin/qa_sweep`'s `--persistence-parity`) is unchanged. The second
|
|
42
|
-
# pairing this generalization exists
|
|
49
|
+
# pairing this generalization exists for is Memory vs SQLite
|
|
43
50
|
# (`QualityControlDials::ADAPTER_PARITY_PAIRS`, `bin/qa_sweep`'s own
|
|
44
51
|
# `adapter_parity_sqlite` mode) — `:sqlite` is nearly as cheap as
|
|
45
52
|
# Memory itself (`IsolatedBoot#rebind_to_sqlite!`'s own header: an
|
|
@@ -48,7 +55,9 @@ module Hecks
|
|
|
48
55
|
# loop instead of needing a deferred wave of its own the way
|
|
49
56
|
# PostgresEra does.
|
|
50
57
|
#
|
|
51
|
-
# `database:`/`schema:`
|
|
58
|
+
# ## `database:`/`schema:`
|
|
59
|
+
#
|
|
60
|
+
# Required only when `:postgres_era` is one of
|
|
52
61
|
# the two adapters (the caller — today, only `bin/qa_sweep` — owns the
|
|
53
62
|
# disposable database's whole lifecycle: created before the sweep,
|
|
54
63
|
# dropped after — see that script's own comment, and the discipline
|
|
@@ -58,21 +67,21 @@ module Hecks
|
|
|
58
67
|
module PersistenceParity
|
|
59
68
|
module_function
|
|
60
69
|
|
|
61
|
-
#
|
|
70
|
+
# The same six fields `bin/qa_sweep`'s own `diff_ruby_vs_rust`
|
|
62
71
|
# compares (its own comment: "instances, events, refusals, queries,
|
|
63
72
|
# sagas, reactions") — deliberately the identical set, so a report
|
|
64
73
|
# this mode produces reads exactly like the sibling mode's own,
|
|
65
74
|
# differing only in which two things were compared, not in what
|
|
66
75
|
# "found something" means.
|
|
67
76
|
#
|
|
68
|
-
#
|
|
69
|
-
# method reduces Rust's
|
|
77
|
+
# Simpler normalization than `diff_ruby_vs_rust`, on purpose — that
|
|
78
|
+
# method reduces Rust's own JSON-over-stdout output to "wire
|
|
70
79
|
# precision" and filters known Ruby/Rust structural gaps, because
|
|
71
80
|
# it is comparing two genuinely different engines that are allowed
|
|
72
81
|
# to differ in already-catalogued, understood ways. Both sides here
|
|
73
|
-
# are the
|
|
82
|
+
# are the same Ruby engine (`Replay.call`, called twice, adapter
|
|
74
83
|
# only) — there is no second engine's own known-gap catalogue to
|
|
75
|
-
# filter against, so any real difference
|
|
84
|
+
# filter against, so any real difference is the finding. Both
|
|
76
85
|
# results still round-trip through `JSON.generate`/`JSON.parse`
|
|
77
86
|
# before comparing, matching `diff_ruby_vs_rust`'s own discipline —
|
|
78
87
|
# not because either side needs a wire-format reduction, but so
|
|
@@ -80,6 +89,21 @@ module Hecks
|
|
|
80
89
|
# sides identically (a `Runtime::Value`, a `Symbol` key, a `Time`
|
|
81
90
|
# nobody asked for — none of that survives an accidental leak into
|
|
82
91
|
# this comparison unnoticed).
|
|
92
|
+
#
|
|
93
|
+
# @param domain_path [String] path to the domain directory to boot
|
|
94
|
+
# @param steps [Array<Hash>] the step list to replay against both adapters
|
|
95
|
+
# @param left [Symbol] the first adapter to replay against, one of
|
|
96
|
+
# `IsolatedBoot`'s adapter symbols (`:memory`, `:sqlite`, `:postgres`,
|
|
97
|
+
# `:postgres_era`)
|
|
98
|
+
# @param right [Symbol] the second adapter to replay against, same set as `left`
|
|
99
|
+
# @param database [String, nil] connection identity for `:postgres_era`; required
|
|
100
|
+
# when `left` or `right` is `:postgres_era`, ignored otherwise
|
|
101
|
+
# @param schema [String, nil] disposable schema name for `:postgres_era`; required
|
|
102
|
+
# when `left` or `right` is `:postgres_era`, ignored otherwise
|
|
103
|
+
# @return [Array<Hash>] divergence entries, each `{field: String, left => Object,
|
|
104
|
+
# right => Object}` — `left`'s and `right`'s own adapter symbols become the
|
|
105
|
+
# entry's own keys, holding each side's JSON-shaped value for that field; empty
|
|
106
|
+
# if both sides agree on every field
|
|
83
107
|
def diff(domain_path, steps, left: :memory, right: :postgres_era, database: nil, schema: nil)
|
|
84
108
|
left_result = Replay.call(domain_path, steps, adapter: left, database: database, schema: schema)
|
|
85
109
|
right_result = Replay.call(domain_path, steps, adapter: right, database: database, schema: schema)
|
|
@@ -94,8 +118,25 @@ module Hecks
|
|
|
94
118
|
divergences
|
|
95
119
|
end
|
|
96
120
|
|
|
121
|
+
# Round-trips `value` through JSON, the same wire-precision reduction
|
|
122
|
+
# `diff_ruby_vs_rust` uses so `Hash#==`/`Array#==` compares plain,
|
|
123
|
+
# JSON-shaped data on both sides.
|
|
124
|
+
#
|
|
125
|
+
# @param value [Object] any JSON-serializable value from a replay result
|
|
126
|
+
# @return [Object] `value`, JSON-round-tripped: Symbol keys become Strings,
|
|
127
|
+
# and any non-JSON-native value surfaces as its own JSON form
|
|
97
128
|
def as_json(value) = JSON.parse(JSON.generate(value))
|
|
98
129
|
|
|
130
|
+
# Compares both sides' stored instances.
|
|
131
|
+
#
|
|
132
|
+
# @param left_result [Hash] `left`'s replay result, as returned by `Fuzzing::Replay.call`
|
|
133
|
+
# @param right_result [Hash] `right`'s replay result, as returned by `Fuzzing::Replay.call`
|
|
134
|
+
# @param left [Symbol] the adapter `left_result` was replayed against; becomes
|
|
135
|
+
# this entry's own key
|
|
136
|
+
# @param right [Symbol] the adapter `right_result` was replayed against; becomes
|
|
137
|
+
# this entry's own key
|
|
138
|
+
# @return [Array<Hash>] one `{field: "instances", left => Object, right => Object}`
|
|
139
|
+
# entry if the two sides' JSON-shaped instances differ; empty otherwise
|
|
99
140
|
def diff_instances(left_result, right_result, left, right)
|
|
100
141
|
l = as_json(left_result[:instances])
|
|
101
142
|
r = as_json(right_result[:instances])
|
|
@@ -104,6 +145,16 @@ module Hecks
|
|
|
104
145
|
[{ field: "instances", left => l, right => r }]
|
|
105
146
|
end
|
|
106
147
|
|
|
148
|
+
# Compares both sides' emitted events.
|
|
149
|
+
#
|
|
150
|
+
# @param left_result [Hash] `left`'s replay result, as returned by `Fuzzing::Replay.call`
|
|
151
|
+
# @param right_result [Hash] `right`'s replay result, as returned by `Fuzzing::Replay.call`
|
|
152
|
+
# @param left [Symbol] the adapter `left_result` was replayed against; becomes
|
|
153
|
+
# this entry's own key
|
|
154
|
+
# @param right [Symbol] the adapter `right_result` was replayed against; becomes
|
|
155
|
+
# this entry's own key
|
|
156
|
+
# @return [Array<Hash>] one `{field: "events", left => Object, right => Object}`
|
|
157
|
+
# entry if the two sides' JSON-shaped events differ; empty otherwise
|
|
107
158
|
def diff_events(left_result, right_result, left, right)
|
|
108
159
|
l = as_json(left_result[:events])
|
|
109
160
|
r = as_json(right_result[:events])
|
|
@@ -112,11 +163,22 @@ module Hecks
|
|
|
112
163
|
[{ field: "events", left => l, right => r }]
|
|
113
164
|
end
|
|
114
165
|
|
|
166
|
+
# Compares both sides' refusals.
|
|
167
|
+
#
|
|
115
168
|
# `verb:`/`kind:` normalized to plain strings the same way
|
|
116
169
|
# `diff_ruby_vs_rust`'s own `ruby_refusals` mapping does — both
|
|
117
170
|
# sides here already answer strings (`Replay#refusal_kind` always
|
|
118
171
|
# returns one), so this is belt-and-suspenders consistency with the
|
|
119
172
|
# sibling mode's own shape, not a real coercion.
|
|
173
|
+
#
|
|
174
|
+
# @param left_result [Hash] `left`'s replay result, as returned by `Fuzzing::Replay.call`
|
|
175
|
+
# @param right_result [Hash] `right`'s replay result, as returned by `Fuzzing::Replay.call`
|
|
176
|
+
# @param left [Symbol] the adapter `left_result` was replayed against; becomes
|
|
177
|
+
# this entry's own key
|
|
178
|
+
# @param right [Symbol] the adapter `right_result` was replayed against; becomes
|
|
179
|
+
# this entry's own key
|
|
180
|
+
# @return [Array<Hash>] one `{field: "refusals", left => Object, right => Object}`
|
|
181
|
+
# entry if the two sides' normalized refusals differ; empty otherwise
|
|
120
182
|
def diff_refusals(left_result, right_result, left, right)
|
|
121
183
|
normalize = lambda do |refusals|
|
|
122
184
|
refusals.map { |r| { "verb" => r[:verb].to_s, "kind" => r[:kind].to_s, "error" => r[:error] } }
|
|
@@ -128,14 +190,22 @@ module Hecks
|
|
|
128
190
|
[{ field: "refusals", left => l, right => r }]
|
|
129
191
|
end
|
|
130
192
|
|
|
131
|
-
#
|
|
132
|
-
#
|
|
133
|
-
#
|
|
134
|
-
#
|
|
135
|
-
#
|
|
136
|
-
#
|
|
193
|
+
# Compares both sides' query answers.
|
|
194
|
+
#
|
|
195
|
+
# `Nondeterministic`'s `query_row` group dropped from every entry —
|
|
196
|
+
# the same group `Differential.diff` drops, for the reason declared
|
|
197
|
+
# there (already covered by `diff_instances` above).
|
|
198
|
+
#
|
|
199
|
+
# @param left_result [Hash] `left`'s replay result, as returned by `Fuzzing::Replay.call`
|
|
200
|
+
# @param right_result [Hash] `right`'s replay result, as returned by `Fuzzing::Replay.call`
|
|
201
|
+
# @param left [Symbol] the adapter `left_result` was replayed against; becomes
|
|
202
|
+
# this entry's own key
|
|
203
|
+
# @param right [Symbol] the adapter `right_result` was replayed against; becomes
|
|
204
|
+
# this entry's own key
|
|
205
|
+
# @return [Array<Hash>] one `{field: "queries", left => Object, right => Object}`
|
|
206
|
+
# entry if the two sides' stripped, JSON-shaped queries differ; empty otherwise
|
|
137
207
|
def diff_queries(left_result, right_result, left, right)
|
|
138
|
-
strip = ->(rows) { rows.map { |row|
|
|
208
|
+
strip = ->(rows) { rows.map { |row| Nondeterministic.strip(row, :query_row) } }
|
|
139
209
|
l = as_json(strip.call(left_result[:queries]))
|
|
140
210
|
r = as_json(strip.call(right_result[:queries]))
|
|
141
211
|
return [] if l == r
|
|
@@ -143,6 +213,16 @@ module Hecks
|
|
|
143
213
|
[{ field: "queries", left => l, right => r }]
|
|
144
214
|
end
|
|
145
215
|
|
|
216
|
+
# Compares both sides' saga logs.
|
|
217
|
+
#
|
|
218
|
+
# @param left_result [Hash] `left`'s replay result, as returned by `Fuzzing::Replay.call`
|
|
219
|
+
# @param right_result [Hash] `right`'s replay result, as returned by `Fuzzing::Replay.call`
|
|
220
|
+
# @param left [Symbol] the adapter `left_result` was replayed against; becomes
|
|
221
|
+
# this entry's own key
|
|
222
|
+
# @param right [Symbol] the adapter `right_result` was replayed against; becomes
|
|
223
|
+
# this entry's own key
|
|
224
|
+
# @return [Array<Hash>] one `{field: "sagas", left => Object, right => Object}`
|
|
225
|
+
# entry if the two sides' JSON-shaped sagas differ; empty otherwise
|
|
146
226
|
def diff_sagas(left_result, right_result, left, right)
|
|
147
227
|
l = as_json(left_result[:sagas])
|
|
148
228
|
r = as_json(right_result[:sagas])
|
|
@@ -151,6 +231,16 @@ module Hecks
|
|
|
151
231
|
[{ field: "sagas", left => l, right => r }]
|
|
152
232
|
end
|
|
153
233
|
|
|
234
|
+
# Compares both sides' reaction logs.
|
|
235
|
+
#
|
|
236
|
+
# @param left_result [Hash] `left`'s replay result, as returned by `Fuzzing::Replay.call`
|
|
237
|
+
# @param right_result [Hash] `right`'s replay result, as returned by `Fuzzing::Replay.call`
|
|
238
|
+
# @param left [Symbol] the adapter `left_result` was replayed against; becomes
|
|
239
|
+
# this entry's own key
|
|
240
|
+
# @param right [Symbol] the adapter `right_result` was replayed against; becomes
|
|
241
|
+
# this entry's own key
|
|
242
|
+
# @return [Array<Hash>] one `{field: "reactions", left => Object, right => Object}`
|
|
243
|
+
# entry if the two sides' JSON-shaped reactions differ; empty otherwise
|
|
154
244
|
def diff_reactions(left_result, right_result, left, right)
|
|
155
245
|
l = as_json(left_result[:reactions])
|
|
156
246
|
r = as_json(right_result[:reactions])
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Fuzzing
|
|
3
3
|
module Properties
|
|
4
|
-
#
|
|
4
|
+
# Angle-9 — `corrects` (retroactive correction) had exactly one
|
|
5
5
|
# declaration anywhere in the corpus (`examples/banking/bluebook/
|
|
6
6
|
# deposit_accounts.bluebook:353`, aggregate-level) and no property
|
|
7
7
|
# anywhere in this file ever checked it, and no `FEATURE_COVERAGE`
|
|
@@ -9,7 +9,7 @@ module Hecks
|
|
|
9
9
|
# stress_domains/corrections` gives it its first real coverage; see
|
|
10
10
|
# that domain's own NOTES.md for what it found (an entity-level
|
|
11
11
|
# `corrects` crashes Ruby at dispatch outright, and Rust's own
|
|
12
|
-
# generated code has
|
|
12
|
+
# generated code has no admissibility check for it at all — neither
|
|
13
13
|
# engine can be compared on the untested combination this property
|
|
14
14
|
# was written to watch, which is itself the headline finding).
|
|
15
15
|
module Corrections
|
|
@@ -19,32 +19,37 @@ module Hecks
|
|
|
19
19
|
# entities`, below, is the same recursive walk `SequenceGenerator::
|
|
20
20
|
# Catalog#each_entity_chain` already uses, for the identical reason:
|
|
21
21
|
# `Aggregate#entities`/`Entity#entities` nest, ADR 0026, S17). For
|
|
22
|
-
# every such command, every event
|
|
22
|
+
# every such command, every event this history actually recorded
|
|
23
23
|
# under one of the command's own `emits` names must have an event
|
|
24
24
|
# named by the command's own `corrects` target — same aggregate-
|
|
25
|
-
# qualified name, same id — appearing
|
|
25
|
+
# qualified name, same id — appearing strictly earlier in the same
|
|
26
26
|
# history.
|
|
27
27
|
#
|
|
28
28
|
# `history[:events]` is already in occurrence order (`Replay.call`'s
|
|
29
29
|
# own `runtime.events`, appended as each step dispatches) — "earlier"
|
|
30
30
|
# is therefore "earlier in this array," no timestamp comparison
|
|
31
31
|
# needed, and no per-step attribution back to which command produced
|
|
32
|
-
# which event is needed either: an event's own declared
|
|
32
|
+
# which event is needed either: an event's own declared name already
|
|
33
33
|
# identifies the one command in its aggregate that can produce it
|
|
34
34
|
# (`AggregateBuilder::Sealing#seal_correction_targets`'s own
|
|
35
35
|
# `emitted_by` hash reads the identical fact, one level shallower).
|
|
36
36
|
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
37
|
+
# Why this cannot be `GUARANTEED_BY_CONSTRUCTION` the way the
|
|
38
|
+
# aggregate-level case almost is: `CommandRules::Admissibility#
|
|
39
39
|
# enforce_correction_target` (the dispatch-time check) and
|
|
40
40
|
# `AggregateBuilder::Sealing#seal_correction_targets` (the build-time
|
|
41
|
-
# check) both exist
|
|
41
|
+
# check) both exist only for an aggregate-level `corrects` —
|
|
42
42
|
# `EntityInterpreter#step_enforce_givens` never calls the former at
|
|
43
43
|
# all, and the latter walks only `@commands` (the aggregate's own
|
|
44
44
|
# top-level list), never `@entities`. An entity-level `corrects`
|
|
45
|
-
# mutation is invisible to
|
|
45
|
+
# mutation is invisible to both doors today — this property is the
|
|
46
46
|
# only thing anywhere, on either engine, that would ever catch one
|
|
47
47
|
# going wrong.
|
|
48
|
+
#
|
|
49
|
+
# @param history [Hash] a replayed history as returned by `Replay.call`
|
|
50
|
+
# @return [true, String] true if every event emitted by a `corrects`-bearing
|
|
51
|
+
# command has a matching, strictly earlier corrected event in the same
|
|
52
|
+
# history; otherwise a message listing every unmatched correction
|
|
48
53
|
def corrections_reference_an_emitted_event(history)
|
|
49
54
|
violations = []
|
|
50
55
|
|
|
@@ -72,6 +77,17 @@ module Hecks
|
|
|
72
77
|
violations.empty? || violations.uniq.join("; ")
|
|
73
78
|
end
|
|
74
79
|
|
|
80
|
+
# Finds every occurrence of `produced_event_name`, on `aggregate_key`, with no
|
|
81
|
+
# matching `corrected_event` for the same id appearing earlier in `events`.
|
|
82
|
+
#
|
|
83
|
+
# @param events [Array<Hash>] `history[:events]`, in occurrence order
|
|
84
|
+
# @param aggregate_key [String] `"domain::AggregateName"` the events belong to
|
|
85
|
+
# @param produced_event_name [String] name of the event a `corrects` mutation's
|
|
86
|
+
# command emits
|
|
87
|
+
# @param corrected_event [String] name of the event the mutation claims to correct
|
|
88
|
+
# @param command_name [String] the command's own `hecks_name`, for the message
|
|
89
|
+
# @return [Array<String>] one message per occurrence with no matching earlier
|
|
90
|
+
# corrected event; empty when every occurrence is matched
|
|
75
91
|
def unmatched_corrections(events, aggregate_key, produced_event_name, corrected_event, command_name)
|
|
76
92
|
own_events = events.each_with_index.select do |event, _index|
|
|
77
93
|
event[:name] == produced_event_name && event[:aggregate] == aggregate_key
|
|
@@ -90,6 +106,15 @@ module Hecks
|
|
|
90
106
|
end
|
|
91
107
|
end
|
|
92
108
|
|
|
109
|
+
# Yields every command declared on `owner`, then recurses into each of its
|
|
110
|
+
# entities to yield theirs too, at any nesting depth (ADR 0026, S17).
|
|
111
|
+
#
|
|
112
|
+
# @param owner [Bluebook::Aggregate, Bluebook::Entity] the aggregate or entity
|
|
113
|
+
# whose own commands, and whose entities' commands, to walk
|
|
114
|
+
# @yield [command] once per declared command, aggregate-level or nested
|
|
115
|
+
# @yieldparam command [Bluebook::Command] a command declared on `owner` or one
|
|
116
|
+
# of its entities
|
|
117
|
+
# @return [void]
|
|
93
118
|
def each_command_including_entities(owner, &block)
|
|
94
119
|
owner.commands.each(&block)
|
|
95
120
|
owner.entities.each { |entity| each_command_including_entities(entity, &block) }
|