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
|
@@ -15,10 +15,10 @@ module Hecks
|
|
|
15
15
|
# own header, and this repo's `.rubocop_todo.yml` for the merge that
|
|
16
16
|
# made the split necessary). Cross-calls into `Coercion`'s own
|
|
17
17
|
# `for_attribute`/`build`/`fields_for`/`value_object_for`/
|
|
18
|
-
# `trusting_stored_state?` work unqualified here exactly as they
|
|
19
|
-
#
|
|
20
|
-
# singleton class once extended — `self` never has to know
|
|
21
|
-
# a sibling method actually lives in.
|
|
18
|
+
# `trusting_stored_state?` work unqualified here, exactly as they do
|
|
19
|
+
# within `Coercion` itself, because both modules land on the same
|
|
20
|
+
# `Value` singleton class once extended — `self` never has to know
|
|
21
|
+
# which file a sibling method actually lives in.
|
|
22
22
|
module EntityListCoercion
|
|
23
23
|
# S17, ADR 0026 — searches the whole entity tree, not only the
|
|
24
24
|
# root's own direct children. `aggregate` here is always the
|
|
@@ -31,6 +31,12 @@ module Hecks
|
|
|
31
31
|
# a nested entity — Dispatch, inside Handler — is not a direct
|
|
32
32
|
# child of the root the way Handler itself is, and a plain
|
|
33
33
|
# `aggregate.entities.find` stops one level short of it.
|
|
34
|
+
#
|
|
35
|
+
# @param construct [Bluebook::Aggregate, Bluebook::Entity] the construct to
|
|
36
|
+
# search, and every entity nested under it
|
|
37
|
+
# @param name [String] the entity's declared `hecks_name` to find
|
|
38
|
+
# @return [Bluebook::Entity, nil] the matching entity anywhere in the tree, or
|
|
39
|
+
# nil if none matches
|
|
34
40
|
def find_entity(construct, name)
|
|
35
41
|
construct.entities.each do |candidate|
|
|
36
42
|
return candidate if candidate.hecks_name == name
|
|
@@ -41,39 +47,54 @@ module Hecks
|
|
|
41
47
|
nil
|
|
42
48
|
end
|
|
43
49
|
|
|
50
|
+
# Hydrates a `list_of` attribute's offered value — each element of a
|
|
51
|
+
# whole-list offering, or a single remove-target value, depending on
|
|
52
|
+
# `attribute.type` and `value`'s own shape.
|
|
53
|
+
#
|
|
44
54
|
# Frozen through: a list read back out of the store is an answer,
|
|
45
55
|
# not a handle on what is stored.
|
|
46
56
|
#
|
|
47
|
-
# ADR 0047 —
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
57
|
+
# ADR 0047 — bailing (`return value unless entity`) the moment
|
|
58
|
+
# `attribute.type` names a value object rather than an entity would
|
|
59
|
+
# hand back the raw, un-hydrated argument untouched. A `sets :field`
|
|
60
|
+
# mutation sourced from a whole-array argument (as opposed to
|
|
61
|
+
# element-by-element `append:`) goes straight through
|
|
52
62
|
# `for_attribute`'s `:list` branch, so `Banking::CardPayment.
|
|
53
|
-
# Authorize`'s own `sets :tags` (`list_of(Tag)`)
|
|
63
|
+
# Authorize`'s own `sets :tags` (`list_of(Tag)`) would store plain
|
|
54
64
|
# Ruby Hashes as its `tags` elements forever — never a real
|
|
55
65
|
# `Value`, never through `Tag`'s own `pattern:`/`invariant`
|
|
56
66
|
# checks. `remove:`'s `==` comparison (a real `Value` against a
|
|
57
|
-
# raw `Hash`) then always
|
|
58
|
-
# that isn't itself a compatible Hash — the bug ADR 0047
|
|
59
|
-
# full. Delegating to `hydrate_value_object_list` below
|
|
60
|
-
# that gap the same way the entity branch already
|
|
61
|
-
# real, validated `Value` per element, reusing
|
|
62
|
-
# own composite-construction path rather than
|
|
63
|
-
# one.
|
|
64
|
-
# BUG#32 (QualityControl ledger) — `remove:`'s own
|
|
65
|
-
# value
|
|
66
|
-
# below unguarded, unlike this method's
|
|
67
|
-
# `hydrate_value_object_list` sibling, whose
|
|
68
|
-
# check exists for exactly this shape (see
|
|
69
|
-
# comment). For an entity-typed list, `remove:`'s
|
|
70
|
-
# (`Ledger.Void`'s `sequence: EntrySequence`) is
|
|
71
|
-
# `Array(2)` merely
|
|
72
|
-
# but the wrapping itself
|
|
73
|
-
# removed`'s `element == value` then
|
|
74
|
-
# Hash against a one-element Array, which can
|
|
75
|
-
# Hash, so nothing
|
|
76
|
-
# either. Routed to `hydrate_entity_identity`
|
|
67
|
+
# raw `Hash`) would then always fail, since `Hash#==` refuses
|
|
68
|
+
# anything that isn't itself a compatible Hash — the bug ADR 0047
|
|
69
|
+
# traces in full. Delegating to `hydrate_value_object_list` below
|
|
70
|
+
# closes that gap the same way the entity branch already works:
|
|
71
|
+
# build a real, validated `Value` per element, reusing
|
|
72
|
+
# `for_attribute`'s own composite-construction path rather than
|
|
73
|
+
# inventing a second one.
|
|
74
|
+
# BUG#32 (QualityControl ledger) — without this, `remove:`'s own
|
|
75
|
+
# single-target value would fall straight into
|
|
76
|
+
# `Array(value).map { ... }` below unguarded, unlike this method's
|
|
77
|
+
# own delegated `hydrate_value_object_list` sibling, whose
|
|
78
|
+
# `value.is_a?(Array)` check exists for exactly this shape (see
|
|
79
|
+
# that method's own comment). For an entity-typed list, `remove:`'s
|
|
80
|
+
# scalar target (`Ledger.Void`'s `sequence: EntrySequence`) is
|
|
81
|
+
# never a Hash, so `Array(2)` would merely wrap it as `[2]` rather
|
|
82
|
+
# than shredding it — but the wrapping itself would still be wrong:
|
|
83
|
+
# `MutationApplier#removed`'s `element == value` would then compare
|
|
84
|
+
# a stored `Entry` Hash against a one-element Array, which can
|
|
85
|
+
# never be `==` a Hash, so nothing would ever be removed and
|
|
86
|
+
# nothing would ever refuse either. Routed to `hydrate_entity_identity`
|
|
87
|
+
# instead, below.
|
|
88
|
+
#
|
|
89
|
+
# @param aggregate [Bluebook::Aggregate] the root aggregate `attribute` is
|
|
90
|
+
# declared on
|
|
91
|
+
# @param attribute [Bluebook::Attribute] the `list_of` attribute being hydrated
|
|
92
|
+
# @param value [Array<Hash, Object>, Object] the offered value: a whole-list
|
|
93
|
+
# Array (a `sets`/hydrate load) or a single remove-target value
|
|
94
|
+
# @return [Object] a frozen Array of hydrated elements when `value` is a
|
|
95
|
+
# whole-list Array and `attribute.type` names an entity; otherwise whatever
|
|
96
|
+
# `hydrate_entity_identity` (a non-Array value naming an entity) or
|
|
97
|
+
# `hydrate_value_object_list` (`attribute.type` naming no entity) returns
|
|
77
98
|
def hydrate_entity_list(aggregate, attribute, value)
|
|
78
99
|
entity = find_entity(aggregate, attribute.type.to_s)
|
|
79
100
|
return hydrate_value_object_list(aggregate, attribute, value) unless entity
|
|
@@ -127,10 +148,23 @@ module Hecks
|
|
|
127
148
|
# same pre-existing, narrower gap BUG#13 documented and left open,
|
|
128
149
|
# not widened here.
|
|
129
150
|
#
|
|
151
|
+
# Refuses a whole-list offering that misnames its own entities' identities —
|
|
152
|
+
# a duplicate identity across two elements, or an element missing one.
|
|
153
|
+
#
|
|
130
154
|
# Skipped under `trusting_stored_state?`, the same guard `validate!`
|
|
131
155
|
# already gives every value object (C6.3): a record already
|
|
132
156
|
# written is trusted as it was, so tightening this check can never
|
|
133
157
|
# make an old, already-persisted record unreadable.
|
|
158
|
+
#
|
|
159
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate `entity` belongs to,
|
|
160
|
+
# named in a refusal
|
|
161
|
+
# @param entity [Bluebook::Entity] the entity type `elements` are shaped as
|
|
162
|
+
# @param elements [Array<Hash, Object>] the hydrated whole-list offering; a
|
|
163
|
+
# non-Hash element is skipped, not checked
|
|
164
|
+
# @return [void]
|
|
165
|
+
# @raise [Runtime::TypeMismatch] if `entity` declares a single-field identity
|
|
166
|
+
# and an element offers no value for it
|
|
167
|
+
# @raise [Runtime::AlreadyExists] if two elements offer the same identity value
|
|
134
168
|
def check_entity_list_identities(aggregate, entity, elements)
|
|
135
169
|
identity = entity.identified_by
|
|
136
170
|
return unless identity
|
|
@@ -183,9 +217,20 @@ module Hecks
|
|
|
183
217
|
# "nothing in this corpus needs it yet" boundary
|
|
184
218
|
# `MutationApplier#check_entity_collision`'s own header already
|
|
185
219
|
# draws for entity identity elsewhere in this runtime.
|
|
220
|
+
# Coerces a `remove:` target against an entity's own single-field
|
|
221
|
+
# identity type, rather than the entity's full shape.
|
|
222
|
+
#
|
|
186
223
|
# `list_element_match?` treats an uncoerced value the same way
|
|
187
224
|
# it always treated the pre-fix wrapped Array: never a match, a
|
|
188
225
|
# documented no-op rather than a crash.
|
|
226
|
+
#
|
|
227
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate `entity` belongs to
|
|
228
|
+
# @param entity [Bluebook::Entity] the entity type `value` addresses
|
|
229
|
+
# @param value [Object] the offered remove-target value
|
|
230
|
+
# @return [Runtime::Value, Object] `value` already coerced (returned unchanged);
|
|
231
|
+
# the value coerced against the identity field's declared type when `entity`
|
|
232
|
+
# has a single identity head and a matching attribute; `value` unchanged
|
|
233
|
+
# otherwise (no identity head, a composite identity, or no matching attribute)
|
|
189
234
|
def hydrate_entity_identity(aggregate, entity, value)
|
|
190
235
|
return value if value.is_a?(self)
|
|
191
236
|
|
|
@@ -198,6 +243,9 @@ module Hecks
|
|
|
198
243
|
for_attribute(aggregate, field, value)
|
|
199
244
|
end
|
|
200
245
|
|
|
246
|
+
# Hydrates a `list_of` attribute's offered value when `attribute.type`
|
|
247
|
+
# names a value object rather than an entity.
|
|
248
|
+
#
|
|
201
249
|
# The value-object sibling of the entity branch above: an element
|
|
202
250
|
# already shaped like the target `Value` (or a `Hash`/scalar that
|
|
203
251
|
# `fields_for` can still open) is rebuilt through the same `build`
|
|
@@ -225,6 +273,17 @@ module Hecks
|
|
|
225
273
|
# `value.is_a?(Array)` up front (true only for a genuine whole-list
|
|
226
274
|
# `sets`/hydrate load) keeps the single-target shape a single
|
|
227
275
|
# target, hydrated the same way, never listified.
|
|
276
|
+
#
|
|
277
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate `attribute` is declared
|
|
278
|
+
# on, or a construct that does not respond to `value_object` (an entity), in
|
|
279
|
+
# which case `value` passes through unchanged
|
|
280
|
+
# @param attribute [Bluebook::Attribute] the `list_of` attribute being hydrated
|
|
281
|
+
# @param value [Array<Hash, Object>, Object] the offered value: a whole-list
|
|
282
|
+
# Array, or a single remove-target value
|
|
283
|
+
# @return [Object] `value` unchanged when `aggregate` does not respond to
|
|
284
|
+
# `value_object`, or `attribute.type` names no value object; otherwise a
|
|
285
|
+
# frozen Array of built `Value`s for a whole-list Array, or a single built
|
|
286
|
+
# `Value` (via `hydrate_value_object_element`) otherwise
|
|
228
287
|
def hydrate_value_object_list(aggregate, attribute, value)
|
|
229
288
|
return value unless aggregate.respond_to?(:value_object)
|
|
230
289
|
|
|
@@ -237,6 +296,19 @@ module Hecks
|
|
|
237
296
|
Freezer.deep(hydrated)
|
|
238
297
|
end
|
|
239
298
|
|
|
299
|
+
# Rebuilds one `list_of` element into a real, validated `Value`, unless it
|
|
300
|
+
# already is one of `value_object`'s own type.
|
|
301
|
+
#
|
|
302
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate `value_object` is
|
|
303
|
+
# resolved against
|
|
304
|
+
# @param attribute [Bluebook::Attribute] the `list_of` attribute `element`
|
|
305
|
+
# belongs to
|
|
306
|
+
# @param value_object [Class] the `Bluebook::ValueObject` subclass `element`
|
|
307
|
+
# is built as
|
|
308
|
+
# @param element [Object] the raw element to build: a Hash of fields, a scalar
|
|
309
|
+
# `fields_for` can still open, or an already-built `Value`
|
|
310
|
+
# @return [Runtime::Value] `element` unchanged if it is already a `Value` of
|
|
311
|
+
# `value_object`'s own type; otherwise a freshly built one
|
|
240
312
|
def hydrate_value_object_element(aggregate, attribute, value_object, element)
|
|
241
313
|
return element if element.is_a?(self) && element.type_name == value_object.hecks_name
|
|
242
314
|
|
data/lib/hecks/runtime/value.rb
CHANGED
|
@@ -36,26 +36,64 @@ module Hecks
|
|
|
36
36
|
# identity to change over — `with` already answers a new one rather
|
|
37
37
|
# than mutating — so freezing it through is what it always claimed
|
|
38
38
|
# to be.
|
|
39
|
+
#
|
|
40
|
+
# @param value_object [Bluebook::ValueObject] the declared type this instance is one of
|
|
41
|
+
# @param fields [Hash] the type's fields, keyed by name (String or Symbol); deep-frozen
|
|
42
|
+
# and stored with Symbol keys
|
|
39
43
|
def initialize(value_object, fields)
|
|
40
44
|
@value_object = value_object
|
|
41
45
|
@fields = Freezer.deep(fields.transform_keys(&:to_sym))
|
|
42
46
|
freeze
|
|
43
47
|
end
|
|
44
48
|
|
|
49
|
+
# Reads the declared type's own name.
|
|
50
|
+
#
|
|
51
|
+
# @return [String] the value object's `hecks_name`
|
|
45
52
|
def type_name = @value_object.hecks_name
|
|
53
|
+
|
|
54
|
+
# Reads one field.
|
|
55
|
+
#
|
|
56
|
+
# @param field [String, Symbol] the field name; `:value` reads the sole field of a
|
|
57
|
+
# single-attribute value object, whatever it is actually named
|
|
58
|
+
# @return [Object, nil] the field's coerced value; nil if the field is not held
|
|
46
59
|
def [](field) = @fields[resolve_field(field)]
|
|
60
|
+
|
|
61
|
+
# Answers whether this value object holds the named field.
|
|
62
|
+
#
|
|
63
|
+
# @param field [String, Symbol] the field name; `:value` resolves the same way `[]` does
|
|
64
|
+
# @return [Boolean] true when the field is held
|
|
47
65
|
def key?(field) = @fields.key?(resolve_field(field))
|
|
48
66
|
def to_h = @fields.transform_values { |value| self.class.materialize(value) }
|
|
67
|
+
|
|
68
|
+
# Renders this value object as JSON, through the same shape `to_h` builds.
|
|
69
|
+
#
|
|
70
|
+
# @return [String] a JSON object of the materialized fields
|
|
49
71
|
def to_json(*) = JSON.generate(to_h)
|
|
50
72
|
|
|
51
73
|
def ==(other)
|
|
52
74
|
other.is_a?(self.class) && other.type_name == type_name && other.to_h == to_h
|
|
53
75
|
end
|
|
54
76
|
|
|
77
|
+
# Builds a new value object of the same type with one field replaced, re-validated.
|
|
78
|
+
#
|
|
79
|
+
# @param field [String, Symbol] the field to replace; `:value` resolves the same way
|
|
80
|
+
# `[]` does
|
|
81
|
+
# @param value [Object] the field's new, uncoerced value
|
|
82
|
+
# @return [Runtime::Value] a new instance of the same type, with `field` replaced
|
|
83
|
+
# @raise [Runtime::TypeMismatch] if the new fields do not satisfy the type's declared
|
|
84
|
+
# shape (an unknown field, a missing required one, a wrong numeric type, …)
|
|
85
|
+
# @raise [Runtime::InvariantViolation] if the new fields violate one of the type's own
|
|
86
|
+
# invariants, or are not a member of its closed set
|
|
55
87
|
def with(field, value)
|
|
56
88
|
self.class.build(@value_object, @fields.merge(resolve_field(field) => value))
|
|
57
89
|
end
|
|
58
90
|
|
|
91
|
+
# Recursively converts a `Runtime::Value` (and any nested inside a Hash or Array) to
|
|
92
|
+
# plain data.
|
|
93
|
+
#
|
|
94
|
+
# @param value [Object] the value to materialize; anything that is not a `Runtime::Value`,
|
|
95
|
+
# Array or Hash passes through unchanged
|
|
96
|
+
# @return [Object] `value` with every nested `Runtime::Value` replaced by its own `to_h`
|
|
59
97
|
def self.materialize(value)
|
|
60
98
|
case value
|
|
61
99
|
when self then value.to_h
|
|
@@ -77,6 +115,12 @@ module Hecks
|
|
|
77
115
|
# used only for a `group_by`-declared head's own rows — grouping
|
|
78
116
|
# needs a real scalar to key by regardless, so a report already
|
|
79
117
|
# asking for that gets the unwrap for free.
|
|
118
|
+
#
|
|
119
|
+
# @param value [Object] the value to materialize; anything that is not a `Runtime::Value`,
|
|
120
|
+
# Array or Hash passes through unchanged
|
|
121
|
+
# @return [Object] `value` with every nested `Runtime::Value` replaced by its own sole
|
|
122
|
+
# field's value (recursively unwrapped), or by its own `Hash` of fields when it has
|
|
123
|
+
# more than one
|
|
80
124
|
def self.materialize_unwrapped(value)
|
|
81
125
|
case value
|
|
82
126
|
when self
|
|
@@ -112,6 +156,12 @@ module Hecks
|
|
|
112
156
|
# started guessing domain semantics would need to keep guessing
|
|
113
157
|
# forever, once per shape of "gone" any caller ever invents. The
|
|
114
158
|
# caller filters and sorts the result; this only groups it.
|
|
159
|
+
#
|
|
160
|
+
# @param rows [Array<Runtime::Value>] a `list_of` attribute's own elements, in append order
|
|
161
|
+
# @param key [Symbol] the method to call on each row to find "the same logical thing"
|
|
162
|
+
# across entries (typically a field reader)
|
|
163
|
+
# @return [Array<Runtime::Value>] one row per distinct `key` value, each the latest row
|
|
164
|
+
# that had it
|
|
115
165
|
def self.latest_by(rows, key)
|
|
116
166
|
rows.to_h { |row| [row.public_send(key), row] }.values
|
|
117
167
|
end
|
data/lib/hecks/runtime.rb
CHANGED
|
@@ -68,17 +68,40 @@ module Hecks
|
|
|
68
68
|
|
|
69
69
|
# Load a bluebook directory and return the Dispatcher bound to it.
|
|
70
70
|
# `install_facade:`, `environment:` — see Loader.boot.
|
|
71
|
+
#
|
|
72
|
+
# @param path [String] path to a domain directory, or a file inside one
|
|
73
|
+
# @param shared [String, nil] a shared-root override; see `Loader.boot`
|
|
74
|
+
# @param install_facade [Boolean] whether to install the `Widget::Item.Add`-style
|
|
75
|
+
# Ruby facade constants for this boot
|
|
76
|
+
# @param environment [String, nil] the environment name passed through to
|
|
77
|
+
# `Adapters::Folder#load_domain`
|
|
78
|
+
# @return [Runtime::Dispatcher, Runtime::RemoteDispatcher] the dispatcher bound
|
|
79
|
+
# to the booted domain
|
|
71
80
|
def boot(path, shared: nil, install_facade: true, environment: nil)
|
|
72
81
|
Loader.boot(path, shared: shared, install_facade: install_facade, environment: environment)
|
|
73
82
|
end
|
|
74
83
|
|
|
75
84
|
# `paths` form — see Loader.boot_files.
|
|
85
|
+
#
|
|
86
|
+
# @param paths [String, Array<String>] one or more file paths within the domain
|
|
87
|
+
# to load, instead of the whole directory
|
|
88
|
+
# @param shared [String, nil] a shared-root override; see `Loader.boot_files`
|
|
89
|
+
# @param install_facade [Boolean] whether to install the `Widget::Item.Add`-style
|
|
90
|
+
# Ruby facade constants for this boot
|
|
91
|
+
# @param environment [String, nil] the environment name passed through to the
|
|
92
|
+
# selected-file loader
|
|
93
|
+
# @return [Runtime::Dispatcher, Runtime::RemoteDispatcher] the dispatcher bound
|
|
94
|
+
# to the booted domain
|
|
76
95
|
def boot_files(paths, shared: nil, install_facade: true, environment: nil)
|
|
77
96
|
Loader.boot_files(paths, shared: shared, install_facade: install_facade, environment: environment)
|
|
78
97
|
end
|
|
79
98
|
|
|
80
99
|
# Bind the ambient registry for the duration of the block, restoring
|
|
81
100
|
# whatever was there before. Nesting is safe ; a raise still restores.
|
|
101
|
+
#
|
|
102
|
+
# @param registry [Runtime::Registry] the registry to make current for the block
|
|
103
|
+
# @yield the code that should see `registry` as `current_registry`
|
|
104
|
+
# @return [Object] the block's result
|
|
82
105
|
def with_registry(registry)
|
|
83
106
|
previous = @current_registry
|
|
84
107
|
@current_registry = registry
|
|
@@ -104,6 +127,15 @@ module Hecks
|
|
|
104
127
|
# `RoleAssignment`'s own `scope`, not just its `role_name`. See
|
|
105
128
|
# `Runtime::Caller::Current`'s own header for why this lives here
|
|
106
129
|
# rather than as a command-level DSL construct.
|
|
130
|
+
# @param role [String, Symbol] the role to check the caller against
|
|
131
|
+
# @param actor_id [String, nil] who is calling, checked against a real Governance
|
|
132
|
+
# `RoleAssignment` when given; string-equality only against `role` when nil
|
|
133
|
+
# @param as_of [Integer, nil] Unix epoch seconds to check a matching `RoleAssignment`'s
|
|
134
|
+
# own `starts_at` against; unchecked when nil
|
|
135
|
+
# @param scope [String, nil] the scope to check a matching `RoleAssignment`'s own
|
|
136
|
+
# `scope` against; unchecked when nil
|
|
137
|
+
# @yield the code to run with this caller bound
|
|
138
|
+
# @return [Object] the block's result
|
|
107
139
|
def as_caller(role:, actor_id: nil, as_of: nil, scope: nil, &)
|
|
108
140
|
Caller.as(role: role, actor_id: actor_id, as_of: as_of, scope: scope, &)
|
|
109
141
|
end
|