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.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +3 -3
  3. data/lib/hecks/cli/check_era.rb +3 -39
  4. data/lib/hecks/doors/mcp_door.rb +16 -2
  5. data/lib/hecks/doors/mcp_door_scope.rb +43 -4
  6. data/lib/hecks/doors/mcp_guide_cache.rb +75 -0
  7. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +17 -1
  8. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +10 -2
  9. data/lib/hecks/hecks/adapters/rust_workspace.rb +1 -1
  10. data/lib/hecks/hecks/adapters/site_toolchain.rb +21 -11
  11. data/lib/hecks/language/bluebook/vocabulary.bluebook +7 -7
  12. data/lib/hecks/language/hecksagon/hecksagon.bluebook +1 -1
  13. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -3
  14. data/lib/hecks/projections/deploy/box/settings.rb +23 -2
  15. data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +2 -0
  16. data/lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl +17 -0
  17. data/lib/hecks/projections/deploy/box.rb +13 -2
  18. data/lib/hecks/projections/site/edge/behaviours.rb +5 -3
  19. data/lib/hecks/projections/site/edge/checks.rb +31 -5
  20. data/lib/hecks/projections/site/edge.rb +25 -11
  21. data/lib/hecks/projections/site/routes_ts.rb +50 -27
  22. data/lib/hecks/projections/site/site_cdn.rb +7 -4
  23. data/lib/hecks/projections/site/table/checks.rb +53 -7
  24. data/lib/hecks/projections/site/table/link_builder.rb +64 -0
  25. data/lib/hecks/projections/site/table/row_builder.rb +21 -6
  26. data/lib/hecks/projections/site/table.rb +16 -5
  27. data/lib/hecks/rust_build/kernel_input.rb +18 -6
  28. data/lib/hecks/rust_build/project_rust.rb +6 -132
  29. data/lib/hecks/site/bluebook/site.bluebook +27 -13
  30. data/lib/hecks/storehouse.rb +35 -1
  31. data/lib/hecks/tools/comment_style.rb +4 -2
  32. data/lib/hecks/tools/rust_comment_style.rb +1 -1
  33. data/lib/hecks/tools/site_routes.rb +69 -28
  34. data/lib/hecks/version.rb +1 -1
  35. data/lib/hecks/vocabulary.rb +1 -1
  36. data/lib/hecks.rb +1 -0
  37. data/rust/build/src/lineage_pass.rs +1 -1
  38. data/rust/build/src/pipeline.rs +1 -1
  39. data/rust/codegen/src/attr.rs +1 -1
  40. data/rust/codegen/src/bridging.rs +1 -1
  41. data/rust/codegen/src/commands.rs +1 -1
  42. data/rust/codegen/src/constraints.rs +1 -1
  43. data/rust/codegen/src/dependency_planning.rs +1 -1
  44. data/rust/codegen/src/domain_generator.rs +2 -2
  45. data/rust/codegen/src/exemplar.rs +1 -1
  46. data/rust/codegen/src/expr_emitter.rs +1 -1
  47. data/rust/codegen/src/fielded.rs +1 -1
  48. data/rust/codegen/src/json_codec.rs +1 -1
  49. data/rust/codegen/src/main.rs +3 -4
  50. data/rust/codegen/src/mutations.rs +1 -1
  51. data/rust/codegen/src/naming.rs +1 -1
  52. data/rust/codegen/src/ports.rs +1 -1
  53. data/rust/codegen/src/queries.rs +3 -3
  54. data/rust/codegen/src/reactions.rs +1 -1
  55. data/rust/codegen/src/read_models.rs +2 -2
  56. data/rust/codegen/src/reference_specs.rs +1 -1
  57. data/rust/codegen/src/registry.rs +4 -4
  58. data/rust/codegen/src/shared.rs +1 -1
  59. data/rust/codegen/src/skip_reason.rs +1 -1
  60. data/rust/codegen/src/types.rs +4 -4
  61. data/rust/host/HECKS_RELEASE +1 -1
  62. data/rust/host/src/needs.rs +94 -27
  63. data/rust/src/exemplar/commands.rs +1 -1
  64. data/rust/src/exemplar/constraints.rs +1 -1
  65. data/rust/src/exemplar/fielded.rs +1 -1
  66. data/rust/src/exemplar/json.rs +1 -1
  67. data/rust/src/exemplar/mod.rs +1 -1
  68. data/rust/src/exemplar/mutations.rs +1 -1
  69. data/rust/src/exemplar/queries.rs +1 -1
  70. data/rust/src/exemplar/reactions.rs +1 -1
  71. data/rust/src/exemplar/read_models.rs +1 -1
  72. data/rust/src/exemplar/registry.rs +1 -1
  73. data/rust/src/exemplar/types.rs +1 -1
  74. data/rust/src/kernel/cli.rs +2 -2
  75. data/rust/src/kernel/read_model.rs +1 -1
  76. data/rust/src/lib.rs +1 -1
  77. metadata +3 -23
  78. data/rust/project/bridging.rb +0 -257
  79. data/rust/project/commands.rb +0 -877
  80. data/rust/project/constraints.rb +0 -86
  81. data/rust/project/dependency_planning.rb +0 -178
  82. data/rust/project/domain_generator.rb +0 -983
  83. data/rust/project/exemplar.rb +0 -148
  84. data/rust/project/expr_emitter.rb +0 -72
  85. data/rust/project/fielded.rb +0 -141
  86. data/rust/project/json_codec.rb +0 -641
  87. data/rust/project/mutations.rb +0 -695
  88. data/rust/project/naming.rb +0 -223
  89. data/rust/project/ports.rb +0 -65
  90. data/rust/project/queries.rb +0 -493
  91. data/rust/project/reactions.rb +0 -317
  92. data/rust/project/read_models.rb +0 -690
  93. data/rust/project/reference_specs.rb +0 -27
  94. data/rust/project/registry.rb +0 -396
  95. data/rust/project/skip_reason.rb +0 -29
  96. data/rust/project/types.rb +0 -225
  97. data/rust/project/write_if_changed.rb +0 -7
  98. data/rust/project.rb +0 -30
  99. data/rust/project_rust_pipeline.rb +0 -391
@@ -1,257 +0,0 @@
1
- module RustProjection
2
- module Projector
3
- module_function
4
-
5
- # `use` statements for value-object types this aggregate's commands
6
- # reference by name but don't declare locally (same chapter only).
7
- def cross_aggregate_vo_imports(aggregate, domain_value_object_owner, mod_name)
8
- local_names = aggregate[:value_objects].map { |vo| vo[:name] }.to_set
9
- attrs = (aggregate[:commands] + aggregate[:entities].flat_map { |e| e[:commands] } +
10
- aggregate[:ports].flat_map { |p| p[:operations] })
11
- .flat_map { |c| c[:attributes] }
12
-
13
- # A locally-declared type is never "foreign", even if some other
14
- # aggregate owns a VO of the same name domain-wide.
15
- foreign_types = attrs.map { |a| a[:type] }.uniq.reject { |type| local_names.include?(type) }
16
- foreign_types.filter_map { |type| domain_value_object_owner[type] && [type, domain_value_object_owner[type]] }
17
- .sort_by { |(type, owner)| [owner, type] }
18
- .map { |(type, owner)| "use crate::generated::#{mod_name}::#{owner.downcase}::#{rust_ident(type)};" }
19
- end
20
-
21
- # Whether target_vo's fields can all be filled from source_vo's fields
22
- # (matched by name) or the target's own defaults.
23
- def vo_field_bridgeable?(source_vo, target_vo)
24
- return false unless source_vo && target_vo
25
- return closed_sets_bridgeable?(source_vo, target_vo) if source_vo[:closed_set] || target_vo[:closed_set]
26
-
27
- target_vo[:attributes].all? do |t_attr|
28
- source_vo[:attributes].any? { |s_attr| s_attr[:name] == t_attr[:name] } || !t_attr[:default].nil?
29
- end
30
- end
31
-
32
- # Source closed-set members mappable to target members, or nil unless
33
- # every source member is also a target member (so the bridge can't fail).
34
- def closed_set_bridge_members(source_vo, target_vo)
35
- return nil unless source_vo[:closed_set] && target_vo[:closed_set]
36
- return nil unless source_vo[:attributes].size == 1 && target_vo[:attributes].size == 1
37
-
38
- target_values = target_vo[:members].map { |row| row.to_h.values.first.to_s }
39
- source_rows = source_vo[:members]
40
- return nil unless source_rows.all? { |row| target_values.include?(row.to_h.values.first.to_s) }
41
-
42
- source_rows
43
- end
44
-
45
- def closed_sets_bridgeable?(source_vo, target_vo)
46
- !closed_set_bridge_members(source_vo, target_vo).nil?
47
- end
48
-
49
- def vo_field_rhs(source_expr, source_vo, target_type, value_objects_by_name)
50
- target_vo = value_objects_by_name[target_type]
51
- if (rows = closed_set_bridge_members(source_vo, target_vo))
52
- arms = rows.map { |row| "#{rust_ident(source_vo[:name])}::#{closed_set_variant(row)} => #{rust_ident(target_type)}::#{closed_set_variant(row)}" }
53
- return "match &#{source_expr} { #{arms.join(', ')} }"
54
- end
55
-
56
- fields = target_vo[:attributes].map do |t_attr|
57
- field = rust_ident_field(t_attr[:name])
58
- if source_vo[:attributes].any? { |s_attr| s_attr[:name] == t_attr[:name] }
59
- "#{field}: #{source_expr}.#{field}.clone()"
60
- else
61
- "#{field}: #{literal_rhs(t_attr[:default])}"
62
- end
63
- end
64
- "#{rust_ident(target_type)} { #{fields.join(', ')} }"
65
- end
66
-
67
- # Whether a value can move from source_type to target_type. Shared
68
- # with `value_rhs`, which performs the bridge this checks for.
69
- def bridgeable_value_types?(source_type, target_type, value_objects_by_name)
70
- return true if source_type == target_type
71
- # Compares Rust representations, not declared type names: a
72
- # `Reference<T>` and a bare `String` are both just `String`.
73
- return true if effective_scalar_type(source_type) && effective_scalar_type(source_type) == effective_scalar_type(target_type)
74
-
75
- source_vo = value_objects_by_name[source_type]
76
- target_vo = value_objects_by_name[target_type]
77
- return vo_field_bridgeable?(source_vo, target_vo) if target_vo
78
-
79
- # A single-field source VO unwraps into a bare scalar target when
80
- # that one field's own Rust representation already matches it.
81
- return false unless source_vo && !source_vo[:closed_set] && source_vo[:attributes].size == 1
82
-
83
- unwrapped_type = source_vo[:attributes].first[:type]
84
- unwrapped_type == target_type ||
85
- (effective_scalar_type(unwrapped_type) && effective_scalar_type(unwrapped_type) == effective_scalar_type(target_type))
86
- end
87
-
88
- def value_rhs(source_expr, source_type, target_type, value_objects_by_name)
89
- return "#{source_expr}.clone()" if source_type == target_type
90
- return "#{source_expr}.clone()" if effective_scalar_type(source_type) && effective_scalar_type(source_type) == effective_scalar_type(target_type)
91
-
92
- source_vo = value_objects_by_name[source_type]
93
- target_vo = value_objects_by_name[target_type]
94
- return vo_field_rhs(source_expr, source_vo, target_type, value_objects_by_name) if target_vo
95
-
96
- raise "unsupported coercion #{source_type} -> #{target_type} — bridgeable_value_types? should have caught this" unless source_vo && !source_vo[:closed_set] && source_vo[:attributes].size == 1
97
-
98
- "#{source_expr}.#{rust_ident_field(source_vo[:attributes].first[:name])}.clone()"
99
- end
100
-
101
- # Whether a list `:set` needs per-element rebuilding, rather than one
102
- # `.clone()`, because source and target elements differ in representation.
103
- def list_bridge_requires_element_mapping?(source_type, target_type)
104
- return false if source_type == target_type
105
-
106
- !(effective_scalar_type(source_type) && effective_scalar_type(source_type) == effective_scalar_type(target_type))
107
- end
108
-
109
- # `value_rhs`, mapped over each element of a list `:set` whose source
110
- # and target element types differ.
111
- def list_value_rhs(source_expr, source_type, target_type, value_objects_by_name)
112
- "#{source_expr}.iter().map(|item| #{value_rhs('item', source_type, target_type, value_objects_by_name)}).collect()"
113
- end
114
-
115
- def literal_set_bridgeable?(value, target_type, value_objects_by_name)
116
- return literal_hash_bridgeable?(value, target_type, value_objects_by_name) if value.is_a?(Hash) && target_type
117
- return false if value.is_a?(Hash)
118
- return false unless value.is_a?(String) || value.is_a?(Numeric) || value == true || value == false
119
- return true unless target_type && value_objects_by_name[target_type]
120
-
121
- # A scalar literal into a single-field VO stands for that field's
122
- # value (mirrors `Value.for_attribute`'s runtime rewrap).
123
- sole = sole_field_of(target_type, value_objects_by_name)
124
- return false unless sole
125
-
126
- literal_hash_bridgeable?({ sole => value }, target_type, value_objects_by_name)
127
- end
128
-
129
- # The right-hand side for a literal into target_type: a Hash, a
130
- # scalar rewrapped into a single-field VO, or the scalar itself.
131
- def literal_rhs_for(value, target_type, value_objects_by_name)
132
- return literal_hash_rhs(value, target_type, value_objects_by_name) if value.is_a?(Hash)
133
-
134
- sole = target_type && value_objects_by_name[target_type] && sole_field_of(target_type, value_objects_by_name)
135
- return literal_hash_rhs({ sole => value }, target_type, value_objects_by_name) if sole
136
-
137
- literal_rhs(value)
138
- end
139
-
140
- # Whether a literal Hash supplies every field a target VO (or one
141
- # of its closed-set members) needs.
142
- def literal_hash_bridgeable?(hash, target_type, value_objects_by_name)
143
- vo = value_objects_by_name[target_type]
144
- return false unless vo
145
-
146
- if vo[:closed_set]
147
- vo[:members].any? { |member| member.all? { |field, value| [hash[field.to_sym], hash[field.to_s]].include?(value) } }
148
- else
149
- vo[:attributes].all? { |attr| hash.key?(attr[:name].to_sym) || hash.key?(attr[:name].to_s) }
150
- end
151
- end
152
-
153
- def literal_hash_rhs(hash, target_type, value_objects_by_name)
154
- vo = value_objects_by_name[target_type]
155
- raise "unsupported literal hash source #{hash.inspect} -> #{target_type} — literal_hash_bridgeable? should have caught this" unless vo
156
-
157
- if vo[:closed_set]
158
- row = vo[:members].find { |member| member.all? { |field, value| [hash[field.to_sym], hash[field.to_s]].include?(value) } }
159
- raise "literal #{hash.inspect} matches no member of #{target_type} — literal_hash_bridgeable? should have caught this" unless row
160
-
161
- "#{rust_ident(target_type)}::#{closed_set_variant(row)}"
162
- else
163
- fields = vo[:attributes].map do |attr|
164
- key = [attr[:name].to_sym, attr[:name].to_s].find { |k| hash.key?(k) }
165
- raise "literal #{hash.inspect} missing field #{attr[:name]} for #{target_type} — literal_hash_bridgeable? should have caught this" unless key
166
-
167
- "#{rust_ident_field(attr[:name])}: #{literal_rhs(hash[key])}"
168
- end
169
- "#{rust_ident(target_type)} { #{fields.join(', ')} }"
170
- end
171
- end
172
-
173
- def integer_field_of(vo)
174
- return nil unless vo && !vo[:closed_set]
175
-
176
- attr = vo[:attributes].find { |a| a[:type] == "Integer" }
177
- attr && attr[:name]
178
- end
179
-
180
- # The attribute and its one Integer field an `:increment`/`:decrement`
181
- # touches, or nil unless the target is a single-field integer VO.
182
- def arithmetic_target_field(mutation, aggregate, value_objects_by_name)
183
- target_attr = aggregate[:attributes].find { |a| a[:name].to_s == mutation[:target].to_s }
184
- return nil unless target_attr && !target_attr[:list]
185
-
186
- field = integer_field_of(value_objects_by_name[target_attr[:type]])
187
- field && [target_attr, field]
188
- end
189
-
190
- # The amount side of an `:increment`/`:decrement` as a raw Rust
191
- # integer expression. Returns nil, not raise, when nothing bridges.
192
- def arithmetic_amount_expr(source, command, value_objects_by_name, target_integer_field)
193
- if source[:kind] == "literal"
194
- value = source[:value]
195
- return literal_rhs(value) if value.is_a?(Integer)
196
- return nil unless value.is_a?(Hash)
197
-
198
- key = [target_integer_field.to_sym, target_integer_field.to_s].find { |k| value.key?(k) }
199
- key && value[key].is_a?(Integer) ? literal_rhs(value[key]) : nil
200
- elsif source[:kind] == "argument"
201
- arg_attr = command[:attributes].find { |a| a[:name].to_s == source[:name] }
202
- return nil unless arg_attr
203
- return "args.#{rust_ident_field(arg_attr[:name])}" if arg_attr[:type] == "Integer"
204
-
205
- field = integer_field_of(value_objects_by_name[arg_attr[:type]])
206
- field && "args.#{rust_ident_field(arg_attr[:name])}.#{rust_ident_field(field)}"
207
- end
208
- end
209
-
210
- # `clamp:`'s literal `[min, max]` bounds, or nil for anything else
211
- # (non-literal source, wrong length, non-Integer bound).
212
- def clamp_bounds_ints(source)
213
- return nil unless source[:kind] == "literal"
214
-
215
- value = source[:value]
216
- return nil unless value.is_a?(Array) && value.size == 2 && value.all? { |v| v.is_a?(Integer) }
217
-
218
- value
219
- end
220
-
221
- # The default for an attribute a creating command's arguments don't
222
- # mention: its own default, or one built from its VO's field defaults.
223
- def creation_default_rhs(attr, value_objects_by_name)
224
- default = attr[:default]
225
- unless default.nil?
226
- return literal_rhs(default) unless default.is_a?(Hash)
227
-
228
- # Completes a partial Hash default with the target VO's own
229
- # per-field defaults first; `literal_hash_rhs` requires every
230
- # field present.
231
- return literal_hash_rhs(complete_hash_default(default, attr[:type], value_objects_by_name), attr[:type], value_objects_by_name)
232
- end
233
-
234
- vo = value_objects_by_name[attr[:type]]
235
- return nil unless vo && !vo[:closed_set] && vo[:attributes].all? { |f| !f[:default].nil? }
236
-
237
- fields = vo[:attributes].map { |f| "#{rust_ident_field(f[:name])}: #{literal_rhs(f[:default])}" }
238
- "#{rust_ident(attr[:type])} { #{fields.join(', ')} }"
239
- end
240
-
241
- # Fields present in `hash`, plus any the target VO defaults on its
242
- # own; a field with neither is left out, not set to nil.
243
- def complete_hash_default(hash, target_type, value_objects_by_name)
244
- vo = value_objects_by_name[target_type]
245
- return hash unless vo && !vo[:closed_set]
246
-
247
- vo[:attributes].each_with_object({}) do |field, completed|
248
- key = [field[:name].to_sym, field[:name].to_s].find { |k| hash.key?(k) }
249
- if key
250
- completed[field[:name].to_sym] = hash[key]
251
- elsif !field[:default].nil?
252
- completed[field[:name].to_sym] = field[:default]
253
- end
254
- end
255
- end
256
- end
257
- end