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
|
@@ -24,15 +24,14 @@ module Hecks
|
|
|
24
24
|
|
|
25
25
|
attr_reader :registry
|
|
26
26
|
|
|
27
|
-
#
|
|
28
|
-
# (language/bluebook/vocabulary.bluebook
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
# Runtime::RefusalWording's own doc comment gives the same reason.
|
|
27
|
+
# **The declared order** — Vocabulary::AggregateDispatchOrder
|
|
28
|
+
# (language/bluebook/vocabulary.bluebook), read off the generated table
|
|
29
|
+
# (lib/hecks/vocabulary.rb) rather than typed here. spec/vocabulary_
|
|
30
|
+
# conformance_spec.rb holds every step to a real `step_<name>` handler,
|
|
31
|
+
# both directions.
|
|
33
32
|
DISPATCH_ORDER = Hecks::Vocabulary.symbols("AggregateDispatchOrder")
|
|
34
33
|
|
|
35
|
-
# A
|
|
34
|
+
# **A last-resort safety valve, not the normal outcome path** — see
|
|
36
35
|
# `Runtime::StaleWrite`'s own comment. Two concurrent writers
|
|
37
36
|
# against one aggregate resolve through exactly one retry in the
|
|
38
37
|
# ordinary case (the loser's retried hydrate reads the winner's now-
|
|
@@ -42,13 +41,13 @@ module Hecks
|
|
|
42
41
|
# two-writer case.
|
|
43
42
|
MAX_STALE_WRITE_RETRIES = 5
|
|
44
43
|
|
|
45
|
-
#
|
|
44
|
+
# Every cross-step local `call` used to thread through its own literal
|
|
46
45
|
# sequence, held in one place now that the sequence is data-driven —
|
|
47
46
|
# `result` and `transition`/`old_state` default to nil until the step
|
|
48
47
|
# that sets them runs, same as they were unset locals before that point.
|
|
49
48
|
Context = Struct.new(:domain, :aggregate, :command, :args, :repository, :instance, :transition, :old_state,
|
|
50
49
|
:result, :correlation, :route, :plan, :strategy, :persistence_outcome, :pending_delegation,
|
|
51
|
-
:dry_run, :correction_bindings, :outbox_rows)
|
|
50
|
+
:dry_run, :correction_bindings, :outbox_rows, :invocation)
|
|
52
51
|
|
|
53
52
|
def initialize(registry, rules:)
|
|
54
53
|
@registry = registry
|
|
@@ -60,21 +59,28 @@ module Hecks
|
|
|
60
59
|
# dispatch would (givens checked, mutations applied to `ctx.instance`
|
|
61
60
|
# in memory); `step_save`/`step_emit` are the only two that read this
|
|
62
61
|
# flag, each skipping its own real work — see their own comments.
|
|
63
|
-
#
|
|
62
|
+
# Retries the whole method body on `StaleWrite` — a fresh `ctx`, a
|
|
64
63
|
# fresh `step_hydrate` re-reading current state, so `enforce_givens`
|
|
65
64
|
# re-evaluates against reality rather than the snapshot that just
|
|
66
65
|
# went stale. See `MAX_STALE_WRITE_RETRIES`/`Runtime::StaleWrite`
|
|
67
66
|
# for why exhaustion is a pathological-contention signal, not the
|
|
68
67
|
# expected shape of a two-writer race.
|
|
69
|
-
|
|
68
|
+
#
|
|
69
|
+
# `invocation` — the `Runtime::Invocation` `Dispatcher` built for this
|
|
70
|
+
# call. `ctx.args` is `invocation.to_args` (the same Hash routing
|
|
71
|
+
# always handed this method), `ctx.route` its `target`.
|
|
72
|
+
def call(domain, aggregate, command, invocation, correlation = nil, dry_run: false)
|
|
73
|
+
args = invocation.to_args
|
|
74
|
+
route = invocation.target
|
|
70
75
|
attempt = 0
|
|
71
76
|
begin
|
|
72
77
|
ctx = Context.new(domain, aggregate, command, args)
|
|
78
|
+
ctx.invocation = invocation
|
|
73
79
|
ctx.correlation = correlation
|
|
74
80
|
ctx.route = route
|
|
75
81
|
ctx.dry_run = dry_run
|
|
76
82
|
ctx.plan = DependencyPlanning::Analyzer.call(aggregate: aggregate, command: command)
|
|
77
|
-
#
|
|
83
|
+
# **Resolved here, once, before hydration** — `Registry#repository`
|
|
78
84
|
# memoizes, so this and `step_hydrate`'s own read of `ctx.repository`
|
|
79
85
|
# (no second fetch there any more) always name the same instance;
|
|
80
86
|
# the isolation decision below (lock vs. CAS+retry) needs the
|
|
@@ -92,6 +98,16 @@ module Hecks
|
|
|
92
98
|
|
|
93
99
|
private
|
|
94
100
|
|
|
101
|
+
# **A no-op, and untraced** — Vocabulary::AggregateDispatchOrder's
|
|
102
|
+
# decode_arguments. Routing (`Runtime::Routing`) has already handed
|
|
103
|
+
# `call` a decoded argument hash by the time any step runs, so there is
|
|
104
|
+
# nothing left to decode here yet; like a conditional step that does
|
|
105
|
+
# not fire, it records nothing in `trace`. Declared so the Rust
|
|
106
|
+
# kernel's generated step enum carries the step its generated decoder
|
|
107
|
+
# will move into (roadmap D2), and so a typed Invocation (I2) has a
|
|
108
|
+
# step to be consumed at.
|
|
109
|
+
def step_decode_arguments(_ctx); end
|
|
110
|
+
|
|
95
111
|
def step_refuse_unknown_arguments(ctx)
|
|
96
112
|
step(:refuse_unknown_arguments) { refuse_unknown_arguments(ctx.domain, ctx.aggregate, ctx.command, ctx.args) }
|
|
97
113
|
end
|
|
@@ -131,14 +147,14 @@ module Hecks
|
|
|
131
147
|
|
|
132
148
|
def step_enforce_givens(ctx)
|
|
133
149
|
step(:enforce_givens) do
|
|
134
|
-
#
|
|
150
|
+
# Structural, before the declared givens — the same ordering
|
|
135
151
|
# NotFound/AlreadyExists already get at hydration: "does the
|
|
136
152
|
# fact this command's corrects names even exist" is not a
|
|
137
153
|
# domain rule an author wrote, it is a precondition for the
|
|
138
154
|
# domain rules to mean anything at all. Also locates the
|
|
139
155
|
# correction target itself, if `as:` named one — carried on
|
|
140
156
|
# `ctx` so `step_enforce_ensures` (the settled-record half)
|
|
141
|
-
# can bind the
|
|
157
|
+
# can bind the same name too, not just this pre-mutation half.
|
|
142
158
|
ctx.correction_bindings = @rules.enforce_correction_target(ctx.instance, ctx.aggregate, ctx.command, domain: ctx.domain)
|
|
143
159
|
@rules.enforce_givens(ctx.instance, ctx.command, ctx.args, domain: ctx.domain,
|
|
144
160
|
declaring: ctx.aggregate, parent: ctx.instance, correction: ctx.correction_bindings)
|
|
@@ -157,12 +173,12 @@ module Hecks
|
|
|
157
173
|
|
|
158
174
|
def step_apply_mutations(ctx)
|
|
159
175
|
# The state as the givens saw it — what `old` names inside an
|
|
160
|
-
# ensures. A shallow dup suffices: mutations
|
|
176
|
+
# ensures. A shallow dup suffices: mutations replace fields (set,
|
|
161
177
|
# arithmetic via Value#with, append builds a new array), never
|
|
162
178
|
# edit a held value in place.
|
|
163
179
|
ctx.old_state = ctx.instance.state.dup unless ctx.command.ensures.empty?
|
|
164
180
|
step(:apply_mutations) do
|
|
165
|
-
#
|
|
181
|
+
# One update set over the pre-dispatch state (C4.2, docs/
|
|
166
182
|
# semantics/bluebook-semantics.md): every effect's sources read
|
|
167
183
|
# `pre` — the state as it was before this command — and its
|
|
168
184
|
# target is written to the candidate; declaration order carries
|
|
@@ -180,19 +196,19 @@ module Hecks
|
|
|
180
196
|
step(:advance_lifecycle) { ctx.instance[ctx.aggregate.lifecycle.field] = ctx.transition.target }
|
|
181
197
|
end
|
|
182
198
|
|
|
183
|
-
#
|
|
199
|
+
# The synchronous cousin of a policy's own `trigger` — see
|
|
184
200
|
# `CommandBuilder#delegates_to`'s own comment for the full reasoning.
|
|
185
|
-
# Runs
|
|
201
|
+
# Runs after this command's own mutations/lifecycle (so a delegating
|
|
186
202
|
# command could in principle still guard with its own `given`s first,
|
|
187
|
-
# though the real use in `domain/chess` declares none) and
|
|
203
|
+
# though the real use in `domain/chess` declares none) and before
|
|
188
204
|
# `enforce_ensures`/`enforce_invariants`/`save`, so a refusal here
|
|
189
|
-
# raises a real, unrescued exception and
|
|
205
|
+
# raises a real, unrescued exception and nothing from either side —
|
|
190
206
|
# this command's own state, the target element's — has been saved
|
|
191
|
-
# yet. `ctx.instance` is the
|
|
207
|
+
# yet. `ctx.instance` is the same in-memory record `step_hydrate`
|
|
192
208
|
# loaded and `step_save` will persist; `EntityElement.locate_chain`
|
|
193
209
|
# mutates it in place exactly the way `EntityInterpreter`'s own
|
|
194
|
-
# `step_locate_element`/`step_apply_mutations` mutate their
|
|
195
|
-
# freshly-loaded copy — the only difference is
|
|
210
|
+
# `step_locate_element`/`step_apply_mutations` mutate their own
|
|
211
|
+
# freshly-loaded copy — the only difference is which already-in-
|
|
196
212
|
# memory record gets handed in.
|
|
197
213
|
#
|
|
198
214
|
# Reimplements the entity command pipeline's own order (givens,
|
|
@@ -219,8 +235,8 @@ module Hecks
|
|
|
219
235
|
"#{entity_name}.#{command_name}, which " \
|
|
220
236
|
"#{entity_name} declares no such command")
|
|
221
237
|
|
|
222
|
-
# `with:`
|
|
223
|
-
# of
|
|
238
|
+
# `with:` remaps, it does not enumerate — starting from a copy
|
|
239
|
+
# of this command's own already-resolved args (`ctx.args`) and
|
|
224
240
|
# overlaying the explicit mapping on top means ambient context
|
|
225
241
|
# the caller never had to think about (the aggregate's own
|
|
226
242
|
# identity, addressed the ordinary way to reach `MoveKnight` at
|
|
@@ -228,7 +244,7 @@ module Hecks
|
|
|
228
244
|
# always would for a caller dispatching the entity command
|
|
229
245
|
# directly. Confirmed necessary, not a defensive guess: a real
|
|
230
246
|
# downstream domain's own AdvancePly-on-Moved policy silently
|
|
231
|
-
# failed to re-locate its
|
|
247
|
+
# failed to re-locate its own aggregate (`reaction_log`: "no
|
|
232
248
|
# Game with label.value ..."), because the emitted event's
|
|
233
249
|
# payload — built from `target_args` alone — never carried
|
|
234
250
|
# `label` at all when `with:` named only `id`/`to`.
|
|
@@ -252,7 +268,7 @@ module Hecks
|
|
|
252
268
|
settled = Instance.new(aggregate: entity, id: view.id, state: element)
|
|
253
269
|
@rules.enforce_ensures(settled, target_command, target_args, old: old_element, domain: ctx.domain, parent: ctx.instance)
|
|
254
270
|
|
|
255
|
-
#
|
|
271
|
+
# Not emitted here — C7.2 (docs/semantics/bluebook-semantics.md):
|
|
256
272
|
# a refused command records nothing, and the parent's own
|
|
257
273
|
# `ensures`/`enforce_invariants`/`save` steps still run after
|
|
258
274
|
# this one. The target's emission is parked and performed by
|
|
@@ -275,29 +291,70 @@ module Hecks
|
|
|
275
291
|
step(:enforce_invariants) { @rules.enforce_invariants(ctx.instance, ctx.aggregate, domain: ctx.domain) }
|
|
276
292
|
end
|
|
277
293
|
|
|
278
|
-
# `dry_run:` skips this — see Dispatcher#
|
|
279
|
-
#
|
|
280
|
-
#
|
|
281
|
-
#
|
|
282
|
-
#
|
|
294
|
+
# `dry_run:` skips the write half of this step — see Dispatcher#
|
|
295
|
+
# dry_run?'s own comment — but not `resolve_state_references`.
|
|
296
|
+
# BUG#127: that check is validation, not persistence — it asks
|
|
297
|
+
# whether the settled in-memory state names a reference target that
|
|
298
|
+
# actually exists, the same question a real dispatch answers before
|
|
299
|
+
# it ever writes anything. Skipping it under `dry_run:` made
|
|
300
|
+
# `dry_run?` disagree with what a real dispatch immediately after it
|
|
301
|
+
# would do: it answered `true` ("would succeed") for a command whose
|
|
302
|
+
# real dispatch refuses with NotFound. Only the persist/raise-on-
|
|
303
|
+
# conflict tail is genuinely persistence-only and stays behind the
|
|
304
|
+
# early return.
|
|
305
|
+
#
|
|
306
|
+
# BUG#132 — a second validation-not-persistence check hid in that
|
|
307
|
+
# same persist/raise-on-conflict tail: `persist_instance`'s own
|
|
308
|
+
# ATOMIC_PUT branch (below) is where `hydrate_complete_state`'s
|
|
309
|
+
# comment says a `creates?` command's duplicate check is
|
|
310
|
+
# deliberately deferred to, for a strategy this fast — a read
|
|
311
|
+
# (`repository.find`) any earlier, before the real write, would be
|
|
312
|
+
# exactly the extra read `insert_only:` exists to avoid paying on
|
|
313
|
+
# every real dispatch. But that means the check never ran at all under
|
|
314
|
+
# `dry_run:` — not eagerly (hydration skips it for this exact
|
|
315
|
+
# strategy, on purpose) and not deferred (this whole branch returns
|
|
316
|
+
# first). `Roster::Roster.Open` against an already-open name is
|
|
317
|
+
# this shape: `dry_run?` answered `true` for a real dispatch
|
|
318
|
+
# immediately after it that refuses `AlreadyExists`. Under
|
|
319
|
+
# `dry_run:` there is no write to race, so paying for that one read
|
|
320
|
+
# is safe here — and only here.
|
|
283
321
|
def step_save(ctx)
|
|
284
|
-
|
|
322
|
+
step(:save) { @rules.resolve_state_references(ctx.domain, ctx.aggregate, ctx.instance.state) }
|
|
323
|
+
|
|
324
|
+
if ctx.dry_run
|
|
325
|
+
step(:save) { check_dry_run_creates_duplicate(ctx) }
|
|
326
|
+
return
|
|
327
|
+
end
|
|
285
328
|
|
|
286
329
|
step(:save) do
|
|
287
|
-
@rules.resolve_state_references(ctx.domain, ctx.aggregate, ctx.instance.state)
|
|
288
330
|
seed_projected_fields(ctx)
|
|
289
331
|
ctx.persistence_outcome = persist_instance(ctx)
|
|
290
332
|
raise_for_persistence_outcome!(ctx)
|
|
291
333
|
end
|
|
292
334
|
end
|
|
293
335
|
|
|
336
|
+
# See `step_save`'s own BUG#132 comment: the only path, real or dry,
|
|
337
|
+
# able to catch a `creates?` command reusing an already-occupied
|
|
338
|
+
# identity when `strategy` is `ATOMIC_PUT` — `hydrate_complete_
|
|
339
|
+
# state` deliberately skips this exact case (its own comment), and
|
|
340
|
+
# a dry run never reaches `persist_instance`'s matching branch.
|
|
341
|
+
def check_dry_run_creates_duplicate(ctx)
|
|
342
|
+
return unless ctx.strategy == DependencyPlanning::ATOMIC_PUT && ctx.command.creates?
|
|
343
|
+
return unless ctx.repository.find(ctx.instance.id)
|
|
344
|
+
|
|
345
|
+
raise(AlreadyExists, RefusalWording.render_site("AlreadyExists", "creating_duplicate",
|
|
346
|
+
command: ctx.command.hecks_name, aggregate: ctx.aggregate.hecks_name,
|
|
347
|
+
identity: identity_reading(ctx.aggregate),
|
|
348
|
+
offered: Rendering.describe(ctx.instance.id)))
|
|
349
|
+
end
|
|
350
|
+
|
|
294
351
|
def persist_instance(ctx)
|
|
295
352
|
if ctx.strategy == DependencyPlanning::ATOMIC_PUT
|
|
296
|
-
# A
|
|
353
|
+
# **A second creation is not a fresh one** — see
|
|
297
354
|
# hydrate_complete_state's own comment; the
|
|
298
355
|
# same refusal, on the same terms, for the
|
|
299
356
|
# complete-state path. `insert_only:` asks the
|
|
300
|
-
#
|
|
357
|
+
# adapter to decide and refuse atomically
|
|
301
358
|
# (never writing a `creates?` command over an
|
|
302
359
|
# identity that already names a record) rather
|
|
303
360
|
# than this interpreter reading the record
|
|
@@ -316,12 +373,12 @@ module Hecks
|
|
|
316
373
|
|
|
317
374
|
def raise_for_persistence_outcome!(ctx)
|
|
318
375
|
if ctx.persistence_outcome.status == :conflicted
|
|
319
|
-
raise(AlreadyExists, RefusalWording.
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
376
|
+
raise(AlreadyExists, RefusalWording.render_site("AlreadyExists", "creating_duplicate",
|
|
377
|
+
command: ctx.command.hecks_name, aggregate: ctx.aggregate.hecks_name,
|
|
378
|
+
identity: identity_reading(ctx.aggregate),
|
|
379
|
+
offered: Rendering.describe(ctx.instance.id)))
|
|
323
380
|
elsif ctx.persistence_outcome.status == :stale
|
|
324
|
-
#
|
|
381
|
+
# Not a `RefusalWording.render` call — this is not a declared
|
|
325
382
|
# vocabulary refusal, just a plain, informative message. See
|
|
326
383
|
# `Runtime::StaleWrite`'s own comment: caught by `#call`'s
|
|
327
384
|
# retry loop, re-raised only once retries are exhausted.
|
|
@@ -332,26 +389,26 @@ module Hecks
|
|
|
332
389
|
end
|
|
333
390
|
end
|
|
334
391
|
|
|
335
|
-
#
|
|
392
|
+
# The one-time, synchronous half of `projects` (S12, ADR 0025) —
|
|
336
393
|
# `RebuildSweep` (`runtime/rebuild_sweep.rb`) is deliberately the
|
|
337
|
-
#
|
|
338
|
-
# target that changes
|
|
394
|
+
# only thing that keeps a projected field current against a
|
|
395
|
+
# target that changes after this record was written — no reactive
|
|
339
396
|
# `Policy#for_each` keeping it live in real time, that stays
|
|
340
397
|
# deferred, same as that file's own header explains. But without
|
|
341
|
-
#
|
|
342
|
-
#
|
|
398
|
+
# some synchronous population, a projected field never gets an
|
|
399
|
+
# initial value at all until an operator remembers to run a
|
|
343
400
|
# sweep by hand — every acting command reading it (`given
|
|
344
401
|
# ("customer is active") { customer_status == "active" }`, say)
|
|
345
402
|
# would refuse a freshly created, genuinely active record for no
|
|
346
403
|
# real reason, which is not the eventual-consistency tradeoff the
|
|
347
404
|
# ADR accepts, just a bug. So: every time a record with `projects`
|
|
348
405
|
# fields is about to save — creating or acting, either can be the
|
|
349
|
-
# first time a referenced record resolves — read each one
|
|
406
|
+
# first time a referenced record resolves — read each one once,
|
|
350
407
|
# here, using the exact same `RebuildSweep.remote_value` a sweep
|
|
351
408
|
# itself would compute. This is still eventually consistent in
|
|
352
|
-
# the sense the ADR means: a change on the
|
|
409
|
+
# the sense the ADR means: a change on the target side after this
|
|
353
410
|
# save still needs a sweep to reach here. It is only ever
|
|
354
|
-
#
|
|
411
|
+
# synchronous with this record's own write, never a live read
|
|
355
412
|
# triggered by a `given`/`ensures`/`invariant` mid-dispatch — the
|
|
356
413
|
# boundary rule those enforce holds exactly as before.
|
|
357
414
|
def seed_projected_fields(ctx)
|
|
@@ -365,10 +422,10 @@ module Hecks
|
|
|
365
422
|
end
|
|
366
423
|
end
|
|
367
424
|
|
|
368
|
-
# A
|
|
425
|
+
# A delegating command emits nothing of its own (`CommandBuilder#build`'s
|
|
369
426
|
# own guard refuses declaring `emits` alongside `delegates_to`) — its
|
|
370
|
-
# result
|
|
371
|
-
# `step_delegate_to_entity` and emitted
|
|
427
|
+
# result is the target entity command's own `emits`, parked by
|
|
428
|
+
# `step_delegate_to_entity` and emitted here, after save (C7.2), not
|
|
372
429
|
# a second, empty call into `@rules.emit` for a command with no
|
|
373
430
|
# announced events at all.
|
|
374
431
|
#
|
|
@@ -397,53 +454,53 @@ module Hecks
|
|
|
397
454
|
def hydrate_existing(repository, aggregate, command, args, route = nil)
|
|
398
455
|
if route
|
|
399
456
|
found = repository.find(route.aggregate) ||
|
|
400
|
-
raise(NotFound, RefusalWording.
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
457
|
+
raise(NotFound, RefusalWording.render_site("NotFound", "record_missing",
|
|
458
|
+
aggregate: aggregate.hecks_name,
|
|
459
|
+
identity: identity_reading(aggregate),
|
|
460
|
+
offered: Rendering.describe(route.aggregate)))
|
|
404
461
|
return found.dup
|
|
405
462
|
end
|
|
406
463
|
|
|
407
464
|
id = identity_of(aggregate, args) ||
|
|
408
465
|
identity_from(aggregate, args, :id) ||
|
|
409
466
|
identity_from(aggregate, args, reference_key(command)) ||
|
|
410
|
-
raise(NotFound, RefusalWording.
|
|
411
|
-
|
|
412
|
-
|
|
467
|
+
raise(NotFound, RefusalWording.render_site("NotFound", "acting_no_identity",
|
|
468
|
+
command: command.hecks_name, aggregate: aggregate.hecks_name,
|
|
469
|
+
identity: identity_reading(aggregate)))
|
|
413
470
|
found = repository.find(id) ||
|
|
414
|
-
raise(NotFound, RefusalWording.
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
471
|
+
raise(NotFound, RefusalWording.render_site("NotFound", "record_missing",
|
|
472
|
+
aggregate: aggregate.hecks_name,
|
|
473
|
+
identity: identity_reading(aggregate),
|
|
474
|
+
offered: Rendering.describe(id)))
|
|
418
475
|
found.dup
|
|
419
476
|
end
|
|
420
477
|
|
|
421
|
-
#
|
|
478
|
+
# Wave 8 (equivalence-gap plan, item 1.6) — not done, and this
|
|
422
479
|
# comment now says precisely how far it got, corrected from an
|
|
423
|
-
#
|
|
480
|
+
# earlier version of itself that briefly (same PR, never released)
|
|
424
481
|
# claimed the inventory was empty and deleted this method outright.
|
|
425
482
|
# A full corpus audit (`DependencyPlanning::Analyzer.call` against
|
|
426
|
-
# every `creates?`-true command in all 5
|
|
483
|
+
# every `creates?`-true command in all 5 example domains — banking,
|
|
427
484
|
# pizzas, chess, compliance, roster) found and fixed 4 real
|
|
428
485
|
# authoring bugs (`Statement.Generate`, `CreatePizza`, `Chess::
|
|
429
486
|
# Game.Start`, `Roster.Open` — each declared an attribute and
|
|
430
487
|
# never `sets` it, so the field silently stayed nil on every
|
|
431
488
|
# created record regardless of what a caller sent), plus a real
|
|
432
|
-
# `DependencyPlanning::Analyzer` bug for
|
|
489
|
+
# `DependencyPlanning::Analyzer` bug for entity-owned commands
|
|
433
490
|
# (`root_aggregate:`, that class's own header) that surfaced one
|
|
434
491
|
# more live corpus bug of its own (`payment_cards.bluebook`'s own
|
|
435
492
|
# `Withdrawal.Dispute`, which never actually checked whether the
|
|
436
493
|
# card was retired) — all real, all kept.
|
|
437
494
|
#
|
|
438
|
-
#
|
|
495
|
+
# Deleting this method on that basis turned out to be wrong,
|
|
439
496
|
# caught by running the full suite rather than trusting the
|
|
440
497
|
# audit's own scope: the 5 example domains are nowhere near the
|
|
441
|
-
#
|
|
498
|
+
# whole inventory of `creates?`-true commands this fallback
|
|
442
499
|
# actually carries. Dozens of separate, purpose-built spec
|
|
443
500
|
# fixtures across the suite (`spec/fixtures/*.bluebook`, and
|
|
444
501
|
# inline `Hecks.bluebook` blocks declared directly inside
|
|
445
502
|
# individual spec files — governance, mutation ops, ports,
|
|
446
|
-
# routing, tenant isolation, sagas, and more) declare their
|
|
503
|
+
# routing, tenant isolation, sagas, and more) declare their own
|
|
447
504
|
# small `creates?`-true commands the same incomplete way, and
|
|
448
505
|
# rely on this exact fallback to create anything at all. Deleting
|
|
449
506
|
# it produced 218 failures across specs with nothing to do with
|
|
@@ -462,7 +519,7 @@ module Hecks
|
|
|
462
519
|
# acquired explicit effects, same as it always was — deliberately
|
|
463
520
|
# isolated from the normal routing and planning path so
|
|
464
521
|
# `reference_to` no longer chooses how a migrated command hydrates
|
|
465
|
-
# or persists. A real, future Wave 8 removes this once
|
|
522
|
+
# or persists. A real, future Wave 8 removes this once that wider
|
|
466
523
|
# inventory is empty, not before.
|
|
467
524
|
#
|
|
468
525
|
# `ctx.plan.complete_state?` already claimed every command whose
|
|
@@ -471,7 +528,7 @@ module Hecks
|
|
|
471
528
|
# incomplete — an un-migrated command still routes here on
|
|
472
529
|
# `creates?` alone, the same as the old `hydrate` did, regardless
|
|
473
530
|
# of whether it happens to have any mutations (`write_set`).
|
|
474
|
-
# Requiring an
|
|
531
|
+
# Requiring an empty write_set here refused every un-migrated
|
|
475
532
|
# creating command that sets even one field.
|
|
476
533
|
def legacy_implicit_creation?(ctx)
|
|
477
534
|
ctx.route.nil? && ctx.command.creates?
|
|
@@ -479,14 +536,14 @@ module Hecks
|
|
|
479
536
|
|
|
480
537
|
def hydrate_legacy_creation(repository, aggregate, command, args)
|
|
481
538
|
id = identity_of(aggregate, args) ||
|
|
482
|
-
raise(NotFound, RefusalWording.
|
|
483
|
-
|
|
484
|
-
|
|
539
|
+
raise(NotFound, RefusalWording.render_site("NotFound", "creating_no_identity",
|
|
540
|
+
command: command.hecks_name, aggregate: aggregate.hecks_name,
|
|
541
|
+
identity: identity_reading(aggregate)))
|
|
485
542
|
if repository.find(id)
|
|
486
|
-
raise(AlreadyExists, RefusalWording.
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
543
|
+
raise(AlreadyExists, RefusalWording.render_site("AlreadyExists", "creating_duplicate",
|
|
544
|
+
command: command.hecks_name, aggregate: aggregate.hecks_name,
|
|
545
|
+
identity: identity_reading(aggregate),
|
|
546
|
+
offered: Rendering.describe(id)))
|
|
490
547
|
end
|
|
491
548
|
|
|
492
549
|
Instance.new(aggregate: aggregate, id: id, args: args)
|
|
@@ -500,29 +557,29 @@ module Hecks
|
|
|
500
557
|
end
|
|
501
558
|
|
|
502
559
|
id = route&.aggregate || derived ||
|
|
503
|
-
raise(NotFound, RefusalWording.
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
560
|
+
raise(NotFound, RefusalWording.render_site("NotFound", "creating_no_identity",
|
|
561
|
+
command: command.hecks_name,
|
|
562
|
+
aggregate: aggregate.hecks_name,
|
|
563
|
+
identity: identity_reading(aggregate)))
|
|
507
564
|
|
|
508
|
-
# A
|
|
565
|
+
# **A second creation is not a fresh one** — `creates?` on an identity
|
|
509
566
|
# a record already exists under refuses (`AlreadyExists`) rather
|
|
510
567
|
# than silently overwriting it, the same refusal `hydrate_prior_
|
|
511
568
|
# or_initial`'s own body gives for its own complete-but-state-
|
|
512
|
-
# dependent case, below.
|
|
569
|
+
# dependent case, below. Only when `strategy` will not be ATOMIC_PUT:
|
|
513
570
|
# an atomic-put-capable adapter enforces this itself, atomically,
|
|
514
571
|
# via `insert_only:` in step_save (no read here, no race with the
|
|
515
572
|
# write) — but `strategy_for` already fell back to a plain `save`
|
|
516
573
|
# for an adapter with no atomic_put capability at all (Heki, today),
|
|
517
574
|
# and a plain `save` never refuses an overwrite on its own. Skipping
|
|
518
|
-
# this check for
|
|
575
|
+
# this check for that case silently dropped the refusal instead of
|
|
519
576
|
# deferring it — the very "no such call — no such check" gap Wave
|
|
520
577
|
# 8 exists to close everywhere else.
|
|
521
578
|
if command.creates? && strategy != DependencyPlanning::ATOMIC_PUT && repository.find(id)
|
|
522
|
-
raise(AlreadyExists, RefusalWording.
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
579
|
+
raise(AlreadyExists, RefusalWording.render_site("AlreadyExists", "creating_duplicate",
|
|
580
|
+
command: command.hecks_name, aggregate: aggregate.hecks_name,
|
|
581
|
+
identity: identity_reading(aggregate),
|
|
582
|
+
offered: Rendering.describe(id)))
|
|
526
583
|
end
|
|
527
584
|
|
|
528
585
|
Instance.new(aggregate: aggregate, id: id, args: args)
|
|
@@ -541,19 +598,19 @@ module Hecks
|
|
|
541
598
|
end
|
|
542
599
|
|
|
543
600
|
id = route&.aggregate || derived ||
|
|
544
|
-
raise(NotFound, RefusalWording.
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
601
|
+
raise(NotFound, RefusalWording.render_site("NotFound", "creating_no_identity",
|
|
602
|
+
command: command.hecks_name,
|
|
603
|
+
aggregate: aggregate.hecks_name,
|
|
604
|
+
identity: identity_reading(aggregate)))
|
|
548
605
|
found = repository.find(id)
|
|
549
606
|
|
|
550
|
-
# A
|
|
607
|
+
# **A second creation is not a fresh one** — see hydrate_complete_
|
|
551
608
|
# state's own comment; the same refusal, on the same terms, for
|
|
552
609
|
# a complete-but-state-dependent command (one with a `given`
|
|
553
610
|
# reading its own prior state, which is what routes here instead
|
|
554
611
|
# of hydrate_complete_state). Gated on `command.creates?`: a
|
|
555
|
-
#
|
|
556
|
-
# "act on whatever this identity already holds" (`found`
|
|
612
|
+
# non-creating command's own complete payload legitimately means
|
|
613
|
+
# "act on whatever this identity already holds" (`found` is the
|
|
557
614
|
# prior state this branch exists to supply), so only a genuine
|
|
558
615
|
# creation reusing an already-occupied identity is a duplicate.
|
|
559
616
|
# `SafeDepositBox.Rent` is exactly this shape — `given("box is
|
|
@@ -562,16 +619,16 @@ module Hecks
|
|
|
562
619
|
# for the wrong reason (not vacant) instead of the right one
|
|
563
620
|
# (already exists).
|
|
564
621
|
if found && command.creates?
|
|
565
|
-
raise(AlreadyExists, RefusalWording.
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
622
|
+
raise(AlreadyExists, RefusalWording.render_site("AlreadyExists", "creating_duplicate",
|
|
623
|
+
command: command.hecks_name, aggregate: aggregate.hecks_name,
|
|
624
|
+
identity: identity_reading(aggregate),
|
|
625
|
+
offered: Rendering.describe(id)))
|
|
569
626
|
end
|
|
570
627
|
|
|
571
628
|
found ? found.dup : Instance.new(aggregate: aggregate, id: id, args: args)
|
|
572
629
|
end
|
|
573
630
|
|
|
574
|
-
#
|
|
631
|
+
# **The join, the dig, and the reading** — all shared with `EntityInterpreter`
|
|
575
632
|
# now, in `Runtime::Identity`, rather than kept as two copies that could
|
|
576
633
|
# only ever drift. See that module for the reasoning ; these three stay
|
|
577
634
|
# here, at the old names, purely so nothing below has to change.
|