hecks 3.2.0 → 3.3.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.
Files changed (187) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hecks/bluebook/assembly/contracts.rb +2 -1
  3. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
  4. data/lib/hecks/bluebook/dsl/command_builder.rb +2 -39
  5. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +3 -3
  6. data/lib/hecks/bluebook/dsl/need_word.rb +50 -0
  7. data/lib/hecks/bluebook/dsl/query_builder.rb +6 -1
  8. data/lib/hecks/bluebook/query.rb +8 -5
  9. data/lib/hecks/cli/check_era.rb +3 -39
  10. data/lib/hecks/cli/interview_draft.rb +86 -31
  11. data/lib/hecks/cli/interview_session.rb +77 -26
  12. data/lib/hecks/cli/interview_shape.rb +179 -0
  13. data/lib/hecks/doors/cli_runner.rb +14 -1
  14. data/lib/hecks/doors/launcher_options.rb +13 -3
  15. data/lib/hecks/doors/mcp_door.rb +16 -2
  16. data/lib/hecks/doors/mcp_door_scope.rb +45 -5
  17. data/lib/hecks/doors/mcp_guide_cache.rb +75 -0
  18. data/lib/hecks/doors.rb +1 -1
  19. data/lib/hecks/embryonaut_bluebook/manifest.rb +117 -0
  20. data/lib/hecks/embryonaut_bluebook/registry.rb +209 -0
  21. data/lib/hecks/embryonaut_bluebook/vendor.rb +1 -1
  22. data/lib/hecks/embryonaut_bluebook.rb +2 -0
  23. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +17 -1
  24. data/lib/hecks/fuzzing/qa_settings.rb +15 -34
  25. data/lib/hecks/gate/stages.yml +113 -8
  26. data/lib/hecks/hecks/adapters/codebase/regeneration.rb +10 -1
  27. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +10 -2
  28. data/lib/hecks/hecks/adapters/first_admin.rb +108 -0
  29. data/lib/hecks/hecks/adapters/git.rb +59 -0
  30. data/lib/hecks/hecks/adapters/in_process_access.rb +25 -0
  31. data/lib/hecks/hecks/adapters/in_process_boot.rb +2 -0
  32. data/lib/hecks/hecks/adapters/rust_toolchain.rb +18 -3
  33. data/lib/hecks/hecks/adapters/rust_workspace.rb +1 -1
  34. data/lib/hecks/hecks/adapters/site_toolchain.rb +88 -11
  35. data/lib/hecks/hecks/codebase.behaviors +6 -0
  36. data/lib/hecks/hecks/codebase.bluebook +21 -0
  37. data/lib/hecks/hecks/custodian.behaviors +6 -0
  38. data/lib/hecks/hecks/custodian.bluebook +217 -0
  39. data/lib/hecks/hecks/hecks.hecksagon +24 -0
  40. data/lib/hecks/hecks/hecks.world +8 -3
  41. data/lib/hecks/hecks/site.world +5 -2
  42. data/lib/hecks/language/bluebook/query.bluebook +25 -0
  43. data/lib/hecks/language/bluebook/vocabulary.bluebook +220 -7
  44. data/lib/hecks/language/hecksagon/hecksagon.bluebook +1 -1
  45. data/lib/hecks/language/oidc.json +5 -0
  46. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -3
  47. data/lib/hecks/projections/deploy/box/hosting.rb +170 -0
  48. data/lib/hecks/projections/deploy/box/hosting_settings.rb +123 -0
  49. data/lib/hecks/projections/deploy/box/settings.rb +40 -6
  50. data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +2 -0
  51. data/lib/hecks/projections/deploy/box/templates/deploy-service.services-roll.part +33 -0
  52. data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +79 -0
  53. data/lib/hecks/projections/deploy/box/templates/deploy-service.taskdef-roll.part +77 -0
  54. data/lib/hecks/projections/deploy/box/templates/deploy-service.taskdef.part +12 -0
  55. data/lib/hecks/projections/deploy/box/templates/hosting.mk.tmpl +24 -0
  56. data/lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl +17 -0
  57. data/lib/hecks/projections/deploy/box/templates/smoke-after-deploy.sh.tmpl +271 -0
  58. data/lib/hecks/projections/deploy/box.rb +28 -4
  59. data/lib/hecks/projections/deploy/fargate.rb +3 -3
  60. data/lib/hecks/projections/deploy/lambda.rb +8 -8
  61. data/lib/hecks/projections/model/deviations.rb +1 -1
  62. data/lib/hecks/projections/site/edge/behaviours.rb +5 -3
  63. data/lib/hecks/projections/site/edge/checks.rb +31 -5
  64. data/lib/hecks/projections/site/edge.rb +25 -11
  65. data/lib/hecks/projections/site/live_edge.rb +162 -0
  66. data/lib/hecks/projections/site/routes_ts.rb +50 -27
  67. data/lib/hecks/projections/site/site_cdn.rb +7 -4
  68. data/lib/hecks/projections/site/table/checks.rb +53 -7
  69. data/lib/hecks/projections/site/table/link_builder.rb +64 -0
  70. data/lib/hecks/projections/site/table/row_builder.rb +21 -6
  71. data/lib/hecks/projections/site/table.rb +16 -5
  72. data/lib/hecks/quality_control/adapters/git_pr.rb +0 -14
  73. data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +1 -1
  74. data/lib/hecks/quality_control/cli/qa_open_pr.rb +32 -16
  75. data/lib/hecks/quality_control/cli/qa_seed_angles.rb +2 -2
  76. data/lib/hecks/quality_control/cli/qa_seed_targets.rb +1 -2
  77. data/lib/hecks/quality_control/quality_control.bluebook +76 -124
  78. data/lib/hecks/runtime/interpreting.rb +3 -22
  79. data/lib/hecks/runtime/needs.rb +48 -0
  80. data/lib/hecks/runtime/query_interpreter.rb +3 -0
  81. data/lib/hecks/runtime/registry/verification.rb +3 -3
  82. data/lib/hecks/rust_build/host.rb +147 -0
  83. data/lib/hecks/rust_build/kernel_input.rb +51 -17
  84. data/lib/hecks/rust_build/project_rust/codegen_run.rb +0 -18
  85. data/lib/hecks/rust_build/project_rust.rb +7 -140
  86. data/lib/hecks/rust_build.rb +1 -0
  87. data/lib/hecks/site/bluebook/site.bluebook +80 -14
  88. data/lib/hecks/site/bluebook/site.ports.hecksagon +6 -0
  89. data/lib/hecks/sme/bluebook/sme.bluebook +128 -1
  90. data/lib/hecks/storehouse.rb +35 -1
  91. data/lib/hecks/tools/comment_style.rb +4 -2
  92. data/lib/hecks/tools/gate.rb +1 -1
  93. data/lib/hecks/tools/rust_comment_style.rb +2 -4
  94. data/lib/hecks/tools/site_routes.rb +69 -28
  95. data/lib/hecks/tools/tools_doc.rb +204 -0
  96. data/lib/hecks/tools.rb +1 -0
  97. data/lib/hecks/version.rb +1 -1
  98. data/lib/hecks/vocabulary.rb +152 -1
  99. data/lib/hecks.rb +1 -0
  100. data/qa/settings.yml +1 -2
  101. data/rust/build/src/lineage_pass.rs +31 -16
  102. data/rust/build/src/main.rs +4 -2
  103. data/rust/build/src/pipeline.rs +9 -20
  104. data/rust/codegen/src/attr.rs +1 -1
  105. data/rust/codegen/src/bridging.rs +8 -8
  106. data/rust/codegen/src/commands.rs +1 -1
  107. data/rust/codegen/src/constraints.rs +1 -1
  108. data/rust/codegen/src/dependency_planning.rs +1 -1
  109. data/rust/codegen/src/domain_generator.rs +2 -2
  110. data/rust/codegen/src/exemplar.rs +1 -1
  111. data/rust/codegen/src/expr_emitter.rs +1 -1
  112. data/rust/codegen/src/fielded.rs +1 -1
  113. data/rust/codegen/src/json.rs +214 -23
  114. data/rust/codegen/src/json_codec.rs +1 -1
  115. data/rust/codegen/src/literal.rs +6 -3
  116. data/rust/codegen/src/main.rs +23 -13
  117. data/rust/codegen/src/mutations.rs +1 -1
  118. data/rust/codegen/src/naming.rs +5 -3
  119. data/rust/codegen/src/optional_pass.rs +226 -0
  120. data/rust/codegen/src/ports.rs +1 -1
  121. data/rust/codegen/src/queries.rs +3 -3
  122. data/rust/codegen/src/reactions.rs +1 -1
  123. data/rust/codegen/src/read_models.rs +2 -2
  124. data/rust/codegen/src/reference_specs.rs +1 -1
  125. data/rust/codegen/src/registry.rs +4 -4
  126. data/rust/codegen/src/shared.rs +1 -1
  127. data/rust/{build → codegen}/src/sidecars.rs +9 -5
  128. data/rust/codegen/src/skip_reason.rs +1 -1
  129. data/rust/codegen/src/types.rs +4 -4
  130. data/rust/host/HECKS_RELEASE +1 -1
  131. data/rust/host/src/api.rs +1 -1
  132. data/rust/host/src/dispatch.rs +5 -0
  133. data/rust/host/src/needs.rs +193 -29
  134. data/rust/host/src/resend.rs +4 -4
  135. data/rust/host/src/ui_schema.rs +1 -1
  136. data/rust/parser/src/emit.rs +12 -0
  137. data/rust/parser/src/ir.rs +2 -0
  138. data/rust/parser/src/keywords.rs +2 -0
  139. data/rust/parser/src/main.rs +1 -0
  140. data/rust/parser/src/parse/command.rs +2 -44
  141. data/rust/parser/src/parse/mod.rs +1 -0
  142. data/rust/parser/src/parse/needs.rs +60 -0
  143. data/rust/parser/src/parse/query.rs +3 -0
  144. data/rust/src/exemplar/commands.rs +1 -1
  145. data/rust/src/exemplar/constraints.rs +1 -1
  146. data/rust/src/exemplar/fielded.rs +1 -1
  147. data/rust/src/exemplar/json.rs +1 -1
  148. data/rust/src/exemplar/mod.rs +1 -1
  149. data/rust/src/exemplar/mutations.rs +1 -1
  150. data/rust/src/exemplar/queries.rs +1 -1
  151. data/rust/src/exemplar/reactions.rs +1 -1
  152. data/rust/src/exemplar/read_models.rs +1 -1
  153. data/rust/src/exemplar/registry.rs +1 -1
  154. data/rust/src/exemplar/types.rs +1 -1
  155. data/rust/src/kernel/cli.rs +18 -8
  156. data/rust/src/kernel/needs.rs +104 -9
  157. data/rust/src/kernel/read_model.rs +1 -1
  158. data/rust/src/lib.rs +1 -1
  159. metadata +24 -30
  160. data/lib/hecks/rust_build/append_optionals.rb +0 -79
  161. data/lib/hecks/rust_build/rust_literal.rb +0 -33
  162. data/lib/hecks/three_zero/forms.yml +0 -127
  163. data/lib/hecks/three_zero.rb +0 -12
  164. data/rust/build/src/json.rs +0 -421
  165. data/rust/build/src/optional_pass.rs +0 -150
  166. data/rust/project/bridging.rb +0 -257
  167. data/rust/project/commands.rb +0 -877
  168. data/rust/project/constraints.rb +0 -86
  169. data/rust/project/dependency_planning.rb +0 -178
  170. data/rust/project/domain_generator.rb +0 -983
  171. data/rust/project/exemplar.rb +0 -148
  172. data/rust/project/expr_emitter.rb +0 -72
  173. data/rust/project/fielded.rb +0 -141
  174. data/rust/project/json_codec.rb +0 -641
  175. data/rust/project/mutations.rb +0 -695
  176. data/rust/project/naming.rb +0 -223
  177. data/rust/project/ports.rb +0 -65
  178. data/rust/project/queries.rb +0 -493
  179. data/rust/project/reactions.rb +0 -317
  180. data/rust/project/read_models.rb +0 -690
  181. data/rust/project/reference_specs.rb +0 -27
  182. data/rust/project/registry.rb +0 -396
  183. data/rust/project/skip_reason.rb +0 -29
  184. data/rust/project/types.rb +0 -225
  185. data/rust/project/write_if_changed.rb +0 -7
  186. data/rust/project.rb +0 -30
  187. data/rust/project_rust_pipeline.rb +0 -391
@@ -1,223 +0,0 @@
1
- require "hecks/vocabulary"
2
- require "hecks/bluebook/model_check"
3
- require "hecks/rust_build/domain_name"
4
- require "hecks/rust_build/rust_literal"
5
-
6
- module RustProjection
7
- # Domain-to-Rust codegen support: type mapping, reserved-name checks, and
8
- # literal escaping shared by the Rust code generator.
9
- module Projector
10
- module_function
11
-
12
- # Ruby has no single Boolean class, so a boolean attribute is declared
13
- # TrueClass or FalseClass; both keys must map here.
14
- SCALAR = { "String" => "String", "Integer" => "i64", "Float" => "f64",
15
- "TrueClass" => "bool", "FalseClass" => "bool" }.freeze
16
- SCALAR_KIND = { "String" => :string, "Integer" => :int, "Float" => :float,
17
- "TrueClass" => :bool, "FalseClass" => :bool }.freeze
18
-
19
- # A `Reference<X>` is represented as a plain `String` at the
20
- # struct-field level — it's a bare id, not a nested object (see
21
- # aggregates-and-value-objects.md's "Pointing at another aggregate").
22
- def reference_type?(type_name) = type_name.to_s.start_with?("Reference<")
23
-
24
- # Returns nil when `type_name` isn't a `Reference<...>` at all.
25
- def reference_target(type_name)
26
- match = type_name.to_s.match(/\AReference<(.+)>\z/)
27
- match && match[1]
28
- end
29
-
30
- def effective_scalar_type(type_name)
31
- return "String" if reference_type?(type_name)
32
-
33
- type_name if SCALAR.key?(type_name)
34
- end
35
-
36
- def rust_type(type_name, list:)
37
- scalar = effective_scalar_type(type_name)
38
- inner = scalar ? SCALAR.fetch(scalar) : rust_ident(type_name)
39
- list ? "Vec<#{inner}>" : inner
40
- end
41
-
42
- def rust_ident(name) = name.to_s.gsub(/[^A-Za-z0-9]/, "")
43
- def dispatch_fn_name(cmd) = cmd.gsub(/(?<=.)([A-Z])/, '_\1').downcase
44
-
45
- # A field name plays two different roles in generated code: a
46
- # string-literal match-arm/payload key (plain text) and a struct-field
47
- # identifier (must be raw-escaped when it collides with a Rust
48
- # keyword) — kept as separate functions so escaping only ever applies
49
- # to the identifier role.
50
- #
51
- # Declared once as the `RustReservedWord` vocabulary;
52
- # `hecks project_reserved_names` projects the same table into
53
- # hecks-codegen's `reserved_names.rs`.
54
- RUST_KEYWORDS = Hecks::Vocabulary.fetch("RustReservedWord")
55
-
56
- # A domain's own name doubles as a directory, a bare Rust module
57
- # identifier (module names get no raw-identifier escape hatch), and a
58
- # Cargo `[features]` key. This list rules out the third: every key
59
- # already used elsewhere in `rust/Cargo.toml` outside `[features]`,
60
- # plus `default`, which Cargo itself reserves for the auto-enabled
61
- # feature set.
62
- #
63
- # Declared once as the `CargoReservedName` vocabulary.
64
- CARGO_RESERVED_DOMAIN_NAMES = Hecks::RustBuild::DomainName::CARGO_RESERVED
65
-
66
- # Moved to `Hecks::RustBuild::DomainName`, which the codegen-backed `hecks project_rust` uses.
67
- def valid_domain_mod_name?(name) = Hecks::RustBuild::DomainName.valid?(name)
68
-
69
- # Identifier-shape check only (a PascalCase name always passes); whether
70
- # the downcased name is a Rust keyword is `reserved_name_refusal`'s job,
71
- # below. An aggregate name never doubles as a Cargo feature key.
72
- def legal_aggregate_mod_identifier?(name)
73
- name.to_s.downcase.match?(/\A[a-z_][a-z0-9_]*\z/)
74
- end
75
-
76
- # Returns nil when every name is usable, else the message
77
- # `DomainGenerator.call` raises. Must return the identical string as
78
- # hecks-codegen's `naming::reserved_name_refusal`
79
- # (rust/codegen/src/naming.rs) for the same input.
80
- def reserved_name_refusal(source_label, mod_name, aggregate_names)
81
- names = aggregate_names.map(&:to_s)
82
- reserved = Hecks::Bluebook::ModelCheck.rust_reserved_name_findings(aggregate_names: names, rust_target: true)
83
- .map(&:subject)
84
- refused = names.select { |name| reserved.include?(name) || !legal_aggregate_mod_identifier?(name) }
85
- if refused.any?
86
- return "#{source_label}: aggregate name(s) #{refused.map(&:inspect).join(', ')} can't be used as-is — " \
87
- "downcased, each becomes a bare Rust module identifier (`pub mod #{refused.first.downcase};`) and a " \
88
- "generated file name, and at least one is not a plain identifier or is a Rust keyword " \
89
- "(RustReservedWord). Module names get no raw-identifier (r#name) escape hatch — rename the aggregate."
90
- end
91
- return nil if valid_domain_mod_name?(mod_name)
92
-
93
- "#{source_label}: domain module name #{mod_name.to_s.inspect} can't be used as-is — it has to double as a Rust " \
94
- "module identifier and a Cargo feature name, and this one is either not a plain lowercase identifier, is a " \
95
- "Rust keyword (RustReservedWord), or is a reserved Cargo.toml key (CargoReservedName). Rename the domain."
96
- end
97
-
98
- # Every declared `name` in the IR, in document order. The `fields` maps under
99
- # `mutations` are skipped: their keys are attribute names and their values
100
- # are source text, not declarations. So is `translations`: an era edge names
101
- # data paths (`attendee.first_name`, a backfill into a nested value object)
102
- # that the host applies to stored rows and never writes into Rust.
103
- def declared_names(node, out = [])
104
- case node
105
- when Hash
106
- node.each do |key, value|
107
- if %w[fields translations].include?(key.to_s)
108
- next
109
- elsif key.to_s == "name" && value.is_a?(String)
110
- out << value
111
- else
112
- declared_names(value, out)
113
- end
114
- end
115
- when Array
116
- node.each { |item| declared_names(item, out) }
117
- end
118
- out
119
- end
120
-
121
- # Returns nil when every declared name is a plain identifier, else the
122
- # message `DomainGenerator.call` raises. Attribute, command, event, query
123
- # and port names are written into the generated crate as field, struct and
124
- # function names, so a name like "a: String, pub evil: u8" would otherwise
125
- # inject code into it. Must return the identical string as hecks-codegen's
126
- # `naming::unsafe_name_refusal` (rust/codegen/src/naming.rs).
127
- def unsafe_name_refusal(source_label, ir)
128
- refused = declared_names(ir).uniq.reject { |name| name.match?(/\A[A-Za-z_][A-Za-z0-9_]*\z/) }
129
- return nil if refused.empty?
130
-
131
- "#{source_label}: declared name(s) #{refused.map(&:inspect).join(', ')} can't be used as-is — each is " \
132
- "written into the generated Rust as an identifier, and at least one is not a plain identifier (letters, " \
133
- "digits and underscores, not starting with a digit). Rename it in the bluebook."
134
- end
135
-
136
- # `r#crate`/`r#self`/`r#super`/`r#Self` are not valid raw-identifier
137
- # syntax in Rust at all, so these four can't be escaped the way
138
- # `rust_ident_field` escapes every other `RUST_KEYWORDS` entry.
139
- RUST_UNESCAPABLE_KEYWORDS = %w[crate self super Self].freeze
140
-
141
- def rust_field(name) = name.to_s
142
-
143
- def rust_ident_field(name)
144
- field = rust_field(name)
145
- if RUST_UNESCAPABLE_KEYWORDS.include?(field)
146
- raise "rust_ident_field(#{field.inspect}): #{field.inspect} is a Rust keyword that cannot be rescued by a raw " \
147
- "identifier (r##{field} is not valid Rust syntax for crate/self/super/Self in any position) — rename " \
148
- "the attribute/field in the bluebook."
149
- end
150
-
151
- RUST_KEYWORDS.include?(field) ? "r##{field}" : field
152
- end
153
-
154
- # Splits on any run of non-alphanumeric characters, since closed-set
155
- # members can be glob patterns like `*.port`. `Self`, the one
156
- # capitalized Rust keyword, is renamed by original casing (SelfType/
157
- # SelfValue) so two different values can't collide into it.
158
- def closed_set_variant(row)
159
- _, value = row.first
160
- variant = value.to_s.split(/[^A-Za-z0-9]+/).reject(&:empty?).map(&:capitalize).join
161
- return variant unless variant == "Self"
162
-
163
- value.to_s.start_with?("S") ? "SelfType" : "SelfValue"
164
- end
165
-
166
- def screaming_snake(name)
167
- name.to_s.gsub(/([a-z0-9])([A-Z])/, '\1_\2').upcase
168
- end
169
-
170
- def scalar_to_value(type_name, rust_expr)
171
- case type_name
172
- when "String" then "Value::Str(#{rust_expr}.clone())"
173
- when "Integer" then "Value::Int(#{rust_expr})"
174
- when "Float" then "Value::Float(#{rust_expr})"
175
- when "TrueClass", "FalseClass" then "Value::Bool(#{rust_expr})"
176
- end
177
- end
178
-
179
- # True when `vo` can emit `Field::Nested(&self.x)`: any ordinary value
180
- # object, or a single-field closed set (which has its own `Fielded`
181
- # impl). False for a multi-field closed set, which has no `Fielded`
182
- # impl to emit against.
183
- def fielded_capable_nested?(vo)
184
- !vo[:closed_set] || vo[:attributes].size == 1
185
- end
186
-
187
- # `Fielded` impl for a single-field closed set: answers "value" as its
188
- # one field, matching the same arms the enum's own generated `to_json`
189
- # already builds (reproduced here rather than shared, so it never
190
- # touches the enum's existing to_json/from_json generation).
191
- def emit_closed_set_fielded_impl(vo)
192
- name = rust_ident(vo[:name])
193
- arms = vo[:members].map do |row|
194
- _, raw = row.first
195
- "#{name}::#{closed_set_variant(row)} => #{rust_string_literal(raw.to_s)}.to_string(),"
196
- end.join(" ")
197
-
198
- "impl crate::kernel::Fielded for #{name} {\n " \
199
- "fn field(&self, name: &str) -> Option<crate::kernel::Field<'_>> {\n " \
200
- "use crate::kernel::{Field, Value};\n " \
201
- "match name {\n " \
202
- "\"value\" => Some(Field::Value(Value::Str(match self { #{arms} }))),\n " \
203
- "_ => None,\n " \
204
- "}\n " \
205
- "}\n " \
206
- "fn as_scalar(&self) -> Option<crate::kernel::Value> {\n " \
207
- "match self.field(\"value\") { Some(crate::kernel::Field::Value(v)) => Some(v), _ => None }\n " \
208
- "}\n" \
209
- "}"
210
- end
211
-
212
- def literal_rhs(literal)
213
- case literal
214
- when String then "#{rust_string_literal(literal)}.to_string()"
215
- when Integer, Float, true, false then literal.to_s
216
- else raise "unsupported literal mutation source #{literal.inspect} — not one of String/Integer/Float/Boolean"
217
- end
218
- end
219
-
220
- # Moved to `Hecks::RustBuild::RustLiteral`, which the codegen-backed `hecks project_rust` uses.
221
- def rust_string_literal(str) = Hecks::RustBuild::RustLiteral.string(str)
222
- end
223
- end
@@ -1,65 +0,0 @@
1
- require_relative "skip_reason"
2
-
3
- module RustProjection
4
- module Projector
5
- module_function
6
-
7
- # A port operation has no givens or mutations, so the only ungenerable shape left is an
8
- # attribute type with no resolved Rust type.
9
- def port_operation_skip_reason(operation, _owner_name, value_objects_by_name)
10
- unresolved = operation[:attributes].reject do |attr|
11
- next true if attr[:list] # a list attribute's element type is checked below
12
- next true if reference_type?(attr[:type])
13
- next true if SCALAR.key?(attr[:type])
14
-
15
- value_objects_by_name.key?(attr[:type])
16
- end
17
- return skip("port_attribute_type", "attribute type(s) #{unresolved.map { |a| a[:type] }.uniq.join(', ')} not generated yet " \
18
- "(a value object this aggregate's own attributes never resolved a Rust type for)") if unresolved.any?
19
-
20
- nil
21
- end
22
-
23
- # Emits the args struct and a pure dispatch function that builds the events the operation
24
- # `emits`, addressed by `receiver_id`. Payloads are `Json::Null` placeholders; the registry's
25
- # `stamp_payload` replaces them. A self-reference to the owner is left out of the args.
26
- def emit_port_operation(operation, port_name, owner_name, domain_name, value_objects_by_name, aggregates_by_name)
27
- args_struct = "#{rust_ident(port_name)}#{rust_ident(operation[:name])}Args"
28
- qualified = "#{domain_name}::#{owner_name}"
29
- fact_attrs = operation[:attributes].reject { |attr| reference_target(attr[:type]) == owner_name }
30
-
31
- struct_lines = ["pub struct #{args_struct} {"]
32
- fact_attrs.each do |attr|
33
- type = rust_type(attr[:type], list: attr[:list])
34
- type = "Option<#{type}>" if attr[:optional]
35
- struct_lines << " #{Exemplar.render('struct_field', 'TmplFieldType' => type, 'tmpl_field' => rust_ident_field(attr[:name]))}"
36
- end
37
- struct_lines << "}"
38
-
39
- invariant_checks = invariant_checks_for(operation, aggregates_by_name, value_objects_by_name)
40
- fn = "#{port_name.downcase}_#{dispatch_fn_name(rust_ident(operation[:name]))}"
41
-
42
- events = operation[:emits].map do |event_name|
43
- " crate::kernel::Event { name: #{event_name.inspect}.to_string(), aggregate: #{qualified.inspect}.to_string(), " \
44
- "id: receiver_id.to_string(), payload: crate::kernel::Json::Null, occurred_at: None, correlation: None },"
45
- end
46
-
47
- dispatch_fn = <<~RUST.rstrip
48
- pub fn dispatch_operation_#{fn}(receiver_id: &str, args: #{args_struct}) -> Result<Vec<crate::kernel::Event>, crate::kernel::Refusal> {
49
- #{invariant_checks.join("\n")}
50
- Ok(vec![
51
- #{events.join("\n")}
52
- ])
53
- }
54
- RUST
55
-
56
- [
57
- "#[derive(Debug, Clone)]\n#{struct_lines.join("\n")}",
58
- emit_to_json_flat(args_struct, fact_attrs, value_objects_by_name),
59
- emit_from_json_flat(args_struct, fact_attrs, value_objects_by_name, command_name: "#{port_name}.#{operation[:name]}",
60
- interleave_checks: true, aggregates_by_name: aggregates_by_name),
61
- dispatch_fn,
62
- ].join("\n\n")
63
- end
64
- end
65
- end