hecks 1.0.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/hecks/adapters/driven/claude_code.rb +3 -3
- data/lib/hecks/adapters/driven/d1.rb +48 -28
- data/lib/hecks/adapters/driven/folder.rb +10 -3
- data/lib/hecks/adapters/driven/google_authentication.rb +2 -4
- data/lib/hecks/adapters/driven/heki/journal.rb +1 -1
- data/lib/hecks/adapters/driven/heki.rb +3 -5
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +4 -1
- data/lib/hecks/adapters/driven/lambda.rb +2 -2
- data/lib/hecks/adapters/driven/local_storage.adapter +3 -0
- data/lib/hecks/adapters/driven/local_storage.rb +120 -0
- data/lib/hecks/adapters/driven/memory.rb +48 -3
- data/lib/hecks/adapters/driven/postgres/codec.rb +3 -1
- data/lib/hecks/adapters/driven/postgres/outbox.rb +100 -0
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +57 -0
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +22 -2
- data/lib/hecks/adapters/driven/postgres.rb +45 -31
- data/lib/hecks/adapters/driven/prism.rb +11 -0
- data/lib/hecks/adapters/driven/sql_query_builder.rb +45 -20
- data/lib/hecks/adapters/driven/sqlite/codec.rb +3 -1
- data/lib/hecks/adapters/driven/sqlite/projection.rb +22 -3
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +23 -1
- data/lib/hecks/adapters/driven/sqlite.rb +87 -8
- data/lib/hecks/adapters/driven.rb +4 -0
- data/lib/hecks/adapters.rb +4 -0
- data/lib/hecks/behaviors/dsl.rb +7 -0
- data/lib/hecks/behaviors/expectations.rb +32 -4
- data/lib/hecks/behaviors/rspec.rb +2 -0
- data/lib/hecks/behaviors/runner.rb +4 -0
- data/lib/hecks/bluebook/aggregate.rb +6 -5
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +4 -4
- data/lib/hecks/bluebook/assembly/build.rb +6 -3
- data/lib/hecks/bluebook/assembly/contract.rb +5 -1
- data/lib/hecks/bluebook/assembly/contracts.rb +4 -0
- data/lib/hecks/bluebook/assembly/marks.rb +15 -4
- data/lib/hecks/bluebook/assembly.rb +1 -3
- data/lib/hecks/bluebook/attribute.rb +6 -0
- data/lib/hecks/bluebook/behaviour/policy.rb +18 -0
- data/lib/hecks/bluebook/behaviour/process_manager.rb +17 -1
- data/lib/hecks/bluebook/behaviour/read_model.rb +53 -8
- data/lib/hecks/bluebook/behaviour/traits.rb +1 -1
- data/lib/hecks/bluebook/command.rb +6 -5
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +4 -1
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +524 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +29 -477
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +14 -6
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +5 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +848 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +25 -784
- data/lib/hecks/bluebook/dsl/command_builder.rb +53 -13
- data/lib/hecks/bluebook/dsl/const_shim.rb +6 -0
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +20 -7
- data/lib/hecks/bluebook/dsl/entity_builder.rb +27 -6
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +12 -2
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +8 -1
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +24 -12
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +40 -1
- data/lib/hecks/bluebook/dsl/policy_builder.rb +10 -1
- data/lib/hecks/bluebook/dsl/port_builder.rb +6 -1
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +33 -12
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +56 -12
- data/lib/hecks/bluebook/dsl/query_builder.rb +7 -1
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +142 -19
- data/lib/hecks/bluebook/dsl/rule_reference.rb +3 -1
- data/lib/hecks/bluebook/dsl/translation_builder.rb +13 -2
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +8 -1
- data/lib/hecks/bluebook/dsl/word_gate.rb +5 -0
- data/lib/hecks/bluebook/dsl/world_builder.rb +13 -3
- data/lib/hecks/bluebook/entity.rb +5 -3
- data/lib/hecks/bluebook/expression/ast_json.rb +88 -9
- data/lib/hecks/bluebook/expression/ast_reader.rb +107 -0
- data/lib/hecks/bluebook/expression/canonical_form.rb +7 -0
- data/lib/hecks/bluebook/expression/evaluator.rb +64 -11
- data/lib/hecks/bluebook/expression/projection.json +12 -0
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +4 -3
- data/lib/hecks/bluebook/expression/resolver.rb +83 -11
- data/lib/hecks/bluebook/expression.rb +1 -0
- data/lib/hecks/bluebook/hexagon.rb +10 -0
- data/lib/hecks/bluebook/lifecycle.rb +9 -0
- data/lib/hecks/bluebook/meta_validator/judge.rb +10 -4
- data/lib/hecks/bluebook/meta_validator/plan.rb +4 -0
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/shapes.rb +3 -3
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +7 -4
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +42 -3
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +1 -1
- data/lib/hecks/bluebook/meta_validator.rb +5 -5
- data/lib/hecks/bluebook/model_check.rb +119 -82
- data/lib/hecks/bluebook/pattern_subset.rb +9 -0
- data/lib/hecks/bluebook/policy.rb +2 -1
- data/lib/hecks/bluebook/process_manager.rb +6 -0
- data/lib/hecks/bluebook/project_register.rb +45 -25
- data/lib/hecks/bluebook/query.rb +4 -0
- data/lib/hecks/bluebook/read_model.rb +2 -2
- data/lib/hecks/bluebook/smoke_test.rb +5 -0
- data/lib/hecks/bluebook/synthesizer.rb +2 -1
- data/lib/hecks/bluebook/translation.rb +3 -0
- data/lib/hecks/bluebook/value_object.rb +2 -2
- data/lib/hecks/codemod.rb +13 -0
- data/lib/hecks/doc/reference.rb +17 -11
- data/lib/hecks/facade/cli_runner.rb +2 -2
- data/lib/hecks/facade/command_request.rb +11 -11
- data/lib/hecks/facade/handle.rb +3 -3
- data/lib/hecks/facade/surface/aggregate_door.rb +52 -38
- data/lib/hecks/facade.rb +4 -0
- data/lib/hecks/forms/app.rb +10 -6
- data/lib/hecks/forms/command_form_renderer.rb +3 -3
- data/lib/hecks/forms/field_renderer.rb +14 -9
- data/lib/hecks/forms/field_shape.rb +7 -2
- data/lib/hecks/forms/html.rb +5 -2
- data/lib/hecks/forms/index_renderer.rb +1 -1
- data/lib/hecks/forms/page.rb +7 -3
- data/lib/hecks/forms/query_form_renderer.rb +3 -2
- data/lib/hecks/forms/record_renderer.rb +22 -16
- data/lib/hecks/forms.rb +4 -0
- data/lib/hecks/fqn.rb +21 -9
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +30 -8
- data/lib/hecks/fuzzing/invalid_value_generator.rb +0 -1
- data/lib/hecks/fuzzing/isolated_boot.rb +14 -6
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +264 -0
- data/lib/hecks/fuzzing/properties/guards.rb +251 -0
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +351 -0
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +99 -0
- data/lib/hecks/fuzzing/properties/querying.rb +206 -0
- data/lib/hecks/fuzzing/properties.rb +41 -1065
- data/lib/hecks/fuzzing/replay.rb +60 -16
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +6 -2
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +2 -1
- data/lib/hecks/fuzzing/sequence_generator.rb +0 -1
- data/lib/hecks/fuzzing/value_generator.rb +2 -2
- data/lib/hecks/grammar/evolve.rb +34 -10
- data/lib/hecks/grammar/expression_operators.json +124 -0
- data/lib/hecks/grammar.rb +15 -5
- data/lib/hecks/ir.rb +9 -0
- data/lib/hecks/language/bluebook/aggregate.bluebook +4 -2
- data/lib/hecks/language/bluebook/command.bluebook +2 -1
- data/lib/hecks/language/bluebook/entity.bluebook +2 -1
- data/lib/hecks/language/bluebook/projection.bluebook +2 -1
- data/lib/hecks/language/bluebook/query.bluebook +4 -2
- data/lib/hecks/language/bluebook/shape.bluebook +2 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +2 -0
- data/lib/hecks/literal.rb +18 -8
- data/lib/hecks/naming.rb +6 -3
- data/lib/hecks/ports/access_control.rb +1 -1
- data/lib/hecks/ports/agent.rb +5 -3
- data/lib/hecks/ports/authentication.rb +3 -2
- data/lib/hecks/ports/authorization.rb +1 -1
- data/lib/hecks/ports/clock.rb +1 -1
- data/lib/hecks/ports/extraction.rb +5 -1
- data/lib/hecks/ports/identity_assignment.rb +1 -1
- data/lib/hecks/ports/identity_generation.rb +1 -1
- data/lib/hecks/ports/identity_resolution.rb +1 -1
- data/lib/hecks/ports/loading.rb +4 -1
- data/lib/hecks/ports/persistence/append_only.rb +51 -4
- data/lib/hecks/ports/persistence/binding_policy.rb +1 -1
- data/lib/hecks/ports/persistence/plugin.rb +3 -0
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +3 -1
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +5 -5
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +14 -5
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +4 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +80 -16
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +23 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +43 -5
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +25 -4
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +49 -9
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +10 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +49 -38
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +77 -18
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +19 -21
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +27 -9
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +9 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +25 -10
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +2 -6
- data/lib/hecks/ports/persistence/plugins/era.rb +3 -1
- data/lib/hecks/ports/persistence/remote_runtime.rb +1 -1
- data/lib/hecks/ports/persistence.rb +7 -3
- data/lib/hecks/ports/projection.rb +13 -5
- data/lib/hecks/ports/query/in_memory.rb +8 -2
- data/lib/hecks/ports/query.rb +1 -3
- data/lib/hecks/ports.rb +2 -0
- data/lib/hecks/projections/diagrams.rb +11 -3
- data/lib/hecks/projections/glossary.rb +374 -0
- data/lib/hecks/projections/ir.rb +2 -0
- data/lib/hecks/projections/model/deviations.rb +2 -1
- data/lib/hecks/projections/model.rb +15 -10
- data/lib/hecks/projections/vocabulary.rb +1 -3
- data/lib/hecks/projections.rb +1 -0
- data/lib/hecks/projector/cli_projector.rb +78 -42
- data/lib/hecks/projector/docs_projector.rb +21 -12
- data/lib/hecks/projector/exporter.rb +34 -1
- data/lib/hecks/projector/narrate_projector.rb +83 -25
- data/lib/hecks/projector.rb +2 -2
- data/lib/hecks/query_ir.rb +60 -36
- data/lib/hecks/query_specification/common/authorization_spec.rb +1 -1
- data/lib/hecks/query_specification/common/comparators.rb +4 -0
- data/lib/hecks/query_specification/common/comparison.rb +8 -1
- data/lib/hecks/query_specification/common/dsl.rb +5 -0
- data/lib/hecks/query_specification/common/limit_spec.rb +9 -2
- data/lib/hecks/query_specification/common/null_policy.rb +6 -2
- data/lib/hecks/query_specification/common/offset_spec.rb +9 -2
- data/lib/hecks/query_specification/common/options.rb +17 -5
- data/lib/hecks/query_specification/common/order_by.rb +9 -2
- data/lib/hecks/query_specification/common/where_clause.rb +15 -2
- data/lib/hecks/query_specification/field_path.rb +4 -4
- data/lib/hecks/query_specification/read_model/specification.rb +5 -2
- data/lib/hecks/router/namespace_installer.rb +22 -10
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +57 -22
- data/lib/hecks/runtime/command_interpreter.rb +104 -65
- data/lib/hecks/runtime/command_rules/admissibility.rb +26 -8
- data/lib/hecks/runtime/command_rules/arithmetic.rb +35 -14
- data/lib/hecks/runtime/dependency_planning.rb +33 -21
- data/lib/hecks/runtime/dispatcher.rb +41 -7
- data/lib/hecks/runtime/entity_element.rb +25 -6
- data/lib/hecks/runtime/entity_interpreter.rb +28 -15
- data/lib/hecks/runtime/errors.rb +28 -2
- data/lib/hecks/runtime/instance.rb +9 -3
- data/lib/hecks/runtime/interpreting.rb +45 -9
- data/lib/hecks/runtime/loader.rb +21 -0
- data/lib/hecks/runtime/outbox.rb +365 -0
- data/lib/hecks/runtime/policy_interpreter.rb +32 -17
- data/lib/hecks/runtime/query_interpreter.rb +36 -16
- data/lib/hecks/runtime/reaction_invocation.rb +35 -22
- data/lib/hecks/runtime/read_model_interpreter.rb +31 -7
- data/lib/hecks/runtime/reference_hop.rb +1 -1
- data/lib/hecks/runtime/refusal_wording.rb +55 -49
- data/lib/hecks/runtime/registry/saga_persistence.rb +3 -1
- data/lib/hecks/runtime/registry/verification.rb +39 -2
- data/lib/hecks/runtime/registry.rb +21 -11
- data/lib/hecks/runtime/remote_dispatcher.rb +3 -1
- data/lib/hecks/runtime/routing.rb +52 -39
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +7 -7
- data/lib/hecks/runtime/saga_interpreter.rb +119 -90
- data/lib/hecks/runtime/saga_pending_dispatch.rb +6 -3
- data/lib/hecks/runtime/tenant_scope.rb +7 -0
- data/lib/hecks/runtime/value/coercion.rb +271 -49
- data/lib/hecks/runtime/value.rb +1 -1
- data/lib/hecks/runtime.rb +7 -2
- data/lib/hecks/storehouse.rb +15 -8
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +1 -0
- data/lib/hecks.rb +17 -13
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +1 -1
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +1 -1
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +1 -1
- metadata +16 -2
|
@@ -82,8 +82,26 @@ module Hecks
|
|
|
82
82
|
Array(t.aggregates).each { |aggregate| judge_aggregate(t, aggregate) }
|
|
83
83
|
end
|
|
84
84
|
|
|
85
|
+
# One Declare, then one AddX pass per rule collection the aggregate
|
|
86
|
+
# carries — each pass is independent of the others (they mutate
|
|
87
|
+
# different fields), so each is its own private method below; only
|
|
88
|
+
# Declare-before-any-Add is order-sensitive, and that order is
|
|
89
|
+
# preserved here.
|
|
85
90
|
def judge_aggregate(translation, aggregate)
|
|
86
91
|
name = aggregate.name
|
|
92
|
+
declare_aggregate(translation, aggregate, name)
|
|
93
|
+
|
|
94
|
+
judge_renames(aggregate, name)
|
|
95
|
+
judge_moves(aggregate, name)
|
|
96
|
+
judge_converts(aggregate, name)
|
|
97
|
+
judge_drops(aggregate, name)
|
|
98
|
+
judge_retypes(aggregate, name)
|
|
99
|
+
judge_computes(aggregate, name)
|
|
100
|
+
judge_rekeys(aggregate, name)
|
|
101
|
+
judge_backfills(aggregate, name)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def declare_aggregate(translation, aggregate, name)
|
|
87
105
|
parent = {
|
|
88
106
|
domain: v(translation.domain),
|
|
89
107
|
from: v(translation.from),
|
|
@@ -91,45 +109,66 @@ module Hecks
|
|
|
91
109
|
}
|
|
92
110
|
send_to("Translation::TranslationAggregate.Declare", name,
|
|
93
111
|
with: { translation_ref: parent, name: v(name), was: v(aggregate.was) })
|
|
112
|
+
end
|
|
94
113
|
|
|
114
|
+
def judge_renames(aggregate, name)
|
|
95
115
|
Hash(aggregate.renames).each do |from, to|
|
|
96
116
|
send_to("Translation::TranslationAggregate.AddRename", name, to: name,
|
|
97
117
|
with: { from: v(from), to: v(to) })
|
|
98
118
|
end
|
|
119
|
+
end
|
|
99
120
|
|
|
121
|
+
def judge_moves(aggregate, name)
|
|
100
122
|
Array(aggregate.moves).each do |move|
|
|
101
123
|
send_to("Translation::TranslationAggregate.AddMove", name, to: name,
|
|
102
124
|
with: { from: v(move.from), to: v(move.to) })
|
|
103
125
|
end
|
|
126
|
+
end
|
|
104
127
|
|
|
128
|
+
def judge_converts(aggregate, name)
|
|
105
129
|
Array(aggregate.converts).each do |convert|
|
|
106
130
|
send_to("Translation::TranslationAggregate.AddConvert", name, to: name,
|
|
107
|
-
with: { from:
|
|
131
|
+
with: { from: v(convert.from),
|
|
132
|
+
to: v(convert.to),
|
|
133
|
+
values: v(convert.values.inspect) })
|
|
108
134
|
end
|
|
135
|
+
end
|
|
109
136
|
|
|
137
|
+
def judge_drops(aggregate, name)
|
|
110
138
|
Array(aggregate.drops).each do |dropped|
|
|
111
139
|
send_to("Translation::TranslationAggregate.AddDrop", name, to: name,
|
|
112
140
|
with: { value: v(dropped) })
|
|
113
141
|
end
|
|
142
|
+
end
|
|
114
143
|
|
|
144
|
+
def judge_retypes(aggregate, name)
|
|
115
145
|
Array(aggregate.retypes).each do |retype|
|
|
116
146
|
send_to("Translation::TranslationAggregate.AddRetype", name, to: name,
|
|
117
147
|
with: { from: v(retype.from), to: v(retype.to) })
|
|
118
148
|
end
|
|
149
|
+
end
|
|
119
150
|
|
|
151
|
+
def judge_computes(aggregate, name)
|
|
120
152
|
Array(aggregate.computes).each do |compute|
|
|
121
153
|
send_to("Translation::TranslationAggregate.AddCompute", name, to: name,
|
|
122
|
-
with: { from: v(compute.from),
|
|
154
|
+
with: { from: v(compute.from),
|
|
155
|
+
to: v(compute.to),
|
|
156
|
+
sql: v(compute.sql) })
|
|
123
157
|
end
|
|
158
|
+
end
|
|
124
159
|
|
|
160
|
+
def judge_rekeys(aggregate, name)
|
|
125
161
|
Array(aggregate.rekeys).each do |rekey|
|
|
126
162
|
send_to("Translation::TranslationAggregate.AddRekey", name, to: name,
|
|
127
163
|
with: { sql: v(rekey.sql) })
|
|
128
164
|
end
|
|
165
|
+
end
|
|
129
166
|
|
|
167
|
+
def judge_backfills(aggregate, name)
|
|
130
168
|
Array(aggregate.backfills).each do |backfill|
|
|
131
169
|
send_to("Translation::TranslationAggregate.AddBackfill", name, to: name,
|
|
132
|
-
with: { name:
|
|
170
|
+
with: { name: v(backfill.name),
|
|
171
|
+
default: v(backfill.default.inspect) })
|
|
133
172
|
end
|
|
134
173
|
end
|
|
135
174
|
end
|
|
@@ -52,11 +52,11 @@ module Hecks
|
|
|
52
52
|
# concept file requires no second catalog here; deterministic filename
|
|
53
53
|
# order is the source order exported to IR.
|
|
54
54
|
GRAMMAR_DIR = File.expand_path("../language/bluebook", __dir__).freeze
|
|
55
|
-
GRAMMAR_FILES = Dir.glob(File.join(GRAMMAR_DIR, "*.bluebook")).
|
|
55
|
+
GRAMMAR_FILES = Dir.glob(File.join(GRAMMAR_DIR, "*.bluebook")).freeze
|
|
56
56
|
# Sibling artifact languages use the same folder-is-the-chapter rule.
|
|
57
57
|
# Their arrays are discovered, sorted source sets—not filename catalogs.
|
|
58
|
-
WORLD_GRAMMAR = Dir.glob(File.expand_path("../language/world/*.bluebook", __dir__)).
|
|
59
|
-
HECKSAGON_GRAMMAR = Dir.glob(File.expand_path("../language/hecksagon/*.bluebook", __dir__)).
|
|
58
|
+
WORLD_GRAMMAR = Dir.glob(File.expand_path("../language/world/*.bluebook", __dir__)).freeze
|
|
59
|
+
HECKSAGON_GRAMMAR = Dir.glob(File.expand_path("../language/hecksagon/*.bluebook", __dir__)).freeze
|
|
60
60
|
# so is a port — whole-project table-unification survey, item #13's
|
|
61
61
|
# remaining builders. Backs the new PortJudge door the same way
|
|
62
62
|
# WORLD_GRAMMAR backs WorldJudge.
|
|
@@ -65,7 +65,7 @@ module Hecks
|
|
|
65
65
|
ADAPTER_GRAMMAR = File.expand_path("../language/adapter.bluebook", __dir__).freeze
|
|
66
66
|
# so is a translation — same reasoning, one file over. Backs
|
|
67
67
|
# TranslationJudge.
|
|
68
|
-
TRANSLATION_GRAMMAR = Dir.glob(File.expand_path("../language/translation/*.bluebook", __dir__)).
|
|
68
|
+
TRANSLATION_GRAMMAR = Dir.glob(File.expand_path("../language/translation/*.bluebook", __dir__)).freeze
|
|
69
69
|
|
|
70
70
|
# ADR 0026's OWN SEAM: THE CORE DOES NOT NAME ITS EXTENSION POINTS.
|
|
71
71
|
#
|
|
@@ -483,7 +483,7 @@ module Hecks
|
|
|
483
483
|
# is added there.
|
|
484
484
|
def self.load_attached_grammar_into(registry)
|
|
485
485
|
Hecks.with_registry(registry) do
|
|
486
|
-
Dir.glob(File.join(ATTACHED_GRAMMAR_DIR, "*.bluebook")).
|
|
486
|
+
Dir.glob(File.join(ATTACHED_GRAMMAR_DIR, "*.bluebook")).each { |file| Kernel.load(file) }
|
|
487
487
|
end
|
|
488
488
|
end
|
|
489
489
|
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
require "set"
|
|
2
|
-
|
|
3
1
|
module Hecks
|
|
4
2
|
module Bluebook
|
|
5
3
|
# Lightweight formal methods over the IR — the same family as TLA+/
|
|
@@ -89,7 +87,7 @@ module Hecks
|
|
|
89
87
|
findings.concat(lifecycle_findings(aggregate, aggregate))
|
|
90
88
|
aggregate.entities.each { |entity| findings.concat(lifecycle_findings(aggregate, entity)) }
|
|
91
89
|
end
|
|
92
|
-
bluebook.process_managers.each { |
|
|
90
|
+
bluebook.process_managers.each { |process_manager| findings.concat(saga_findings(bluebook, process_manager)) }
|
|
93
91
|
bluebook.policies.each { |policy| findings.concat(policy_findings(bluebook, policy, hecksagon, known_domains)) }
|
|
94
92
|
findings
|
|
95
93
|
end
|
|
@@ -103,38 +101,46 @@ module Hecks
|
|
|
103
101
|
subject = declaring.equal?(aggregate) ? aggregate.hecks_name : "#{aggregate.hecks_name}::#{declaring.hecks_name}"
|
|
104
102
|
commands = Array(declaring.commands).map(&:hecks_name)
|
|
105
103
|
|
|
106
|
-
findings = []
|
|
107
|
-
findings.concat(unknown_transition_commands(lifecycle, commands, subject))
|
|
108
|
-
|
|
109
104
|
full = full_states(lifecycle)
|
|
110
105
|
reached = reachable_states(lifecycle)
|
|
111
106
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
107
|
+
findings = []
|
|
108
|
+
findings.concat(unknown_transition_commands(lifecycle, commands, subject))
|
|
109
|
+
findings.concat(unreachable_state_findings(lifecycle, full, reached, subject))
|
|
110
|
+
findings.concat(dead_transition_findings(lifecycle, reached, subject))
|
|
111
|
+
findings.concat(stuck_state_findings(lifecycle, reached, subject))
|
|
112
|
+
findings
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def unreachable_state_findings(lifecycle, full, reached, subject)
|
|
116
|
+
(full - reached.to_a).map do |state|
|
|
117
|
+
Finding.new(kind: :unreachable_state, severity: :error, subject: subject,
|
|
118
|
+
message: "#{state.inspect} is declared (in a transition's from: or target) " \
|
|
119
|
+
"but no path from #{lifecycle.default.inspect} ever reaches it")
|
|
116
120
|
end
|
|
121
|
+
end
|
|
117
122
|
|
|
118
|
-
|
|
123
|
+
def dead_transition_findings(lifecycle, reached, subject)
|
|
124
|
+
lifecycle.transitions.filter_map do |command, transition|
|
|
119
125
|
next unless transition.constrained?
|
|
120
126
|
next if Array(transition.from).any? { |source| reached.include?(source) }
|
|
121
127
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
128
|
+
Finding.new(kind: :dead_transition, severity: :error, subject: subject,
|
|
129
|
+
message: "#{command} from #{Array(transition.from).inspect} can never fire — " \
|
|
130
|
+
"none of those states is ever reached")
|
|
125
131
|
end
|
|
132
|
+
end
|
|
126
133
|
|
|
134
|
+
def stuck_state_findings(lifecycle, reached, subject)
|
|
127
135
|
any_unconstrained = lifecycle.transitions.any? { |_, t| !t.constrained? }
|
|
128
|
-
(reached - terminal_exempt(lifecycle)).
|
|
136
|
+
(reached - terminal_exempt(lifecycle)).filter_map do |state|
|
|
129
137
|
next if any_unconstrained
|
|
130
138
|
next if lifecycle.transitions.any? { |_, t| t.constrained? && Array(t.from).include?(state) }
|
|
131
139
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
140
|
+
Finding.new(kind: :stuck_state, severity: :warning, subject: subject,
|
|
141
|
+
message: "#{state.inspect} is reached but no transition ever leaves it — " \
|
|
142
|
+
"fine if that is meant to be terminal")
|
|
135
143
|
end
|
|
136
|
-
|
|
137
|
-
findings
|
|
138
144
|
end
|
|
139
145
|
|
|
140
146
|
def unknown_transition_commands(lifecycle, commands, subject)
|
|
@@ -165,6 +171,11 @@ module Hecks
|
|
|
165
171
|
reached = Set.new([lifecycle.default])
|
|
166
172
|
loop do
|
|
167
173
|
grown = false
|
|
174
|
+
# `transitions` is an Array of [from, transition]-shaped entries
|
|
175
|
+
# (Lifecycle#transitions), not a Hash — Style/HashEachMethods'
|
|
176
|
+
# `each_value` rewrite assumed otherwise from the block shape
|
|
177
|
+
# alone. False positive.
|
|
178
|
+
# rubocop:disable-next Style/HashEachMethods
|
|
168
179
|
lifecycle.transitions.each do |_, transition|
|
|
169
180
|
next if reached.include?(transition.target)
|
|
170
181
|
next if transition.constrained? && Array(transition.from).none? { |source| reached.include?(source) }
|
|
@@ -202,79 +213,105 @@ module Hecks
|
|
|
202
213
|
|
|
203
214
|
# ── process managers / sagas ──────────────────────────────────────
|
|
204
215
|
|
|
205
|
-
def saga_findings(bluebook,
|
|
216
|
+
def saga_findings(bluebook, process_manager)
|
|
217
|
+
emitted = emitted_events(bluebook)
|
|
218
|
+
verbs = verbs_of(bluebook)
|
|
219
|
+
reached = pm_reachable_states(process_manager, emitted)
|
|
220
|
+
|
|
206
221
|
findings = []
|
|
207
|
-
emitted
|
|
208
|
-
|
|
222
|
+
findings.concat(deaf_trigger_findings(process_manager, emitted))
|
|
223
|
+
findings.concat(unreachable_pm_state_findings(process_manager, reached))
|
|
224
|
+
process_manager.handlers.each do |handler|
|
|
225
|
+
findings.concat(handler_findings(bluebook, process_manager, emitted, verbs, handler))
|
|
226
|
+
end
|
|
227
|
+
findings.concat(dead_compensation_findings(process_manager, reached))
|
|
228
|
+
findings
|
|
229
|
+
end
|
|
209
230
|
|
|
210
|
-
|
|
231
|
+
def deaf_trigger_findings(process_manager, emitted)
|
|
232
|
+
[process_manager.starts_on, process_manager.ends_on].compact.filter_map do |event|
|
|
211
233
|
next if emitted.include?(bare(event))
|
|
212
234
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
235
|
+
Finding.new(kind: :deaf_trigger, severity: :error, subject: process_manager.name,
|
|
236
|
+
message: "starts_on/ends_on names #{event.inspect}, which no command in this " \
|
|
237
|
+
"domain emits")
|
|
216
238
|
end
|
|
239
|
+
end
|
|
217
240
|
|
|
218
|
-
|
|
219
|
-
(Array(
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
241
|
+
def unreachable_pm_state_findings(process_manager, reached)
|
|
242
|
+
(Array(process_manager.states) - reached.to_a).map do |state|
|
|
243
|
+
Finding.new(kind: :unreachable_pm_state, severity: :error, subject: process_manager.name,
|
|
244
|
+
message: "#{state.inspect} is declared but no handler chain from " \
|
|
245
|
+
"#{process_manager.states.first.inspect} ever reaches it")
|
|
223
246
|
end
|
|
247
|
+
end
|
|
224
248
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
"in this domain emits")
|
|
234
|
-
end
|
|
249
|
+
# ONE HANDLER'S OWN FINDINGS — deaf_handler, unknown_dispatch (one
|
|
250
|
+
# per dispatch), and unarmed_compensation (one per compensating
|
|
251
|
+
# dispatch), pulled out of saga_findings' own handler loop; each
|
|
252
|
+
# check reads only this handler plus the domain-wide emitted/verbs
|
|
253
|
+
# sets saga_findings already resolved once, no state shared BETWEEN
|
|
254
|
+
# handlers.
|
|
255
|
+
def handler_findings(bluebook, process_manager, emitted, verbs, handler)
|
|
256
|
+
findings = []
|
|
235
257
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
findings << Finding.new(kind: :unknown_dispatch, severity: :error, subject: pm.name,
|
|
246
|
-
message: "dispatches #{dispatch.command_name.inspect}, which this domain " \
|
|
247
|
-
"declares no command at — cross-domain dispatch is out of this " \
|
|
248
|
-
"checker's scope, same as CommandRules#resolve_references")
|
|
249
|
-
end
|
|
258
|
+
# The compensating leg answers REFUSED, a synthetic trigger no
|
|
259
|
+
# command ever emits by name (ProcessManager::REFUSED) — not
|
|
260
|
+
# a deaf handler, the one handler this domain's own events can
|
|
261
|
+
# never satisfy on purpose.
|
|
262
|
+
if handler.event_type != ProcessManager::REFUSED && !emitted.include?(bare(handler.event_type))
|
|
263
|
+
findings << Finding.new(kind: :deaf_handler, severity: :error, subject: process_manager.name,
|
|
264
|
+
message: "a handler answers #{handler.event_type.inspect}, which no command " \
|
|
265
|
+
"in this domain emits")
|
|
266
|
+
end
|
|
250
267
|
|
|
251
|
-
|
|
252
|
-
#
|
|
253
|
-
#
|
|
254
|
-
#
|
|
255
|
-
#
|
|
256
|
-
#
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
268
|
+
handler.dispatches.each do |dispatch|
|
|
269
|
+
# SAME-DOMAIN, same as `SagaInterpreter#qualified` — a dispatch
|
|
270
|
+
# naming no domain at all (the ordinary shape a bare command
|
|
271
|
+
# constant now produces, S6) means THIS one, and is compared
|
|
272
|
+
# against `verbs_of`'s own fully-qualified spelling qualified
|
|
273
|
+
# the identical way, not left bare to miss it on a technicality.
|
|
274
|
+
qualified = if dispatch.command_name.include?("::")
|
|
275
|
+
dispatch.command_name
|
|
276
|
+
else
|
|
277
|
+
"#{bluebook.name}::#{dispatch.command_name}"
|
|
278
|
+
end
|
|
279
|
+
next if verbs.include?(qualified)
|
|
280
|
+
|
|
281
|
+
findings << Finding.new(kind: :unknown_dispatch, severity: :error, subject: process_manager.name,
|
|
282
|
+
message: "dispatches #{dispatch.command_name.inspect}, which this domain " \
|
|
283
|
+
"declares no command at — cross-domain dispatch is out of this " \
|
|
284
|
+
"checker's scope, same as CommandRules#resolve_references")
|
|
267
285
|
end
|
|
268
286
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
287
|
+
# A `compensates` DECLARED WITH NOWHERE TO EVER FIRE — the exact
|
|
288
|
+
# shape of the real bug this whole feature closes ("the
|
|
289
|
+
# reversal was written and never armed"), caught at build/
|
|
290
|
+
# model-check time instead of discovered in production. No
|
|
291
|
+
# handler anywhere answers REFUSED (`process_manager.saga?` false) means
|
|
292
|
+
# `SagaInterpreter#unwind` never runs for this process
|
|
293
|
+
# manager at all, so a declared `compensates` is structurally
|
|
294
|
+
# unreachable — not a warning about style, a dead declaration.
|
|
295
|
+
if !process_manager.saga? && handler.dispatches.any?(&:compensates)
|
|
296
|
+
handler.dispatches.select(&:compensates).each do |dispatch|
|
|
297
|
+
findings << Finding.new(kind: :unarmed_compensation, severity: :error, subject: process_manager.name,
|
|
298
|
+
message: "#{dispatch.command_name} compensates #{dispatch.compensates.command_name}, " \
|
|
299
|
+
"but no handler anywhere in this saga answers a refusal — the " \
|
|
300
|
+
"compensation is declared and can never fire")
|
|
301
|
+
end
|
|
273
302
|
end
|
|
274
303
|
|
|
275
304
|
findings
|
|
276
305
|
end
|
|
277
306
|
|
|
307
|
+
def dead_compensation_findings(process_manager, reached)
|
|
308
|
+
return [] unless process_manager.saga? && !reached.include?(process_manager.saga.from_state)
|
|
309
|
+
|
|
310
|
+
[Finding.new(kind: :dead_compensation, severity: :error, subject: process_manager.name,
|
|
311
|
+
message: "the compensation leaves #{process_manager.saga.from_state.inspect}, which no " \
|
|
312
|
+
"handler chain ever reaches — a refusal here can never fire it")]
|
|
313
|
+
end
|
|
314
|
+
|
|
278
315
|
# A handler edge is only usable in the closure if it can actually
|
|
279
316
|
# FIRE — REFUSED always can (it is a compensation trigger, not an
|
|
280
317
|
# event), and any other handler needs its event genuinely emitted.
|
|
@@ -283,13 +320,13 @@ module Hecks
|
|
|
283
320
|
# it, which would hide exactly the states this walk exists to
|
|
284
321
|
# catch (a state only "reachable" through a handler that itself
|
|
285
322
|
# never fires).
|
|
286
|
-
def pm_reachable_states(
|
|
287
|
-
return Set.new if Array(
|
|
323
|
+
def pm_reachable_states(process_manager, emitted)
|
|
324
|
+
return Set.new if Array(process_manager.states).empty?
|
|
288
325
|
|
|
289
|
-
reached = Set.new([
|
|
326
|
+
reached = Set.new([process_manager.states.first])
|
|
290
327
|
loop do
|
|
291
328
|
grown = false
|
|
292
|
-
|
|
329
|
+
process_manager.handlers.each do |handler|
|
|
293
330
|
next unless handler.event_type == ProcessManager::REFUSED || emitted.include?(bare(handler.event_type))
|
|
294
331
|
next unless reached.include?(handler.from_state)
|
|
295
332
|
next if reached.include?(handler.to_state)
|
|
@@ -446,9 +483,9 @@ module Hecks
|
|
|
446
483
|
bluebook.aggregates.flat_map do |aggregate|
|
|
447
484
|
verbs = aggregate.commands.map { |command| "#{bluebook.name}::#{aggregate.hecks_name}.#{command.hecks_name}" }
|
|
448
485
|
verbs + aggregate.entities.flat_map do |entity|
|
|
449
|
-
entity.commands.map
|
|
486
|
+
entity.commands.map do |command|
|
|
450
487
|
"#{bluebook.name}::#{aggregate.hecks_name}.#{entity.hecks_name}.#{command.hecks_name}"
|
|
451
|
-
|
|
488
|
+
end
|
|
452
489
|
end
|
|
453
490
|
end
|
|
454
491
|
end
|
|
@@ -63,6 +63,15 @@ module Hecks
|
|
|
63
63
|
# characters "(?=" and says nothing about lookahead — which is why this
|
|
64
64
|
# steps over each backslash pair rather than matching the pattern as a
|
|
65
65
|
# whole.
|
|
66
|
+
# One character walk is the subset's DEFINITION (see the method
|
|
67
|
+
# comment above): each construct-check is a branch in a single
|
|
68
|
+
# ordered pass sharing `index`/`in_class`/`class_start`. Splitting
|
|
69
|
+
# the branches into separate methods would force those three cursor
|
|
70
|
+
# variables to thread as parameters/return values between them,
|
|
71
|
+
# turning "the subset is this walk" into "the subset is these
|
|
72
|
+
# methods agreeing about a shared cursor" — exactly what the walk's
|
|
73
|
+
# own comment says a cleverer spelling would obscure.
|
|
74
|
+
# rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
66
75
|
def validate(pattern)
|
|
67
76
|
chars = pattern.to_s.chars
|
|
68
77
|
index = 0
|
|
@@ -16,7 +16,8 @@ module Hecks
|
|
|
16
16
|
target_domain: :target_domain,
|
|
17
17
|
where: :where,
|
|
18
18
|
for_each: :for_each,
|
|
19
|
-
with_spec: -> { with_spec.map { |key, value| [key.to_s, Bluebook.render_value(value)] } }
|
|
19
|
+
with_spec: -> { with_spec.map { |key, value| [key.to_s, Bluebook.render_value(value)] } },
|
|
20
|
+
where_ast: -> { where_ast }
|
|
20
21
|
)
|
|
21
22
|
|
|
22
23
|
attr_reader :name, :on_event, :trigger_command, :target_domain, :where, :for_each, :with_spec
|
|
@@ -74,6 +74,12 @@ module Hecks
|
|
|
74
74
|
def to_s = "#{trigger} → #{to_state} (#{undoes.join(', ')})"
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
+
# The built form of a `process_manager "Name" do ... end` block,
|
|
78
|
+
# produced by `DSL::ProcessManagerBuilder` — its start/end events,
|
|
79
|
+
# correlation field, derived states, and handler rows. Its
|
|
80
|
+
# compensation half (`saga`, `Behaviour::ProcessManager#saga`) is
|
|
81
|
+
# DERIVED from the handler answering `REFUSED`, below, not declared as
|
|
82
|
+
# its own construct.
|
|
77
83
|
class ProcessManager
|
|
78
84
|
# The BLUEBOOK's name for this construct, asked the same way of a class
|
|
79
85
|
# that has crossed over and of an IR object that has not. Collapses into
|
|
@@ -35,34 +35,56 @@ module Hecks
|
|
|
35
35
|
"#{bluebook.name} in #{directory} declares latest #{world.latest.inspect}, not #{bluebook.version.inspect}"
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
-
bluebook.aggregates.each
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
38
|
+
bluebook.aggregates.each { |aggregate| register_aggregate!(aggregate, bluebook, world, realm, directory, dispatcher) }
|
|
39
|
+
bluebook.read_models.each { |model| register_read_model!(model, bluebook, world, realm, directory, dispatcher) }
|
|
40
|
+
end
|
|
41
|
+
self
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
# Split out of `register` per-aggregate: same versioned-then-current
|
|
47
|
+
# registration shape as `register_read_model!`, just applied to a
|
|
48
|
+
# command/query pair instead of a single report. No state threads
|
|
49
|
+
# back to `register` — each `add` call is independent.
|
|
50
|
+
def register_aggregate!(aggregate, bluebook, world, realm, directory, dispatcher)
|
|
51
|
+
aggregate.commands.each do |command|
|
|
52
|
+
if bluebook.version
|
|
53
|
+
add(Fqn.command(realm: realm, domain: bluebook.name, version: bluebook.version,
|
|
54
|
+
aggregate: aggregate.hecks_name, command: command.hecks_name),
|
|
55
|
+
directory, dispatcher, command.hecks_name, bluebook.version)
|
|
54
56
|
end
|
|
55
|
-
bluebook
|
|
57
|
+
if current?(bluebook, world)
|
|
58
|
+
add(Fqn.command(realm: realm, domain: bluebook.name, aggregate: aggregate.hecks_name,
|
|
59
|
+
command: command.hecks_name), directory, dispatcher, command.hecks_name, bluebook.version)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
aggregate.queries.each do |query|
|
|
63
|
+
name = Naming.snake(query.name)
|
|
64
|
+
if bluebook.version
|
|
56
65
|
add(Fqn.query(realm: realm, domain: bluebook.name, version: bluebook.version,
|
|
57
|
-
query:
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
aggregate: aggregate.hecks_name, query: name), directory, dispatcher, query.name, bluebook.version)
|
|
67
|
+
end
|
|
68
|
+
if current?(bluebook, world)
|
|
69
|
+
add(Fqn.query(realm: realm, domain: bluebook.name, aggregate: aggregate.hecks_name,
|
|
70
|
+
query: name), directory, dispatcher, query.name, bluebook.version)
|
|
60
71
|
end
|
|
61
72
|
end
|
|
62
|
-
self
|
|
63
73
|
end
|
|
64
74
|
|
|
65
|
-
|
|
75
|
+
# Split out of `register` per-read-model: registers the versioned FQN
|
|
76
|
+
# (if this bluebook declares a version) and the current/unversioned
|
|
77
|
+
# FQN (if this bluebook is the world's latest) for one report.
|
|
78
|
+
def register_read_model!(model, bluebook, world, realm, directory, dispatcher)
|
|
79
|
+
if bluebook.version
|
|
80
|
+
add(Fqn.query(realm: realm, domain: bluebook.name, version: bluebook.version,
|
|
81
|
+
query: model.query_name), directory, dispatcher, model.query_name, bluebook.version)
|
|
82
|
+
end
|
|
83
|
+
if current?(bluebook, world)
|
|
84
|
+
add(Fqn.query(realm: realm, domain: bluebook.name, query: model.query_name),
|
|
85
|
+
directory, dispatcher, model.query_name, bluebook.version)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
66
88
|
|
|
67
89
|
# THE ACTUAL "more than one tenant" MOMENT — Runtime::TenantCheck's
|
|
68
90
|
# own header names this table as the one place real multitenancy
|
|
@@ -93,9 +115,7 @@ module Hecks
|
|
|
93
115
|
|
|
94
116
|
def add(fqn, directory, dispatcher, declared_verb, domain_version)
|
|
95
117
|
existing = entries[fqn.to_s]
|
|
96
|
-
if existing
|
|
97
|
-
raise DuplicateFqn, "#{fqn} is declared in both #{existing.source_directory} and #{directory}"
|
|
98
|
-
end
|
|
118
|
+
raise DuplicateFqn, "#{fqn} is declared in both #{existing.source_directory} and #{directory}" if existing
|
|
99
119
|
|
|
100
120
|
entries[fqn.to_s] = Entry.new(
|
|
101
121
|
fqn: fqn, source_directory: directory, dispatcher: dispatcher,
|
data/lib/hecks/bluebook/query.rb
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
require_relative "behaviour/query"
|
|
2
2
|
|
|
3
3
|
module Hecks
|
|
4
|
+
# Reopened here only to add `render_value` below — the pinned
|
|
5
|
+
# wire-literal rendering (`Literal.render`) the `emits_ir` Procs
|
|
6
|
+
# throughout this chapter reach for. See lib/hecks/bluebook.rb's own
|
|
7
|
+
# header for what `Bluebook` is as a whole.
|
|
4
8
|
module Bluebook
|
|
5
9
|
def self.render_value(value) = Literal.render(value)
|
|
6
10
|
|
|
@@ -35,8 +35,8 @@ module Hecks
|
|
|
35
35
|
# `reference_target.nil?` stays a real, checkable fact for the
|
|
36
36
|
# interpreter instead of silently becoming `""`.
|
|
37
37
|
def initialize(name:, description: nil, reference_name: nil, reference_target: nil, aggregate_heads: [],
|
|
38
|
-
group_by: [], count: nil, median_field: nil, **
|
|
39
|
-
super(joins: aggregate_heads, **
|
|
38
|
+
group_by: [], count: nil, median_field: nil, **)
|
|
39
|
+
super(joins: aggregate_heads, **)
|
|
40
40
|
@name = name.to_s
|
|
41
41
|
@hecks_name = @name
|
|
42
42
|
@description = description
|
|
@@ -118,6 +118,11 @@ module Hecks
|
|
|
118
118
|
# the time that aggregate's own turn comes, the same reason
|
|
119
119
|
# `Synthesizer#args_for`'s `created` map is threaded through in
|
|
120
120
|
# this same order.
|
|
121
|
+
# `created` and `failures` thread through both the aggregate loop and the
|
|
122
|
+
# read_models loop below in the declaration order the comment above already
|
|
123
|
+
# documents; splitting would turn that ordering into an implicit contract on
|
|
124
|
+
# parameter/return passing instead of one visible sequence.
|
|
125
|
+
# rubocop:disable-next Metrics/AbcSize
|
|
121
126
|
def smoke_domain(dispatcher, domain)
|
|
122
127
|
chapter = dispatcher.registry.bluebook(domain)
|
|
123
128
|
return [] unless chapter
|
|
@@ -64,7 +64,8 @@ module Hecks
|
|
|
64
64
|
chapter.aggregates.filter_map { |a| a.value_object(type_name) }.first
|
|
65
65
|
return "smoke-test" unless value_object
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
closed_set = value_object.respond_to?(:closed_set?) && value_object.closed_set? && value_object.members.any?
|
|
68
|
+
return value_object.members.first.to_h if closed_set
|
|
68
69
|
|
|
69
70
|
value_object.attributes.to_h { |field| [field.name, field_value_for(chapter, aggregate, field.type)] }
|
|
70
71
|
end
|
|
@@ -11,6 +11,9 @@ module Hecks
|
|
|
11
11
|
# Paths follow `TranslationMove`'s convention (dotted reaches a VO
|
|
12
12
|
# member); `values` maps every old-era value that can appear to its
|
|
13
13
|
# new-era replacement.
|
|
14
|
+
# `:values` shadows Struct#values on purpose (same reasoning as
|
|
15
|
+
# AttributeCollector::OneOf) — no caller reads the built-in behavior.
|
|
16
|
+
# rubocop:disable-next Lint/StructNewOverride
|
|
14
17
|
TranslationConvert = Struct.new(:from, :to, :values)
|
|
15
18
|
|
|
16
19
|
# A value object's or entity's own TYPE NAME changed with its member
|