hecks 1.3.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 +5 -4
- 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/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 +18 -18
- data/lib/hecks/behaviors/dsl.rb +2 -2
- data/lib/hecks/behaviors/expectations.rb +23 -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 +7 -7
- 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 +101 -87
- 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 +170 -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 +180 -19
- 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 +140 -45
- 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 +37 -18
- data/lib/hecks/fuzzing/form_census.rb +35 -20
- 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 +62 -62
- data/lib/hecks/fuzzing/nondeterministic.rb +67 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +19 -21
- data/lib/hecks/fuzzing/properties/corrections.rb +9 -9
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +154 -49
- data/lib/hecks/fuzzing/properties/guards.rb +42 -42
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +32 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +22 -35
- data/lib/hecks/fuzzing/properties/outbox.rb +24 -24
- data/lib/hecks/fuzzing/properties/querying.rb +17 -17
- data/lib/hecks/fuzzing/properties.rb +64 -40
- data/lib/hecks/fuzzing/qa_settings.rb +152 -0
- data/lib/hecks/fuzzing/replay.rb +114 -114
- data/lib/hecks/fuzzing/rotation_priority.rb +10 -10
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +113 -0
- data/lib/hecks/fuzzing/self_consistency.rb +141 -112
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +35 -35
- 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 +58 -31
- data/lib/hecks/fuzzing/sequence_generator.rb +111 -23
- data/lib/hecks/fuzzing/shrinker.rb +197 -0
- data/lib/hecks/fuzzing/structural_skips.rb +23 -130
- data/lib/hecks/fuzzing/sweep_depth.rb +4 -4
- data/lib/hecks/fuzzing/target_capabilities.rb +49 -18
- data/lib/hecks/fuzzing/value_generator.rb +14 -14
- data/lib/hecks/fuzzing.rb +5 -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 +348 -13
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/literal.rb +9 -9
- data/lib/hecks/naming.rb +25 -25
- 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 +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 +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 +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 +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 +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 +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 +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 +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 +3 -3
- data/lib/hecks/projections/glossary/markdown.rb +3 -3
- data/lib/hecks/projections/glossary/mermaid.rb +1 -1
- data/lib/hecks/projections/glossary/sections.rb +1 -1
- data/lib/hecks/projections/glossary/sentences.rb +5 -5
- data/lib/hecks/projections/glossary.rb +15 -15
- 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 +11 -11
- 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 +109 -27
- 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 +42 -63
- 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 +35 -35
- 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 +151 -67
- data/lib/hecks/runtime/entity_interpreter.rb +108 -82
- 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 +55 -64
- data/lib/hecks/runtime/reaction_invocation.rb +20 -20
- 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 -115
- 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 +45 -28
- data/lib/hecks/runtime/remote_dispatcher.rb +38 -23
- data/lib/hecks/runtime/routing.rb +10 -153
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +17 -17
- data/lib/hecks/runtime/saga_interpreter.rb +64 -64
- 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 +318 -127
- data/lib/hecks/runtime/value/entity_list_coercion.rb +34 -34
- data/lib/hecks/runtime/value.rb +21 -21
- 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 +205 -4
- 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 +22 -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
|
|
@@ -76,7 +74,50 @@ module Hecks
|
|
|
76
74
|
Context = Struct.new(:domain, :aggregate, :entity, :entity_name, :command, :command_name,
|
|
77
75
|
:args, :repository, :instance, :chain, :element, :view, :transition,
|
|
78
76
|
:old_element, :result, :route, :plan, :persistence_outcome, :dry_run, :outbox_rows,
|
|
79
|
-
:correction_bindings)
|
|
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
|
|
80
121
|
|
|
81
122
|
def initialize(registry, rules:)
|
|
82
123
|
@registry = registry
|
|
@@ -87,39 +128,37 @@ module Hecks
|
|
|
87
128
|
# comment for the shared reasoning (Dispatcher#dry_run?'s own entry
|
|
88
129
|
# point). `step_save`/`step_emit` are the only two steps here that
|
|
89
130
|
# read it either.
|
|
90
|
-
#
|
|
131
|
+
# Retries the whole method body on `StaleWrite` — same reasoning as
|
|
91
132
|
# `CommandInterpreter#call`'s own retry: a fresh `ctx`, a fresh
|
|
92
133
|
# `step_hydrate_parent`/`step_locate_element` re-reading current
|
|
93
134
|
# state.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
entity: entity.hecks_name, command: command_name.inspect))
|
|
106
|
-
|
|
107
|
-
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
|
|
108
146
|
attempt = 0
|
|
109
147
|
begin
|
|
110
|
-
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
|
|
111
150
|
ctx.chain = chain
|
|
112
151
|
ctx.route = route
|
|
113
152
|
ctx.dry_run = dry_run
|
|
114
153
|
# `root_aggregate:` — `entity` is the immediate owner (what
|
|
115
154
|
# `owner_fields` inside the Analyzer means), but a `parent.X`
|
|
116
|
-
# read inside this command's own given/ensures means the
|
|
117
|
-
# 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
|
|
118
157
|
# that root (this method's own first parameter, never the
|
|
119
158
|
# entity). See DependencyPlanning::Analyzer.call's own header
|
|
120
159
|
# for the bug this closes.
|
|
121
160
|
ctx.plan = DependencyPlanning::Analyzer.call(aggregate: entity, command: command, root_aggregate: aggregate)
|
|
122
|
-
#
|
|
161
|
+
# **Resolved here, once** — see CommandInterpreter#call's own comment;
|
|
123
162
|
# `step_hydrate_parent` reads `ctx.repository` without re-fetching.
|
|
124
163
|
ctx.repository = @registry.repository(domain, aggregate)
|
|
125
164
|
lock_id = Identity.best_effort(aggregate, args, route)
|
|
@@ -134,28 +173,13 @@ module Hecks
|
|
|
134
173
|
|
|
135
174
|
private
|
|
136
175
|
|
|
137
|
-
#
|
|
138
|
-
#
|
|
139
|
-
|
|
140
|
-
# step reading `.entities` exactly the way the single-level case
|
|
141
|
-
# always did — a nested entity is "structurally interchangeable
|
|
142
|
-
# with an aggregate" (Entity's own header) for precisely this
|
|
143
|
-
# reason. Two levels is what Handler/Dispatch need today ; nothing
|
|
144
|
-
# here assumes it stops at two.
|
|
145
|
-
def walk_entity_chain(aggregate, entity_names)
|
|
146
|
-
owner = aggregate
|
|
147
|
-
entity_names.map do |name|
|
|
148
|
-
found = owner.entities.find { |piece| piece.hecks_name == name } ||
|
|
149
|
-
raise(UnknownVerb, RefusalWording.render("UnknownVerb", "entity_unknown",
|
|
150
|
-
aggregate: owner.hecks_name, entity: name.inspect))
|
|
151
|
-
owner = found
|
|
152
|
-
found
|
|
153
|
-
end
|
|
154
|
-
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
|
|
155
179
|
|
|
156
180
|
# `extra_identity_heads:` — every entity `ctx.chain` walks through, not
|
|
157
181
|
# just the root aggregate `ArgumentGate` already knows about. A
|
|
158
|
-
# two-hop dispatch (`Handler.Dispatch.Bind`) is addressed by
|
|
182
|
+
# two-hop dispatch (`Handler.Dispatch.Bind`) is addressed by both
|
|
159
183
|
# hops' own identity, each read straight out of `args` by
|
|
160
184
|
# `EntityElement#element_of` — refusing those as unknown would refuse
|
|
161
185
|
# every legitimate nested-entity dispatch there is, the same reasoning
|
|
@@ -202,30 +226,30 @@ module Hecks
|
|
|
202
226
|
ctx.element = step(:locate_element) do
|
|
203
227
|
EntityElement.locate_chain(ctx.aggregate, ctx.chain, ctx.instance, ctx.args, ctx.command_name, ctx.route)
|
|
204
228
|
end
|
|
205
|
-
# `view` was hydrated
|
|
229
|
+
# `view` was hydrated once, here, into its own state hash
|
|
206
230
|
# (Value.hydrate builds a fresh Hash — never aliased with `element`)
|
|
207
231
|
# — exactly right for enforce_givens, which must read pre-mutation.
|
|
208
232
|
ctx.view = Instance.new(aggregate: ctx.entity, id: EntityElement.element_identity(ctx.entity, ctx.element).to_s,
|
|
209
233
|
state: ctx.element)
|
|
210
234
|
end
|
|
211
235
|
|
|
212
|
-
# BUG#30 —
|
|
236
|
+
# BUG#30 — the entity-level half of `CommandInterpreter#step_enforce_
|
|
213
237
|
# givens`'s own structural-before-declared ordering (see that
|
|
214
238
|
# method's comment for the shared reasoning): "does the fact this
|
|
215
239
|
# command's `corrects` names even exist" is checked here too, once,
|
|
216
240
|
# before the entity's own `given`s.
|
|
217
241
|
#
|
|
218
|
-
#
|
|
219
|
-
# deliberately `ctx.instance`/`ctx.aggregate` (the
|
|
220
|
-
# record and the
|
|
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`/
|
|
221
245
|
# `ctx.entity` (the entity's own pre-mutation view/construct). This
|
|
222
|
-
# is not a simplification; it is the
|
|
246
|
+
# is not a simplification; it is the only choice that lines up with
|
|
223
247
|
# how the event being corrected was actually recorded: an entity has
|
|
224
248
|
# no event stream of its own — `CommandRules::Emission#emit` (called
|
|
225
|
-
# from
|
|
249
|
+
# from this class's own `step_emit`, and from `CommandInterpreter`'s
|
|
226
250
|
# `step_emit` for an aggregate-level command alike) always stamps an
|
|
227
|
-
# emitted event with the
|
|
228
|
-
# (`"#{domain}::#{aggregate.hecks_name}"`) and the
|
|
251
|
+
# emitted event with the root aggregate's own qualified name
|
|
252
|
+
# (`"#{domain}::#{aggregate.hecks_name}"`) and the parent record's
|
|
229
253
|
# own id (`ctx.instance.id`), regardless of which level dispatched
|
|
230
254
|
# it. `enforce_correction_target` (CommandRules::Admissibility)
|
|
231
255
|
# looks a correction target up by exactly those two fields plus the
|
|
@@ -234,31 +258,31 @@ module Hecks
|
|
|
234
258
|
# carry, and every entity-level correction would refuse
|
|
235
259
|
# (NothingToCorrect) even against a real, already-emitted event.
|
|
236
260
|
# `qa/stress_domains/corrections`' own `Entry.Amend` (corrects
|
|
237
|
-
# "EntryRecorded", which `Ledger.Record` — an
|
|
261
|
+
# "EntryRecorded", which `Ledger.Record` — an aggregate-level
|
|
238
262
|
# command — actually emits) is exactly this shape: the corrected
|
|
239
|
-
# event's `aggregate`/`id` are the
|
|
263
|
+
# event's `aggregate`/`id` are the ledger's, never the Entry's own
|
|
240
264
|
# (an Entry has no id an event could be filed under in the first
|
|
241
265
|
# place). `Fuzzing::Properties::Corrections#corrections_reference_
|
|
242
266
|
# an_emitted_event` independently encodes the identical rule
|
|
243
|
-
# (`aggregate_key` built off the
|
|
267
|
+
# (`aggregate_key` built off the outer aggregate for both the
|
|
244
268
|
# `corrects` target and the `emits` produced event, regardless of
|
|
245
269
|
# entity nesting depth) — this is that property's dispatch-time
|
|
246
270
|
# enforcement counterpart, not a new invention.
|
|
247
271
|
#
|
|
248
272
|
# One structural consequence, worth being explicit about for a
|
|
249
|
-
# Rust port: because the lookup is scoped to the
|
|
273
|
+
# Rust port: because the lookup is scoped to the parent record
|
|
250
274
|
# (not to any one entity element within it), an entity-level
|
|
251
275
|
# `corrects` only proves "this parent record has emitted the named
|
|
252
|
-
# event at some point" — it does
|
|
276
|
+
# event at some point" — it does not, and cannot, further narrow
|
|
253
277
|
# to "...specifically for THIS entity element" (a Ledger with three
|
|
254
278
|
# Entries all satisfy the same `EntryRecorded`-was-emitted check).
|
|
255
|
-
# That is not a gap this fix introduces: it is the
|
|
279
|
+
# That is not a gap this fix introduces: it is the same granularity
|
|
256
280
|
# the aggregate-level check already has (one record, one event
|
|
257
281
|
# history), just observed from one level down. A command wanting a
|
|
258
282
|
# tighter, element-specific correlation has to encode it itself, in
|
|
259
283
|
# its own `given`s, off `correction`-bound payload fields.
|
|
260
284
|
#
|
|
261
|
-
# `correction:` bindings computed here are threaded through to
|
|
285
|
+
# `correction:` bindings computed here are threaded through to both
|
|
262
286
|
# halves of the same command's admissibility, same as the
|
|
263
287
|
# aggregate-level path: `ctx.correction_bindings` is read again by
|
|
264
288
|
# `step_enforce_ensures`, below, so an `as:`-named binding is
|
|
@@ -292,7 +316,7 @@ module Hecks
|
|
|
292
316
|
step(:advance_lifecycle) { ctx.element[ctx.entity.lifecycle.field] = ctx.transition.target }
|
|
293
317
|
end
|
|
294
318
|
|
|
295
|
-
# An ensures reads the
|
|
319
|
+
# An ensures reads the settled record, so it needs a view hydrated from
|
|
296
320
|
# `element` as it stands now, mutations included — unlike `view` above,
|
|
297
321
|
# built once and read pre-mutation by enforce_givens.
|
|
298
322
|
def step_enforce_ensures(ctx)
|
|
@@ -309,9 +333,9 @@ module Hecks
|
|
|
309
333
|
end
|
|
310
334
|
end
|
|
311
335
|
|
|
312
|
-
#
|
|
336
|
+
# The parent aggregate's own invariants — `ctx.instance` is the
|
|
313
337
|
# parent record an entity mutation writes into (this file's own
|
|
314
|
-
# `Context` comment), the
|
|
338
|
+
# `Context` comment), the same boundary an aggregate-level
|
|
315
339
|
# invariant guards regardless of which interpreter changed it. No
|
|
316
340
|
# separate "entity invariant" exists (S10, ADR 0025 scopes
|
|
317
341
|
# `invariant` to the aggregate only) — see `Admissibility#
|
|
@@ -320,21 +344,23 @@ module Hecks
|
|
|
320
344
|
step(:enforce_invariants) { @rules.enforce_invariants(ctx.instance, ctx.aggregate, domain: ctx.domain) }
|
|
321
345
|
end
|
|
322
346
|
|
|
323
|
-
# `dry_run:` skips this — see
|
|
324
|
-
#
|
|
325
|
-
#
|
|
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.
|
|
326
351
|
def step_save(ctx)
|
|
352
|
+
step(:save) { @rules.resolve_state_references(ctx.domain, ctx.aggregate, ctx.instance.state) }
|
|
353
|
+
|
|
327
354
|
return if ctx.dry_run
|
|
328
355
|
|
|
329
356
|
step(:save) do
|
|
330
|
-
@rules.resolve_state_references(ctx.domain, ctx.aggregate, ctx.instance.state)
|
|
331
357
|
# `expected_version:` — see CommandInterpreter#step_save's own
|
|
332
358
|
# comment: nil for a repository that isn't CAS-capable, or an
|
|
333
359
|
# instance never read from storage, either of which falls
|
|
334
360
|
# through to a plain save inside `AppendOnly#save`.
|
|
335
361
|
ctx.persistence_outcome = ctx.repository.save(ctx.instance, expected_version: ctx.instance.version)
|
|
336
362
|
if ctx.persistence_outcome.status == :stale
|
|
337
|
-
#
|
|
363
|
+
# Not a `RefusalWording.render` call — see
|
|
338
364
|
# `CommandInterpreter#step_save`'s identical branch and
|
|
339
365
|
# `Runtime::StaleWrite`'s own comment.
|
|
340
366
|
raise(StaleWrite,
|
|
@@ -353,7 +379,7 @@ module Hecks
|
|
|
353
379
|
ctx.result = step(:emit) { @rules.emit(ctx.command, ctx.domain, ctx.aggregate, ctx.instance, ctx.args, ctx.repository) }
|
|
354
380
|
end
|
|
355
381
|
|
|
356
|
-
#
|
|
382
|
+
# The parent aggregate, addressed exactly as `CommandInterpreter#hydrate`
|
|
357
383
|
# addresses one acting on itself — derive from the declared identity first
|
|
358
384
|
# (`Identity.of`), and let a bare `id:` name an already-derived record when
|
|
359
385
|
# the identity itself is not what the caller is holding.
|
|
@@ -361,14 +387,14 @@ module Hecks
|
|
|
361
387
|
parent_id = route&.aggregate ||
|
|
362
388
|
Identity.of(aggregate, args) ||
|
|
363
389
|
Identity.from(aggregate, args, :id) ||
|
|
364
|
-
raise(NotFound, RefusalWording.
|
|
365
|
-
|
|
366
|
-
|
|
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)))
|
|
367
393
|
found = repository.find(parent_id) ||
|
|
368
|
-
raise(NotFound, RefusalWording.
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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)))
|
|
372
398
|
found.dup
|
|
373
399
|
end
|
|
374
400
|
|
|
@@ -378,7 +404,7 @@ module Hecks
|
|
|
378
404
|
# `delegate_to_entity` step can locate and mutate the same element the
|
|
379
405
|
# same way, against an aggregate record already held in memory. `call`,
|
|
380
406
|
# above, and every `step_*` method reach them through that module now;
|
|
381
|
-
# nothing about the
|
|
407
|
+
# nothing about the steps themselves changed.
|
|
382
408
|
end
|
|
383
409
|
end
|
|
384
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)
|