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
|
@@ -21,6 +21,15 @@ module Hecks
|
|
|
21
21
|
include WordGate
|
|
22
22
|
include Sealing
|
|
23
23
|
|
|
24
|
+
# @param name [String] the aggregate's name, as written after `aggregate`
|
|
25
|
+
# @param chapter_named_givens [Hash{String => Hash{String => Bluebook::Given}}] the
|
|
26
|
+
# chapter-wide given pool, shared and written through by `given_impl`
|
|
27
|
+
# @param chapter_pending_givens [Array<Hash>] unresolved chapter-wide bare given
|
|
28
|
+
# references, appended to when this aggregate's own reference cannot resolve yet
|
|
29
|
+
# @param chapter_entity_named_givens [Hash{String => Hash{String => Bluebook::Given}}]
|
|
30
|
+
# the chapter-wide, entity-scoped given pool, threaded unchanged to every entity
|
|
31
|
+
# @param chapter_entity_pending_givens [Array<Hash>] unresolved chapter-wide,
|
|
32
|
+
# entity-scoped bare given references, threaded unchanged to every entity
|
|
24
33
|
def initialize(name, chapter_named_givens: {}, chapter_pending_givens: [],
|
|
25
34
|
chapter_entity_named_givens: {}, chapter_entity_pending_givens: [])
|
|
26
35
|
@name = name
|
|
@@ -66,33 +75,45 @@ module Hecks
|
|
|
66
75
|
@pending_queries = []
|
|
67
76
|
end
|
|
68
77
|
|
|
78
|
+
# Sets the human-readable description shown for this aggregate.
|
|
79
|
+
#
|
|
80
|
+
# @param value [String] the description text
|
|
81
|
+
# @return [String] the description as stored
|
|
69
82
|
def description(value)
|
|
70
83
|
# moved to the language: Description invariant, on Root.Declare
|
|
71
84
|
|
|
72
85
|
@description = value
|
|
73
86
|
end
|
|
74
87
|
|
|
88
|
+
# Names where a concept adopted from a canonical source came from.
|
|
89
|
+
#
|
|
75
90
|
# Origin, not runtime identity — a concept adopted from a canonical
|
|
76
91
|
# source (§28) names where it came from without that fact ever
|
|
77
92
|
# touching `hecks_fqn`/dispatch. Captured raw, the same way
|
|
78
93
|
# `attribute ..., default: { value: "small" }` captures a literal
|
|
79
94
|
# Hash untouched — no re-parsing, no structure imposed beyond
|
|
80
95
|
# "whatever the author wrote."
|
|
81
|
-
#
|
|
82
|
-
# `
|
|
96
|
+
#
|
|
97
|
+
# Answers the `provenance` word (and, via the same table rows,
|
|
98
|
+
# its siblings `projects`/`lifecycle`/`entity`/
|
|
99
|
+
# `query`/`policy`/`command` below) through the table's `calls:`
|
|
100
|
+
# column — item #13's full
|
|
83
101
|
# metaprogrammed dispatch (slice 4c). All bootstrap-reachable
|
|
84
102
|
# (used throughout the core/attached chapters), all in
|
|
85
|
-
# GenericDispatch::BOOTSTRAP_CALLS_FALLBACK
|
|
103
|
+
# `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`.
|
|
104
|
+
#
|
|
105
|
+
# @param from [Object] the canonical source, captured exactly as written
|
|
106
|
+
# @return [Object] `from` as stored
|
|
86
107
|
def provenance_impl(from:)
|
|
87
108
|
@provenance = from
|
|
88
109
|
end
|
|
89
110
|
|
|
111
|
+
# Declares a reference from this aggregate's own head to another aggregate's identity.
|
|
112
|
+
#
|
|
90
113
|
# `optional:` — matching `CommandBuilder#reference_to`'s own
|
|
91
|
-
# signature, which already had it
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
# commit that added this comment (`optional: optional`, below).
|
|
95
|
-
# It was a real gap because an aggregate that can point at one
|
|
114
|
+
# signature, which already had it, and forwarded here to
|
|
115
|
+
# `attribute_impl()`/`relationship_attribute` (`optional: optional`, below). A real
|
|
116
|
+
# need: an aggregate that can point at one
|
|
96
117
|
# of several targets (Item's own `personal_list_id`/
|
|
97
118
|
# `camping_list_id`, never both) needs each reference optional
|
|
98
119
|
# on the aggregate's own persisted schema, not just as a
|
|
@@ -100,10 +121,19 @@ module Hecks
|
|
|
100
121
|
# `spec/fixtures/hop_chain.bluebook`'s own `Proposal` aggregate
|
|
101
122
|
# declares `reference_to Engagement, optional: true` at the
|
|
102
123
|
# aggregate head.
|
|
103
|
-
#
|
|
124
|
+
#
|
|
125
|
+
# Answers the `reference_to` word through the table's `calls:`
|
|
126
|
+
# column — item #13's full metaprogrammed
|
|
104
127
|
# dispatch (slice 4b). Bootstrap-reachable (every core/attached
|
|
105
128
|
# grammar chapter uses reference_to to describe itself), so also
|
|
106
|
-
# named in GenericDispatch::BOOTSTRAP_CALLS_FALLBACK
|
|
129
|
+
# named in `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`.
|
|
130
|
+
#
|
|
131
|
+
# @param type [Module, Symbol, String] the referenced aggregate, written as a bare
|
|
132
|
+
# constant
|
|
133
|
+
# @param as [Symbol, nil] the attribute's name; nil derives it from `type`
|
|
134
|
+
# @param optional [Boolean] whether the reference may be absent
|
|
135
|
+
# @return [void]
|
|
136
|
+
# @raise [Bluebook::DSL::Malformed] if `as` (or the derived name) is already declared
|
|
107
137
|
def reference_to_impl(type, as: nil, optional: false)
|
|
108
138
|
target = Naming.demodulise(type)
|
|
109
139
|
@reference_targets << target
|
|
@@ -111,13 +141,15 @@ module Hecks
|
|
|
111
141
|
as || default_reference_name(target), optional: optional)
|
|
112
142
|
end
|
|
113
143
|
|
|
144
|
+
# Declares that this aggregate holds its own kept-fresh copy of a field reached through
|
|
145
|
+
# a reference, so a rule can read it locally instead of reaching across the boundary.
|
|
146
|
+
#
|
|
114
147
|
# A rule may only read within its own aggregate boundary (S12,
|
|
115
|
-
# ADR 0025 — "Consistency across aggregate boundaries").
|
|
116
|
-
# `given`/`ensures`/`invariant` used to reach through a
|
|
148
|
+
# ADR 0025 — "Consistency across aggregate boundaries"). Reaching through a
|
|
117
149
|
# `reference_to` at rule-evaluation time (`References#
|
|
118
150
|
# dereference`, a live query against another aggregate's own
|
|
119
|
-
# repository
|
|
120
|
-
# only this record" model everywhere else
|
|
151
|
+
# repository) would be unbounded and inconsistent with the "a rule reads
|
|
152
|
+
# only this record" model everywhere else — `projects` is what
|
|
121
153
|
# replaces that: `projects :customer_status, from: :"customer.
|
|
122
154
|
# status"` declares that this aggregate holds its own copy of
|
|
123
155
|
# `Customer`'s own `:status`, kept fresh by a rebuild sweep
|
|
@@ -137,6 +169,14 @@ module Hecks
|
|
|
137
169
|
# aggregate in the chapter is real, not by `AggregateBuilder`
|
|
138
170
|
# itself) — `validate_projected_fields!` is where that half
|
|
139
171
|
# happens.
|
|
172
|
+
#
|
|
173
|
+
# @param name [Symbol, String] the local field's name this aggregate projects the
|
|
174
|
+
# remote value into
|
|
175
|
+
# @param from [Symbol, String] the local reference and remote field, dotted, such as
|
|
176
|
+
# `:"customer.status"`
|
|
177
|
+
# @return [Array<Bluebook::ProjectedField>] every projected field declared so far, this
|
|
178
|
+
# one last
|
|
179
|
+
# @raise [Bluebook::DSL::Malformed] if `from` is not `reference.field` shaped
|
|
140
180
|
def projects_impl(name, from:)
|
|
141
181
|
reference, _, remote_field = from.to_s.rpartition(".")
|
|
142
182
|
|
|
@@ -165,12 +205,27 @@ module Hecks
|
|
|
165
205
|
# did when it was written — real, if rare corpus: "Combined corpus
|
|
166
206
|
# uses: one."
|
|
167
207
|
#
|
|
168
|
-
#
|
|
169
|
-
#
|
|
170
|
-
# `
|
|
171
|
-
#
|
|
172
|
-
#
|
|
173
|
-
# `
|
|
208
|
+
# Declares a list-typed relationship to another aggregate, referenced by its plural name.
|
|
209
|
+
#
|
|
210
|
+
# Answers the `has_many` word (and, via the same table rows, its
|
|
211
|
+
# siblings `has_one`/`belongs_to` below) through the table's
|
|
212
|
+
# `calls:` column — item #13's full metaprogrammed dispatch
|
|
213
|
+
# (slice 4). Each Keyword row's own `calls:` names the matching
|
|
214
|
+
# `_impl`, so all three are carried in
|
|
215
|
+
# `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK` like every other
|
|
216
|
+
# `calls:`-routed word, but never actually exercised during real
|
|
217
|
+
# bootstrap (no core/attached chapter uses one of these to
|
|
218
|
+
# describe itself), unlike `attribute`/`role`.
|
|
219
|
+
#
|
|
220
|
+
# @param type [Module, Symbol, String] the related aggregate's plural name, a bare
|
|
221
|
+
# constant
|
|
222
|
+
# @param as [Symbol, nil] the attribute's name; nil derives it from `type`
|
|
223
|
+
# @param legacy_options [Hash] must be empty outside shadow-parsing; under
|
|
224
|
+
# shadow-parsing, `:optional` is read for the legacy single-reference form
|
|
225
|
+
# @return [void]
|
|
226
|
+
# @raise [Bluebook::DSL::Malformed] outside shadow-parsing, if `legacy_options` is
|
|
227
|
+
# non-empty; may also raise from `relationship_attribute` if the derived name is
|
|
228
|
+
# already declared
|
|
174
229
|
def has_many_impl(type, as: nil, **legacy_options)
|
|
175
230
|
return legacy_has_many(type, as: as, optional: legacy_options.fetch(:optional, false)) if MetaValidator.shadow_parsing?
|
|
176
231
|
|
|
@@ -186,6 +241,12 @@ module Hecks
|
|
|
186
241
|
list: true)
|
|
187
242
|
end
|
|
188
243
|
|
|
244
|
+
# Declares a single-valued relationship this aggregate holds toward another.
|
|
245
|
+
#
|
|
246
|
+
# @param type [Module, Symbol, String] the related aggregate, a bare constant
|
|
247
|
+
# @param as [Symbol, nil] the attribute's name; nil derives it from `type`
|
|
248
|
+
# @param optional [Boolean] whether the relationship may be absent
|
|
249
|
+
# @return [void]
|
|
189
250
|
def has_one_impl(type, as: nil, optional: false)
|
|
190
251
|
return legacy_has_one(type, as: as, optional: optional) if MetaValidator.shadow_parsing?
|
|
191
252
|
|
|
@@ -195,6 +256,12 @@ module Hecks
|
|
|
195
256
|
optional: optional)
|
|
196
257
|
end
|
|
197
258
|
|
|
259
|
+
# Declares a single-valued relationship toward the aggregate that owns this one.
|
|
260
|
+
#
|
|
261
|
+
# @param type [Module, Symbol, String] the owning aggregate, a bare constant
|
|
262
|
+
# @param as [Symbol, nil] the attribute's name; nil derives it from `type`
|
|
263
|
+
# @param optional [Boolean] whether the relationship may be absent
|
|
264
|
+
# @return [void]
|
|
198
265
|
def belongs_to_impl(type, as: nil, optional: false)
|
|
199
266
|
return legacy_has_one(type, as: as, optional: optional) if MetaValidator.shadow_parsing?
|
|
200
267
|
|
|
@@ -204,10 +271,19 @@ module Hecks
|
|
|
204
271
|
optional: optional)
|
|
205
272
|
end
|
|
206
273
|
|
|
274
|
+
# Declares this aggregate's own state machine.
|
|
275
|
+
#
|
|
276
|
+
# @param field [Symbol, String] the attribute the state machine lives on
|
|
277
|
+
# @param default [String, Symbol] the state a new record starts in
|
|
278
|
+
# @yield the lifecycle body of `transition` rows, evaluated against a `LifecycleBuilder`
|
|
279
|
+
# @return [Bluebook::Lifecycle] the built state machine
|
|
280
|
+
# @raise [Bluebook::DSL::Malformed] if two transitions for one command overlap
|
|
207
281
|
def lifecycle_impl(field, default:, &)
|
|
208
282
|
@lifecycle = LifecycleBuilder.build(field, default: default, &)
|
|
209
283
|
end
|
|
210
284
|
|
|
285
|
+
# Queues a piece nested in this aggregate, built later once every sibling has been seen.
|
|
286
|
+
#
|
|
211
287
|
# A piece is declared in this aggregate — its owner is stamped by
|
|
212
288
|
# `Aggregate#initialize`, once the aggregate exists. Its own
|
|
213
289
|
# commands were given the piece as their owner when it was declared,
|
|
@@ -230,25 +306,42 @@ module Hecks
|
|
|
230
306
|
# different canonical — bare `customer.status`, not
|
|
231
307
|
# `parent.customer.status`, wrong scope for a piece's own command
|
|
232
308
|
# to evaluate) nor round 4's single-piece `given` could reach.
|
|
309
|
+
# @param name [String] the nested piece's name
|
|
310
|
+
# @yield the piece body, evaluated against an `EntityBuilder` once drained
|
|
311
|
+
# @return [Array<Array>] every pending piece queued so far, this one last
|
|
233
312
|
def entity_impl(name, &block)
|
|
234
313
|
@pending_entities << [name, block]
|
|
235
314
|
end
|
|
236
315
|
|
|
316
|
+
# Queues a query declared on this aggregate, built later once every sibling has been seen.
|
|
317
|
+
#
|
|
318
|
+
# @param name [String] the query's name
|
|
319
|
+
# @yield the query body, evaluated against a `QueryBuilder` once drained
|
|
320
|
+
# @return [Array<Array>] every pending query queued so far, this one last
|
|
237
321
|
def query_impl(name, &block)
|
|
238
322
|
@pending_queries << [name, block]
|
|
239
323
|
end
|
|
240
324
|
|
|
325
|
+
# Declares a policy scoped to this aggregate, stamping it with the aggregate's own name.
|
|
326
|
+
#
|
|
327
|
+
# @param name [String] the policy's name
|
|
328
|
+
# @yield the policy body, evaluated against a `PolicyBuilder`
|
|
329
|
+
# @return [Array<Bluebook::Policy>] every policy declared so far, this one last
|
|
330
|
+
# @raise [Bluebook::DSL::Malformed] if the body fails any check the policy builder raises
|
|
241
331
|
def policy_impl(name, &)
|
|
242
332
|
reaction = PolicyBuilder.build(name, &)
|
|
243
333
|
reaction.aggregate = @name
|
|
244
334
|
@policies << reaction
|
|
245
335
|
end
|
|
246
336
|
|
|
247
|
-
#
|
|
248
|
-
#
|
|
249
|
-
#
|
|
250
|
-
#
|
|
251
|
-
#
|
|
337
|
+
# Declares a value object on this aggregate, either as a block of `attribute` lines or
|
|
338
|
+
# (the `type` shorthand) as a single `:value` attribute.
|
|
339
|
+
#
|
|
340
|
+
# `builder.closed_sets` too, not only `builder.build` — a real
|
|
341
|
+
# gap this exact fix closes: a
|
|
342
|
+
# value_object's own inline `attribute :x, one_of(...)` (legal
|
|
343
|
+
# since S3, ADR 0025 removed the wrong-arity collision that would
|
|
344
|
+
# otherwise crash it) synthesises its own
|
|
252
345
|
# anonymous value object via the same `AttributeCollector#closed_
|
|
253
346
|
# sets` mechanism an aggregate's own attributes already use — and
|
|
254
347
|
# nothing installed it anywhere. `Box.attributes` said `size:
|
|
@@ -278,6 +371,16 @@ module Hecks
|
|
|
278
371
|
# Neither type nor block keeps its historical behavior untouched
|
|
279
372
|
# (an empty attribute list — judged, or not, by the language
|
|
280
373
|
# downstream, the same as before this parameter existed).
|
|
374
|
+
#
|
|
375
|
+
# @param name [String] the value object's name
|
|
376
|
+
# @param type [Module, nil] the bare shorthand's single attribute type; mutually
|
|
377
|
+
# exclusive with `block`
|
|
378
|
+
# @yield the value object body of `attribute`/`invariant`/`one_of` lines; mutually
|
|
379
|
+
# exclusive with `type`
|
|
380
|
+
# @return [Array<Bluebook::ValueObject>] this aggregate's own value objects, including
|
|
381
|
+
# this one and any closed sets its attributes synthesised
|
|
382
|
+
# @raise [Bluebook::DSL::Malformed] if both `type` and a block are given, or the body
|
|
383
|
+
# fails any check the value object language or its builder raises
|
|
281
384
|
def value_object(name, type = nil, &block)
|
|
282
385
|
if type && block
|
|
283
386
|
raise Malformed,
|
|
@@ -293,6 +396,9 @@ module Hecks
|
|
|
293
396
|
@value_objects.concat(builder.closed_sets)
|
|
294
397
|
end
|
|
295
398
|
|
|
399
|
+
# Queues a command declared on this aggregate, built later once every sibling has been
|
|
400
|
+
# seen.
|
|
401
|
+
#
|
|
296
402
|
# `from:` — lifecycle state becomes a command guard (S10, ADR
|
|
297
403
|
# 0025) — `command "Debit", from: "open"` replaces `given
|
|
298
404
|
# ("account is open") { status == "open" }`, written 35 times
|
|
@@ -302,6 +408,12 @@ module Hecks
|
|
|
302
408
|
# the lifecycle already declares which states exist, so naming
|
|
303
409
|
# the legal ones is checkable against it, where a free-text
|
|
304
410
|
# given could drift out of sync with the state machine and did.
|
|
411
|
+
#
|
|
412
|
+
# @param name [String] the command's name
|
|
413
|
+
# @param from [String, Symbol, Array<String, Symbol>, nil] the lifecycle state(s) this
|
|
414
|
+
# command guards from; nil admits from any state
|
|
415
|
+
# @yield the command body, evaluated against a `CommandBuilder` once drained
|
|
416
|
+
# @return [Array<Array>] every pending command queued so far, this one last
|
|
305
417
|
def command_impl(name, from: nil, &block)
|
|
306
418
|
# The verb is declared on this aggregate — the owner `acts_on` answers
|
|
307
419
|
# with — stamped by `Aggregate#initialize` once the aggregate
|
|
@@ -312,6 +424,9 @@ module Hecks
|
|
|
312
424
|
@pending_commands << [name, from, block]
|
|
313
425
|
end
|
|
314
426
|
|
|
427
|
+
# Declares a rule this aggregate's own commands must satisfy, or references one a
|
|
428
|
+
# sibling aggregate in the chapter already declared.
|
|
429
|
+
#
|
|
315
430
|
# A precondition shared across commands, declared once (S10, ADR
|
|
316
431
|
# 0025) — an aggregate-level `given`, block required, stored by
|
|
317
432
|
# its own description rather than appended anywhere: a command
|
|
@@ -354,9 +469,23 @@ module Hecks
|
|
|
354
469
|
# textually-different canonical registers under the same
|
|
355
470
|
# description; see `reference_named_chapter_given`'s own
|
|
356
471
|
# ambiguity error for how that surfaces.
|
|
357
|
-
#
|
|
358
|
-
#
|
|
359
|
-
#
|
|
472
|
+
# Answers the `given` word through the table's `calls:` column —
|
|
473
|
+
# item #13's full metaprogrammed dispatch
|
|
474
|
+
# (slice 4b), same reasoning as `reference_to_impl` above:
|
|
475
|
+
# bootstrap-reachable, in `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`.
|
|
476
|
+
#
|
|
477
|
+
# @param description [String] the rule's description; also the name a sibling aggregate
|
|
478
|
+
# references it by when no block is given
|
|
479
|
+
# @param declared_by [Module, Symbol, String, nil] disambiguates which aggregate's own
|
|
480
|
+
# rule to reference, a bare constant, when more than one shares `description`; only
|
|
481
|
+
# meaningful with no block
|
|
482
|
+
# @yield the predicate body; evaluated for its extracted source, never called directly
|
|
483
|
+
# @return [void]
|
|
484
|
+
# @raise [Bluebook::DSL::Malformed] if given a block whose source cannot be extracted;
|
|
485
|
+
# given no block, the description is immediately ambiguous between more than one
|
|
486
|
+
# already-loaded aggregate with no `declared_by` to disambiguate; an unresolved
|
|
487
|
+
# reference defers instead, and may still raise once the whole chapter has loaded, if
|
|
488
|
+
# it then resolves to none or more than one candidate
|
|
360
489
|
def given_impl(description, declared_by: nil, &predicate)
|
|
361
490
|
return reference_named_chapter_given(description, declared_by: declared_by) unless predicate
|
|
362
491
|
|
|
@@ -448,23 +577,39 @@ module Hecks
|
|
|
448
577
|
|
|
449
578
|
public
|
|
450
579
|
|
|
580
|
+
# Declares a rule the whole aggregate must satisfy, checked after every command, before
|
|
581
|
+
# save.
|
|
582
|
+
#
|
|
451
583
|
# The aggregate boundary is what an invariant defines (S10, ADR
|
|
452
|
-
# 0025 — "Rules") —
|
|
453
|
-
# the same way a value object's already is
|
|
584
|
+
# 0025 — "Rules") — the same check a value object's already gets
|
|
454
585
|
# (`ValueObjectBuilder#invariant`, whose own shape this mirrors
|
|
455
|
-
# exactly).
|
|
456
|
-
# an aggregate-level rule had nowhere to live, so "the balance
|
|
586
|
+
# exactly). Without an aggregate-level rule, "the balance
|
|
457
587
|
# never goes negative" was three different `given`/`ensures`
|
|
458
588
|
# texts across banking's six balance-moving commands, and the
|
|
459
589
|
# four that only increase it said nothing at all — completeness
|
|
460
590
|
# depended on someone noticing which commands could decrease it.
|
|
461
|
-
#
|
|
462
|
-
#
|
|
591
|
+
#
|
|
592
|
+
# Answers the `invariant` word through the table's `calls:`
|
|
593
|
+
# column — item #13's full metaprogrammed
|
|
594
|
+
# dispatch (slice 4b), same reasoning as `given_impl` above.
|
|
595
|
+
#
|
|
596
|
+
# @param description [String] the rule's description
|
|
597
|
+
# @yield the predicate body; evaluated for its extracted source, never called directly
|
|
598
|
+
# @return [void]
|
|
599
|
+
# @raise [Bluebook::DSL::Malformed] if the block's source could not be extracted
|
|
463
600
|
def invariant_impl(description, &predicate)
|
|
464
601
|
@invariants << build_rule(Invariant, description, predicate, owner_name: @name, word: "invariant",
|
|
465
602
|
extraction_failure: "it would be a rule the IR cannot carry")
|
|
466
603
|
end
|
|
467
604
|
|
|
605
|
+
# Assembles every declared attribute, construct and rule into an `Aggregate`, after
|
|
606
|
+
# draining pending commands/queries/entities and running every `seal_*` check.
|
|
607
|
+
#
|
|
608
|
+
# @return [Bluebook::Aggregate] the built aggregate
|
|
609
|
+
# @raise [Bluebook::DSL::Malformed] if identity resolution or any `seal_*` check fails —
|
|
610
|
+
# a mutation or query naming an undeclared field, an inconsistent default, a lifecycle
|
|
611
|
+
# guard with no lifecycle, a lifecycle-field mutation outside a transition, a projected
|
|
612
|
+
# field naming an undeclared reference, or a correction targeting an unreferenced field
|
|
468
613
|
def build
|
|
469
614
|
drain_pending!
|
|
470
615
|
resolve_pending_identity!
|
|
@@ -500,6 +645,20 @@ module Hecks
|
|
|
500
645
|
ir
|
|
501
646
|
end
|
|
502
647
|
|
|
648
|
+
# Evaluates an `aggregate` block against a fresh builder and returns the built aggregate.
|
|
649
|
+
#
|
|
650
|
+
# @param name [String] the aggregate's name
|
|
651
|
+
# @param chapter_named_givens [Hash{String => Hash{String => Bluebook::Given}}] the
|
|
652
|
+
# chapter-wide given pool
|
|
653
|
+
# @param chapter_pending_givens [Array<Hash>] unresolved chapter-wide bare given
|
|
654
|
+
# references
|
|
655
|
+
# @param chapter_entity_named_givens [Hash{String => Hash{String => Bluebook::Given}}]
|
|
656
|
+
# the chapter-wide, entity-scoped given pool
|
|
657
|
+
# @param chapter_entity_pending_givens [Array<Hash>] unresolved chapter-wide,
|
|
658
|
+
# entity-scoped bare given references
|
|
659
|
+
# @yield the aggregate body, evaluated with the builder as `self`; may be omitted
|
|
660
|
+
# @return [Bluebook::Aggregate] the built aggregate
|
|
661
|
+
# @raise [Bluebook::DSL::Malformed] if the body fails any check `#build` raises
|
|
503
662
|
def self.build(name, chapter_named_givens: {}, chapter_pending_givens: [],
|
|
504
663
|
chapter_entity_named_givens: {}, chapter_entity_pending_givens: [], &block)
|
|
505
664
|
builder = new(name, chapter_named_givens: chapter_named_givens, chapter_pending_givens: chapter_pending_givens,
|
|
@@ -511,9 +670,9 @@ module Hecks
|
|
|
511
670
|
|
|
512
671
|
private
|
|
513
672
|
|
|
514
|
-
#
|
|
515
|
-
# immediately, inline, the moment their own DSL line
|
|
516
|
-
# `instance_eval
|
|
673
|
+
# Deferred construction — `entity`/`command`/`query` queue a descriptor rather than
|
|
674
|
+
# building immediately, inline, the moment their own DSL line runs during
|
|
675
|
+
# `instance_eval`. Building immediately would mean a command's own resolution (`sets
|
|
517
676
|
# :field` importing the owner's own attribute, `given("desc")`
|
|
518
677
|
# referencing an aggregate-level precondition, a query's own
|
|
519
678
|
# positional-param resolution) only ever saw whatever `@entities`/
|
|
@@ -20,12 +20,20 @@ module Hecks
|
|
|
20
20
|
UNSET = Object.new.freeze
|
|
21
21
|
private_constant :UNSET
|
|
22
22
|
|
|
23
|
+
# Returns the attributes declared so far, minting the accumulating list on first use.
|
|
24
|
+
#
|
|
25
|
+
# @return [Array<Bluebook::Attribute>] the attributes declared so far, in declaration order
|
|
23
26
|
def attributes = @attributes ||= []
|
|
24
27
|
|
|
25
28
|
# Value objects synthesised from inline closed sets, collected here and
|
|
26
29
|
# installed by whoever owns value objects (the aggregate).
|
|
30
|
+
#
|
|
31
|
+
# @return [Array<Bluebook::ValueObject>] the value objects synthesised so far
|
|
27
32
|
def closed_sets = @closed_sets ||= []
|
|
28
33
|
|
|
34
|
+
# Declares one field on the owning construct, with its type and, optionally, a default,
|
|
35
|
+
# an optionality flag, a pattern, a closed-set reference, or an inline closed set.
|
|
36
|
+
#
|
|
29
37
|
# `admits:` names a closed set that is already declared elsewhere —
|
|
30
38
|
#
|
|
31
39
|
# attribute :op, String, admits: "Vocabulary::QueryComparator"
|
|
@@ -54,22 +62,39 @@ module Hecks
|
|
|
54
62
|
# (S0b) already resolves a bare, not-yet-declared constant to the
|
|
55
63
|
# same forward reference the quoted form existed for — a bareword
|
|
56
64
|
# `Name` reaches a value object named "Name" declared later in the
|
|
57
|
-
# same block exactly
|
|
58
|
-
# renders either one identically.
|
|
59
|
-
# era text (checked directly), so
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
# The word `attribute` itself is
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
# name
|
|
65
|
+
# same block, exactly what the quoted form `"Name"` would have
|
|
66
|
+
# reached — `spell`'s own `to_s` renders either one identically.
|
|
67
|
+
# Neither form appears in any frozen era text (checked directly), so
|
|
68
|
+
# both are refused unconditionally — nothing for
|
|
69
|
+
# `MetaValidator.shadow_parsing?` to answer for.
|
|
70
|
+
#
|
|
71
|
+
# The word `attribute` itself is not a real method any builder answers
|
|
72
|
+
# directly: every (context, word) Keyword row for it carries
|
|
73
|
+
# `calls: "attribute_impl"`, and `GenericDispatch` forwards the whole
|
|
74
|
+
# call here untouched. `attribute_collector_spec.rb`
|
|
75
|
+
# (`AttributeCollector has no method without a test` —
|
|
76
|
+
# dsl_coverage_spec.rb) and the bootstrap fallback
|
|
77
|
+
# (`GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`) both name this same
|
|
78
|
+
# string; they must never drift apart.
|
|
79
|
+
#
|
|
80
|
+
# @param name [Symbol] the attribute's name
|
|
81
|
+
# @param type [Module, Symbol, ListOf, OneOf] the attribute's type: a bare constant
|
|
82
|
+
# (resolved by `ConstShim`), or the `ListOf`/`OneOf` wrapper `list_of`/`one_of` return
|
|
83
|
+
# when called in this same type position; quoted text is refused
|
|
84
|
+
# @param default [Object, nil] the value an omitted attribute defaults to; not type-checked
|
|
85
|
+
# here
|
|
86
|
+
# @param optional [Boolean] whether the attribute may be omitted entirely
|
|
87
|
+
# @param pattern [Regexp, String, nil] a pattern the attribute's value must match; refused
|
|
88
|
+
# if it uses a construct `PatternSubset` disallows
|
|
89
|
+
# @param admits [String, nil] the qualified name of an already-declared closed set this
|
|
90
|
+
# attribute's value may come from, such as `"Vocabulary::QueryComparator"`
|
|
91
|
+
# @param one_of [Array<String, Symbol>, nil] permitted values for a field-shaped closed
|
|
92
|
+
# set, declared inline via `one_of: [...]`; meaningful only inside a `value_object`
|
|
93
|
+
# (`ValueObjectBuilder` overrides `install_inline_closed_set`) — refused everywhere else
|
|
94
|
+
# @return [void]
|
|
95
|
+
# @raise [Bluebook::DSL::Malformed] if `name` is already declared, `type` is omitted or
|
|
96
|
+
# quoted text, `pattern` uses a disallowed construct, or `one_of:` is given outside a
|
|
97
|
+
# `value_object`
|
|
73
98
|
def attribute_impl(name, type = UNSET, default: nil, optional: false, pattern: nil,
|
|
74
99
|
admits: nil, one_of: nil)
|
|
75
100
|
# moved to the language: FieldName invariant, on Root.Attribute
|
|
@@ -109,17 +134,22 @@ module Hecks
|
|
|
109
134
|
install_inline_closed_set(name, one_of) if one_of
|
|
110
135
|
end
|
|
111
136
|
|
|
112
|
-
#
|
|
113
|
-
#
|
|
114
|
-
# position (`attribute :x,
|
|
115
|
-
# list_of`
|
|
116
|
-
#
|
|
117
|
-
# fallback, the same
|
|
118
|
-
#
|
|
119
|
-
# it), so
|
|
120
|
-
#
|
|
121
|
-
#
|
|
122
|
-
# reasoning as the
|
|
137
|
+
# Wraps a type so `attribute_impl` records it as a list-valued attribute.
|
|
138
|
+
#
|
|
139
|
+
# Called in an attribute's own type position (`attribute :x,
|
|
140
|
+
# list_of(Y)`), never through a `def list_of` any one builder
|
|
141
|
+
# answers as its own word — reached via
|
|
142
|
+
# `WordGate#word_gate_dispatch`'s "Type"-context fallback, the same
|
|
143
|
+
# one `one_of_impl` below uses. Bootstrap-reachable (every core
|
|
144
|
+
# chapter's own list-typed attributes use it), so
|
|
145
|
+
# `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK` carries a single
|
|
146
|
+
# `["Type", "list_of"]` entry rather than one per calling context —
|
|
147
|
+
# the bootstrap branch checks that key too, same reasoning as the
|
|
148
|
+
# ordinary fallback.
|
|
149
|
+
#
|
|
150
|
+
# @param type [Module, Symbol] the list's element type, a bare constant
|
|
151
|
+
# @return [Bluebook::DSL::AttributeCollector::ListOf] the wrapper `attribute_impl` reads
|
|
152
|
+
# to mark the attribute list-valued
|
|
123
153
|
def list_of_impl(type) = ListOf.new(type)
|
|
124
154
|
|
|
125
155
|
# `reference_to Account` mints `:account` — no `_id` — the default
|
|
@@ -155,16 +185,22 @@ module Hecks
|
|
|
155
185
|
# attribute's type is still a declared value object, which is now a
|
|
156
186
|
# structural rule rather than a predicate.
|
|
157
187
|
#
|
|
158
|
-
#
|
|
159
|
-
#
|
|
160
|
-
#
|
|
161
|
-
# pinned
|
|
162
|
-
#
|
|
163
|
-
#
|
|
188
|
+
# Wraps a list of permitted values so `attribute_impl` synthesises a closed-set value
|
|
189
|
+
# object for them.
|
|
190
|
+
#
|
|
191
|
+
# The desugaring is pinned — the same bluebook must always yield the
|
|
192
|
+
# same IR, so a plain String attribute silently discarding the
|
|
193
|
+
# values is not an outcome this spelling can produce.
|
|
194
|
+
#
|
|
195
|
+
# Reached the same way `list_of_impl` above is. Same name as
|
|
164
196
|
# `ValueObjectBuilder#one_of_impl`'s own override on purpose —
|
|
165
197
|
# that method's own `super(*values)` call (the no-block, bare
|
|
166
198
|
# type-position case) resolves by method name up the ancestor
|
|
167
199
|
# chain, and renaming only one side would silently break it.
|
|
200
|
+
#
|
|
201
|
+
# @param values [Array<String, Symbol>] the permitted values
|
|
202
|
+
# @return [Bluebook::DSL::AttributeCollector::OneOf] the wrapper `attribute_impl` reads to
|
|
203
|
+
# synthesise the closed-set value object
|
|
168
204
|
def one_of_impl(*values) = OneOf.new(values)
|
|
169
205
|
|
|
170
206
|
private
|
|
@@ -204,9 +240,9 @@ module Hecks
|
|
|
204
240
|
# (`seal_mutation_targets`, `seal_query_field`, `projects`'s own
|
|
205
241
|
# local check, `Instance#[]`, ...) uses `Array#find`/`any?`, which
|
|
206
242
|
# silently answers whichever declaration happens to come first and
|
|
207
|
-
# discards the second
|
|
208
|
-
#
|
|
209
|
-
#
|
|
243
|
+
# discards the second — left unrefused, both declarations would
|
|
244
|
+
# survive into the IR, one of them permanently unreachable by name.
|
|
245
|
+
# Refused here, at the one place every owner
|
|
210
246
|
# (Aggregate/Entity/Command/Query/PortOperation/ValueObject, each
|
|
211
247
|
# `include AttributeCollector`) mints an attribute through, rather
|
|
212
248
|
# than taught to each of those readers individually.
|
|
@@ -302,9 +338,9 @@ module Hecks
|
|
|
302
338
|
# regardless of where `identified_by` was actually written.
|
|
303
339
|
# Most real bluebooks write it first (insert_at 0); one (a
|
|
304
340
|
# ScheduledPayment corpus member) writes it after a reference_to
|
|
305
|
-
# and an attribute — this matches either,
|
|
306
|
-
# hand-writing `attribute field, Type` at that
|
|
307
|
-
#
|
|
341
|
+
# and an attribute — this matches either, producing the same
|
|
342
|
+
# result a person hand-writing `attribute field, Type` at that
|
|
343
|
+
# exact point would.
|
|
308
344
|
attributes.insert(insert_at, attributes.pop)
|
|
309
345
|
vo.attributes.flat_map do |attribute|
|
|
310
346
|
identity_paths_for_attribute(attribute, value_objects, context_name,
|
|
@@ -336,9 +372,10 @@ module Hecks
|
|
|
336
372
|
# of a compound `identified_by :a, :b`) names a single field, and
|
|
337
373
|
# deriving its path only makes sense while every value object along
|
|
338
374
|
# the way wraps exactly one field itself — the same "single-field
|
|
339
|
-
# value object" ADR 0025 names this shape after.
|
|
340
|
-
# value object here
|
|
341
|
-
# own fields, minting an unannounced compound key
|
|
375
|
+
# value object" ADR 0025 names this shape after. Left unrefused, a
|
|
376
|
+
# multi-field value object here would expand silently into every
|
|
377
|
+
# one of its own fields, minting an unannounced compound key
|
|
378
|
+
# nothing declared.
|
|
342
379
|
if nested.attributes.size != 1
|
|
343
380
|
candidates = nested.attributes.map(&:name).join(", ")
|
|
344
381
|
raise Malformed,
|