hecks 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/hecks/adapters/driven/claude_code.rb +72 -7
- data/lib/hecks/adapters/driven/d1.rb +187 -23
- data/lib/hecks/adapters/driven/folder.rb +83 -10
- data/lib/hecks/adapters/driven/google_authentication.rb +33 -12
- data/lib/hecks/adapters/driven/governance_authorization.rb +87 -18
- data/lib/hecks/adapters/driven/heki/journal.rb +13 -4
- data/lib/hecks/adapters/driven/heki/saga_store.rb +56 -10
- data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
- data/lib/hecks/adapters/driven/heki.rb +106 -9
- data/lib/hecks/adapters/driven/identity_registry.rb +12 -2
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +25 -3
- data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
- data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +67 -14
- data/lib/hecks/adapters/driven/lambda.rb +82 -35
- data/lib/hecks/adapters/driven/local_storage.rb +83 -10
- data/lib/hecks/adapters/driven/memory.rb +205 -9
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +21 -1
- data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
- data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
- data/lib/hecks/adapters/driven/postgres.rb +175 -28
- data/lib/hecks/adapters/driven/prism.rb +50 -11
- data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
- data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
- data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
- data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
- data/lib/hecks/adapters/driven/sqlite.rb +181 -21
- data/lib/hecks/adapters/driven/system_clock.rb +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
- data/lib/hecks/adapters/driven.rb +6 -4
- data/lib/hecks/adapters/driving/github_webhook.rb +31 -18
- data/lib/hecks/behaviors/dsl.rb +60 -2
- data/lib/hecks/behaviors/expectations.rb +190 -29
- data/lib/hecks/behaviors/ir.rb +12 -1
- data/lib/hecks/behaviors/rspec.rb +9 -1
- data/lib/hecks/behaviors/runner.rb +21 -2
- data/lib/hecks/behaviors.rb +9 -1
- data/lib/hecks/bluebook/aggregate.rb +43 -13
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +17 -10
- data/lib/hecks/bluebook/assembly/build.rb +26 -5
- data/lib/hecks/bluebook/assembly/contract.rb +98 -23
- data/lib/hecks/bluebook/assembly/contracts.rb +59 -52
- data/lib/hecks/bluebook/assembly/marks.rb +159 -30
- data/lib/hecks/bluebook/assembly/specializer.rb +38 -21
- data/lib/hecks/bluebook/assembly.rb +32 -14
- data/lib/hecks/bluebook/attribute.rb +26 -12
- data/lib/hecks/bluebook/behaviour/aggregate.rb +43 -11
- data/lib/hecks/bluebook/behaviour/attribute.rb +18 -5
- data/lib/hecks/bluebook/behaviour/chapter.rb +76 -5
- data/lib/hecks/bluebook/behaviour/command.rb +55 -25
- data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -7
- data/lib/hecks/bluebook/behaviour/entity.rb +20 -8
- data/lib/hecks/bluebook/behaviour/hexagon.rb +30 -4
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +27 -6
- data/lib/hecks/bluebook/behaviour/policy.rb +42 -17
- data/lib/hecks/bluebook/behaviour/process_manager.rb +39 -8
- data/lib/hecks/bluebook/behaviour/query.rb +6 -1
- data/lib/hecks/bluebook/behaviour/read_model.rb +29 -8
- data/lib/hecks/bluebook/behaviour/traits.rb +48 -12
- data/lib/hecks/bluebook/behaviour/value_object.rb +21 -9
- data/lib/hecks/bluebook/capabilities.rb +27 -0
- data/lib/hecks/bluebook/chapter.rb +51 -9
- data/lib/hecks/bluebook/command.rb +62 -17
- data/lib/hecks/bluebook/domain_port.rb +34 -9
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +282 -123
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +112 -75
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +81 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +486 -117
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +179 -47
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +284 -122
- data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
- data/lib/hecks/bluebook/dsl/entity_builder.rb +191 -61
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +130 -30
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +38 -21
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
- data/lib/hecks/bluebook/dsl/policy_builder.rb +86 -36
- data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +111 -47
- data/lib/hecks/bluebook/dsl/query_builder.rb +37 -8
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +127 -52
- data/lib/hecks/bluebook/dsl/rule_reference.rb +97 -43
- data/lib/hecks/bluebook/dsl/translation_builder.rb +150 -44
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +68 -20
- data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
- data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
- data/lib/hecks/bluebook/entity.rb +40 -11
- data/lib/hecks/bluebook/expression/ast_json.rb +128 -36
- data/lib/hecks/bluebook/expression/ast_reader.rb +32 -3
- data/lib/hecks/bluebook/expression/canonical_form.rb +55 -16
- data/lib/hecks/bluebook/expression/evaluator.rb +221 -43
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +54 -18
- data/lib/hecks/bluebook/expression/resolver.rb +369 -128
- data/lib/hecks/bluebook/hexagon.rb +35 -1
- data/lib/hecks/bluebook/lifecycle.rb +12 -1
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/judge.rb +126 -108
- data/lib/hecks/bluebook/meta_validator/plan.rb +81 -46
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +3 -2
- data/lib/hecks/bluebook/meta_validator/readings.rb +200 -50
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +68 -41
- data/lib/hecks/bluebook/meta_validator/shapes.rb +166 -21
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +286 -46
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +11 -10
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -5
- data/lib/hecks/bluebook/meta_validator.rb +235 -139
- data/lib/hecks/bluebook/model_check.rb +434 -104
- data/lib/hecks/bluebook/pattern_subset.rb +32 -10
- data/lib/hecks/bluebook/policy.rb +15 -13
- data/lib/hecks/bluebook/process_manager.rb +27 -14
- data/lib/hecks/bluebook/project_discovery.rb +5 -0
- data/lib/hecks/bluebook/project_loader.rb +40 -0
- data/lib/hecks/bluebook/project_register.rb +50 -6
- data/lib/hecks/bluebook/query.rb +31 -4
- data/lib/hecks/bluebook/read_model.rb +35 -15
- data/lib/hecks/bluebook/reference.rb +26 -13
- data/lib/hecks/bluebook/smoke_test.rb +46 -23
- data/lib/hecks/bluebook/synthesizer.rb +46 -12
- data/lib/hecks/bluebook/translation.rb +34 -5
- data/lib/hecks/bluebook/value_object.rb +29 -11
- data/lib/hecks/bluebook.rb +5 -6
- data/lib/hecks/codemod.rb +138 -50
- data/lib/hecks/construct.rb +21 -7
- data/lib/hecks/corpus.rb +438 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
- data/lib/hecks/doc/reference.rb +200 -31
- data/lib/hecks/embryonaut_bluebook.rb +38 -15
- data/lib/hecks/facade/cli_door.rb +69 -10
- data/lib/hecks/facade/cli_runner.rb +105 -24
- data/lib/hecks/facade/command_request.rb +23 -0
- data/lib/hecks/facade/handle.rb +155 -35
- data/lib/hecks/facade/json_door.rb +106 -25
- data/lib/hecks/facade/surface/aggregate_door.rb +50 -27
- data/lib/hecks/facade/surface/chapter.rb +26 -17
- data/lib/hecks/facade/surface.rb +16 -3
- data/lib/hecks/facade.rb +15 -4
- data/lib/hecks/forms/app.rb +46 -30
- data/lib/hecks/forms/command_form_renderer.rb +70 -9
- data/lib/hecks/forms/field_renderer.rb +142 -6
- data/lib/hecks/forms/field_shape.rb +183 -19
- data/lib/hecks/forms/html.rb +51 -7
- data/lib/hecks/forms/index_renderer.rb +14 -2
- data/lib/hecks/forms/page.rb +14 -0
- data/lib/hecks/forms/params.rb +120 -23
- data/lib/hecks/forms/port_argument.rb +14 -2
- data/lib/hecks/forms/query_form_renderer.rb +65 -2
- data/lib/hecks/forms/record_renderer.rb +60 -2
- data/lib/hecks/forms/record_table.rb +28 -1
- data/lib/hecks/forms/reference_options.rb +24 -0
- data/lib/hecks/forms/value_object_shape.rb +13 -3
- data/lib/hecks/forms.rb +24 -4
- data/lib/hecks/fqn.rb +59 -1
- data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
- data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
- data/lib/hecks/framework/oidc.json +15 -0
- data/lib/hecks/framework.rb +79 -25
- data/lib/hecks/freezer.rb +27 -11
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +241 -106
- data/lib/hecks/fuzzing/combination_miner.rb +178 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +241 -45
- data/lib/hecks/fuzzing/coverage_campaign.rb +161 -0
- data/lib/hecks/fuzzing/differential.rb +192 -0
- data/lib/hecks/fuzzing/domain_generator.rb +871 -0
- data/lib/hecks/fuzzing/era_boundary.rb +68 -19
- data/lib/hecks/fuzzing/form_census.rb +121 -20
- data/lib/hecks/fuzzing/generated_domain_check.rb +171 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +45 -6
- data/lib/hecks/fuzzing/isolated_boot.rb +137 -80
- data/lib/hecks/fuzzing/nondeterministic.rb +79 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +111 -21
- data/lib/hecks/fuzzing/properties/corrections.rb +34 -9
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +300 -51
- data/lib/hecks/fuzzing/properties/guards.rb +86 -42
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +80 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +40 -35
- data/lib/hecks/fuzzing/properties/outbox.rb +70 -32
- data/lib/hecks/fuzzing/properties/querying.rb +82 -28
- data/lib/hecks/fuzzing/properties.rb +84 -51
- data/lib/hecks/fuzzing/qa_settings.rb +164 -0
- data/lib/hecks/fuzzing/replay.rb +241 -133
- data/lib/hecks/fuzzing/rotation_priority.rb +48 -28
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +139 -0
- data/lib/hecks/fuzzing/self_consistency.rb +315 -137
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +46 -40
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +18 -11
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +13 -12
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +21 -12
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +59 -32
- data/lib/hecks/fuzzing/sequence_generator.rb +158 -23
- data/lib/hecks/fuzzing/shrinker.rb +309 -0
- data/lib/hecks/fuzzing/structural_skips.rb +37 -130
- data/lib/hecks/fuzzing/sweep_depth.rb +12 -4
- data/lib/hecks/fuzzing/target_capabilities.rb +107 -29
- data/lib/hecks/fuzzing/value_generator.rb +110 -22
- data/lib/hecks/fuzzing.rb +5 -0
- data/lib/hecks/grammar/evolve.rb +188 -12
- data/lib/hecks/grammar.rb +53 -7
- data/lib/hecks/ir.rb +51 -20
- data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
- data/lib/hecks/language/bluebook/policy.bluebook +11 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +348 -13
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/literal.rb +41 -9
- data/lib/hecks/naming.rb +112 -31
- data/lib/hecks/ports/access_control.rb +53 -2
- data/lib/hecks/ports/agent/answers.rb +83 -6
- data/lib/hecks/ports/agent.rb +119 -35
- data/lib/hecks/ports/authentication.rb +44 -4
- data/lib/hecks/ports/authorization.rb +50 -11
- data/lib/hecks/ports/clock.rb +42 -23
- data/lib/hecks/ports/extraction.rb +16 -0
- data/lib/hecks/ports/identity_assignment.rb +23 -2
- data/lib/hecks/ports/identity_generation.rb +17 -3
- data/lib/hecks/ports/identity_resolution.rb +17 -1
- data/lib/hecks/ports/key_vault.port +6 -0
- data/lib/hecks/ports/key_vault.rb +58 -0
- data/lib/hecks/ports/loading.rb +4 -0
- data/lib/hecks/ports/persistence/append_only.rb +172 -8
- data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
- data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
- data/lib/hecks/ports/persistence/execution.rb +4 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
- data/lib/hecks/ports/persistence/plugin.rb +42 -4
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +118 -66
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +34 -5
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +33 -9
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +95 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +314 -90
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +48 -8
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +58 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +96 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +9 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
- data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
- data/lib/hecks/ports/persistence/repository_factory.rb +23 -3
- data/lib/hecks/ports/persistence/state_codec.rb +319 -0
- data/lib/hecks/ports/persistence.rb +36 -1
- data/lib/hecks/ports/projection.rb +61 -7
- data/lib/hecks/ports/query/in_memory.rb +41 -8
- data/lib/hecks/ports/query/ordering.rb +21 -6
- data/lib/hecks/ports/query.rb +35 -0
- data/lib/hecks/ports.rb +1 -0
- data/lib/hecks/projections/bootstrap_table.rb +147 -0
- data/lib/hecks/projections/diagrams.rb +315 -79
- data/lib/hecks/projections/glossary/html.rb +91 -3
- data/lib/hecks/projections/glossary/markdown.rb +50 -3
- data/lib/hecks/projections/glossary/mermaid.rb +49 -1
- data/lib/hecks/projections/glossary/sections.rb +1 -1
- data/lib/hecks/projections/glossary/sentences.rb +110 -5
- data/lib/hecks/projections/glossary.rb +173 -31
- data/lib/hecks/projections/ir.rb +1 -1
- data/lib/hecks/projections/model/deviations.rb +62 -17
- data/lib/hecks/projections/model.rb +75 -21
- data/lib/hecks/projections/oidc.rb +45 -8
- data/lib/hecks/projections/parser_table.rb +53 -12
- data/lib/hecks/projections/reference.rb +15 -3
- data/lib/hecks/projections/rust_vocabulary.rb +646 -0
- data/lib/hecks/projections/shape.rb +10 -2
- data/lib/hecks/projections/statements.rb +69 -22
- data/lib/hecks/projections/vocabulary.rb +26 -9
- data/lib/hecks/projections.rb +5 -3
- data/lib/hecks/projector/cli_projector.rb +245 -37
- data/lib/hecks/projector/docs_projector.rb +154 -28
- data/lib/hecks/projector/exporter.rb +104 -29
- data/lib/hecks/projector/ir_projector.rb +7 -1
- data/lib/hecks/projector/narrate_projector.rb +144 -23
- data/lib/hecks/projector/target.rb +42 -18
- data/lib/hecks/projector.rb +86 -18
- data/lib/hecks/query_ir.rb +94 -47
- data/lib/hecks/query_specification/common/comparators.rb +19 -3
- data/lib/hecks/query_specification/common/comparison.rb +109 -27
- data/lib/hecks/query_specification/common/dsl.rb +65 -9
- data/lib/hecks/query_specification/common/null_policy.rb +61 -15
- data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
- data/lib/hecks/query_specification/common/options.rb +25 -0
- data/lib/hecks/query_specification/field_path.rb +69 -15
- data/lib/hecks/query_specification/hop_path.rb +57 -20
- data/lib/hecks/query_specification/read_model/specification.rb +4 -0
- data/lib/hecks/rendering.rb +9 -3
- data/lib/hecks/router/namespace_installer.rb +16 -3
- data/lib/hecks/router.rb +56 -1
- data/lib/hecks/runtime/aggregate_lock.rb +20 -11
- data/lib/hecks/runtime/boot_gates.rb +21 -3
- data/lib/hecks/runtime/caller.rb +40 -8
- data/lib/hecks/runtime/capability_graph.rb +13 -2
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +40 -41
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +45 -67
- data/lib/hecks/runtime/command_interpreter.rb +195 -113
- data/lib/hecks/runtime/command_rules/admissibility.rb +231 -80
- data/lib/hecks/runtime/command_rules/arithmetic.rb +154 -96
- data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
- data/lib/hecks/runtime/command_rules/emission.rb +18 -1
- data/lib/hecks/runtime/command_rules/references.rb +145 -55
- data/lib/hecks/runtime/command_rules.rb +3 -0
- data/lib/hecks/runtime/dependency_planning.rb +56 -11
- data/lib/hecks/runtime/dispatcher.rb +263 -92
- data/lib/hecks/runtime/entity_element.rb +304 -67
- data/lib/hecks/runtime/entity_interpreter.rb +149 -88
- data/lib/hecks/runtime/errors.rb +37 -23
- data/lib/hecks/runtime/event.rb +15 -10
- data/lib/hecks/runtime/identity.rb +91 -23
- data/lib/hecks/runtime/instance.rb +103 -18
- data/lib/hecks/runtime/interpreting.rb +24 -16
- data/lib/hecks/runtime/invocation.rb +358 -0
- data/lib/hecks/runtime/loader.rb +105 -19
- data/lib/hecks/runtime/outbox.rb +164 -26
- data/lib/hecks/runtime/policy_interpreter.rb +73 -60
- data/lib/hecks/runtime/port_operation_interpreter.rb +42 -19
- data/lib/hecks/runtime/query_interpreter.rb +93 -74
- data/lib/hecks/runtime/reaction_invocation.rb +73 -28
- data/lib/hecks/runtime/read_model_interpreter.rb +60 -44
- data/lib/hecks/runtime/rebuild_sweep.rb +32 -4
- data/lib/hecks/runtime/reference_hop.rb +48 -6
- data/lib/hecks/runtime/refusal_wording.rb +142 -115
- data/lib/hecks/runtime/registry/saga_persistence.rb +32 -21
- data/lib/hecks/runtime/registry/verification.rb +153 -28
- data/lib/hecks/runtime/registry.rb +202 -32
- data/lib/hecks/runtime/remote_dispatcher.rb +125 -24
- data/lib/hecks/runtime/routing.rb +36 -154
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +25 -27
- data/lib/hecks/runtime/saga_interpreter.rb +90 -76
- data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
- data/lib/hecks/runtime/tenant_check.rb +33 -13
- data/lib/hecks/runtime/tenant_scope.rb +23 -5
- data/lib/hecks/runtime/value/admission.rb +75 -30
- data/lib/hecks/runtime/value/coercion.rb +555 -142
- data/lib/hecks/runtime/value/entity_list_coercion.rb +132 -60
- data/lib/hecks/runtime/value.rb +71 -21
- data/lib/hecks/runtime.rb +39 -7
- data/lib/hecks/storehouse.rb +368 -72
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
- data/lib/hecks/version.rb +3 -3
- data/lib/hecks/vocabulary.rb +205 -4
- data/lib/hecks.rb +91 -11
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +19 -11
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +24 -12
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +38 -14
- metadata +28 -2
|
@@ -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,14 +41,17 @@ module Hecks
|
|
|
42
41
|
# two-writer case.
|
|
43
42
|
MAX_STALE_WRITE_RETRIES = 5
|
|
44
43
|
|
|
45
|
-
#
|
|
46
|
-
#
|
|
44
|
+
# Every cross-step local held in one place, now that the sequence is
|
|
45
|
+
# data-driven rather than `call`'s own literal method-call sequence —
|
|
47
46
|
# `result` and `transition`/`old_state` default to nil until the step
|
|
48
|
-
# that sets them runs, same as
|
|
47
|
+
# that sets them runs, the same as unset locals would be 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
|
|
|
52
|
+
# @param registry [Runtime::Registry] the booted registry this interpreter reads
|
|
53
|
+
# @param rules [Runtime::CommandRules] the shared rules engine (admissibility,
|
|
54
|
+
# references, arithmetic, authorization, emission) dispatch runs through
|
|
53
55
|
def initialize(registry, rules:)
|
|
54
56
|
@registry = registry
|
|
55
57
|
@rules = rules
|
|
@@ -60,21 +62,49 @@ module Hecks
|
|
|
60
62
|
# dispatch would (givens checked, mutations applied to `ctx.instance`
|
|
61
63
|
# in memory); `step_save`/`step_emit` are the only two that read this
|
|
62
64
|
# flag, each skipping its own real work — see their own comments.
|
|
63
|
-
#
|
|
65
|
+
# Retries the whole method body on `StaleWrite` — a fresh `ctx`, a
|
|
64
66
|
# fresh `step_hydrate` re-reading current state, so `enforce_givens`
|
|
65
67
|
# re-evaluates against reality rather than the snapshot that just
|
|
66
68
|
# went stale. See `MAX_STALE_WRITE_RETRIES`/`Runtime::StaleWrite`
|
|
67
69
|
# for why exhaustion is a pathological-contention signal, not the
|
|
68
70
|
# expected shape of a two-writer race.
|
|
69
|
-
|
|
71
|
+
#
|
|
72
|
+
# `invocation` — the `Runtime::Invocation` `Dispatcher` built for this
|
|
73
|
+
# call. `ctx.args` is `invocation.to_args` (the same Hash routing
|
|
74
|
+
# always handed this method), `ctx.route` its `target`.
|
|
75
|
+
#
|
|
76
|
+
# @param domain [String, Symbol] the domain `aggregate` belongs to
|
|
77
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate the command acts on
|
|
78
|
+
# @param command [Class] the command class (`Bluebook::Command` subclass) to dispatch
|
|
79
|
+
# @param invocation [Runtime::Invocation] the invocation `Dispatcher` built for
|
|
80
|
+
# this call
|
|
81
|
+
# @param correlation [Hash{Symbol => Object}, nil] correlation head => value,
|
|
82
|
+
# stamped on every emitted event when a saga leg causes this dispatch; nil
|
|
83
|
+
# otherwise
|
|
84
|
+
# @param dry_run [Boolean] whether to run every step through validation without
|
|
85
|
+
# saving, emitting or enqueueing
|
|
86
|
+
# @return [Array(Runtime::Instance, Array<Runtime::Event>,
|
|
87
|
+
# Runtime::DependencyPlanning::Plan, Ports::Persistence::Execution,
|
|
88
|
+
# Array<Runtime::Outbox::Row>)] the settled instance, emitted events,
|
|
89
|
+
# execution plan, persistence outcome and outbox rows — the last
|
|
90
|
+
# three nil on a dry run, which skips save/emit/outbox
|
|
91
|
+
# @raise [StandardError] any class in `Runtime::DOMAIN_REFUSALS` when a
|
|
92
|
+
# given/ensures/invariant/authorization/admissibility rule refuses
|
|
93
|
+
# @raise [Runtime::StaleWrite] if concurrent writers beat this one through every
|
|
94
|
+
# retry (`MAX_STALE_WRITE_RETRIES`)
|
|
95
|
+
# @raise [Runtime::WiringError] if the aggregate's repository cannot be resolved
|
|
96
|
+
def call(domain, aggregate, command, invocation, correlation = nil, dry_run: false)
|
|
97
|
+
args = invocation.to_args
|
|
98
|
+
route = invocation.target
|
|
70
99
|
attempt = 0
|
|
71
100
|
begin
|
|
72
101
|
ctx = Context.new(domain, aggregate, command, args)
|
|
102
|
+
ctx.invocation = invocation
|
|
73
103
|
ctx.correlation = correlation
|
|
74
104
|
ctx.route = route
|
|
75
105
|
ctx.dry_run = dry_run
|
|
76
106
|
ctx.plan = DependencyPlanning::Analyzer.call(aggregate: aggregate, command: command)
|
|
77
|
-
#
|
|
107
|
+
# **Resolved here, once, before hydration** — `Registry#repository`
|
|
78
108
|
# memoizes, so this and `step_hydrate`'s own read of `ctx.repository`
|
|
79
109
|
# (no second fetch there any more) always name the same instance;
|
|
80
110
|
# the isolation decision below (lock vs. CAS+retry) needs the
|
|
@@ -92,6 +122,16 @@ module Hecks
|
|
|
92
122
|
|
|
93
123
|
private
|
|
94
124
|
|
|
125
|
+
# **A no-op, and untraced** — Vocabulary::AggregateDispatchOrder's
|
|
126
|
+
# decode_arguments. Routing (`Runtime::Routing`) has already handed
|
|
127
|
+
# `call` a decoded argument hash by the time any step runs, so there is
|
|
128
|
+
# nothing left to decode here yet; like a conditional step that does
|
|
129
|
+
# not fire, it records nothing in `trace`. Declared so the Rust
|
|
130
|
+
# kernel's generated step enum carries the step its generated decoder
|
|
131
|
+
# will move into (roadmap D2), and so a typed Invocation (I2) has a
|
|
132
|
+
# step to be consumed at.
|
|
133
|
+
def step_decode_arguments(_ctx); end
|
|
134
|
+
|
|
95
135
|
def step_refuse_unknown_arguments(ctx)
|
|
96
136
|
step(:refuse_unknown_arguments) { refuse_unknown_arguments(ctx.domain, ctx.aggregate, ctx.command, ctx.args) }
|
|
97
137
|
end
|
|
@@ -131,14 +171,14 @@ module Hecks
|
|
|
131
171
|
|
|
132
172
|
def step_enforce_givens(ctx)
|
|
133
173
|
step(:enforce_givens) do
|
|
134
|
-
#
|
|
174
|
+
# Structural, before the declared givens — the same ordering
|
|
135
175
|
# NotFound/AlreadyExists already get at hydration: "does the
|
|
136
176
|
# fact this command's corrects names even exist" is not a
|
|
137
177
|
# domain rule an author wrote, it is a precondition for the
|
|
138
178
|
# domain rules to mean anything at all. Also locates the
|
|
139
179
|
# correction target itself, if `as:` named one — carried on
|
|
140
180
|
# `ctx` so `step_enforce_ensures` (the settled-record half)
|
|
141
|
-
# can bind the
|
|
181
|
+
# can bind the same name too, not just this pre-mutation half.
|
|
142
182
|
ctx.correction_bindings = @rules.enforce_correction_target(ctx.instance, ctx.aggregate, ctx.command, domain: ctx.domain)
|
|
143
183
|
@rules.enforce_givens(ctx.instance, ctx.command, ctx.args, domain: ctx.domain,
|
|
144
184
|
declaring: ctx.aggregate, parent: ctx.instance, correction: ctx.correction_bindings)
|
|
@@ -157,12 +197,12 @@ module Hecks
|
|
|
157
197
|
|
|
158
198
|
def step_apply_mutations(ctx)
|
|
159
199
|
# The state as the givens saw it — what `old` names inside an
|
|
160
|
-
# ensures. A shallow dup suffices: mutations
|
|
200
|
+
# ensures. A shallow dup suffices: mutations replace fields (set,
|
|
161
201
|
# arithmetic via Value#with, append builds a new array), never
|
|
162
202
|
# edit a held value in place.
|
|
163
203
|
ctx.old_state = ctx.instance.state.dup unless ctx.command.ensures.empty?
|
|
164
204
|
step(:apply_mutations) do
|
|
165
|
-
#
|
|
205
|
+
# One update set over the pre-dispatch state (C4.2, docs/
|
|
166
206
|
# semantics/bluebook-semantics.md): every effect's sources read
|
|
167
207
|
# `pre` — the state as it was before this command — and its
|
|
168
208
|
# target is written to the candidate; declaration order carries
|
|
@@ -180,19 +220,19 @@ module Hecks
|
|
|
180
220
|
step(:advance_lifecycle) { ctx.instance[ctx.aggregate.lifecycle.field] = ctx.transition.target }
|
|
181
221
|
end
|
|
182
222
|
|
|
183
|
-
#
|
|
223
|
+
# The synchronous cousin of a policy's own `trigger` — see
|
|
184
224
|
# `CommandBuilder#delegates_to`'s own comment for the full reasoning.
|
|
185
|
-
# Runs
|
|
225
|
+
# Runs after this command's own mutations/lifecycle (so a delegating
|
|
186
226
|
# command could in principle still guard with its own `given`s first,
|
|
187
|
-
# though the real use in `domain/chess` declares none) and
|
|
227
|
+
# though the real use in `domain/chess` declares none) and before
|
|
188
228
|
# `enforce_ensures`/`enforce_invariants`/`save`, so a refusal here
|
|
189
|
-
# raises a real, unrescued exception and
|
|
229
|
+
# raises a real, unrescued exception and nothing from either side —
|
|
190
230
|
# this command's own state, the target element's — has been saved
|
|
191
|
-
# yet. `ctx.instance` is the
|
|
231
|
+
# yet. `ctx.instance` is the same in-memory record `step_hydrate`
|
|
192
232
|
# loaded and `step_save` will persist; `EntityElement.locate_chain`
|
|
193
233
|
# 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
|
|
234
|
+
# `step_locate_element`/`step_apply_mutations` mutate their own
|
|
235
|
+
# freshly-loaded copy — the only difference is which already-in-
|
|
196
236
|
# memory record gets handed in.
|
|
197
237
|
#
|
|
198
238
|
# Reimplements the entity command pipeline's own order (givens,
|
|
@@ -219,8 +259,8 @@ module Hecks
|
|
|
219
259
|
"#{entity_name}.#{command_name}, which " \
|
|
220
260
|
"#{entity_name} declares no such command")
|
|
221
261
|
|
|
222
|
-
# `with:`
|
|
223
|
-
# of
|
|
262
|
+
# `with:` remaps, it does not enumerate — starting from a copy
|
|
263
|
+
# of this command's own already-resolved args (`ctx.args`) and
|
|
224
264
|
# overlaying the explicit mapping on top means ambient context
|
|
225
265
|
# the caller never had to think about (the aggregate's own
|
|
226
266
|
# identity, addressed the ordinary way to reach `MoveKnight` at
|
|
@@ -228,7 +268,7 @@ module Hecks
|
|
|
228
268
|
# always would for a caller dispatching the entity command
|
|
229
269
|
# directly. Confirmed necessary, not a defensive guess: a real
|
|
230
270
|
# downstream domain's own AdvancePly-on-Moved policy silently
|
|
231
|
-
# failed to re-locate its
|
|
271
|
+
# failed to re-locate its own aggregate (`reaction_log`: "no
|
|
232
272
|
# Game with label.value ..."), because the emitted event's
|
|
233
273
|
# payload — built from `target_args` alone — never carried
|
|
234
274
|
# `label` at all when `with:` named only `id`/`to`.
|
|
@@ -252,14 +292,14 @@ module Hecks
|
|
|
252
292
|
settled = Instance.new(aggregate: entity, id: view.id, state: element)
|
|
253
293
|
@rules.enforce_ensures(settled, target_command, target_args, old: old_element, domain: ctx.domain, parent: ctx.instance)
|
|
254
294
|
|
|
255
|
-
#
|
|
295
|
+
# Not emitted here — C7.2 (docs/semantics/bluebook-semantics.md):
|
|
256
296
|
# a refused command records nothing, and the parent's own
|
|
257
297
|
# `ensures`/`enforce_invariants`/`save` steps still run after
|
|
258
298
|
# this one. The target's emission is parked and performed by
|
|
259
299
|
# `step_emit`, after the parent committed — where every other
|
|
260
|
-
# command's events are emitted too. (
|
|
261
|
-
# leg's events
|
|
262
|
-
# the parent could refuse.)
|
|
300
|
+
# command's events are emitted too. (Without parking it here, the
|
|
301
|
+
# entity leg's events would be on the event log and in the adapter
|
|
302
|
+
# before the parent could refuse.)
|
|
263
303
|
ctx.pending_delegation = [target_command, target_args]
|
|
264
304
|
end
|
|
265
305
|
end
|
|
@@ -275,29 +315,70 @@ module Hecks
|
|
|
275
315
|
step(:enforce_invariants) { @rules.enforce_invariants(ctx.instance, ctx.aggregate, domain: ctx.domain) }
|
|
276
316
|
end
|
|
277
317
|
|
|
278
|
-
# `dry_run:` skips this — see Dispatcher#
|
|
279
|
-
#
|
|
280
|
-
#
|
|
281
|
-
#
|
|
282
|
-
#
|
|
318
|
+
# `dry_run:` skips the write half of this step — see Dispatcher#
|
|
319
|
+
# dry_run?'s own comment — but not `resolve_state_references`.
|
|
320
|
+
# BUG#127: that check is validation, not persistence — it asks
|
|
321
|
+
# whether the settled in-memory state names a reference target that
|
|
322
|
+
# actually exists, the same question a real dispatch answers before
|
|
323
|
+
# it ever writes anything. Skipping it under `dry_run:` made
|
|
324
|
+
# `dry_run?` disagree with what a real dispatch immediately after it
|
|
325
|
+
# would do: it answered `true` ("would succeed") for a command whose
|
|
326
|
+
# real dispatch refuses with NotFound. Only the persist/raise-on-
|
|
327
|
+
# conflict tail is genuinely persistence-only and stays behind the
|
|
328
|
+
# early return.
|
|
329
|
+
#
|
|
330
|
+
# BUG#132 — a second validation-not-persistence check hid in that
|
|
331
|
+
# same persist/raise-on-conflict tail: `persist_instance`'s own
|
|
332
|
+
# ATOMIC_PUT branch (below) is where `hydrate_complete_state`'s
|
|
333
|
+
# comment says a `creates?` command's duplicate check is
|
|
334
|
+
# deliberately deferred to, for a strategy this fast — a read
|
|
335
|
+
# (`repository.find`) any earlier, before the real write, would be
|
|
336
|
+
# exactly the extra read `insert_only:` exists to avoid paying on
|
|
337
|
+
# every real dispatch. But that means the check never ran at all under
|
|
338
|
+
# `dry_run:` — not eagerly (hydration skips it for this exact
|
|
339
|
+
# strategy, on purpose) and not deferred (this whole branch returns
|
|
340
|
+
# first). `Roster::Roster.Open` against an already-open name is
|
|
341
|
+
# this shape: `dry_run?` answered `true` for a real dispatch
|
|
342
|
+
# immediately after it that refuses `AlreadyExists`. Under
|
|
343
|
+
# `dry_run:` there is no write to race, so paying for that one read
|
|
344
|
+
# is safe here — and only here.
|
|
283
345
|
def step_save(ctx)
|
|
284
|
-
|
|
346
|
+
step(:save) { @rules.resolve_state_references(ctx.domain, ctx.aggregate, ctx.instance.state) }
|
|
347
|
+
|
|
348
|
+
if ctx.dry_run
|
|
349
|
+
step(:save) { check_dry_run_creates_duplicate(ctx) }
|
|
350
|
+
return
|
|
351
|
+
end
|
|
285
352
|
|
|
286
353
|
step(:save) do
|
|
287
|
-
@rules.resolve_state_references(ctx.domain, ctx.aggregate, ctx.instance.state)
|
|
288
354
|
seed_projected_fields(ctx)
|
|
289
355
|
ctx.persistence_outcome = persist_instance(ctx)
|
|
290
356
|
raise_for_persistence_outcome!(ctx)
|
|
291
357
|
end
|
|
292
358
|
end
|
|
293
359
|
|
|
360
|
+
# See `step_save`'s own BUG#132 comment: the only path, real or dry,
|
|
361
|
+
# able to catch a `creates?` command reusing an already-occupied
|
|
362
|
+
# identity when `strategy` is `ATOMIC_PUT` — `hydrate_complete_
|
|
363
|
+
# state` deliberately skips this exact case (its own comment), and
|
|
364
|
+
# a dry run never reaches `persist_instance`'s matching branch.
|
|
365
|
+
def check_dry_run_creates_duplicate(ctx)
|
|
366
|
+
return unless ctx.strategy == DependencyPlanning::ATOMIC_PUT && ctx.command.creates?
|
|
367
|
+
return unless ctx.repository.find(ctx.instance.id)
|
|
368
|
+
|
|
369
|
+
raise(AlreadyExists, RefusalWording.render_site("AlreadyExists", "creating_duplicate",
|
|
370
|
+
command: ctx.command.hecks_name, aggregate: ctx.aggregate.hecks_name,
|
|
371
|
+
identity: identity_reading(ctx.aggregate),
|
|
372
|
+
offered: Rendering.describe(ctx.instance.id)))
|
|
373
|
+
end
|
|
374
|
+
|
|
294
375
|
def persist_instance(ctx)
|
|
295
376
|
if ctx.strategy == DependencyPlanning::ATOMIC_PUT
|
|
296
|
-
# A
|
|
377
|
+
# A second creation is not a fresh one — see
|
|
297
378
|
# hydrate_complete_state's own comment; the
|
|
298
379
|
# same refusal, on the same terms, for the
|
|
299
380
|
# complete-state path. `insert_only:` asks the
|
|
300
|
-
#
|
|
381
|
+
# adapter to decide and refuse atomically
|
|
301
382
|
# (never writing a `creates?` command over an
|
|
302
383
|
# identity that already names a record) rather
|
|
303
384
|
# than this interpreter reading the record
|
|
@@ -316,12 +397,12 @@ module Hecks
|
|
|
316
397
|
|
|
317
398
|
def raise_for_persistence_outcome!(ctx)
|
|
318
399
|
if ctx.persistence_outcome.status == :conflicted
|
|
319
|
-
raise(AlreadyExists, RefusalWording.
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
400
|
+
raise(AlreadyExists, RefusalWording.render_site("AlreadyExists", "creating_duplicate",
|
|
401
|
+
command: ctx.command.hecks_name, aggregate: ctx.aggregate.hecks_name,
|
|
402
|
+
identity: identity_reading(ctx.aggregate),
|
|
403
|
+
offered: Rendering.describe(ctx.instance.id)))
|
|
323
404
|
elsif ctx.persistence_outcome.status == :stale
|
|
324
|
-
#
|
|
405
|
+
# Not a `RefusalWording.render` call — this is not a declared
|
|
325
406
|
# vocabulary refusal, just a plain, informative message. See
|
|
326
407
|
# `Runtime::StaleWrite`'s own comment: caught by `#call`'s
|
|
327
408
|
# retry loop, re-raised only once retries are exhausted.
|
|
@@ -332,26 +413,26 @@ module Hecks
|
|
|
332
413
|
end
|
|
333
414
|
end
|
|
334
415
|
|
|
335
|
-
#
|
|
416
|
+
# The one-time, synchronous half of `projects` (S12, ADR 0025) —
|
|
336
417
|
# `RebuildSweep` (`runtime/rebuild_sweep.rb`) is deliberately the
|
|
337
|
-
#
|
|
338
|
-
# target that changes
|
|
418
|
+
# only thing that keeps a projected field current against a
|
|
419
|
+
# target that changes after this record was written — no reactive
|
|
339
420
|
# `Policy#for_each` keeping it live in real time, that stays
|
|
340
421
|
# deferred, same as that file's own header explains. But without
|
|
341
|
-
#
|
|
342
|
-
#
|
|
422
|
+
# some synchronous population, a projected field never gets an
|
|
423
|
+
# initial value at all until an operator remembers to run a
|
|
343
424
|
# sweep by hand — every acting command reading it (`given
|
|
344
425
|
# ("customer is active") { customer_status == "active" }`, say)
|
|
345
426
|
# would refuse a freshly created, genuinely active record for no
|
|
346
427
|
# real reason, which is not the eventual-consistency tradeoff the
|
|
347
428
|
# ADR accepts, just a bug. So: every time a record with `projects`
|
|
348
429
|
# fields is about to save — creating or acting, either can be the
|
|
349
|
-
# first time a referenced record resolves — read each one
|
|
430
|
+
# first time a referenced record resolves — read each one once,
|
|
350
431
|
# here, using the exact same `RebuildSweep.remote_value` a sweep
|
|
351
432
|
# itself would compute. This is still eventually consistent in
|
|
352
|
-
# the sense the ADR means: a change on the
|
|
433
|
+
# the sense the ADR means: a change on the target side after this
|
|
353
434
|
# save still needs a sweep to reach here. It is only ever
|
|
354
|
-
#
|
|
435
|
+
# synchronous with this record's own write, never a live read
|
|
355
436
|
# triggered by a `given`/`ensures`/`invariant` mid-dispatch — the
|
|
356
437
|
# boundary rule those enforce holds exactly as before.
|
|
357
438
|
def seed_projected_fields(ctx)
|
|
@@ -365,10 +446,10 @@ module Hecks
|
|
|
365
446
|
end
|
|
366
447
|
end
|
|
367
448
|
|
|
368
|
-
# A
|
|
449
|
+
# A delegating command emits nothing of its own (`CommandBuilder#build`'s
|
|
369
450
|
# own guard refuses declaring `emits` alongside `delegates_to`) — its
|
|
370
|
-
# result
|
|
371
|
-
# `step_delegate_to_entity` and emitted
|
|
451
|
+
# result is the target entity command's own `emits`, parked by
|
|
452
|
+
# `step_delegate_to_entity` and emitted here, after save (C7.2), not
|
|
372
453
|
# a second, empty call into `@rules.emit` for a command with no
|
|
373
454
|
# announced events at all.
|
|
374
455
|
#
|
|
@@ -384,8 +465,9 @@ module Hecks
|
|
|
384
465
|
# `:delegate` mutation.
|
|
385
466
|
if ctx.pending_delegation
|
|
386
467
|
target_command, target_args = ctx.pending_delegation
|
|
387
|
-
# The same dispatch, so the same correlation —
|
|
388
|
-
#
|
|
468
|
+
# The same dispatch, so the same correlation — without
|
|
469
|
+
# threading `ctx.correlation` through, a saga-driven door's
|
|
470
|
+
# events would lose their stamp here.
|
|
389
471
|
next @rules.emit(target_command, ctx.domain, ctx.aggregate, ctx.instance, target_args, ctx.repository,
|
|
390
472
|
ctx.correlation)
|
|
391
473
|
end
|
|
@@ -397,53 +479,53 @@ module Hecks
|
|
|
397
479
|
def hydrate_existing(repository, aggregate, command, args, route = nil)
|
|
398
480
|
if route
|
|
399
481
|
found = repository.find(route.aggregate) ||
|
|
400
|
-
raise(NotFound, RefusalWording.
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
482
|
+
raise(NotFound, RefusalWording.render_site("NotFound", "record_missing",
|
|
483
|
+
aggregate: aggregate.hecks_name,
|
|
484
|
+
identity: identity_reading(aggregate),
|
|
485
|
+
offered: Rendering.describe(route.aggregate)))
|
|
404
486
|
return found.dup
|
|
405
487
|
end
|
|
406
488
|
|
|
407
489
|
id = identity_of(aggregate, args) ||
|
|
408
490
|
identity_from(aggregate, args, :id) ||
|
|
409
491
|
identity_from(aggregate, args, reference_key(command)) ||
|
|
410
|
-
raise(NotFound, RefusalWording.
|
|
411
|
-
|
|
412
|
-
|
|
492
|
+
raise(NotFound, RefusalWording.render_site("NotFound", "acting_no_identity",
|
|
493
|
+
command: command.hecks_name, aggregate: aggregate.hecks_name,
|
|
494
|
+
identity: identity_reading(aggregate)))
|
|
413
495
|
found = repository.find(id) ||
|
|
414
|
-
raise(NotFound, RefusalWording.
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
496
|
+
raise(NotFound, RefusalWording.render_site("NotFound", "record_missing",
|
|
497
|
+
aggregate: aggregate.hecks_name,
|
|
498
|
+
identity: identity_reading(aggregate),
|
|
499
|
+
offered: Rendering.describe(id)))
|
|
418
500
|
found.dup
|
|
419
501
|
end
|
|
420
502
|
|
|
421
|
-
#
|
|
503
|
+
# Wave 8 (equivalence-gap plan, item 1.6) — not done, and this
|
|
422
504
|
# comment now says precisely how far it got, corrected from an
|
|
423
|
-
#
|
|
505
|
+
# earlier version of itself that briefly (same PR, never released)
|
|
424
506
|
# claimed the inventory was empty and deleted this method outright.
|
|
425
507
|
# A full corpus audit (`DependencyPlanning::Analyzer.call` against
|
|
426
|
-
# every `creates?`-true command in all 5
|
|
508
|
+
# every `creates?`-true command in all 5 example domains — banking,
|
|
427
509
|
# pizzas, chess, compliance, roster) found and fixed 4 real
|
|
428
510
|
# authoring bugs (`Statement.Generate`, `CreatePizza`, `Chess::
|
|
429
511
|
# Game.Start`, `Roster.Open` — each declared an attribute and
|
|
430
512
|
# never `sets` it, so the field silently stayed nil on every
|
|
431
513
|
# created record regardless of what a caller sent), plus a real
|
|
432
|
-
# `DependencyPlanning::Analyzer` bug for
|
|
514
|
+
# `DependencyPlanning::Analyzer` bug for entity-owned commands
|
|
433
515
|
# (`root_aggregate:`, that class's own header) that surfaced one
|
|
434
516
|
# more live corpus bug of its own (`payment_cards.bluebook`'s own
|
|
435
517
|
# `Withdrawal.Dispute`, which never actually checked whether the
|
|
436
518
|
# card was retired) — all real, all kept.
|
|
437
519
|
#
|
|
438
|
-
#
|
|
520
|
+
# Deleting this method on that basis turned out to be wrong,
|
|
439
521
|
# caught by running the full suite rather than trusting the
|
|
440
522
|
# audit's own scope: the 5 example domains are nowhere near the
|
|
441
|
-
#
|
|
523
|
+
# whole inventory of `creates?`-true commands this fallback
|
|
442
524
|
# actually carries. Dozens of separate, purpose-built spec
|
|
443
525
|
# fixtures across the suite (`spec/fixtures/*.bluebook`, and
|
|
444
526
|
# inline `Hecks.bluebook` blocks declared directly inside
|
|
445
527
|
# individual spec files — governance, mutation ops, ports,
|
|
446
|
-
# routing, tenant isolation, sagas, and more) declare their
|
|
528
|
+
# routing, tenant isolation, sagas, and more) declare their own
|
|
447
529
|
# small `creates?`-true commands the same incomplete way, and
|
|
448
530
|
# rely on this exact fallback to create anything at all. Deleting
|
|
449
531
|
# it produced 218 failures across specs with nothing to do with
|
|
@@ -462,7 +544,7 @@ module Hecks
|
|
|
462
544
|
# acquired explicit effects, same as it always was — deliberately
|
|
463
545
|
# isolated from the normal routing and planning path so
|
|
464
546
|
# `reference_to` no longer chooses how a migrated command hydrates
|
|
465
|
-
# or persists. A real, future Wave 8 removes this once
|
|
547
|
+
# or persists. A real, future Wave 8 removes this once that wider
|
|
466
548
|
# inventory is empty, not before.
|
|
467
549
|
#
|
|
468
550
|
# `ctx.plan.complete_state?` already claimed every command whose
|
|
@@ -471,7 +553,7 @@ module Hecks
|
|
|
471
553
|
# incomplete — an un-migrated command still routes here on
|
|
472
554
|
# `creates?` alone, the same as the old `hydrate` did, regardless
|
|
473
555
|
# of whether it happens to have any mutations (`write_set`).
|
|
474
|
-
# Requiring an
|
|
556
|
+
# Requiring an empty write_set here refused every un-migrated
|
|
475
557
|
# creating command that sets even one field.
|
|
476
558
|
def legacy_implicit_creation?(ctx)
|
|
477
559
|
ctx.route.nil? && ctx.command.creates?
|
|
@@ -479,14 +561,14 @@ module Hecks
|
|
|
479
561
|
|
|
480
562
|
def hydrate_legacy_creation(repository, aggregate, command, args)
|
|
481
563
|
id = identity_of(aggregate, args) ||
|
|
482
|
-
raise(NotFound, RefusalWording.
|
|
483
|
-
|
|
484
|
-
|
|
564
|
+
raise(NotFound, RefusalWording.render_site("NotFound", "creating_no_identity",
|
|
565
|
+
command: command.hecks_name, aggregate: aggregate.hecks_name,
|
|
566
|
+
identity: identity_reading(aggregate)))
|
|
485
567
|
if repository.find(id)
|
|
486
|
-
raise(AlreadyExists, RefusalWording.
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
568
|
+
raise(AlreadyExists, RefusalWording.render_site("AlreadyExists", "creating_duplicate",
|
|
569
|
+
command: command.hecks_name, aggregate: aggregate.hecks_name,
|
|
570
|
+
identity: identity_reading(aggregate),
|
|
571
|
+
offered: Rendering.describe(id)))
|
|
490
572
|
end
|
|
491
573
|
|
|
492
574
|
Instance.new(aggregate: aggregate, id: id, args: args)
|
|
@@ -500,29 +582,29 @@ module Hecks
|
|
|
500
582
|
end
|
|
501
583
|
|
|
502
584
|
id = route&.aggregate || derived ||
|
|
503
|
-
raise(NotFound, RefusalWording.
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
585
|
+
raise(NotFound, RefusalWording.render_site("NotFound", "creating_no_identity",
|
|
586
|
+
command: command.hecks_name,
|
|
587
|
+
aggregate: aggregate.hecks_name,
|
|
588
|
+
identity: identity_reading(aggregate)))
|
|
507
589
|
|
|
508
|
-
# A
|
|
590
|
+
# A second creation is not a fresh one — `creates?` on an identity
|
|
509
591
|
# a record already exists under refuses (`AlreadyExists`) rather
|
|
510
592
|
# than silently overwriting it, the same refusal `hydrate_prior_
|
|
511
593
|
# or_initial`'s own body gives for its own complete-but-state-
|
|
512
|
-
# dependent case, below.
|
|
594
|
+
# dependent case, below. Only when `strategy` will not be ATOMIC_PUT:
|
|
513
595
|
# an atomic-put-capable adapter enforces this itself, atomically,
|
|
514
596
|
# via `insert_only:` in step_save (no read here, no race with the
|
|
515
597
|
# write) — but `strategy_for` already fell back to a plain `save`
|
|
516
598
|
# for an adapter with no atomic_put capability at all (Heki, today),
|
|
517
599
|
# and a plain `save` never refuses an overwrite on its own. Skipping
|
|
518
|
-
# this check for
|
|
600
|
+
# this check for that case silently dropped the refusal instead of
|
|
519
601
|
# deferring it — the very "no such call — no such check" gap Wave
|
|
520
602
|
# 8 exists to close everywhere else.
|
|
521
603
|
if command.creates? && strategy != DependencyPlanning::ATOMIC_PUT && repository.find(id)
|
|
522
|
-
raise(AlreadyExists, RefusalWording.
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
604
|
+
raise(AlreadyExists, RefusalWording.render_site("AlreadyExists", "creating_duplicate",
|
|
605
|
+
command: command.hecks_name, aggregate: aggregate.hecks_name,
|
|
606
|
+
identity: identity_reading(aggregate),
|
|
607
|
+
offered: Rendering.describe(id)))
|
|
526
608
|
end
|
|
527
609
|
|
|
528
610
|
Instance.new(aggregate: aggregate, id: id, args: args)
|
|
@@ -541,37 +623,37 @@ module Hecks
|
|
|
541
623
|
end
|
|
542
624
|
|
|
543
625
|
id = route&.aggregate || derived ||
|
|
544
|
-
raise(NotFound, RefusalWording.
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
626
|
+
raise(NotFound, RefusalWording.render_site("NotFound", "creating_no_identity",
|
|
627
|
+
command: command.hecks_name,
|
|
628
|
+
aggregate: aggregate.hecks_name,
|
|
629
|
+
identity: identity_reading(aggregate)))
|
|
548
630
|
found = repository.find(id)
|
|
549
631
|
|
|
550
|
-
# A
|
|
632
|
+
# A second creation is not a fresh one — see hydrate_complete_
|
|
551
633
|
# state's own comment; the same refusal, on the same terms, for
|
|
552
634
|
# a complete-but-state-dependent command (one with a `given`
|
|
553
635
|
# reading its own prior state, which is what routes here instead
|
|
554
636
|
# of hydrate_complete_state). Gated on `command.creates?`: a
|
|
555
|
-
#
|
|
556
|
-
# "act on whatever this identity already holds" (`found`
|
|
637
|
+
# non-creating command's own complete payload legitimately means
|
|
638
|
+
# "act on whatever this identity already holds" (`found` is the
|
|
557
639
|
# prior state this branch exists to supply), so only a genuine
|
|
558
640
|
# creation reusing an already-occupied identity is a duplicate.
|
|
559
641
|
# `SafeDepositBox.Rent` is exactly this shape — `given("box is
|
|
560
|
-
# vacant")` makes it state-dependent, so
|
|
561
|
-
# silently hydrate the existing box as "prior
|
|
562
|
-
# for the wrong reason (not vacant) instead of
|
|
563
|
-
# (already exists).
|
|
642
|
+
# vacant")` makes it state-dependent, so without this check a
|
|
643
|
+
# second Rent would silently hydrate the existing box as "prior
|
|
644
|
+
# state" and refuse for the wrong reason (not vacant) instead of
|
|
645
|
+
# the right one (already exists).
|
|
564
646
|
if found && command.creates?
|
|
565
|
-
raise(AlreadyExists, RefusalWording.
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
647
|
+
raise(AlreadyExists, RefusalWording.render_site("AlreadyExists", "creating_duplicate",
|
|
648
|
+
command: command.hecks_name, aggregate: aggregate.hecks_name,
|
|
649
|
+
identity: identity_reading(aggregate),
|
|
650
|
+
offered: Rendering.describe(id)))
|
|
569
651
|
end
|
|
570
652
|
|
|
571
653
|
found ? found.dup : Instance.new(aggregate: aggregate, id: id, args: args)
|
|
572
654
|
end
|
|
573
655
|
|
|
574
|
-
#
|
|
656
|
+
# The join, the dig, and the reading — all shared with `EntityInterpreter`
|
|
575
657
|
# now, in `Runtime::Identity`, rather than kept as two copies that could
|
|
576
658
|
# only ever drift. See that module for the reasoning ; these three stay
|
|
577
659
|
# here, at the old names, purely so nothing below has to change.
|