hecks 3.0.5 → 3.1.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 (150) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +24 -12
  3. data/lib/hecks/adapters/driven/lambda/client.rb +1 -1
  4. data/lib/hecks/adapters/driven/lambda.rb +1 -1
  5. data/lib/hecks/adapters/driven/tenant_provisioner.rb +2 -2
  6. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +59 -32
  7. data/lib/hecks/bluebook/hexagon.rb +45 -25
  8. data/lib/hecks/bluebook/model_check.rb +7 -7
  9. data/lib/hecks/chapters.rb +31 -3
  10. data/lib/hecks/cli/check_era.rb +1 -1
  11. data/lib/hecks/cli/deploy_template_diff.rb +1 -1
  12. data/lib/hecks/cli/doc_coverage.rb +1 -1
  13. data/lib/hecks/cli/domain_stub.rb +128 -0
  14. data/lib/hecks/cli/interview_draft.rb +246 -0
  15. data/lib/hecks/cli/model_check.rb +1 -1
  16. data/lib/hecks/cli/project_cli.rb +51 -29
  17. data/lib/hecks/cli/reference.rb +2 -1
  18. data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +1 -1
  19. data/lib/hecks/cli/seed_semantics_corpus.rb +3 -3
  20. data/lib/hecks/cli/spec_example.rb +1 -1
  21. data/lib/hecks/cli/stress_concurrency_specs.rb +14 -5
  22. data/lib/hecks/corpus.rb +11 -8
  23. data/lib/hecks/deploy/bluebook/deploy.bluebook +85 -12
  24. data/lib/hecks/deploy/oidc.json +5 -0
  25. data/lib/hecks/doc/reference.rb +4 -3
  26. data/lib/hecks/doors/cli_door.rb +13 -13
  27. data/lib/hecks/doors/cli_runner.rb +52 -32
  28. data/lib/hecks/doors/launcher_options.rb +5 -5
  29. data/lib/hecks/doors/mcp_door.rb +40 -18
  30. data/lib/hecks/doors/mcp_door_scope.rb +244 -24
  31. data/lib/hecks/doors.rb +1 -1
  32. data/lib/hecks/embryonaut_bluebook.rb +2 -2
  33. data/lib/hecks/forms/banking_presentation.rb +1 -1
  34. data/lib/hecks/framework/bluebook/compliance.bluebook +1 -1
  35. data/lib/hecks/framework/bluebook/console_settings.bluebook +1 -1
  36. data/lib/hecks/framework/bluebook/framework.hecksagon +4 -4
  37. data/lib/hecks/framework.rb +1 -1
  38. data/lib/hecks/fuzzing/combination_miner.rb +1 -1
  39. data/lib/hecks/fuzzing/concurrent_dispatch.rb +1 -1
  40. data/lib/hecks/fuzzing/differential.rb +4 -2
  41. data/lib/hecks/fuzzing/era_boundary.rb +1 -1
  42. data/lib/hecks/fuzzing/form_census.rb +2 -2
  43. data/lib/hecks/fuzzing/generated_domain_check.rb +1 -1
  44. data/lib/hecks/fuzzing/isolated_boot.rb +10 -3
  45. data/lib/hecks/fuzzing/persistence_parity.rb +2 -2
  46. data/lib/hecks/fuzzing/target_capabilities.rb +3 -3
  47. data/lib/hecks/gate/stages.yml +5 -5
  48. data/lib/hecks/grammar/grammar.hecksagon +2 -2
  49. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +4 -4
  50. data/lib/hecks/hecks/adapters/host_http.rb +4 -3
  51. data/lib/hecks/hecks/adapters/local_files.rb +59 -0
  52. data/lib/hecks/hecks/adapters/site_toolchain.adapter +7 -0
  53. data/lib/hecks/hecks/adapters/site_toolchain.rb +54 -0
  54. data/lib/hecks/hecks/codebase.bluebook +4 -3
  55. data/lib/hecks/hecks/context_map.hecksagon +2 -2
  56. data/lib/hecks/hecks/custodian.bluebook +45 -3
  57. data/lib/hecks/hecks/hecks.hecksagon +10 -4
  58. data/lib/hecks/hecks/hecks.world +6 -6
  59. data/lib/hecks/hecks/site.behaviors +17 -0
  60. data/lib/hecks/hecks/site.world +10 -0
  61. data/lib/hecks/language/bluebook/bluebook.hecksagon +1 -1
  62. data/lib/hecks/language/hecksagon/hecksagon.bluebook +24 -47
  63. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +4 -4
  64. data/lib/hecks/projections/deploy/box/settings.rb +248 -0
  65. data/lib/hecks/projections/deploy/box/templates/box.yaml.tmpl +267 -0
  66. data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +70 -0
  67. data/lib/hecks/projections/deploy/box/templates/fetch-secrets.sh +21 -0
  68. data/lib/hecks/projections/deploy/box/templates/rds.yaml.tmpl +165 -0
  69. data/lib/hecks/projections/deploy/box/templates/render-compose.sh.tmpl +43 -0
  70. data/lib/hecks/projections/deploy/box.rb +387 -0
  71. data/lib/hecks/projections/deploy/fargate/cdn.rb +13 -1
  72. data/lib/hecks/projections/deploy/fargate.rb +1 -1
  73. data/lib/hecks/projections/deploy/lambda.rb +11 -10
  74. data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +1 -1
  75. data/lib/hecks/projections/diagrams.rb +2 -2
  76. data/lib/hecks/projections/site/edge/behaviours.rb +107 -0
  77. data/lib/hecks/projections/site/edge/checks.rb +137 -0
  78. data/lib/hecks/projections/site/edge/pattern.rb +50 -0
  79. data/lib/hecks/projections/site/edge/rules.rb +128 -0
  80. data/lib/hecks/projections/site/edge.rb +136 -0
  81. data/lib/hecks/projections/site/regions.rb +44 -0
  82. data/lib/hecks/projections/site/routes_ts.rb +284 -0
  83. data/lib/hecks/projections/site/site_cdn.rb +78 -0
  84. data/lib/hecks/projections/site/table/checks.rb +124 -0
  85. data/lib/hecks/projections/site/table/row_builder.rb +155 -0
  86. data/lib/hecks/projections/site/table.rb +98 -0
  87. data/lib/hecks/projections.rb +2 -0
  88. data/lib/hecks/projector/cli_projector.rb +122 -104
  89. data/lib/hecks/quality_control/adapters/agent.adapter +1 -1
  90. data/lib/hecks/quality_control/adapters/agent.rb +2 -2
  91. data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +3 -3
  92. data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +8 -8
  93. data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +3 -3
  94. data/lib/hecks/quality_control/cli/qa_generated_domains.rb +4 -4
  95. data/lib/hecks/quality_control/cli/qa_log_bug.rb +2 -2
  96. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +6 -5
  97. data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +2 -2
  98. data/lib/hecks/quality_control/cli/qa_postgres_role.rb +5 -5
  99. data/lib/hecks/quality_control/cli/qa_pr_check.rb +3 -3
  100. data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +3 -3
  101. data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +2 -2
  102. data/lib/hecks/quality_control/cli/qa_sweep.rb +10 -9
  103. data/lib/hecks/quality_control/cli/qa_tick.rb +3 -3
  104. data/lib/hecks/quality_control/quality_control.bluebook +18 -18
  105. data/lib/hecks/quality_control/quality_control.hecksagon +1 -1
  106. data/lib/hecks/runtime/registry/verification.rb +15 -29
  107. data/lib/hecks/runtime/registry.rb +14 -20
  108. data/lib/hecks/rust_build/append_optionals.rb +79 -0
  109. data/lib/hecks/rust_build/conformance_fuzz.rb +2 -1
  110. data/lib/hecks/rust_build/domain_name.rb +26 -0
  111. data/lib/hecks/rust_build/kernel_input.rb +58 -0
  112. data/lib/hecks/rust_build/project_rust/codegen_run.rb +143 -0
  113. data/lib/hecks/rust_build/project_rust/root_mod.rb +1 -1
  114. data/lib/hecks/rust_build/project_rust.rb +57 -24
  115. data/lib/hecks/rust_build/rust_literal.rb +33 -0
  116. data/lib/hecks/rust_build/write_if_changed.rb +86 -0
  117. data/lib/hecks/site/bluebook/site.bluebook +205 -0
  118. data/lib/hecks/site/bluebook/site.hecksagon +5 -0
  119. data/lib/hecks/site/bluebook/site.ports.hecksagon +15 -0
  120. data/lib/hecks/sme/bluebook/sme.bluebook +336 -0
  121. data/lib/hecks/sme/bluebook/sme.hecksagon +5 -0
  122. data/lib/hecks/storehouse.rb +37 -7
  123. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +1 -1
  124. data/lib/hecks/three_zero/forms.yml +88 -88
  125. data/lib/hecks/tools/ci_gates.rb +1 -1
  126. data/lib/hecks/tools/deploy_recipe.rb +14 -6
  127. data/lib/hecks/tools/oidc_manifests.rb +2 -1
  128. data/lib/hecks/tools/sequence_script.rb +2 -2
  129. data/lib/hecks/tools/site_routes.rb +152 -0
  130. data/lib/hecks/tools/tenant_provisioning.rb +1 -1
  131. data/lib/hecks/tools.rb +1 -0
  132. data/lib/hecks/version.rb +1 -1
  133. data/rust/build/src/cargo_sync.rs +1 -1
  134. data/rust/build/src/pipeline.rs +23 -16
  135. data/rust/build/src/resolve.rs +39 -6
  136. data/rust/host/HECKS_RELEASE +1 -1
  137. data/rust/host/src/api.rs +1 -1
  138. data/rust/host/src/dispatch.rs +87 -7
  139. data/rust/host/src/needs.rs +111 -0
  140. data/rust/parser/src/keywords.rs +7 -6
  141. data/rust/parser/src/main.rs +4 -3
  142. data/rust/parser/src/parse/chapter.rs +1 -1
  143. data/rust/parser/src/parse/hecksagon.rs +25 -10
  144. data/rust/project/mutations.rb +4 -30
  145. data/rust/project/naming.rb +8 -28
  146. data/rust/project/write_if_changed.rb +4 -80
  147. data/rust/project_rust_pipeline.rb +16 -12
  148. data/rust/src/kernel/cli.rs +2 -0
  149. data/rust/src/kernel/needs.rs +74 -1
  150. metadata +37 -1
@@ -1,83 +1,7 @@
1
- require "fileutils"
2
- require "set"
1
+ require "hecks/rust_build/write_if_changed"
3
2
 
4
- # Skips writes whose content is unchanged so file mtimes stay stable.
5
- # A rewrite alone forces Cargo to rebuild downstream crates.
6
3
  module RustProjection
7
- module WriteIfChanged
8
- module_function
9
-
10
- # Records every path written or confirmed unchanged inside the block, then deletes
11
- # files left in `dir` that were never touched (orphans of removed aggregates).
12
- # Every write is recorded against every open ancestor directory.
13
- # `push_directory`/`pop_and_prune` are the same operation split for non-adjacent writes.
14
- def track_directory(dir)
15
- push_directory(dir)
16
- yield
17
- ensure
18
- pop_and_prune(dir)
19
- end
20
-
21
- def push_directory(dir)
22
- FileUtils.mkdir_p(dir)
23
- stack.push([File.expand_path(dir), Set.new])
24
- end
25
-
26
- # Matches by directory, not LIFO: nested directories can close out of order.
27
- def pop_and_prune(dir)
28
- abs_dir = File.expand_path(dir)
29
- idx = stack.rindex { |tracked_dir, _| tracked_dir == abs_dir }
30
- return unless idx
31
-
32
- _dir, touched = stack.delete_at(idx)
33
- existing = Dir.glob(File.join(abs_dir, "*"))
34
- (existing - touched.to_a).each do |orphan|
35
- FileUtils.rm_rf(orphan)
36
- puts "pruned #{orphan} (no longer generated)"
37
- end
38
- end
39
-
40
- def stack
41
- Thread.current[:hecks_write_if_changed_stack] ||= []
42
- end
43
- private_class_method :stack
44
-
45
- def record_touch(path)
46
- abs = File.expand_path(path)
47
- stack.each do |dir, touched|
48
- touched << abs if abs.start_with?("#{dir}/")
49
- end
50
- end
51
- private_class_method :record_touch
52
-
53
- # Writes `content` to `path` unless it already holds identical bytes.
54
- # Returns whether it wrote.
55
- def call(path, content)
56
- record_touch(path)
57
- # `.b` on both sides: String#== is encoding-aware, so UTF-8 vs ASCII-8BIT
58
- # content can compare unequal even when the bytes are identical.
59
- return false if File.exist?(path) && File.binread(path) == content.b
60
-
61
- File.write(path, content)
62
- true
63
- end
64
-
65
- # Like `call`, for callers that build content by writing to an IO. Yields a sibling
66
- # temp file, then renames it into place only if the bytes differ. Returns whether it wrote.
67
- def block(path)
68
- record_touch(path)
69
- tmp = "#{path}.tmp-#{Process.pid}-#{rand(1_000_000)}"
70
- begin
71
- File.open(tmp, "w") { |f| yield f }
72
- if File.exist?(path) && FileUtils.compare_file(tmp, path)
73
- false
74
- else
75
- FileUtils.mv(tmp, path)
76
- true
77
- end
78
- ensure
79
- File.delete(tmp) if File.exist?(tmp)
80
- end
81
- end
82
- end
4
+ # The write-if-unchanged helper moved to `Hecks::RustBuild::WriteIfChanged`, which the
5
+ # codegen-backed `hecks project_rust` also uses; this name stays until this generator is deleted.
6
+ WriteIfChanged = Hecks::RustBuild::WriteIfChanged
83
7
  end
@@ -34,10 +34,10 @@ module RustProjectPipeline
34
34
  target_chapter_name = header_chapter_name(bluebook_paths.first)
35
35
  target_bluebooks = bluebook_paths.select { |path| header_chapter_name(path) == target_chapter_name }
36
36
 
37
- uses_framework_names, uses_embryonaut_bluebook_names =
37
+ gem_chapter_names, vendored_package_names =
38
38
  if hecksagon_path
39
39
  resolved = JSON.parse(run_capture!(PARSER_BIN, "resolve", "--chapter", target_chapter_name, hecksagon_path))
40
- [resolved.fetch("uses_framework"), resolved.fetch("uses_embryonaut_bluebook")]
40
+ [resolved.fetch("gem_chapters"), resolved.fetch("vendored_packages")]
41
41
  else
42
42
  [[], []]
43
43
  end
@@ -50,18 +50,22 @@ module RustProjectPipeline
50
50
 
51
51
  # Mirrors hecks project_rust's own Framework.load! resolution. A framework
52
52
  # member has no .hecksagon of its own, only its .bluebook.
53
- chapters = uses_framework_names.map do |fw_name|
53
+ # A gem chapter that is not a framework member (the language, Tenancy, Deploy) is loaded by
54
+ # the Ruby runtime alone, so it is checked against the one table and skipped here.
55
+ chapters = gem_chapter_names.filter_map do |fw_name|
56
+ next if Hecks::Chapters.index.key?(fw_name) && !Hecks::Framework.members.key?(fw_name)
57
+
54
58
  fw_path = Hecks::Framework.members.fetch(fw_name) do
55
- abort "hecks project_rust (Rust path): uses_framework #{fw_name.inspect} names no known framework member — known: #{Hecks::Framework.members.keys.sort.join(', ')}"
59
+ abort "hecks project_rust (Rust path): attaches #{fw_name.inspect} names no chapter the gem carries — known: #{Hecks::Chapters.table.keys.sort.join(', ')}"
56
60
  end
57
61
  fw_chapter_name = header_chapter_name(fw_path)
58
62
  unless fw_chapter_name == fw_name
59
- abort "hecks project_rust (Rust path): #{fw_path} declares chapter #{fw_chapter_name.inspect}, but uses_framework named #{fw_name.inspect}"
63
+ abort "hecks project_rust (Rust path): #{fw_path} declares chapter #{fw_chapter_name.inspect}, but attaches named #{fw_name.inspect}"
60
64
  end
61
65
  fw_ir_text = derive_append_optionals(run_capture!(PARSER_BIN, "chapter", "--chapter", fw_chapter_name, fw_path))
62
66
  {
63
67
  mod_name: fw_name.downcase,
64
- source_label: "#{domain} (uses_framework #{fw_name.inspect})",
68
+ source_label: "#{domain} (attaches #{fw_name.inspect})",
65
69
  ir_text: fw_ir_text
66
70
  }
67
71
  end
@@ -69,22 +73,22 @@ module RustProjectPipeline
69
73
  # Mirrors EmbryonautBluebook.load!: every .bluebook file directly under
70
74
  # vendor/embryonaut_bluebooks/<name>/bluebook/, sorted. A vendored
71
75
  # package has no .hecksagon of its own either, just its .bluebook(s).
72
- chapters += uses_embryonaut_bluebook_names.map do |pkg_name|
76
+ chapters += vendored_package_names.map do |pkg_name|
73
77
  unless pkg_name.to_s.match?(Hecks::EmbryonautBluebook::PACKAGE_NAME)
74
- abort "hecks project_rust (Rust path): uses_embryonaut_bluebook #{pkg_name.inspect} is not a package " \
78
+ abort "hecks project_rust (Rust path): attaches #{pkg_name.inspect}, from: :vendor is not a package " \
75
79
  "name — it must match [a-z][a-z0-9_]* (the name `hecks vendor` accepts)"
76
80
  end
77
81
  pkg_dir = File.join(domain, "vendor", "embryonaut_bluebooks", pkg_name.to_s, "bluebook")
78
82
  pkg_files = Dir.glob(File.join(pkg_dir, "*.bluebook")).sort
79
83
  if pkg_files.empty?
80
- abort "hecks project_rust (Rust path): uses_embryonaut_bluebook #{pkg_name.inspect} names no vendored " \
84
+ abort "hecks project_rust (Rust path): attaches #{pkg_name.inspect}, from: :vendor names no vendored " \
81
85
  "bluebook at #{pkg_dir} — run hecks vendor #{pkg_name}"
82
86
  end
83
87
  pkg_chapter_name = header_chapter_name(pkg_files.first)
84
88
  expected_chapter_name = Hecks::Naming.pascal(pkg_name.to_s)
85
89
  unless pkg_chapter_name == expected_chapter_name
86
90
  abort "hecks project_rust (Rust path): #{pkg_files.first} declares chapter #{pkg_chapter_name.inspect}, but " \
87
- "uses_embryonaut_bluebook #{pkg_name.inspect} expects #{expected_chapter_name.inspect}"
91
+ "attaches #{pkg_name.inspect}, from: :vendor expects #{expected_chapter_name.inspect}"
88
92
  end
89
93
  pkg_bluebooks = pkg_files.select { |path| header_chapter_name(path) == pkg_chapter_name }
90
94
  pkg_ir_text = derive_append_optionals(run_capture!(PARSER_BIN, "chapter", "--chapter", pkg_chapter_name, *pkg_bluebooks))
@@ -92,7 +96,7 @@ module RustProjectPipeline
92
96
  # Derived from the declared chapter name, not pkg_name.downcase
93
97
  # directly — the two diverge once a package name has an underscore.
94
98
  mod_name: expected_chapter_name.downcase,
95
- source_label: "#{domain} (uses_embryonaut_bluebook #{pkg_name.inspect})",
99
+ source_label: "#{domain} (attaches #{pkg_name.inspect}, from: :vendor)",
96
100
  ir_text: pkg_ir_text
97
101
  }
98
102
  end
@@ -294,7 +298,7 @@ module RustProjectPipeline
294
298
  f.puts "// chapter (governance/identity — no merged.rs of its own, so absent"
295
299
  f.puts "// from `domains`) stays unconditional: it has no single feature of its"
296
300
  f.puts "// own to gate behind, and is compiled in by whichever domain(s) attach"
297
- f.puts "// it via `uses_framework`."
301
+ f.puts "// it via `attaches`."
298
302
  all_dirs.each do |name|
299
303
  f.puts "#[cfg(feature = #{name.inspect})]" if domains.include?(name)
300
304
  f.puts "pub mod #{name};"
@@ -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 `"defaults"`: each command's declared argument defaults, filled the same way.
22
+ super::needs::install_defaults(parsed.get("defaults"));
21
23
 
22
24
  let steps = match parsed.get("steps").and_then(Json::as_array) {
23
25
  Some(s) => s,
@@ -25,6 +25,7 @@ pub struct Need {
25
25
 
26
26
  thread_local! {
27
27
  static TABLE: RefCell<HashMap<String, Vec<Need>>> = RefCell::new(HashMap::new());
28
+ static DEFAULTS: RefCell<HashMap<String, Vec<(String, Json)>>> = RefCell::new(HashMap::new());
28
29
  static FIXED_CLOCK: Cell<Option<i64>> = const { Cell::new(None) };
29
30
  }
30
31
 
@@ -52,6 +53,23 @@ pub fn install(table: Option<&Json>) {
52
53
  TABLE.with(|t| *t.borrow_mut() = parsed);
53
54
  }
54
55
 
56
+ /// Installs the declared defaults the host passed, `{ verb: { attribute: value } }` (an absent or
57
+ /// malformed `"defaults"` installs an empty table). They fill after the needs, so a fact that is
58
+ /// both needed and defaulted takes the answer.
59
+ pub fn install_defaults(table: Option<&Json>) {
60
+ let parsed = match table {
61
+ Some(Json::Object(entries)) => entries
62
+ .iter()
63
+ .filter_map(|(verb, attributes)| match attributes {
64
+ Json::Object(held) => Some((verb.clone(), held.clone())),
65
+ _ => None,
66
+ })
67
+ .collect(),
68
+ _ => HashMap::new(),
69
+ };
70
+ DEFAULTS.with(|t| *t.borrow_mut() = parsed);
71
+ }
72
+
55
73
  /// Makes the kernel clock answer `secs` on this thread until cleared; the test seam.
56
74
  pub fn fix_clock(secs: Option<i64>) {
57
75
  FIXED_CLOCK.with(|c| c.set(secs));
@@ -100,7 +118,11 @@ fn answer(need: &Need, occurred_at: Option<&str>) -> Option<Json> {
100
118
  /// needed filling (no needs, or every fact supplied). Reads `with` when the call carries one,
101
119
  /// else the flat object, as `CommandInvocation` does.
102
120
  pub fn enrich(verb: &str, args: &Json, occurred_at: Option<&str>) -> Option<Json> {
103
- let needs = TABLE.with(|t| t.borrow().get(verb).cloned())?;
121
+ let needs = TABLE.with(|t| t.borrow().get(verb).cloned()).unwrap_or_default();
122
+ let defaults = DEFAULTS.with(|t| t.borrow().get(verb).cloned()).unwrap_or_default();
123
+ if needs.is_empty() && defaults.is_empty() {
124
+ return None;
125
+ }
104
126
  let explicit = matches!(args.get("with"), Some(Json::Object(_)));
105
127
  let facts = if explicit { args.get("with")? } else { args };
106
128
  let Json::Object(held) = facts else { return None };
@@ -114,6 +136,11 @@ pub fn enrich(verb: &str, args: &Json, occurred_at: Option<&str>) -> Option<Json
114
136
  filled.push((need.fact.clone(), value));
115
137
  }
116
138
  }
139
+ for (name, value) in &defaults {
140
+ if !filled.iter().any(|(k, _)| k == name) {
141
+ filled.push((name.clone(), value.clone()));
142
+ }
143
+ }
117
144
  if filled.len() == before {
118
145
  return None;
119
146
  }
@@ -191,4 +218,50 @@ mod tests {
191
218
  assert_eq!(now_secs(Some("1970-01-01T00:00:00Z")), 0);
192
219
  assert!(now_secs(None) > 1_700_000_000);
193
220
  }
221
+
222
+ fn defaults() -> Json {
223
+ Json::parse(r#"{"D::A.Start": {"runs": 30}, "D::A.Instant": {"holder": "nobody"}}"#).unwrap()
224
+ }
225
+
226
+ #[test]
227
+ fn an_omitted_argument_takes_its_declared_default() {
228
+ install(None);
229
+ install_defaults(Some(&defaults()));
230
+ let filled = enrich("D::A.Start", &with(vec![("ref", Json::int(1))]), None).unwrap();
231
+ assert_eq!(filled.get("with").unwrap().get("runs"), Some(&Json::int(30)));
232
+ assert_eq!(filled.get("with").unwrap().get("ref"), Some(&Json::int(1)));
233
+ }
234
+
235
+ #[test]
236
+ fn a_supplied_argument_keeps_its_value_over_the_default() {
237
+ install(None);
238
+ install_defaults(Some(&defaults()));
239
+ assert_eq!(enrich("D::A.Start", &with(vec![("runs", Json::int(7))]), None), None);
240
+ }
241
+
242
+ #[test]
243
+ fn a_flat_invocation_is_filled_at_its_top_level() {
244
+ install(None);
245
+ install_defaults(Some(&defaults()));
246
+ let filled = enrich("D::A.Start", &Json::obj(vec![("ref", Json::int(1))]), None).unwrap();
247
+ assert_eq!(filled.get("runs"), Some(&Json::int(30)));
248
+ }
249
+
250
+ #[test]
251
+ fn a_needed_fact_is_answered_and_the_default_fills_beside_it() {
252
+ install(Some(&table()));
253
+ install_defaults(Some(&defaults()));
254
+ fix_clock(Some(3));
255
+ let filled = enrich("D::A.Instant", &with(vec![]), None).unwrap();
256
+ assert_eq!(filled.get("with").unwrap().get("now"), Some(&Json::obj(vec![("value", Json::int(3))])));
257
+ assert_eq!(filled.get("with").unwrap().get("holder"), Some(&Json::str("nobody")));
258
+ fix_clock(None);
259
+ }
260
+
261
+ #[test]
262
+ fn no_installed_defaults_leaves_a_command_untouched() {
263
+ install(None);
264
+ install_defaults(None);
265
+ assert_eq!(enrich("D::A.Start", &with(vec![]), None), None);
266
+ }
194
267
  }
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.0.5
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Young
@@ -215,9 +215,11 @@ files:
215
215
  - lib/hecks/cli/deploy_template_diff.rb
216
216
  - lib/hecks/cli/doc_coverage.rb
217
217
  - lib/hecks/cli/document.rb
218
+ - lib/hecks/cli/domain_stub.rb
218
219
  - lib/hecks/cli/expression_projection.rb
219
220
  - lib/hecks/cli/follow.rb
220
221
  - lib/hecks/cli/history.rb
222
+ - lib/hecks/cli/interview_draft.rb
221
223
  - lib/hecks/cli/ir.rb
222
224
  - lib/hecks/cli/mcp.rb
223
225
  - lib/hecks/cli/model_check.rb
@@ -393,6 +395,8 @@ files:
393
395
  - lib/hecks/hecks/adapters/rust_workspace.rb
394
396
  - lib/hecks/hecks/adapters/shell.adapter
395
397
  - lib/hecks/hecks/adapters/shell.rb
398
+ - lib/hecks/hecks/adapters/site_toolchain.adapter
399
+ - lib/hecks/hecks/adapters/site_toolchain.rb
396
400
  - lib/hecks/hecks/adapters/terminal.adapter
397
401
  - lib/hecks/hecks/adapters/terminal.rb
398
402
  - lib/hecks/hecks/codebase.behaviors
@@ -408,6 +412,8 @@ files:
408
412
  - lib/hecks/hecks/hecks.bluebook
409
413
  - lib/hecks/hecks/hecks.hecksagon
410
414
  - lib/hecks/hecks/hecks.world
415
+ - lib/hecks/hecks/site.behaviors
416
+ - lib/hecks/hecks/site.world
411
417
  - lib/hecks/ir.rb
412
418
  - lib/hecks/kernel_capabilities.rb
413
419
  - lib/hecks/language/adapter.bluebook
@@ -521,6 +527,13 @@ files:
521
527
  - lib/hecks/projection_files.rb
522
528
  - lib/hecks/projections.rb
523
529
  - lib/hecks/projections/bootstrap_table.rb
530
+ - lib/hecks/projections/deploy/box.rb
531
+ - lib/hecks/projections/deploy/box/settings.rb
532
+ - lib/hecks/projections/deploy/box/templates/box.yaml.tmpl
533
+ - lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl
534
+ - lib/hecks/projections/deploy/box/templates/fetch-secrets.sh
535
+ - lib/hecks/projections/deploy/box/templates/rds.yaml.tmpl
536
+ - lib/hecks/projections/deploy/box/templates/render-compose.sh.tmpl
524
537
  - lib/hecks/projections/deploy/fargate.rb
525
538
  - lib/hecks/projections/deploy/fargate/assembly.rb
526
539
  - lib/hecks/projections/deploy/fargate/cdn.rb
@@ -573,6 +586,17 @@ files:
573
586
  - lib/hecks/projections/reference.rb
574
587
  - lib/hecks/projections/rust_vocabulary.rb
575
588
  - lib/hecks/projections/shape.rb
589
+ - lib/hecks/projections/site/edge.rb
590
+ - lib/hecks/projections/site/edge/behaviours.rb
591
+ - lib/hecks/projections/site/edge/checks.rb
592
+ - lib/hecks/projections/site/edge/pattern.rb
593
+ - lib/hecks/projections/site/edge/rules.rb
594
+ - lib/hecks/projections/site/regions.rb
595
+ - lib/hecks/projections/site/routes_ts.rb
596
+ - lib/hecks/projections/site/site_cdn.rb
597
+ - lib/hecks/projections/site/table.rb
598
+ - lib/hecks/projections/site/table/checks.rb
599
+ - lib/hecks/projections/site/table/row_builder.rb
576
600
  - lib/hecks/projections/statements.rb
577
601
  - lib/hecks/projections/vocabulary.rb
578
602
  - lib/hecks/projector.rb
@@ -715,14 +739,25 @@ files:
715
739
  - lib/hecks/runtime/value/field_checks.rb
716
740
  - lib/hecks/runtime/value/invariant_violation.rb
717
741
  - lib/hecks/rust_build.rb
742
+ - lib/hecks/rust_build/append_optionals.rb
718
743
  - lib/hecks/rust_build/conformance.rb
719
744
  - lib/hecks/rust_build/conformance_fuzz.rb
720
745
  - lib/hecks/rust_build/coverage.rb
746
+ - lib/hecks/rust_build/domain_name.rb
747
+ - lib/hecks/rust_build/kernel_input.rb
721
748
  - lib/hecks/rust_build/native_build.rb
722
749
  - lib/hecks/rust_build/project_rust.rb
750
+ - lib/hecks/rust_build/project_rust/codegen_run.rb
723
751
  - lib/hecks/rust_build/project_rust/root_mod.rb
752
+ - lib/hecks/rust_build/rust_literal.rb
724
753
  - lib/hecks/rust_build/wasm.rb
725
754
  - lib/hecks/rust_build/wasm_browser.rb
755
+ - lib/hecks/rust_build/write_if_changed.rb
756
+ - lib/hecks/site/bluebook/site.bluebook
757
+ - lib/hecks/site/bluebook/site.hecksagon
758
+ - lib/hecks/site/bluebook/site.ports.hecksagon
759
+ - lib/hecks/sme/bluebook/sme.bluebook
760
+ - lib/hecks/sme/bluebook/sme.hecksagon
726
761
  - lib/hecks/storehouse.rb
727
762
  - lib/hecks/tenancy/bluebook/tenancy.bluebook
728
763
  - lib/hecks/tenancy/bluebook/tenancy.hecksagon
@@ -750,6 +785,7 @@ files:
750
785
  - lib/hecks/tools/regeneration_run.rb
751
786
  - lib/hecks/tools/rust_comment_style.rb
752
787
  - lib/hecks/tools/sequence_script.rb
788
+ - lib/hecks/tools/site_routes.rb
753
789
  - lib/hecks/tools/tail_merge.rb
754
790
  - lib/hecks/tools/tenant_provisioning.rb
755
791
  - lib/hecks/tools/translation_audit.rb