From 6c30e3d472dee088002921afecf66ff5ab437bb1 Mon Sep 17 00:00:00 2001 From: Pawel Date: Fri, 22 May 2026 13:35:33 +0200 Subject: [PATCH] =?UTF-8?q?fix(install):=20validate=20app=20before=20insta?= =?UTF-8?q?lling=20=E2=80=94=20refuse=20write-without-safety?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit aware app install now runs the same validation as aware app validate before copying any files. A write-mode node missing its required safety: block (E_APP_WRITE_WITHOUT_SAFETY) is surfaced at install time with the full issue list printed to stderr; nothing is written to ~/.aware on failure. Previously the invalid app installed cleanly and only failed much later at run pre-flight. Fixes #134 --- cli/src/commands/app.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/cli/src/commands/app.rs b/cli/src/commands/app.rs index 9185bd8c0..a141be156 100644 --- a/cli/src/commands/app.rs +++ b/cli/src/commands/app.rs @@ -492,6 +492,41 @@ fn install(ctx: &Context, spec: &str) -> Result<(), AwareError> { path.display() ))); } + + // Validate BEFORE copying anything — fail fast, write nothing on error + // (app-spec § Safety contract: "aware app validate refuses to install an + // app missing `safety:` on a write-mode node"; install must enforce the + // same contract as the standalone `validate` command, #134). + let src_manifest = std::fs::read_dir(&path)? + .flatten() + .map(|e| e.path()) + .find(|p| { + matches!( + p.extension().and_then(|e| e.to_str()), + Some("flo") | Some("app") + ) + }) + .ok_or_else(|| { + AwareError::Validation(format!("no .flo or .app file in {}", path.display())) + })?; + let src_app = crate::manifest::loader::load_app(&src_manifest)?; + let mut issues = crate::validate::validate_app(&src_app); + if let Ok(agents) = crate::manifest::loader::discover_agents(&ctx.paths) { + issues.extend(crate::validate::validate_app_safety(&src_app, &agents)); + } + if crate::validate::has_errors(&issues) { + for i in &issues { + let tag = match i.severity { + crate::validate::Severity::Error => "\u{2717}", + crate::validate::Severity::Warning => "\u{26a0}", + }; + eprintln!("{tag} [{}] {}", i.code, i.message); + } + return Err(AwareError::Validation( + "app install refused: app failed validation (fix errors above and retry)".into(), + )); + } + let app_id = crate::install::install_app_from_path(&path, &ctx.paths)?; // Locate the installed .flo / .app file