Report component inventory failures together

Collect independent missing-region and rendered-code-preview errors before native capture so repairs do not require one model turn per omitted region. Preserve all rejection rules. Refresh the embedded review bundle from the unchanged source with pinned Bun 1.3.13.

AI-assisted implementation and validation by Codex under maintainer direction. Validation: cargo test --workspace; release build; IMPECCABLE_BIN=target/release/impeccable bun run test; bun run build.
This commit is contained in:
Paul Bakaus
2026-09-20 18:48:10 -07:00
parent bc426dd34f
commit d49867771f
3 changed files with 98 additions and 40 deletions
File diff suppressed because one or more lines are too long
@@ -121,13 +121,21 @@ pub fn freeze(project: &Path, input: &Value) -> Result<(Value, BTreeMap<String,
comp_files.insert(spec_path.into(), pin(project, spec_path, &mut files)?);
let spec: Value = serde_json::from_slice(&files[spec_path]).map_err(|e| e.to_string())?;
measured_regions = spec["regions"].as_array().ok_or("measured spec needs regions")?.clone();
// These checks are independent. Report the complete inventory repair in
// one response before any browser work, without publishing partial proof.
let mut errors = Vec::new();
for region in &measured_regions {
let component = input["components"].as_array().and_then(|items| items.iter().find(|c| c["id"] == region["id"]))
.ok_or_else(|| format!("component review omitted measured region {}", region["id"]))?;
if matches!(region["kind"].as_str(), Some("text" | "control")) && component["preview"]["kind"] != "page" {
return Err(format!("semantic region {} requires a rendered code preview", region["id"]));
match input["components"].as_array().and_then(|items| items.iter().find(|c| c["id"] == region["id"])) {
None => errors.push(format!("component review omitted measured region {}", region["id"])),
Some(component) if matches!(region["kind"].as_str(), Some("text" | "control")) && component["preview"]["kind"] != "page" => {
errors.push(format!("semantic region {} requires a rendered code preview", region["id"]));
}
_ => {}
}
}
if !errors.is_empty() {
return Err(errors.join("\n"));
}
}
view(&mut packet["comp"], project, &mut files, &mut comp_files)?;
@@ -170,8 +178,9 @@ pub fn freeze(project: &Path, input: &Value) -> Result<(Value, BTreeMap<String,
if let Some(group) = input["components"].as_array().and_then(|cs| cs.iter().find(|c| c["id"] == region["id"]))
.and_then(|c| c.get("reviewGroup")) { region["reviewGroup"] = group.clone(); }
}
if let Some((id, message)) = impeccable_comp::review_groups::issues(&measured_regions).first() {
return Err(format!("component {id}: {message}"));
let group_issues = impeccable_comp::review_groups::issues(&measured_regions);
if !group_issues.is_empty() {
return Err(group_issues.iter().map(|(id, message)| format!("component {id}: {message}")).collect::<Vec<_>>().join("\n"));
}
let mut ids = BTreeSet::new();
let components = packet["components"]
@@ -709,3 +709,18 @@ fn component_review_refuses_groups_that_mix_measured_roles() {
fs::write(&path, spec.to_string()).unwrap();
assert_eq!(manifest::freeze(&f.project,&input).unwrap().0["components"].as_array().unwrap().len(),3);
}
#[test]
fn measured_inventory_reports_all_independent_failures_before_capture() {
let f = Fixture::new();
fs::create_dir_all(f.project.join(".impeccable/build")).unwrap();
fs::write(f.project.join(".impeccable/build/spec.json"), br#"{"regions":[{"id":"missing-one","kind":"plate"},{"id":"control","kind":"control"},{"id":"missing-two","kind":"text"}]}"#).unwrap();
let mut input = f.manifest();
input["stage"] = json!("components");
input["components"][1]["preview"] = json!({"kind":"image","path":"art.png"});
let error = store::prepare(&f.store, &f.project, &input).unwrap_err();
assert!(error.contains("missing-one"));
assert!(error.contains("missing-two"));
assert!(error.contains("semantic region \"control\" requires a rendered code preview"));
assert!(!f.store.exists(), "invalid inventory must not publish a review");
}