hecks 1.4.0 → 1.5.1
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.bluebook +105 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
- data/lib/hecks/deploy/oidc.json +5 -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/compliance.bluebook +221 -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 +85 -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/deploy/fargate.rb +666 -0
- data/lib/hecks/projections/deploy/lambda.rb +2423 -0
- data/lib/hecks/projections/deploy/shared.rb +624 -0
- 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/projections.rb +3 -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 +47 -10
- data/lib/hecks/projector.rb +101 -19
- 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 +14 -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
- data/lib/hecks/framework/bluebook/compliance.bluebook +0 -1
|
@@ -32,6 +32,12 @@ module Hecks
|
|
|
32
32
|
# `save` only runs when a projected value would differ from what
|
|
33
33
|
# is already stored, so re-running a sweep with nothing having
|
|
34
34
|
# moved on the target side touches the append log not at all.
|
|
35
|
+
#
|
|
36
|
+
# @param registry [Runtime::Registry] the booted registry to read repositories from
|
|
37
|
+
# @param domain [String] the domain the aggregate belongs to
|
|
38
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate whose `projected_fields` to
|
|
39
|
+
# refresh
|
|
40
|
+
# @return [Integer] the number of records actually changed and saved
|
|
35
41
|
def call(registry, domain, aggregate)
|
|
36
42
|
return 0 if aggregate.projected_fields.empty?
|
|
37
43
|
|
|
@@ -39,6 +45,16 @@ module Hecks
|
|
|
39
45
|
repository.all.count { |record| refresh(registry, domain, aggregate, record, repository) }
|
|
40
46
|
end
|
|
41
47
|
|
|
48
|
+
# Refreshes one record's own projected fields in place, saving it if any changed.
|
|
49
|
+
#
|
|
50
|
+
# @param registry [Runtime::Registry] the booted registry to read the target's
|
|
51
|
+
# repository from
|
|
52
|
+
# @param domain [String] the domain the aggregate belongs to
|
|
53
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate `record` is an instance of
|
|
54
|
+
# @param record [Runtime::Instance] the record to refresh, mutated in place
|
|
55
|
+
# @param repository [Ports::Persistence::AppendOnly] the repository to save `record`
|
|
56
|
+
# through when it changes
|
|
57
|
+
# @return [Boolean] true if any projected field's value changed and `record` was saved
|
|
42
58
|
def refresh(registry, domain, aggregate, record, repository)
|
|
43
59
|
changed = false
|
|
44
60
|
|
|
@@ -55,10 +71,22 @@ module Hecks
|
|
|
55
71
|
changed
|
|
56
72
|
end
|
|
57
73
|
|
|
74
|
+
# Reads the current value of one projected field's remote target field.
|
|
75
|
+
#
|
|
58
76
|
# `nil` when the reference itself does not resolve in this
|
|
59
77
|
# chapter (a cross-domain target left "unfollowed" the same way
|
|
60
78
|
# References#dereference already leaves one) or when the record
|
|
61
79
|
# names no target at all — an optional reference nobody set.
|
|
80
|
+
#
|
|
81
|
+
# @param registry [Runtime::Registry] the booted registry to read the target's
|
|
82
|
+
# repository from
|
|
83
|
+
# @param domain [String] the domain the aggregate belongs to
|
|
84
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate declaring `field`
|
|
85
|
+
# @param record [Runtime::Instance] the record holding the reference to follow
|
|
86
|
+
# @param field [Bluebook::ProjectedField] the projected field to resolve
|
|
87
|
+
# @return [Object, nil] the target record's own `field.remote_field` value; nil if the
|
|
88
|
+
# reference type does not resolve, the record names no target, or the target record
|
|
89
|
+
# cannot be found
|
|
62
90
|
def remote_value(registry, domain, aggregate, record, field)
|
|
63
91
|
target = aggregate.attribute(field.reference)&.type&.resolve
|
|
64
92
|
return nil unless target
|
|
@@ -31,6 +31,21 @@ module Hecks
|
|
|
31
31
|
module ReferenceHop
|
|
32
32
|
module_function
|
|
33
33
|
|
|
34
|
+
# Folds every hop clause in `declared.wheres` into a synthetic local `in` clause.
|
|
35
|
+
#
|
|
36
|
+
# @param declared [Bluebook::Query, Runtime::TenantScope::Scoped,
|
|
37
|
+
# QuerySpecification::Common::Options] the declared query specification to fold hop
|
|
38
|
+
# clauses of
|
|
39
|
+
# @param args [Hash] the query's arguments, read when resolving each hop's own query
|
|
40
|
+
# @param registry [Runtime::Registry] the booted registry to resolve each hop's target
|
|
41
|
+
# repository from
|
|
42
|
+
# @param domain [String] the domain `aggregate` belongs to
|
|
43
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate `declared` queries
|
|
44
|
+
# @return [Bluebook::Query, Runtime::TenantScope::Scoped, QuerySpecification::Common::
|
|
45
|
+
# Options, Hecks::Runtime::ReferenceHop::Folded] `declared` unchanged when it has no
|
|
46
|
+
# hop clauses; otherwise a `Folded` wrapper whose `#wheres` replaces each hop clause
|
|
47
|
+
# with its folded `in` clause
|
|
48
|
+
# @raise [Runtime::WiringError] if a hop's target no longer resolves (see `fold`)
|
|
34
49
|
def apply(declared, args, registry:, domain:, aggregate:)
|
|
35
50
|
hopped, local = declared.wheres.partition { |clause| QuerySpecification::HopPath.hop_head?(clause.field, aggregate.attributes) }
|
|
36
51
|
return declared if hopped.empty?
|
|
@@ -39,6 +54,18 @@ module Hecks
|
|
|
39
54
|
Folded.new(declared, local + folded)
|
|
40
55
|
end
|
|
41
56
|
|
|
57
|
+
# Folds one hop clause into a synthetic `in` clause over the hop attribute's own ids.
|
|
58
|
+
#
|
|
59
|
+
# @param clause [QuerySpecification::Common::WhereClause] the hop clause to fold; its
|
|
60
|
+
# `field` names the hop path, dotted past the first segment
|
|
61
|
+
# @param args [Hash] the query's arguments, read when resolving the inner query
|
|
62
|
+
# @param registry [Runtime::Registry] the booted registry to resolve the hop's target
|
|
63
|
+
# repository from
|
|
64
|
+
# @param domain [String] the domain `aggregate` belongs to
|
|
65
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate `clause` is declared against
|
|
66
|
+
# @return [QuerySpecification::Common::WhereClause] a synthetic `in` clause on the hop
|
|
67
|
+
# attribute's name, whose value is every id the inner clause admits on the target
|
|
68
|
+
# @raise [Runtime::WiringError] if the hop's target aggregate no longer resolves
|
|
42
69
|
def fold(clause, args, registry:, domain:, aggregate:)
|
|
43
70
|
step = QuerySpecification::HopPath.next_hop(clause.field, aggregate.attributes)
|
|
44
71
|
hop, rest = step
|
|
@@ -70,6 +97,16 @@ module Hecks
|
|
|
70
97
|
# aggregate is actually bound to (which may not be the engine
|
|
71
98
|
# the outer aggregate is bound to at all) rather than by a
|
|
72
99
|
# second reading of the comparators.
|
|
100
|
+
#
|
|
101
|
+
# @param domain [String] the domain `target` belongs to
|
|
102
|
+
# @param target [Bluebook::Aggregate] the hop's target aggregate to query
|
|
103
|
+
# @param wheres [Array<QuerySpecification::Common::WhereClause>] the inner clause(s) to
|
|
104
|
+
# run against `target`
|
|
105
|
+
# @param args [Hash] the outer query's arguments, read when resolving the inner query
|
|
106
|
+
# @param registry [Runtime::Registry] the booted registry to resolve `target`'s
|
|
107
|
+
# repository from
|
|
108
|
+
# @return [Array<String>] every distinct id the inner clause(s) admit on `target`
|
|
109
|
+
# @raise [Runtime::WiringError] if a hop nested inside `wheres` no longer resolves
|
|
73
110
|
def matching_ids(domain, target, wheres, args, registry:)
|
|
74
111
|
spec = apply(QuerySpecification::Common::Options.new(wheres: wheres), args,
|
|
75
112
|
registry: registry, domain: domain, aggregate: target)
|
|
@@ -87,6 +124,11 @@ module Hecks
|
|
|
87
124
|
# same reason: SimpleDelegator only intercepts calls made
|
|
88
125
|
# directly on the wrapper.
|
|
89
126
|
class Folded < SimpleDelegator
|
|
127
|
+
# @param declared [Bluebook::Query, Runtime::TenantScope::Scoped,
|
|
128
|
+
# QuerySpecification::Common::Options] the wrapped query specification, delegated to
|
|
129
|
+
# for everything but `#wheres`
|
|
130
|
+
# @param wheres [Array<QuerySpecification::Common::WhereClause>] the replacement
|
|
131
|
+
# where-clauses, hop clauses folded to synthetic `in` clauses
|
|
90
132
|
def initialize(declared, wheres)
|
|
91
133
|
super(declared)
|
|
92
134
|
@wheres = wheres
|
|
@@ -31,9 +31,18 @@ module Hecks
|
|
|
31
31
|
|
|
32
32
|
module_function
|
|
33
33
|
|
|
34
|
+
# Renders `refusal`/`site`'s template, substituting `values` verbatim.
|
|
35
|
+
#
|
|
34
36
|
# Plain text substitution, never expression syntax — a template is
|
|
35
37
|
# read, not evaluated. Values arrive already formatted; prefer
|
|
36
38
|
# `render_site`, which formats them off the declared rows.
|
|
39
|
+
#
|
|
40
|
+
# @param refusal [String] the `DomainRefusal` class name, such as `"UnknownArgument"`
|
|
41
|
+
# @param site [String] the template site within `refusal`, such as `"unknown_args"`
|
|
42
|
+
# @param values [Hash{Symbol => #to_s}] each `{name}` placeholder's already-formatted
|
|
43
|
+
# replacement text
|
|
44
|
+
# @return [String] the rendered refusal message
|
|
45
|
+
# @raise [KeyError] if no `RefusalTemplate` row declares `refusal`/`site`
|
|
37
46
|
def render(refusal, site, **values)
|
|
38
47
|
substitute(template(refusal, site), values)
|
|
39
48
|
end
|
|
@@ -46,6 +55,16 @@ module Hecks
|
|
|
46
55
|
# RefusalWording.render_site("UnknownArgument", "unknown_args",
|
|
47
56
|
# command: "Close", unknown: [:parcel], declared: [])
|
|
48
57
|
# # => "Close does not declare parcel — it takes none"
|
|
58
|
+
#
|
|
59
|
+
# @param refusal [String] the `DomainRefusal` class name, such as `"UnknownArgument"`
|
|
60
|
+
# @param site [String] the template site within `refusal`, such as `"unknown_args"`
|
|
61
|
+
# @param arguments [Hash{Symbol => Object}] raw values, one per `RefusalSiteArgument`
|
|
62
|
+
# row declared for `refusal`/`site`; formatted per row before substitution
|
|
63
|
+
# @return [String] the rendered refusal message
|
|
64
|
+
# @raise [ArgumentError] if `arguments` is missing a declared argument or offers one
|
|
65
|
+
# `refusal`/`site` does not declare
|
|
66
|
+
# @raise [KeyError] if no `RefusalTemplate`/`RefusalSiteArgument` rows declare
|
|
67
|
+
# `refusal`/`site`
|
|
49
68
|
def render_site(refusal, site, **arguments)
|
|
50
69
|
specs = argument_rows(refusal, site)
|
|
51
70
|
declared = specs.map { |spec| spec["argument"].to_sym }
|
|
@@ -62,6 +81,12 @@ module Hecks
|
|
|
62
81
|
# `render_site` without the registry lookups: a template, its
|
|
63
82
|
# argument rows, and raw values. The Rust projection calls this with
|
|
64
83
|
# the chapter's own rows to compute the expected wording it pins.
|
|
84
|
+
#
|
|
85
|
+
# @param template [String] the raw template, `{name}` placeholders unsubstituted
|
|
86
|
+
# @param specs [Array<Hash>] the `RefusalSiteArgument` rows to format `arguments`
|
|
87
|
+
# against, one per declared argument name
|
|
88
|
+
# @param arguments [Hash{Symbol => Object}] raw values, one per entry in `specs`
|
|
89
|
+
# @return [String] the rendered refusal message
|
|
65
90
|
def render_with(template, specs, arguments)
|
|
66
91
|
values = specs.to_h do |spec|
|
|
67
92
|
name = spec["argument"].to_sym
|
|
@@ -73,6 +98,12 @@ module Hecks
|
|
|
73
98
|
# One argument, written the way its row says. A list is sorted first
|
|
74
99
|
# (before quoting), then each item quoted, then joined; an empty list
|
|
75
100
|
# reads `when_empty`. A scalar is quoted or taken as its own text.
|
|
101
|
+
#
|
|
102
|
+
# @param spec [Hash] the argument's `RefusalSiteArgument` row (`"shape"`, `"quoting"`,
|
|
103
|
+
# and, for a list, `"sorted"`, `"separator"`, `"when_empty"`)
|
|
104
|
+
# @param value [Object, Array] the raw value to format; an Array (or anything
|
|
105
|
+
# `Array()`-coercible) for a `"list"`-shaped spec, a scalar otherwise
|
|
106
|
+
# @return [String] the formatted text
|
|
76
107
|
def format_argument(spec, value)
|
|
77
108
|
inspect = spec.fetch("quoting") == "inspect"
|
|
78
109
|
return inspect ? value.inspect : value.to_s unless spec.fetch("shape") == "list"
|
|
@@ -83,10 +114,22 @@ module Hecks
|
|
|
83
114
|
items.empty? ? spec.fetch("when_empty") : items.join(spec.fetch("separator"))
|
|
84
115
|
end
|
|
85
116
|
|
|
117
|
+
# Replaces each `{name}` placeholder in `template` with its value's text.
|
|
118
|
+
#
|
|
119
|
+
# @param template [String] the raw template, `{name}` placeholders unsubstituted
|
|
120
|
+
# @param values [Hash{Symbol => #to_s}] each placeholder name mapped to its
|
|
121
|
+
# replacement text
|
|
122
|
+
# @return [String] `template` with every `{name}` placeholder substituted
|
|
86
123
|
def substitute(template, values)
|
|
87
124
|
values.reduce(template) { |text, (key, value)| text.gsub("{#{key}}", value.to_s) }
|
|
88
125
|
end
|
|
89
126
|
|
|
127
|
+
# Looks up the raw template text declared for one refusal site.
|
|
128
|
+
#
|
|
129
|
+
# @param refusal [String] the `DomainRefusal` class name, such as `"UnknownArgument"`
|
|
130
|
+
# @param site [String] the template site within `refusal`, such as `"unknown_args"`
|
|
131
|
+
# @return [String] the raw `RefusalTemplate` text, `{name}` placeholders unsubstituted
|
|
132
|
+
# @raise [KeyError] if no `RefusalTemplate` row declares `refusal`/`site`
|
|
90
133
|
def template(refusal, site)
|
|
91
134
|
TEMPLATES.fetch([refusal, site]) do
|
|
92
135
|
raise KeyError, "no refusal template for #{refusal}/#{site} — declare it in " \
|
|
@@ -94,9 +137,16 @@ module Hecks
|
|
|
94
137
|
end
|
|
95
138
|
end
|
|
96
139
|
|
|
140
|
+
# The declared arguments for one refusal site.
|
|
141
|
+
#
|
|
97
142
|
# Read lazily, not into a constant: bin/project_vocabulary boots
|
|
98
143
|
# `hecks` (and so this file) before it writes the table a newly
|
|
99
144
|
# declared site's rows live in.
|
|
145
|
+
#
|
|
146
|
+
# @param refusal [String] the `DomainRefusal` class name, such as `"UnknownArgument"`
|
|
147
|
+
# @param site [String] the template site within `refusal`, such as `"unknown_args"`
|
|
148
|
+
# @return [Array<Hash>] the `RefusalSiteArgument` rows declared for `refusal`/`site`
|
|
149
|
+
# @raise [KeyError] if no `RefusalSiteArgument` rows declare `refusal`/`site`
|
|
100
150
|
def argument_rows(refusal, site)
|
|
101
151
|
@argument_rows ||= Hecks::Vocabulary.rows("RefusalSiteArgument")
|
|
102
152
|
.group_by { |row| [row["refusal"], row["site"]] }
|
|
@@ -40,6 +40,15 @@ module Hecks
|
|
|
40
40
|
# state lands in). Double-checked locking against a dedicated mutex
|
|
41
41
|
# — never `@saga_mutex` — see `Registry#initialize`'s own comment for
|
|
42
42
|
# why reusing that one would deadlock.
|
|
43
|
+
# Resolves and memoizes the adapter a domain's sagas persist through.
|
|
44
|
+
#
|
|
45
|
+
# @param domain [String, Symbol] the domain to resolve saga persistence for
|
|
46
|
+
# @return [Adapters::Heki, Adapters::Postgres, Adapters::Sqlite, Adapters::D1,
|
|
47
|
+
# Ports::Persistence::Plugins::Era::PostgresEra, Ports::Persistence::NullSagaStore]
|
|
48
|
+
# the same adapter instance the domain's anchor aggregate persists through, when it
|
|
49
|
+
# implements `save_saga`; `NULL_SAGA_STORE` for a domain with no anchor aggregate, an
|
|
50
|
+
# adapter that does not implement the capability, a `RemoteRuntime`-shaped adapter,
|
|
51
|
+
# or a `Runtime::WiringError` resolving the anchor's own bind
|
|
43
52
|
def saga_persistence(domain)
|
|
44
53
|
key = domain.to_s
|
|
45
54
|
@saga_persistence[key] || @saga_persistence_mutex.synchronize do
|
|
@@ -67,6 +76,8 @@ module Hecks
|
|
|
67
76
|
# points inside `@saga_mutex.synchronize` blocks (`saga_interpreter.
|
|
68
77
|
# rb`), which genuinely do and are guarded accordingly.
|
|
69
78
|
# rubocop:disable-next Hecks/ThreadSharedIvarMutation
|
|
79
|
+
#
|
|
80
|
+
# @return [Hecks::Runtime::Registry] self
|
|
70
81
|
def rehydrate_sagas!
|
|
71
82
|
@hecksagons.each_key do |domain|
|
|
72
83
|
saga_persistence(domain).each_saga do |process_manager, correlation, state, memory, completed_compensations = []|
|
|
@@ -10,9 +10,19 @@ module Hecks
|
|
|
10
10
|
# loading, and the smaller checks are also called piecemeal by the
|
|
11
11
|
# repository factory.
|
|
12
12
|
module Verification
|
|
13
|
+
# Runs the whole wiring gate against this registry's loaded bluebooks,
|
|
14
|
+
# hexagons, ports and adapters.
|
|
15
|
+
#
|
|
16
|
+
# @return [Runtime::Registry] self
|
|
17
|
+
# @raise [Runtime::WiringError] if a bind names an undeclared aggregate, an
|
|
18
|
+
# adapter cannot satisfy its port's verb or declared `answers`, a world
|
|
19
|
+
# setting names a field its adapter does not declare, the default adapter
|
|
20
|
+
# is unusable, or a command declares a role with no authorization provider
|
|
21
|
+
# attached
|
|
13
22
|
def verify!
|
|
14
23
|
verify_default_adapter!
|
|
15
24
|
verify_singleton_port_answers!
|
|
25
|
+
refuse_cross_package_bluebook_merge!
|
|
16
26
|
|
|
17
27
|
@hecksagons.each_value do |hexagon|
|
|
18
28
|
refuse_ungoverned_roles!(hexagon)
|
|
@@ -50,6 +60,13 @@ module Hecks
|
|
|
50
60
|
self
|
|
51
61
|
end
|
|
52
62
|
|
|
63
|
+
# Checks that the framework-wide default persistence adapter (used by any
|
|
64
|
+
# aggregate left out of an explicit bind list) is itself wired correctly.
|
|
65
|
+
#
|
|
66
|
+
# @return [Runtime::Registry] self
|
|
67
|
+
# @raise [Runtime::WiringError] if the default adapter cannot satisfy the
|
|
68
|
+
# persistence port's verb, is missing a declared `answers` method, or has
|
|
69
|
+
# no Ruby implementation
|
|
53
70
|
def verify_default_adapter!
|
|
54
71
|
name = Ports::Persistence::DEFAULT_ADAPTER
|
|
55
72
|
|
|
@@ -68,6 +85,14 @@ module Hecks
|
|
|
68
85
|
"aggregate with no bind could not be given one: #{e.message}"
|
|
69
86
|
end
|
|
70
87
|
|
|
88
|
+
# Checks that `bind`'s adapter implements the port it names and satisfies
|
|
89
|
+
# the verb the bind declares.
|
|
90
|
+
#
|
|
91
|
+
# @param bind [Bluebook::Bind] the bind to check
|
|
92
|
+
# @return [void]
|
|
93
|
+
# @raise [Runtime::WiringError] if `bind`'s adapter is unknown, declares an
|
|
94
|
+
# unknown port, is missing a declared `answers` method, or cannot satisfy
|
|
95
|
+
# `bind`'s own verb
|
|
71
96
|
def check_verb(bind)
|
|
72
97
|
port = port_for(bind)
|
|
73
98
|
check_answers(port, bind.adapter)
|
|
@@ -85,6 +110,13 @@ module Hecks
|
|
|
85
110
|
# actually call. `answers` is optional per port (an empty list is
|
|
86
111
|
# today's pre-existing behavior, unchecked), so this only ever
|
|
87
112
|
# tightens a port that opted in.
|
|
113
|
+
#
|
|
114
|
+
# @param port [Bluebook::Port] the port whose declared `answers` methods
|
|
115
|
+
# `adapter_name` must respond to
|
|
116
|
+
# @param adapter_name [String] the adapter's declared name to check
|
|
117
|
+
# @return [void]
|
|
118
|
+
# @raise [Runtime::WiringError] if `adapter_name` has no Ruby implementation,
|
|
119
|
+
# or its implementation does not respond to one of `port.answers`
|
|
88
120
|
def check_answers(port, adapter_name)
|
|
89
121
|
answers = Array(port.answers)
|
|
90
122
|
return if answers.empty?
|
|
@@ -114,6 +146,12 @@ module Hecks
|
|
|
114
146
|
# exactly one adapter, wired, missing a method `answers` names.
|
|
115
147
|
PER_AGGREGATE_PORTS = %w[persistence projection loading].freeze
|
|
116
148
|
|
|
149
|
+
# Checks every singleton port (not per-aggregate-bound) with exactly one
|
|
150
|
+
# wired adapter against its own declared `answers` methods.
|
|
151
|
+
#
|
|
152
|
+
# @return [Runtime::Registry] self
|
|
153
|
+
# @raise [Runtime::WiringError] if a singleton port's one wired adapter is
|
|
154
|
+
# missing one of its declared `answers` methods
|
|
117
155
|
def verify_singleton_port_answers!
|
|
118
156
|
@ports.each_value do |port|
|
|
119
157
|
next if PER_AGGREGATE_PORTS.include?(port.name)
|
|
@@ -127,6 +165,16 @@ module Hecks
|
|
|
127
165
|
self
|
|
128
166
|
end
|
|
129
167
|
|
|
168
|
+
# Checks that every setting `settings` declares (besides `:adapter`) is a
|
|
169
|
+
# field `bind`'s adapter actually admits.
|
|
170
|
+
#
|
|
171
|
+
# @param bind [Bluebook::Bind] the bind naming the adapter to check against;
|
|
172
|
+
# a no-op if its adapter is unknown
|
|
173
|
+
# @param settings [Hash{Symbol => Object}] the world's declared settings for
|
|
174
|
+
# this bind
|
|
175
|
+
# @return [void]
|
|
176
|
+
# @raise [Runtime::WiringError] if `settings` declares a field `bind`'s
|
|
177
|
+
# adapter does not declare
|
|
130
178
|
def check_settings(bind, settings)
|
|
131
179
|
adapter = @adapters[bind.adapter]
|
|
132
180
|
return unless adapter
|
|
@@ -141,6 +189,12 @@ module Hecks
|
|
|
141
189
|
"Add the field to the adapter, or remove it from the world."
|
|
142
190
|
end
|
|
143
191
|
|
|
192
|
+
# Finds the port `bind`'s adapter declares.
|
|
193
|
+
#
|
|
194
|
+
# @param bind [Bluebook::Bind] the bind naming the adapter to look up
|
|
195
|
+
# @return [Bluebook::Port] the port `bind`'s adapter declares
|
|
196
|
+
# @raise [Runtime::WiringError] if `bind` names an unknown adapter, or one
|
|
197
|
+
# declaring an unknown port
|
|
144
198
|
def port_for(bind)
|
|
145
199
|
adapter = @adapters[bind.adapter]
|
|
146
200
|
raise WiringError, "unknown adapter #{bind.adapter.inspect}" unless adapter
|
|
@@ -149,6 +203,12 @@ module Hecks
|
|
|
149
203
|
raise(WiringError, "adapter #{bind.adapter} declares unknown port #{adapter.port.inspect}")
|
|
150
204
|
end
|
|
151
205
|
|
|
206
|
+
# Finds the Ruby module implementing the adapter declared `name`.
|
|
207
|
+
#
|
|
208
|
+
# @param name [String] the adapter's declared name, such as `"PostgresEra"`
|
|
209
|
+
# @return [Module] the adapter module or class under `Hecks::Adapters`
|
|
210
|
+
# @raise [Runtime::WiringError] if no Ruby implementation named `name` exists
|
|
211
|
+
# under `Hecks::Adapters`
|
|
152
212
|
def adapter_class(name)
|
|
153
213
|
Adapters.const_get(name)
|
|
154
214
|
rescue NameError
|
|
@@ -164,9 +224,9 @@ module Hecks
|
|
|
164
224
|
# unchecked, exactly the defect ADR 0025 §9 names ("role gates
|
|
165
225
|
# access control by exact string equality ... Governance ...
|
|
166
226
|
# connected to none of it"). Checked here, at `verify!` — recovered
|
|
167
|
-
# and moved, not new: this
|
|
168
|
-
# build time (Bluebook::DSL::HecksagonBuilder#build),
|
|
169
|
-
# the moment a domain
|
|
227
|
+
# and moved, not new: running this per-block, at hecksagon
|
|
228
|
+
# build time (Bluebook::DSL::HecksagonBuilder#build), breaks
|
|
229
|
+
# the moment a domain is split across multiple hecksagon
|
|
170
230
|
# blocks (base + an `environments/<name>.hecksagon` overlay,
|
|
171
231
|
# Runtime::Loader.boot's `environment:` — see its own comment for
|
|
172
232
|
# the recovery provenance): every block but the one declaring
|
|
@@ -179,7 +239,7 @@ module Hecks
|
|
|
179
239
|
# correct (a check against an incomplete, not-yet-merged
|
|
180
240
|
# hecksagon can never see the real final shape).
|
|
181
241
|
#
|
|
182
|
-
# A provider is
|
|
242
|
+
# A provider is recognised by its declaration, not its name —
|
|
183
243
|
# `authorization_provider_for` answers for the domain's own
|
|
184
244
|
# chapter too, so Governance (which declares `provides
|
|
185
245
|
# "authorization"`) passes here because of what it declares, and
|
|
@@ -217,6 +277,61 @@ module Hecks
|
|
|
217
277
|
bluebook_ir.aggregates.flat_map { |aggregate| aggregate.commands + aggregate.entities.flat_map(&:commands) }
|
|
218
278
|
end
|
|
219
279
|
|
|
280
|
+
# TWO UNRELATED PACKAGES, ONE CHAPTER NAME BY COINCIDENCE — the
|
|
281
|
+
# real risk `Registry#bluebook_sources` exists to catch (found
|
|
282
|
+
# live: a stale `vendor/hecksagain` fork's own copy of Governance/
|
|
283
|
+
# Identity/Deploy, still reachable on 4 consuming apps' own load
|
|
284
|
+
# paths alongside the real gem). `BluebookBuilder.build`'s own
|
|
285
|
+
# accumulation (several files declaring the SAME chapter name ON
|
|
286
|
+
# PURPOSE — `lib/hecks/language/bluebook/*.bluebook` all open
|
|
287
|
+
# `Hecks.bluebook "Bluebook"`) is never touched here — that merge
|
|
288
|
+
# stays unconditional, checked only AFTER every file has loaded,
|
|
289
|
+
# the same "check the merged final result once" timing
|
|
290
|
+
# `refuse_ungoverned_roles!` already uses and for the same reason
|
|
291
|
+
# (a check against an incomplete load can never see the real
|
|
292
|
+
# shape). What distinguishes intentional accumulation from
|
|
293
|
+
# coincidence is PACKAGE ROOT, not file identity: files a real
|
|
294
|
+
# gemspec or a `vendor/` boundary already treats as one unit are
|
|
295
|
+
# expected to share a name; files from two DIFFERENT roots never
|
|
296
|
+
# legitimately do.
|
|
297
|
+
def refuse_cross_package_bluebook_merge!
|
|
298
|
+
@bluebook_sources.each do |name, paths|
|
|
299
|
+
roots = paths.map { |path| package_root_for(path) }.uniq
|
|
300
|
+
next if roots.size <= 1
|
|
301
|
+
|
|
302
|
+
raise WiringError,
|
|
303
|
+
"#{name.inspect} is declared by more than one package: #{roots.join(' and ')} — " \
|
|
304
|
+
"these are two unrelated sources sharing a chapter name by coincidence, not one " \
|
|
305
|
+
"domain split across files, and merging their declarations into one chapter is " \
|
|
306
|
+
"almost certainly a stale/vendored copy left on the load path (paths: " \
|
|
307
|
+
"#{paths.join(', ')})"
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# THE NEAREST BOUNDARY A PATH ALREADY BELONGS TO — a real
|
|
312
|
+
# gemspec (this IS a package, whatever depends on it or vendors
|
|
313
|
+
# it), or a bare `vendor/` path component, treated as its OWN
|
|
314
|
+
# root regardless of what gemspec might sit above it: vendored
|
|
315
|
+
# code should never be considered "the same package" as whatever
|
|
316
|
+
# it's vendored into, even when nothing else marks the boundary.
|
|
317
|
+
# Neither found, the path's own directory is the root — two
|
|
318
|
+
# files with no closer marker only "belong together" if they are
|
|
319
|
+
# literally the same file.
|
|
320
|
+
def package_root_for(path)
|
|
321
|
+
return path.to_s if path.nil?
|
|
322
|
+
|
|
323
|
+
dir = File.dirname(File.expand_path(path))
|
|
324
|
+
loop do
|
|
325
|
+
return "vendor:#{dir}" if File.basename(dir) == "vendor"
|
|
326
|
+
return dir if Dir.glob(File.join(dir, "*.gemspec")).any?
|
|
327
|
+
|
|
328
|
+
parent = File.dirname(dir)
|
|
329
|
+
return dir if parent == dir
|
|
330
|
+
|
|
331
|
+
dir = parent
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
|
|
220
335
|
# A domain that declares a `process_manager` but whose
|
|
221
336
|
# `saga_persistence` resolves to `NULL_SAGA_STORE` (no anchor
|
|
222
337
|
# aggregate, a RemoteRuntime-shaped adapter, an adapter that
|