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
|
@@ -4,17 +4,36 @@ require_relative "../ir"
|
|
|
4
4
|
module Hecks
|
|
5
5
|
module Bluebook
|
|
6
6
|
Port = Struct.new(:name, :verb, :signal, :answers, keyword_init: true) do
|
|
7
|
+
# Says whether this port answers its verb with a return value.
|
|
8
|
+
#
|
|
9
|
+
# @return [Boolean] whether `signal` is `:reply`
|
|
7
10
|
def reply? = signal == :reply
|
|
11
|
+
|
|
12
|
+
# Says whether this port answers its verb by taking effect, with no return value.
|
|
13
|
+
#
|
|
14
|
+
# @return [Boolean] whether `signal` is `:effect`
|
|
8
15
|
def effect? = signal == :effect
|
|
9
16
|
end
|
|
10
17
|
|
|
11
18
|
Adapter = Struct.new(:name, :port, :fields, :secrets, keyword_init: true) do
|
|
19
|
+
# Says whether this adapter declares a field, plain or secret.
|
|
20
|
+
#
|
|
21
|
+
# @param field [Symbol, String] the field to check
|
|
22
|
+
# @return [Boolean] whether `field` is one of this adapter's own `fields` or `secrets`
|
|
12
23
|
def declares?(field) = all_fields.include?(field.to_sym)
|
|
13
24
|
|
|
25
|
+
# Lists every field this adapter declares, plain and secret alike.
|
|
26
|
+
#
|
|
27
|
+
# @return [Array<Symbol>] every field this adapter declares, `fields` and `secrets`
|
|
28
|
+
# combined
|
|
14
29
|
def all_fields = (fields || []) + (secrets || [])
|
|
15
30
|
end
|
|
16
31
|
|
|
17
32
|
Bind = Struct.new(:aggregate, :verb, :adapter, :role, keyword_init: true) do
|
|
33
|
+
# Names the aggregate this bind applies to.
|
|
34
|
+
#
|
|
35
|
+
# @return [String] this bind's aggregate name, demodulised, or `""` for a
|
|
36
|
+
# domain-level default bind with no aggregate
|
|
18
37
|
def aggregate_name = Naming.demodulise(aggregate)
|
|
19
38
|
end
|
|
20
39
|
|
|
@@ -37,6 +56,14 @@ module Hecks
|
|
|
37
56
|
|
|
38
57
|
attr_reader :domain, :binds, :subscriptions, :framework_members, :vendored_bluebooks
|
|
39
58
|
|
|
59
|
+
# @param domain [String, Symbol] the domain this hecksagon wires
|
|
60
|
+
# @param binds [Array<Bluebook::Bind>] the declared adapter binds
|
|
61
|
+
# @param subscriptions [Array<String, Symbol>] the external events this domain
|
|
62
|
+
# subscribes to
|
|
63
|
+
# @param framework_members [Array<String, Symbol>] the framework members
|
|
64
|
+
# (`Governance`, `Identity`, ...) this domain attaches
|
|
65
|
+
# @param vendored_bluebooks [Array<String, Symbol>] the vendored embryonaut
|
|
66
|
+
# bluebook package names this domain attaches
|
|
40
67
|
def initialize(domain:, binds: [], subscriptions: [], framework_members: [], vendored_bluebooks: [])
|
|
41
68
|
@domain = domain.to_s
|
|
42
69
|
@binds = binds
|
|
@@ -59,6 +86,13 @@ module Hecks
|
|
|
59
86
|
|
|
60
87
|
attr_reader :domain, :realm, :latest, :settings
|
|
61
88
|
|
|
89
|
+
# @param domain [String, Symbol] the domain this world configures
|
|
90
|
+
# @param realm [String, Symbol, nil] the declared realm/version marker, or `nil`
|
|
91
|
+
# if none is declared
|
|
92
|
+
# @param latest [String, Symbol, nil] the declared latest-version marker, or `nil`
|
|
93
|
+
# if none is declared
|
|
94
|
+
# @param settings [Hash] the declared adapter bind settings, keyed by verb and,
|
|
95
|
+
# for a qualified entry, `"verb:adapter"`
|
|
62
96
|
def initialize(domain:, realm: nil, latest: nil, settings: {})
|
|
63
97
|
@domain = domain.to_s
|
|
64
98
|
@realm = realm&.to_s
|
|
@@ -9,6 +9,9 @@ module Hecks
|
|
|
9
9
|
class StateTransition
|
|
10
10
|
attr_reader :target, :from
|
|
11
11
|
|
|
12
|
+
# @param target [String, Symbol] the state this transition moves the record to
|
|
13
|
+
# @param from [String, Symbol, Array<String, Symbol>, nil] the state, or states, this
|
|
14
|
+
# transition applies from; `nil` means any current state admits it
|
|
12
15
|
def initialize(target:, from: nil)
|
|
13
16
|
@target = target.to_s
|
|
14
17
|
@from = case from
|
|
@@ -18,6 +21,10 @@ module Hecks
|
|
|
18
21
|
end
|
|
19
22
|
end
|
|
20
23
|
|
|
24
|
+
# Says whether this transition is guarded to specific source states.
|
|
25
|
+
#
|
|
26
|
+
# @return [Boolean] whether this transition is guarded to specific source states,
|
|
27
|
+
# rather than applying from any current state
|
|
21
28
|
def constrained? = !@from.nil?
|
|
22
29
|
end
|
|
23
30
|
|
|
@@ -41,6 +48,10 @@ module Hecks
|
|
|
41
48
|
|
|
42
49
|
attr_reader :field, :default, :transitions
|
|
43
50
|
|
|
51
|
+
# @param field [Symbol, String] the attribute this state machine lives on
|
|
52
|
+
# @param default [String, Symbol] the state a new record starts in
|
|
53
|
+
# @param transitions [Array<Array(String, Bluebook::StateTransition)>] each declared
|
|
54
|
+
# `command name, StateTransition` pair, in declaration order
|
|
44
55
|
def initialize(field:, default:, transitions: [])
|
|
45
56
|
@field = field.to_sym
|
|
46
57
|
@default = default.to_s
|
|
@@ -3,18 +3,19 @@ module Hecks
|
|
|
3
3
|
module MetaValidator
|
|
4
4
|
# Offers every declaration in a built bluebook to the meta-domain.
|
|
5
5
|
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# —
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
6
|
+
# There are no hand-written branches, one per category: that shape's
|
|
7
|
+
# real cost is any verb the language declares that the judge never
|
|
8
|
+
# offers — a branch nobody wrote for it — going undetected, since a
|
|
9
|
+
# branch that does not exist cannot fail. `Command.Argument` and
|
|
10
|
+
# `ValueObject.Field` were both admitted that way once, undetected;
|
|
11
|
+
# every rule hanging off them was decoration until offered for real.
|
|
12
12
|
#
|
|
13
|
-
# So
|
|
14
|
-
#
|
|
15
|
-
# each list through the command that appends to it, then each child. A
|
|
16
|
-
# in the plan with no offer is now impossible — there is no
|
|
17
|
-
# which to forget one, and spec/judge_coverage_spec
|
|
13
|
+
# So instead the judge walks: it reads the plan the language makes of
|
|
14
|
+
# itself (Plan), and for each node offers the creating command, then
|
|
15
|
+
# each list through the command that appends to it, then each child. A
|
|
16
|
+
# verb in the plan with no offer is now impossible — there is no
|
|
17
|
+
# branch left in which to forget one, and spec/judge_coverage_spec
|
|
18
|
+
# holds it to that.
|
|
18
19
|
#
|
|
19
20
|
# What is not uniform lives in Readings, and only where the IR's shape
|
|
20
21
|
# differs from the language's. Naming differences do not appear at all: the
|
|
@@ -61,6 +62,8 @@ module Hecks
|
|
|
61
62
|
# not a curiosity, so the runtime is simply readable.
|
|
62
63
|
attr_reader :runtime
|
|
63
64
|
|
|
65
|
+
# @param bluebook [Object] a built bluebook chapter graph, as
|
|
66
|
+
# `MetaValidator.call` receives it
|
|
64
67
|
def initialize(bluebook)
|
|
65
68
|
@bluebook = bluebook
|
|
66
69
|
@refusals = []
|
|
@@ -160,12 +163,12 @@ module Hecks
|
|
|
160
163
|
|
|
161
164
|
# Declared before detailed, for every set of siblings.
|
|
162
165
|
#
|
|
163
|
-
#
|
|
164
|
-
#
|
|
165
|
-
#
|
|
166
|
-
# points at another aggregate could only resolve if that aggregate
|
|
167
|
-
# to be declared earlier in the file
|
|
168
|
-
# written above Account.
|
|
166
|
+
# Offering a node whole — declared, then its lists, then its children
|
|
167
|
+
# — one sibling at a time would mean an aggregate's attributes are
|
|
168
|
+
# offered before its later siblings exist, so an attribute that
|
|
169
|
+
# points at another aggregate could only resolve if that aggregate
|
|
170
|
+
# happens to be declared earlier in the file: banking would survive
|
|
171
|
+
# on luck, Customer written above Account.
|
|
169
172
|
#
|
|
170
173
|
# So siblings are declared in one pass and detailed in a second. It is the
|
|
171
174
|
# same ordering the walk already used one level down — value objects before
|
|
@@ -256,7 +259,7 @@ module Hecks
|
|
|
256
259
|
#
|
|
257
260
|
# the parent link (plan.parent_key) -> `parent_id`, the id
|
|
258
261
|
# the walk already carries in from one level up
|
|
259
|
-
# a walk-minted one (POSITION)
|
|
262
|
+
# a walk-minted one (`POSITION`) -> the walk index itself ;
|
|
260
263
|
# never a stored field (Member's own header:
|
|
261
264
|
# "position is not a mint — it is read straight
|
|
262
265
|
# out of the source file")
|
|
@@ -265,12 +268,12 @@ module Hecks
|
|
|
265
268
|
# (Handler's own `event_type`, Dispatch's own
|
|
266
269
|
# `command_name`)
|
|
267
270
|
#
|
|
268
|
-
# `carried` still decides bare-vs-wrapped the normal way ; POSITION
|
|
271
|
+
# `carried` still decides bare-vs-wrapped the normal way ; `POSITION`
|
|
269
272
|
# is the one case with no verb to ask `carried` about (`plan.
|
|
270
273
|
# declare` is always nil for an entity-owned category — Plan#read's
|
|
271
274
|
# own comment says why), so it is minted straight as a value object,
|
|
272
275
|
# matching exactly what `declare`'s own field loop already mints a
|
|
273
|
-
# POSITION field as.
|
|
276
|
+
# `POSITION` field as.
|
|
274
277
|
def node_identity(plan, category, node, index, parent_id)
|
|
275
278
|
plan.identity_paths.each_with_object({}) do |path, fields|
|
|
276
279
|
head = path.to_s.split(".").first
|
|
@@ -384,10 +387,11 @@ module Hecks
|
|
|
384
387
|
# Where it sits among its siblings is a fact about the walk, not about the
|
|
385
388
|
# node : a command does not know it is the third command on its aggregate.
|
|
386
389
|
# The walk knows, so the walk supplies it, and every other field still
|
|
387
|
-
# comes from the node.
|
|
388
|
-
# meta store
|
|
389
|
-
# ask ordered any other way would
|
|
390
|
-
# the one reader that must have the source's order
|
|
390
|
+
# comes from the node. Without this, declaration order would survive only
|
|
391
|
+
# because the meta store happens to iterate in insertion order — an
|
|
392
|
+
# accident that an ask ordered any other way would take away, and
|
|
393
|
+
# Reconstruction is the one reader that must have the source's order
|
|
394
|
+
# rather than a stable one.
|
|
391
395
|
# `private` above has no effect on a constant; kept here anyway,
|
|
392
396
|
# beside the method that reads it, for the narrative.
|
|
393
397
|
# rubocop:disable-next Lint/UselessConstantScoping
|
|
@@ -654,8 +658,8 @@ module Hecks
|
|
|
654
658
|
|
|
655
659
|
# Command -> commands, ValueObject -> value_objects, Query -> queries.
|
|
656
660
|
# Convention, not a table : the IR names a collection after what it holds.
|
|
657
|
-
# The pluraliser lives in Naming
|
|
658
|
-
# one
|
|
661
|
+
# The pluraliser lives in Naming, kept to exactly one implementation — a
|
|
662
|
+
# second one risks disagreeing with it — see Naming.plural.
|
|
659
663
|
def collection_reader(category) = Naming.plural(Naming.snake(category))
|
|
660
664
|
end
|
|
661
665
|
end
|
|
@@ -3,11 +3,12 @@ module Hecks
|
|
|
3
3
|
module MetaValidator
|
|
4
4
|
# What the language says about itself, read back as something walkable.
|
|
5
5
|
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# belongs to which list is not derivable from a
|
|
9
|
-
#
|
|
10
|
-
#
|
|
6
|
+
# ## Why a plan, not a hand-written branch per category
|
|
7
|
+
#
|
|
8
|
+
# "Which append command belongs to which list is not derivable from a
|
|
9
|
+
# name" sounds like a reason to hand-write one branch per category —
|
|
10
|
+
# and is beside the point. It is derivable from the language's own IR,
|
|
11
|
+
# because every append command declares its target:
|
|
11
12
|
#
|
|
12
13
|
# command "Argument" do
|
|
13
14
|
# reference_to Command
|
|
@@ -24,7 +25,7 @@ module Hecks
|
|
|
24
25
|
# Query / Entity, ValueObject -> Member, ProcessManager -> Handler ->
|
|
25
26
|
# Dispatch all fall out of the declarations rather than being restated here.
|
|
26
27
|
#
|
|
27
|
-
# Usage
|
|
28
|
+
# ## Usage
|
|
28
29
|
#
|
|
29
30
|
# plan = Plan.for(MetaValidator.grammar_registry)
|
|
30
31
|
# plan.category("Command").parent # => "Aggregate"
|
|
@@ -52,6 +53,10 @@ module Hecks
|
|
|
52
53
|
# Every verb this category declares, in declaration order. `alternates`
|
|
53
54
|
# matters here: two commands can append to one list, and a verb missing
|
|
54
55
|
# from this is a verb the coverage gate stops watching.
|
|
56
|
+
#
|
|
57
|
+
# @return [Array<String>] every command name this category declares
|
|
58
|
+
# (the creating command, every setter, appender, alternate
|
|
59
|
+
# appender, and sealer), `nil` entries dropped
|
|
55
60
|
def verbs
|
|
56
61
|
[declare, *setters.map(&:verb), *appends.values.map(&:verb),
|
|
57
62
|
*alternates.map(&:verb), *sealers].compact
|
|
@@ -61,19 +66,35 @@ module Hecks
|
|
|
61
66
|
# an id is a scalar — so the judge offers it bare, where every other
|
|
62
67
|
# field goes as a one-field value object. The language answers this
|
|
63
68
|
# about itself, so declaring a new reference needs no change here.
|
|
69
|
+
#
|
|
70
|
+
# @param verb [String] the command name declaring `argument`
|
|
71
|
+
# @param argument [String] the argument name to check
|
|
72
|
+
# @return [Boolean] whether `argument` on `verb` is a reference
|
|
64
73
|
def references?(verb, argument)
|
|
65
74
|
Array(references[verb.to_s]).include?(argument.to_s)
|
|
66
75
|
end
|
|
67
76
|
|
|
77
|
+
# Reports whether this category is the root of the containment
|
|
78
|
+
# tree.
|
|
79
|
+
#
|
|
80
|
+
# @return [Boolean] whether this category has no parent — the root
|
|
81
|
+
# of the containment tree
|
|
68
82
|
def root? = parent.nil?
|
|
69
83
|
end
|
|
70
84
|
|
|
85
|
+
# Reads the language's own self-description off `registry`.
|
|
86
|
+
#
|
|
87
|
+
# @param registry [Runtime::Registry] a registry with the language's
|
|
88
|
+
# own "Bluebook" chapter already judged and assembled
|
|
89
|
+
# @return [Plan] the plan, built from that chapter's own aggregates
|
|
71
90
|
def self.for(registry)
|
|
72
91
|
new(registry.bluebook("Bluebook"))
|
|
73
92
|
end
|
|
74
93
|
|
|
75
94
|
attr_reader :categories
|
|
76
95
|
|
|
96
|
+
# @param meta [Bluebook::Chapter] the language's own assembled
|
|
97
|
+
# "Bluebook" chapter
|
|
77
98
|
def initialize(meta)
|
|
78
99
|
@categories = {}
|
|
79
100
|
meta.aggregates.each do |aggregate|
|
|
@@ -121,7 +142,17 @@ module Hecks
|
|
|
121
142
|
@categories.freeze
|
|
122
143
|
end
|
|
123
144
|
|
|
145
|
+
# Looks up one category by name.
|
|
146
|
+
#
|
|
147
|
+
# @param name [String, Symbol] the category name, such as
|
|
148
|
+
# `"Command"` or `"ValueObject"`
|
|
149
|
+
# @return [Category, nil] the named category, or `nil` if the
|
|
150
|
+
# language declares no such category
|
|
124
151
|
def category(name) = @categories[name.to_s]
|
|
152
|
+
|
|
153
|
+
# Lists every category this plan holds.
|
|
154
|
+
#
|
|
155
|
+
# @return [Array<String>] every category name this plan holds
|
|
125
156
|
def names = @categories.keys
|
|
126
157
|
|
|
127
158
|
# Every verb the language declares, spelled as the judge would
|
|
@@ -135,6 +166,10 @@ module Hecks
|
|
|
135
166
|
# entity-owned category's own parent may itself be entity-
|
|
136
167
|
# owned), so this stays what it already is: the judge's own
|
|
137
168
|
# coverage promise, not a second guess at it.
|
|
169
|
+
#
|
|
170
|
+
# @return [Array<String>] every verb the language declares, dotted
|
|
171
|
+
# and prefixed with `"Bluebook::"`, such as `"Bluebook::Aggregate.
|
|
172
|
+
# Command.Argument"`
|
|
138
173
|
def verbs
|
|
139
174
|
@categories.flat_map do |name, category|
|
|
140
175
|
category.verbs.map { |verb| "Bluebook::#{dotted_prefix(name)}.#{verb}" }
|
|
@@ -218,8 +253,8 @@ module Hecks
|
|
|
218
253
|
# than restated. The judge has to know a record's id before it
|
|
219
254
|
# dispatches, because the children it walks next carry it as their
|
|
220
255
|
# parent — so it derives the same join the runtime will, off the same
|
|
221
|
-
# declaration
|
|
222
|
-
#
|
|
256
|
+
# declaration: a hand-written branch per category, disagreeing with
|
|
257
|
+
# the runtime by even one separator, would be a broken reference.
|
|
223
258
|
identity_paths: aggregate.identity_paths,
|
|
224
259
|
entity_owned: entity_owned
|
|
225
260
|
)
|
|
@@ -26,6 +26,13 @@ module Hecks
|
|
|
26
26
|
# every one of them was a fact `Assembly::Contracts` is the right place to
|
|
27
27
|
# keep: which shaper turns this list into rows. A list with no shaper reads
|
|
28
28
|
# straight off the node, which is most of them.
|
|
29
|
+
#
|
|
30
|
+
# @param category [String] the node's own construct category, such
|
|
31
|
+
# as `"Command"` or `"ValueObject"`
|
|
32
|
+
# @param list_name [String] the appendable list's own field name
|
|
33
|
+
# @param node [Object] the built IR node being judged
|
|
34
|
+
# @return [Array<Hash>] one row per element of `node`'s own
|
|
35
|
+
# `list_name`, shaped by the category's own shaper if it has one
|
|
29
36
|
def rows_for(category, list_name, node)
|
|
30
37
|
shaper = Assembly.contract(category).shaper(list_name)
|
|
31
38
|
return Array(node.public_send(list_name)) unless shaper
|
|
@@ -37,20 +44,39 @@ module Hecks
|
|
|
37
44
|
# symbol argument as ":ceiling". Reading the object instead lost the colon,
|
|
38
45
|
# and nothing downstream could tell an argument from a literal of the same
|
|
39
46
|
# name.
|
|
47
|
+
#
|
|
48
|
+
# @param node [Object] the built IR node being judged (a `Query` or
|
|
49
|
+
# `ReadModel`)
|
|
50
|
+
# @return [Array<Hash{Symbol => Object}>] each declared `where`
|
|
51
|
+
# clause's own fields
|
|
40
52
|
def where_rows(node) = Array(node.wheres).map(&:to_h)
|
|
41
53
|
|
|
42
54
|
# The language holds a value object's name here ; the IR holds the object.
|
|
55
|
+
#
|
|
56
|
+
# @param node [Object] the built IR node being judged (an
|
|
57
|
+
# `Aggregate`)
|
|
58
|
+
# @return [Array<Hash{Symbol => String}>] `{name: ...}` for every
|
|
59
|
+
# declared value object
|
|
43
60
|
def value_object_names(node) = node.value_objects.map { |shape| { name: shape.hecks_name } }
|
|
44
61
|
|
|
45
62
|
# An identity is a list of parts, so it is offered one part at a time — the
|
|
46
63
|
# same way attributes and transitions are. The IR holds the paths ; the
|
|
47
64
|
# language holds a row per path, and the order between them is the whole
|
|
48
65
|
# meaning, because the identity is their join.
|
|
66
|
+
#
|
|
67
|
+
# @param node [Object] the built IR node being judged (an
|
|
68
|
+
# `Aggregate` or `Entity`)
|
|
69
|
+
# @return [Array<Hash{Symbol => String}>] `{value: ...}` for every
|
|
70
|
+
# identity path part, in order
|
|
49
71
|
def identity_rows(node) = node.identity_paths.map { |path| { value: path } }
|
|
50
72
|
|
|
51
73
|
# Through to_h, which is where Bluebook.render_value spells a symbol argument as
|
|
52
74
|
# ":source". The raw with_spec lost the colon, and a binding that reads an
|
|
53
75
|
# argument became indistinguishable from one carrying a literal string.
|
|
76
|
+
#
|
|
77
|
+
# @param node [Object] the built IR node being judged (a `Dispatch`)
|
|
78
|
+
# @return [Array<Hash{Symbol => Object}>] `pair_rows`' own return
|
|
79
|
+
# for `node`'s own `with_spec`
|
|
54
80
|
def with_spec_rows(node) = pair_rows(node.to_h[:with_spec])
|
|
55
81
|
|
|
56
82
|
# The same read, one level in — `compensates` folds `DispatchSpec`
|
|
@@ -59,19 +85,40 @@ module Hecks
|
|
|
59
85
|
# "Dispatch"), so its own with_spec pairs live nested one hash
|
|
60
86
|
# down from where `with_spec_rows` looks. `&.dig(...)` — no
|
|
61
87
|
# compensation at all is not an error, it is `pair_rows(nil)`, empty.
|
|
88
|
+
#
|
|
89
|
+
# @param node [Object] the built IR node being judged (a `Dispatch`)
|
|
90
|
+
# @return [Array<Hash{Symbol => Object}>] `pair_rows`' own return
|
|
91
|
+
# for `node`'s own `compensates.with_spec`, `[]` when `node` has
|
|
92
|
+
# no compensation
|
|
62
93
|
def compensates_with_spec_rows(node) = pair_rows(node.to_h[:compensates]&.dig(:with_spec))
|
|
63
94
|
|
|
64
95
|
# A read model carries the same options an ask does, plus its filters — see
|
|
65
96
|
# option_rows.
|
|
97
|
+
#
|
|
98
|
+
# @param node [Object] the built IR node being judged (a
|
|
99
|
+
# `ReadModel`)
|
|
100
|
+
# @return [Array<Hash{Symbol => Object}>] `option_rows`' own return,
|
|
101
|
+
# with filters included
|
|
66
102
|
def read_model_option_rows(node) = option_rows(node, filters: true)
|
|
67
103
|
|
|
68
104
|
# The canonical-form table is the expression grammar's, not this chapter's, so
|
|
69
105
|
# the node is not consulted at all.
|
|
106
|
+
#
|
|
107
|
+
# @param _node [Object] unused; every node shares the one table
|
|
108
|
+
# @return [Array<Hash{Symbol => Object}>] `normalisation_rows`' own
|
|
109
|
+
# return
|
|
70
110
|
def normalisation_table(_node) = normalisation_rows
|
|
71
111
|
|
|
72
112
|
# `lifecycle :status do transition "Retire" => "retired", from: ["issued", "active"] end`
|
|
73
113
|
# is one declaration and two transitions. Offering it once would leave the
|
|
74
114
|
# second unjudged, which is the whole failure this judge exists to avoid.
|
|
115
|
+
#
|
|
116
|
+
# @param node [Object] the built IR node being judged (an
|
|
117
|
+
# `Aggregate` or `Entity`)
|
|
118
|
+
# @return [Array<Hash{Symbol => Object}>] `:command`, `:from_state`,
|
|
119
|
+
# and `:to_state`, one row per declared `from` state (or one row
|
|
120
|
+
# with a `nil` `:from_state` for an unconstrained transition);
|
|
121
|
+
# `[]` if `node` declares no lifecycle
|
|
75
122
|
def transition_rows(node)
|
|
76
123
|
lifecycle = node.respond_to?(:lifecycle) ? node.lifecycle : nil
|
|
77
124
|
return [] unless lifecycle
|
|
@@ -87,6 +134,10 @@ module Hecks
|
|
|
87
134
|
# An open map — a member's fields, a dispatch's argument bindings — has no
|
|
88
135
|
# value object that can hold it, so each entry becomes its own row. This is
|
|
89
136
|
# why Member and Dispatch are roots in the language rather than lists.
|
|
137
|
+
#
|
|
138
|
+
# @param map [Hash, nil] the open map to flatten, or `nil`
|
|
139
|
+
# @return [Array<Hash{Symbol => Object}>] `{key:, value:}` for every
|
|
140
|
+
# entry, `[]` when `map` is `nil`
|
|
90
141
|
def pair_rows(map)
|
|
91
142
|
Array(map&.to_h).map { |key, value| { key: key, value: value } }
|
|
92
143
|
end
|
|
@@ -125,6 +176,14 @@ module Hecks
|
|
|
125
176
|
#
|
|
126
177
|
# Named `wheres`, `order_by` and `limit` so they gather back into exactly the
|
|
127
178
|
# declaration keys the assembly already reads.
|
|
179
|
+
#
|
|
180
|
+
# @param node [Object] the built IR node being judged (a `Query` or
|
|
181
|
+
# `ReadModel`)
|
|
182
|
+
# @param filters [Boolean] whether to also flatten `node`'s own
|
|
183
|
+
# `wheres`/`order_by`/`limit`
|
|
184
|
+
# @return [Array<Hash{Symbol => Object}>] `[]` if `node` answers no
|
|
185
|
+
# `extra_options_to_h`; otherwise `parts`' own rows for every
|
|
186
|
+
# option `node` carries
|
|
128
187
|
def option_rows(node, filters: false)
|
|
129
188
|
return [] unless node.respond_to?(:extra_options_to_h)
|
|
130
189
|
|
|
@@ -139,6 +198,14 @@ module Hecks
|
|
|
139
198
|
end
|
|
140
199
|
end
|
|
141
200
|
|
|
201
|
+
# Reads a read model's own filtering fields, in the shape
|
|
202
|
+
# `option_rows` expects to flatten alongside its ordinary options.
|
|
203
|
+
#
|
|
204
|
+
# @param node [Object] the built IR node being judged (a
|
|
205
|
+
# `ReadModel`)
|
|
206
|
+
# @return [Hash{Symbol => Object}] `:wheres` (`Array<Hash>`),
|
|
207
|
+
# `:order_by`, and `:limit` (each `Hash` or absent), absent keys
|
|
208
|
+
# dropped
|
|
142
209
|
def filter_options(node)
|
|
143
210
|
{
|
|
144
211
|
wheres: Array(node.wheres).map(&:to_h),
|
|
@@ -147,6 +214,17 @@ module Hecks
|
|
|
147
214
|
}.reject { |_, held| held.nil? || held == [] }
|
|
148
215
|
end
|
|
149
216
|
|
|
217
|
+
# Flattens one option's own value (or one element of a repeated
|
|
218
|
+
# option) into key/value rows.
|
|
219
|
+
#
|
|
220
|
+
# @param option [Symbol] the option's own name
|
|
221
|
+
# @param held [Object] the option's own value (a Hash-coercible
|
|
222
|
+
# object) — one repetition's worth, for a repeated option
|
|
223
|
+
# @param at [Integer, nil] the repetition index, for a repeated
|
|
224
|
+
# option; `nil` otherwise
|
|
225
|
+
# @return [Array<Hash{Symbol => String, Object}>] `:option`, `:key`,
|
|
226
|
+
# `:value`, and `:at` (stringified, `nil` when `at` is `nil`), one
|
|
227
|
+
# per key of `held`
|
|
150
228
|
def parts(option, held, at)
|
|
151
229
|
Hash(held).map do |key, value|
|
|
152
230
|
{ option: option.to_s, key: key.to_s, value: value, at: at&.to_s }
|
|
@@ -158,9 +236,14 @@ module Hecks
|
|
|
158
236
|
# (`append: { name: :name, amount: :amount }`). So an append is offered
|
|
159
237
|
# once per binding, and each one is judged.
|
|
160
238
|
#
|
|
161
|
-
#
|
|
162
|
-
#
|
|
163
|
-
#
|
|
239
|
+
# Sending `field: v(""), kind: v("argument"), source: v("")` here
|
|
240
|
+
# instead — three stubbed values — would hand every rule about what
|
|
241
|
+
# a mutation reads a blank, so it could never refuse.
|
|
242
|
+
#
|
|
243
|
+
# @param node [Object] the built IR node being judged (a `Command`)
|
|
244
|
+
# @return [Array<Hash{Symbol => Object}>] one row per binding for an
|
|
245
|
+
# append/delegate/corrects mutation, or `set_row`'s own single-row
|
|
246
|
+
# array for a set/increment/decrement
|
|
164
247
|
def mutation_rows(node)
|
|
165
248
|
Array(node.mutations).flat_map do |mutation|
|
|
166
249
|
# `:delegate`/`:corrects` (CommandBuilder#delegates_to's and
|
|
@@ -185,6 +268,11 @@ module Hecks
|
|
|
185
268
|
|
|
186
269
|
# A set/increment/decrement reads one thing: a command argument, or a
|
|
187
270
|
# literal written into the bluebook.
|
|
271
|
+
#
|
|
272
|
+
# @param mutation [Bluebook::Mutation] a set/increment/decrement
|
|
273
|
+
# mutation
|
|
274
|
+
# @return [Array(Hash{Symbol => Object})] a single-element array
|
|
275
|
+
# holding `:target`, `:op`, `:field`, `:kind`, and `:source`
|
|
188
276
|
def set_row(mutation)
|
|
189
277
|
classified = mutation.to_h[:source] || {}
|
|
190
278
|
|
|
@@ -196,6 +284,10 @@ module Hecks
|
|
|
196
284
|
# The normalisation table belongs to the expression grammar, not to any one
|
|
197
285
|
# bluebook — it is how the canonical form of a rule is spelled. The language
|
|
198
286
|
# models it because a bluebook's rules are canonicalised on the way in.
|
|
287
|
+
#
|
|
288
|
+
# @return [Array<Hash{Symbol => Object}>] one row per admitted
|
|
289
|
+
# normalisation rule (`:strategy`, `:source_token`, `:replacement`,
|
|
290
|
+
# `:boundary`, `:position`), `[]` if the table cannot be read
|
|
199
291
|
def normalisation_rows
|
|
200
292
|
table = Expression::CanonicalForm.table
|
|
201
293
|
return [] unless table
|
|
@@ -217,10 +309,12 @@ module Hecks
|
|
|
217
309
|
|
|
218
310
|
# What the bluebook calls a node, whichever kind of thing the node is.
|
|
219
311
|
#
|
|
220
|
-
#
|
|
221
|
-
#
|
|
222
|
-
#
|
|
223
|
-
#
|
|
312
|
+
# Every construct answers `hecks_name` now, so there is nothing to sniff
|
|
313
|
+
# (`respond_to?(:hecks_name) ? … : node.name`) or choose between. This
|
|
314
|
+
# disappears entirely when the DSL stops handing the judge nodes at all.
|
|
315
|
+
#
|
|
316
|
+
# @param node [Object] the built IR node being judged
|
|
317
|
+
# @return [String] the node's own declared name
|
|
224
318
|
def declared_name(node) = node.hecks_name
|
|
225
319
|
|
|
226
320
|
# One field of a Declare payload. Mostly a reader of the same name — the
|
|
@@ -233,6 +327,18 @@ module Hecks
|
|
|
233
327
|
# `:parent` there ; a folded field names the object and member it lives in.
|
|
234
328
|
# So the exceptions are looked up rather than repeated, and a new fold is one
|
|
235
329
|
# line in one file instead of two lines in two.
|
|
330
|
+
#
|
|
331
|
+
# @param category [String] the node's own construct category, such
|
|
332
|
+
# as `"Command"` or `"ValueObject"`
|
|
333
|
+
# @param node [Object] the built IR node being judged
|
|
334
|
+
# @param field [Symbol] the Declare payload field to read
|
|
335
|
+
# @param parent_id [String] the id the containment walk carries in
|
|
336
|
+
# from one level up, offered when `field` is the parent pointer
|
|
337
|
+
# @return [Object] `field`'s own value: `node`'s declared name,
|
|
338
|
+
# `parent_id`, a folded member's value (`through`), the read
|
|
339
|
+
# model's own limit value, an encoded literal (`provenance`), or
|
|
340
|
+
# `node.public_send(field)`; `nil` if `node` does not respond to
|
|
341
|
+
# `field`
|
|
236
342
|
def field_value(category, node, field, parent_id)
|
|
237
343
|
return declared_name(node) if field == :name
|
|
238
344
|
|
|
@@ -271,6 +377,14 @@ module Hecks
|
|
|
271
377
|
# member names are the ones the IR spells — a Lifecycle's `default`, an
|
|
272
378
|
# OrderBy's `direction` — and reading the object raw is how a colon or a type
|
|
273
379
|
# goes missing.
|
|
380
|
+
#
|
|
381
|
+
# @param node [Object] the built IR node being judged
|
|
382
|
+
# @param object [Symbol] the member object's own field name on
|
|
383
|
+
# `node`, such as `:lifecycle`
|
|
384
|
+
# @param member [Symbol] the field to read off that object, such as
|
|
385
|
+
# `:default`; `:transitions` returns the object itself
|
|
386
|
+
# @return [Object, nil] the member's own value, or `nil` if `node`
|
|
387
|
+
# does not respond to `object` or that object is absent
|
|
274
388
|
def through(node, object, member)
|
|
275
389
|
held = node.respond_to?(object) ? node.public_send(object) : nil
|
|
276
390
|
return nil unless held
|
|
@@ -281,6 +395,13 @@ module Hecks
|
|
|
281
395
|
# What a setting command writes. A setter whose source is absent is not
|
|
282
396
|
# dispatched at all — absent is not empty, and offering "" would turn every
|
|
283
397
|
# "if you declare it, declare something" rule into "you must declare it".
|
|
398
|
+
#
|
|
399
|
+
# @param category [String] the node's own construct category
|
|
400
|
+
# @param node [Object] the built IR node being judged
|
|
401
|
+
# @param target [Symbol] the setter's own target field
|
|
402
|
+
# @return [Object, nil] the target's own value: the closed set's own
|
|
403
|
+
# row count, a folded member's value (`through`), or
|
|
404
|
+
# `node.public_send(target)`; `nil` if absent
|
|
284
405
|
def setter_value(category, node, target)
|
|
285
406
|
# `rows` folds into `closed_set` and `members` between them, with no single
|
|
286
407
|
# member to name, so it keeps its own reading — see Contract#folded.
|
|
@@ -294,6 +415,11 @@ module Hecks
|
|
|
294
415
|
|
|
295
416
|
# Only a declared closed set has a row count. An empty one is the defect,
|
|
296
417
|
# so `rows` must stay absent rather than arrive as zero.
|
|
418
|
+
#
|
|
419
|
+
# @param node [Object] the built IR node being judged (a
|
|
420
|
+
# `ValueObject`)
|
|
421
|
+
# @return [Integer, nil] the closed set's own row count, or `nil` if
|
|
422
|
+
# `node` is not a declared closed set
|
|
297
423
|
def closed_set_size(node)
|
|
298
424
|
return nil unless node.respond_to?(:closed_set?) && node.closed_set?
|
|
299
425
|
|
|
@@ -305,6 +431,12 @@ module Hecks
|
|
|
305
431
|
# that head's ID, and resolution does the rest. Encoding and decoding both
|
|
306
432
|
# live here, because this is where the IR's shape differs from the
|
|
307
433
|
# language's and nowhere else should know the spelling.
|
|
434
|
+
#
|
|
435
|
+
# @param row [Bluebook::Attribute] the attribute being offered
|
|
436
|
+
# @param aggregate_id [String] the owning head's own id, read for
|
|
437
|
+
# its chapter prefix
|
|
438
|
+
# @return [String, nil] the target head's own id, or `nil` if `row`
|
|
439
|
+
# is not a `reference_to` attribute
|
|
308
440
|
def points_at(row, aggregate_id)
|
|
309
441
|
return nil unless row.reference?
|
|
310
442
|
|
|
@@ -331,15 +463,33 @@ module Hecks
|
|
|
331
463
|
#
|
|
332
464
|
# nil stays nil rather than becoming "nil": absent is a real answer here,
|
|
333
465
|
# and the language's own field is optional.
|
|
466
|
+
#
|
|
467
|
+
# @param value [Object] the value to encode: `nil`, `Symbol`,
|
|
468
|
+
# `String`, `Hecks::StateRef`, `true`, `false`, `Integer`, `Float`,
|
|
469
|
+
# `Hash`, or `Array` (recursively)
|
|
470
|
+
# @return [String, nil] the self-describing wire spelling, or `nil`
|
|
471
|
+
# when `value` is `nil`
|
|
334
472
|
def encode_literal(value) = value.nil? ? nil : Literal.render(value)
|
|
335
473
|
|
|
336
474
|
# The way back out: an aggregate id becomes the type the IR spells. The
|
|
337
475
|
# id is a join of chapter + name (Naming::IDENTITY_JOIN, the same join
|
|
338
476
|
# `points_at` built it with, not the "::" a real bluebook's own type
|
|
339
477
|
# names never carry) ; the wire format wants only the bare name.
|
|
478
|
+
#
|
|
479
|
+
# @param points_at_id [String] a target head's own id, as `points_at`
|
|
480
|
+
# built it
|
|
481
|
+
# @return [String] `"Reference<Name>"`, the bare target name
|
|
340
482
|
def reference_type(points_at_id) = "Reference<#{points_at_id.to_s.split(Naming::IDENTITY_JOIN).last}>"
|
|
341
483
|
|
|
342
484
|
# One value out of a row, named by the value object's field.
|
|
485
|
+
#
|
|
486
|
+
# @param row [Hash, Object] the row to read: a Hash, an object
|
|
487
|
+
# answering `field` directly, an object answering `to_h`, or a
|
|
488
|
+
# bare scalar
|
|
489
|
+
# @param field [Symbol] the field name to read
|
|
490
|
+
# @return [Object] `row[field]` for a Hash, `row.public_send(field)`
|
|
491
|
+
# when `row` answers `field` directly, `row.to_h[field]` for
|
|
492
|
+
# anything else to_h-able, or `row` itself for a bare scalar
|
|
343
493
|
def row_value(row, field)
|
|
344
494
|
# A Hash first. Hash answers to `key` (Hash#key(value)) and to `value` on
|
|
345
495
|
# some rows, so asking respond_to? before checking for a Hash reads a
|