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.
- checksums.yaml +4 -4
- data/lib/hecks/bluebook/assembly/contracts.rb +2 -1
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +2 -39
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +3 -3
- data/lib/hecks/bluebook/dsl/need_word.rb +50 -0
- data/lib/hecks/bluebook/dsl/query_builder.rb +6 -1
- data/lib/hecks/bluebook/query.rb +8 -5
- data/lib/hecks/cli/check_era.rb +3 -39
- data/lib/hecks/cli/interview_draft.rb +86 -31
- data/lib/hecks/cli/interview_session.rb +77 -26
- data/lib/hecks/cli/interview_shape.rb +179 -0
- data/lib/hecks/doors/cli_runner.rb +14 -1
- data/lib/hecks/doors/launcher_options.rb +13 -3
- data/lib/hecks/doors/mcp_door.rb +16 -2
- data/lib/hecks/doors/mcp_door_scope.rb +45 -5
- data/lib/hecks/doors/mcp_guide_cache.rb +75 -0
- data/lib/hecks/doors.rb +1 -1
- data/lib/hecks/embryonaut_bluebook/manifest.rb +117 -0
- data/lib/hecks/embryonaut_bluebook/registry.rb +209 -0
- data/lib/hecks/embryonaut_bluebook/vendor.rb +1 -1
- data/lib/hecks/embryonaut_bluebook.rb +2 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +17 -1
- data/lib/hecks/fuzzing/qa_settings.rb +15 -34
- data/lib/hecks/gate/stages.yml +113 -8
- data/lib/hecks/hecks/adapters/codebase/regeneration.rb +10 -1
- data/lib/hecks/hecks/adapters/codebase/test_runner.rb +10 -2
- data/lib/hecks/hecks/adapters/first_admin.rb +108 -0
- data/lib/hecks/hecks/adapters/git.rb +59 -0
- data/lib/hecks/hecks/adapters/in_process_access.rb +25 -0
- data/lib/hecks/hecks/adapters/in_process_boot.rb +2 -0
- data/lib/hecks/hecks/adapters/rust_toolchain.rb +18 -3
- data/lib/hecks/hecks/adapters/rust_workspace.rb +1 -1
- data/lib/hecks/hecks/adapters/site_toolchain.rb +88 -11
- data/lib/hecks/hecks/codebase.behaviors +6 -0
- data/lib/hecks/hecks/codebase.bluebook +21 -0
- data/lib/hecks/hecks/custodian.behaviors +6 -0
- data/lib/hecks/hecks/custodian.bluebook +217 -0
- data/lib/hecks/hecks/hecks.hecksagon +24 -0
- data/lib/hecks/hecks/hecks.world +8 -3
- data/lib/hecks/hecks/site.world +5 -2
- data/lib/hecks/language/bluebook/query.bluebook +25 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +220 -7
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +1 -1
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -3
- data/lib/hecks/projections/deploy/box/hosting.rb +170 -0
- data/lib/hecks/projections/deploy/box/hosting_settings.rb +123 -0
- data/lib/hecks/projections/deploy/box/settings.rb +40 -6
- data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +2 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.services-roll.part +33 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +79 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.taskdef-roll.part +77 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.taskdef.part +12 -0
- data/lib/hecks/projections/deploy/box/templates/hosting.mk.tmpl +24 -0
- data/lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl +17 -0
- data/lib/hecks/projections/deploy/box/templates/smoke-after-deploy.sh.tmpl +271 -0
- data/lib/hecks/projections/deploy/box.rb +28 -4
- data/lib/hecks/projections/deploy/fargate.rb +3 -3
- data/lib/hecks/projections/deploy/lambda.rb +8 -8
- data/lib/hecks/projections/model/deviations.rb +1 -1
- 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/live_edge.rb +162 -0
- 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/quality_control/adapters/git_pr.rb +0 -14
- data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_open_pr.rb +32 -16
- data/lib/hecks/quality_control/cli/qa_seed_angles.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_seed_targets.rb +1 -2
- data/lib/hecks/quality_control/quality_control.bluebook +76 -124
- data/lib/hecks/runtime/interpreting.rb +3 -22
- data/lib/hecks/runtime/needs.rb +48 -0
- data/lib/hecks/runtime/query_interpreter.rb +3 -0
- data/lib/hecks/runtime/registry/verification.rb +3 -3
- data/lib/hecks/rust_build/host.rb +147 -0
- data/lib/hecks/rust_build/kernel_input.rb +51 -17
- data/lib/hecks/rust_build/project_rust/codegen_run.rb +0 -18
- data/lib/hecks/rust_build/project_rust.rb +7 -140
- data/lib/hecks/rust_build.rb +1 -0
- data/lib/hecks/site/bluebook/site.bluebook +80 -14
- data/lib/hecks/site/bluebook/site.ports.hecksagon +6 -0
- data/lib/hecks/sme/bluebook/sme.bluebook +128 -1
- data/lib/hecks/storehouse.rb +35 -1
- data/lib/hecks/tools/comment_style.rb +4 -2
- data/lib/hecks/tools/gate.rb +1 -1
- data/lib/hecks/tools/rust_comment_style.rb +2 -4
- data/lib/hecks/tools/site_routes.rb +69 -28
- data/lib/hecks/tools/tools_doc.rb +204 -0
- data/lib/hecks/tools.rb +1 -0
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +152 -1
- data/lib/hecks.rb +1 -0
- data/qa/settings.yml +1 -2
- data/rust/build/src/lineage_pass.rs +31 -16
- data/rust/build/src/main.rs +4 -2
- data/rust/build/src/pipeline.rs +9 -20
- data/rust/codegen/src/attr.rs +1 -1
- data/rust/codegen/src/bridging.rs +8 -8
- 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.rs +214 -23
- data/rust/codegen/src/json_codec.rs +1 -1
- data/rust/codegen/src/literal.rs +6 -3
- data/rust/codegen/src/main.rs +23 -13
- data/rust/codegen/src/mutations.rs +1 -1
- data/rust/codegen/src/naming.rs +5 -3
- data/rust/codegen/src/optional_pass.rs +226 -0
- 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/{build → codegen}/src/sidecars.rs +9 -5
- 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/api.rs +1 -1
- data/rust/host/src/dispatch.rs +5 -0
- data/rust/host/src/needs.rs +193 -29
- data/rust/host/src/resend.rs +4 -4
- data/rust/host/src/ui_schema.rs +1 -1
- data/rust/parser/src/emit.rs +12 -0
- data/rust/parser/src/ir.rs +2 -0
- data/rust/parser/src/keywords.rs +2 -0
- data/rust/parser/src/main.rs +1 -0
- data/rust/parser/src/parse/command.rs +2 -44
- data/rust/parser/src/parse/mod.rs +1 -0
- data/rust/parser/src/parse/needs.rs +60 -0
- data/rust/parser/src/parse/query.rs +3 -0
- 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 +18 -8
- data/rust/src/kernel/needs.rs +104 -9
- data/rust/src/kernel/read_model.rs +1 -1
- data/rust/src/lib.rs +1 -1
- metadata +24 -30
- data/lib/hecks/rust_build/append_optionals.rb +0 -79
- data/lib/hecks/rust_build/rust_literal.rb +0 -33
- data/lib/hecks/three_zero/forms.yml +0 -127
- data/lib/hecks/three_zero.rb +0 -12
- data/rust/build/src/json.rs +0 -421
- data/rust/build/src/optional_pass.rs +0 -150
- 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/host/src/needs.rs
CHANGED
|
@@ -110,21 +110,34 @@ pub fn table(domain_ir: &Value) -> Value {
|
|
|
110
110
|
Value::Object(verbs)
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
fn list<'a>(value: &'a Value, key: &str) -> &'a [Value] {
|
|
114
|
+
value.get(key).and_then(Value::as_array).map(Vec::as_slice).unwrap_or(&[])
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
fn named(value: &Value, name: &str) -> bool {
|
|
118
|
+
value.get("name").and_then(Value::as_str) == Some(name)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/// The command a verb names: `Domain::Aggregate.Command` on the aggregate itself, or
|
|
122
|
+
/// `Domain::Aggregate.Entity[.Entity].Command` on an entity nested inside it.
|
|
123
|
+
fn command_for<'a>(domain_ir: &'a Value, verb: &str) -> Option<&'a Value> {
|
|
124
|
+
let mut segments = verb.split('.');
|
|
125
|
+
let aggregate = segments.next()?.rsplit("::").next()?;
|
|
126
|
+
let mut path: Vec<&str> = segments.collect();
|
|
127
|
+
let command_name = path.pop()?;
|
|
128
|
+
let mut node = list(domain_ir, "aggregates").iter().find(|a| named(a, aggregate))?;
|
|
129
|
+
for entity in path {
|
|
130
|
+
node = list(node, "entities").iter().find(|e| named(e, entity))?;
|
|
131
|
+
}
|
|
132
|
+
list(node, "commands").iter().find(|c| named(c, command_name))
|
|
133
|
+
}
|
|
134
|
+
|
|
113
135
|
/// The command's attributes that declare a default (`attribute :runs, Count, default: 30`), each
|
|
114
136
|
/// with the declared value. An attribute whose default is null declares none.
|
|
115
137
|
fn declared_defaults<'a>(domain_ir: &'a Value, verb: &str) -> Vec<(&'a str, &'a Value)> {
|
|
116
|
-
let Some(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
let list = |value: &'a Value, key: &str| -> Vec<&'a Value> {
|
|
120
|
-
value.get(key).and_then(Value::as_array).map(|a| a.iter().collect()).unwrap_or_default()
|
|
121
|
-
};
|
|
122
|
-
list(domain_ir, "aggregates")
|
|
123
|
-
.into_iter()
|
|
124
|
-
.filter(|a| named(a, aggregate))
|
|
125
|
-
.flat_map(|a| list(a, "commands"))
|
|
126
|
-
.filter(|c| named(c, command_name))
|
|
127
|
-
.flat_map(|c| list(c, "attributes"))
|
|
138
|
+
let Some(command) = command_for(domain_ir, verb) else { return Vec::new() };
|
|
139
|
+
list(command, "attributes")
|
|
140
|
+
.iter()
|
|
128
141
|
.filter_map(|a| {
|
|
129
142
|
let default = a.get("default").filter(|d| !d.is_null())?;
|
|
130
143
|
Some((a.get("name").and_then(Value::as_str)?, default))
|
|
@@ -133,29 +146,35 @@ fn declared_defaults<'a>(domain_ir: &'a Value, verb: &str) -> Vec<(&'a str, &'a
|
|
|
133
146
|
}
|
|
134
147
|
|
|
135
148
|
/// Every command's declared defaults as the kernel's input `"defaults"` reads them:
|
|
136
|
-
/// `{ verb: { attribute: value } }
|
|
137
|
-
/// the host fills the outermost one.
|
|
149
|
+
/// `{ verb: { attribute: value } }`, entity commands included. The kernel fills a reaction's
|
|
150
|
+
/// command from this the same way the host fills the outermost one.
|
|
138
151
|
pub fn defaults_table(domain_ir: &Value) -> Value {
|
|
139
152
|
let domain = domain_ir.get("name").and_then(Value::as_str).unwrap_or_default();
|
|
140
153
|
let mut verbs = serde_json::Map::new();
|
|
141
|
-
|
|
142
|
-
for aggregate in aggregates {
|
|
154
|
+
for aggregate in list(domain_ir, "aggregates") {
|
|
143
155
|
let aggregate_name = aggregate.get("name").and_then(Value::as_str).unwrap_or_default();
|
|
144
|
-
|
|
145
|
-
let command_name = command.get("name").and_then(Value::as_str).unwrap_or_default();
|
|
146
|
-
let verb = format!("{domain}::{aggregate_name}.{command_name}");
|
|
147
|
-
let defaults: serde_json::Map<String, Value> = declared_defaults(domain_ir, &verb)
|
|
148
|
-
.into_iter()
|
|
149
|
-
.map(|(name, value)| (name.to_string(), value.clone()))
|
|
150
|
-
.collect();
|
|
151
|
-
if !defaults.is_empty() {
|
|
152
|
-
verbs.insert(verb, Value::Object(defaults));
|
|
153
|
-
}
|
|
154
|
-
}
|
|
156
|
+
collect_defaults(domain_ir, aggregate, &format!("{domain}::{aggregate_name}"), &mut verbs);
|
|
155
157
|
}
|
|
156
158
|
Value::Object(verbs)
|
|
157
159
|
}
|
|
158
160
|
|
|
161
|
+
/// Adds the defaults of `node`'s commands, then of each entity nested in it, under `prefix`.
|
|
162
|
+
fn collect_defaults(domain_ir: &Value, node: &Value, prefix: &str, verbs: &mut serde_json::Map<String, Value>) {
|
|
163
|
+
for command in list(node, "commands") {
|
|
164
|
+
let command_name = command.get("name").and_then(Value::as_str).unwrap_or_default();
|
|
165
|
+
let verb = format!("{prefix}.{command_name}");
|
|
166
|
+
let defaults: serde_json::Map<String, Value> =
|
|
167
|
+
declared_defaults(domain_ir, &verb).into_iter().map(|(name, value)| (name.to_string(), value.clone())).collect();
|
|
168
|
+
if !defaults.is_empty() {
|
|
169
|
+
verbs.insert(verb, Value::Object(defaults));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
for entity in list(node, "entities") {
|
|
173
|
+
let entity_name = entity.get("name").and_then(Value::as_str).unwrap_or_default();
|
|
174
|
+
collect_defaults(domain_ir, entity, &format!("{prefix}.{entity_name}"), verbs);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
159
178
|
/// The facts of a command invocation: the `with` object when the call carries one, else the
|
|
160
179
|
/// flat object itself (the kernel's `CommandInvocation` reads both shapes).
|
|
161
180
|
fn facts_mut(args: &mut Value) -> Option<&mut serde_json::Map<String, Value>> {
|
|
@@ -167,9 +186,11 @@ fn facts_mut(args: &mut Value) -> Option<&mut serde_json::Map<String, Value>> {
|
|
|
167
186
|
|
|
168
187
|
fn answer(fact: &str, attribute_type: Option<&str>, clock: &dyn Clock) -> Option<Value> {
|
|
169
188
|
match fact {
|
|
170
|
-
|
|
189
|
+
// `now` is epoch seconds; `today` the day those fall in, whole days since the epoch (UTC).
|
|
190
|
+
"now" | "today" => {
|
|
171
191
|
let secs = clock.now_secs();
|
|
172
|
-
|
|
192
|
+
let answer = if fact == "today" { secs.div_euclid(86_400) } else { secs };
|
|
193
|
+
Some(if attribute_type == Some("Integer") { json!(answer) } else { json!({ "value": answer }) })
|
|
173
194
|
}
|
|
174
195
|
_ => None,
|
|
175
196
|
}
|
|
@@ -193,6 +214,49 @@ pub fn fill_needs(domain_ir: &Value, verb: &str, args: &mut Value, clock: &dyn C
|
|
|
193
214
|
}
|
|
194
215
|
}
|
|
195
216
|
|
|
217
|
+
/// The facts the query `question` (`Domain::Aggregate.Query`) needs, each with the type of the
|
|
218
|
+
/// argument of the same name. An entity's query is not looked up here.
|
|
219
|
+
fn declared_query_needs<'a>(domain_ir: &'a Value, question: &str) -> Vec<(&'a str, Option<&'a str>)> {
|
|
220
|
+
let Some((qualified_aggregate, query_name)) = question.rsplit_once('.') else { return Vec::new() };
|
|
221
|
+
let aggregate = qualified_aggregate.rsplit("::").next().unwrap_or(qualified_aggregate);
|
|
222
|
+
let Some(query) = list(domain_ir, "aggregates")
|
|
223
|
+
.iter()
|
|
224
|
+
.find(|a| named(a, aggregate))
|
|
225
|
+
.and_then(|a| list(a, "queries").iter().find(|q| named(q, query_name)))
|
|
226
|
+
else {
|
|
227
|
+
return Vec::new();
|
|
228
|
+
};
|
|
229
|
+
let attributes = list(query, "attributes");
|
|
230
|
+
list(query, "needs")
|
|
231
|
+
.iter()
|
|
232
|
+
.filter_map(|n| n.get("fact").and_then(Value::as_str))
|
|
233
|
+
.map(|fact| {
|
|
234
|
+
let attribute_type =
|
|
235
|
+
attributes.iter().find(|a| named(a, fact)).and_then(|a| a.get("type")).and_then(Value::as_str);
|
|
236
|
+
(fact, attribute_type)
|
|
237
|
+
})
|
|
238
|
+
.collect()
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/// Fills each fact the query `question` needs that `args` leaves out, before the kernel checks the
|
|
242
|
+
/// arguments. A key the caller supplied (even a null) is kept; a query that needs nothing, or whose
|
|
243
|
+
/// arguments are not an object, is untouched.
|
|
244
|
+
pub fn fill_query_needs(domain_ir: &Value, question: &str, args: &mut Value, clock: &dyn Clock) {
|
|
245
|
+
let needs = declared_query_needs(domain_ir, question);
|
|
246
|
+
if needs.is_empty() {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
let Some(facts) = args.as_object_mut() else { return };
|
|
250
|
+
for (fact, attribute_type) in needs {
|
|
251
|
+
if facts.contains_key(fact) {
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if let Some(value) = answer(fact, attribute_type, clock) {
|
|
255
|
+
facts.insert(fact.to_string(), value);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
196
260
|
/// Gives each argument `verb` declares a default for, and `args` leaves out, that default, after
|
|
197
261
|
/// `fill_needs` and before any gate reads the arguments. A key the caller supplied (even a null)
|
|
198
262
|
/// is kept, as is a fact `fill_needs` already answered.
|
|
@@ -351,4 +415,104 @@ mod tests {
|
|
|
351
415
|
assert_eq!(defaults_table(&defaulted_ir()), json!({"Qc::Check.Start": {"runs": 30}}));
|
|
352
416
|
assert_eq!(defaults_table(&integer_ir()), json!({}));
|
|
353
417
|
}
|
|
418
|
+
|
|
419
|
+
// A board entity with a defaulted command, and a card nested inside it with another.
|
|
420
|
+
fn nested_ir() -> Value {
|
|
421
|
+
json!({"name": "Np", "aggregates": [{"name": "Workspace", "commands": [], "entities": [
|
|
422
|
+
{"name": "Board", "commands": [
|
|
423
|
+
{"name": "Retitle", "attributes": [
|
|
424
|
+
{"name": "label", "type": "BoardLabel", "default": {"value": "untitled"}}], "needs": []}
|
|
425
|
+
], "entities": [
|
|
426
|
+
{"name": "Card", "commands": [
|
|
427
|
+
{"name": "Remark", "attributes": [
|
|
428
|
+
{"name": "note", "type": "CardNote", "default": {"text": "none"}}], "needs": []}
|
|
429
|
+
]}
|
|
430
|
+
]}
|
|
431
|
+
]}]})
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
#[test]
|
|
435
|
+
fn an_entity_command_is_filled_from_its_own_declared_default() {
|
|
436
|
+
let mut args = json!({"reference": {"value": "W1"}, "number": {"value": 1}});
|
|
437
|
+
fill_defaults(&nested_ir(), "Np::Workspace.Board.Retitle", &mut args);
|
|
438
|
+
assert_eq!(args["label"], json!({"value": "untitled"}));
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
#[test]
|
|
442
|
+
fn a_command_on_an_entity_nested_in_an_entity_is_filled_too() {
|
|
443
|
+
let mut args = json!({"with": {"reference": {"value": "W1"}}});
|
|
444
|
+
fill_defaults(&nested_ir(), "Np::Workspace.Board.Card.Remark", &mut args);
|
|
445
|
+
assert_eq!(args["with"]["note"], json!({"text": "none"}));
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
#[test]
|
|
449
|
+
fn an_entity_path_that_names_nothing_is_left_untouched() {
|
|
450
|
+
let mut args = json!({"number": 1});
|
|
451
|
+
fill_defaults(&nested_ir(), "Np::Workspace.Shelf.Retitle", &mut args);
|
|
452
|
+
fill_defaults(&nested_ir(), "Np::Workspace.Board.Remark", &mut args);
|
|
453
|
+
assert_eq!(args, json!({"number": 1}));
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// An aggregate with a query that needs `now` and one that needs `today`.
|
|
457
|
+
fn query_ir() -> Value {
|
|
458
|
+
json!({"name": "Lease", "aggregates": [{"name": "Slot", "commands": [], "queries": [
|
|
459
|
+
{"name": "Expired", "attributes": [{"name": "now", "type": "LeaseInstant"}],
|
|
460
|
+
"needs": [{"fact": "now"}]},
|
|
461
|
+
{"name": "IssuedToday", "attributes": [{"name": "today", "type": "Integer"}],
|
|
462
|
+
"needs": [{"fact": "today"}]},
|
|
463
|
+
{"name": "Plain", "attributes": [{"name": "now", "type": "LeaseInstant"}]}
|
|
464
|
+
]}]})
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
#[test]
|
|
468
|
+
fn a_needed_query_fact_the_caller_left_out_is_filled_from_the_clock() {
|
|
469
|
+
let mut args = json!({});
|
|
470
|
+
fill_query_needs(&query_ir(), "Lease::Slot.Expired", &mut args, &FixedClock(1_700_000_000));
|
|
471
|
+
assert_eq!(args["now"], json!({"value": 1_700_000_000}));
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
#[test]
|
|
475
|
+
fn a_query_value_the_caller_supplied_is_kept_even_when_null() {
|
|
476
|
+
let mut args = json!({"now": null});
|
|
477
|
+
fill_query_needs(&query_ir(), "Lease::Slot.Expired", &mut args, &FixedClock(5));
|
|
478
|
+
assert!(args["now"].is_null());
|
|
479
|
+
let mut named = json!({"now": {"value": 9}});
|
|
480
|
+
fill_query_needs(&query_ir(), "Lease::Slot.Expired", &mut named, &FixedClock(5));
|
|
481
|
+
assert_eq!(named["now"], json!({"value": 9}));
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
#[test]
|
|
485
|
+
fn a_query_that_needs_nothing_or_names_nothing_is_untouched() {
|
|
486
|
+
let mut args = json!({});
|
|
487
|
+
fill_query_needs(&query_ir(), "Lease::Slot.Plain", &mut args, &FixedClock(5));
|
|
488
|
+
fill_query_needs(&query_ir(), "Lease::Slot.Missing", &mut args, &FixedClock(5));
|
|
489
|
+
fill_query_needs(&query_ir(), "Lease::Shelf.Expired", &mut args, &FixedClock(5));
|
|
490
|
+
assert_eq!(args, json!({}));
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
#[test]
|
|
494
|
+
fn today_is_whole_days_since_the_epoch_shaped_for_its_argument() {
|
|
495
|
+
let mut args = json!({});
|
|
496
|
+
fill_query_needs(&query_ir(), "Lease::Slot.IssuedToday", &mut args, &FixedClock((19_000 * 86_400) + 86_399));
|
|
497
|
+
assert_eq!(args["today"], json!(19_000));
|
|
498
|
+
|
|
499
|
+
let mut command = json!({"with": {}});
|
|
500
|
+
let ir = json!({"name": "D", "aggregates": [{"name": "A", "commands": [
|
|
501
|
+
{"name": "Open", "attributes": [{"name": "day", "type": "Day"}], "needs": [{"fact": "day"}]},
|
|
502
|
+
{"name": "Take", "attributes": [{"name": "today", "type": "Day"}], "needs": [{"fact": "today"}]}
|
|
503
|
+
]}]});
|
|
504
|
+
fill_needs(&ir, "D::A.Take", &mut command, &FixedClock(19_001 * 86_400));
|
|
505
|
+
assert_eq!(command["with"]["today"], json!({"value": 19_001}));
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
#[test]
|
|
509
|
+
fn the_kernel_table_lists_entity_commands_under_their_full_path() {
|
|
510
|
+
assert_eq!(
|
|
511
|
+
defaults_table(&nested_ir()),
|
|
512
|
+
json!({
|
|
513
|
+
"Np::Workspace.Board.Retitle": {"label": {"value": "untitled"}},
|
|
514
|
+
"Np::Workspace.Board.Card.Remark": {"note": {"text": "none"}}
|
|
515
|
+
})
|
|
516
|
+
);
|
|
517
|
+
}
|
|
354
518
|
}
|
data/rust/host/src/resend.rs
CHANGED
|
@@ -256,8 +256,8 @@ mod tests {
|
|
|
256
256
|
|
|
257
257
|
#[test]
|
|
258
258
|
fn a_reply_to_address_rides_in_the_payload_and_is_absent_without_one() {
|
|
259
|
-
let with = payload("f", Some("
|
|
260
|
-
assert_eq!(with["reply_to"], "
|
|
259
|
+
let with = payload("f", Some("replies@example.com"), &email("a@b.com", "x"));
|
|
260
|
+
assert_eq!(with["reply_to"], "replies@example.com");
|
|
261
261
|
assert!(payload("f", None, &email("a@b.com", "x")).get("reply_to").is_none());
|
|
262
262
|
}
|
|
263
263
|
|
|
@@ -273,11 +273,11 @@ mod tests {
|
|
|
273
273
|
#[tokio::test]
|
|
274
274
|
async fn a_live_send_carries_the_reply_to_address_to_resend() {
|
|
275
275
|
let fake = FakeResend::start(vec![("200 OK", "", r#"{"id":"em_9"}"#)]);
|
|
276
|
-
let mailer = Mailer::live("re_x", "News <n@mail.example.com>", &fake.base).replying_to("
|
|
276
|
+
let mailer = Mailer::live("re_x", "News <n@mail.example.com>", &fake.base).replying_to("replies@example.com");
|
|
277
277
|
|
|
278
278
|
mailer.deliver(&email("a@b.com", "x")).await;
|
|
279
279
|
|
|
280
|
-
assert!(fake.requests()[0].contains(r#""reply_to":"
|
|
280
|
+
assert!(fake.requests()[0].contains(r#""reply_to":"replies@example.com""#), "{}", fake.requests()[0]);
|
|
281
281
|
}
|
|
282
282
|
|
|
283
283
|
#[test]
|
data/rust/host/src/ui_schema.rs
CHANGED
|
@@ -860,7 +860,7 @@ fn humanize(key: &str) -> String {
|
|
|
860
860
|
mod tests {
|
|
861
861
|
use super::*;
|
|
862
862
|
|
|
863
|
-
//
|
|
863
|
+
// Shaped like a real member-gated app, the kind the Ruby engine
|
|
864
864
|
// actually serves. Each test below pins one rule of ui_schema.rb.
|
|
865
865
|
|
|
866
866
|
fn domain() -> Value {
|
data/rust/parser/src/emit.rs
CHANGED
|
@@ -463,6 +463,18 @@ fn query_json(q: &ir::Query) -> JsonValue {
|
|
|
463
463
|
if let Some(returns) = &q.returns {
|
|
464
464
|
pairs.push(("returns".to_string(), JsonValue::str(returns.clone())));
|
|
465
465
|
}
|
|
466
|
+
// `needs` follows `returns`, only when declared, so a query that needs nothing keeps its shape.
|
|
467
|
+
if !q.needs.is_empty() {
|
|
468
|
+
pairs.push((
|
|
469
|
+
"needs".to_string(),
|
|
470
|
+
JsonValue::Array(
|
|
471
|
+
q.needs
|
|
472
|
+
.iter()
|
|
473
|
+
.map(|fact| JsonValue::Object(vec![("fact".to_string(), JsonValue::str(fact.clone()))]))
|
|
474
|
+
.collect(),
|
|
475
|
+
),
|
|
476
|
+
));
|
|
477
|
+
}
|
|
466
478
|
JsonValue::Object(pairs)
|
|
467
479
|
}
|
|
468
480
|
|
data/rust/parser/src/ir.rs
CHANGED
|
@@ -133,6 +133,8 @@ pub struct Query {
|
|
|
133
133
|
pub options: QueryOptions,
|
|
134
134
|
/// `returns Name` or `returns list_of(Name)`, spelled as `Query#returns` holds it.
|
|
135
135
|
pub returns: Option<String>,
|
|
136
|
+
/// Outside facts the runtime supplies before the filter reads the arguments (`needs :now`).
|
|
137
|
+
pub needs: Vec<String>,
|
|
136
138
|
}
|
|
137
139
|
|
|
138
140
|
/// `AuthorizationSpec#to_h`: both fields are bare `.to_s`, never Literal-rendered.
|
data/rust/parser/src/keywords.rs
CHANGED
|
@@ -161,6 +161,7 @@ pub static KEYWORDS: &[KeywordRow] = &[
|
|
|
161
161
|
KeywordRow { word: "authorize", context: "Query", body: "none", inner: "", opens: "", fills: "options", status: "admitted", was: "", resolves_via: "", disambiguator: "" },
|
|
162
162
|
KeywordRow { word: "inspect_query", context: "Query", body: "none", inner: "", opens: "", fills: "options", status: "admitted", was: "", resolves_via: "", disambiguator: "" },
|
|
163
163
|
KeywordRow { word: "returns", context: "Query", body: "none", inner: "", opens: "", fills: "returns", status: "admitted", was: "", resolves_via: "", disambiguator: "" },
|
|
164
|
+
KeywordRow { word: "needs", context: "Query", body: "none", inner: "", opens: "", fills: "needs", status: "admitted", was: "", resolves_via: "", disambiguator: "" },
|
|
164
165
|
KeywordRow { word: "attribute", context: "ValueObject", body: "none", inner: "", opens: "", fills: "attributes", status: "admitted", was: "", resolves_via: "", disambiguator: "" },
|
|
165
166
|
KeywordRow { word: "one_of", context: "ValueObject", body: "rows", inner: "OneOf", opens: "", fills: "rows", status: "admitted", was: "", resolves_via: "", disambiguator: "" },
|
|
166
167
|
KeywordRow { word: "invariant", context: "ValueObject", body: "source", inner: "", opens: "", fills: "invariants", status: "admitted", was: "", resolves_via: "sibling_scan", disambiguator: "" },
|
|
@@ -437,6 +438,7 @@ pub static ARGUMENTS: &[ArgumentRow] = &[
|
|
|
437
438
|
ArgumentRow { keyword: "authorize", context: "Query", at: "", named: "tenant", kind: "symbol", required: "false", fills: "tenant", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
438
439
|
ArgumentRow { keyword: "inspect_query", context: "Query", at: "1", named: "", kind: "symbol", required: "false", fills: "mode", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
439
440
|
ArgumentRow { keyword: "returns", context: "Query", at: "1", named: "", kind: "constant", required: "true", fills: "type", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
441
|
+
ArgumentRow { keyword: "needs", context: "Query", at: "1", named: "", kind: "symbol", required: "true", fills: "fact", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
440
442
|
ArgumentRow { keyword: "attribute", context: "ValueObject", at: "1", named: "", kind: "symbol", required: "true", fills: "name", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
441
443
|
ArgumentRow { keyword: "attribute", context: "ValueObject", at: "2", named: "", kind: "constant", required: "true", fills: "type", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
442
444
|
ArgumentRow { keyword: "attribute", context: "ValueObject", at: "", named: "default", kind: "literal", required: "false", fills: "default", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
data/rust/parser/src/main.rs
CHANGED
|
@@ -164,7 +164,7 @@ pub fn parse_body(
|
|
|
164
164
|
owner_entities,
|
|
165
165
|
);
|
|
166
166
|
refuse_duplicate_targets(file, *pos, &command)?;
|
|
167
|
-
|
|
167
|
+
super::needs::refuse_undeclared(file, *pos, &command.name, &command.needs, &command.attributes)?;
|
|
168
168
|
return Ok((command, pending));
|
|
169
169
|
};
|
|
170
170
|
let line = gated.line.number;
|
|
@@ -206,27 +206,7 @@ pub fn parse_body(
|
|
|
206
206
|
canonical: canonical::apply(&raw),
|
|
207
207
|
});
|
|
208
208
|
}
|
|
209
|
-
"needs" =>
|
|
210
|
-
let fact = super::positional_symbol(file, line, "needs", &gated.args, 1)?;
|
|
211
|
-
if !NEEDABLE_FACTS.contains(&fact.as_str()) {
|
|
212
|
-
return Err(Diagnostic::new(
|
|
213
|
-
file,
|
|
214
|
-
line,
|
|
215
|
-
format!(
|
|
216
|
-
"{} needs :{fact}, which the runtime cannot supply — it supplies :now",
|
|
217
|
-
command.name
|
|
218
|
-
),
|
|
219
|
-
));
|
|
220
|
-
}
|
|
221
|
-
if command.needs.contains(&fact) {
|
|
222
|
-
return Err(Diagnostic::new(
|
|
223
|
-
file,
|
|
224
|
-
line,
|
|
225
|
-
format!("{} declares needs :{fact} twice", command.name),
|
|
226
|
-
));
|
|
227
|
-
}
|
|
228
|
-
command.needs.push(fact);
|
|
229
|
-
}
|
|
209
|
+
"needs" => super::needs::declare(file, line, &command.name, &gated.args, &mut command.needs)?,
|
|
230
210
|
"provenance" => {
|
|
231
211
|
let raw = super::named_raw(&gated.args, "from")
|
|
232
212
|
.ok_or_else(|| Diagnostic::new(file, line, "'provenance' requires a from:"))?;
|
|
@@ -319,28 +299,6 @@ fn state_ref(raw: &str) -> Option<String> {
|
|
|
319
299
|
Some(inner.to_string())
|
|
320
300
|
}
|
|
321
301
|
|
|
322
|
-
/// The outside facts a command may declare it needs; mirrors `CommandBuilder::NEEDABLE_FACTS`.
|
|
323
|
-
const NEEDABLE_FACTS: &[&str] = &["now"];
|
|
324
|
-
|
|
325
|
-
/// A need names the attribute the runtime fills, so the command must declare it.
|
|
326
|
-
fn refuse_undeclared_needs(file: &str, line: usize, command: &ir::Command) -> ParseResult<()> {
|
|
327
|
-
match command
|
|
328
|
-
.needs
|
|
329
|
-
.iter()
|
|
330
|
-
.find(|fact| !command.attributes.iter().any(|a| &a.name == *fact))
|
|
331
|
-
{
|
|
332
|
-
Some(fact) => Err(Diagnostic::new(
|
|
333
|
-
file,
|
|
334
|
-
line,
|
|
335
|
-
format!(
|
|
336
|
-
"{} needs :{fact} but declares no attribute :{fact} for the runtime to fill — add `attribute :{fact}, <type>`",
|
|
337
|
-
command.name
|
|
338
|
-
),
|
|
339
|
-
)),
|
|
340
|
-
None => Ok(()),
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
302
|
/// Refuses a field written twice: a command's effects are one update set (C4.2).
|
|
345
303
|
/// `delegates_to`/`corrects` name no field and are not counted.
|
|
346
304
|
fn refuse_duplicate_targets(file: &str, line: usize, command: &ir::Command) -> ParseResult<()> {
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
//! The `needs` word, shared by the two declarations that may use it: a command and a query
|
|
2
|
+
//! (ADR 0081). Mirrors `Hecks::Bluebook::DSL::NeedWord`.
|
|
3
|
+
|
|
4
|
+
use crate::diag::{Diagnostic, ParseResult};
|
|
5
|
+
use crate::ir;
|
|
6
|
+
use crate::parse::ArgumentGateResult;
|
|
7
|
+
|
|
8
|
+
/// The outside facts a declaration may need, each the name of the attribute the runtime fills;
|
|
9
|
+
/// mirrors `NeedWord::NEEDABLE_FACTS`. `now` is the clock port's reading in epoch seconds and
|
|
10
|
+
/// `today` the day it falls in, whole days since the epoch in UTC.
|
|
11
|
+
const NEEDABLE_FACTS: &[&str] = &["now", "today"];
|
|
12
|
+
|
|
13
|
+
/// Reads one `needs :fact` line into `needs`.
|
|
14
|
+
///
|
|
15
|
+
/// Refuses a fact the runtime cannot supply and a fact declared twice, as the Ruby builder does.
|
|
16
|
+
pub fn declare(
|
|
17
|
+
file: &str,
|
|
18
|
+
line: usize,
|
|
19
|
+
owner: &str,
|
|
20
|
+
args: &ArgumentGateResult,
|
|
21
|
+
needs: &mut Vec<String>,
|
|
22
|
+
) -> ParseResult<()> {
|
|
23
|
+
let fact = super::positional_symbol(file, line, "needs", args, 1)?;
|
|
24
|
+
if !NEEDABLE_FACTS.contains(&fact.as_str()) {
|
|
25
|
+
let supplied: Vec<String> = NEEDABLE_FACTS.iter().map(|known| format!(":{known}")).collect();
|
|
26
|
+
return Err(Diagnostic::new(
|
|
27
|
+
file,
|
|
28
|
+
line,
|
|
29
|
+
format!(
|
|
30
|
+
"{owner} needs :{fact}, which the runtime cannot supply — it supplies {}",
|
|
31
|
+
supplied.join(", ")
|
|
32
|
+
),
|
|
33
|
+
));
|
|
34
|
+
}
|
|
35
|
+
if needs.contains(&fact) {
|
|
36
|
+
return Err(Diagnostic::new(file, line, format!("{owner} declares needs :{fact} twice")));
|
|
37
|
+
}
|
|
38
|
+
needs.push(fact);
|
|
39
|
+
Ok(())
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// A need names the attribute the runtime fills, so the declaration must have one.
|
|
43
|
+
pub fn refuse_undeclared(
|
|
44
|
+
file: &str,
|
|
45
|
+
line: usize,
|
|
46
|
+
owner: &str,
|
|
47
|
+
needs: &[String],
|
|
48
|
+
attributes: &[ir::Attribute],
|
|
49
|
+
) -> ParseResult<()> {
|
|
50
|
+
match needs.iter().find(|fact| !attributes.iter().any(|a| &a.name == *fact)) {
|
|
51
|
+
Some(fact) => Err(Diagnostic::new(
|
|
52
|
+
file,
|
|
53
|
+
line,
|
|
54
|
+
format!(
|
|
55
|
+
"{owner} needs :{fact} but declares no attribute :{fact} for the runtime to fill — add `attribute :{fact}, <type>`"
|
|
56
|
+
),
|
|
57
|
+
)),
|
|
58
|
+
None => Ok(()),
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -25,6 +25,8 @@ pub fn parse_body(
|
|
|
25
25
|
|
|
26
26
|
loop {
|
|
27
27
|
let Some(gated) = super::next_line(file, lines, pos, "Query")? else {
|
|
28
|
+
let end = lines.get(pos.saturating_sub(1)).map_or(0, |l| l.number);
|
|
29
|
+
super::needs::refuse_undeclared(file, end, name, &query.needs, &query.attributes)?;
|
|
28
30
|
return Ok(query);
|
|
29
31
|
};
|
|
30
32
|
let line = gated.line.number;
|
|
@@ -83,6 +85,7 @@ pub fn parse_body(
|
|
|
83
85
|
type_name
|
|
84
86
|
});
|
|
85
87
|
}
|
|
88
|
+
"needs" => super::needs::declare(file, line, name, &gated.args, &mut query.needs)?,
|
|
86
89
|
word if OPTION_WORDS.contains(&word) => {
|
|
87
90
|
query_options::apply(file, line, word, &gated.args, &mut query.options)?
|
|
88
91
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//! Exemplar shapes for rust/
|
|
1
|
+
//! Exemplar shapes for rust/codegen/src/commands.rs (see mod.rs).
|
|
2
2
|
//!
|
|
3
3
|
//! `dispatch_fn` proves the outer `pub fn .. -> DispatchResult { .. dispatch(..) .. }` wrapper.
|
|
4
4
|
#![allow(dead_code, unused_variables)]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//! Exemplar shapes for rust/
|
|
1
|
+
//! Exemplar shapes for rust/codegen/src/constraints.rs (see mod.rs).
|
|
2
2
|
//!
|
|
3
3
|
//! Both shapes are single-line refusal checks spliced into a `Result`-returning body.
|
|
4
4
|
#![allow(dead_code, unused_variables)]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//! Exemplar shapes for rust/
|
|
1
|
+
//! Exemplar shapes for rust/codegen/src/fielded.rs (see mod.rs).
|
|
2
2
|
//!
|
|
3
3
|
//! Each `Fielded::field` arm shape is a standalone leaf so both outer skeletons can reuse it.
|
|
4
4
|
#![allow(dead_code, unused_variables)]
|
data/rust/src/exemplar/json.rs
CHANGED
data/rust/src/exemplar/mod.rs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Compile-checked Rust for every shape rust/
|
|
1
|
+
// Compile-checked Rust for every shape rust/codegen/src/*.rs emits; exemplar.rs slices the fenced
|
|
2
2
|
// `// TMPL:<id> BEGIN` / `END` regions and substitutes real names for the `tmpl_` vocabulary.
|
|
3
3
|
//
|
|
4
4
|
// `tmpl_*_host` functions only give fenced fragments real types and are never called, hence
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Exemplar shape for rust/
|
|
1
|
+
// Exemplar shape for rust/codegen/src/registry.rs's `emit_query_table`; see mod.rs.
|
|
2
2
|
#![allow(dead_code, unused_variables)]
|
|
3
3
|
|
|
4
4
|
// A `const` cannot call a function, so the row is a real literal substituted wholesale.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Exemplar shapes for rust/
|
|
1
|
+
// Exemplar shapes for rust/codegen/src/read_models.rs's `emit_read_model_table`
|
|
2
2
|
// — see mod.rs's own header. `ReadModelDef`/`ReadModelHead`/`FilteredHead`/
|
|
3
3
|
// `ReferenceField` are real kernel types (`rust/src/kernel/read_model.rs`).
|
|
4
4
|
#![allow(dead_code, unused_variables)]
|
data/rust/src/exemplar/types.rs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Exemplar shapes for rust/
|
|
1
|
+
// Exemplar shapes for rust/codegen/src/types.rs; see mod.rs.
|
|
2
2
|
// `closed_set_enum` is a one-field closed set (one variant per member); a multi-field closed
|
|
3
3
|
// set is a fixed data table instead.
|
|
4
4
|
#![allow(dead_code, unused_variables)]
|