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
data/lib/hecks/query_ir.rb
CHANGED
|
@@ -37,6 +37,11 @@ module Hecks
|
|
|
37
37
|
|
|
38
38
|
module_function
|
|
39
39
|
|
|
40
|
+
# Reads the fields the language declares for one construct kind, from the grammar itself.
|
|
41
|
+
#
|
|
42
|
+
# @param name [String] a `CONSTRUCTS` key, such as `"Aggregate"`
|
|
43
|
+
# @return [Array<Symbol>] the attribute names the self-hosted meta-domain
|
|
44
|
+
# grammar declares for `name`
|
|
40
45
|
def meta_declared(name)
|
|
41
46
|
Hecks::Bluebook::MetaValidator.grammar_registry
|
|
42
47
|
.bluebook("Bluebook").aggregate(name).attributes.map(&:name)
|
|
@@ -47,6 +52,11 @@ module Hecks
|
|
|
47
52
|
# for it — the same comparison spec/model_shape_conformance_spec.rb
|
|
48
53
|
# makes, reusing its own Deviations data so this can never silently
|
|
49
54
|
# drift from what that gate actually checks.
|
|
55
|
+
#
|
|
56
|
+
# @param name [String] a `CONSTRUCTS` key, such as `"Aggregate"`
|
|
57
|
+
# @return [Hash{Symbol => Object}] `:name`, `:declared`, `:emitted`, plus
|
|
58
|
+
# `:missing_from_ruby` and `:unaccounted_in_ruby` — each an `Array<Symbol>`
|
|
59
|
+
# @raise [ArgumentError] if `name` is not a `CONSTRUCTS` key
|
|
50
60
|
def construct_diff(name)
|
|
51
61
|
klass = CONSTRUCTS.fetch(name) do
|
|
52
62
|
raise ArgumentError, "no such construct #{name.inspect} — known: #{CONSTRUCTS.keys.join(', ')}"
|
|
@@ -72,6 +82,10 @@ module Hecks
|
|
|
72
82
|
missing_from_ruby: accounted - emitted, unaccounted_in_ruby: unaccounted }
|
|
73
83
|
end
|
|
74
84
|
|
|
85
|
+
# Diffs one or every construct kind at once.
|
|
86
|
+
#
|
|
87
|
+
# @param names [Array<String>] `CONSTRUCTS` keys to diff, every construct when empty
|
|
88
|
+
# @return [Array<Hash>] one `construct_diff` result per name
|
|
75
89
|
def constructs(names = [])
|
|
76
90
|
targets = names.empty? ? CONSTRUCTS.keys : names
|
|
77
91
|
targets.map { |name| construct_diff(name) }
|
|
@@ -96,6 +110,10 @@ module Hecks
|
|
|
96
110
|
# already a distinct object, whether it was block-declared or
|
|
97
111
|
# bare-referenced — object identity carries no signal past that
|
|
98
112
|
# point, for any construct, not just this one).
|
|
113
|
+
#
|
|
114
|
+
# @param registry [Runtime::Registry] the booted registry to walk
|
|
115
|
+
# @param chapter_name [String, nil] one chapter to walk, every booted chapter when nil
|
|
116
|
+
# @return [Array<Rule>]
|
|
99
117
|
def collect_rules(registry, chapter_name = nil)
|
|
100
118
|
rules = []
|
|
101
119
|
chapters = chapter_name ? [registry.bluebook(chapter_name)] : registry.bluebooks.values
|
|
@@ -158,6 +176,10 @@ module Hecks
|
|
|
158
176
|
# local_givens` reads it directly to group `collect_rules`' own
|
|
159
177
|
# output by owner itself, the same reading `duplicates`' own
|
|
160
178
|
# `declaration_count` makes.
|
|
179
|
+
#
|
|
180
|
+
# @param location [String] a `Rule#location`
|
|
181
|
+
# @return [String] the owning construct's path, with `" (declared)"` or a trailing
|
|
182
|
+
# `.CommandName` segment stripped
|
|
161
183
|
def owner_of(location)
|
|
162
184
|
return location.sub(/ \(declared\)\z/, "") if location.end_with?(" (declared)")
|
|
163
185
|
|
|
@@ -188,6 +210,12 @@ module Hecks
|
|
|
188
210
|
# `domains: []` means "the self-hosted meta-domain only" — pass real
|
|
189
211
|
# domain directories explicitly to include them, or `nil` (the
|
|
190
212
|
# default) for meta-domain plus every real example.
|
|
213
|
+
#
|
|
214
|
+
# @param domains [Array<String>, nil] domain root directories to scan; every real
|
|
215
|
+
# example (`Codemod::EXAMPLE_ROOTS`) when nil, none beyond the meta-domain when `[]`
|
|
216
|
+
# @param include_meta [Boolean] whether to also scan the self-hosted meta-domain
|
|
217
|
+
# @return [Array<Hash>] one entry per duplicate group: `:kind`, `:description`,
|
|
218
|
+
# `:canonical`, and `:locations` (`Array<String>`)
|
|
191
219
|
def duplicates(domains: nil, include_meta: true)
|
|
192
220
|
domains ||= Codemod::EXAMPLE_ROOTS
|
|
193
221
|
all_rules = []
|
|
@@ -260,6 +288,9 @@ module Hecks
|
|
|
260
288
|
# case above, before assuming a group naming two pieces under
|
|
261
289
|
# different aggregates is fresh duplication rather than an already-
|
|
262
290
|
# resolved chapter-wide reference.
|
|
291
|
+
#
|
|
292
|
+
# @param rules [Array<Rule>] one duplicate-key group's own rules
|
|
293
|
+
# @return [Integer] how many of `rules` are real, independent declarations
|
|
263
294
|
def declaration_count(rules)
|
|
264
295
|
declared = rules.select { |r| r.location.end_with?(" (declared)") }
|
|
265
296
|
declared_owners = declared.to_set { |r| owner_of(r.location) }
|
|
@@ -282,6 +313,8 @@ module Hecks
|
|
|
282
313
|
# `bin/hecks_query_ir_mcp` (an MCP tool result, itself a text
|
|
283
314
|
# block) want the identical human-readable rendering; only the
|
|
284
315
|
# outer framing differs (plain stdout vs. a JSON-RPC content array).
|
|
316
|
+
# @param diffs [Array<Hash>] `constructs`' own output
|
|
317
|
+
# @return [String] the human-readable rendering
|
|
285
318
|
def format_constructs(diffs)
|
|
286
319
|
diffs.map do |diff|
|
|
287
320
|
lines = ["== #{diff[:name]} =="]
|
|
@@ -328,6 +361,12 @@ module Hecks
|
|
|
328
361
|
# existing gate (`model_shape_conformance_spec.rb`,
|
|
329
362
|
# `assembly_spec.rb`, `meta_domain_coverage_spec.rb`) before trusting
|
|
330
363
|
# a `false` here as a real gap.
|
|
364
|
+
# @param name [String] a `CONSTRUCTS` key, such as `"Aggregate"`
|
|
365
|
+
# @param field [String, Symbol] the declared field to check propagation for
|
|
366
|
+
# @return [Hash{Symbol => Object}] `:name`, `:field`, and `:touchpoints` — an
|
|
367
|
+
# `Array<Hash>` of `:touchpoint` (String) and `:present` (Boolean, or nil when the
|
|
368
|
+
# touchpoint does not apply to `name`)
|
|
369
|
+
# @raise [ArgumentError] if `name` is not a `CONSTRUCTS` key
|
|
331
370
|
def impact_preview(name, field)
|
|
332
371
|
CONSTRUCTS.fetch(name) { raise ArgumentError, "no such construct #{name.inspect} — known: #{CONSTRUCTS.keys.join(', ')}" }
|
|
333
372
|
field = field.to_s
|
|
@@ -402,6 +441,10 @@ module Hecks
|
|
|
402
441
|
end
|
|
403
442
|
private_class_method :rust_mentions?
|
|
404
443
|
|
|
444
|
+
# Renders one field's touchpoint checklist as text.
|
|
445
|
+
#
|
|
446
|
+
# @param preview [Hash] `impact_preview`'s own output
|
|
447
|
+
# @return [String] the human-readable rendering
|
|
405
448
|
def format_impact_preview(preview)
|
|
406
449
|
lines = ["== #{preview[:name]}##{preview[:field]} =="]
|
|
407
450
|
preview[:touchpoints].each do |t|
|
|
@@ -421,6 +464,10 @@ module Hecks
|
|
|
421
464
|
lines.join("\n")
|
|
422
465
|
end
|
|
423
466
|
|
|
467
|
+
# Renders the duplicate-rule groups as text.
|
|
468
|
+
#
|
|
469
|
+
# @param groups [Array<Hash>] `duplicates`' own output
|
|
470
|
+
# @return [String] the human-readable rendering
|
|
424
471
|
def format_duplicates(groups)
|
|
425
472
|
return "no duplicate given/invariant/ensures rule found" if groups.empty?
|
|
426
473
|
|
|
@@ -22,7 +22,8 @@ module Hecks
|
|
|
22
22
|
# `field DESC, id DESC`.
|
|
23
23
|
#
|
|
24
24
|
# @param records [Array<Object>] the rows to order, already in identity order
|
|
25
|
-
# @param direction [Symbol, String] `desc` sorts descending;
|
|
25
|
+
# @param direction [Symbol, String] `desc` in any letter case sorts descending;
|
|
26
|
+
# anything else ascending
|
|
26
27
|
# @param policy [NullSemantics, nil] where nulls go: mode `first` or `last`; `nil` or
|
|
27
28
|
# any other mode (`native`) puts nulls first ascending and last descending
|
|
28
29
|
# @yield reads the sort key out of one record
|
|
@@ -32,16 +33,17 @@ module Hecks
|
|
|
32
33
|
# @return [Array<Object>] a new Array holding every record in the requested order
|
|
33
34
|
# @raise [ArgumentError] if two non-nil keys cannot be compared with each other
|
|
34
35
|
def order(records, direction:, policy: nil, &key)
|
|
36
|
+
descending = direction.to_s.downcase == "desc"
|
|
35
37
|
null_rows, valued_rows = records.partition { |record| key.call(record).nil? }
|
|
36
38
|
sorted = valued_rows.each_with_index.sort_by { |record, index| [key.call(record), index] }.map(&:first)
|
|
37
|
-
if
|
|
39
|
+
if descending
|
|
38
40
|
sorted.reverse!
|
|
39
41
|
null_rows.reverse!
|
|
40
42
|
end
|
|
41
43
|
case policy&.mode.to_s
|
|
42
44
|
when "first" then null_rows + sorted
|
|
43
45
|
when "last" then sorted + null_rows
|
|
44
|
-
else
|
|
46
|
+
else descending ? sorted + null_rows : null_rows + sorted
|
|
45
47
|
end
|
|
46
48
|
end
|
|
47
49
|
|
data/lib/hecks/rendering.rb
CHANGED
|
@@ -17,6 +17,12 @@ module Hecks
|
|
|
17
17
|
module Rendering
|
|
18
18
|
module_function
|
|
19
19
|
|
|
20
|
+
# Renders a value the way it should read inside a refusal message.
|
|
21
|
+
#
|
|
22
|
+
# @param value [Object] the value to render
|
|
23
|
+
# @return [String] `"nil"` for nil, JSON for a Hash/Array or a duck-typed value
|
|
24
|
+
# object (unwrapped to its bare scalar when it has exactly one field), or
|
|
25
|
+
# `value.inspect` for anything else
|
|
20
26
|
def describe(value)
|
|
21
27
|
case value
|
|
22
28
|
when nil then "nil"
|
|
@@ -12,6 +12,13 @@ module Hecks
|
|
|
12
12
|
# to the router, pinned to this specific FQN version rather than
|
|
13
13
|
# the router's default resolution.
|
|
14
14
|
class OptionsProxy
|
|
15
|
+
# @param router [Router] router to dispatch resolved calls through
|
|
16
|
+
# @param realm [String, nil] realm segment of the FQN this proxy pins
|
|
17
|
+
# @param domain [String] domain segment of the FQN this proxy pins
|
|
18
|
+
# @param aggregate [String, nil] aggregate segment, or nil for a domain-level query
|
|
19
|
+
# @param options [Hash{Symbol => Object}] the `.options(...)` call's keywords; only
|
|
20
|
+
# `:version` is accepted
|
|
21
|
+
# @raise [ArgumentError] if `options` has a key other than `:version`
|
|
15
22
|
def initialize(router:, realm:, domain:, aggregate:, options:)
|
|
16
23
|
unknown = options.keys - [:version]
|
|
17
24
|
raise ArgumentError, "unknown router options: #{unknown.join(', ')}" unless unknown.empty?
|
|
@@ -45,10 +52,16 @@ module Hecks
|
|
|
45
52
|
end
|
|
46
53
|
end
|
|
47
54
|
|
|
55
|
+
# @param router [Router] router whose current routes get Ruby constants and methods
|
|
48
56
|
def initialize(router)
|
|
49
57
|
@router = router
|
|
50
58
|
end
|
|
51
59
|
|
|
60
|
+
# Installs every current-version route as a namespace constant/method,
|
|
61
|
+
# plus the aggregate `find`/`all`/`count`/`events`/`repository` door and
|
|
62
|
+
# short `Aggregate.verb` shortcuts.
|
|
63
|
+
#
|
|
64
|
+
# @return [self]
|
|
52
65
|
def install!
|
|
53
66
|
current_entries.each { |entry| install_namespace_entry(entry) }
|
|
54
67
|
install_shortcuts!
|
data/lib/hecks/router.rb
CHANGED
|
@@ -9,31 +9,60 @@ module Hecks
|
|
|
9
9
|
|
|
10
10
|
attr_reader :register
|
|
11
11
|
|
|
12
|
+
# Loads a project's bluebooks and builds a router over them, without
|
|
13
|
+
# installing any Ruby namespace shortcuts.
|
|
14
|
+
#
|
|
15
|
+
# @param root [String] project root to discover bluebooks under
|
|
16
|
+
# @return [Router] the built router
|
|
12
17
|
def self.load(root) = new(Bluebook::ProjectLoader.load(root))
|
|
13
18
|
|
|
14
19
|
# Install one project router for ordinary application calls. The explicit
|
|
15
20
|
# `Router.load` API remains useful for tests and embedded hosts.
|
|
21
|
+
#
|
|
22
|
+
# @param root [String] project root to discover bluebooks under
|
|
23
|
+
# @return [Router] the booted router, also stored as `.default`
|
|
16
24
|
def self.boot(root)
|
|
17
25
|
router = load(root)
|
|
18
26
|
router.install_namespace!
|
|
19
27
|
@default = router
|
|
20
28
|
end
|
|
21
29
|
|
|
30
|
+
# The process-wide router `.boot` installed.
|
|
31
|
+
#
|
|
32
|
+
# @return [Router] the router `.boot` installed
|
|
33
|
+
# @raise [NotBooted] if `.boot` has not been called yet
|
|
22
34
|
def self.default
|
|
23
35
|
@default || raise(NotBooted, "no project router is booted — call Hecks::Router.boot(root) first")
|
|
24
36
|
end
|
|
25
37
|
|
|
38
|
+
# (see #dispatch)
|
|
26
39
|
def self.dispatch(address, **args) = default.dispatch(address, **args)
|
|
40
|
+
# (see #query)
|
|
27
41
|
def self.query(address, **args) = default.query(address, **args)
|
|
28
42
|
|
|
43
|
+
# @param register [Bluebook::ProjectRegister, Bluebook::ProjectLoader] the FQN
|
|
44
|
+
# catalogue to resolve addresses against
|
|
29
45
|
def initialize(register)
|
|
30
46
|
@register = register
|
|
31
47
|
end
|
|
32
48
|
|
|
49
|
+
# Lists every routed FQN entry.
|
|
50
|
+
#
|
|
51
|
+
# @return [Array<Bluebook::ProjectRegister::Entry>] every routed FQN entry
|
|
33
52
|
def available = register.entries.values
|
|
34
53
|
|
|
54
|
+
# Installs Ruby namespace constants and shortcut methods for every
|
|
55
|
+
# current-version route.
|
|
56
|
+
#
|
|
57
|
+
# @return [NamespaceInstaller] the installer that performed the install
|
|
35
58
|
def install_namespace! = NamespaceInstaller.new(self).install!
|
|
36
59
|
|
|
60
|
+
# Resolves an address to its routed FQN entry.
|
|
61
|
+
#
|
|
62
|
+
# @param address [String] a fully-qualified command or query address, realm included
|
|
63
|
+
# @return [Bluebook::ProjectRegister::Entry] the routed entry
|
|
64
|
+
# @raise [UnknownAddress] if `address` has no realm, or names no known route
|
|
65
|
+
# @raise [Fqn::Invalid] if `address` is not a well-formed FQN
|
|
37
66
|
def resolve(address)
|
|
38
67
|
fqn = Fqn.parse(address)
|
|
39
68
|
raise UnknownAddress, "router addresses require a realm: #{address.inspect}" unless fqn.realm
|
|
@@ -43,6 +72,19 @@ module Hecks
|
|
|
43
72
|
raise UnknownAddress, "no Bluebook route for #{address.inspect}"
|
|
44
73
|
end
|
|
45
74
|
|
|
75
|
+
# Dispatches a command to its resolved aggregate.
|
|
76
|
+
#
|
|
77
|
+
# @param address [String] a fully-qualified command address, realm included
|
|
78
|
+
# @param args [Hash] command facts, plus the dispatcher's optional `:to`, `:with`,
|
|
79
|
+
# and `:saga_correlation` keys
|
|
80
|
+
# @return [Runtime::Dispatcher::Result] the dispatch result
|
|
81
|
+
# @raise [UnknownAddress] if `address` has no realm, or names no known route
|
|
82
|
+
# @raise [WrongVerbKind] if `address` names a query
|
|
83
|
+
# @raise [Runtime::UnknownVerb] if the resolved verb names something undeclared
|
|
84
|
+
# @raise [StandardError] any class in `Runtime::DOMAIN_REFUSALS` when the domain refuses
|
|
85
|
+
# the call
|
|
86
|
+
# @raise [Runtime::StaleWrite] if concurrent writers beat this one through every retry
|
|
87
|
+
# @raise [Runtime::WiringError] if the aggregate's repository cannot be resolved
|
|
46
88
|
def dispatch(address, **args)
|
|
47
89
|
entry = resolve(address)
|
|
48
90
|
raise WrongVerbKind, "#{address.inspect} names a query; use #query" unless entry.command?
|
|
@@ -50,6 +92,19 @@ module Hecks
|
|
|
50
92
|
entry.dispatcher.dispatch_flat(local_verb(entry), args)
|
|
51
93
|
end
|
|
52
94
|
|
|
95
|
+
# Queries a resolved aggregate, entity, or read model.
|
|
96
|
+
#
|
|
97
|
+
# @param address [String] a fully-qualified query address, realm included
|
|
98
|
+
# @param args [Hash{Symbol => Object}] the query's declared arguments
|
|
99
|
+
# @return [Array<Hash>] one row Hash per match; see `Runtime::Dispatcher#query`
|
|
100
|
+
# for the exact shape per address kind
|
|
101
|
+
# @raise [UnknownAddress] if `address` has no realm, or names no known route
|
|
102
|
+
# @raise [WrongVerbKind] if `address` names a command
|
|
103
|
+
# @raise [Runtime::UnknownVerb] if the resolved verb names something undeclared
|
|
104
|
+
# @raise [Runtime::NotFound] if a read model's root reference names no record
|
|
105
|
+
# @raise [Runtime::TypeMismatch] if an argument cannot be coerced to its declared type
|
|
106
|
+
# @raise [KeyError] if a rooted read model is asked without its reference argument
|
|
107
|
+
# @raise [Runtime::WiringError] if the aggregate's repository cannot be resolved
|
|
53
108
|
def query(address, **args)
|
|
54
109
|
entry = resolve(address)
|
|
55
110
|
raise WrongVerbKind, "#{address.inspect} names a command; use #dispatch" unless entry.query?
|
|
@@ -29,12 +29,21 @@ module Hecks
|
|
|
29
29
|
@locks = {}
|
|
30
30
|
|
|
31
31
|
class << self
|
|
32
|
+
# Returns the Mutex striped to one record, creating it on first use.
|
|
33
|
+
#
|
|
32
34
|
# `AggregateLock.for(domain, aggregate, id).synchronize { ... }`
|
|
33
35
|
# `id: nil` — identity could not be resolved yet (see
|
|
34
36
|
# `Identity.best_effort`) — locks by aggregate type alone, coarser
|
|
35
37
|
# (every record of this aggregate serializes against every other)
|
|
36
38
|
# but still correct: it can only ever make dispatch more
|
|
37
39
|
# conservative than a resolved id would.
|
|
40
|
+
#
|
|
41
|
+
# @param domain [String] the domain the aggregate belongs to
|
|
42
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate being dispatched
|
|
43
|
+
# @param id [String, nil] the resolved record id, or nil to stripe by aggregate type
|
|
44
|
+
# alone
|
|
45
|
+
# @return [Mutex] the mutex for this `[domain, aggregate, id]` key, held for the full
|
|
46
|
+
# hydrate-through-save critical section
|
|
38
47
|
def for(domain, aggregate, id = nil)
|
|
39
48
|
key = id.nil? ? [domain.to_s, aggregate.hecks_name] : [domain.to_s, aggregate.hecks_name, id.to_s]
|
|
40
49
|
@registry_lock.synchronize { @locks[key] ||= Mutex.new }
|
|
@@ -20,19 +20,37 @@ module Hecks
|
|
|
20
20
|
# Sharing a primitive between them is deferred until a third consumer
|
|
21
21
|
# actually wants it (0031's own Rejected Alternatives).
|
|
22
22
|
class BootGates
|
|
23
|
+
# @return [void]
|
|
23
24
|
def initialize
|
|
24
25
|
@gates = Hash.new { |h, k| h[k] = [] }
|
|
25
26
|
end
|
|
26
27
|
|
|
28
|
+
# Adds a gate to `phase`, run in registration order alongside any other gate there.
|
|
29
|
+
#
|
|
30
|
+
# @param name [Symbol] the gate's name, checked by `registered?`
|
|
31
|
+
# @param gate [Proc, Method] the gate; called as `gate.call(registry, directory)`
|
|
32
|
+
# @param phase [Symbol] the phase this gate runs under, such as `:pre_verify` or
|
|
33
|
+
# `:post_verify`
|
|
34
|
+
# @return [Hecks::Runtime::BootGates] self, for chaining
|
|
27
35
|
def register(name, gate, phase:)
|
|
28
36
|
@gates[phase] << [name, gate]
|
|
29
37
|
self
|
|
30
38
|
end
|
|
31
39
|
|
|
40
|
+
# Reports whether a gate named `name` has been registered, under any phase.
|
|
41
|
+
#
|
|
42
|
+
# @param name [Symbol] the gate name to look for, across every phase
|
|
43
|
+
# @return [Boolean] true if a gate named `name` was registered under any phase
|
|
32
44
|
def registered?(name)
|
|
33
45
|
@gates.values.flatten(1).any? { |registered_name, _gate| registered_name == name }
|
|
34
46
|
end
|
|
35
47
|
|
|
48
|
+
# Runs every gate registered under `phase`, in registration order.
|
|
49
|
+
#
|
|
50
|
+
# @param phase [Symbol] the phase to run, such as `:pre_verify` or `:post_verify`
|
|
51
|
+
# @param registry [Runtime::Registry] the booted registry, passed to each gate
|
|
52
|
+
# @param directory [String] the boot directory, passed to each gate
|
|
53
|
+
# @return [void]
|
|
36
54
|
def run!(phase, registry, directory)
|
|
37
55
|
@gates[phase].each { |pair| pair.last.call(registry, directory) }
|
|
38
56
|
end
|
data/lib/hecks/runtime/caller.rb
CHANGED
|
@@ -37,8 +37,34 @@ module Hecks
|
|
|
37
37
|
|
|
38
38
|
module_function
|
|
39
39
|
|
|
40
|
+
# The ambient caller bound by the innermost enclosing `as` block on this thread.
|
|
41
|
+
#
|
|
42
|
+
# @return [Runtime::Caller::Current, nil] the bound caller, or nil when no `as`
|
|
43
|
+
# block is on the stack for this thread
|
|
40
44
|
def current = Thread.current[:hecks_caller]
|
|
41
45
|
|
|
46
|
+
# Binds the ambient caller for the duration of the block, restoring whatever
|
|
47
|
+
# caller (if any) was bound before, even if the block raises.
|
|
48
|
+
#
|
|
49
|
+
# `actor_id`, `as_of` and `scope` are each optional, self-asserted facts about
|
|
50
|
+
# the caller rather than derived from the command — see `Current`'s own header
|
|
51
|
+
# above for why each stays here instead of becoming a command-level DSL
|
|
52
|
+
# construct.
|
|
53
|
+
#
|
|
54
|
+
# @param role [String] the role the caller states it holds, checked by string
|
|
55
|
+
# equality against the command's declared `role`
|
|
56
|
+
# @param actor_id [String, nil] who the caller is; when given, lets
|
|
57
|
+
# `CommandRules::Authorization` check a real Governance `RoleAssignment`
|
|
58
|
+
# instead of the bare role-string comparison
|
|
59
|
+
# @param as_of [Integer, nil] Unix epoch seconds the caller asserts as "now",
|
|
60
|
+
# filled at the door from `Ports::Clock.now`; nil leaves a `RoleAssignment`'s
|
|
61
|
+
# `starts_at` unchecked
|
|
62
|
+
# @param scope [String, nil] the scope the caller states it is acting in,
|
|
63
|
+
# checked against the matching `RoleAssignment`'s own `scope`; nil skips
|
|
64
|
+
# that check
|
|
65
|
+
# @yield the code that should see `role`/`actor_id`/`as_of`/`scope` as the
|
|
66
|
+
# ambient caller
|
|
67
|
+
# @return [Object] the block's result
|
|
42
68
|
def as(role:, actor_id: nil, as_of: nil, scope: nil)
|
|
43
69
|
previous = Thread.current[:hecks_caller]
|
|
44
70
|
Thread.current[:hecks_caller] = Current.new(
|
|
@@ -49,11 +75,17 @@ module Hecks
|
|
|
49
75
|
Thread.current[:hecks_caller] = previous
|
|
50
76
|
end
|
|
51
77
|
|
|
78
|
+
# Clears the ambient caller for the duration of the block, restoring
|
|
79
|
+
# whatever caller (if any) was bound before, even if the block raises.
|
|
80
|
+
#
|
|
52
81
|
# A reaction is the system acting, not the caller who happened to be
|
|
53
82
|
# on the stack when the triggering command ran — `Dispatcher#reenter`
|
|
54
83
|
# clears the ambient caller around a reaction's own dispatch so a
|
|
55
84
|
# triggering caller's role can neither satisfy nor block a reaction
|
|
56
85
|
# command it has nothing to do with.
|
|
86
|
+
#
|
|
87
|
+
# @yield the code that should see no ambient caller bound
|
|
88
|
+
# @return [Object] the block's result
|
|
57
89
|
def without
|
|
58
90
|
previous = Thread.current[:hecks_caller]
|
|
59
91
|
Thread.current[:hecks_caller] = nil
|
|
@@ -14,6 +14,8 @@ module Hecks
|
|
|
14
14
|
# adapter names the port it implements, and neither can point at a third
|
|
15
15
|
# port. There is no edge for a cycle to be made of.
|
|
16
16
|
class CapabilityGraph
|
|
17
|
+
# @param registry [Runtime::Registry] the booted registry to read ports and adapters from
|
|
18
|
+
# @return [void]
|
|
17
19
|
def initialize(registry)
|
|
18
20
|
@registry = registry
|
|
19
21
|
end
|
|
@@ -21,6 +23,9 @@ module Hecks
|
|
|
21
23
|
# { port name => [adapter name, ...] }, for every port the registry
|
|
22
24
|
# declares — including the ports nothing implements, so a caller can
|
|
23
25
|
# tell "declared, unfulfilled" from "never declared at all".
|
|
26
|
+
#
|
|
27
|
+
# @return [Hash{String => Array<String>}] each declared port's name mapped to the names
|
|
28
|
+
# of every adapter bound to it (empty when none are)
|
|
24
29
|
def fulfillments
|
|
25
30
|
@fulfillments ||= @registry.ports.each_key.to_h { |name| [name, adapters_for(name)] }
|
|
26
31
|
end
|
|
@@ -28,10 +33,16 @@ module Hecks
|
|
|
28
33
|
# The ports with zero adapters bound — the gap `Runtime::WiringError`
|
|
29
34
|
# would otherwise only surface at the moment something tries to dispatch
|
|
30
35
|
# through one.
|
|
36
|
+
#
|
|
37
|
+
# @return [Array<String>] the names of every declared port with no adapter bound to it
|
|
31
38
|
def unfulfilled
|
|
32
39
|
fulfillments.select { |_, adapters| adapters.empty? }.keys
|
|
33
40
|
end
|
|
34
41
|
|
|
42
|
+
# Reports the port-dependency cycles this registry has — always none; see the module
|
|
43
|
+
# header for why no port model here can express one.
|
|
44
|
+
#
|
|
45
|
+
# @return [Array] always empty
|
|
35
46
|
def cycles = []
|
|
36
47
|
|
|
37
48
|
private
|
|
@@ -10,10 +10,10 @@ module Hecks
|
|
|
10
10
|
module ArgumentGate
|
|
11
11
|
private
|
|
12
12
|
|
|
13
|
-
#
|
|
14
|
-
# normalize_args walks the declared attributes, so
|
|
15
|
-
# never had
|
|
16
|
-
# nothing, in silence.
|
|
13
|
+
# Without this check, anything else would ride along in the payload
|
|
14
|
+
# untouched — normalize_args walks only the declared attributes, so
|
|
15
|
+
# a name the command never had is simply never looked at, and a
|
|
16
|
+
# misspelled argument would do nothing, in silence.
|
|
17
17
|
#
|
|
18
18
|
# The keys that are legitimately not attributes are the ones that address
|
|
19
19
|
# the aggregate rather than describe it : `id`, whatever the aggregate is
|
|
@@ -49,12 +49,13 @@ module Hecks
|
|
|
49
49
|
# until fuzz went looking : a name the command never declared was refused,
|
|
50
50
|
# while a name it did declare could simply be left out.
|
|
51
51
|
#
|
|
52
|
-
# `Customer.Register`
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
52
|
+
# Refusing `Customer.Register` for a missing `name` needs to be
|
|
53
|
+
# deliberate, not an accident with a lie for a message. Without this
|
|
54
|
+
# check, `then_set :name, to: :name` finds nothing to resolve, passes
|
|
55
|
+
# the literal symbol on, and coercion reports `name is a PersonName —
|
|
56
|
+
# pass its fields as an object`, which describes a mistake the caller
|
|
57
|
+
# did not make. The real mistake — an argument simply missing — is
|
|
58
|
+
# never the one named unless this refuses it on purpose.
|
|
58
59
|
#
|
|
59
60
|
# No command attribute anywhere in the corpus carries a default — checked,
|
|
60
61
|
# all eight chapters, zero — so there is no optional argument for this to
|
|
@@ -65,16 +66,17 @@ module Hecks
|
|
|
65
66
|
# (`CommandBuilder#reference_to`'s bare self-reference mints no
|
|
66
67
|
# attribute to be absent) — but `PortOperationBuilder#reference_to`
|
|
67
68
|
# always mints one (this file's own header on `PortOperation`: "no
|
|
68
|
-
# creates?/acts_on distinction to protect"), because an operation
|
|
69
|
-
#
|
|
70
|
-
# `to:` is that
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
#
|
|
74
|
-
# operation with an identity attribute
|
|
75
|
-
# calls, dispatched exactly the way `to:`
|
|
76
|
-
# the same terms an aggregate command's
|
|
77
|
-
# an address, not a fact the operation
|
|
69
|
+
# creates?/acts_on distinction to protect"), because an operation had
|
|
70
|
+
# no other way to say which record it addressed before `to:` existed.
|
|
71
|
+
# `to:` is that way now (`Dispatcher#port_invocation` promotes the
|
|
72
|
+
# identity attribute's own value out of the payload and into
|
|
73
|
+
# routing) — which leaves the attribute still declared, still
|
|
74
|
+
# non-optional, and never present in `args` at all: without this
|
|
75
|
+
# exemption, every operation with an identity attribute would refuse
|
|
76
|
+
# its own well-formed calls, dispatched exactly the way `to:`
|
|
77
|
+
# intends. Exempted here on the same terms an aggregate command's
|
|
78
|
+
# self-address always was — an address, not a fact the operation
|
|
79
|
+
# still needs handed back.
|
|
78
80
|
def refuse_absent_arguments(command, args, aggregate: nil)
|
|
79
81
|
given = args.keys.map(&:to_sym)
|
|
80
82
|
exempt = aggregate && command.respond_to?(:identity_attribute) &&
|
|
@@ -92,7 +94,7 @@ module Hecks
|
|
|
92
94
|
declared: declared_names(command))
|
|
93
95
|
end
|
|
94
96
|
|
|
95
|
-
#
|
|
97
|
+
# A command that declares nothing still has to say so. `Account
|
|
96
98
|
# .Freeze` is `reference_to Account` and no attributes at all, so
|
|
97
99
|
# `{declared}` rendered empty and the sentence trailed off mid-
|
|
98
100
|
# clause : "Freeze does not declare standing — it takes ". Read
|
|
@@ -165,9 +165,9 @@ module Hecks
|
|
|
165
165
|
fields)
|
|
166
166
|
end
|
|
167
167
|
|
|
168
|
-
# Frozen, like every other value the domain hands back
|
|
169
|
-
# appended list
|
|
170
|
-
# push straight into an aggregate's own state after the
|
|
168
|
+
# Frozen, like every other value the domain hands back — without
|
|
169
|
+
# this, an appended list would come back mutable, letting a
|
|
170
|
+
# caller push straight into an aggregate's own state after the
|
|
171
171
|
# dispatch had finished.
|
|
172
172
|
Freezer.deep(Array(instance[mutation.target]) + [element])
|
|
173
173
|
end
|
|
@@ -197,11 +197,11 @@ module Hecks
|
|
|
197
197
|
|
|
198
198
|
# Vendored fix, not (yet) upstream hecks (migration plan
|
|
199
199
|
# task 9): #apply's `:increment`/`:decrement`/`:multiply`
|
|
200
|
-
# branches
|
|
200
|
+
# branches wrapping `amount` into a `Value` unconditionally
|
|
201
201
|
# whenever the target attribute existed, never checking whether
|
|
202
202
|
# `current` (the field's own existing value, read straight off
|
|
203
|
-
# `instance[mutation.target]`) was also wrapped
|
|
204
|
-
# of the same arithmetic call
|
|
203
|
+
# `instance[mutation.target]`) was also wrapped, would let the two
|
|
204
|
+
# sides of the same arithmetic call disagree on Value-ness. On a
|
|
205
205
|
# phantom-created field this is the common case, not an edge
|
|
206
206
|
# one: `Instance.defaults`/`#default_for` leaves a VO-typed
|
|
207
207
|
# attribute with no declared `default:` genuinely absent (nil),
|
|
@@ -276,16 +276,15 @@ module Hecks
|
|
|
276
276
|
held.max.to_i + 1
|
|
277
277
|
end
|
|
278
278
|
|
|
279
|
-
#
|
|
280
|
-
# — BUG#145
|
|
281
|
-
#
|
|
279
|
+
# The entity-collision check lives in `EntityElement.check_entity_collision`
|
|
280
|
+
# (entity_element.rb) — BUG#145 — shared by both `#entity_element` above
|
|
281
|
+
# (an aggregate's own entity list, e.g. `Workspace.boards`) and
|
|
282
282
|
# `EntityElement#appended_to_element`'s own nested-entity branch (an
|
|
283
|
-
# entity's own entity list one hop further in, e.g. `Board.cards`)
|
|
284
|
-
# needs the exact same guard
|
|
285
|
-
#
|
|
286
|
-
#
|
|
287
|
-
#
|
|
288
|
-
# auto-mint reasoning) now lives with the code, in entity_element.rb.
|
|
283
|
+
# entity's own entity list one hop further in, e.g. `Board.cards`),
|
|
284
|
+
# which needs the exact same guard against a caller-supplied nested
|
|
285
|
+
# identity silently duplicating. See that method's own call site and
|
|
286
|
+
# comment; the check's own doc comment (heads/composite/auto-mint
|
|
287
|
+
# reasoning) lives with the code, in entity_element.rb.
|
|
289
288
|
end
|
|
290
289
|
end
|
|
291
290
|
end
|