hecks 1.2.0 → 1.4.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 +7 -7
- data/lib/hecks/adapters/driven/d1.rb +187 -23
- data/lib/hecks/adapters/driven/folder.rb +10 -10
- data/lib/hecks/adapters/driven/google_authentication.rb +8 -8
- data/lib/hecks/adapters/driven/governance_authorization.rb +31 -13
- data/lib/hecks/adapters/driven/heki/journal.rb +60 -2
- data/lib/hecks/adapters/driven/heki/saga_store.rb +5 -5
- data/lib/hecks/adapters/driven/heki.rb +13 -7
- data/lib/hecks/adapters/driven/identity_registry.rb +2 -2
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +3 -3
- data/lib/hecks/adapters/driven/lambda/client.rb +34 -9
- data/lib/hecks/adapters/driven/lambda.rb +39 -33
- data/lib/hecks/adapters/driven/local_storage.rb +17 -10
- data/lib/hecks/adapters/driven/memory.rb +205 -9
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +1 -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/postgres_era.adapter +5 -0
- data/lib/hecks/adapters/driven/prism.rb +4 -4
- 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.rb +4 -4
- data/lib/hecks/adapters/driving/github_webhook.rb +145 -0
- data/lib/hecks/behaviors/dsl.rb +2 -2
- data/lib/hecks/behaviors/expectations.rb +51 -23
- data/lib/hecks/behaviors/ir.rb +1 -1
- data/lib/hecks/behaviors/rspec.rb +1 -1
- data/lib/hecks/behaviors/runner.rb +2 -2
- data/lib/hecks/behaviors.rb +1 -1
- data/lib/hecks/bluebook/aggregate.rb +13 -13
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +10 -10
- data/lib/hecks/bluebook/assembly/build.rb +1 -1
- data/lib/hecks/bluebook/assembly/contract.rb +39 -16
- data/lib/hecks/bluebook/assembly/contracts.rb +40 -38
- data/lib/hecks/bluebook/assembly/marks.rb +18 -18
- data/lib/hecks/bluebook/assembly/specializer.rb +19 -21
- data/lib/hecks/bluebook/assembly.rb +9 -9
- data/lib/hecks/bluebook/attribute.rb +9 -9
- data/lib/hecks/bluebook/behaviour/aggregate.rb +11 -11
- data/lib/hecks/bluebook/behaviour/attribute.rb +5 -5
- data/lib/hecks/bluebook/behaviour/chapter.rb +23 -5
- data/lib/hecks/bluebook/behaviour/command.rb +23 -23
- data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -3
- data/lib/hecks/bluebook/behaviour/entity.rb +8 -8
- data/lib/hecks/bluebook/behaviour/hexagon.rb +4 -4
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +5 -5
- data/lib/hecks/bluebook/behaviour/policy.rb +12 -12
- data/lib/hecks/bluebook/behaviour/process_manager.rb +7 -7
- data/lib/hecks/bluebook/behaviour/query.rb +1 -1
- data/lib/hecks/bluebook/behaviour/read_model.rb +8 -8
- data/lib/hecks/bluebook/behaviour/traits.rb +12 -12
- data/lib/hecks/bluebook/behaviour/value_object.rb +6 -6
- data/lib/hecks/bluebook/capabilities.rb +27 -0
- data/lib/hecks/bluebook/chapter.rb +28 -9
- data/lib/hecks/bluebook/command.rb +12 -12
- data/lib/hecks/bluebook/domain_port.rb +9 -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 +96 -96
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +41 -41
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +22 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +111 -74
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +48 -30
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +103 -103
- 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 +56 -56
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +89 -30
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +17 -17
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
- data/lib/hecks/bluebook/dsl/policy_builder.rb +30 -21
- 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 +35 -35
- data/lib/hecks/bluebook/dsl/query_builder.rb +5 -5
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +34 -34
- data/lib/hecks/bluebook/dsl/rule_reference.rb +41 -39
- data/lib/hecks/bluebook/dsl/translation_builder.rb +9 -9
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +16 -16
- 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 +11 -11
- data/lib/hecks/bluebook/expression/ast_json.rb +20 -20
- data/lib/hecks/bluebook/expression/ast_reader.rb +3 -3
- data/lib/hecks/bluebook/expression/canonical_form.rb +9 -9
- data/lib/hecks/bluebook/expression/evaluator.rb +18 -18
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +18 -18
- data/lib/hecks/bluebook/expression/resolver.rb +60 -62
- data/lib/hecks/bluebook/hexagon.rb +1 -1
- data/lib/hecks/bluebook/lifecycle.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/judge.rb +122 -86
- data/lib/hecks/bluebook/meta_validator/plan.rb +39 -39
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -2
- data/lib/hecks/bluebook/meta_validator/readings.rb +43 -43
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +44 -37
- data/lib/hecks/bluebook/meta_validator/shapes.rb +25 -21
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +145 -31
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +6 -6
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +5 -5
- data/lib/hecks/bluebook/meta_validator.rb +70 -70
- data/lib/hecks/bluebook/model_check.rb +301 -84
- data/lib/hecks/bluebook/pattern_subset.rb +9 -9
- data/lib/hecks/bluebook/policy.rb +15 -13
- data/lib/hecks/bluebook/process_manager.rb +14 -14
- data/lib/hecks/bluebook/project_register.rb +6 -6
- data/lib/hecks/bluebook/query.rb +4 -4
- data/lib/hecks/bluebook/read_model.rb +14 -14
- data/lib/hecks/bluebook/reference.rb +8 -8
- data/lib/hecks/bluebook/smoke_test.rb +19 -19
- data/lib/hecks/bluebook/synthesizer.rb +12 -12
- data/lib/hecks/bluebook/translation.rb +4 -4
- data/lib/hecks/bluebook/value_object.rb +6 -6
- data/lib/hecks/bluebook.rb +2 -2
- data/lib/hecks/codemod/legacy_dispatch_args.rb +299 -0
- data/lib/hecks/codemod/legacy_dispatch_recorder.rb +186 -0
- data/lib/hecks/codemod.rb +36 -35
- data/lib/hecks/construct.rb +6 -6
- data/lib/hecks/corpus.rb +317 -0
- data/lib/hecks/deprecation.rb +95 -0
- data/lib/hecks/doc/reference.rb +19 -19
- data/lib/hecks/embryonaut_bluebook.rb +11 -11
- 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 +79 -32
- data/lib/hecks/facade/json_door.rb +106 -25
- data/lib/hecks/facade/surface/aggregate_door.rb +42 -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 -20
- data/lib/hecks/forms/html.rb +51 -7
- data/lib/hecks/forms/index_renderer.rb +14 -2
- data/lib/hecks/forms/params.rb +120 -23
- data/lib/hecks/forms/port_argument.rb +2 -2
- data/lib/hecks/forms/query_form_renderer.rb +2 -2
- data/lib/hecks/forms/record_renderer.rb +2 -2
- data/lib/hecks/forms/record_table.rb +1 -1
- data/lib/hecks/forms/value_object_shape.rb +3 -3
- data/lib/hecks/forms.rb +24 -4
- data/lib/hecks/fqn.rb +1 -1
- data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
- data/lib/hecks/framework.rb +48 -17
- data/lib/hecks/freezer.rb +11 -11
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +90 -90
- data/lib/hecks/fuzzing/combination_miner.rb +119 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +361 -0
- data/lib/hecks/fuzzing/coverage_campaign.rb +118 -0
- data/lib/hecks/fuzzing/differential.rb +158 -0
- data/lib/hecks/fuzzing/domain_generator.rb +694 -0
- data/lib/hecks/fuzzing/era_boundary.rb +124 -0
- data/lib/hecks/fuzzing/form_census.rb +199 -0
- data/lib/hecks/fuzzing/generated_domain_check.rb +95 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +6 -6
- data/lib/hecks/fuzzing/isolated_boot.rb +226 -38
- data/lib/hecks/fuzzing/nondeterministic.rb +67 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +161 -0
- data/lib/hecks/fuzzing/properties/corrections.rb +100 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +321 -41
- data/lib/hecks/fuzzing/properties/guards.rb +129 -26
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +32 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +31 -11
- data/lib/hecks/fuzzing/properties/outbox.rb +142 -0
- data/lib/hecks/fuzzing/properties/querying.rb +17 -17
- data/lib/hecks/fuzzing/properties.rb +84 -39
- data/lib/hecks/fuzzing/qa_settings.rb +152 -0
- data/lib/hecks/fuzzing/replay.rb +224 -114
- data/lib/hecks/fuzzing/rotation_priority.rb +94 -0
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +113 -0
- data/lib/hecks/fuzzing/self_consistency.rb +676 -0
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +526 -0
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +101 -30
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +55 -8
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +31 -11
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +123 -32
- data/lib/hecks/fuzzing/sequence_generator.rb +152 -31
- data/lib/hecks/fuzzing/shrinker.rb +197 -0
- data/lib/hecks/fuzzing/structural_skips.rb +39 -0
- data/lib/hecks/fuzzing/sweep_depth.rb +53 -0
- data/lib/hecks/fuzzing/target_capabilities.rb +180 -0
- data/lib/hecks/fuzzing/value_generator.rb +66 -14
- data/lib/hecks/fuzzing.rb +11 -0
- data/lib/hecks/grammar/evolve.rb +10 -10
- data/lib/hecks/grammar.rb +7 -7
- data/lib/hecks/ir.rb +13 -13
- 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 +365 -15
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/literal.rb +9 -9
- data/lib/hecks/naming.rb +89 -21
- data/lib/hecks/ports/access_control.rb +58 -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 +53 -11
- data/lib/hecks/ports/clock.rb +42 -23
- data/lib/hecks/ports/extraction.rb +16 -0
- data/lib/hecks/ports/identity_assignment.rb +24 -2
- data/lib/hecks/ports/identity_generation.rb +17 -3
- data/lib/hecks/ports/identity_resolution.rb +18 -1
- 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 +160 -58
- 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 +28 -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 +44 -6
- 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 +57 -4
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +338 -83
- 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 +42 -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 +18 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +5 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +1 -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 +29 -6
- 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 +3 -3
- data/lib/hecks/ports/query/ordering.rb +6 -6
- data/lib/hecks/ports/query.rb +35 -0
- data/lib/hecks/projections/bootstrap_table.rb +112 -0
- data/lib/hecks/projections/diagrams.rb +75 -75
- data/lib/hecks/projections/glossary/html.rb +250 -0
- data/lib/hecks/projections/glossary/markdown.rb +105 -0
- data/lib/hecks/projections/glossary/mermaid.rb +110 -0
- data/lib/hecks/projections/glossary/page.css +271 -0
- data/lib/hecks/projections/glossary/page.js +72 -0
- data/lib/hecks/projections/glossary/sections.rb +17 -0
- data/lib/hecks/projections/glossary/sentences.rb +205 -0
- data/lib/hecks/projections/glossary.rb +214 -286
- data/lib/hecks/projections/ir.rb +1 -1
- data/lib/hecks/projections/model/deviations.rb +18 -17
- data/lib/hecks/projections/model.rb +25 -21
- data/lib/hecks/projections/oidc.rb +7 -7
- data/lib/hecks/projections/parser_table.rb +5 -5
- data/lib/hecks/projections/reference.rb +3 -3
- data/lib/hecks/projections/rust_vocabulary.rb +443 -0
- data/lib/hecks/projections/shape.rb +2 -2
- data/lib/hecks/projections/statements.rb +11 -11
- data/lib/hecks/projections/vocabulary.rb +9 -9
- data/lib/hecks/projections.rb +5 -3
- data/lib/hecks/projector/cli_projector.rb +29 -29
- data/lib/hecks/projector/docs_projector.rb +13 -13
- data/lib/hecks/projector/exporter.rb +42 -21
- data/lib/hecks/projector/ir_projector.rb +1 -1
- data/lib/hecks/projector/narrate_projector.rb +15 -22
- data/lib/hecks/projector/target.rb +13 -13
- data/lib/hecks/projector.rb +15 -15
- data/lib/hecks/query_ir.rb +47 -47
- data/lib/hecks/query_specification/common/comparators.rb +19 -3
- data/lib/hecks/query_specification/common/comparison.rb +132 -24
- data/lib/hecks/query_specification/common/dsl.rb +65 -9
- data/lib/hecks/query_specification/common/null_policy.rb +57 -13
- 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 +3 -3
- data/lib/hecks/router/namespace_installer.rb +3 -3
- data/lib/hecks/router.rb +1 -1
- data/lib/hecks/runtime/aggregate_lock.rb +11 -11
- data/lib/hecks/runtime/boot_gates.rb +3 -3
- data/lib/hecks/runtime/caller.rb +8 -8
- data/lib/hecks/runtime/capability_graph.rb +2 -2
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +22 -25
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +64 -67
- data/lib/hecks/runtime/command_interpreter.rb +159 -102
- data/lib/hecks/runtime/command_rules/admissibility.rb +69 -69
- data/lib/hecks/runtime/command_rules/arithmetic.rb +137 -91
- 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 +94 -19
- data/lib/hecks/runtime/command_rules.rb +3 -0
- data/lib/hecks/runtime/dependency_planning.rb +11 -11
- data/lib/hecks/runtime/dispatcher.rb +283 -90
- data/lib/hecks/runtime/entity_element.rb +282 -46
- data/lib/hecks/runtime/entity_interpreter.rb +160 -69
- data/lib/hecks/runtime/errors.rb +19 -19
- data/lib/hecks/runtime/event.rb +6 -6
- data/lib/hecks/runtime/identity.rb +22 -22
- data/lib/hecks/runtime/instance.rb +39 -14
- data/lib/hecks/runtime/interpreting.rb +12 -12
- data/lib/hecks/runtime/invocation.rb +276 -0
- data/lib/hecks/runtime/loader.rb +14 -14
- data/lib/hecks/runtime/outbox.rb +23 -23
- data/lib/hecks/runtime/policy_interpreter.rb +54 -54
- data/lib/hecks/runtime/port_operation_interpreter.rb +22 -19
- data/lib/hecks/runtime/query_interpreter.rb +111 -56
- data/lib/hecks/runtime/reaction_invocation.rb +76 -9
- data/lib/hecks/runtime/read_model_interpreter.rb +40 -40
- data/lib/hecks/runtime/rebuild_sweep.rb +4 -4
- data/lib/hecks/runtime/reference_hop.rb +6 -6
- data/lib/hecks/runtime/refusal_wording.rb +92 -112
- data/lib/hecks/runtime/registry/saga_persistence.rb +21 -21
- data/lib/hecks/runtime/registry/verification.rb +36 -26
- data/lib/hecks/runtime/registry.rb +56 -27
- data/lib/hecks/runtime/remote_dispatcher.rb +38 -23
- data/lib/hecks/runtime/routing.rb +10 -88
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +17 -17
- data/lib/hecks/runtime/saga_interpreter.rb +93 -56
- data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
- data/lib/hecks/runtime/tenant_check.rb +9 -9
- data/lib/hecks/runtime/tenant_scope.rb +5 -5
- data/lib/hecks/runtime/value/admission.rb +75 -30
- data/lib/hecks/runtime/value/coercion.rb +379 -226
- data/lib/hecks/runtime/value/entity_list_coercion.rb +248 -0
- data/lib/hecks/runtime/value.rb +28 -23
- data/lib/hecks/runtime.rb +7 -7
- data/lib/hecks/storehouse.rb +64 -64
- data/lib/hecks/version.rb +3 -3
- data/lib/hecks/vocabulary.rb +207 -5
- data/lib/hecks.rb +13 -11
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +11 -11
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +14 -12
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +11 -11
- metadata +43 -2
|
@@ -6,7 +6,7 @@ require_relative "identity"
|
|
|
6
6
|
require_relative "instance"
|
|
7
7
|
require_relative "value"
|
|
8
8
|
require_relative "refusal_wording"
|
|
9
|
-
require_relative "
|
|
9
|
+
require_relative "invocation"
|
|
10
10
|
require_relative "dependency_planning"
|
|
11
11
|
require_relative "../ports/persistence/execution"
|
|
12
12
|
require_relative "entity_element"
|
|
@@ -23,9 +23,9 @@ module Hecks
|
|
|
23
23
|
# entity-owned commands.
|
|
24
24
|
class EntityInterpreter
|
|
25
25
|
include Interpreting
|
|
26
|
-
#
|
|
26
|
+
# The same payload gate aggregate commands and port operations already
|
|
27
27
|
# run — bug audit H1 (docs/audits/2026-08-10-main-bug-audit.md): this
|
|
28
|
-
# class used to run
|
|
28
|
+
# class used to run neither refuse_unknown_arguments nor
|
|
29
29
|
# refuse_absent_arguments, on a comment claiming "an entity inherits
|
|
30
30
|
# its aggregate's own gate." Nothing on the entity dispatch path ever
|
|
31
31
|
# ran one — confirmed live, `LedgerEntry.Reverse` accepted an
|
|
@@ -39,11 +39,9 @@ module Hecks
|
|
|
39
39
|
|
|
40
40
|
attr_reader :registry
|
|
41
41
|
|
|
42
|
-
#
|
|
43
|
-
# (language/bluebook/vocabulary.bluebook
|
|
44
|
-
#
|
|
45
|
-
# own DISPATCH_ORDER is; see that constant's doc comment for why this is
|
|
46
|
-
# hand-typed rather than read live off the meta-domain at every dispatch.
|
|
42
|
+
# **The declared order** — Vocabulary::EntityDispatchOrder
|
|
43
|
+
# (language/bluebook/vocabulary.bluebook), read off the generated table
|
|
44
|
+
# the same way CommandInterpreter's own DISPATCH_ORDER is.
|
|
47
45
|
# `refuse_unknown_arguments`/`refuse_absent_arguments` now lead it, same
|
|
48
46
|
# position `AggregateDispatchOrder` holds them at (H1, above) — the only
|
|
49
47
|
# remaining difference from the aggregate order is no
|
|
@@ -55,7 +53,7 @@ module Hecks
|
|
|
55
53
|
# see that constant's own comment.
|
|
56
54
|
MAX_STALE_WRITE_RETRIES = 5
|
|
57
55
|
|
|
58
|
-
# `instance` is the
|
|
56
|
+
# `instance` is the parent aggregate record (what gets saved and
|
|
59
57
|
# returned) ; `element`/`view` are the entity piece itself — `view`
|
|
60
58
|
# wraps `element` as it stood at `locate_element`, pre-mutation, and
|
|
61
59
|
# `enforce_ensures` builds its own settled wrapper off `element` as it
|
|
@@ -63,7 +61,7 @@ module Hecks
|
|
|
63
61
|
#
|
|
64
62
|
# `chain` — S17, ADR 0026 — every entity the dotted verb passes
|
|
65
63
|
# through, root-first (`[Handler, Dispatch]` for `Handler.Dispatch.
|
|
66
|
-
# Bind`) ; `entity`/`entity_name` stay the
|
|
64
|
+
# Bind`) ; `entity`/`entity_name` stay the chain's own last entry,
|
|
67
65
|
# the one a command actually belongs to and a mutation actually
|
|
68
66
|
# targets, so every step written before this ADR (enforce_givens,
|
|
69
67
|
# apply_mutations, advance_lifecycle, element_identity, ...) reads
|
|
@@ -75,7 +73,51 @@ module Hecks
|
|
|
75
73
|
# rubocop:disable-next Lint/StructNewOverride
|
|
76
74
|
Context = Struct.new(:domain, :aggregate, :entity, :entity_name, :command, :command_name,
|
|
77
75
|
:args, :repository, :instance, :chain, :element, :view, :transition,
|
|
78
|
-
:old_element, :result, :route, :plan, :persistence_outcome, :dry_run, :outbox_rows
|
|
76
|
+
:old_element, :result, :route, :plan, :persistence_outcome, :dry_run, :outbox_rows,
|
|
77
|
+
:correction_bindings, :invocation)
|
|
78
|
+
|
|
79
|
+
# A dotted entity verb resolved against its aggregate. `Resolution.of`
|
|
80
|
+
# lives here rather than as a second public verb on the interpreter
|
|
81
|
+
# (spec/runtime/command_rules_spec.rb holds each interpreter to one),
|
|
82
|
+
# so `Dispatcher` can resolve it at the point `Invocation.from_call`
|
|
83
|
+
# reads the declaring command — after `to:` is parsed, before the
|
|
84
|
+
# facts are.
|
|
85
|
+
Resolution = Data.define(:entity_names, :chain, :command_name, :command) do
|
|
86
|
+
# Refuses UnknownVerb for an unknown entity or command.
|
|
87
|
+
def self.of(aggregate, dotted)
|
|
88
|
+
*entity_names, command_name = dotted.to_s.split(".")
|
|
89
|
+
if entity_names.empty?
|
|
90
|
+
raise UnknownVerb, RefusalWording.render_site("UnknownVerb", "entity_unknown",
|
|
91
|
+
aggregate: aggregate.hecks_name, entity: dotted.to_s)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
chain = walk(aggregate, entity_names)
|
|
95
|
+
command = chain.last.command(command_name) ||
|
|
96
|
+
raise(UnknownVerb, RefusalWording.render_site("UnknownVerb", "entity_no_command",
|
|
97
|
+
entity: chain.last.hecks_name, command: command_name))
|
|
98
|
+
new(entity_names: entity_names, chain: chain, command_name: command_name, command: command)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# **One hop per dotted segment** — `ProcessManager.Handler.Dispatch.Bind`
|
|
102
|
+
# (once the dispatcher has already stripped "Domain::Aggregate.")
|
|
103
|
+
# walks Handler off the aggregate, then Dispatch off Handler, each
|
|
104
|
+
# step reading `.entities` exactly the way the single-level case
|
|
105
|
+
# always did — a nested entity is "structurally interchangeable
|
|
106
|
+
# with an aggregate" (Entity's own header) for precisely this
|
|
107
|
+
# reason. Two levels is what Handler/Dispatch need today ; nothing
|
|
108
|
+
# here assumes it stops at two.
|
|
109
|
+
def self.walk(aggregate, entity_names)
|
|
110
|
+
owner = aggregate
|
|
111
|
+
entity_names.map do |name|
|
|
112
|
+
found = owner.entities.find { |piece| piece.hecks_name == name } ||
|
|
113
|
+
raise(UnknownVerb, RefusalWording.render_site("UnknownVerb", "entity_unknown",
|
|
114
|
+
aggregate: owner.hecks_name, entity: name))
|
|
115
|
+
owner = found
|
|
116
|
+
found
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
private_class_method :walk
|
|
120
|
+
end
|
|
79
121
|
|
|
80
122
|
def initialize(registry, rules:)
|
|
81
123
|
@registry = registry
|
|
@@ -86,39 +128,37 @@ module Hecks
|
|
|
86
128
|
# comment for the shared reasoning (Dispatcher#dry_run?'s own entry
|
|
87
129
|
# point). `step_save`/`step_emit` are the only two steps here that
|
|
88
130
|
# read it either.
|
|
89
|
-
#
|
|
131
|
+
# Retries the whole method body on `StaleWrite` — same reasoning as
|
|
90
132
|
# `CommandInterpreter#call`'s own retry: a fresh `ctx`, a fresh
|
|
91
133
|
# `step_hydrate_parent`/`step_locate_element` re-reading current
|
|
92
134
|
# state.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
entity: entity.hecks_name, command: command_name.inspect))
|
|
105
|
-
|
|
106
|
-
args = Routing.payload(command, with: with, legacy: legacy_args)
|
|
135
|
+
#
|
|
136
|
+
# `resolution` is #resolve's answer; `invocation` the
|
|
137
|
+
# `Runtime::Invocation` `Dispatcher` built — `ctx.args` is its
|
|
138
|
+
# `to_args`, `ctx.route` its `target`.
|
|
139
|
+
def call(domain, aggregate, resolution, invocation, dry_run: false)
|
|
140
|
+
chain = resolution.chain
|
|
141
|
+
entity = chain.last
|
|
142
|
+
command = resolution.command
|
|
143
|
+
command_name = resolution.command_name
|
|
144
|
+
route = invocation.target
|
|
145
|
+
args = invocation.to_args
|
|
107
146
|
attempt = 0
|
|
108
147
|
begin
|
|
109
|
-
ctx = Context.new(domain, aggregate, entity, entity_names.join("."), command, command_name, args)
|
|
148
|
+
ctx = Context.new(domain, aggregate, entity, resolution.entity_names.join("."), command, command_name, args)
|
|
149
|
+
ctx.invocation = invocation
|
|
110
150
|
ctx.chain = chain
|
|
111
151
|
ctx.route = route
|
|
112
152
|
ctx.dry_run = dry_run
|
|
113
153
|
# `root_aggregate:` — `entity` is the immediate owner (what
|
|
114
154
|
# `owner_fields` inside the Analyzer means), but a `parent.X`
|
|
115
|
-
# read inside this command's own given/ensures means the
|
|
116
|
-
# aggregate's own field, not the entity's — `aggregate` here
|
|
155
|
+
# read inside this command's own given/ensures means the root
|
|
156
|
+
# aggregate's own field, not the entity's — `aggregate` here is
|
|
117
157
|
# that root (this method's own first parameter, never the
|
|
118
158
|
# entity). See DependencyPlanning::Analyzer.call's own header
|
|
119
159
|
# for the bug this closes.
|
|
120
160
|
ctx.plan = DependencyPlanning::Analyzer.call(aggregate: entity, command: command, root_aggregate: aggregate)
|
|
121
|
-
#
|
|
161
|
+
# **Resolved here, once** — see CommandInterpreter#call's own comment;
|
|
122
162
|
# `step_hydrate_parent` reads `ctx.repository` without re-fetching.
|
|
123
163
|
ctx.repository = @registry.repository(domain, aggregate)
|
|
124
164
|
lock_id = Identity.best_effort(aggregate, args, route)
|
|
@@ -133,28 +173,13 @@ module Hecks
|
|
|
133
173
|
|
|
134
174
|
private
|
|
135
175
|
|
|
136
|
-
#
|
|
137
|
-
#
|
|
138
|
-
|
|
139
|
-
# step reading `.entities` exactly the way the single-level case
|
|
140
|
-
# always did — a nested entity is "structurally interchangeable
|
|
141
|
-
# with an aggregate" (Entity's own header) for precisely this
|
|
142
|
-
# reason. Two levels is what Handler/Dispatch need today ; nothing
|
|
143
|
-
# here assumes it stops at two.
|
|
144
|
-
def walk_entity_chain(aggregate, entity_names)
|
|
145
|
-
owner = aggregate
|
|
146
|
-
entity_names.map do |name|
|
|
147
|
-
found = owner.entities.find { |piece| piece.hecks_name == name } ||
|
|
148
|
-
raise(UnknownVerb, RefusalWording.render("UnknownVerb", "entity_unknown",
|
|
149
|
-
aggregate: owner.hecks_name, entity: name.inspect))
|
|
150
|
-
owner = found
|
|
151
|
-
found
|
|
152
|
-
end
|
|
153
|
-
end
|
|
176
|
+
# **A no-op, and untraced** — Vocabulary::EntityDispatchOrder's
|
|
177
|
+
# decode_arguments. See CommandInterpreter#step_decode_arguments.
|
|
178
|
+
def step_decode_arguments(_ctx); end
|
|
154
179
|
|
|
155
180
|
# `extra_identity_heads:` — every entity `ctx.chain` walks through, not
|
|
156
181
|
# just the root aggregate `ArgumentGate` already knows about. A
|
|
157
|
-
# two-hop dispatch (`Handler.Dispatch.Bind`) is addressed by
|
|
182
|
+
# two-hop dispatch (`Handler.Dispatch.Bind`) is addressed by both
|
|
158
183
|
# hops' own identity, each read straight out of `args` by
|
|
159
184
|
# `EntityElement#element_of` — refusing those as unknown would refuse
|
|
160
185
|
# every legitimate nested-entity dispatch there is, the same reasoning
|
|
@@ -201,16 +226,73 @@ module Hecks
|
|
|
201
226
|
ctx.element = step(:locate_element) do
|
|
202
227
|
EntityElement.locate_chain(ctx.aggregate, ctx.chain, ctx.instance, ctx.args, ctx.command_name, ctx.route)
|
|
203
228
|
end
|
|
204
|
-
# `view` was hydrated
|
|
229
|
+
# `view` was hydrated once, here, into its own state hash
|
|
205
230
|
# (Value.hydrate builds a fresh Hash — never aliased with `element`)
|
|
206
231
|
# — exactly right for enforce_givens, which must read pre-mutation.
|
|
207
232
|
ctx.view = Instance.new(aggregate: ctx.entity, id: EntityElement.element_identity(ctx.entity, ctx.element).to_s,
|
|
208
233
|
state: ctx.element)
|
|
209
234
|
end
|
|
210
235
|
|
|
236
|
+
# BUG#30 — the entity-level half of `CommandInterpreter#step_enforce_
|
|
237
|
+
# givens`'s own structural-before-declared ordering (see that
|
|
238
|
+
# method's comment for the shared reasoning): "does the fact this
|
|
239
|
+
# command's `corrects` names even exist" is checked here too, once,
|
|
240
|
+
# before the entity's own `given`s.
|
|
241
|
+
#
|
|
242
|
+
# **Admissibility is checked against the parent/root, not the entity** —
|
|
243
|
+
# deliberately `ctx.instance`/`ctx.aggregate` (the parent aggregate
|
|
244
|
+
# record and the root aggregate construct), never `ctx.view`/
|
|
245
|
+
# `ctx.entity` (the entity's own pre-mutation view/construct). This
|
|
246
|
+
# is not a simplification; it is the only choice that lines up with
|
|
247
|
+
# how the event being corrected was actually recorded: an entity has
|
|
248
|
+
# no event stream of its own — `CommandRules::Emission#emit` (called
|
|
249
|
+
# from this class's own `step_emit`, and from `CommandInterpreter`'s
|
|
250
|
+
# `step_emit` for an aggregate-level command alike) always stamps an
|
|
251
|
+
# emitted event with the root aggregate's own qualified name
|
|
252
|
+
# (`"#{domain}::#{aggregate.hecks_name}"`) and the parent record's
|
|
253
|
+
# own id (`ctx.instance.id`), regardless of which level dispatched
|
|
254
|
+
# it. `enforce_correction_target` (CommandRules::Admissibility)
|
|
255
|
+
# looks a correction target up by exactly those two fields plus the
|
|
256
|
+
# event name — asking it in terms of the entity instead would search
|
|
257
|
+
# for an event key/id that no emitted event could ever actually
|
|
258
|
+
# carry, and every entity-level correction would refuse
|
|
259
|
+
# (NothingToCorrect) even against a real, already-emitted event.
|
|
260
|
+
# `qa/stress_domains/corrections`' own `Entry.Amend` (corrects
|
|
261
|
+
# "EntryRecorded", which `Ledger.Record` — an aggregate-level
|
|
262
|
+
# command — actually emits) is exactly this shape: the corrected
|
|
263
|
+
# event's `aggregate`/`id` are the ledger's, never the Entry's own
|
|
264
|
+
# (an Entry has no id an event could be filed under in the first
|
|
265
|
+
# place). `Fuzzing::Properties::Corrections#corrections_reference_
|
|
266
|
+
# an_emitted_event` independently encodes the identical rule
|
|
267
|
+
# (`aggregate_key` built off the outer aggregate for both the
|
|
268
|
+
# `corrects` target and the `emits` produced event, regardless of
|
|
269
|
+
# entity nesting depth) — this is that property's dispatch-time
|
|
270
|
+
# enforcement counterpart, not a new invention.
|
|
271
|
+
#
|
|
272
|
+
# One structural consequence, worth being explicit about for a
|
|
273
|
+
# Rust port: because the lookup is scoped to the parent record
|
|
274
|
+
# (not to any one entity element within it), an entity-level
|
|
275
|
+
# `corrects` only proves "this parent record has emitted the named
|
|
276
|
+
# event at some point" — it does not, and cannot, further narrow
|
|
277
|
+
# to "...specifically for THIS entity element" (a Ledger with three
|
|
278
|
+
# Entries all satisfy the same `EntryRecorded`-was-emitted check).
|
|
279
|
+
# That is not a gap this fix introduces: it is the same granularity
|
|
280
|
+
# the aggregate-level check already has (one record, one event
|
|
281
|
+
# history), just observed from one level down. A command wanting a
|
|
282
|
+
# tighter, element-specific correlation has to encode it itself, in
|
|
283
|
+
# its own `given`s, off `correction`-bound payload fields.
|
|
284
|
+
#
|
|
285
|
+
# `correction:` bindings computed here are threaded through to both
|
|
286
|
+
# halves of the same command's admissibility, same as the
|
|
287
|
+
# aggregate-level path: `ctx.correction_bindings` is read again by
|
|
288
|
+
# `step_enforce_ensures`, below, so an `as:`-named binding is
|
|
289
|
+
# visible to a settled-record `ensures` exactly as freely as it is
|
|
290
|
+
# here, pre-mutation.
|
|
211
291
|
def step_enforce_givens(ctx)
|
|
212
292
|
step(:enforce_givens) do
|
|
213
|
-
@rules.
|
|
293
|
+
ctx.correction_bindings = @rules.enforce_correction_target(ctx.instance, ctx.aggregate, ctx.command, domain: ctx.domain)
|
|
294
|
+
@rules.enforce_givens(ctx.view, ctx.command, ctx.args, domain: ctx.domain, declaring: ctx.entity, parent: ctx.instance,
|
|
295
|
+
correction: ctx.correction_bindings)
|
|
214
296
|
end
|
|
215
297
|
end
|
|
216
298
|
|
|
@@ -234,19 +316,26 @@ module Hecks
|
|
|
234
316
|
step(:advance_lifecycle) { ctx.element[ctx.entity.lifecycle.field] = ctx.transition.target }
|
|
235
317
|
end
|
|
236
318
|
|
|
237
|
-
# An ensures reads the
|
|
319
|
+
# An ensures reads the settled record, so it needs a view hydrated from
|
|
238
320
|
# `element` as it stands now, mutations included — unlike `view` above,
|
|
239
321
|
# built once and read pre-mutation by enforce_givens.
|
|
240
322
|
def step_enforce_ensures(ctx)
|
|
241
323
|
step(:enforce_ensures) do
|
|
242
324
|
settled = Instance.new(aggregate: ctx.entity, id: ctx.view.id, state: ctx.element)
|
|
243
|
-
|
|
325
|
+
# `correction:` — same `as:`-bound corrected-event payload
|
|
326
|
+
# `step_enforce_givens` already located, above; `|| {}` covers
|
|
327
|
+
# a command with no `corrects` mutation at all, where
|
|
328
|
+
# `ctx.correction_bindings` is `{}` from that call already, or
|
|
329
|
+
# (belt-and-braces, matching `CommandInterpreter#step_enforce_
|
|
330
|
+
# ensures`'s own identical `|| {}`) never set.
|
|
331
|
+
@rules.enforce_ensures(settled, ctx.command, ctx.args, old: ctx.old_element, domain: ctx.domain, parent: ctx.instance,
|
|
332
|
+
correction: ctx.correction_bindings || {})
|
|
244
333
|
end
|
|
245
334
|
end
|
|
246
335
|
|
|
247
|
-
#
|
|
336
|
+
# The parent aggregate's own invariants — `ctx.instance` is the
|
|
248
337
|
# parent record an entity mutation writes into (this file's own
|
|
249
|
-
# `Context` comment), the
|
|
338
|
+
# `Context` comment), the same boundary an aggregate-level
|
|
250
339
|
# invariant guards regardless of which interpreter changed it. No
|
|
251
340
|
# separate "entity invariant" exists (S10, ADR 0025 scopes
|
|
252
341
|
# `invariant` to the aggregate only) — see `Admissibility#
|
|
@@ -255,21 +344,23 @@ module Hecks
|
|
|
255
344
|
step(:enforce_invariants) { @rules.enforce_invariants(ctx.instance, ctx.aggregate, domain: ctx.domain) }
|
|
256
345
|
end
|
|
257
346
|
|
|
258
|
-
# `dry_run:` skips this — see
|
|
259
|
-
#
|
|
260
|
-
#
|
|
347
|
+
# `dry_run:` skips the write half of this step — see
|
|
348
|
+
# CommandInterpreter#step_save's own comment (BUG#127): the
|
|
349
|
+
# reference-existence check stays unconditional, only the actual
|
|
350
|
+
# persist is behind the early return.
|
|
261
351
|
def step_save(ctx)
|
|
352
|
+
step(:save) { @rules.resolve_state_references(ctx.domain, ctx.aggregate, ctx.instance.state) }
|
|
353
|
+
|
|
262
354
|
return if ctx.dry_run
|
|
263
355
|
|
|
264
356
|
step(:save) do
|
|
265
|
-
@rules.resolve_state_references(ctx.domain, ctx.aggregate, ctx.instance.state)
|
|
266
357
|
# `expected_version:` — see CommandInterpreter#step_save's own
|
|
267
358
|
# comment: nil for a repository that isn't CAS-capable, or an
|
|
268
359
|
# instance never read from storage, either of which falls
|
|
269
360
|
# through to a plain save inside `AppendOnly#save`.
|
|
270
361
|
ctx.persistence_outcome = ctx.repository.save(ctx.instance, expected_version: ctx.instance.version)
|
|
271
362
|
if ctx.persistence_outcome.status == :stale
|
|
272
|
-
#
|
|
363
|
+
# Not a `RefusalWording.render` call — see
|
|
273
364
|
# `CommandInterpreter#step_save`'s identical branch and
|
|
274
365
|
# `Runtime::StaleWrite`'s own comment.
|
|
275
366
|
raise(StaleWrite,
|
|
@@ -288,7 +379,7 @@ module Hecks
|
|
|
288
379
|
ctx.result = step(:emit) { @rules.emit(ctx.command, ctx.domain, ctx.aggregate, ctx.instance, ctx.args, ctx.repository) }
|
|
289
380
|
end
|
|
290
381
|
|
|
291
|
-
#
|
|
382
|
+
# The parent aggregate, addressed exactly as `CommandInterpreter#hydrate`
|
|
292
383
|
# addresses one acting on itself — derive from the declared identity first
|
|
293
384
|
# (`Identity.of`), and let a bare `id:` name an already-derived record when
|
|
294
385
|
# the identity itself is not what the caller is holding.
|
|
@@ -296,14 +387,14 @@ module Hecks
|
|
|
296
387
|
parent_id = route&.aggregate ||
|
|
297
388
|
Identity.of(aggregate, args) ||
|
|
298
389
|
Identity.from(aggregate, args, :id) ||
|
|
299
|
-
raise(NotFound, RefusalWording.
|
|
300
|
-
|
|
301
|
-
|
|
390
|
+
raise(NotFound, RefusalWording.render_site("NotFound", "entity_parent_no_identity",
|
|
391
|
+
command: command_name, aggregate: aggregate.hecks_name,
|
|
392
|
+
entity: entity_name, identity: Identity.reading(aggregate)))
|
|
302
393
|
found = repository.find(parent_id) ||
|
|
303
|
-
raise(NotFound, RefusalWording.
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
394
|
+
raise(NotFound, RefusalWording.render_site("NotFound", "record_missing",
|
|
395
|
+
aggregate: aggregate.hecks_name,
|
|
396
|
+
identity: Identity.reading(aggregate),
|
|
397
|
+
offered: Rendering.describe(parent_id)))
|
|
307
398
|
found.dup
|
|
308
399
|
end
|
|
309
400
|
|
|
@@ -313,7 +404,7 @@ module Hecks
|
|
|
313
404
|
# `delegate_to_entity` step can locate and mutate the same element the
|
|
314
405
|
# same way, against an aggregate record already held in memory. `call`,
|
|
315
406
|
# above, and every `step_*` method reach them through that module now;
|
|
316
|
-
# nothing about the
|
|
407
|
+
# nothing about the steps themselves changed.
|
|
317
408
|
end
|
|
318
409
|
end
|
|
319
410
|
end
|
data/lib/hecks/runtime/errors.rb
CHANGED
|
@@ -7,12 +7,12 @@ module Hecks
|
|
|
7
7
|
class EnsuresNotMet < StandardError; end
|
|
8
8
|
|
|
9
9
|
# `detail` — the failing comparison's own resolved operands, "left: X,
|
|
10
|
-
# right: Y" — set only when the given's
|
|
10
|
+
# right: Y" — set only when the given's top-level shape is a bare
|
|
11
11
|
# comparison (`Evaluator.comparison_detail`'s own comment has the full
|
|
12
|
-
# scoping); nil otherwise. Deliberately
|
|
12
|
+
# scoping); nil otherwise. Deliberately not folded into `#message`:
|
|
13
13
|
# that string is pinned byte-for-byte across this corpus's own specs
|
|
14
14
|
# (`raise_error(GivenNotMet, "...")`, command_rules_spec.rb and every
|
|
15
|
-
# domain that vendors this gem) as
|
|
15
|
+
# domain that vendors this gem) as contract, so changing its shape by
|
|
16
16
|
# default would be a breaking change for every one of them. Riding on
|
|
17
17
|
# `#detailed_message` instead (Ruby 3.2+, what irb/a Rails console's
|
|
18
18
|
# own unhandled-exception banner already calls to show more than
|
|
@@ -39,7 +39,7 @@ module Hecks
|
|
|
39
39
|
# is the right name carrying the wrong thing, this one is a name the command
|
|
40
40
|
# never had. Both are the payload gate refusing before any rule runs.
|
|
41
41
|
class UnknownArgument < StandardError; end
|
|
42
|
-
# The third of the trio, and the one that was missing : a name the command
|
|
42
|
+
# The third of the trio, and the one that was missing : a name the command does
|
|
43
43
|
# declare, absent. TypeMismatch is the right name carrying the wrong thing,
|
|
44
44
|
# UnknownArgument a name that was never declared, AbsentArgument a declared name
|
|
45
45
|
# that never arrived. Between them they say a command takes exactly the
|
|
@@ -56,18 +56,18 @@ module Hecks
|
|
|
56
56
|
# `given`/`ensures`/`invariant` that reads the field would otherwise
|
|
57
57
|
# evaluate against a value nobody wrote, which is the same silent-
|
|
58
58
|
# wrong-answer class as an unpopulated projection reading "not
|
|
59
|
-
# active" (ADR 0025, "Added attributes and absence"). An
|
|
59
|
+
# active" (ADR 0025, "Added attributes and absence"). An optional
|
|
60
60
|
# attribute in the same spot reads nil instead — that is what
|
|
61
61
|
# optional means, and this refusal is deliberately narrower than the
|
|
62
62
|
# nil-read it sits beside, not a replacement for it.
|
|
63
63
|
class AttributeAbsent < StandardError; end
|
|
64
|
-
#
|
|
64
|
+
# The same silent-wrong-answer class as above, one line up — a
|
|
65
65
|
# `projects` field (S12, ADR 0025) this record predates, or that no
|
|
66
66
|
# rebuild sweep has populated yet, read by a `given`/`ensures`/
|
|
67
67
|
# `invariant` as though it carried a real value. `GuardState` is
|
|
68
68
|
# the one place this is raised, the same way AttributeAbsent is —
|
|
69
|
-
# a
|
|
70
|
-
# from that one only in
|
|
69
|
+
# a declared field the record does not yet carry, distinguished
|
|
70
|
+
# from that one only in why: an ordinary attribute is absent
|
|
71
71
|
# because nobody backfilled it, a projected field is absent
|
|
72
72
|
# because nobody has swept it yet.
|
|
73
73
|
class ProjectionAbsent < StandardError; end
|
|
@@ -79,14 +79,14 @@ module Hecks
|
|
|
79
79
|
class Unauthorized < StandardError; end
|
|
80
80
|
# `corrects` names a past event this record must have already emitted
|
|
81
81
|
# (CommandBuilder#corrects_impl's own comment) — a fact the expression
|
|
82
|
-
# evaluator cannot check (it is not a predicate over the record's
|
|
82
|
+
# evaluator cannot check (it is not a predicate over the record's own
|
|
83
83
|
# fields, it is "did this exact record ever announce this"), so it is
|
|
84
84
|
# raised structurally, the same way AlreadyExists/NotFound are, rather
|
|
85
85
|
# than being expressible as an ordinary `given`. Raised by
|
|
86
86
|
# `CommandRules::Admissibility#enforce_correction_target`.
|
|
87
87
|
class NothingToCorrect < StandardError; end
|
|
88
88
|
|
|
89
|
-
# A
|
|
89
|
+
# A runtime fault, not a domain refusal — deliberately absent from
|
|
90
90
|
# `DOMAIN_REFUSALS` below and from `vocabulary.bluebook`'s own
|
|
91
91
|
# `DomainRefusal` list. Raised when an optimistic-concurrency CAS write
|
|
92
92
|
# (`AppendOnly#save`'s `expected_version:`) finds the stored version has
|
|
@@ -102,29 +102,29 @@ module Hecks
|
|
|
102
102
|
|
|
103
103
|
# A Lambda-routed domain's own refusal (rust/host, `Runtime::
|
|
104
104
|
# RemoteDispatcher`), carrying Rust's own refusal text verbatim —
|
|
105
|
-
#
|
|
105
|
+
# not yet mapped back to the specific matching class above
|
|
106
106
|
# (GivenNotMet vs. EnsuresNotMet vs. ...), a real, known,
|
|
107
107
|
# documented gap: the WASM projector's own event/refusal-wording
|
|
108
|
-
# parity work (ADR 0021) makes the
|
|
108
|
+
# parity work (ADR 0021) makes the text match Ruby's, but nothing
|
|
109
109
|
# yet parses that text back into a typed Ruby exception the way a
|
|
110
110
|
# local dispatch already raises one directly. Callers that only
|
|
111
111
|
# need "the domain said no" (not which specific rule) are
|
|
112
|
-
# unaffected; callers pattern-matching a
|
|
112
|
+
# unaffected; callers pattern-matching a specific refusal class
|
|
113
113
|
# against a Lambda-routed domain are the ones this gap would bite.
|
|
114
114
|
class RemoteRefusal < StandardError; end
|
|
115
115
|
|
|
116
|
-
# The domain saying
|
|
116
|
+
# The domain saying no — the errors a reaction may legitimately meet and
|
|
117
117
|
# record as an undelivered outcome. A policy whose target refuses is a fact
|
|
118
118
|
# about the domain ; the originating command still stands.
|
|
119
119
|
#
|
|
120
|
-
# Everything
|
|
120
|
+
# Everything else is a defect : a NoMethodError in an interpreter, a
|
|
121
121
|
# NameError from a missing constant, a TypeError from a bad assumption. A
|
|
122
122
|
# blanket `rescue StandardError` used to fold both into one line —
|
|
123
123
|
# `delivered: false, reason: "..."` — so a crash in the runtime was
|
|
124
124
|
# indistinguishable from a rule doing its job, and read as normal operation
|
|
125
125
|
# in the log.
|
|
126
126
|
#
|
|
127
|
-
# UnknownVerb
|
|
127
|
+
# UnknownVerb is one of these, and deliberately : a cross-domain policy
|
|
128
128
|
# (`across "Notifications"`) fires in deployments where that domain is not
|
|
129
129
|
# loaded, and recording the undelivered reaction rather than raising is the
|
|
130
130
|
# design — spec/policy_spec states it in so many words, "records a reaction
|
|
@@ -133,12 +133,12 @@ module Hecks
|
|
|
133
133
|
# its own rule is the domain saying no as plainly as a given is — but the
|
|
134
134
|
# class is declared over in value.rb and never made the list, so the policy
|
|
135
135
|
# and saga interpreters, which rescue exactly these, would let it propagate
|
|
136
|
-
# as though the
|
|
136
|
+
# as though the runtime had broken. A reaction whose target violates an
|
|
137
137
|
# invariant is declined, not crashed. Found by spec/domain_refusal_spec on
|
|
138
138
|
# its first run : every corpus refusal must be a class named here, and 23
|
|
139
139
|
# of banking's were InvariantViolation.
|
|
140
|
-
#
|
|
141
|
-
# `DomainRefusal` declares
|
|
140
|
+
# The names come from the language, the classes from this module.
|
|
141
|
+
# `DomainRefusal` declares which refusals are the domain's own —
|
|
142
142
|
# a rule the caller broke — as against a runtime fault. Resolving
|
|
143
143
|
# each name here means a refusal declared but never defined fails
|
|
144
144
|
# at load with a NameError, rather than being quietly absent from
|
data/lib/hecks/runtime/event.rb
CHANGED
|
@@ -2,7 +2,7 @@ require "time"
|
|
|
2
2
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Runtime
|
|
5
|
-
# `correlation` is
|
|
5
|
+
# `correlation` is not on the wire — `to_h` below deliberately omits it,
|
|
6
6
|
# the same as `bin/run`'s own event projection does. It is runtime
|
|
7
7
|
# bookkeeping stamped by `Dispatcher#dispatch` when a saga leg's own
|
|
8
8
|
# dispatch causes this event (see `SagaInterpreter#deliver_saga_dispatch`
|
|
@@ -11,19 +11,19 @@ module Hecks
|
|
|
11
11
|
# one correlating on a different field. Absent for any event no saga
|
|
12
12
|
# dispatch caused, which is most of them.
|
|
13
13
|
Event = Struct.new(:name, :aggregate, :id, :payload, :occurred_at, :correlation, keyword_init: true) do
|
|
14
|
-
#
|
|
15
|
-
# mutable audit trail is not one. The
|
|
16
|
-
# event carries — is frozen
|
|
14
|
+
# An emitted event is a record of something that happened, and a
|
|
15
|
+
# mutable audit trail is not one. The payload — the domain fact the
|
|
16
|
+
# event carries — is frozen through on emission: freezing the Hash
|
|
17
17
|
# alone would leave every value in it editable in place, which is
|
|
18
18
|
# the shape all four previous freezing bugs had.
|
|
19
19
|
#
|
|
20
|
-
#
|
|
20
|
+
# The whole event, not just its payload. Correlation used to be
|
|
21
21
|
# merged onto already-emitted events by `Dispatcher#dispatch`, which
|
|
22
22
|
# is what kept an event writable after it had happened; it is set at
|
|
23
23
|
# construction now, because it is part of the transaction and known
|
|
24
24
|
# from `dispatch`'s own argument before anything is emitted.
|
|
25
25
|
#
|
|
26
|
-
# The
|
|
26
|
+
# The log stays appendable: new events are still recorded. It is
|
|
27
27
|
# each event that stops changing once it exists.
|
|
28
28
|
def emit!
|
|
29
29
|
Freezer.deep(payload)
|