1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
//! render_health — typed measurement of rendered scaffold completeness.
//!
//! Per the operator's directive "everything we consume has running
//! tests, flows, and makes it all the way out to OSS with green CI":
//! this module measures the LOCAL prerequisites — manifest present,
//! auto-release workflow present, test stub present — for each
//! rendered scaffold.
//!
//! Composes with fidelity: fidelity scores how well consumption
//! PRESERVES the original; render-health scores how well the
//! rendered output is WIRED for the auto-release pipeline. Both
//! contribute to substrate-quality at scale.
use std::path::{Path, PathBuf};
/// Typed health snapshot of one rendered scaffold.
#[derive(Debug, Clone, Default)]
pub struct RenderHealth {
pub ecosystem: String,
/// Manifest file (Cargo.toml, package.json, etc.) is present.
pub has_manifest: bool,
/// Path to the located manifest, if any.
pub manifest_path: Option<PathBuf>,
/// Count of workflow files under .github/workflows/.
pub workflow_count: usize,
/// The substrate's typed auto-release shim is present.
pub has_auto_release: bool,
/// Count of test-surface files (per-ecosystem location).
pub test_count: usize,
/// Paths to detected test files (useful for operator drill-down).
pub test_paths: Vec<PathBuf>,
/// All workflow .yml files parse cleanly via serde_yaml. False if
/// ANY workflow file fails to parse — surfaces typed-emission
/// regressions in workflow renderers mechanically.
pub workflows_parse: bool,
/// Per-workflow parse outcomes — paths that failed to parse, with
/// the serde_yaml error message. Empty when workflows_parse is true.
pub workflow_parse_errors: Vec<(PathBuf, String)>,
/// The ecosystem's manifest file parses cleanly (where parseable):
/// - helm Chart.yaml + github-action action.yml: via serde_yaml
/// - others: trivially true (parser not yet wired)
/// False = substrate emitted a structurally-broken manifest.
pub manifest_parses: bool,
/// Parser error when manifest_parses is false.
pub manifest_parse_error: Option<String>,
}
impl RenderHealth {
/// True iff all five pipeline prereqs are present + valid: manifest
/// file present, manifest parses, auto-release workflow present,
/// at least one test file, AND every workflow .yml parses cleanly.
/// The minimum state required for the GitHub-Actions auto-release
/// loop to run + succeed.
pub fn is_pipeline_ready(&self) -> bool {
self.has_manifest && self.manifest_parses && self.has_auto_release
&& self.test_count > 0 && self.workflows_parse
}
/// Score in permille — 1000 = fully wired + valid for the auto-
/// release pipeline; 0 = nothing rendered. Five equal-weight gates.
pub fn score_permille(&self) -> i64 {
let mut score = 0_i64;
if self.has_manifest { score += 200; }
if self.manifest_parses { score += 200; }
if self.has_auto_release { score += 200; }
if self.test_count > 0 { score += 200; }
if self.workflows_parse { score += 200; }
score
}
}
/// Check a rendered-scaffold directory + return typed health.
pub fn check(rendered: &Path, ecosystem: &str) -> RenderHealth {
let mut h = RenderHealth {
ecosystem: ecosystem.to_string(),
workflows_parse: true, // assume true; flipped false on any failure
manifest_parses: true, // assume true; flipped false on parse fail
..Default::default()
};
// Manifest detection + parse validation per ecosystem.
let manifest_name = manifest_for(ecosystem);
if let Some(name) = manifest_name {
let path = rendered.join(name);
if path.is_file() {
h.has_manifest = true;
h.manifest_path = Some(path.clone());
// Per-ecosystem manifest parse — only YAML-shape manifests
// for now (Chart.yaml, action.yml). TOML/JSON parse gates
// arrive when those parser crates land as deps.
if let Ok(text) = std::fs::read_to_string(&path) {
let parse_result: Result<(), String> = match ecosystem {
"helm" | "github-action" =>
serde_yaml::from_str::<serde_yaml::Value>(&text)
.map(|_| ()).map_err(|e| e.to_string()),
"rust-single-crate" | "rust-workspace" |
"python" | "python-pdm" | "python-pipenv" =>
toml::from_str::<toml::Value>(&text)
.map(|_| ()).map_err(|e| e.to_string()),
"npm" | "js-pnpm" =>
serde_json::from_str::<serde_json::Value>(&text)
.map(|_| ()).map_err(|e| e.to_string()),
_ => Ok(()),
};
if let Err(e) = parse_result {
h.manifest_parses = false;
h.manifest_parse_error = Some(e);
}
}
}
}
// Workflow + auto-release detection + parse validation.
let wf_dir = rendered.join(".github").join("workflows");
if wf_dir.is_dir() {
if let Ok(entries) = std::fs::read_dir(&wf_dir) {
for entry in entries.flatten() {
let name = entry.file_name();
let name_str = name.to_string_lossy();
if name_str.ends_with(".yml") || name_str.ends_with(".yaml") {
h.workflow_count += 1;
if name_str == "auto-release.yml" || name_str == "auto-release.yaml" {
h.has_auto_release = true;
}
// Typed parse validation via serde_yaml — catches
// any structural YAML emission regression mechanically.
let path = entry.path();
if let Ok(text) = std::fs::read_to_string(&path) {
if let Err(e) = serde_yaml::from_str::<serde_yaml::Value>(&text) {
h.workflows_parse = false;
h.workflow_parse_errors.push((path, e.to_string()));
}
}
}
}
}
}
// Test-surface detection per ecosystem.
for test_glob in tests_for(ecosystem) {
let p = rendered.join(test_glob);
if p.is_file() {
h.test_count += 1;
h.test_paths.push(p);
}
}
h
}
/// Per-ecosystem manifest filename (the file whose presence ANCHORS
/// the rendered scaffold). Returns None for ecosystems we haven't
/// surveyed yet.
fn manifest_for(ecosystem: &str) -> Option<&'static str> {
Some(match ecosystem {
"rust-single-crate" | "rust-workspace" => "Cargo.toml",
"npm" | "js-pnpm" => "package.json",
"python" | "python-pdm" | "python-pipenv" => "pyproject.toml",
"helm" => "Chart.yaml",
"github-action" => "action.yml",
"nix-flake" => "flake.nix",
"go" => "go.mod",
"zig" => "build.zig",
"ruby-gem" => "Gemfile",
"elixir-mix" => "mix.exs",
"java-gradle-kts" => "build.gradle.kts",
"java-maven" => "pom.xml",
_ => return None,
})
}
/// Per-ecosystem test-surface filenames the substrate's renderer + its
/// GreenCiStarter actually emit. Multiple entries are OR'd — any one
/// present scores. Paths must match what `green_ci::starter_for(eco)`
/// writes; mismatch = false negative in pipeline-readiness reports.
fn tests_for(ecosystem: &str) -> &'static [&'static str] {
match ecosystem {
"rust-single-crate" | "rust-workspace" => &["src/lib.rs", "tests/smoke.rs"],
"npm" | "js-pnpm" => &["test/test.js", "test/index.test.js", "tests/smoke.js"],
"python" | "python-pdm" | "python-pipenv" => &["tests/test_smoke.py"],
"helm" => &["tests/example.yaml"],
"github-action" => &["run.tlisp"],
"nix-flake" => &["flake.nix"],
"go" => &["main_test.go", "smoke_test.go"],
"zig" => &["src/main.zig"],
"java-gradle-kts" => &["src/test/java/io/pleme/wrapper/SmokeTest.java"],
"java-maven" => &["src/test/java/io/pleme/wrapper/SmokeTest.java"],
_ => &[],
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
fn mk(files: &[(&str, &str)]) -> tempdir::TempDir {
let tmp = tempdir::TempDir::new("rh").unwrap();
for (rel, body) in files {
let p = tmp.path().join(rel);
if let Some(parent) = p.parent() { fs::create_dir_all(parent).unwrap(); }
fs::write(&p, body).unwrap();
}
tmp
}
#[test]
fn fully_wired_rust_scaffold_scores_1000() {
let dir = mk(&[
("Cargo.toml", "[package]\nname=\"x\"\n"),
(".github/workflows/auto-release.yml", "name: x\n"),
("src/lib.rs", "#[cfg(test)] mod t { #[test] fn s(){} }"),
]);
let h = check(dir.path(), "rust-single-crate");
assert!(h.has_manifest);
assert!(h.manifest_parses); // rust: not gated yet (TOML parser pending)
assert!(h.has_auto_release);
assert_eq!(h.test_count, 1);
assert!(h.is_pipeline_ready());
assert_eq!(h.score_permille(), 1000);
}
#[test]
fn malformed_action_yml_flips_manifest_parses_false() {
let dir = mk(&[
("action.yml", "name: x\nbroken: [unclosed\n"),
(".github/workflows/auto-release.yml", "name: x\n"),
("run.tlisp", "(noop)"),
]);
let h = check(dir.path(), "github-action");
assert!(h.has_manifest);
assert!(!h.manifest_parses,
"expected manifest_parses=false on malformed action.yml");
assert!(h.manifest_parse_error.is_some());
assert!(!h.is_pipeline_ready());
}
#[test]
fn missing_auto_release_breaks_pipeline_ready() {
let dir = mk(&[
("Cargo.toml", "[package]\nname=\"x\"\n"),
(".github/workflows/other.yml", "name: x\n"),
("src/lib.rs", "// stub"),
]);
let h = check(dir.path(), "rust-single-crate");
assert!(!h.has_auto_release);
assert_eq!(h.workflow_count, 1);
assert!(h.workflows_parse, "valid YAML should parse");
assert!(!h.is_pipeline_ready());
assert!(h.score_permille() < 1000);
}
#[test]
fn empty_dir_scores_only_parse_gates() {
// Empty dir has no workflows to fail + no manifest to fail →
// workflows_parse + manifest_parses both default true → two of
// five gates met → 400/1000.
let dir = mk(&[]);
let h = check(dir.path(), "rust-single-crate");
assert_eq!(h.score_permille(), 400);
assert!(!h.is_pipeline_ready());
}
#[test]
fn malformed_workflow_yaml_flips_parse_gate_false() {
let dir = mk(&[
("Cargo.toml", "[package]\nname=\"x\"\n"),
(".github/workflows/auto-release.yml", "name: x\nbroken: [unclosed\n"),
("src/lib.rs", "// stub"),
]);
let h = check(dir.path(), "rust-single-crate");
assert!(!h.workflows_parse,
"expected workflows_parse=false on malformed YAML");
assert!(!h.workflow_parse_errors.is_empty());
assert!(!h.is_pipeline_ready(),
"pipeline_ready should require workflows_parse");
}
#[test]
fn nix_flake_uses_flake_nix_as_test_surface() {
// Per the substrate's nix-flake model: the flake itself IS
// the surface; `nix flake check` is the runtime test.
let dir = mk(&[
("flake.nix", "{ description = \"x\"; outputs = ...; }"),
(".github/workflows/auto-release.yml", "name: x\n"),
]);
let h = check(dir.path(), "nix-flake");
assert!(h.has_manifest);
assert_eq!(h.test_count, 1); // flake.nix counts as its own test surface
assert!(h.is_pipeline_ready());
}
#[test]
fn unknown_ecosystem_returns_partial_zero_health() {
let dir = mk(&[
("Cargo.toml", "[package]\nname=\"x\"\n"),
]);
let h = check(dir.path(), "totally-unknown-eco");
assert!(!h.has_manifest); // no manifest_for entry → not checked
assert_eq!(h.test_count, 0);
}
}