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
|
@@ -49,8 +49,21 @@ module Hecks
|
|
|
49
49
|
# recursively via `build`) with `scalar` as what's left once
|
|
50
50
|
# neither of those applies (the raw value, passed through
|
|
51
51
|
# unchanged).
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
# `boundary: false` is the QUERY door (`QueryInterpreter#normalize_args`):
|
|
53
|
+
# a query attribute's declared type names the argument for callers and
|
|
54
|
+
# generators, never a runtime shape — comparison unwraps both sides
|
|
55
|
+
# itself, so a `reference: {value: ...}` offered against a `String`
|
|
56
|
+
# query field is the documented allowance (see banking's own
|
|
57
|
+
# `Account.OpenForCustomer`), not a C3.8 mismatch.
|
|
58
|
+
# `argument: true` is the CALLER'S door only (`Interpreting#normalize_
|
|
59
|
+
# args`, every command/entity/port dispatch): the one place a nil
|
|
60
|
+
# for a non-optional attribute is the caller leaving a required
|
|
61
|
+
# argument empty (C3.7). Every other caller — `sets` copying an
|
|
62
|
+
# optional argument into state, hydration, entity elements,
|
|
63
|
+
# identity, defaults — is state assembly, where nil is a legitimate
|
|
64
|
+
# "absent is not empty" value the aggregate's own attribute may hold.
|
|
65
|
+
def for_attribute(aggregate, attribute, value, boundary: true, argument: false)
|
|
66
|
+
return nil_or_missing(aggregate, attribute, value, argument) if attribute.nil? || value.nil?
|
|
54
67
|
return reference_list(attribute, value) if attribute.list? && attribute.reference?
|
|
55
68
|
return reference_identity(attribute, value) if attribute.reference?
|
|
56
69
|
return hydrate_entity_list(aggregate, attribute, value) if attribute.list? # :list
|
|
@@ -64,21 +77,65 @@ module Hecks
|
|
|
64
77
|
# AFTER coercion, not before: a scalar arrives wrapped in whatever holder
|
|
65
78
|
# its type names (`{value: "append"}` for an OpName), and checking the
|
|
66
79
|
# raw payload would be checking the envelope.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
value
|
|
75
|
-
end
|
|
80
|
+
value_object = value_object_for(aggregate, attribute.type)
|
|
81
|
+
return bare_primitive(aggregate, attribute, value, boundary) if value_object.nil?
|
|
82
|
+
|
|
83
|
+
coerced = if value.is_a?(self) && value.type_name == value_object.hecks_name
|
|
84
|
+
value
|
|
85
|
+
else
|
|
86
|
+
build(value_object, fields_for(value_object, attribute.name, value), aggregate)
|
|
76
87
|
end
|
|
77
88
|
|
|
78
89
|
admit_declared_set(aggregate, attribute, coerced)
|
|
79
90
|
coerced
|
|
80
91
|
end
|
|
81
92
|
|
|
93
|
+
# NIL IS NOT A VALUE FOR A NON-OPTIONAL ARGUMENT (C3.7/C3.8). An
|
|
94
|
+
# `optional:` attribute and a load from the store pass nil through
|
|
95
|
+
# as they always did; a command argument offered as null for a
|
|
96
|
+
# required attribute is
|
|
97
|
+
# refused as the field the caller left empty — a value object is
|
|
98
|
+
# BUILT from no fields, so its first required field refuses with
|
|
99
|
+
# exactly the wording the Rust side's `from_json` gives it
|
|
100
|
+
# ("Money.cents expects Integer, got nil"), and a bare scalar
|
|
101
|
+
# refuses through `check_bare_primitive`'s own wording. Lists and
|
|
102
|
+
# references keep their nil passthrough (their absence is an empty
|
|
103
|
+
# relationship, `validate_relationship_cardinality`'s business).
|
|
104
|
+
# The `attribute.nil?`/`value.nil?` branch of `for_attribute`,
|
|
105
|
+
# pulled out on its own — an unknown attribute has no shape left
|
|
106
|
+
# to branch on, and a nil VALUE is either an ordinary absence
|
|
107
|
+
# (state assembly, hydration, a query ask) or, at the argument
|
|
108
|
+
# door only, `nil_argument`'s own C3.7 refusal.
|
|
109
|
+
private def nil_or_missing(aggregate, attribute, value, argument)
|
|
110
|
+
return value if attribute.nil? || !value.nil?
|
|
111
|
+
|
|
112
|
+
argument ? nil_argument(aggregate, attribute) : value
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
private def nil_argument(aggregate, attribute)
|
|
116
|
+
return nil if attribute.optional? || trusting_stored_state?
|
|
117
|
+
return nil if attribute.list? || attribute.reference?
|
|
118
|
+
|
|
119
|
+
value_object = aggregate.respond_to?(:value_object) ? value_object_for(aggregate, attribute.type) : nil
|
|
120
|
+
return build(value_object, {}, aggregate) if value_object
|
|
121
|
+
|
|
122
|
+
raise TypeMismatch,
|
|
123
|
+
RefusalWording.render("TypeMismatch", "numeric_field",
|
|
124
|
+
type: aggregate.hecks_name, field: attribute.name,
|
|
125
|
+
expected: attribute.type, offered: "nil")
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# A BARE PRIMITIVE IS TYPE-CHECKED AT THE BOUNDARY TOO (C3.8,
|
|
129
|
+
# docs/semantics/bluebook-semantics.md) — the same two predicates a
|
|
130
|
+
# value object's own fields get, so wrong-typed caller input is a
|
|
131
|
+
# TypeMismatch refusal here, never an evaluation fault later. Its
|
|
132
|
+
# `admits:` set is still checked, exactly as a value object's is.
|
|
133
|
+
private def bare_primitive(aggregate, attribute, value, boundary)
|
|
134
|
+
check_bare_primitive(aggregate, attribute, value) if boundary
|
|
135
|
+
admit_declared_set(aggregate, attribute, value)
|
|
136
|
+
value
|
|
137
|
+
end
|
|
138
|
+
|
|
82
139
|
# Aggregate-local value objects remain authoritative, which permits
|
|
83
140
|
# intentional duplication. An ordinary fact may also name an identity
|
|
84
141
|
# value object declared on another aggregate; that shape is borrowed
|
|
@@ -112,29 +169,43 @@ module Hecks
|
|
|
112
169
|
paths = target.identity_paths
|
|
113
170
|
return value if paths.empty?
|
|
114
171
|
|
|
115
|
-
direct_head =
|
|
116
|
-
|
|
117
|
-
target.attribute(head)&.type.to_s == value.type_name ? head.to_s : nil
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
parts = paths.map do |path|
|
|
121
|
-
segments = path.to_s.split(".")
|
|
122
|
-
segments.shift if direct_head && segments.first == direct_head
|
|
123
|
-
segments.reduce(materialized) do |held, segment|
|
|
124
|
-
next nil unless held.is_a?(Hash)
|
|
125
|
-
|
|
126
|
-
# `key?` decides which spelling answers, never `||` — a
|
|
127
|
-
# genuinely-held `false` must not fall through to the
|
|
128
|
-
# other spelling (usually absent) and read as `nil`.
|
|
129
|
-
sym = segment.to_sym
|
|
130
|
-
held.key?(sym) ? held[sym] : held[segment]
|
|
131
|
-
end
|
|
132
|
-
end
|
|
172
|
+
direct_head = direct_identity_head(value, target)
|
|
173
|
+
parts = paths.map { |path| identity_part(materialized, path, direct_head) }
|
|
133
174
|
return value if parts.any? { |part| part.nil? || (part.respond_to?(:empty?) && part.empty?) }
|
|
134
175
|
|
|
135
176
|
Naming.identity(parts)
|
|
136
177
|
end
|
|
137
178
|
|
|
179
|
+
# Whether `value` is itself the target's own (single) identity
|
|
180
|
+
# value object — pure, self-contained: reads only `value` and
|
|
181
|
+
# `target`, decides nothing about any particular path.
|
|
182
|
+
def direct_identity_head(value, target)
|
|
183
|
+
return nil unless value.is_a?(self) && target.identity_heads.one?
|
|
184
|
+
|
|
185
|
+
head = target.identity_heads.first
|
|
186
|
+
target.attribute(head)&.type.to_s == value.type_name ? head.to_s : nil
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# One identity path's own value out of the materialized hash —
|
|
190
|
+
# stripping a leading segment already covered by
|
|
191
|
+
# `direct_identity_head`, then walking the rest. Pure given its
|
|
192
|
+
# three inputs; extracted from `reference_identity` alongside
|
|
193
|
+
# `direct_identity_head` above purely to keep that method to its
|
|
194
|
+
# own guard-clause shape.
|
|
195
|
+
def identity_part(materialized, path, direct_head)
|
|
196
|
+
segments = path.to_s.split(".")
|
|
197
|
+
segments.shift if direct_head && segments.first == direct_head
|
|
198
|
+
segments.reduce(materialized) do |held, segment|
|
|
199
|
+
next nil unless held.is_a?(Hash)
|
|
200
|
+
|
|
201
|
+
# `key?` decides which spelling answers, never `||` — a
|
|
202
|
+
# genuinely-held `false` must not fall through to the
|
|
203
|
+
# other spelling (usually absent) and read as `nil`.
|
|
204
|
+
sym = segment.to_sym
|
|
205
|
+
held.key?(sym) ? held[sym] : held[segment]
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
138
209
|
def reference_list(attribute, value)
|
|
139
210
|
unless value.is_a?(Array)
|
|
140
211
|
raise TypeMismatch,
|
|
@@ -170,9 +241,7 @@ module Hecks
|
|
|
170
241
|
# refuse below, unchanged -- only the genuinely unambiguous
|
|
171
242
|
# single-field case auto-wraps, matching from_identifier's own
|
|
172
243
|
# precedent exactly.
|
|
173
|
-
if value_object.attributes.size == 1
|
|
174
|
-
return { value_object.attributes.first.name => value }
|
|
175
|
-
end
|
|
244
|
+
return { value_object.attributes.first.name => value } if value_object.attributes.size == 1
|
|
176
245
|
|
|
177
246
|
raise TypeMismatch,
|
|
178
247
|
RefusalWording.render("TypeMismatch", "value_object_shape",
|
|
@@ -218,7 +287,7 @@ module Hecks
|
|
|
218
287
|
# `build`, so a nested field's own default is filled in before its
|
|
219
288
|
# own invariants read it.
|
|
220
289
|
def normalize_composite_fields(aggregate, value_object, fields)
|
|
221
|
-
return fields unless aggregate
|
|
290
|
+
return fields unless aggregate.respond_to?(:value_object)
|
|
222
291
|
|
|
223
292
|
value_object.attributes.each do |attribute|
|
|
224
293
|
next if attribute.list? || !fields.key?(attribute.name)
|
|
@@ -250,13 +319,22 @@ module Hecks
|
|
|
250
319
|
# the same way, with exactly the same wording, as the identical type
|
|
251
320
|
# declared directly on a command.
|
|
252
321
|
def validate!(value_object, fields)
|
|
322
|
+
# C6.3 (docs/semantics/bluebook-semantics.md) — a value object is
|
|
323
|
+
# validated on CONSTRUCTION FROM INPUT only; state read back from
|
|
324
|
+
# the store is trusted as it was written, so tightening an
|
|
325
|
+
# invariant never makes an old record unreadable (migration is
|
|
326
|
+
# the era system's job). `hydrate` — the one load door — sets
|
|
327
|
+
# the flag; every input door leaves it unset.
|
|
328
|
+
return if trusting_stored_state?
|
|
329
|
+
|
|
330
|
+
check_required_fields(value_object, fields)
|
|
253
331
|
admit_member(value_object, fields)
|
|
254
332
|
check_admitted(value_object, fields)
|
|
255
333
|
check_numeric_fields(value_object, fields)
|
|
256
334
|
check_scalar_shapes(value_object, fields)
|
|
257
335
|
check_patterns(value_object, fields)
|
|
258
336
|
value_object.invariants.each do |invariant|
|
|
259
|
-
next if Bluebook::Expression::Evaluator.
|
|
337
|
+
next if Bluebook::Expression::Evaluator.call_rule(invariant, fields)
|
|
260
338
|
|
|
261
339
|
raise InvariantViolation,
|
|
262
340
|
RefusalWording.render("InvariantViolation", "value_object_invariant",
|
|
@@ -273,13 +351,27 @@ module Hecks
|
|
|
273
351
|
end
|
|
274
352
|
|
|
275
353
|
def hydrate(aggregate, state)
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
354
|
+
trusting_stored_state do
|
|
355
|
+
state.each_with_object({}) do |(name, value), hydrated|
|
|
356
|
+
key = name.to_sym
|
|
357
|
+
attribute = aggregate.attribute(key)
|
|
358
|
+
hydrated[key] = attribute ? for_attribute(aggregate, attribute, value) : value
|
|
359
|
+
end
|
|
280
360
|
end
|
|
281
361
|
end
|
|
282
362
|
|
|
363
|
+
TRUSTED_LOAD_KEY = :hecks_trusting_stored_state
|
|
364
|
+
|
|
365
|
+
def trusting_stored_state
|
|
366
|
+
previous = Thread.current[TRUSTED_LOAD_KEY]
|
|
367
|
+
Thread.current[TRUSTED_LOAD_KEY] = true
|
|
368
|
+
yield
|
|
369
|
+
ensure
|
|
370
|
+
Thread.current[TRUSTED_LOAD_KEY] = previous
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def trusting_stored_state? = Thread.current[TRUSTED_LOAD_KEY] == true
|
|
374
|
+
|
|
283
375
|
# S17, ADR 0026 — SEARCHES THE WHOLE ENTITY TREE, not only the
|
|
284
376
|
# root's own direct children. `aggregate` here is always the
|
|
285
377
|
# ROOT aggregate — `for_attribute`'s own `aggregate` argument is
|
|
@@ -307,16 +399,16 @@ module Hecks
|
|
|
307
399
|
entity = find_entity(aggregate, attribute.type.to_s)
|
|
308
400
|
return value unless entity
|
|
309
401
|
|
|
310
|
-
Array(value).map do |element|
|
|
402
|
+
hydrated = Array(value).map do |element|
|
|
311
403
|
next element unless element.is_a?(Hash)
|
|
312
404
|
|
|
313
|
-
element.each_with_object({}) do |(name, field_value),
|
|
405
|
+
element.each_with_object({}) do |(name, field_value), acc|
|
|
314
406
|
key = name.to_sym
|
|
315
407
|
field = entity.attribute(key)
|
|
316
|
-
|
|
408
|
+
acc[key] = field ? for_attribute(aggregate, field, field_value) : field_value
|
|
317
409
|
end
|
|
318
410
|
end
|
|
319
|
-
|
|
411
|
+
Freezer.deep(hydrated)
|
|
320
412
|
end
|
|
321
413
|
|
|
322
414
|
# `Value.identifier` used to live here: hand it a one-field value object
|
|
@@ -451,6 +543,78 @@ module Hecks
|
|
|
451
543
|
# where the domain should have said no, and the run contract recorded the
|
|
452
544
|
# crash beside genuine refusals as though the domain had judged it.
|
|
453
545
|
#
|
|
546
|
+
# C3.8 — the boundary check for an attribute whose type is a bare
|
|
547
|
+
# primitive rather than a value object: `Integer`/`Float` by exact
|
|
548
|
+
# numeric class (`NUMERIC`), `String`/booleans by rejecting a
|
|
549
|
+
# composite shape (`COMPOSITE_SHAPES`) — identical to what
|
|
550
|
+
# `check_numeric_fields`/`check_scalar_shapes` hold a value
|
|
551
|
+
# object's own fields to, worded by the same template with the
|
|
552
|
+
# owning construct as `type`. A `String` field still admits any
|
|
553
|
+
# other scalar (the self-hosted grammar's own bootstrap relies on
|
|
554
|
+
# it — see `check_scalar_shapes`' comment); the Rust boundary is
|
|
555
|
+
# stricter there, recorded in the clause.
|
|
556
|
+
private def check_bare_primitive(owner, attribute, value)
|
|
557
|
+
type = attribute.type.to_s
|
|
558
|
+
expected = NUMERIC[type]
|
|
559
|
+
mistyped = if expected
|
|
560
|
+
!value.is_a?(expected)
|
|
561
|
+
elsif NON_NUMERIC_SCALARS.include?(type)
|
|
562
|
+
COMPOSITE_SHAPES.any? { |shape| value.is_a?(shape) }
|
|
563
|
+
else
|
|
564
|
+
false
|
|
565
|
+
end
|
|
566
|
+
if mistyped
|
|
567
|
+
raise TypeMismatch,
|
|
568
|
+
RefusalWording.render("TypeMismatch", "numeric_field",
|
|
569
|
+
type: owner.hecks_name, field: attribute.name,
|
|
570
|
+
expected: type, offered: Rendering.describe(value))
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
check_numeric_bounds(owner.hecks_name, attribute.name, value)
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
# C3.3/C3.4 — the value model's own bounds, held at every boundary:
|
|
577
|
+
# an Integer must fit in signed 64 bits, a Float must be finite.
|
|
578
|
+
# One check for value-object fields and bare-primitive arguments
|
|
579
|
+
# alike (`check_numeric_fields` and `check_bare_primitive`).
|
|
580
|
+
INT64_RANGE = (-(2**63))..((2**63) - 1)
|
|
581
|
+
|
|
582
|
+
private def check_numeric_bounds(type_name, field_name, given)
|
|
583
|
+
if given.is_a?(Integer) && !INT64_RANGE.cover?(given)
|
|
584
|
+
raise TypeMismatch,
|
|
585
|
+
RefusalWording.render("TypeMismatch", "integer_range",
|
|
586
|
+
type: type_name, field: field_name, offered: Rendering.describe(given))
|
|
587
|
+
end
|
|
588
|
+
return unless given.is_a?(Float) && !given.finite?
|
|
589
|
+
|
|
590
|
+
raise TypeMismatch,
|
|
591
|
+
RefusalWording.render("TypeMismatch", "non_finite_field",
|
|
592
|
+
type: type_name, field: field_name, offered: Rendering.describe(given))
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
# C3.7 — A VALUE OBJECT IS A TYPED FIELD PRODUCT: every non-optional
|
|
596
|
+
# field arrives, or construction refuses. A missing field and a null
|
|
597
|
+
# one are the same absence (`fields[name]` reads nil for both), worded
|
|
598
|
+
# as the type mismatch it is — "{type}.{field} expects {expected}, got
|
|
599
|
+
# nil" — the identical string the Rust side's generated `from_json`
|
|
600
|
+
# gives the same input, so the corpus can pin it on both. Checked
|
|
601
|
+
# FIRST: an invariant reading a field that never arrived is exactly
|
|
602
|
+
# the thing that used to answer "invariant violated" (or nothing at
|
|
603
|
+
# all — `ToppingName`'s `{value: null}` used to be accepted and
|
|
604
|
+
# stored). A `default:` has already been filled in by `apply_defaults`;
|
|
605
|
+
# a list field's absence is an empty list, never a refusal.
|
|
606
|
+
private def check_required_fields(value_object, fields)
|
|
607
|
+
value_object.attributes.each do |attribute|
|
|
608
|
+
next if attribute.optional? || attribute.list?
|
|
609
|
+
next unless fields[attribute.name].nil?
|
|
610
|
+
|
|
611
|
+
raise TypeMismatch,
|
|
612
|
+
RefusalWording.render("TypeMismatch", "numeric_field",
|
|
613
|
+
type: value_object.hecks_name, field: attribute.name,
|
|
614
|
+
expected: attribute.type, offered: "nil")
|
|
615
|
+
end
|
|
616
|
+
end
|
|
617
|
+
|
|
454
618
|
# Checked BEFORE invariants, because an invariant reading a mistyped field
|
|
455
619
|
# is exactly the thing that used to explode.
|
|
456
620
|
NUMERIC = { "Integer" => Integer, "Float" => Numeric }.freeze
|
|
@@ -489,12 +653,7 @@ module Hecks
|
|
|
489
653
|
# cleanly (confirmed empirically), and is a legitimate,
|
|
490
654
|
# meaningful float value (a signed zero), not a corruption
|
|
491
655
|
# risk — only NaN and +/-Infinity are.
|
|
492
|
-
|
|
493
|
-
raise TypeMismatch,
|
|
494
|
-
RefusalWording.render("TypeMismatch", "non_finite_field",
|
|
495
|
-
type: value_object.hecks_name, field: attribute.name,
|
|
496
|
-
offered: Rendering.describe(given))
|
|
497
|
-
end
|
|
656
|
+
check_numeric_bounds(value_object.hecks_name, attribute.name, given)
|
|
498
657
|
end
|
|
499
658
|
end
|
|
500
659
|
|
data/lib/hecks/runtime/value.rb
CHANGED
|
@@ -108,7 +108,7 @@ module Hecks
|
|
|
108
108
|
# forever, once per shape of "gone" any caller ever invents. The
|
|
109
109
|
# caller filters and sorts the result; this only groups it.
|
|
110
110
|
def self.latest_by(rows, key)
|
|
111
|
-
rows.
|
|
111
|
+
rows.to_h { |row| [row.public_send(key), row] }.values
|
|
112
112
|
end
|
|
113
113
|
|
|
114
114
|
def method_missing(name, *args)
|
data/lib/hecks/runtime.rb
CHANGED
|
@@ -34,6 +34,7 @@ require_relative "runtime/value"
|
|
|
34
34
|
require_relative "runtime/identity"
|
|
35
35
|
require_relative "runtime/instance"
|
|
36
36
|
require_relative "runtime/registry"
|
|
37
|
+
require_relative "runtime/outbox"
|
|
37
38
|
require_relative "runtime/capability_graph"
|
|
38
39
|
require_relative "runtime/errors"
|
|
39
40
|
require_relative "runtime/refusal_wording"
|
|
@@ -54,6 +55,10 @@ require_relative "runtime/tenant_check"
|
|
|
54
55
|
require_relative "runtime/loader"
|
|
55
56
|
|
|
56
57
|
module Hecks
|
|
58
|
+
# See the file header above for what this module is and owns; nested
|
|
59
|
+
# here (rather than documented in place) only because the
|
|
60
|
+
# `require_relative` calls above must run before Ruby reopens the
|
|
61
|
+
# module they populate.
|
|
57
62
|
module Runtime
|
|
58
63
|
class << self
|
|
59
64
|
# The registry declarations are currently landing in, or nil outside a
|
|
@@ -99,8 +104,8 @@ module Hecks
|
|
|
99
104
|
# `RoleAssignment`'s own `scope`, not just its `role_name`. See
|
|
100
105
|
# `Runtime::Caller::Current`'s own header for why this lives here
|
|
101
106
|
# rather than as a command-level DSL construct.
|
|
102
|
-
def as_caller(role:, actor_id: nil, as_of: nil, scope: nil, &
|
|
103
|
-
Caller.as(role: role, actor_id: actor_id, as_of: as_of, scope: scope, &
|
|
107
|
+
def as_caller(role:, actor_id: nil, as_of: nil, scope: nil, &)
|
|
108
|
+
Caller.as(role: role, actor_id: actor_id, as_of: as_of, scope: scope, &)
|
|
104
109
|
end
|
|
105
110
|
end
|
|
106
111
|
end
|
data/lib/hecks/storehouse.rb
CHANGED
|
@@ -592,14 +592,7 @@ module Hecks
|
|
|
592
592
|
bluebook = bluebook_for(runtime)
|
|
593
593
|
fqn = aggregate ? "#{bluebook.name}::#{aggregate_ir!(bluebook, aggregate).hecks_name}" : nil
|
|
594
594
|
|
|
595
|
-
found = log_lines(bluebook.name).filter_map
|
|
596
|
-
next unless entry[:tool] == "dispatch" && entry[:ok] && entry[:events]
|
|
597
|
-
next if fqn && !entry[:verb].to_s.start_with?("#{fqn}.")
|
|
598
|
-
next if id && entry[:id] != id
|
|
599
|
-
|
|
600
|
-
entry[:events].map { |event| event.merge(time: entry[:time], verb: entry[:verb], id: entry[:id]) }
|
|
601
|
-
end.flatten(1)
|
|
602
|
-
|
|
595
|
+
found = log_lines(bluebook.name).filter_map { |entry| entry_events(entry, fqn, id) }.flatten(1)
|
|
603
596
|
found = found.last(limit.to_i) if limit
|
|
604
597
|
|
|
605
598
|
ok(domain: bluebook.name, events: found)
|
|
@@ -607,6 +600,20 @@ module Hecks
|
|
|
607
600
|
refused(e)
|
|
608
601
|
end
|
|
609
602
|
|
|
603
|
+
# One log line's own contribution to `events`, above — nil unless it
|
|
604
|
+
# was a successful dispatch carrying events, matching `fqn` (when an
|
|
605
|
+
# aggregate narrowed the search) and `id` (when a record did). A
|
|
606
|
+
# self-contained per-entry check with nothing to share with its
|
|
607
|
+
# neighbors, extracted only to keep `events` itself to the query's
|
|
608
|
+
# own shape: build the filter, apply the limit, wrap the result.
|
|
609
|
+
def entry_events(entry, fqn, id)
|
|
610
|
+
return unless entry[:tool] == "dispatch" && entry[:ok] && entry[:events]
|
|
611
|
+
return if fqn && !entry[:verb].to_s.start_with?("#{fqn}.")
|
|
612
|
+
return if id && entry[:id] != id
|
|
613
|
+
|
|
614
|
+
entry[:events].map { |event| event.merge(time: entry[:time], verb: entry[:verb], id: entry[:id]) }
|
|
615
|
+
end
|
|
616
|
+
|
|
610
617
|
# ── shared shape ────────────────────────────────────────────────────
|
|
611
618
|
|
|
612
619
|
# `Runtime::WiringError` BELONGS HERE TOO, alongside the true domain
|
data/lib/hecks/version.rb
CHANGED
data/lib/hecks/vocabulary.rb
CHANGED
|
@@ -153,6 +153,7 @@ module Hecks
|
|
|
153
153
|
{"refusal"=>"TypeMismatch", "site"=>"composite_identity", "template"=>"{type} is a composite identity — an identity must have exactly one field"}.freeze,
|
|
154
154
|
{"refusal"=>"TypeMismatch", "site"=>"numeric_field", "template"=>"{type}.{field} expects {expected}, got {offered}"}.freeze,
|
|
155
155
|
{"refusal"=>"TypeMismatch", "site"=>"non_finite_field", "template"=>"{type}.{field} must be a finite number, got {offered}"}.freeze,
|
|
156
|
+
{"refusal"=>"TypeMismatch", "site"=>"integer_range", "template"=>"{type}.{field} must fit in a 64-bit integer, got {offered}"}.freeze,
|
|
156
157
|
{"refusal"=>"TypeMismatch", "site"=>"pattern_mismatch", "template"=>"{type}.{field} must match {pattern}, got {offered}"}.freeze,
|
|
157
158
|
{"refusal"=>"TypeMismatch", "site"=>"arithmetic_amount", "template"=>"{op} of {target} needs an Integer, got {offered}"}.freeze,
|
|
158
159
|
{"refusal"=>"TypeMismatch", "site"=>"arithmetic_current", "template"=>"{op} of {target} needs an Integer {target}, got {offered}"}.freeze,
|
data/lib/hecks.rb
CHANGED
|
@@ -34,12 +34,16 @@ require_relative "hecks/storehouse"
|
|
|
34
34
|
require_relative "hecks/framework"
|
|
35
35
|
require_relative "hecks/embryonaut_bluebook"
|
|
36
36
|
|
|
37
|
+
# The root namespace and public facade of the whole DSL/runtime: `Hecks.boot`/
|
|
38
|
+
# `.boot_files` assemble a running domain from `.bluebook`/`.hecksagon`/
|
|
39
|
+
# `.world` files, and `.bluebook`/`.hecksagon`/`.port`/`.adapter`/`.world`/
|
|
40
|
+
# `.data_translation` are the top-level declaration words every such file
|
|
41
|
+
# opens with, each collecting its built construct into the currently booting
|
|
42
|
+
# Registry (`#collect`, private below).
|
|
37
43
|
module Hecks
|
|
38
44
|
class LoadOutsideBoot < StandardError; end
|
|
39
45
|
|
|
40
46
|
class << self
|
|
41
|
-
attr_reader :current_registry
|
|
42
|
-
|
|
43
47
|
# The loading words below collect into the registry the RUNTIME is
|
|
44
48
|
# holding open ; booting and that ambient state belong to the runtime
|
|
45
49
|
# layer, so this module is their facade and Hecks::Runtime is where
|
|
@@ -73,7 +77,7 @@ module Hecks
|
|
|
73
77
|
Runtime.boot_files(paths, shared: shared, install_facade: install_facade, environment: environment)
|
|
74
78
|
end
|
|
75
79
|
|
|
76
|
-
def with_registry(registry, &
|
|
80
|
+
def with_registry(registry, &) = Runtime.with_registry(registry, &)
|
|
77
81
|
|
|
78
82
|
def current_registry = Runtime.current_registry
|
|
79
83
|
|
|
@@ -91,15 +95,15 @@ module Hecks
|
|
|
91
95
|
#
|
|
92
96
|
# `as_of` and `scope` are OPTIONAL too, same shape — see
|
|
93
97
|
# `Runtime.as_caller`'s own header for what each does.
|
|
94
|
-
def as_caller(role:, actor_id: nil, as_of: nil, scope: nil, &
|
|
95
|
-
Runtime.as_caller(role: role, actor_id: actor_id, as_of: as_of, scope: scope, &
|
|
98
|
+
def as_caller(role:, actor_id: nil, as_of: nil, scope: nil, &)
|
|
99
|
+
Runtime.as_caller(role: role, actor_id: actor_id, as_of: as_of, scope: scope, &)
|
|
96
100
|
end
|
|
97
101
|
|
|
98
|
-
def bluebook(name, version: nil, &
|
|
99
|
-
collect(:add_bluebook, Bluebook::DSL::BluebookBuilder.build(name, version: version, &
|
|
102
|
+
def bluebook(name, version: nil, &)
|
|
103
|
+
collect(:add_bluebook, Bluebook::DSL::BluebookBuilder.build(name, version: version, &))
|
|
100
104
|
end
|
|
101
105
|
|
|
102
|
-
def hecksagon(name, &
|
|
106
|
+
def hecksagon(name, &) = collect(:add_hecksagon, Bluebook::DSL::HecksagonBuilder.build(name, &))
|
|
103
107
|
# REPOINTED TO DomainPortBuilder — the migration DomainPort's own
|
|
104
108
|
# class comment names as its goal, now landed for the top-level
|
|
105
109
|
# `.port` file callers too (the aggregate-scoped `Thing.port(...)`,
|
|
@@ -120,12 +124,12 @@ module Hecks
|
|
|
120
124
|
# (`BindingProxy#port`, `HecksagonBuilder#port_impl`) — only this,
|
|
121
125
|
# the literal top-level `.port` file entry point, keeps the older,
|
|
122
126
|
# looser rule (see `DomainPortBuilder#initialize`'s own comment).
|
|
123
|
-
def port(name, &
|
|
124
|
-
def adapter(name, &
|
|
125
|
-
def world(name, &
|
|
127
|
+
def port(name, &) = collect(:add_port, Bluebook::DSL::DomainPortBuilder.build(name, legacy_bare_port: true, &))
|
|
128
|
+
def adapter(name, &) = collect(:add_adapter, Bluebook::DSL::AdapterBuilder.build(name, &))
|
|
129
|
+
def world(name, &) = collect(:add_world, Bluebook::DSL::WorldBuilder.build(name, &))
|
|
126
130
|
|
|
127
|
-
def data_translation(name, from:, to:, &
|
|
128
|
-
collect(:add_translation, Bluebook::DSL::TranslationBuilder.build(name, from: from, to: to, &
|
|
131
|
+
def data_translation(name, from:, to:, &)
|
|
132
|
+
collect(:add_translation, Bluebook::DSL::TranslationBuilder.build(name, from: from, to: to, &))
|
|
129
133
|
end
|
|
130
134
|
|
|
131
135
|
private
|
|
@@ -53,7 +53,7 @@ module RuboCop
|
|
|
53
53
|
"silently discarded in favor of `%<receiver>s[%<rhs_key>s]` instead of being " \
|
|
54
54
|
"returned. Use `%<receiver>s.key?(%<lhs_key>s) ? %<receiver>s[%<lhs_key>s] : " \
|
|
55
55
|
"%<receiver>s[%<rhs_key>s]`, or a shared digger (see `key?` in " \
|
|
56
|
-
"`Hecks::QuerySpecification::FieldPath#read`), instead."
|
|
56
|
+
"`Hecks::QuerySpecification::FieldPath#read`), instead.".freeze
|
|
57
57
|
|
|
58
58
|
# @!method bracket_lookup(node)
|
|
59
59
|
def_node_matcher :bracket_lookup, "(send $_receiver :[] $_key)"
|
|
@@ -60,7 +60,7 @@ module RuboCop
|
|
|
60
60
|
"because the first rule's write clobbers the second rule's read target before it runs (the exact " \
|
|
61
61
|
"bug fixed for Lineage#apply_renames). Snapshot every old key's value FIRST, delete all old keys, " \
|
|
62
62
|
"then write all new keys, so the pass applies as one simultaneous permutation instead of a " \
|
|
63
|
-
"sequence of edits each stepping on the last."
|
|
63
|
+
"sequence of edits each stepping on the last.".freeze
|
|
64
64
|
|
|
65
65
|
RESTRICT_ON_SEND = [:[]=].freeze
|
|
66
66
|
|
|
@@ -61,7 +61,7 @@ module RuboCop
|
|
|
61
61
|
"other's view of it, the exact bug already fixed for `Dispatcher#reaction_depth` " \
|
|
62
62
|
"(see dispatcher.rb's `#reenter`). Use `Thread.current[:...]` for per-thread " \
|
|
63
63
|
"state, or a `Mutex`-guarded critical section (`Registry#saga_mutex`) if the " \
|
|
64
|
-
"state genuinely must be shared."
|
|
64
|
+
"state genuinely must be shared.".freeze
|
|
65
65
|
|
|
66
66
|
THREAD_SHARED_CLASSES = ["Dispatcher", "Registry"].freeze
|
|
67
67
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hecks
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chris Young
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-09-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: prism
|
|
@@ -58,6 +58,8 @@ files:
|
|
|
58
58
|
- lib/hecks/adapters/driven/lambda.adapter
|
|
59
59
|
- lib/hecks/adapters/driven/lambda.rb
|
|
60
60
|
- lib/hecks/adapters/driven/lambda/client.rb
|
|
61
|
+
- lib/hecks/adapters/driven/local_storage.adapter
|
|
62
|
+
- lib/hecks/adapters/driven/local_storage.rb
|
|
61
63
|
- lib/hecks/adapters/driven/memory.adapter
|
|
62
64
|
- lib/hecks/adapters/driven/memory.rb
|
|
63
65
|
- lib/hecks/adapters/driven/mock_stripe_adapter.adapter
|
|
@@ -65,6 +67,7 @@ files:
|
|
|
65
67
|
- lib/hecks/adapters/driven/postgres.adapter
|
|
66
68
|
- lib/hecks/adapters/driven/postgres.rb
|
|
67
69
|
- lib/hecks/adapters/driven/postgres/codec.rb
|
|
70
|
+
- lib/hecks/adapters/driven/postgres/outbox.rb
|
|
68
71
|
- lib/hecks/adapters/driven/postgres/schema_builder.rb
|
|
69
72
|
- lib/hecks/adapters/driven/postgres_era.adapter
|
|
70
73
|
- lib/hecks/adapters/driven/prism.adapter
|
|
@@ -115,9 +118,11 @@ files:
|
|
|
115
118
|
- lib/hecks/bluebook/dsl.rb
|
|
116
119
|
- lib/hecks/bluebook/dsl/adapter_builder.rb
|
|
117
120
|
- lib/hecks/bluebook/dsl/aggregate_builder.rb
|
|
121
|
+
- lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb
|
|
118
122
|
- lib/hecks/bluebook/dsl/attribute_collector.rb
|
|
119
123
|
- lib/hecks/bluebook/dsl/binding_proxy.rb
|
|
120
124
|
- lib/hecks/bluebook/dsl/bluebook_builder.rb
|
|
125
|
+
- lib/hecks/bluebook/dsl/bluebook_builder/validation.rb
|
|
121
126
|
- lib/hecks/bluebook/dsl/command_builder.rb
|
|
122
127
|
- lib/hecks/bluebook/dsl/const_shim.rb
|
|
123
128
|
- lib/hecks/bluebook/dsl/domain_port_builder.rb
|
|
@@ -141,6 +146,7 @@ files:
|
|
|
141
146
|
- lib/hecks/bluebook/entity.rb
|
|
142
147
|
- lib/hecks/bluebook/expression.rb
|
|
143
148
|
- lib/hecks/bluebook/expression/ast_json.rb
|
|
149
|
+
- lib/hecks/bluebook/expression/ast_reader.rb
|
|
144
150
|
- lib/hecks/bluebook/expression/canonical_form.rb
|
|
145
151
|
- lib/hecks/bluebook/expression/evaluator.rb
|
|
146
152
|
- lib/hecks/bluebook/expression/projection.json
|
|
@@ -219,6 +225,11 @@ files:
|
|
|
219
225
|
- lib/hecks/fuzzing/invalid_value_generator.rb
|
|
220
226
|
- lib/hecks/fuzzing/isolated_boot.rb
|
|
221
227
|
- lib/hecks/fuzzing/properties.rb
|
|
228
|
+
- lib/hecks/fuzzing/properties/dispatch_and_mutations.rb
|
|
229
|
+
- lib/hecks/fuzzing/properties/guards.rb
|
|
230
|
+
- lib/hecks/fuzzing/properties/invariants_and_aggregation.rb
|
|
231
|
+
- lib/hecks/fuzzing/properties/lifecycle_and_replay.rb
|
|
232
|
+
- lib/hecks/fuzzing/properties/querying.rb
|
|
222
233
|
- lib/hecks/fuzzing/replay.rb
|
|
223
234
|
- lib/hecks/fuzzing/sequence_generator.rb
|
|
224
235
|
- lib/hecks/fuzzing/sequence_generator/catalog.rb
|
|
@@ -394,6 +405,7 @@ files:
|
|
|
394
405
|
- lib/hecks/runtime/instance.rb
|
|
395
406
|
- lib/hecks/runtime/interpreting.rb
|
|
396
407
|
- lib/hecks/runtime/loader.rb
|
|
408
|
+
- lib/hecks/runtime/outbox.rb
|
|
397
409
|
- lib/hecks/runtime/policy_interpreter.rb
|
|
398
410
|
- lib/hecks/runtime/port_operation_interpreter.rb
|
|
399
411
|
- lib/hecks/runtime/query_interpreter.rb
|