hecks 3.2.0 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +3 -3
- data/lib/hecks/cli/check_era.rb +3 -39
- data/lib/hecks/doors/mcp_door.rb +16 -2
- data/lib/hecks/doors/mcp_door_scope.rb +43 -4
- data/lib/hecks/doors/mcp_guide_cache.rb +75 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +17 -1
- data/lib/hecks/hecks/adapters/codebase/test_runner.rb +10 -2
- data/lib/hecks/hecks/adapters/rust_workspace.rb +1 -1
- data/lib/hecks/hecks/adapters/site_toolchain.rb +21 -11
- data/lib/hecks/language/bluebook/vocabulary.bluebook +7 -7
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +1 -1
- data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -3
- data/lib/hecks/projections/deploy/box/settings.rb +23 -2
- data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +2 -0
- data/lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl +17 -0
- data/lib/hecks/projections/deploy/box.rb +13 -2
- data/lib/hecks/projections/site/edge/behaviours.rb +5 -3
- data/lib/hecks/projections/site/edge/checks.rb +31 -5
- data/lib/hecks/projections/site/edge.rb +25 -11
- data/lib/hecks/projections/site/routes_ts.rb +50 -27
- data/lib/hecks/projections/site/site_cdn.rb +7 -4
- data/lib/hecks/projections/site/table/checks.rb +53 -7
- data/lib/hecks/projections/site/table/link_builder.rb +64 -0
- data/lib/hecks/projections/site/table/row_builder.rb +21 -6
- data/lib/hecks/projections/site/table.rb +16 -5
- data/lib/hecks/rust_build/kernel_input.rb +18 -6
- data/lib/hecks/rust_build/project_rust.rb +6 -132
- data/lib/hecks/site/bluebook/site.bluebook +27 -13
- data/lib/hecks/storehouse.rb +35 -1
- data/lib/hecks/tools/comment_style.rb +4 -2
- data/lib/hecks/tools/rust_comment_style.rb +1 -1
- data/lib/hecks/tools/site_routes.rb +69 -28
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +1 -1
- data/lib/hecks.rb +1 -0
- data/rust/build/src/lineage_pass.rs +1 -1
- data/rust/build/src/pipeline.rs +1 -1
- data/rust/codegen/src/attr.rs +1 -1
- data/rust/codegen/src/bridging.rs +1 -1
- data/rust/codegen/src/commands.rs +1 -1
- data/rust/codegen/src/constraints.rs +1 -1
- data/rust/codegen/src/dependency_planning.rs +1 -1
- data/rust/codegen/src/domain_generator.rs +2 -2
- data/rust/codegen/src/exemplar.rs +1 -1
- data/rust/codegen/src/expr_emitter.rs +1 -1
- data/rust/codegen/src/fielded.rs +1 -1
- data/rust/codegen/src/json_codec.rs +1 -1
- data/rust/codegen/src/main.rs +3 -4
- data/rust/codegen/src/mutations.rs +1 -1
- data/rust/codegen/src/naming.rs +1 -1
- data/rust/codegen/src/ports.rs +1 -1
- data/rust/codegen/src/queries.rs +3 -3
- data/rust/codegen/src/reactions.rs +1 -1
- data/rust/codegen/src/read_models.rs +2 -2
- data/rust/codegen/src/reference_specs.rs +1 -1
- data/rust/codegen/src/registry.rs +4 -4
- data/rust/codegen/src/shared.rs +1 -1
- data/rust/codegen/src/skip_reason.rs +1 -1
- data/rust/codegen/src/types.rs +4 -4
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/src/needs.rs +94 -27
- data/rust/src/exemplar/commands.rs +1 -1
- data/rust/src/exemplar/constraints.rs +1 -1
- data/rust/src/exemplar/fielded.rs +1 -1
- data/rust/src/exemplar/json.rs +1 -1
- data/rust/src/exemplar/mod.rs +1 -1
- data/rust/src/exemplar/mutations.rs +1 -1
- data/rust/src/exemplar/queries.rs +1 -1
- data/rust/src/exemplar/reactions.rs +1 -1
- data/rust/src/exemplar/read_models.rs +1 -1
- data/rust/src/exemplar/registry.rs +1 -1
- data/rust/src/exemplar/types.rs +1 -1
- data/rust/src/kernel/cli.rs +2 -2
- data/rust/src/kernel/read_model.rs +1 -1
- data/rust/src/lib.rs +1 -1
- metadata +3 -23
- data/rust/project/bridging.rb +0 -257
- data/rust/project/commands.rb +0 -877
- data/rust/project/constraints.rb +0 -86
- data/rust/project/dependency_planning.rb +0 -178
- data/rust/project/domain_generator.rb +0 -983
- data/rust/project/exemplar.rb +0 -148
- data/rust/project/expr_emitter.rb +0 -72
- data/rust/project/fielded.rb +0 -141
- data/rust/project/json_codec.rb +0 -641
- data/rust/project/mutations.rb +0 -695
- data/rust/project/naming.rb +0 -223
- data/rust/project/ports.rb +0 -65
- data/rust/project/queries.rb +0 -493
- data/rust/project/reactions.rb +0 -317
- data/rust/project/read_models.rb +0 -690
- data/rust/project/reference_specs.rb +0 -27
- data/rust/project/registry.rb +0 -396
- data/rust/project/skip_reason.rb +0 -29
- data/rust/project/types.rb +0 -225
- data/rust/project/write_if_changed.rb +0 -7
- data/rust/project.rb +0 -30
- data/rust/project_rust_pipeline.rb +0 -391
data/rust/project/constraints.rb
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
module RustProjection
|
|
2
|
-
module Projector
|
|
3
|
-
module_function
|
|
4
|
-
|
|
5
|
-
# Scalar that `admits:`/`pattern:` checks operate on: the value itself for a `String`, or
|
|
6
|
-
# the sole field of a single-field value object. `nil` for any other shape.
|
|
7
|
-
def scalar_field_expr(value_expr, attr_type, value_objects_by_name)
|
|
8
|
-
return value_expr if attr_type == "String"
|
|
9
|
-
|
|
10
|
-
vo = value_objects_by_name[attr_type]
|
|
11
|
-
return nil unless vo && vo[:attributes].size == 1
|
|
12
|
-
|
|
13
|
-
"#{value_expr}.#{rust_ident_field(vo[:attributes].first[:name])}"
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
# An optional attribute is `Option<T>`, so the check must sit inside an `if let Some(..)`.
|
|
17
|
-
# Returns `[scalar_expr, nil]` when required, `[expr_over_binding, option_source]` when not.
|
|
18
|
-
def optional_scalar_expr(value_expr, attr, value_objects_by_name)
|
|
19
|
-
return [scalar_field_expr(value_expr, attr[:type], value_objects_by_name), nil] unless attr[:optional]
|
|
20
|
-
|
|
21
|
-
[scalar_field_expr(OPTIONAL_BINDING, attr[:type], value_objects_by_name), value_expr]
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
# Binds by reference into the `Option`; a `None` field skips the check, as in Ruby.
|
|
25
|
-
OPTIONAL_BINDING = "__optional_value"
|
|
26
|
-
|
|
27
|
-
def wrap_if_optional(check, optional_source)
|
|
28
|
-
return check unless optional_source
|
|
29
|
-
|
|
30
|
-
"if let Some(#{OPTIONAL_BINDING}) = &#{optional_source} { #{check} }"
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
# Members of the closed set named by `"Aggregate::SetName"`, resolved against the full domain.
|
|
34
|
-
# `nil` when the target is not a closed-set value object: codegen skips the check.
|
|
35
|
-
def admitted_set_members(admits, aggregates_by_name)
|
|
36
|
-
aggregate_name, set_name = admits.to_s.split("::", 2)
|
|
37
|
-
aggregate = aggregates_by_name[aggregate_name]
|
|
38
|
-
return nil unless aggregate && set_name
|
|
39
|
-
|
|
40
|
-
vo = aggregate[:value_objects].find { |v| v[:name] == set_name }
|
|
41
|
-
return nil unless vo && vo[:closed_set]
|
|
42
|
-
|
|
43
|
-
vo[:members].map { |row| row.first[1] }
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# Emits the `admits_declared_set` check. The member list is resolved at codegen time and passed
|
|
47
|
-
# as a `&[&str]` literal; the refusal wording lives only in Vocabulary::RefusalTemplate.
|
|
48
|
-
def emit_admits_check(value_expr, attr, aggregates_by_name, value_objects_by_name)
|
|
49
|
-
return nil unless attr[:admits]
|
|
50
|
-
|
|
51
|
-
members = admitted_set_members(attr[:admits], aggregates_by_name)
|
|
52
|
-
return nil unless members
|
|
53
|
-
|
|
54
|
-
scalar, optional_source = optional_scalar_expr(value_expr, attr, value_objects_by_name)
|
|
55
|
-
return nil unless scalar
|
|
56
|
-
|
|
57
|
-
members_array = "[#{members.map(&:inspect).join(', ')}]"
|
|
58
|
-
check = Exemplar.render(
|
|
59
|
-
"admits_check",
|
|
60
|
-
'["tmpl_member_a", "tmpl_member_b"]' => members_array,
|
|
61
|
-
"tmpl_scalar" => scalar,
|
|
62
|
-
'"tmpl_admits_name"' => attr[:name].to_s.inspect,
|
|
63
|
-
'"tmpl_admits_target"' => attr[:admits].to_s.inspect
|
|
64
|
-
)
|
|
65
|
-
wrap_if_optional(check, optional_source)
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# Emits the `pattern_mismatch` check. The regex goes over as its raw source string, unescaped,
|
|
69
|
-
# matching Ruby's verbatim interpolation.
|
|
70
|
-
def emit_pattern_check(value_expr, attr, owner_type_name, value_objects_by_name)
|
|
71
|
-
return nil unless attr[:pattern]
|
|
72
|
-
|
|
73
|
-
scalar, optional_source = optional_scalar_expr(value_expr, attr, value_objects_by_name)
|
|
74
|
-
return nil unless scalar
|
|
75
|
-
|
|
76
|
-
check = Exemplar.render(
|
|
77
|
-
"pattern_check",
|
|
78
|
-
'"tmpl_pattern_text"' => attr[:pattern].inspect,
|
|
79
|
-
"tmpl_scalar" => scalar,
|
|
80
|
-
'"tmpl_pattern_owner"' => owner_type_name.to_s.inspect,
|
|
81
|
-
'"tmpl_pattern_field"' => attr[:name].to_s.inspect
|
|
82
|
-
)
|
|
83
|
-
wrap_if_optional(check, optional_source)
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
end
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
module RustProjection
|
|
2
|
-
module Projector
|
|
3
|
-
module_function
|
|
4
|
-
|
|
5
|
-
# Whether a creating command's state is fully determined by its payload and declared defaults.
|
|
6
|
-
# Port of `Runtime::DependencyPlanning::Analyzer`'s `complete_state?`, over the exported IR
|
|
7
|
-
# hash so codegen never needs live Bluebook objects or a call into Ruby.
|
|
8
|
-
# Only meaningful for a command `creates_owner?` already accepted.
|
|
9
|
-
def complete_state_creation?(aggregate, command, value_objects_by_name)
|
|
10
|
-
owner_fields = creation_owner_fields(aggregate)
|
|
11
|
-
payload_fields = command[:attributes].to_set { |a| a[:name].to_s }
|
|
12
|
-
|
|
13
|
-
known_writes, disqualified = creation_known_writes(aggregate, command, owner_fields, payload_fields, value_objects_by_name)
|
|
14
|
-
!disqualified && owner_fields.subset?(known_writes)
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
# Port of `complete_state? && state_independent?`: when true, the `AlreadyExists` check is
|
|
18
|
-
# deferred until after givens, mutations, ensures and invariants have run.
|
|
19
|
-
# Only meaningful for a command `creates_owner?` already accepted.
|
|
20
|
-
def state_independent_creation?(aggregate, command, value_objects_by_name)
|
|
21
|
-
return false unless complete_state_creation?(aggregate, command, value_objects_by_name)
|
|
22
|
-
|
|
23
|
-
owner_fields = creation_owner_fields(aggregate)
|
|
24
|
-
payload_fields = command[:attributes].to_set { |a| a[:name].to_s }
|
|
25
|
-
|
|
26
|
-
rules = command[:givens].map { |rule| [rule, :before] } +
|
|
27
|
-
command[:ensures].map { |rule| [rule, :after] } +
|
|
28
|
-
aggregate[:invariants].map { |rule| [rule, :after] }
|
|
29
|
-
|
|
30
|
-
rules.all? do |rule, phase|
|
|
31
|
-
creation_rule_state_independent?(rule[:ast], phase, payload_fields, owner_fields)
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# Every field a fresh instance carries: declared attributes, the lifecycle field, and projected
|
|
36
|
-
# fields (seeded outside this analysis, so they count as owner state).
|
|
37
|
-
def creation_owner_fields(aggregate)
|
|
38
|
-
fields = aggregate[:attributes].to_set { |a| a[:name].to_s }
|
|
39
|
-
fields << aggregate[:lifecycle][:field].to_s if aggregate[:lifecycle]
|
|
40
|
-
Array(aggregate[:projected_fields]).each { |field| fields << field[:name].to_s }
|
|
41
|
-
fields
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# Ports `analyze_initial_state`, `analyze_mutations` and `analyze_lifecycle`, in that order.
|
|
45
|
-
# Returns `[known_writes, disqualified]`; the Analyzer's `unresolved` folds into one boolean.
|
|
46
|
-
def creation_known_writes(aggregate, command, owner_fields, payload_fields, value_objects_by_name)
|
|
47
|
-
known = Set.new
|
|
48
|
-
aggregate[:attributes].each do |attr|
|
|
49
|
-
known << attr[:name].to_s if creation_deterministic_initial_value?(attr, value_objects_by_name)
|
|
50
|
-
end
|
|
51
|
-
known << aggregate[:lifecycle][:field].to_s if aggregate[:lifecycle]
|
|
52
|
-
|
|
53
|
-
# A creating command guarded by a lifecycle `from:` state reads prior state that is absent.
|
|
54
|
-
disqualified = command[:from] && aggregate[:lifecycle] ? true : false
|
|
55
|
-
|
|
56
|
-
command[:mutations].each do |mutation|
|
|
57
|
-
target = mutation[:target].to_s
|
|
58
|
-
unless owner_fields.include?(target)
|
|
59
|
-
disqualified = true
|
|
60
|
-
next
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
case creation_mutation_outcome(mutation, payload_fields, owner_fields)
|
|
64
|
-
when :known then known << target
|
|
65
|
-
when :unresolved then disqualified = true
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
[known, disqualified]
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# One mutation's outcome: `:known` (adds to known writes), `:unresolved` (disqualifies), or
|
|
73
|
-
# `:state_read` (neither; stateful ops simply never enter known writes).
|
|
74
|
-
def creation_mutation_outcome(mutation, payload_fields, owner_fields)
|
|
75
|
-
case mutation[:op].to_s
|
|
76
|
-
when "set"
|
|
77
|
-
creation_classify_source(mutation[:source], payload_fields, owner_fields)
|
|
78
|
-
when "append"
|
|
79
|
-
# Stateful, so never a known write; only an undeclared source name in its fields
|
|
80
|
-
# disqualifies.
|
|
81
|
-
creation_append_outcome(mutation[:fields], payload_fields, owner_fields)
|
|
82
|
-
when "increment", "decrement", "multiply", "clamp", "remove"
|
|
83
|
-
# Stateful like `append`; no creating command in the corpus declares one.
|
|
84
|
-
:state_read
|
|
85
|
-
else
|
|
86
|
-
:unresolved
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def creation_append_outcome(fields, payload_fields, owner_fields)
|
|
91
|
-
fields.each_value do |wire_value|
|
|
92
|
-
parsed = append_field_source(wire_value)
|
|
93
|
-
next unless parsed.is_a?(Symbol)
|
|
94
|
-
|
|
95
|
-
return :unresolved if creation_classify_symbol(parsed.to_s, payload_fields, owner_fields) == :unresolved
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
:state_read
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
# True for a list or optional attribute, or one with a non-nil default; otherwise true only
|
|
102
|
-
# when its type is a value object whose every attribute has a non-nil default.
|
|
103
|
-
def creation_deterministic_initial_value?(attr, value_objects_by_name)
|
|
104
|
-
return true if [attr[:list], attr[:optional], !attr[:default].nil?].any?
|
|
105
|
-
|
|
106
|
-
vo = value_objects_by_name[attr[:type]]
|
|
107
|
-
return false unless vo
|
|
108
|
-
|
|
109
|
-
vo[:attributes].all? { |field| !field[:default].nil? }
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
# Classifies a name as `:known` (payload argument), `:state_read` (owner field), or
|
|
113
|
-
# `:unresolved`.
|
|
114
|
-
def creation_classify_symbol(name, payload_fields, owner_fields)
|
|
115
|
-
return :known if payload_fields.include?(name)
|
|
116
|
-
return :state_read if owner_fields.include?(name)
|
|
117
|
-
|
|
118
|
-
:unresolved
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
# Classifies a mutation's top-level `source:` (`{kind:, name:/value:}`).
|
|
122
|
-
# A `"state"` source is `:known`, matching the live Analyzer, which has no StateRef branch;
|
|
123
|
-
# "fixing" it would make this port disagree with the Analyzer it mirrors.
|
|
124
|
-
def creation_classify_source(source, payload_fields, owner_fields)
|
|
125
|
-
return creation_classify_symbol(source[:name].to_s, payload_fields, owner_fields) if source[:kind] == "argument"
|
|
126
|
-
|
|
127
|
-
:known
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
# True when one rule's exported `ast` reads nothing that disqualifies state independence
|
|
131
|
-
# (an unresolved path or a prior-state read).
|
|
132
|
-
def creation_rule_state_independent?(ast, phase, payload_fields, owner_fields)
|
|
133
|
-
creation_paths(ast, Set.new).all? do |path|
|
|
134
|
-
creation_path_state_independent?(path, phase, payload_fields, owner_fields)
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
# Collects dotted lookup paths from the JSON `ast`, mirroring `ExpressionReads.collect`.
|
|
139
|
-
# Paths rooted at a name bound by a `block_predicate` param are skipped.
|
|
140
|
-
def creation_paths(node, bound_names)
|
|
141
|
-
case node
|
|
142
|
-
when ::Hash
|
|
143
|
-
case node["op"]
|
|
144
|
-
when "lookup"
|
|
145
|
-
root = node["path"].first.to_s
|
|
146
|
-
bound_names.include?(root) ? [] : [node["path"].join(".")]
|
|
147
|
-
when "block_predicate"
|
|
148
|
-
creation_paths(node["receiver"], bound_names) +
|
|
149
|
-
creation_paths(node["predicate"], bound_names | [node["param"].to_s])
|
|
150
|
-
else
|
|
151
|
-
node.each_value.flat_map { |value| creation_paths(value, bound_names) }
|
|
152
|
-
end
|
|
153
|
-
when ::Array
|
|
154
|
-
node.flat_map { |value| creation_paths(value, bound_names) }
|
|
155
|
-
else
|
|
156
|
-
[]
|
|
157
|
-
end
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
# Port of `classify_path`. A `parent`/`old` head always disqualifies. A payload read is fine.
|
|
161
|
-
# An owner-field read is fine only after mutation (`:after`): every owner field is a known
|
|
162
|
-
# write by then, so a given (`:before`) is the one disqualifying case.
|
|
163
|
-
# Unknown names disqualify.
|
|
164
|
-
def creation_path_state_independent?(path, phase, payload_fields, owner_fields)
|
|
165
|
-
head, = path.split(".", 2)
|
|
166
|
-
|
|
167
|
-
case head
|
|
168
|
-
when "parent", "old"
|
|
169
|
-
false
|
|
170
|
-
else
|
|
171
|
-
return true if payload_fields.include?(head)
|
|
172
|
-
return false unless owner_fields.include?(head)
|
|
173
|
-
|
|
174
|
-
phase != :before
|
|
175
|
-
end
|
|
176
|
-
end
|
|
177
|
-
end
|
|
178
|
-
end
|