hecks 1.0.2 → 1.1.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 +97 -0
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +3 -2
- data/lib/hecks/adapters/driven/postgres.rb +25 -14
- 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 +17 -1
- 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 +2 -1
- 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/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 +19 -472
- 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 +11 -3
- 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 +40 -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 +23 -13
- 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/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/projector/cli_projector.rb +49 -29
- data/lib/hecks/projector/docs_projector.rb +21 -12
- data/lib/hecks/projector/exporter.rb +10 -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/null_policy.rb +6 -2
- data/lib/hecks/query_specification/common/options.rb +17 -5
- 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 +29 -2
- 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 +26 -3
- 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 +207 -48
- 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 +14 -2
|
@@ -74,27 +74,43 @@ module Hecks
|
|
|
74
74
|
held_attrs = (held_shape["attributes"] || []).to_h { |attribute| [attribute["name"], attribute] }
|
|
75
75
|
current_attrs = (current_shape["attributes"] || []).to_h { |attribute| [attribute["name"], attribute] }
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
rules.concat(retype_rules(held_attrs, current_attrs))
|
|
78
|
+
retyped = rules.filter_map { |rule| rule[:from] if rule[:kind] == :retype }
|
|
79
|
+
|
|
80
|
+
vanished = vanished_paths(held_attrs, current_attrs, retyped)
|
|
81
|
+
appeared = appeared_paths(held_attrs, current_attrs, retyped)
|
|
82
|
+
|
|
83
|
+
# Mutates `rules` in place (not build-and-concat, like the two
|
|
84
|
+
# passes above) because it must READ its own earlier writes:
|
|
85
|
+
# `taken`, below, is recomputed from `rules` at the top of every
|
|
86
|
+
# iteration, so a target this same loop already claimed for an
|
|
87
|
+
# earlier vanished path is excluded from a later one. Passing a
|
|
88
|
+
# snapshot instead of the live array would let two vanished
|
|
89
|
+
# paths both match the same appeared target.
|
|
90
|
+
resolve_vanished_rules!(rules, vanished, appeared)
|
|
91
|
+
rules
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# retype pass: same attribute name, same member structure, the
|
|
95
|
+
# TYPE's own name changed
|
|
96
|
+
def retype_rules(held_attrs, current_attrs)
|
|
97
|
+
(held_attrs.keys & current_attrs.keys).filter_map do |name|
|
|
80
98
|
held = held_attrs[name]
|
|
81
99
|
current = current_attrs[name]
|
|
82
100
|
next if held == current
|
|
83
101
|
next unless container?(held) && container?(current)
|
|
84
102
|
next unless held["type"]["members"] == current["type"]["members"] && held["list"] == current["list"]
|
|
85
103
|
|
|
86
|
-
|
|
104
|
+
{ kind: :retype, from: held["type"]["type"], to: current["type"]["type"] }
|
|
87
105
|
end
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
vanished = vanished_paths(held_attrs, current_attrs, retyped)
|
|
91
|
-
appeared = appeared_paths(held_attrs, current_attrs, retyped)
|
|
106
|
+
end
|
|
92
107
|
|
|
108
|
+
def resolve_vanished_rules!(rules, vanished, appeared)
|
|
93
109
|
vanished.each do |path, signature|
|
|
94
110
|
matches = appeared.select { |_, candidate| candidate == signature }.keys
|
|
95
111
|
taken = rules.filter_map { |rule| rule[:to] if %i[rename move].include?(rule[:kind]) }
|
|
96
112
|
matches -= taken
|
|
97
|
-
if matches.size == 1 && vanished.
|
|
113
|
+
if matches.size == 1 && vanished.one? { |_, other| other == signature }
|
|
98
114
|
target = matches.first
|
|
99
115
|
kind = path.include?(".") || target.include?(".") ? :move : :rename
|
|
100
116
|
rules << { kind: kind, from: path, to: target }
|
|
@@ -103,7 +119,6 @@ module Hecks
|
|
|
103
119
|
rules << { kind: :unresolved, from: path, candidates: candidates }
|
|
104
120
|
end
|
|
105
121
|
end
|
|
106
|
-
rules
|
|
107
122
|
end
|
|
108
123
|
|
|
109
124
|
# Held paths that need explaining: whole attributes that vanished,
|
|
@@ -10,12 +10,8 @@ module Hecks
|
|
|
10
10
|
edge.aggregates.each do |aggregate|
|
|
11
11
|
header = " aggregate #{aggregate.name.inspect}"
|
|
12
12
|
header += ", was: #{aggregate.was.inspect}" if aggregate.was
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
else
|
|
16
|
-
lines << "#{header} do"
|
|
17
|
-
aggregate.rules.each { |rule| lines << " #{render_rule(rule)}" }
|
|
18
|
-
end
|
|
13
|
+
lines << "#{header} do"
|
|
14
|
+
aggregate.rules.each { |rule| lines << " #{render_rule(rule)}" } unless aggregate.rules.empty?
|
|
19
15
|
lines << " end"
|
|
20
16
|
end
|
|
21
17
|
edge.retired.each { |name| lines << " retired #{name.inspect}" }
|
|
@@ -35,7 +35,9 @@ module Hecks
|
|
|
35
35
|
# unchanged: registered only when this registry has an aggregate
|
|
36
36
|
# actually bound to a lineage-capable adapter.
|
|
37
37
|
def contribute_boot_gates(registry, gates)
|
|
38
|
-
gates.register(:era_compute_rules,
|
|
38
|
+
gates.register(:era_compute_rules, lambda { |reg, _dir|
|
|
39
|
+
Runtime::EraCheck.check_compute_rules_for_registry!(reg)
|
|
40
|
+
}, phase: :pre_verify)
|
|
39
41
|
gates.register(:era_check, Runtime::EraCheck.method(:check_lineage!), phase: :pre_verify) if
|
|
40
42
|
Runtime::EraCheck.lineage_capable_registry?(registry)
|
|
41
43
|
end
|
|
@@ -33,7 +33,7 @@ module Hecks
|
|
|
33
33
|
"#{self.class.name} is a remote-runtime delegate — dispatch through " \
|
|
34
34
|
"Runtime::RemoteDispatcher instead of writing through the repository directly"
|
|
35
35
|
end
|
|
36
|
-
|
|
36
|
+
alias project append
|
|
37
37
|
|
|
38
38
|
def entries = []
|
|
39
39
|
end
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Ports
|
|
3
3
|
module Persistence
|
|
4
|
-
NAME = "persistence"
|
|
5
|
-
VERB = "persisted_by"
|
|
6
|
-
DEFAULT_ADAPTER = "Memory"
|
|
4
|
+
NAME = "persistence".freeze
|
|
5
|
+
VERB = "persisted_by".freeze
|
|
6
|
+
DEFAULT_ADAPTER = "Memory".freeze
|
|
7
7
|
end
|
|
8
8
|
end
|
|
9
9
|
end
|
|
@@ -18,6 +18,10 @@ require_relative "persistence/null_saga_store"
|
|
|
18
18
|
|
|
19
19
|
module Hecks
|
|
20
20
|
module Ports
|
|
21
|
+
# Reopened once `BindingPolicy`/`RepositoryFactory` (required above) are
|
|
22
|
+
# loaded, to add the persistence port's actual call surface: resolving
|
|
23
|
+
# which adapter authoritatively binds an aggregate and building the
|
|
24
|
+
# repository that reads/writes it.
|
|
21
25
|
module Persistence
|
|
22
26
|
module_function
|
|
23
27
|
|
|
@@ -7,8 +7,8 @@ module Hecks
|
|
|
7
7
|
# Projection is a read-side port. Its stores are rebuildable consumers of
|
|
8
8
|
# authoritative entries and never participate in command decisions.
|
|
9
9
|
module Projection
|
|
10
|
-
NAME = "projection"
|
|
11
|
-
VERB = "projected_by"
|
|
10
|
+
NAME = "projection".freeze
|
|
11
|
+
VERB = "projected_by".freeze
|
|
12
12
|
|
|
13
13
|
module_function
|
|
14
14
|
|
|
@@ -26,6 +26,11 @@ module Hecks
|
|
|
26
26
|
Worker.new(authoritative, target, policy: policy)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
+
# Catches one projection's store up to its authoritative journal:
|
|
30
|
+
# replays whatever entries the store hasn't yet appended/projected.
|
|
31
|
+
# Constructed via Projection.worker; #catch_up! runs from a
|
|
32
|
+
# separate process or scheduler, never from the command-side write
|
|
33
|
+
# path.
|
|
29
34
|
class Worker
|
|
30
35
|
attr_reader :projection
|
|
31
36
|
|
|
@@ -67,7 +72,10 @@ module Hecks
|
|
|
67
72
|
unless consistent?(entries, present)
|
|
68
73
|
raise Runtime::WiringError, "projection history does not match its authoritative history" if @policy == :strict
|
|
69
74
|
end
|
|
70
|
-
entries.drop(present.length).each
|
|
75
|
+
entries.drop(present.length).each do |entry|
|
|
76
|
+
@projection.append(entry)
|
|
77
|
+
@projection.project(entry)
|
|
78
|
+
end
|
|
71
79
|
@projection
|
|
72
80
|
end
|
|
73
81
|
|
|
@@ -78,9 +86,9 @@ module Hecks
|
|
|
78
86
|
def same_entry?(left, right) = left.operation == right.operation && left.id == right.id && left.state == right.state
|
|
79
87
|
|
|
80
88
|
def consistent?(entries, present)
|
|
81
|
-
present.length <= entries.length && entries.first(present.length).zip(present).all?
|
|
89
|
+
present.length <= entries.length && entries.first(present.length).zip(present).all? do |left, right|
|
|
82
90
|
same_entry?(left, right)
|
|
83
|
-
|
|
91
|
+
end
|
|
84
92
|
end
|
|
85
93
|
end
|
|
86
94
|
|
|
@@ -6,6 +6,12 @@ require_relative "../../query_specification/common/comparison"
|
|
|
6
6
|
module Hecks
|
|
7
7
|
module Ports
|
|
8
8
|
module Query
|
|
9
|
+
# In-memory implementation of a declared query specification:
|
|
10
|
+
# applies wheres/order_by/offset/limit directly to an Array of
|
|
11
|
+
# records instead of compiling SQL. Deliberately kept in exact
|
|
12
|
+
# LIMIT/OFFSET-order agreement with SqlQueryBuilder (see #execute's
|
|
13
|
+
# own comment) so a query answers the same page regardless of
|
|
14
|
+
# which adapter backs it.
|
|
9
15
|
module InMemory
|
|
10
16
|
FieldPath = QuerySpecification::FieldPath
|
|
11
17
|
Comparison = QuerySpecification::Common::Comparison
|
|
@@ -14,9 +20,9 @@ module Hecks
|
|
|
14
20
|
|
|
15
21
|
def execute(records, declared, args = {}, registry: nil)
|
|
16
22
|
matched = records.select do |record|
|
|
17
|
-
declared.wheres.all?
|
|
23
|
+
declared.wheres.all? do |clause|
|
|
18
24
|
holds?(clause, comparable(FieldPath.dig(record, clause.field)), args, registry: registry)
|
|
19
|
-
|
|
25
|
+
end
|
|
20
26
|
end
|
|
21
27
|
field = declared.order_by&.field
|
|
22
28
|
matched = Ordering.apply(matched, declared.order_by, declared.null_semantics,
|
data/lib/hecks/ports/query.rb
CHANGED
|
@@ -19,9 +19,7 @@ module Hecks
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def validate!(specification, adapter)
|
|
22
|
-
if specification.cursor && specification.offset
|
|
23
|
-
raise Unsupported, "a query cannot combine cursor and offset pagination"
|
|
24
|
-
end
|
|
22
|
+
raise Unsupported, "a query cannot combine cursor and offset pagination" if specification.cursor && specification.offset
|
|
25
23
|
|
|
26
24
|
return if specification.inspection.nil? || adapter.respond_to?(:inspect_query)
|
|
27
25
|
return if specification.inspection.mode.to_s == "sql" && adapter.respond_to?(:query)
|
data/lib/hecks/ports.rb
CHANGED
|
@@ -448,9 +448,17 @@ module Hecks
|
|
|
448
448
|
# single source at all — its own field NAMES are the fact worth
|
|
449
449
|
# stating (real: `Order.AddTopping` appends `name, amount`).
|
|
450
450
|
def surface_diagram(bluebook, holder)
|
|
451
|
-
lines = holder.commands.map
|
|
452
|
-
|
|
453
|
-
|
|
451
|
+
lines = holder.commands.map do |command|
|
|
452
|
+
" #{holder.hecks_name}[(#{holder.hecks_name})] -->|does| #{command_node(holder.hecks_name, command.hecks_name)}"
|
|
453
|
+
end
|
|
454
|
+
lines += holder.commands.flat_map do |command|
|
|
455
|
+
command.mutations.map do |mutation|
|
|
456
|
+
mutation_edge(holder, command, mutation)
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
lines += holder.queries.map do |query|
|
|
460
|
+
" #{holder.hecks_name}[(#{holder.hecks_name})] -.->|asks| #{query_node(holder.hecks_name, query.hecks_name)}"
|
|
461
|
+
end
|
|
454
462
|
|
|
455
463
|
subject = "#{holder.hecks_name}'s own declared commands (and what each writes) and queries"
|
|
456
464
|
"#{header(bluebook.name, subject)}flowchart LR\n#{lines.uniq.join("\n")}\n"
|
data/lib/hecks/projections/ir.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
require_relative "../projector"
|
|
2
2
|
|
|
3
3
|
module Hecks
|
|
4
|
+
# Reopened from projections.rb (see there for the namespace's full
|
|
5
|
+
# rationale) to define and register the :ir target below.
|
|
4
6
|
module Projections
|
|
5
7
|
# The canonical IR, as a constant. The implementation already existed
|
|
6
8
|
# and is already golden-tested (`Projector::IRProjector`, registered
|
|
@@ -60,7 +60,8 @@ module Hecks
|
|
|
60
60
|
"Bluebook" => { ir_version: "the EMISSION's own version, not the domain's",
|
|
61
61
|
canonical_form: "the normalisation table every reader needs beside the IR" },
|
|
62
62
|
"ValueObject" => { closed_set: "an empty one_of and no one_of are otherwise indistinguishable" },
|
|
63
|
-
"Aggregate" => { ports: "declared in the hecksagon, attached after the aggregate exists" }
|
|
63
|
+
"Aggregate" => { ports: "declared in the hecksagon, attached after the aggregate exists" },
|
|
64
|
+
"Policy" => { where_ast: "the structured form of `where`, derived from the same text at emission" }
|
|
64
65
|
}.freeze
|
|
65
66
|
|
|
66
67
|
# DECLARED, AND DELIBERATELY NOT EMITTED. The category that had no
|
|
@@ -50,7 +50,11 @@ module Hecks
|
|
|
50
50
|
# a binding that reads an event field and one that supplies a
|
|
51
51
|
# literal string are otherwise indistinguishable once written
|
|
52
52
|
# down (see `MetaValidator::Readings`' own note on exactly that).
|
|
53
|
-
renders: { with_spec: "-> { with_spec.map { |key, value| [key.to_s, Bluebook.render_value(value)] } }"
|
|
53
|
+
renders: { with_spec: "-> { with_spec.map { |key, value| [key.to_s, Bluebook.render_value(value)] } }",
|
|
54
|
+
# COMPUTED (Deviations::COMPUTED["Policy"]): the structured
|
|
55
|
+
# form of `where`, a pure function of that text, the same
|
|
56
|
+
# `ast` every rule row carries beside its `canonical`.
|
|
57
|
+
where_ast: "-> { where_ast }" },
|
|
54
58
|
settles: false
|
|
55
59
|
}
|
|
56
60
|
}.freeze
|
|
@@ -66,7 +70,7 @@ module Hecks
|
|
|
66
70
|
# GENERATED — projected from the language's own #{name} aggregate.
|
|
67
71
|
# DO NOT EDIT: the holding half is rendered, and #{host.fetch(:behaviour)}
|
|
68
72
|
# is where anything hand-written belongs.
|
|
69
|
-
require_relative "behaviour/#{File.basename(host.fetch(:file),
|
|
73
|
+
require_relative "behaviour/#{File.basename(host.fetch(:file), '.rb')}"
|
|
70
74
|
|
|
71
75
|
module Hecks
|
|
72
76
|
module Bluebook
|
|
@@ -87,14 +91,15 @@ module Hecks
|
|
|
87
91
|
|
|
88
92
|
# The emission, keyed as the model spells it and sourced as the
|
|
89
93
|
# language declares it.
|
|
94
|
+
# A COMPUTED field (Deviations::COMPUTED) is emitted too — it is
|
|
95
|
+
# model-only by definition, so it rides after the declared fields
|
|
96
|
+
# and MUST have a `renders` entry, there being nothing to `send`.
|
|
90
97
|
def emits(bluebook, name)
|
|
91
|
-
fields = emitted_fields(bluebook, name)
|
|
98
|
+
fields = emitted_fields(bluebook, name) + Deviations.computed(name)
|
|
92
99
|
renders = HOST.fetch(name).fetch(:renders, {})
|
|
93
100
|
width = fields.map { |f| f.to_s.length }.max.to_i
|
|
94
101
|
|
|
95
|
-
"emits_ir(\n" +
|
|
96
|
-
fields.map { |f| " #{"#{f}:".ljust(width + 1)} #{renders.fetch(f, ":#{f}")}" }.join(",\n") +
|
|
97
|
-
"\n)"
|
|
102
|
+
"emits_ir(\n#{fields.map { |f| " #{"#{f}:".ljust(width + 1)} #{renders.fetch(f, ":#{f}")}" }.join(",\n")}\n)"
|
|
98
103
|
end
|
|
99
104
|
|
|
100
105
|
# WHAT THE CONSTRUCT EMITS: what the language declares, less every
|
|
@@ -121,18 +126,18 @@ module Hecks
|
|
|
121
126
|
# here rather than typed in — a comment that survives
|
|
122
127
|
# regeneration is one the generator writes.
|
|
123
128
|
reasons = Deviations::OFF_THE_WIRE.fetch(host.fetch(:construct, ""), {})
|
|
124
|
-
(lines + accessors.map
|
|
129
|
+
(lines + accessors.map do |a|
|
|
125
130
|
why = reasons[a]
|
|
126
131
|
(why ? "\n# #{a.upcase}, DECLARED AND DELIBERATELY OFF THE WIRE\n# #{wrap(why)}\n" : "") +
|
|
127
132
|
"attr_accessor :#{a}"
|
|
128
|
-
|
|
133
|
+
end).join("\n")
|
|
129
134
|
end
|
|
130
135
|
|
|
131
136
|
def constructor(host)
|
|
132
137
|
args = host.fetch(:defaults).map { |f, d| d ? "#{f}: #{d}" : "#{f}:" }.join(", ")
|
|
133
|
-
body = host.fetch(:defaults).keys.map
|
|
138
|
+
body = host.fetch(:defaults).keys.map do |f|
|
|
134
139
|
" @#{f} = #{f}#{host.fetch(:coerce, {})[f]}"
|
|
135
|
-
|
|
140
|
+
end
|
|
136
141
|
body << "\n settle" if host.fetch(:settles, true)
|
|
137
142
|
|
|
138
143
|
"def initialize(#{args})\n#{body.join("\n")}\nend"
|
|
@@ -58,9 +58,7 @@ module Hecks
|
|
|
58
58
|
|
|
59
59
|
def render(bluebook)
|
|
60
60
|
rows = tables(bluebook).sort_by(&:first).map do |name, members|
|
|
61
|
-
" #{name.inspect} => [\n"
|
|
62
|
-
members.map { |row| " #{row.inspect}.freeze" }.join(",\n") +
|
|
63
|
-
"\n ].freeze"
|
|
61
|
+
" #{name.inspect} => [\n#{members.map { |row| " #{row.inspect}.freeze" }.join(",\n")}\n ].freeze"
|
|
64
62
|
end
|
|
65
63
|
|
|
66
64
|
<<~RUBY
|
|
@@ -54,12 +54,12 @@ module Hecks
|
|
|
54
54
|
aggregate.queries.each { |q| claim(questions, name_for(aggregate, q), query_spec(bluebook, aggregate, nil, q)) }
|
|
55
55
|
|
|
56
56
|
aggregate.entities.each do |entity|
|
|
57
|
-
entity.commands.each
|
|
57
|
+
entity.commands.each do |c|
|
|
58
58
|
claim(verbs, name_for(aggregate, c, entity), command_spec(bluebook, aggregate, entity, c))
|
|
59
|
-
|
|
60
|
-
entity.queries.each
|
|
59
|
+
end
|
|
60
|
+
entity.queries.each do |q|
|
|
61
61
|
claim(questions, name_for(aggregate, q, entity), query_spec(bluebook, aggregate, entity, q))
|
|
62
|
-
|
|
62
|
+
end
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
# A PORT IS A VERB TOO, and leaving it off the map was a real gap
|
|
@@ -235,7 +235,11 @@ module Hecks
|
|
|
235
235
|
# `CommandInterpreter`/`EntityInterpreter` call it, never the port
|
|
236
236
|
# dispatch path), so `role_gated: false` always, unlike
|
|
237
237
|
# `command_spec` where the same key name means a real one.
|
|
238
|
-
role: operation.outbound?
|
|
238
|
+
role: if operation.outbound?
|
|
239
|
+
"#{aggregate.hecks_name} asking #{port.name}"
|
|
240
|
+
else
|
|
241
|
+
"#{port.name} telling #{aggregate.hecks_name}"
|
|
242
|
+
end,
|
|
239
243
|
role_gated: false,
|
|
240
244
|
summary: port_summary(port, operation), arguments: arguments }
|
|
241
245
|
end
|
|
@@ -289,8 +293,8 @@ module Hecks
|
|
|
289
293
|
# malformed records into a real store, which is what it did on its first
|
|
290
294
|
# run against the pizzas database.
|
|
291
295
|
def options_for(attribute, holder, aggregate, prefix = nil, optional = nil)
|
|
292
|
-
path
|
|
293
|
-
optional
|
|
296
|
+
path = [prefix, attribute.name].compact.join(".")
|
|
297
|
+
optional ||= attribute.optional?
|
|
294
298
|
return [reference_option(attribute)] if attribute.reference?
|
|
295
299
|
|
|
296
300
|
value_object = value_object_for(attribute, holder, aggregate)
|
|
@@ -408,38 +412,54 @@ module Hecks
|
|
|
408
412
|
text.to_s.split(/(?<=\.)\s/).first.to_s
|
|
409
413
|
end
|
|
410
414
|
|
|
415
|
+
# FOUR TEXT BLOCKS, IN FIXED DISPLAY ORDER — meta (name/kind/role),
|
|
416
|
+
# invocation, arguments, refusals. Each block is independent of the
|
|
417
|
+
# others' content (only the OUTPUT ORDER is fixed, and stays fixed
|
|
418
|
+
# below), so each is its own method returning the lines it
|
|
419
|
+
# contributes — `[]` when it contributes none — concatenated in the
|
|
420
|
+
# same order the original inline version built them in.
|
|
411
421
|
def verb_help(program, name, spec, ask: false)
|
|
422
|
+
out = verb_help_meta_lines(name, spec)
|
|
423
|
+
out.concat(verb_help_invocation_lines(program, name, spec, ask))
|
|
424
|
+
out.concat(verb_help_argument_lines(spec))
|
|
425
|
+
out.concat(verb_help_refusal_lines(spec))
|
|
426
|
+
out.join("\n")
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
def verb_help_meta_lines(name, spec)
|
|
412
430
|
out = ["#{name} — #{spec[:summary]}", ""]
|
|
413
431
|
out << "dispatches #{spec[:verb]}" if spec[:kind] == :command
|
|
414
432
|
out << "reads #{spec[:verb]}" if spec[:kind] == :query
|
|
415
433
|
out << "issued by #{spec[:role]}" if spec[:role]
|
|
416
|
-
out
|
|
434
|
+
out
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
def verb_help_invocation_lines(program, name, spec, ask)
|
|
417
438
|
invocation = ask ? "#{program} ask #{name}" : "#{program} #{name}"
|
|
418
|
-
|
|
419
|
-
|
|
439
|
+
["", " #{invocation}#{spec[:arguments].map { |a| " #{a[:path]}=…" }.join}", ""]
|
|
440
|
+
end
|
|
420
441
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
442
|
+
def verb_help_argument_lines(spec)
|
|
443
|
+
return [] if spec[:arguments].empty?
|
|
444
|
+
|
|
445
|
+
width = spec[:arguments].map { |a| a[:path].length }.max
|
|
446
|
+
lines = spec[:arguments].map do |argument|
|
|
447
|
+
notes = []
|
|
448
|
+
notes << argument[:type]
|
|
449
|
+
notes << "one of #{argument[:enum].join(', ')}" if argument[:enum]
|
|
450
|
+
notes << "matches #{argument[:pattern]}" if argument[:pattern]
|
|
451
|
+
notes << "defaults to #{argument[:default].inspect}" unless argument[:default].nil?
|
|
452
|
+
notes << argument[:note] if argument[:note]
|
|
453
|
+
notes << "optional" unless argument[:required]
|
|
454
|
+
" #{argument[:path].ljust(width)} #{notes.join('; ')}"
|
|
434
455
|
end
|
|
456
|
+
lines << ""
|
|
457
|
+
end
|
|
435
458
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
spec[:refusals].each { |refusal| out << " #{refusal}" }
|
|
439
|
-
out << ""
|
|
440
|
-
end
|
|
459
|
+
def verb_help_refusal_lines(spec)
|
|
460
|
+
return [] if Array(spec[:refusals]).empty?
|
|
441
461
|
|
|
442
|
-
|
|
462
|
+
["refused when:", *spec[:refusals].map { |refusal| " #{refusal}" }, ""]
|
|
443
463
|
end
|
|
444
464
|
end
|
|
445
465
|
end
|
|
@@ -52,7 +52,7 @@ module Hecks
|
|
|
52
52
|
out << chapter_header(bluebook, depth) unless only
|
|
53
53
|
Array(aggregates(bluebook, only)).each { |aggregate| out << aggregate_section(aggregate, only ? depth : depth + 1) }
|
|
54
54
|
out << closing(bluebook, depth + 1) unless only
|
|
55
|
-
out.compact.join("\n").rstrip
|
|
55
|
+
"#{out.compact.join("\n").rstrip}\n"
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
# A NAME THAT NAMES NOTHING IS REFUSED, not answered with an empty
|
|
@@ -179,7 +179,7 @@ module Hecks
|
|
|
179
179
|
value_object = value_object_for(attribute, holder)
|
|
180
180
|
inner =
|
|
181
181
|
if value_object
|
|
182
|
-
"{
|
|
182
|
+
"{ #{value_object.attributes.map { |f| "#{f.name}: #{f.type}" }.join(', ')} }"
|
|
183
183
|
else
|
|
184
184
|
attribute.type.to_s
|
|
185
185
|
end
|
|
@@ -244,19 +244,13 @@ module Hecks
|
|
|
244
244
|
end
|
|
245
245
|
|
|
246
246
|
def command_entry(command, holder, depth)
|
|
247
|
-
out = [h(depth, "#{command.hecks_name}#{
|
|
247
|
+
out = [h(depth, "#{command.hecks_name}#{' *(creates)*' if command.creates?}"), ""]
|
|
248
248
|
out += [command.goal, ""] if command.goal
|
|
249
249
|
out << "Issued by: **#{command.role}**." if command.role
|
|
250
250
|
out << ""
|
|
251
251
|
|
|
252
252
|
arguments = command.attributes
|
|
253
|
-
unless arguments.empty?
|
|
254
|
-
rows = arguments.map do |attribute|
|
|
255
|
-
shape = attribute.reference? ? "id of a `#{attribute.type.target_name}`" : shape_of(attribute, holder)
|
|
256
|
-
["`#{attribute.name}`", shape, attribute.optional? ? "" : "required"]
|
|
257
|
-
end
|
|
258
|
-
out << table(%w[argument shape needed], rows)
|
|
259
|
-
end
|
|
253
|
+
out << table(%w[argument shape needed], command_argument_rows(arguments, holder)) unless arguments.empty?
|
|
260
254
|
|
|
261
255
|
refusals = refusals_of(command, holder)
|
|
262
256
|
unless refusals.empty?
|
|
@@ -272,6 +266,17 @@ module Hecks
|
|
|
272
266
|
out.join("\n")
|
|
273
267
|
end
|
|
274
268
|
|
|
269
|
+
# The argument table's own rows — a pure per-attribute mapping with
|
|
270
|
+
# nothing to share with `command_entry`'s other sections, extracted
|
|
271
|
+
# only to keep that method to the one shape every section there
|
|
272
|
+
# follows: build a chunk, append it if non-empty.
|
|
273
|
+
def command_argument_rows(arguments, holder)
|
|
274
|
+
arguments.map do |attribute|
|
|
275
|
+
shape = attribute.reference? ? "id of a `#{attribute.type.target_name}`" : shape_of(attribute, holder)
|
|
276
|
+
["`#{attribute.name}`", shape, attribute.optional? ? "" : "required"]
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
275
280
|
# EVERY WAY THIS VERB CAN SAY NO, gathered from the three places a
|
|
276
281
|
# chapter states them — the lifecycle it is an edge of, its own
|
|
277
282
|
# `given`s, and the fact that a reference has to resolve. A caller
|
|
@@ -284,7 +289,11 @@ module Hecks
|
|
|
284
289
|
froms = lifecycle && lifecycle.transitions.filter_map do |name, transition|
|
|
285
290
|
Array(transition.from) if name.to_s == command.hecks_name
|
|
286
291
|
end.flatten.uniq
|
|
287
|
-
|
|
292
|
+
if froms && !froms.empty?
|
|
293
|
+
refusals << "`#{lifecycle.field}` is anything other than #{froms.map do |f|
|
|
294
|
+
"`#{f}`"
|
|
295
|
+
end.join(' or ')}"
|
|
296
|
+
end
|
|
288
297
|
|
|
289
298
|
command.attributes.select(&:reference?).each do |reference|
|
|
290
299
|
refusals << "no `#{reference.type.target_name}` exists for the id given as `#{reference.name}`"
|
|
@@ -302,7 +311,7 @@ module Hecks
|
|
|
302
311
|
queries.each do |query|
|
|
303
312
|
shape = query.to_h
|
|
304
313
|
takes = Array(shape[:attributes]).map { |a| "`#{a[:name]}`" }.join(", ")
|
|
305
|
-
out << "**#{query.hecks_name}**#{
|
|
314
|
+
out << "**#{query.hecks_name}**#{" (#{takes})" unless takes.empty?} "
|
|
306
315
|
out << (query.description ? "#{query.description} " : "")
|
|
307
316
|
filters = Array(shape[:wheres]).map { |w| "`#{w[:field]} #{w[:op]} #{w[:value].inspect}`" }
|
|
308
317
|
out << "Filters: #{filters.join(', ')}." unless filters.empty?
|
|
@@ -3,6 +3,13 @@ require_relative "../ports/persistence"
|
|
|
3
3
|
|
|
4
4
|
module Hecks
|
|
5
5
|
module Projector
|
|
6
|
+
# Registry-wide serialization to Hash/JSON: bluebook IR
|
|
7
|
+
# (`call`/`json`), era-adapter lineage-capability flags (`lineage`),
|
|
8
|
+
# and translation edges in both their DIGEST-relevant declared shape
|
|
9
|
+
# (`translation_hash`, what ApprovalDigest hashes) and their
|
|
10
|
+
# consumer-ready COMPILED shape with precompiled SQL attached
|
|
11
|
+
# (`translations`/`compiled_translation_aggregate`). Read directly by
|
|
12
|
+
# bin/ir, bin/project_rust, and the translation/audit approval digest.
|
|
6
13
|
module Exporter
|
|
7
14
|
module_function
|
|
8
15
|
|
|
@@ -150,7 +157,9 @@ module Hecks
|
|
|
150
157
|
|
|
151
158
|
translation_aggregate(aggregate).merge(
|
|
152
159
|
compiled_state_expression: Translation::RuleCompiler.compile_rules(aggregate),
|
|
153
|
-
compiled_id_expression: Translation::RuleCompiler.rekeyed?(aggregate)
|
|
160
|
+
compiled_id_expression: (if Translation::RuleCompiler.rekeyed?(aggregate)
|
|
161
|
+
Translation::RuleCompiler.compile_id_expression(aggregate)
|
|
162
|
+
end)
|
|
154
163
|
)
|
|
155
164
|
end
|
|
156
165
|
end
|