Capture original review images and bind the measured inventory

Support static WebP and JPEG without conversion-cache evidence. Bind the measured spec centrally, require complete semantic component previews, and present the initial kit before automatic gate-driven repairs.

AI assistance: implemented and validated with OpenAI Codex.
This commit is contained in:
Paul Bakaus
2026-09-13 21:21:06 -07:00
parent 5b305ccede
commit ded752a5c7
47 changed files with 125 additions and 96 deletions
+3 -10
View File
@@ -23,15 +23,8 @@ fn source(view: &Value) -> Result<&str, String> {
.ok_or_else(|| "preview needs a pinned source".into())
}
fn material(png: &[u8], format: &str) -> Result<Value, String> {
if !impeccable_comp::png_io::is_png(png) || png.len() < 24 {
return Err("native review currently requires PNG raster assets".into());
}
let width = u32::from_be_bytes(png[16..20].try_into().unwrap());
let height = u32::from_be_bytes(png[20..24].try_into().unwrap());
if u64::from(width) * u64::from(height) > 32_000_000 {
return Err("preview exceeds 32 megapixels".into());
}
let image = impeccable_comp::png_io::decode_png(png)?.image;
let (image, source_format) = impeccable_comp::png_io::decode_review_image(png)?;
let format = if format == "Captured HTML / CSS / SVG" { format } else { source_format };
Ok(
json!({"format":format,"width":image.width,"height":image.height,"alpha":if image.data.chunks_exact(4).any(|p|p[3]<255){"transparent"}else{"opaque"}}),
)
@@ -158,7 +151,7 @@ impl ComponentCapturer for NativeComponentCapturer {
.ok_or("missing approved reference")?;
let reference_size = material(reference, "PNG")?;
if reference_size["width"] != width || reference_size["height"] != height {
return Err("comp dimensions do not match its PNG".into());
return Err("comp dimensions do not match its image".into());
}
let env = std::env::vars().collect();
let exe =
+45
View File
@@ -154,3 +154,48 @@ pub fn load_raster(file: &std::path::Path) -> Result<(Decoded, std::path::PathBu
let _ = std::fs::write(&cache, &bytes);
Ok((Decoded { image: img, text: HashMap::new() }, cache))
}
/// Decode a static review image from its pinned bytes, without consulting or
/// writing sibling conversion caches. Keep the original file as review evidence.
pub fn decode_review_image(bytes: &[u8]) -> Result<(Image, &'static str), String> {
if is_png(bytes) {
if bytes.len() < 24 { return Err("truncated PNG".into()); }
let width = u32::from_be_bytes(bytes[16..20].try_into().unwrap());
let height = u32::from_be_bytes(bytes[20..24].try_into().unwrap());
if u64::from(width) * u64::from(height) > 32_000_000 { return Err("preview exceeds 32 megapixels".into()); }
return Ok((decode_png(bytes)?.image, "PNG"));
}
let format = image::guess_format(bytes).map_err(|e| format!("unsupported review image: {e}"))?;
let name = match format {
image::ImageFormat::WebP => {
let decoder = image::codecs::webp::WebPDecoder::new(std::io::Cursor::new(bytes)).map_err(|e| e.to_string())?;
if decoder.has_animation() { return Err("animated WebP requires a static review state".into()); }
"WebP"
},
image::ImageFormat::Jpeg => "JPEG",
_ => return Err("review images must be static PNG, WebP or JPEG".into()),
};
let reader = image::ImageReader::with_format(std::io::Cursor::new(bytes), format);
let (width, height) = reader.into_dimensions().map_err(|e| e.to_string())?;
if u64::from(width) * u64::from(height) > 32_000_000 { return Err("preview exceeds 32 megapixels".into()); }
let rgba = image::load_from_memory_with_format(bytes, format).map_err(|e| e.to_string())?.to_rgba8();
Ok((Image {width: width as usize, height: height as usize, data: rgba.into_raw()}, name))
}
#[cfg(test)]
mod review_image_tests {
use super::*;
#[test]
fn review_webp_and_jpeg_decode_original_bytes_and_keep_alpha() {
let pixels = [25, 90, 65, 120, 200, 40, 35, 255];
let mut webp = Vec::new();
image::codecs::webp::WebPEncoder::new_lossless(&mut webp).encode(&pixels, 2, 1, image::ExtendedColorType::Rgba8).unwrap();
let (decoded, format) = decode_review_image(&webp).unwrap();
assert_eq!(format, "WebP"); assert_eq!(decoded.width, 2); assert_eq!(decoded.data, pixels);
let mut jpeg = Vec::new();
image::codecs::jpeg::JpegEncoder::new(&mut jpeg).encode(&[25, 90, 65], 1, 1, image::ExtendedColorType::Rgb8).unwrap();
let (decoded, format) = decode_review_image(&jpeg).unwrap();
assert_eq!(format, "JPEG"); assert_eq!(decoded.data[3], 255);
assert!(decode_review_image(b"not an image").is_err());
}
}
@@ -110,6 +110,21 @@ pub fn freeze(project: &Path, input: &Value) -> Result<(Value, BTreeMap<String,
}
let mut files = BTreeMap::new();
let mut comp_files = BTreeMap::new();
// The component inventory is measured against this spec. Bind it centrally
// rather than requiring every component author to repeat this dependency.
if input["stage"] == "components" {
let spec_path = ".impeccable/build/spec.json";
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())?;
for region in spec["regions"].as_array().ok_or("measured spec needs 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"]));
}
}
}
view(&mut packet["comp"], project, &mut files, &mut comp_files)?;
let mut ids = BTreeSet::new();
let components = packet["components"]
@@ -471,3 +471,21 @@ fn verify_requires_native_approval_and_current_manifest_and_dependencies() {
fs::write(f.project.join("shared.css"), b"changed after approval").unwrap();
assert!(super::verify::approved(&f.store,&f.project,"review.json").unwrap_err().contains("changed"));
}
#[test]
fn measured_inventory_is_bound_without_repeated_author_dependencies() {
let f = Fixture::new();
fs::create_dir_all(f.project.join(".impeccable/build")).unwrap();
let path = f.project.join(".impeccable/build/spec.json");
fs::write(&path, br#"{"regions":[{"id":"art","kind":"plate"},{"id":"control","kind":"control"}]}"#).unwrap();
let mut input = f.manifest(); input["stage"] = json!("components");
let dir = store::prepare(&f.store, &f.project, &input).unwrap();
let state = store::read(&dir.join("current.json")).unwrap();
assert!(state["sources"][".impeccable/build/spec.json"].is_string());
let mut missing = input.clone(); missing["components"].as_array_mut().unwrap().pop();
assert!(store::prepare(&f.store, &f.project, &missing).unwrap_err().contains("omitted measured region"));
let mut flattened = input; flattened["components"][1]["preview"] = json!({"kind":"image","path":"art.png"});
assert!(store::prepare(&f.store, &f.project, &flattened).unwrap_err().contains("rendered code preview"));
fs::write(path, br#"{"regions":[]}"#).unwrap();
assert!(store::sources_current(&state).is_err());
}