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
@@ -18,6 +18,8 @@ pub fn run(input: &str) -> String {
18
18
 
19
19
  // Optional `"needs"`: which commands need which outside facts (ADR 0081), passed by the host.
20
20
  super::needs::install(parsed.get("needs"));
21
+ // Optional `"query_needs"`: which queries need which outside facts, filled the same way.
22
+ super::needs::install_queries(parsed.get("query_needs"));
21
23
  // Optional `"defaults"`: each command's declared argument defaults, filled the same way.
22
24
  super::needs::install_defaults(parsed.get("defaults"));
23
25
 
@@ -86,6 +88,14 @@ pub fn run(input: &str) -> String {
86
88
  // A query step's `args` sit at the sibling `"args"` key, as for a command step.
87
89
  let empty_args = Json::Object(vec![]);
88
90
  let args = step.get("args").unwrap_or(&empty_args);
91
+ // What the caller offered is what the log echoes; a fact the query `needs` is answered
92
+ // for the run alone (ADR 0081), so the log holds no value that depends on the clock.
93
+ let offered = args;
94
+ let filled = match step.get("query") {
95
+ Some(Json::Str(question)) => super::needs::enrich_query(question, args, step.get("occurred_at").and_then(Json::as_str)),
96
+ _ => None,
97
+ };
98
+ let args = filled.as_ref().unwrap_or(args);
89
99
 
90
100
  // A `"query"` step carries no verb and takes one of three shapes:
91
101
  // - `"Domain::Aggregate.Query"`: a declared aggregate query, looked up in `QUERIES`.
@@ -106,7 +116,7 @@ pub fn run(input: &str) -> String {
106
116
  let rows = Json::Array(rows);
107
117
  query_results.push(Json::obj(vec![
108
118
  ("query", Json::Str(question.clone())),
109
- ("args", args.clone()),
119
+ ("args", offered.clone()),
110
120
  ("rows", rows.clone()),
111
121
  ("reference_rows", rows),
112
122
  ]));
@@ -115,7 +125,7 @@ pub fn run(input: &str) -> String {
115
125
  let message = refusal.to_string();
116
126
  query_results.push(Json::obj(vec![
117
127
  ("query", Json::Str(question.clone())),
118
- ("args", args.clone()),
128
+ ("args", offered.clone()),
119
129
  ("rows", Json::Null),
120
130
  ("error", Json::Str(message.clone())),
121
131
  ("reference_rows", Json::Null),
@@ -131,7 +141,7 @@ pub fn run(input: &str) -> String {
131
141
  let rows = Json::Array(entries.into_iter().map(|(id, record)| repository::row_json(id, record)).collect());
132
142
  query_results.push(Json::obj(vec![
133
143
  ("query", Json::Str(question.clone())),
134
- ("args", args.clone()),
144
+ ("args", offered.clone()),
135
145
  ("rows", rows.clone()),
136
146
  // Same as `rows`: one compiled path (named_query.rs).
137
147
  ("reference_rows", rows),
@@ -143,7 +153,7 @@ pub fn run(input: &str) -> String {
143
153
  let message = refusal.to_string();
144
154
  query_results.push(Json::obj(vec![
145
155
  ("query", Json::Str(question.clone())),
146
- ("args", args.clone()),
156
+ ("args", offered.clone()),
147
157
  ("rows", Json::Null),
148
158
  ("error", Json::Str(message.clone())),
149
159
  ("reference_rows", Json::Null),
@@ -159,7 +169,7 @@ pub fn run(input: &str) -> String {
159
169
  real declared query whose shape this generator's codegen doesn't cover yet (order_by/limit/\
160
170
  cursor/consistency/freshness/authorization/null_semantics/inspection/index_hints, a where \
161
171
  clause hopping through a reference, or a literal comparator value whose true JSON type \
162
- can't be recovered from the exported IR — rust/project/queries.rb's own header has the \
172
+ can't be recovered from the exported IR — rust/codegen/src/queries.rs's own header has the \
163
173
  full argument); the wheres-only, single-aggregate field-comparator subset and the ad hoc \
164
174
  filter shape ({{\"aggregate\",\"field\",\"op\",\"value\"}}) both execute for real"
165
175
  )),
@@ -170,7 +180,7 @@ pub fn run(input: &str) -> String {
170
180
  Ok(row) => {
171
181
  query_results.push(Json::obj(vec![
172
182
  ("query", Json::Str(question.clone())),
173
- ("args", args.clone()),
183
+ ("args", offered.clone()),
174
184
  ("rows", Json::Array(vec![row])),
175
185
  // No `reference_rows` key: Ruby sets it only for questions with a
176
186
  // reference twin, and a read model has none.
@@ -181,7 +191,7 @@ pub fn run(input: &str) -> String {
181
191
  let message = refusal.to_string();
182
192
  query_results.push(Json::obj(vec![
183
193
  ("query", Json::Str(question.clone())),
184
- ("args", args.clone()),
194
+ ("args", offered.clone()),
185
195
  ("rows", Json::Null),
186
196
  ("error", Json::Str(message)),
187
197
  ]));
@@ -195,7 +205,7 @@ pub fn run(input: &str) -> String {
195
205
  or a real declared read model whose shape this generator's codegen doesn't cover yet \
196
206
  (anything beyond a root aggregate fetched by reference id plus reference-matched sibling \
197
207
  heads — where/order_by/limit/offset/cursor/consistency/freshness/authorize(TenantScope)/\
198
- nulls/inspect_query/use_index — rust/project/read_models.rb's own header has the full \
208
+ nulls/inspect_query/use_index — rust/codegen/src/read_models.rs's own header has the full \
199
209
  argument, including why where/order_by/limit specifically can never be recovered from the \
200
210
  canonical IR at all); the named/declared AGGREGATE query form (\"Domain::Aggregate.Query\") \
201
211
  and the ad hoc filter shape ({{\"aggregate\",\"field\",\"op\",\"value\"}}) both execute for \
@@ -26,15 +26,16 @@ pub struct Need {
26
26
  thread_local! {
27
27
  static TABLE: RefCell<HashMap<String, Vec<Need>>> = RefCell::new(HashMap::new());
28
28
  static DEFAULTS: RefCell<HashMap<String, Vec<(String, Json)>>> = RefCell::new(HashMap::new());
29
+ static QUERY_TABLE: RefCell<HashMap<String, Vec<Need>>> = RefCell::new(HashMap::new());
29
30
  static FIXED_CLOCK: Cell<Option<i64>> = const { Cell::new(None) };
30
31
  }
31
32
 
32
- /// Installs the table the host passed (an absent or malformed `"needs"` installs an empty one).
33
- pub fn install(table: Option<&Json>) {
34
- let parsed = match table {
33
+ /// Reads a `{ name: [{fact, type}] }` table (an absent or malformed one reads as empty).
34
+ fn parse_table(table: Option<&Json>) -> HashMap<String, Vec<Need>> {
35
+ match table {
35
36
  Some(Json::Object(entries)) => entries
36
37
  .iter()
37
- .map(|(verb, needs)| {
38
+ .map(|(name, needs)| {
38
39
  let needs = needs
39
40
  .as_array()
40
41
  .unwrap_or(&[])
@@ -45,12 +46,23 @@ pub fn install(table: Option<&Json>) {
45
46
  Some(Need { fact, attribute_type })
46
47
  })
47
48
  .collect();
48
- (verb.clone(), needs)
49
+ (name.clone(), needs)
49
50
  })
50
51
  .collect(),
51
52
  _ => HashMap::new(),
52
- };
53
- TABLE.with(|t| *t.borrow_mut() = parsed);
53
+ }
54
+ }
55
+
56
+ /// Installs the table the host passed, command verb to its needs (an absent or malformed
57
+ /// `"needs"` installs an empty one).
58
+ pub fn install(table: Option<&Json>) {
59
+ TABLE.with(|t| *t.borrow_mut() = parse_table(table));
60
+ }
61
+
62
+ /// Installs the table of queries that need a fact, `"Domain::Aggregate.Query"` to its needs. Kept
63
+ /// apart from the commands': a command and a query may share a qualified name.
64
+ pub fn install_queries(table: Option<&Json>) {
65
+ QUERY_TABLE.with(|t| *t.borrow_mut() = parse_table(table));
54
66
  }
55
67
 
56
68
  /// Installs the declared defaults the host passed, `{ verb: { attribute: value } }` (an absent or
@@ -106,9 +118,11 @@ fn parse_iso8601_utc(text: &str) -> Option<i64> {
106
118
 
107
119
  fn answer(need: &Need, occurred_at: Option<&str>) -> Option<Json> {
108
120
  match need.fact.as_str() {
109
- "now" => {
121
+ // `now` is epoch seconds; `today` the day those fall in, whole days since the epoch (UTC).
122
+ "now" | "today" => {
110
123
  let secs = now_secs(occurred_at);
111
- Some(if need.attribute_type == "Integer" { Json::int(secs) } else { Json::obj(vec![("value", Json::int(secs))]) })
124
+ let answer = if need.fact == "today" { secs.div_euclid(86_400) } else { secs };
125
+ Some(if need.attribute_type == "Integer" { Json::int(answer) } else { Json::obj(vec![("value", Json::int(answer))]) })
112
126
  }
113
127
  _ => None,
114
128
  }
@@ -151,6 +165,27 @@ pub fn enrich(verb: &str, args: &Json, occurred_at: Option<&str>) -> Option<Json
151
165
  Some(Json::Object(envelope.iter().map(|(k, v)| if k == "with" { (k.clone(), Json::Object(filled.clone())) } else { (k.clone(), v.clone()) }).collect()))
152
166
  }
153
167
 
168
+ /// `args` with each fact the query `question` needs and the caller left out answered, or `None`
169
+ /// when nothing needed filling (the query needs nothing, or every fact is supplied). A query's
170
+ /// arguments are the flat object of its step.
171
+ pub fn enrich_query(question: &str, args: &Json, occurred_at: Option<&str>) -> Option<Json> {
172
+ let needs = QUERY_TABLE.with(|t| t.borrow().get(question).cloned())?;
173
+ let Json::Object(held) = args else { return None };
174
+ let mut filled = held.clone();
175
+ for need in &needs {
176
+ if held.iter().any(|(k, _)| k == &need.fact) {
177
+ continue;
178
+ }
179
+ if let Some(value) = answer(need, occurred_at) {
180
+ filled.push((need.fact.clone(), value));
181
+ }
182
+ }
183
+ if filled.len() == held.len() {
184
+ return None;
185
+ }
186
+ Some(Json::Object(filled))
187
+ }
188
+
154
189
  #[cfg(test)]
155
190
  mod tests {
156
191
  use super::*;
@@ -264,4 +299,64 @@ mod tests {
264
299
  install_defaults(None);
265
300
  assert_eq!(enrich("D::A.Start", &with(vec![]), None), None);
266
301
  }
302
+
303
+ fn query_table() -> Json {
304
+ Json::parse(
305
+ r#"{"D::A.Expired": [{"fact": "now", "type": "Integer"}],
306
+ "D::A.Today": [{"fact": "today", "type": "DayNumber"}]}"#,
307
+ )
308
+ .unwrap()
309
+ }
310
+
311
+ #[test]
312
+ fn a_needed_query_fact_the_caller_left_out_is_answered_by_the_clock() {
313
+ install_queries(Some(&query_table()));
314
+ fix_clock(Some(1_700_000_000));
315
+ let filled = enrich_query("D::A.Expired", &Json::obj(vec![]), None).unwrap();
316
+ assert_eq!(filled.get("now"), Some(&Json::int(1_700_000_000)));
317
+ fix_clock(None);
318
+ install_queries(None);
319
+ }
320
+
321
+ #[test]
322
+ fn a_query_value_the_caller_supplied_is_kept() {
323
+ install_queries(Some(&query_table()));
324
+ fix_clock(Some(1));
325
+ assert_eq!(enrich_query("D::A.Expired", &Json::obj(vec![("now", Json::int(7))]), None), None);
326
+ fix_clock(None);
327
+ install_queries(None);
328
+ }
329
+
330
+ #[test]
331
+ fn a_query_that_needs_nothing_is_untouched() {
332
+ install_queries(Some(&query_table()));
333
+ assert_eq!(enrich_query("D::A.Plain", &Json::obj(vec![]), None), None);
334
+ install_queries(None);
335
+ assert_eq!(enrich_query("D::A.Expired", &Json::obj(vec![]), None), None);
336
+ }
337
+
338
+ #[test]
339
+ fn a_command_and_a_query_of_one_name_keep_their_own_needs() {
340
+ install(Some(&table()));
341
+ install_queries(Some(&query_table()));
342
+ fix_clock(Some(5));
343
+ assert_eq!(enrich_query("D::A.Instant", &Json::obj(vec![]), None), None);
344
+ assert!(enrich("D::A.Instant", &with(vec![]), None).is_some());
345
+ fix_clock(None);
346
+ install(None);
347
+ install_queries(None);
348
+ }
349
+
350
+ #[test]
351
+ fn today_is_the_day_the_clock_falls_in_whole_days_since_the_epoch() {
352
+ install_queries(Some(&query_table()));
353
+ fix_clock(Some((19_000 * 86_400) + 86_399));
354
+ let filled = enrich_query("D::A.Today", &Json::obj(vec![]), None).unwrap();
355
+ assert_eq!(filled.get("today"), Some(&Json::obj(vec![("value", Json::int(19_000))])));
356
+ fix_clock(Some(19_001 * 86_400));
357
+ let next = enrich_query("D::A.Today", &Json::obj(vec![]), None).unwrap();
358
+ assert_eq!(next.get("today"), Some(&Json::obj(vec![("value", Json::int(19_001))])));
359
+ fix_clock(None);
360
+ install_queries(None);
361
+ }
267
362
  }
@@ -1,5 +1,5 @@
1
1
  //! Interprets a compiled `report` block (`ReadModelDef`) against a domain's generated
2
- //! `READ_MODELS` table — the hand-written counterpart to `rust/project/read_models.rb`.
2
+ //! `READ_MODELS` table — the hand-written counterpart to the retired Ruby generator's `read_models.rb`.
3
3
 
4
4
  use super::refusal_wording::{InvariantViolationGroupByCollisionArgs, NotFoundReadModelReferenceMissingArgs, UnauthorizedTenantRequiredArgs};
5
5
  use super::{named_query, query_comparators, query_ordering, repository, AggregateScan, Json, QueryCondition, QueryConditionValue, Refusal};
data/rust/src/lib.rs CHANGED
@@ -3,6 +3,6 @@
3
3
  pub mod generated;
4
4
  pub mod kernel;
5
5
 
6
- // Test-only: rust/project/exemplar.rb slices shapes out of this; never in a release build.
6
+ // Test-only: rust/codegen/src/exemplar.rs slices shapes out of this; never in a release build.
7
7
  #[cfg(test)]
8
8
  pub mod exemplar;
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hecks
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.0
4
+ version: 3.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Young
@@ -162,6 +162,7 @@ files:
162
162
  - lib/hecks/bluebook/dsl/identity_declaration.rb
163
163
  - lib/hecks/bluebook/dsl/lifecycle_builder.rb
164
164
  - lib/hecks/bluebook/dsl/malformed.rb
165
+ - lib/hecks/bluebook/dsl/need_word.rb
165
166
  - lib/hecks/bluebook/dsl/policy_builder.rb
166
167
  - lib/hecks/bluebook/dsl/port_builder.rb
167
168
  - lib/hecks/bluebook/dsl/port_operation_builder.rb
@@ -232,6 +233,7 @@ files:
232
233
  - lib/hecks/cli/interview_draft.rb
233
234
  - lib/hecks/cli/interview_run.rb
234
235
  - lib/hecks/cli/interview_session.rb
236
+ - lib/hecks/cli/interview_shape.rb
235
237
  - lib/hecks/cli/ir.rb
236
238
  - lib/hecks/cli/mcp.rb
237
239
  - lib/hecks/cli/model_check.rb
@@ -273,12 +275,15 @@ files:
273
275
  - lib/hecks/doors/launcher_options.rb
274
276
  - lib/hecks/doors/mcp_door.rb
275
277
  - lib/hecks/doors/mcp_door_scope.rb
278
+ - lib/hecks/doors/mcp_guide_cache.rb
276
279
  - lib/hecks/doors/ruby_door.rb
277
280
  - lib/hecks/doors/ruby_door/aggregate_door.rb
278
281
  - lib/hecks/doors/ruby_door/chapter.rb
279
282
  - lib/hecks/doors/usage_cache.rb
280
283
  - lib/hecks/embryonaut_bluebook.rb
281
284
  - lib/hecks/embryonaut_bluebook/lock.rb
285
+ - lib/hecks/embryonaut_bluebook/manifest.rb
286
+ - lib/hecks/embryonaut_bluebook/registry.rb
282
287
  - lib/hecks/embryonaut_bluebook/shape.rb
283
288
  - lib/hecks/embryonaut_bluebook/vendor.rb
284
289
  - lib/hecks/embryonaut_bluebook/vendor_cli.rb
@@ -386,10 +391,12 @@ files:
386
391
  - lib/hecks/hecks/adapters/deploy_toolchain.rb
387
392
  - lib/hecks/hecks/adapters/finding_github.adapter
388
393
  - lib/hecks/hecks/adapters/finding_github.rb
394
+ - lib/hecks/hecks/adapters/first_admin.rb
389
395
  - lib/hecks/hecks/adapters/git.adapter
390
396
  - lib/hecks/hecks/adapters/git.rb
391
397
  - lib/hecks/hecks/adapters/host_http.adapter
392
398
  - lib/hecks/hecks/adapters/host_http.rb
399
+ - lib/hecks/hecks/adapters/in_process_access.rb
393
400
  - lib/hecks/hecks/adapters/in_process_boot.adapter
394
401
  - lib/hecks/hecks/adapters/in_process_boot.rb
395
402
  - lib/hecks/hecks/adapters/in_process_operations.rb
@@ -545,14 +552,22 @@ files:
545
552
  - lib/hecks/projections.rb
546
553
  - lib/hecks/projections/bootstrap_table.rb
547
554
  - lib/hecks/projections/deploy/box.rb
555
+ - lib/hecks/projections/deploy/box/hosting.rb
556
+ - lib/hecks/projections/deploy/box/hosting_settings.rb
548
557
  - lib/hecks/projections/deploy/box/settings.rb
549
558
  - lib/hecks/projections/deploy/box/templates/box.yaml.tmpl
550
559
  - lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl
560
+ - lib/hecks/projections/deploy/box/templates/deploy-service.services-roll.part
561
+ - lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl
562
+ - lib/hecks/projections/deploy/box/templates/deploy-service.taskdef-roll.part
563
+ - lib/hecks/projections/deploy/box/templates/deploy-service.taskdef.part
551
564
  - lib/hecks/projections/deploy/box/templates/fetch-secrets.sh
565
+ - lib/hecks/projections/deploy/box/templates/hosting.mk.tmpl
552
566
  - lib/hecks/projections/deploy/box/templates/rds.yaml.tmpl
553
567
  - lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl
554
568
  - lib/hecks/projections/deploy/box/templates/render-compose.sh.tmpl
555
569
  - lib/hecks/projections/deploy/box/templates/restore-to-rds.sh.tmpl
570
+ - lib/hecks/projections/deploy/box/templates/smoke-after-deploy.sh.tmpl
556
571
  - lib/hecks/projections/deploy/box/templates/verify-copy.sh.tmpl
557
572
  - lib/hecks/projections/deploy/fargate.rb
558
573
  - lib/hecks/projections/deploy/fargate/assembly.rb
@@ -611,11 +626,13 @@ files:
611
626
  - lib/hecks/projections/site/edge/checks.rb
612
627
  - lib/hecks/projections/site/edge/pattern.rb
613
628
  - lib/hecks/projections/site/edge/rules.rb
629
+ - lib/hecks/projections/site/live_edge.rb
614
630
  - lib/hecks/projections/site/regions.rb
615
631
  - lib/hecks/projections/site/routes_ts.rb
616
632
  - lib/hecks/projections/site/site_cdn.rb
617
633
  - lib/hecks/projections/site/table.rb
618
634
  - lib/hecks/projections/site/table/checks.rb
635
+ - lib/hecks/projections/site/table/link_builder.rb
619
636
  - lib/hecks/projections/site/table/row_builder.rb
620
637
  - lib/hecks/projections/statements.rb
621
638
  - lib/hecks/projections/vocabulary.rb
@@ -732,6 +749,7 @@ files:
732
749
  - lib/hecks/runtime/interpreting.rb
733
750
  - lib/hecks/runtime/invocation.rb
734
751
  - lib/hecks/runtime/loader.rb
752
+ - lib/hecks/runtime/needs.rb
735
753
  - lib/hecks/runtime/outbox.rb
736
754
  - lib/hecks/runtime/policy_interpreter.rb
737
755
  - lib/hecks/runtime/port_operation_interpreter.rb
@@ -760,17 +778,16 @@ files:
760
778
  - lib/hecks/runtime/value/field_checks.rb
761
779
  - lib/hecks/runtime/value/invariant_violation.rb
762
780
  - lib/hecks/rust_build.rb
763
- - lib/hecks/rust_build/append_optionals.rb
764
781
  - lib/hecks/rust_build/conformance.rb
765
782
  - lib/hecks/rust_build/conformance_fuzz.rb
766
783
  - lib/hecks/rust_build/coverage.rb
767
784
  - lib/hecks/rust_build/domain_name.rb
785
+ - lib/hecks/rust_build/host.rb
768
786
  - lib/hecks/rust_build/kernel_input.rb
769
787
  - lib/hecks/rust_build/native_build.rb
770
788
  - lib/hecks/rust_build/project_rust.rb
771
789
  - lib/hecks/rust_build/project_rust/codegen_run.rb
772
790
  - lib/hecks/rust_build/project_rust/root_mod.rb
773
- - lib/hecks/rust_build/rust_literal.rb
774
791
  - lib/hecks/rust_build/wasm.rb
775
792
  - lib/hecks/rust_build/wasm_browser.rb
776
793
  - lib/hecks/rust_build/write_if_changed.rb
@@ -782,8 +799,6 @@ files:
782
799
  - lib/hecks/storehouse.rb
783
800
  - lib/hecks/tenancy/bluebook/tenancy.bluebook
784
801
  - lib/hecks/tenancy/bluebook/tenancy.hecksagon
785
- - lib/hecks/three_zero.rb
786
- - lib/hecks/three_zero/forms.yml
787
802
  - lib/hecks/tickets/bluebook/tickets.bluebook
788
803
  - lib/hecks/tickets/bluebook/tickets.hecksagon
789
804
  - lib/hecks/tickets/bluebook/tickets.ports.hecksagon
@@ -812,6 +827,7 @@ files:
812
827
  - lib/hecks/tools/site_routes.rb
813
828
  - lib/hecks/tools/tail_merge.rb
814
829
  - lib/hecks/tools/tenant_provisioning.rb
830
+ - lib/hecks/tools/tools_doc.rb
815
831
  - lib/hecks/tools/translation_audit.rb
816
832
  - lib/hecks/tools/translation_scaffold.rb
817
833
  - lib/hecks/vendoring.rb
@@ -833,15 +849,12 @@ files:
833
849
  - rust/build/Cargo.toml
834
850
  - rust/build/src/build_artifact.rs
835
851
  - rust/build/src/cargo_sync.rs
836
- - rust/build/src/json.rs
837
852
  - rust/build/src/lineage_pass.rs
838
853
  - rust/build/src/main.rs
839
- - rust/build/src/optional_pass.rs
840
854
  - rust/build/src/pipeline.rs
841
855
  - rust/build/src/reserved_names.rs
842
856
  - rust/build/src/resolve.rs
843
857
  - rust/build/src/root.rs
844
- - rust/build/src/sidecars.rs
845
858
  - rust/build/src/subprocess.rs
846
859
  - rust/build/src/tmp.rs
847
860
  - rust/codegen/.cargo/config.toml
@@ -865,6 +878,7 @@ files:
865
878
  - rust/codegen/src/manifest.rs
866
879
  - rust/codegen/src/mutations.rs
867
880
  - rust/codegen/src/naming.rs
881
+ - rust/codegen/src/optional_pass.rs
868
882
  - rust/codegen/src/ports.rs
869
883
  - rust/codegen/src/prelude.rs
870
884
  - rust/codegen/src/queries.rs
@@ -874,6 +888,7 @@ files:
874
888
  - rust/codegen/src/registry.rs
875
889
  - rust/codegen/src/reserved_names.rs
876
890
  - rust/codegen/src/shared.rs
891
+ - rust/codegen/src/sidecars.rs
877
892
  - rust/codegen/src/skip_reason.rs
878
893
  - rust/codegen/src/types.rs
879
894
  - rust/host/.cargo/config.toml
@@ -967,34 +982,13 @@ files:
967
982
  - rust/parser/src/parse/hecksagon.rs
968
983
  - rust/parser/src/parse/lifecycle.rs
969
984
  - rust/parser/src/parse/mod.rs
985
+ - rust/parser/src/parse/needs.rs
970
986
  - rust/parser/src/parse/policy.rs
971
987
  - rust/parser/src/parse/process_manager.rs
972
988
  - rust/parser/src/parse/query.rs
973
989
  - rust/parser/src/parse/read_model.rs
974
990
  - rust/parser/src/parse/value_object.rs
975
991
  - rust/parser/src/ruby_value.rs
976
- - rust/project.rb
977
- - rust/project/bridging.rb
978
- - rust/project/commands.rb
979
- - rust/project/constraints.rb
980
- - rust/project/dependency_planning.rb
981
- - rust/project/domain_generator.rb
982
- - rust/project/exemplar.rb
983
- - rust/project/expr_emitter.rb
984
- - rust/project/fielded.rb
985
- - rust/project/json_codec.rb
986
- - rust/project/mutations.rb
987
- - rust/project/naming.rb
988
- - rust/project/ports.rb
989
- - rust/project/queries.rb
990
- - rust/project/reactions.rb
991
- - rust/project/read_models.rb
992
- - rust/project/reference_specs.rb
993
- - rust/project/registry.rb
994
- - rust/project/skip_reason.rb
995
- - rust/project/types.rb
996
- - rust/project/write_if_changed.rb
997
- - rust/project_rust_pipeline.rb
998
992
  - rust/src/exemplar/commands.rs
999
993
  - rust/src/exemplar/constraints.rs
1000
994
  - rust/src/exemplar/fielded.rs
@@ -1,79 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Hecks
4
- module RustBuild
5
- # Makes an appended element's field `Option<T>` in the IR when any command sources it from an
6
- # optional argument, since Ruby stores the omitted nil without complaint. `ir.json` records
7
- # the marking, so it runs before the IR is written or generated from.
8
- #
9
- # `rust/build/src/optional_pass.rs` is the Ruby-free build's port of this; it looks only at an
10
- # aggregate's own value objects, where this reads the domain-wide set.
11
- module AppendOptionals
12
- # Marks every aggregate in the IR, in place.
13
- #
14
- # @param tree [Hash] a domain's IR, symbol-keyed
15
- # @return [Hash] the same IR
16
- def self.mark(tree)
17
- domain_wide = by_name(tree[:aggregates].flat_map { |aggregate| aggregate[:value_objects] })
18
- tree[:aggregates].each do |aggregate|
19
- mark_aggregate(aggregate, domain_wide.merge(by_name(aggregate[:value_objects])))
20
- end
21
- tree
22
- end
23
-
24
- # Resolves an `append` target to its entity or value object, or nil if it is neither. A
25
- # local entity wins over a domain-wide value object of the same name: Syntax's local
26
- # `Argument` entity shares a name with Command's `Argument` value object.
27
- #
28
- # @param aggregate [Hash] the aggregate owning the command
29
- # @param target_type [String] the appended-to attribute's element type
30
- # @param value_objects_by_name [Hash{String => Hash}] the value objects in scope
31
- # @return [Hash, nil] the element, or nil
32
- def self.element(aggregate, target_type, value_objects_by_name)
33
- aggregate[:entities].find { |entity| entity[:name] == target_type } ||
34
- value_objects_by_name[target_type]
35
- end
36
-
37
- # @param aggregate [Hash] one aggregate of the IR
38
- # @param value_objects_by_name [Hash{String => Hash}] the value objects in scope
39
- # @return [void]
40
- def self.mark_aggregate(aggregate, value_objects_by_name)
41
- aggregate[:attributes].each do |target_attr|
42
- target = element(aggregate, target_attr[:type], value_objects_by_name)
43
- next unless target
44
-
45
- aggregate[:commands].each do |command|
46
- appends = command[:mutations].select { |m| append_to?(m, target_attr) }
47
- appends.each { |mutation| mark_fields(mutation, command, target) }
48
- end
49
- end
50
- end
51
-
52
- def self.append_to?(mutation, target_attr)
53
- mutation[:op].to_s == "append" && mutation[:target].to_s == target_attr[:name].to_s
54
- end
55
- private_class_method :append_to?
56
-
57
- # A Symbol is an argument name, and an optional one is what this pass wants; anything
58
- # else is a literal.
59
- def self.mark_fields(mutation, command, target)
60
- mutation[:fields].each do |field_name, source|
61
- parsed = Hecks::Bluebook::Assembly::Marks.read(source)
62
- next unless parsed.is_a?(Symbol)
63
-
64
- source_attr = command[:attributes].find { |attr| attr[:name].to_s == parsed.to_s }
65
- next unless source_attr && source_attr[:optional]
66
-
67
- field_attr = target[:attributes].find { |attr| attr[:name].to_s == field_name.to_s }
68
- field_attr[:optional] = true if field_attr
69
- end
70
- end
71
- private_class_method :mark_fields
72
-
73
- def self.by_name(value_objects)
74
- value_objects.to_h { |value_object| [value_object[:name], value_object] }
75
- end
76
- private_class_method :by_name
77
- end
78
- end
79
- end
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Hecks
4
- module RustBuild
5
- # Rust string literals. Ruby's `String#inspect` escapes for Ruby's own read-back (a
6
- # brace-less `\uXXXX`, for one), which is not valid Rust syntax.
7
- module RustLiteral
8
- # Escapes only what Rust's string-literal grammar needs: backslash, double quote and
9
- # control characters.
10
- #
11
- # @param text [#to_s] the text to embed
12
- # @return [String] the quoted literal
13
- def self.string(text)
14
- escaped = text.to_s.each_char.map { |char| escape(char) }.join
15
- "\"#{escaped}\""
16
- end
17
-
18
- def self.escape(char)
19
- case char
20
- when "\\" then "\\\\"
21
- when "\"" then "\\\""
22
- when "\n" then "\\n"
23
- when "\r" then "\\r"
24
- when "\t" then "\\t"
25
- else
26
- code = char.ord
27
- code < 0x20 || code == 0x7F ? format("\\u{%x}", code) : char
28
- end
29
- end
30
- private_class_method :escape
31
- end
32
- end
33
- end