hecks 1.4.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 +65 -0
- data/lib/hecks/adapters/driven/folder.rb +73 -0
- data/lib/hecks/adapters/driven/google_authentication.rb +25 -4
- data/lib/hecks/adapters/driven/governance_authorization.rb +57 -6
- data/lib/hecks/adapters/driven/heki/journal.rb +8 -0
- data/lib/hecks/adapters/driven/heki/saga_store.rb +53 -7
- data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
- data/lib/hecks/adapters/driven/heki.rb +93 -2
- data/lib/hecks/adapters/driven/identity_registry.rb +10 -0
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +22 -0
- 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 +35 -7
- data/lib/hecks/adapters/driven/lambda.rb +43 -2
- data/lib/hecks/adapters/driven/local_storage.rb +67 -1
- data/lib/hecks/adapters/driven/memory.rb +13 -13
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +20 -0
- data/lib/hecks/adapters/driven/prism.rb +46 -7
- data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
- 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 +2 -0
- data/lib/hecks/adapters/driving/github_webhook.rb +14 -1
- data/lib/hecks/behaviors/dsl.rb +58 -0
- data/lib/hecks/behaviors/expectations.rb +168 -7
- data/lib/hecks/behaviors/ir.rb +11 -0
- data/lib/hecks/behaviors/rspec.rb +8 -0
- data/lib/hecks/behaviors/runner.rb +19 -0
- data/lib/hecks/behaviors.rb +8 -0
- data/lib/hecks/bluebook/aggregate.rb +30 -0
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +7 -0
- data/lib/hecks/bluebook/assembly/build.rb +25 -4
- data/lib/hecks/bluebook/assembly/contract.rb +66 -14
- data/lib/hecks/bluebook/assembly/contracts.rb +24 -19
- data/lib/hecks/bluebook/assembly/marks.rb +143 -14
- data/lib/hecks/bluebook/assembly/specializer.rb +19 -0
- data/lib/hecks/bluebook/assembly.rb +25 -7
- data/lib/hecks/bluebook/attribute.rb +17 -3
- data/lib/hecks/bluebook/behaviour/aggregate.rb +32 -0
- data/lib/hecks/bluebook/behaviour/attribute.rb +13 -0
- data/lib/hecks/bluebook/behaviour/chapter.rb +55 -2
- data/lib/hecks/bluebook/behaviour/command.rb +33 -3
- data/lib/hecks/bluebook/behaviour/domain_port.rb +20 -0
- data/lib/hecks/bluebook/behaviour/entity.rb +12 -0
- data/lib/hecks/bluebook/behaviour/hexagon.rb +26 -0
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +23 -2
- data/lib/hecks/bluebook/behaviour/policy.rb +34 -9
- data/lib/hecks/bluebook/behaviour/process_manager.rb +32 -1
- data/lib/hecks/bluebook/behaviour/query.rb +5 -0
- data/lib/hecks/bluebook/behaviour/read_model.rb +21 -0
- data/lib/hecks/bluebook/behaviour/traits.rb +36 -0
- data/lib/hecks/bluebook/behaviour/value_object.rb +17 -5
- data/lib/hecks/bluebook/chapter.rb +23 -0
- data/lib/hecks/bluebook/command.rb +53 -8
- data/lib/hecks/bluebook/domain_port.rb +25 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +196 -37
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +79 -42
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +59 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +385 -53
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +135 -21
- data/lib/hecks/bluebook/dsl/command_builder.rb +189 -27
- data/lib/hecks/bluebook/dsl/entity_builder.rb +139 -9
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +41 -0
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +23 -6
- data/lib/hecks/bluebook/dsl/policy_builder.rb +60 -19
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +79 -15
- data/lib/hecks/bluebook/dsl/query_builder.rb +33 -4
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +102 -27
- data/lib/hecks/bluebook/dsl/rule_reference.rb +60 -8
- data/lib/hecks/bluebook/dsl/translation_builder.rb +146 -40
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +55 -7
- data/lib/hecks/bluebook/entity.rb +29 -0
- data/lib/hecks/bluebook/expression/ast_json.rb +115 -23
- data/lib/hecks/bluebook/expression/ast_reader.rb +29 -0
- data/lib/hecks/bluebook/expression/canonical_form.rb +48 -9
- data/lib/hecks/bluebook/expression/evaluator.rb +207 -29
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +36 -0
- data/lib/hecks/bluebook/expression/resolver.rb +320 -77
- data/lib/hecks/bluebook/hexagon.rb +34 -0
- data/lib/hecks/bluebook/lifecycle.rb +11 -0
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -0
- data/lib/hecks/bluebook/meta_validator/judge.rb +30 -26
- data/lib/hecks/bluebook/meta_validator/plan.rb +43 -8
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +1 -0
- data/lib/hecks/bluebook/meta_validator/readings.rb +157 -7
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -4
- data/lib/hecks/bluebook/meta_validator/shapes.rb +141 -0
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +149 -23
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +5 -4
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +1 -0
- data/lib/hecks/bluebook/meta_validator.rb +180 -84
- data/lib/hecks/bluebook/model_check.rb +268 -24
- data/lib/hecks/bluebook/pattern_subset.rb +23 -1
- data/lib/hecks/bluebook/process_manager.rb +13 -0
- 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 +44 -0
- data/lib/hecks/bluebook/query.rb +27 -0
- data/lib/hecks/bluebook/read_model.rb +21 -1
- data/lib/hecks/bluebook/reference.rb +21 -8
- data/lib/hecks/bluebook/smoke_test.rb +29 -6
- data/lib/hecks/bluebook/synthesizer.rb +34 -0
- data/lib/hecks/bluebook/translation.rb +30 -1
- data/lib/hecks/bluebook/value_object.rb +23 -5
- data/lib/hecks/bluebook.rb +3 -4
- data/lib/hecks/codemod.rb +107 -20
- data/lib/hecks/construct.rb +15 -1
- data/lib/hecks/corpus.rb +146 -25
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
- data/lib/hecks/doc/reference.rb +185 -16
- data/lib/hecks/embryonaut_bluebook.rb +32 -9
- data/lib/hecks/facade/handle.rb +76 -3
- data/lib/hecks/facade/surface/aggregate_door.rb +8 -0
- data/lib/hecks/forms/field_shape.rb +3 -0
- data/lib/hecks/forms/page.rb +14 -0
- data/lib/hecks/forms/port_argument.rb +12 -0
- data/lib/hecks/forms/query_form_renderer.rb +63 -0
- data/lib/hecks/forms/record_renderer.rb +58 -0
- data/lib/hecks/forms/record_table.rb +27 -0
- data/lib/hecks/forms/reference_options.rb +24 -0
- data/lib/hecks/forms/value_object_shape.rb +10 -0
- data/lib/hecks/fqn.rb +58 -0
- data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
- data/lib/hecks/framework/oidc.json +15 -0
- data/lib/hecks/framework.rb +43 -20
- data/lib/hecks/freezer.rb +17 -1
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +159 -24
- data/lib/hecks/fuzzing/combination_miner.rb +59 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +109 -8
- data/lib/hecks/fuzzing/coverage_campaign.rb +56 -13
- data/lib/hecks/fuzzing/differential.rb +34 -0
- data/lib/hecks/fuzzing/domain_generator.rb +188 -11
- data/lib/hecks/fuzzing/era_boundary.rb +45 -15
- data/lib/hecks/fuzzing/form_census.rb +86 -0
- data/lib/hecks/fuzzing/generated_domain_check.rb +76 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +39 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +79 -22
- data/lib/hecks/fuzzing/nondeterministic.rb +13 -1
- data/lib/hecks/fuzzing/persistence_parity.rb +95 -3
- data/lib/hecks/fuzzing/properties/corrections.rb +25 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +158 -14
- data/lib/hecks/fuzzing/properties/guards.rb +44 -0
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +48 -0
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +18 -0
- data/lib/hecks/fuzzing/properties/outbox.rb +49 -11
- data/lib/hecks/fuzzing/properties/querying.rb +68 -14
- data/lib/hecks/fuzzing/properties.rb +24 -15
- data/lib/hecks/fuzzing/qa_settings.rb +12 -0
- data/lib/hecks/fuzzing/replay.rb +137 -29
- data/lib/hecks/fuzzing/rotation_priority.rb +41 -21
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +46 -20
- data/lib/hecks/fuzzing/self_consistency.rb +189 -40
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +12 -6
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +1 -1
- data/lib/hecks/fuzzing/sequence_generator.rb +47 -0
- data/lib/hecks/fuzzing/shrinker.rb +114 -2
- data/lib/hecks/fuzzing/structural_skips.rb +18 -4
- data/lib/hecks/fuzzing/sweep_depth.rb +8 -0
- data/lib/hecks/fuzzing/target_capabilities.rb +61 -14
- data/lib/hecks/fuzzing/value_generator.rb +98 -10
- data/lib/hecks/grammar/evolve.rb +178 -2
- data/lib/hecks/grammar.rb +46 -0
- data/lib/hecks/ir.rb +38 -7
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
- data/lib/hecks/literal.rb +32 -0
- data/lib/hecks/naming.rb +88 -7
- data/lib/hecks/ports/access_control.rb +5 -10
- data/lib/hecks/ports/authorization.rb +3 -6
- data/lib/hecks/ports/identity_assignment.rb +1 -2
- data/lib/hecks/ports/identity_resolution.rb +1 -2
- data/lib/hecks/ports/key_vault.port +6 -0
- data/lib/hecks/ports/key_vault.rb +58 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +6 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +38 -2
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +40 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +92 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +8 -0
- data/lib/hecks/ports/query/in_memory.rb +39 -6
- data/lib/hecks/ports/query/ordering.rb +15 -0
- data/lib/hecks/ports.rb +1 -0
- data/lib/hecks/projections/bootstrap_table.rb +43 -8
- data/lib/hecks/projections/diagrams.rb +243 -7
- data/lib/hecks/projections/glossary/html.rb +88 -0
- data/lib/hecks/projections/glossary/markdown.rb +47 -0
- data/lib/hecks/projections/glossary/mermaid.rb +48 -0
- data/lib/hecks/projections/glossary/sentences.rb +105 -0
- data/lib/hecks/projections/glossary.rb +161 -19
- data/lib/hecks/projections/model/deviations.rb +44 -0
- data/lib/hecks/projections/model.rb +51 -1
- data/lib/hecks/projections/oidc.rb +40 -3
- data/lib/hecks/projections/parser_table.rb +49 -8
- data/lib/hecks/projections/reference.rb +12 -0
- data/lib/hecks/projections/rust_vocabulary.rb +219 -16
- data/lib/hecks/projections/shape.rb +8 -0
- data/lib/hecks/projections/statements.rb +63 -16
- data/lib/hecks/projections/vocabulary.rb +17 -0
- data/lib/hecks/projector/cli_projector.rb +218 -10
- data/lib/hecks/projector/docs_projector.rb +145 -19
- data/lib/hecks/projector/exporter.rb +65 -11
- data/lib/hecks/projector/ir_projector.rb +6 -0
- data/lib/hecks/projector/narrate_projector.rb +136 -15
- data/lib/hecks/projector/target.rb +29 -5
- data/lib/hecks/projector.rb +74 -6
- data/lib/hecks/query_ir.rb +47 -0
- data/lib/hecks/query_specification/common/null_policy.rb +5 -3
- data/lib/hecks/rendering.rb +6 -0
- data/lib/hecks/router/namespace_installer.rb +13 -0
- data/lib/hecks/router.rb +55 -0
- data/lib/hecks/runtime/aggregate_lock.rb +9 -0
- data/lib/hecks/runtime/boot_gates.rb +18 -0
- data/lib/hecks/runtime/caller.rb +32 -0
- data/lib/hecks/runtime/capability_graph.rb +11 -0
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +23 -21
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +14 -15
- data/lib/hecks/runtime/command_interpreter.rb +42 -17
- data/lib/hecks/runtime/command_rules/admissibility.rb +165 -14
- data/lib/hecks/runtime/command_rules/arithmetic.rb +17 -5
- data/lib/hecks/runtime/command_rules/references.rb +118 -28
- data/lib/hecks/runtime/dependency_planning.rb +45 -0
- data/lib/hecks/runtime/dispatcher.rb +28 -50
- data/lib/hecks/runtime/entity_element.rb +161 -8
- data/lib/hecks/runtime/entity_interpreter.rb +44 -9
- data/lib/hecks/runtime/errors.rb +18 -4
- data/lib/hecks/runtime/event.rb +10 -5
- data/lib/hecks/runtime/identity.rb +71 -3
- data/lib/hecks/runtime/instance.rb +67 -7
- data/lib/hecks/runtime/interpreting.rb +13 -5
- data/lib/hecks/runtime/invocation.rb +118 -36
- data/lib/hecks/runtime/loader.rb +94 -8
- data/lib/hecks/runtime/outbox.rb +145 -7
- data/lib/hecks/runtime/policy_interpreter.rb +22 -9
- data/lib/hecks/runtime/port_operation_interpreter.rb +20 -0
- data/lib/hecks/runtime/query_interpreter.rb +40 -12
- data/lib/hecks/runtime/reaction_invocation.rb +53 -8
- data/lib/hecks/runtime/read_model_interpreter.rb +23 -7
- data/lib/hecks/runtime/rebuild_sweep.rb +28 -0
- data/lib/hecks/runtime/reference_hop.rb +42 -0
- data/lib/hecks/runtime/refusal_wording.rb +50 -0
- data/lib/hecks/runtime/registry/saga_persistence.rb +11 -0
- data/lib/hecks/runtime/registry/verification.rb +119 -4
- data/lib/hecks/runtime/registry.rb +157 -4
- data/lib/hecks/runtime/remote_dispatcher.rb +92 -6
- data/lib/hecks/runtime/routing.rb +27 -2
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +10 -12
- data/lib/hecks/runtime/saga_interpreter.rb +27 -13
- data/lib/hecks/runtime/tenant_check.rb +26 -6
- data/lib/hecks/runtime/tenant_scope.rb +18 -0
- data/lib/hecks/runtime/value/coercion.rb +255 -33
- data/lib/hecks/runtime/value/entity_list_coercion.rb +102 -30
- data/lib/hecks/runtime/value.rb +50 -0
- data/lib/hecks/runtime.rb +32 -0
- data/lib/hecks/storehouse.rb +305 -9
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks.rb +79 -1
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -0
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +12 -2
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +29 -5
- metadata +11 -5
- data/lib/hecks/codemod/legacy_dispatch_args.rb +0 -299
- data/lib/hecks/codemod/legacy_dispatch_recorder.rb +0 -186
- data/lib/hecks/deprecation.rb +0 -95
|
@@ -10,6 +10,9 @@ module Hecks
|
|
|
10
10
|
# naming them; the dispatcher itself builds an Invocation instead.
|
|
11
11
|
module Routing
|
|
12
12
|
Envelope = Struct.new(:aggregate, :entities, keyword_init: true) do
|
|
13
|
+
# @param aggregate [String, #to_s] the receiving aggregate's identity
|
|
14
|
+
# @param entities [Array<String, #to_s>, nil] the entity-hop identities after the
|
|
15
|
+
# aggregate, in call order; empty for an aggregate-level command
|
|
13
16
|
def initialize(aggregate:, entities: [])
|
|
14
17
|
super(aggregate: aggregate.to_s, entities: Array(entities).map(&:to_s).freeze)
|
|
15
18
|
freeze
|
|
@@ -18,12 +21,34 @@ module Hecks
|
|
|
18
21
|
|
|
19
22
|
module_function
|
|
20
23
|
|
|
24
|
+
# Resolves a `to:` argument into a routing envelope. A thin delegator to
|
|
25
|
+
# `Invocation.route`; the dispatcher itself builds an `Invocation` instead.
|
|
26
|
+
#
|
|
27
|
+
# @param to [String, Hash, nil] a bare aggregate identity, or a Hash with
|
|
28
|
+
# `aggregate:` and `entity:`/`entities:`; nil for no receiver
|
|
29
|
+
# @param entity_depth [Integer] the number of entity-hop identities the call expects
|
|
30
|
+
# @return [Routing::Envelope, nil] the resolved envelope, or nil when `to` is nil
|
|
31
|
+
# @raise [Runtime::TypeMismatch] if `to` is malformed or its entity count does not
|
|
32
|
+
# match `entity_depth`
|
|
21
33
|
def envelope(to, entity_depth: 0) = Invocation.route(to, entity_depth: entity_depth)
|
|
22
34
|
|
|
35
|
+
# Resolves a command's offered facts into its flat args hash. A thin
|
|
36
|
+
# delegator to `Invocation.facts_for` for any caller still naming it.
|
|
37
|
+
#
|
|
23
38
|
# Loaded by invocation.rb (which requires this file), so `Invocation`
|
|
24
39
|
# is always defined by the time either method runs.
|
|
25
|
-
|
|
26
|
-
|
|
40
|
+
#
|
|
41
|
+
# @param command [Class] a `Bluebook::Command` subclass (or port-operation class)
|
|
42
|
+
# responding to `hecks_name` and `attributes`
|
|
43
|
+
# @param with [Hash, nil] the command's facts, keyed by attribute name; nil when the
|
|
44
|
+
# caller offers `flat` instead
|
|
45
|
+
# @param flat [Hash] the command's facts as a flat args hash, used when `with` is nil
|
|
46
|
+
# @return [Hash{String, Symbol => Object}] the offered facts by attribute name; an
|
|
47
|
+
# attribute offered as nil is kept as nil
|
|
48
|
+
# @raise [Runtime::TypeMismatch] if both `with` and a non-empty `flat` are given, if
|
|
49
|
+
# `with` is not a Hash, or if `with` names an unknown or omits a required attribute
|
|
50
|
+
def payload(command, with:, flat:)
|
|
51
|
+
facts = Invocation.facts_for(command, with: with, flat: flat)
|
|
27
52
|
Invocation.new(verb: nil, target: nil, facts: facts).to_args
|
|
28
53
|
end
|
|
29
54
|
end
|
|
@@ -50,18 +50,16 @@ module Hecks
|
|
|
50
50
|
|
|
51
51
|
# A self-referencing leg carries the correlation forward under its
|
|
52
52
|
# own emitting record's identity — `event.id`, not a field dug back
|
|
53
|
-
# out of the payload
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
# lines at all): the saga silently stopped advancing, forever, for
|
|
64
|
-
# exactly the leg this tier exists to correlate.
|
|
53
|
+
# out of the payload, because an addressing key is not a fact the
|
|
54
|
+
# payload should carry: routing is kept separate from payload
|
|
55
|
+
# (`to:`/`with:`, the facade's own `Handle#run` always uses it), so
|
|
56
|
+
# `own_key` (the aggregate's own reference-key convention, "wire",
|
|
57
|
+
# "transfer") never rides along in the payload unfiltered. Digging
|
|
58
|
+
# for it there would read an empty Hash for any self-referencing
|
|
59
|
+
# leg with no other declared attributes (`OnboardingCase.Clear`,
|
|
60
|
+
# `.Decline` — no `attribute` lines at all), silently stopping the
|
|
61
|
+
# saga from advancing, forever, for exactly the leg this tier
|
|
62
|
+
# exists to correlate.
|
|
65
63
|
#
|
|
66
64
|
# `event.id` says the identical thing this tier always meant —
|
|
67
65
|
# "the record that just emitted this event, by its own identity" —
|
|
@@ -28,14 +28,29 @@ module Hecks
|
|
|
28
28
|
|
|
29
29
|
attr_reader :registry
|
|
30
30
|
|
|
31
|
+
# @param registry [Runtime::Registry] the booted registry whose declared
|
|
32
|
+
# process managers and saga persistence this interpreter runs against
|
|
33
|
+
# @param door [Runtime::Dispatcher] the dispatcher a saga leg's own dispatch
|
|
34
|
+
# re-enters through
|
|
31
35
|
def initialize(registry, door:)
|
|
32
36
|
@registry = registry
|
|
33
37
|
@door = door
|
|
34
38
|
end
|
|
35
39
|
|
|
40
|
+
# Runs `domain`'s declared process managers against `event`: begins,
|
|
41
|
+
# advances or ends each matching saga instance, checkpointing durably
|
|
42
|
+
# before its own dispatches run.
|
|
43
|
+
#
|
|
36
44
|
# `only:` — one process manager, the outbox relay's way of running
|
|
37
45
|
# exactly the consumer a row names (`Runtime::Outbox::Relay#
|
|
38
46
|
# run_consumer`); nil advances every manager the domain declares.
|
|
47
|
+
#
|
|
48
|
+
# @param event [Runtime::Event] the just-emitted event to react to
|
|
49
|
+
# @param domain [String, Symbol] the domain whose declared process managers
|
|
50
|
+
# are checked
|
|
51
|
+
# @param only [Bluebook::ProcessManager, nil] one process manager to run
|
|
52
|
+
# exactly, instead of every manager `domain` declares
|
|
53
|
+
# @return [void]
|
|
39
54
|
def advance(event, domain, only: nil)
|
|
40
55
|
bluebook = @registry.bluebook(domain)
|
|
41
56
|
return unless bluebook
|
|
@@ -56,12 +71,11 @@ module Hecks
|
|
|
56
71
|
# could otherwise interleave their writes out of order, silently
|
|
57
72
|
# reordering a saga's own transition history — worse for the
|
|
58
73
|
# adapters with no locking of their own (Heki) than for Postgres.
|
|
59
|
-
# `deep_copy` guards against
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
# itself calls `JSON.generate` on it.
|
|
74
|
+
# `deep_copy` guards against a real bug shape (over-freezing a live,
|
|
75
|
+
# still-mutated Hash) — never hand a persistence adapter the same
|
|
76
|
+
# object `advance_saga`/`unwind` go on to mutate in place;
|
|
77
|
+
# round-tripping through JSON is also what guarantees the value is
|
|
78
|
+
# safe for every adapter that itself calls `JSON.generate` on it.
|
|
65
79
|
# `pending:` — see saga_pending_dispatch.rb. Injected into the
|
|
66
80
|
# written copy of memory only, never into `instance[:memory]`
|
|
67
81
|
# itself: every other reader of a live instance's memory
|
|
@@ -423,7 +437,7 @@ module Hecks
|
|
|
423
437
|
# Same non-reentrancy reasoning as `advance_saga`'s own comment —
|
|
424
438
|
# the mutex covers only the check-and-mutate-and-checkpoint step.
|
|
425
439
|
advanced = @registry.saga_mutex.synchronize do
|
|
426
|
-
# The compensating leg is selected by (REFUSED
|
|
440
|
+
# The compensating leg is selected by (`REFUSED`, current state)
|
|
427
441
|
# too — C10.3, one rule for every leg.
|
|
428
442
|
handler = process_manager.handler_for(REFUSED, instance[:state])
|
|
429
443
|
unless handler
|
|
@@ -507,9 +521,9 @@ module Hecks
|
|
|
507
521
|
end
|
|
508
522
|
|
|
509
523
|
# BUG#6 — unconditionally the saga's own home domain, never inferred
|
|
510
|
-
# from `command_name`'s own shape.
|
|
511
|
-
# `::` after `Naming.command_ref`'s own rewrite
|
|
512
|
-
# domain-qualified" and left alone
|
|
524
|
+
# from `command_name`'s own shape. Guessing from a leftover
|
|
525
|
+
# `::` after `Naming.command_ref`'s own rewrite (read as "already
|
|
526
|
+
# domain-qualified" and left alone) cannot actually
|
|
513
527
|
# tell a genuinely cross-domain reference (`Banking::Account::
|
|
514
528
|
# Debit` -> one `::` survives) apart from a same-domain entity
|
|
515
529
|
# command reference (`Manifest::Slot::Fill` -> one `::` survives
|
|
@@ -519,11 +533,11 @@ module Hecks
|
|
|
519
533
|
# (confirmed against `Naming.command_ref`'s own rewrite: it only
|
|
520
534
|
# ever strips the last `::`, so the count of what remains is blind
|
|
521
535
|
# to why it's there). Picking the cross-domain reading unconditionally
|
|
522
|
-
#
|
|
536
|
+
# would leave `qa/stress_domains/waybill`'s own `Packing` saga dispatching
|
|
523
537
|
# `Manifest::Slot::Fill` — an entity command in its own domain —
|
|
524
|
-
# unprefixed, so `Naming.split_verb` read "Manifest" as a domain
|
|
538
|
+
# unprefixed, so `Naming.split_verb` would read "Manifest" as a domain
|
|
525
539
|
# name instead of this chapter's own aggregate, and the dispatch
|
|
526
|
-
#
|
|
540
|
+
# would fail with `UnknownVerb`, silently recorded as an ordinary
|
|
527
541
|
# domain refusal rather than surfacing as the real bug it is.
|
|
528
542
|
#
|
|
529
543
|
# The fix mirrors `PolicyInterpreter#deliver`'s own mechanism,
|
|
@@ -10,10 +10,11 @@ module Hecks
|
|
|
10
10
|
# Dispatcher, its own adapter instances) rather than one shared
|
|
11
11
|
# process switching connections mid-dispatch.
|
|
12
12
|
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
13
|
+
# ## Why no ambient "current tenant"
|
|
14
|
+
#
|
|
15
|
+
# The project register (Bluebook::ProjectRegister) already resolves an
|
|
16
|
+
# address's realm to a dispatcher at registration time — Router#resolve
|
|
17
|
+
# looks the FQN up in one flat table keyed by realm::domain::aggregate.verb,
|
|
17
18
|
# and each entry already carries its own dispatcher from its own
|
|
18
19
|
# boot. So "which tenant" is decided once, at boot/registration time
|
|
19
20
|
# (which of possibly many boots of the same directory a request's
|
|
@@ -23,8 +24,9 @@ module Hecks
|
|
|
23
24
|
# already gives for free — each tenant's own PostgresEra instance
|
|
24
25
|
# is its own connection, held for the life of that boot.
|
|
25
26
|
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
27
|
+
# ## What `tenant_capable?` really asks
|
|
28
|
+
#
|
|
29
|
+
# Not "can this adapter switch tenants," but "does booting this
|
|
28
30
|
# adapter twice, with different settings, for the same directory,
|
|
29
31
|
# actually keep the two boots' data apart." Memory answers true
|
|
30
32
|
# trivially — a `@records` Hash is a plain instance variable, and
|
|
@@ -41,6 +43,9 @@ module Hecks
|
|
|
41
43
|
module TenantCheck
|
|
42
44
|
module_function
|
|
43
45
|
|
|
46
|
+
# Refuses to let `domain` boot for more than one tenant unless every
|
|
47
|
+
# aggregate's resolved persistence adapter is `tenant_capable?`.
|
|
48
|
+
#
|
|
44
49
|
# A domain is safe to boot for more than one tenant only if every
|
|
45
50
|
# aggregate's resolved persistence adapter is tenant_capable? — one
|
|
46
51
|
# ungoverned adapter sharing state across two tenant boots is a
|
|
@@ -48,6 +53,12 @@ module Hecks
|
|
|
48
53
|
# a second tenant boot of the same directory is trusted, the same
|
|
49
54
|
# severity EraCheck/refuse_ungoverned_roles! already hold their
|
|
50
55
|
# own gates to.
|
|
56
|
+
#
|
|
57
|
+
# @param registry [Runtime::Registry] the booted registry to check
|
|
58
|
+
# @param domain [String, Symbol] the domain name to check every aggregate of
|
|
59
|
+
# @return [void]
|
|
60
|
+
# @raise [Runtime::WiringError] if any aggregate in `domain` is bound to an adapter
|
|
61
|
+
# that is not `tenant_capable?`
|
|
51
62
|
def refuse_unless_tenant_capable!(registry, domain)
|
|
52
63
|
bluebook = registry.bluebook(domain)
|
|
53
64
|
return unless bluebook
|
|
@@ -67,12 +78,21 @@ module Hecks
|
|
|
67
78
|
"or keep #{domain} single-tenant."
|
|
68
79
|
end
|
|
69
80
|
|
|
81
|
+
# Answers whether `adapter_name`'s Ruby implementation keeps two
|
|
82
|
+
# tenants' boots of the same directory from sharing data.
|
|
83
|
+
#
|
|
70
84
|
# The capability idiom itself — an adapter class that answers
|
|
71
85
|
# tenant_capable? with true keeps two boots' data apart by
|
|
72
86
|
# construction (Memory) or by an explicit per-boot isolation
|
|
73
87
|
# setting (PostgresEra's schema:). Same defensive shape
|
|
74
88
|
# EraCheck#lineage_capable? already uses: a class that doesn't
|
|
75
89
|
# respond at all is false, not an error.
|
|
90
|
+
#
|
|
91
|
+
# @param registry [Runtime::Registry] the booted registry the adapter is wired into
|
|
92
|
+
# @param adapter_name [String] the adapter's declared name, such as `"PostgresEra"`
|
|
93
|
+
# @return [Boolean] true when the adapter is registered, has a Ruby implementation,
|
|
94
|
+
# and answers `tenant_capable?` true; false for any other case, including a
|
|
95
|
+
# missing adapter or one whose lookup raises
|
|
76
96
|
def tenant_capable?(registry, adapter_name)
|
|
77
97
|
adapter_class = registry.adapters[adapter_name] && registry.adapter_class(adapter_name)
|
|
78
98
|
adapter_class.respond_to?(:tenant_capable?) && adapter_class.tenant_capable?
|
|
@@ -29,6 +29,16 @@ module Hecks
|
|
|
29
29
|
module TenantScope
|
|
30
30
|
module_function
|
|
31
31
|
|
|
32
|
+
# Wraps a declared query/read-model spec with its tenant boundary clause, if it has one.
|
|
33
|
+
#
|
|
34
|
+
# @param declared [Bluebook::Query, Bluebook::ReadModel] the declared specification to
|
|
35
|
+
# scope
|
|
36
|
+
# @param args [Hash] the query's arguments, checked for the declared tenant field
|
|
37
|
+
# @return [Bluebook::Query, Bluebook::ReadModel, Runtime::TenantScope::Scoped]
|
|
38
|
+
# `declared` unchanged when it declares no `authorize policy, tenant:`; otherwise a
|
|
39
|
+
# `Scoped` wrapper whose `#wheres` adds the tenant `eq` clause
|
|
40
|
+
# @raise [Runtime::Unauthorized] if `declared` declares a tenant boundary and `args`
|
|
41
|
+
# omits that field
|
|
32
42
|
def apply(declared, args)
|
|
33
43
|
tenant = declared.authorization&.tenant
|
|
34
44
|
return declared unless tenant
|
|
@@ -50,11 +60,19 @@ module Hecks
|
|
|
50
60
|
# own internal `.wheres` read would bypass this override (see this
|
|
51
61
|
# module's own header for why).
|
|
52
62
|
class Scoped < SimpleDelegator
|
|
63
|
+
# @param declared [Bluebook::Query, Bluebook::ReadModel] the specification to wrap,
|
|
64
|
+
# delegated to for everything but `#wheres`
|
|
65
|
+
# @param clause [QuerySpecification::Common::WhereClause] the synthetic tenant `eq`
|
|
66
|
+
# clause to append
|
|
53
67
|
def initialize(declared, clause)
|
|
54
68
|
super(declared)
|
|
55
69
|
@clause = clause
|
|
56
70
|
end
|
|
57
71
|
|
|
72
|
+
# Reads the wrapped specification's where-clauses, with the tenant clause appended.
|
|
73
|
+
#
|
|
74
|
+
# @return [Array<QuerySpecification::Common::WhereClause>] `declared.wheres` with the
|
|
75
|
+
# tenant clause appended
|
|
58
76
|
def wheres = __getobj__.wheres + [@clause]
|
|
59
77
|
end
|
|
60
78
|
end
|