From 9a694b7ca959f47a585d19076c7b42741426b58b Mon Sep 17 00:00:00 2001 From: antony Date: Sun, 18 Oct 2026 19:58:07 +1000 Subject: [PATCH 1/2] fix(advisories): a Maven-only project is not "ready"; the Brief names what it did not check (W5a, follow-ups to #974) - Readiness counts an ecosystem only when an auditable row carries an installed version. The manifest scanner registers pom.xml / build.gradle dependencies with no version, a versionless dependency is never matched (#974), and those rows made "Maven" a required ecosystem: its sync succeeded and readiness said Ready for a project nothing was checked in. Now a Maven-only project reads NoInventory with its pom.xml named, and an npm + Maven machine reads Ready for npm with the Maven files named. The scanner keeps those rows for ACE context; the sync is unchanged. (Live DB: every versioned row is lockfile-backed npm/Rust, so the operator's readiness does not change.) - BriefFacts carries #974's not-covered report. The deterministic brief's all-clear reads "Nothing new touches the code 4DA checked today." and a "Not checked: ..." line names the files 4DA does not read and the lockfiles it could not read (three named, then "and N more"). A narrated brief's all-clear is rewritten the same way (idempotent), and the prompt tells the model the facts are complete only for the files read. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_015KSXWNneouUjTiVV3Rc3FH --- src-tauri/src/brief_facts.rs | 5 + src-tauri/src/brief_facts_build.rs | 1 + src-tauri/src/briefing_deterministic.rs | 110 +++++++++++++- src-tauri/src/briefing_deterministic_tests.rs | 141 ++++++++++++++++++ src-tauri/src/briefing_prompt.rs | 9 ++ src-tauri/src/osv/readiness_tests.rs | 64 ++++++++ src-tauri/src/osv/sync.rs | 25 +++- 7 files changed, 350 insertions(+), 5 deletions(-) diff --git a/src-tauri/src/brief_facts.rs b/src-tauri/src/brief_facts.rs index ae83d68af..1940f1592 100644 --- a/src-tauri/src/brief_facts.rs +++ b/src-tauri/src/brief_facts.rs @@ -270,6 +270,11 @@ pub(crate) struct BriefFacts { /// OS temp directory. With `NoInventory` that, not coverage, is why /// nothing was checked. pub advisory_temp_skipped: u32, + /// Dependency files the walk found but does not read installed versions + /// from (Maven, Gradle, NuGet ...), and lockfiles it could not read + /// (#974). Nothing in them was checked, so the brief never calls the + /// whole codebase clear while any exist (W5a). + pub not_covered: Option, } // ============================================================================ diff --git a/src-tauri/src/brief_facts_build.rs b/src-tauri/src/brief_facts_build.rs index 6d4ec7c39..744a7435d 100644 --- a/src-tauri/src/brief_facts_build.rs +++ b/src-tauri/src/brief_facts_build.rs @@ -51,6 +51,7 @@ pub(crate) fn build_brief_facts(db: &Database) -> BriefFacts { no_dependencies_known, advisories: Some(readiness.state), advisory_temp_skipped: readiness.skipped_temp, + not_covered: readiness.not_covered, } } diff --git a/src-tauri/src/briefing_deterministic.rs b/src-tauri/src/briefing_deterministic.rs index e5cd2a384..7552d9382 100644 --- a/src-tauri/src/briefing_deterministic.rs +++ b/src-tauri/src/briefing_deterministic.rs @@ -138,7 +138,12 @@ pub(crate) fn build_deterministic_brief(facts: &BriefFacts, reason: FloorReason) out.push_str(line); out.push_str("\n\n"); } else if new_security.is_empty() && new_upgrades.is_empty() && facts.worth_knowing.is_empty() { - out.push_str("Nothing new touches your code today.\n\n"); + out.push_str(all_clear_line(facts)); + out.push_str("\n\n"); + } + if let Some(line) = not_covered_line(facts) { + out.push_str(&line); + out.push_str("\n\n"); } let still: Vec = facts @@ -244,6 +249,79 @@ pub(crate) fn advisory_gap_line(facts: &BriefFacts) -> Option<&'static str> { }) } +/// The all-clear, scoped to what was checked: with files 4DA did not read, +/// "your code" would claim the parts it never looked at (W5a). +pub(crate) fn all_clear_line(facts: &BriefFacts) -> &'static str { + if facts.not_covered.is_some() { + "Nothing new touches the code 4DA checked today." + } else { + "Nothing new touches your code today." + } +} + +/// Most dependency files named in the not-covered line. +const NOT_COVERED_SHOWN: usize = 3; + +/// The project-relative tail of a path: `spring-petclinic/pom.xml`. +fn short_path(path: &str) -> String { + let parts: Vec<&str> = path.split(['/', '\\']).filter(|p| !p.is_empty()).collect(); + parts[parts.len().saturating_sub(2)..].join("/") +} + +/// What 4DA did not check, by name (#974's not-covered report): the files it +/// does not read installed versions from, and the lockfiles it could not +/// read. `None` when every file was read. +pub(crate) fn not_covered_line(facts: &BriefFacts) -> Option { + let nc = facts.not_covered.as_ref()?; + let mut parts: Vec = Vec::new(); + if nc.count > 0 { + let named: Vec = nc + .files + .iter() + .take(NOT_COVERED_SHOWN) + .map(|f| format!("{} ({})", short_path(&f.path), f.ecosystem)) + .collect(); + let more = (nc.count as usize).saturating_sub(named.len()); + let tail = if more > 0 { + format!(", and {more} more") + } else { + String::new() + }; + parts.push(format!( + "Not checked: {} dependency file{} 4DA does not read installed versions from: {}{tail}.", + nc.count, + if nc.count == 1 { "" } else { "s" }, + named.join(", ") + )); + } + if nc.unreadable_count > 0 { + let named: Vec = nc + .unreadable + .iter() + .take(NOT_COVERED_SHOWN) + .map(|f| format!("{} ({})", short_path(&f.path), f.reason)) + .collect(); + let more = (nc.unreadable_count as usize).saturating_sub(named.len()); + let tail = if more > 0 { + format!(", and {more} more") + } else { + String::new() + }; + parts.push(format!( + "{} lockfile{} could not be read, so what {} pinned was not checked: {}{tail}.", + nc.unreadable_count, + if nc.unreadable_count == 1 { "" } else { "s" }, + if nc.unreadable_count == 1 { + "it" + } else { + "they" + }, + named.join(", ") + )); + } + (!parts.is_empty()).then(|| parts.join(" ")) +} + /// A footer that names "OSV advisories" as a source only when the advisory /// check actually completed. fn with_honest_sources(footer: &str, facts: &BriefFacts) -> String { @@ -273,7 +351,7 @@ pub(crate) fn honest_without_lockfiles(content: &str, facts: &BriefFacts) -> Str } else { match advisory_gap_line(facts) { Some(line) => line, - None => return content.to_string(), + None => return with_not_covered(content, facts), } }; let kept: Vec<&str> = content @@ -291,6 +369,34 @@ pub(crate) fn honest_without_lockfiles(content: &str, facts: &BriefFacts) -> Str out } +/// A narrated brief while some files were not read (W5a): an all-clear the +/// model wrote is scoped to what was checked, and the brief ends on the line +/// that names what was not. Idempotent. +fn with_not_covered(content: &str, facts: &BriefFacts) -> String { + let Some(line) = not_covered_line(facts) else { + return content.to_string(); + }; + let kept: Vec = content + .lines() + .map(|l| { + if l.contains("Nothing new touches your code") { + all_clear_line(facts).to_string() + } else { + l.to_string() + } + }) + .collect(); + let mut out = kept.join("\n").trim_end().to_string(); + if !out.contains(&line) { + if !out.is_empty() { + out.push_str("\n\n"); + } + out.push_str(&line); + } + out.push('\n'); + out +} + /// Escape the characters that would change how an interpolated title renders /// as Markdown. LWN titles start "[$] ..." (subscriber-only), and inside a /// link label the bracket closed the label early, so the brief showed the raw diff --git a/src-tauri/src/briefing_deterministic_tests.rs b/src-tauri/src/briefing_deterministic_tests.rs index ce24b1e36..ab8ff9db6 100644 --- a/src-tauri/src/briefing_deterministic_tests.rs +++ b/src-tauri/src/briefing_deterministic_tests.rs @@ -366,3 +366,144 @@ fn a_narration_before_the_advisory_check_drops_its_all_clear() { assert!(prompt.contains("ADVISORY CHECK NOT COMPLETE"), "{prompt}"); assert!(!prompt.contains("none — no confirmed advisory"), "{prompt}"); } + +fn not_covered( + files: &[(&str, &str)], + unreadable: &[(&str, &str)], +) -> crate::osv::readiness::NotCovered { + crate::osv::readiness::NotCovered { + count: files.len() as u32, + files: files + .iter() + .map(|(path, eco)| crate::osv::readiness::NotCoveredFile { + path: (*path).to_string(), + ecosystem: (*eco).to_string(), + }) + .collect(), + unreadable_count: unreadable.len() as u32, + unreadable: unreadable + .iter() + .map(|(path, reason)| crate::osv::readiness::UnreadableFile { + path: (*path).to_string(), + reason: (*reason).to_string(), + }) + .collect(), + } +} + +fn ready_with_gaps(nc: crate::osv::readiness::NotCovered) -> BriefFacts { + BriefFacts { + advisories: Some(crate::osv::readiness::AdvisorySyncState::Ready), + not_covered: Some(nc), + ..BriefFacts::default() + } +} + +/// W5a: with a pom.xml 4DA does not read, "Nothing new touches your code +/// today" claimed code it never checked. The all-clear is scoped to what was +/// checked, and the files it did not check are named. +#[test] +fn the_all_clear_names_what_was_not_checked() { + let facts = ready_with_gaps(not_covered( + &[("C:/p/spring-petclinic/pom.xml", "Maven")], + &[], + )); + let out = build_deterministic_brief(&facts, FloorReason::NoCapableModel); + assert!(!out.contains("Nothing new touches your code"), "{out}"); + assert!( + out.contains("Nothing new touches the code 4DA checked today."), + "{out}" + ); + assert!( + out.contains( + "Not checked: 1 dependency file 4DA does not read installed versions from: \ + spring-petclinic/pom.xml (Maven)." + ), + "{out}" + ); +} + +#[test] +fn the_not_checked_line_counts_the_rest_and_names_unreadable_lockfiles() { + let facts = ready_with_gaps(not_covered( + &[ + ("C:/p/a/pom.xml", "Maven"), + ("C:/p/b/build.gradle", "Gradle"), + ("C:/p/c/build.gradle.kts", "Gradle"), + ("C:/p/d/packages.lock.json", "NuGet lockfile"), + ], + &[("C:/p/web/package-lock.json", "invalid JSON")], + )); + let line = super::not_covered_line(&facts).expect("a line"); + assert!(line.contains("4 dependency files"), "{line}"); + assert!( + line.contains("c/build.gradle.kts (Gradle), and 1 more."), + "{line}" + ); + assert!( + !line.contains("packages.lock.json"), + "only three are named: {line}" + ); + assert!( + line.contains( + "1 lockfile could not be read, so what it pinned was not checked: \ + web/package-lock.json (invalid JSON)." + ), + "{line}" + ); +} + +/// A narrated brief: the model's all-clear is scoped and the not-checked line +/// appended; running it twice changes nothing. +#[test] +fn a_narrated_all_clear_is_scoped_to_what_was_checked() { + let facts = ready_with_gaps(not_covered( + &[("C:/p/spring-petclinic/pom.xml", "Maven")], + &[], + )); + let draft = "## Worth knowing\n- Vite 7.1\n\nNothing new touches your code today.\n"; + let out = super::honest_without_lockfiles(draft, &facts); + assert!(!out.contains("Nothing new touches your code"), "{out}"); + assert!( + out.contains("Nothing new touches the code 4DA checked today."), + "{out}" + ); + assert!(out.contains("spring-petclinic/pom.xml (Maven)"), "{out}"); + assert_eq!(super::honest_without_lockfiles(&out, &facts), out); +} + +/// The model is told before it writes: complete only for what was read. +#[test] +fn the_prompt_says_not_everything_was_checked() { + let facts = ready_with_gaps(not_covered( + &[("C:/p/spring-petclinic/pom.xml", "Maven")], + &[], + )); + let prompt = crate::digest_commands::render_facts_for_prompt(&facts); + assert!( + prompt.starts_with("NOT EVERYTHING WAS CHECKED:"), + "{prompt}" + ); + assert!( + prompt.contains("spring-petclinic/pom.xml (Maven)"), + "{prompt}" + ); + assert!(!prompt.contains("authoritative and complete"), "{prompt}"); +} + +/// Every file read: the brief is as before. +#[test] +fn with_everything_read_the_all_clear_is_unchanged() { + let facts = BriefFacts { + advisories: Some(crate::osv::readiness::AdvisorySyncState::Ready), + ..BriefFacts::default() + }; + let out = build_deterministic_brief(&facts, FloorReason::NoCapableModel); + assert!( + out.contains("Nothing new touches your code today."), + "{out}" + ); + assert!(!out.contains("Not checked:"), "{out}"); + let draft = "Nothing new touches your code today.\n"; + assert_eq!(super::honest_without_lockfiles(draft, &facts), draft); +} diff --git a/src-tauri/src/briefing_prompt.rs b/src-tauri/src/briefing_prompt.rs index 88fe78ff8..c1ded08db 100644 --- a/src-tauri/src/briefing_prompt.rs +++ b/src-tauri/src/briefing_prompt.rs @@ -231,6 +231,15 @@ pub(crate) fn render_facts_for_prompt(facts: &BriefFacts) -> String { unaffected; the software appends the status line itself.\n\n\ FACTS (computed by 4DA from the user's lockfiles and package registries):\n\n", ) + } else if let Some(line) = crate::briefing_deterministic::not_covered_line(facts) { + // Complete for what was read, not for the files 4DA does not read (W5a). + format!( + "NOT EVERYTHING WAS CHECKED: {line} Say \"the code 4DA checked\", never \"your code\", \ + for any all-clear, and never call the user's whole codebase unaffected; the software \ + appends the not-checked line itself.\n\n\ + FACTS (computed by 4DA from the user's lockfiles, OSV advisories and package \ + registries; authoritative for the files it read):\n\n" + ) } else { String::from( "FACTS (computed by 4DA from the user's lockfiles, OSV advisories and package \ diff --git a/src-tauri/src/osv/readiness_tests.rs b/src-tauri/src/osv/readiness_tests.rs index b4daf9c1f..bec6121bd 100644 --- a/src-tauri/src/osv/readiness_tests.rs +++ b/src-tauri/src/osv/readiness_tests.rs @@ -271,3 +271,67 @@ fn readiness_has_no_not_covered_when_every_file_was_read() { .unwrap(); assert_eq!(old.not_covered, None); } + +const MAVEN_REPORT: &str = r#"{"lockfiles_read":0,"unsupported_count":1,"unsupported":[ + {"path":"C:/p/spring-petclinic/pom.xml","reason":"Maven (installed versions not resolved)"}]}"#; + +/// W5a: the manifest scanner registers a pom.xml's dependencies with no +/// version, and a versionless dependency is never matched (#974). They made +/// "Maven" a required ecosystem, its sync succeeded, and readiness said Ready +/// for a project nothing was checked in. Only a VERSIONED row makes an +/// ecosystem required: a Maven-only project has no inventory, and the +/// not-covered list names its pom.xml. +#[test] +fn a_maven_only_project_has_nothing_checked_and_names_its_pom() { + let db = crate::test_utils::test_db(); + db.set_kv(crate::ace::lockfile::report::REPORT_KV_KEY, MAVEN_REPORT) + .unwrap(); + db.store_dependency( + "/spring-petclinic", + "org.springframework.boot:spring-boot-starter-web", + None, + "java", + false, + None, + ) + .unwrap(); + db.update_osv_sync_status("Maven", 0, None).unwrap(); + + let r = advisory_sync(&db); + assert_eq!( + r.state, + AdvisorySyncState::NoInventory, + "nothing was checked" + ); + assert_eq!(r.ecosystems, 0); + let nc = r.not_covered.expect("the pom.xml is named"); + assert_eq!(nc.files[0].ecosystem, "Maven"); + assert!(nc.files[0].path.ends_with("pom.xml")); +} + +/// The read part of a mixed machine is still Ready, and the Maven part is +/// named, not counted as covered. +#[test] +fn npm_and_maven_read_ready_for_npm_and_name_the_maven_files() { + let db = crate::test_utils::test_db(); + db.set_kv(crate::ace::lockfile::report::REPORT_KV_KEY, MAVEN_REPORT) + .unwrap(); + db.store_dependency("/web", "minimist", Some("1.2.5"), "javascript", false, None) + .unwrap(); + db.store_dependency( + "/spring-petclinic", + "org.springframework.boot:spring-boot-starter-web", + None, + "java", + false, + None, + ) + .unwrap(); + db.update_osv_sync_status("npm", 0, None).unwrap(); + + let r = advisory_sync(&db); + assert_eq!(r.state, AdvisorySyncState::Ready); + assert_eq!(r.ecosystems, 1, "only npm is required: {r:?}"); + let nc = r.not_covered.expect("the pom.xml is named"); + assert_eq!(nc.files[0].ecosystem, "Maven"); +} diff --git a/src-tauri/src/osv/sync.rs b/src-tauri/src/osv/sync.rs index defb7be19..71b83381f 100644 --- a/src-tauri/src/osv/sync.rs +++ b/src-tauri/src/osv/sync.rs @@ -128,11 +128,30 @@ fn auditable_packages_by_ecosystem(db: &Database) -> Result Result> { - let mut ecosystems: Vec = auditable_packages_by_ecosystem(db)?.into_keys().collect(); + let mut deps = db + .get_auditable_user_dependencies() + .map_err(|e| FourDaError::Internal(format!("Failed to read dependencies: {e}")))?; + deps.extend( + db.get_auditable_scanned_dependencies().map_err(|e| { + FourDaError::Internal(format!("Failed to read scanned dependencies: {e}")) + })?, + ); + let mut ecosystems: Vec = deps + .iter() + .filter(|d| d.version.as_deref().is_some_and(|v| !v.trim().is_empty())) + .filter_map(|d| normalize_supported_to_osv(&d.ecosystem)) + .map(str::to_string) + .collect(); ecosystems.sort(); + ecosystems.dedup(); Ok(ecosystems) } From 30a2a4fc84a530b1d8c77e6e3ad331804ed817b6 Mon Sep 17 00:00:00 2001 From: antony Date: Sun, 18 Oct 2026 22:27:28 +1000 Subject: [PATCH 2/2] fix(advisories): review of #987: a Maven-only brief names its pom in every branch; NoInventory says no installed versions were read; cold start is not "nothing to check" - B1: honest_without_lockfiles and the prompt's advisory-gap branch name the not-covered files too. A Maven-only project is NoInventory, so it took the gap branch and the narrated brief never named its pom.xml. - B2: NoInventory now says what it means: 4DA read no installed versions (a manifest without a lockfile, or a format it does not read such as Maven or Gradle). "No ecosystem it can check" was false: OSV covers Maven, and a package.json with no lockfile is npm. Rust and all 13 locales (preemption.advisories.uncovered.*, firstRun.advisory.uncovered). - S3: cold start. Before the first lockfile walk reports, every manifest row is versionless; readiness reads NotStarted / Syncing, not NoInventory (auditable_coverage tells "not read yet" from "nothing checkable"). - S4: the brief reuse fingerprint carries the not-covered and unreadable counts. - S5: scoping the all-clear replaces the phrase, so bullets and second sentences survive. - name_list helper for the two named lists. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_015KSXWNneouUjTiVV3Rc3FH --- src-tauri/src/brief_facts_build.rs | 9 ++ src-tauri/src/briefing_deterministic.rs | 82 +++++++++++-------- src-tauri/src/briefing_deterministic_tests.rs | 60 +++++++++++++- src-tauri/src/briefing_prompt.rs | 7 +- src-tauri/src/osv/readiness.rs | 17 +++- src-tauri/src/osv/readiness_tests.rs | 20 +++++ src-tauri/src/osv/sync.rs | 40 ++++++--- src/locales/ar/ui.json | 6 +- src/locales/de/ui.json | 6 +- src/locales/en/ui.json | 6 +- src/locales/es/ui.json | 6 +- src/locales/fr/ui.json | 6 +- src/locales/hi/ui.json | 6 +- src/locales/it/ui.json | 6 +- src/locales/ja/ui.json | 6 +- src/locales/ko/ui.json | 6 +- src/locales/pt-BR/ui.json | 6 +- src/locales/ru/ui.json | 6 +- src/locales/tr/ui.json | 6 +- src/locales/zh/ui.json | 6 +- 20 files changed, 221 insertions(+), 92 deletions(-) diff --git a/src-tauri/src/brief_facts_build.rs b/src-tauri/src/brief_facts_build.rs index 744a7435d..5cd9485e0 100644 --- a/src-tauri/src/brief_facts_build.rs +++ b/src-tauri/src/brief_facts_build.rs @@ -37,6 +37,15 @@ pub(crate) fn build_brief_facts(db: &Database) -> BriefFacts { &fingerprint(&security, &also_open, &upgrades), Some(readiness.state), ); + // What was not covered is part of the brief (W5a): a newly unread file + // must not reuse yesterday's text (#987 review S4). + let fingerprint = match &readiness.not_covered { + Some(nc) => format!( + "{fingerprint}:not_covered={}/{}", + nc.count, nc.unreadable_count + ), + None => fingerprint, + }; // A failed read keeps the old behaviour (dependencies assumed known) // rather than telling a scanned user to scan. let no_dependencies_known = crate::open_db_connection() diff --git a/src-tauri/src/briefing_deterministic.rs b/src-tauri/src/briefing_deterministic.rs index 7552d9382..7f4abeb07 100644 --- a/src-tauri/src/briefing_deterministic.rs +++ b/src-tauri/src/briefing_deterministic.rs @@ -242,9 +242,14 @@ pub(crate) fn advisory_gap_line(facts: &BriefFacts) -> Option<&'static str> { copies and does not check against vulnerability advisories, so nothing here means \ they are clear. Move them out of the temp folder and scan again to check them." } + // No installed version was read (#987 review B2): OSV covers Maven, and + // a package.json with no lockfile is npm, so "no ecosystem it can + // check" was false for what reaches this arm. The not-covered line + // that follows names the files. S::NoInventory => { - "None of the dependencies 4DA read are in an ecosystem it can check against \ - vulnerability advisories, so nothing here means they are clear." + "4DA found dependencies but read no installed versions for them (a manifest without a \ + lockfile, or a format it does not read such as Maven or Gradle), so nothing was \ + checked against vulnerability advisories and nothing here means they are clear." } }) } @@ -268,6 +273,22 @@ fn short_path(path: &str) -> String { parts[parts.len().saturating_sub(2)..].join("/") } +/// `a (x), b (y), c (z), and N more` for `total` entries of which `named` are +/// shown (at most [`NOT_COVERED_SHOWN`]). +fn name_list<'a>(named: impl Iterator, total: u32) -> String { + let shown: Vec = named + .take(NOT_COVERED_SHOWN) + .map(|(path, what)| format!("{} ({what})", short_path(path))) + .collect(); + let more = (total as usize).saturating_sub(shown.len()); + let tail = if more > 0 { + format!(", and {more} more") + } else { + String::new() + }; + format!("{}{tail}", shown.join(", ")) +} + /// What 4DA did not check, by name (#974's not-covered report): the files it /// does not read installed versions from, and the lockfiles it could not /// read. `None` when every file was read. @@ -275,40 +296,27 @@ pub(crate) fn not_covered_line(facts: &BriefFacts) -> Option { let nc = facts.not_covered.as_ref()?; let mut parts: Vec = Vec::new(); if nc.count > 0 { - let named: Vec = nc - .files - .iter() - .take(NOT_COVERED_SHOWN) - .map(|f| format!("{} ({})", short_path(&f.path), f.ecosystem)) - .collect(); - let more = (nc.count as usize).saturating_sub(named.len()); - let tail = if more > 0 { - format!(", and {more} more") - } else { - String::new() - }; + let named = name_list( + nc.files + .iter() + .map(|f| (f.path.as_str(), f.ecosystem.as_str())), + nc.count, + ); parts.push(format!( - "Not checked: {} dependency file{} 4DA does not read installed versions from: {}{tail}.", + "Not checked: {} dependency file{} 4DA does not read installed versions from: {named}.", nc.count, if nc.count == 1 { "" } else { "s" }, - named.join(", ") )); } if nc.unreadable_count > 0 { - let named: Vec = nc - .unreadable - .iter() - .take(NOT_COVERED_SHOWN) - .map(|f| format!("{} ({})", short_path(&f.path), f.reason)) - .collect(); - let more = (nc.unreadable_count as usize).saturating_sub(named.len()); - let tail = if more > 0 { - format!(", and {more} more") - } else { - String::new() - }; + let named = name_list( + nc.unreadable + .iter() + .map(|f| (f.path.as_str(), f.reason.as_str())), + nc.unreadable_count, + ); parts.push(format!( - "{} lockfile{} could not be read, so what {} pinned was not checked: {}{tail}.", + "{} lockfile{} could not be read, so what {} pinned was not checked: {named}.", nc.unreadable_count, if nc.unreadable_count == 1 { "" } else { "s" }, if nc.unreadable_count == 1 { @@ -316,7 +324,6 @@ pub(crate) fn not_covered_line(facts: &BriefFacts) -> Option { } else { "they" }, - named.join(", ") )); } (!parts.is_empty()).then(|| parts.join(" ")) @@ -365,6 +372,12 @@ pub(crate) fn honest_without_lockfiles(content: &str, facts: &BriefFacts) -> Str } out.push_str(line); } + // A Maven-only project is NoInventory: the gap line says nothing was + // checked, and this names the files (#987 review B1). + if let Some(files) = not_covered_line(facts).filter(|f| !out.contains(f.as_str())) { + out.push_str("\n\n"); + out.push_str(&files); + } out.push('\n'); out } @@ -379,11 +392,10 @@ fn with_not_covered(content: &str, facts: &BriefFacts) -> String { let kept: Vec = content .lines() .map(|l| { - if l.contains("Nothing new touches your code") { - all_clear_line(facts).to_string() - } else { - l.to_string() - } + l.replace( + "Nothing new touches your code", + "Nothing new touches the code 4DA checked", + ) }) .collect(); let mut out = kept.join("\n").trim_end().to_string(); diff --git a/src-tauri/src/briefing_deterministic_tests.rs b/src-tauri/src/briefing_deterministic_tests.rs index ab8ff9db6..317ae8199 100644 --- a/src-tauri/src/briefing_deterministic_tests.rs +++ b/src-tauri/src/briefing_deterministic_tests.rs @@ -295,7 +295,7 @@ fn no_all_clear_until_the_advisory_check_is_ready() { (S::Matching, "downloading vulnerability advisories"), (S::Failed, "check failed"), (S::Stale, "more than three days old"), - (S::NoInventory, "can check against vulnerability advisories"), + (S::NoInventory, "read no installed versions"), ]; for (state, phrase) in cases { let facts = BriefFacts { @@ -507,3 +507,61 @@ fn with_everything_read_the_all_clear_is_unchanged() { let draft = "Nothing new touches your code today.\n"; assert_eq!(super::honest_without_lockfiles(draft, &facts), draft); } + +/// #987 review B1: a Maven-only project is NoInventory, so a narrated brief +/// takes the gap branch. It must still name the pom.xml, and so must the +/// prompt the model writes from. +#[test] +fn a_narrated_maven_only_brief_names_the_pom() { + let facts = BriefFacts { + advisories: Some(crate::osv::readiness::AdvisorySyncState::NoInventory), + not_covered: Some(not_covered( + &[("C:/p/spring-petclinic/pom.xml", "Maven")], + &[], + )), + ..BriefFacts::default() + }; + let draft = "Nothing new touches your code today.\n"; + let out = super::honest_without_lockfiles(draft, &facts); + assert!(!out.contains("Nothing new touches your code"), "{out}"); + assert!(out.contains("read no installed versions"), "{out}"); + assert!(out.contains("spring-petclinic/pom.xml (Maven)"), "{out}"); + assert_eq!(super::honest_without_lockfiles(&out, &facts), out); + let prompt = crate::digest_commands::render_facts_for_prompt(&facts); + assert!( + prompt.contains("NOT CHECKED: Not checked: 1 dependency file"), + "{prompt}" + ); + let floor = build_deterministic_brief(&facts, FloorReason::NoCapableModel); + assert!( + floor.contains("spring-petclinic/pom.xml (Maven)"), + "{floor}" + ); +} + +/// #987 review B2: NoInventory means no installed version was read (OSV covers +/// Maven; a package.json with no lockfile is npm), not "no ecosystem it can +/// check". +#[test] +fn no_inventory_says_no_installed_versions_were_read() { + let facts = BriefFacts { + advisories: Some(crate::osv::readiness::AdvisorySyncState::NoInventory), + ..BriefFacts::default() + }; + let out = build_deterministic_brief(&facts, FloorReason::NoCapableModel); + assert!(out.contains("read no installed versions"), "{out}"); + assert!(out.contains("a manifest without a lockfile"), "{out}"); + assert!(!out.contains("ecosystem it can check"), "{out}"); +} + +/// #987 review S5: only the claim is scoped; the rest of the line survives. +#[test] +fn scoping_the_all_clear_keeps_the_rest_of_the_line() { + let facts = ready_with_gaps(not_covered(&[("C:/p/a/pom.xml", "Maven")], &[])); + let draft = "- Nothing new touches your code today, and Vite 7.1 shipped.\n"; + let out = super::honest_without_lockfiles(draft, &facts); + assert!( + out.contains("- Nothing new touches the code 4DA checked today, and Vite 7.1 shipped."), + "{out}" + ); +} diff --git a/src-tauri/src/briefing_prompt.rs b/src-tauri/src/briefing_prompt.rs index c1ded08db..c46df2ead 100644 --- a/src-tauri/src/briefing_prompt.rs +++ b/src-tauri/src/briefing_prompt.rs @@ -221,14 +221,17 @@ pub(crate) fn render_facts_for_prompt(facts: &BriefFacts) -> String { .to_string(); } let advisory_gap = crate::briefing_deterministic::advisory_gap_line(facts).is_some(); + let not_covered = crate::briefing_deterministic::not_covered_line(facts) + .map(|line| format!("NOT CHECKED: {line}\n\n")) + .unwrap_or_default(); let mut out = if advisory_gap { // The security lanes below are empty because advisories were not (all) // checked, not because nothing applies (audit E3). - String::from( + format!( "ADVISORY CHECK NOT COMPLETE: the user's dependencies have not been fully checked \ against published vulnerability advisories yet. An empty ACT NOW or ALSO OPEN does \ NOT mean they are unaffected. Never say nothing touches their code or that they are \ - unaffected; the software appends the status line itself.\n\n\ + unaffected; the software appends the status line itself.\n\n{not_covered}\ FACTS (computed by 4DA from the user's lockfiles and package registries):\n\n", ) } else if let Some(line) = crate::briefing_deterministic::not_covered_line(facts) { diff --git a/src-tauri/src/osv/readiness.rs b/src-tauri/src/osv/readiness.rs index 8a0cf3f92..a34dc3c3c 100644 --- a/src-tauri/src/osv/readiness.rs +++ b/src-tauri/src/osv/readiness.rs @@ -189,8 +189,8 @@ pub(crate) fn classify( /// as `Ready`. pub(crate) fn advisory_sync(db: &Database) -> AdvisorySync { let in_flight = refresh::sync_in_flight(); - let required: Vec = match super::sync::auditable_ecosystems(db) { - Ok(r) => r, + let coverage = match super::sync::auditable_coverage(db) { + Ok(c) => c, Err(e) => { tracing::warn!(target: "4da::osv", error = %e, "advisory readiness: inventory unreadable"); return unreadable(in_flight); @@ -204,12 +204,23 @@ pub(crate) fn advisory_sync(db: &Database) -> AdvisorySync { } }; let mut out = classify( - &required, + &coverage.versioned, &statuses, chrono::Utc::now().naive_utc(), in_flight, ); let report = lockfile_report(db); + // Cold start (#987 review S3): the manifest scan registers dependencies + // with no version before the first lockfile walk reports. That is "not + // read yet", never "nothing checkable": the doctrine forbids telling a + // first-run user there is nothing to check. + if out.state == AdvisorySyncState::NoInventory && report.is_none() && coverage.any_checkable { + out.state = if in_flight { + AdvisorySyncState::Syncing + } else { + AdvisorySyncState::NotStarted + }; + } if out.state == AdvisorySyncState::NoInventory { out.skipped_temp = report .as_ref() diff --git a/src-tauri/src/osv/readiness_tests.rs b/src-tauri/src/osv/readiness_tests.rs index bec6121bd..3b180aa9b 100644 --- a/src-tauri/src/osv/readiness_tests.rs +++ b/src-tauri/src/osv/readiness_tests.rs @@ -335,3 +335,23 @@ fn npm_and_maven_read_ready_for_npm_and_name_the_maven_files() { let nc = r.not_covered.expect("the pom.xml is named"); assert_eq!(nc.files[0].ecosystem, "Maven"); } + +/// #987 review S3 (cold start): the manifest scan registers dependencies with +/// no version before the first lockfile walk reports. That is "not read yet": +/// never "nothing checkable" to a first-run user. +#[test] +fn before_the_first_lockfile_walk_versionless_rows_are_not_started() { + let db = crate::test_utils::test_db(); + db.store_dependency("/web", "minimist", None, "javascript", false, None) + .unwrap(); + let r = advisory_sync(&db); + assert_eq!(r.state, AdvisorySyncState::NotStarted, "no walk report yet"); + // Once the walk has reported, the same rows are what was read: nothing + // versioned, so nothing to check. + db.set_kv( + crate::ace::lockfile::report::REPORT_KV_KEY, + r#"{"lockfiles_read":0}"#, + ) + .unwrap(); + assert_eq!(advisory_sync(&db).state, AdvisorySyncState::NoInventory); +} diff --git a/src-tauri/src/osv/sync.rs b/src-tauri/src/osv/sync.rs index 71b83381f..496fbe356 100644 --- a/src-tauri/src/osv/sync.rs +++ b/src-tauri/src/osv/sync.rs @@ -128,14 +128,24 @@ fn auditable_packages_by_ecosystem(db: &Database) -> Result Result> { +/// What the auditable inventory holds, by OSV ecosystem. +pub(crate) struct AuditableCoverage { + /// The ecosystems the advisory check can speak for: those with at least + /// one auditable row that carries an installed version, sorted. A + /// manifest row has none until a lockfile pins it, and a versionless + /// dependency is never matched (#974). A Maven-only project made "Maven" + /// required, the sync succeeded, and readiness said Ready while nothing + /// was checked (W5a). The sync itself still covers every auditable + /// ecosystem ([`sync`], [`needs_sync`]). + pub versioned: Vec, + /// Some auditable row is in an ecosystem OSV covers, versioned or not. + pub any_checkable: bool, +} + +/// The auditable inventory's coverage, read once for readiness: which +/// ecosystems are checkable now, and whether any checkable row exists at all +/// (readiness tells "not read yet" from "nothing checkable" with it). +pub(crate) fn auditable_coverage(db: &Database) -> Result { let mut deps = db .get_auditable_user_dependencies() .map_err(|e| FourDaError::Internal(format!("Failed to read dependencies: {e}")))?; @@ -144,15 +154,21 @@ pub(crate) fn auditable_ecosystems(db: &Database) -> Result> { FourDaError::Internal(format!("Failed to read scanned dependencies: {e}")) })?, ); - let mut ecosystems: Vec = deps + let any_checkable = deps + .iter() + .any(|d| normalize_supported_to_osv(&d.ecosystem).is_some()); + let mut versioned: Vec = deps .iter() .filter(|d| d.version.as_deref().is_some_and(|v| !v.trim().is_empty())) .filter_map(|d| normalize_supported_to_osv(&d.ecosystem)) .map(str::to_string) .collect(); - ecosystems.sort(); - ecosystems.dedup(); - Ok(ecosystems) + versioned.sort(); + versioned.dedup(); + Ok(AuditableCoverage { + versioned, + any_checkable, + }) } /// Whether any dependency ecosystem is missing a successful recent sync. diff --git a/src/locales/ar/ui.json b/src/locales/ar/ui.json index 11698edf3..2eb165782 100644 --- a/src/locales/ar/ui.json +++ b/src/locales/ar/ui.json @@ -1001,7 +1001,7 @@ "firstRun.advisory.notStarted": "يبدأ فحص الثغرات تلقائيًا خلال دقائق. تظهر النتائج في Preemption.", "firstRun.advisory.stale": "تنبيهات الثغرات التي فحص 4DA تبعياتك مقابلها آخر مرة أقدم من ثلاثة أيام. يعيد الفحص تلقائيًا، ويعرض Preemption الحالة.", "firstRun.advisory.failed": "لم يكتمل فحص الثغرات. يعيد 4DA المحاولة تلقائيًا؛ ويعرض Preemption الحالة.", - "firstRun.advisory.uncovered": "لا ينتمي أي منها بعد إلى نظام بيئي يستطيع 4DA مقارنته بتنبيهات الثغرات.", + "firstRun.advisory.uncovered": "لم يقرأ 4DA بعد الإصدارات المثبتة لهذه التبعيات (ملف بيان بلا ملف قفل، أو تنسيق لا يقرؤه)، لذلك لم يمكن مطابقة أي منها مع إرشادات الثغرات.", "firstRun.skipToResults": "تخطي إلى النتائج", "firstRun.sourcesScanned": "مصادر", "firstRun.stackInsight": "رؤية الحزمة التقنية", @@ -1342,8 +1342,8 @@ "preemption.advisories.failed.body": "تعذّر على 4DA مزامنة التنبيهات المنشورة، لذا لا يمكنه القول إن تبعياتك سليمة. سيعيد المحاولة في دورته التالية.", "preemption.advisories.stale.title": "بيانات التنبيهات قديمة", "preemption.advisories.stale.body": "آخر مزامنة ناجحة للتنبيهات كانت {{age}}. لن يعلن 4DA أن حزمتك التقنية سليمة بناءً على بيانات بهذا القدم؛ سيعيد المحاولة في دورته التالية.", - "preemption.advisories.uncovered.title": "لا توجد قاعدة بيانات تنبيهات تغطي هذه التبعيات", - "preemption.advisories.uncovered.body": "لا تنتمي أي من التبعيات التي قرأها 4DA إلى نظام بيئي يمكنه فحصه مقابل التنبيهات المنشورة، لذا لا يوجد ما يمكنه إعلانه سليمًا.", + "preemption.advisories.uncovered.title": "لا توجد إصدارات مثبتة للتحقق منها", + "preemption.advisories.uncovered.body": "عثر 4DA على تبعيات لكنه لم يقرأ إصداراتها المثبتة: فهي تأتي من ملف بيان بلا ملف قفل، أو من تنسيق لا يقرؤه 4DA (Maven وGradle وNuGet…). لم يمكن مطابقة أي شيء مع الإرشادات المنشورة، لذلك لا يوجد ما يمكن اعتباره سليمًا.", "preemption.advisories.notStarted.title": "لم يتم تنزيل بيانات الثغرات بعد", "preemption.advisories.notStarted.body": "قرأ 4DA تبعياتك لكنه لم يقارنها بعد بالتنبيهات المنشورة، لذا لا يمكنه القول إنها سليمة. يبدأ الفحص تلقائيًا خلال دقائق، أو شغّله الآن.", "preemption.advisories.checkNow": "افحص الآن", diff --git a/src/locales/de/ui.json b/src/locales/de/ui.json index e9b7cb024..16d3b7df5 100644 --- a/src/locales/de/ui.json +++ b/src/locales/de/ui.json @@ -426,8 +426,8 @@ "preemption.advisories.failed.body": "4DA konnte die veröffentlichten Sicherheitshinweise nicht synchronisieren und kann deine Abhängigkeiten daher nicht als unbedenklich einstufen. Beim nächsten Durchlauf versucht es es erneut.", "preemption.advisories.stale.title": "Die Daten der Sicherheitshinweise sind veraltet", "preemption.advisories.stale.body": "Die letzte erfolgreiche Synchronisierung war {{age}}. Auf so alten Daten erklärt 4DA deinen Stack nicht für sauber; beim nächsten Durchlauf versucht es es erneut.", - "preemption.advisories.uncovered.title": "Keine Datenbank für Sicherheitshinweise deckt diese Abhängigkeiten ab", - "preemption.advisories.uncovered.body": "Keine der von 4DA gelesenen Abhängigkeiten gehört zu einem Ökosystem, das es gegen veröffentlichte Sicherheitshinweise prüfen kann. Es gibt also nichts, das es für sauber erklären könnte.", + "preemption.advisories.uncovered.title": "Keine installierten Versionen zum Prüfen", + "preemption.advisories.uncovered.body": "4DA hat Abhängigkeiten gefunden, aber keine installierten Versionen dafür gelesen: Sie stammen aus einem Manifest ohne Lockfile oder aus einem Format, das 4DA nicht liest (Maven, Gradle, NuGet …). Nichts konnte mit veröffentlichten Sicherheitshinweisen abgeglichen werden, daher kann nichts als sauber gelten.", "preemption.advisories.notStarted.title": "Schwachstellendaten wurden noch nicht heruntergeladen", "preemption.advisories.notStarted.body": "4DA hat deine Abhängigkeiten gelesen, sie aber noch nicht mit veröffentlichten Sicherheitshinweisen abgeglichen und kann sie daher nicht als sauber bezeichnen. Die Prüfung startet in wenigen Minuten von selbst, oder du startest sie jetzt.", "preemption.advisories.checkNow": "Jetzt prüfen", @@ -1232,7 +1232,7 @@ "firstRun.advisory.notStarted": "Die Schwachstellenprüfung startet in wenigen Minuten von selbst. Treffer erscheinen in Preemption.", "firstRun.advisory.stale": "Die Schwachstellenhinweise, mit denen 4DA zuletzt abgeglichen hat, sind älter als drei Tage. 4DA prüft von selbst erneut; Preemption zeigt den Status.", "firstRun.advisory.failed": "Die Schwachstellenprüfung wurde nicht abgeschlossen. 4DA versucht es von selbst erneut; Preemption zeigt den Status.", - "firstRun.advisory.uncovered": "Keine davon gehört zu einem Ökosystem, das 4DA bereits mit Schwachstellenhinweisen abgleichen kann.", + "firstRun.advisory.uncovered": "4DA hat für diese noch keine installierten Versionen gelesen (ein Manifest ohne Lockfile oder ein Format, das 4DA nicht liest), daher konnte keine davon mit Sicherheitshinweisen abgeglichen werden.", "shortcuts.title": "Tastaturkürzel", "shortcuts.close": "Tastaturkürzel schließen", "shortcuts.runAnalysis": "Analyse starten", diff --git a/src/locales/en/ui.json b/src/locales/en/ui.json index b526e4268..65a4c2d66 100644 --- a/src/locales/en/ui.json +++ b/src/locales/en/ui.json @@ -506,8 +506,8 @@ "preemption.advisories.failed.body": "4DA could not sync published advisories, so it cannot say your dependencies are clear. It retries on its next cycle.", "preemption.advisories.stale.title": "Advisory data is out of date", "preemption.advisories.stale.body": "The last successful advisory sync was {{age}}. 4DA will not call your stack clean on data that old; it retries on its next cycle.", - "preemption.advisories.uncovered.title": "No advisory database covers these dependencies", - "preemption.advisories.uncovered.body": "None of the dependencies 4DA read are in an ecosystem it can check against published advisories, so there is nothing it can call clean.", + "preemption.advisories.uncovered.title": "No installed versions to check", + "preemption.advisories.uncovered.body": "4DA found dependencies but read no installed versions for them: they come from a manifest without a lockfile, or from a format it does not read (Maven, Gradle, NuGet …). Nothing could be checked against published advisories, so there is nothing it can call clean.", "preemption.advisories.notStarted.title": "Vulnerability data has not been downloaded yet", "preemption.advisories.notStarted.body": "4DA read your dependencies but has not checked them against published advisories yet, so it cannot call them clean. The check starts on its own within a few minutes, or run it now.", "preemption.advisories.checkNow": "Check now", @@ -1327,7 +1327,7 @@ "firstRun.advisory.notStarted": "The vulnerability check starts on its own within a few minutes. Findings appear in Preemption.", "firstRun.advisory.stale": "The vulnerability advisories 4DA last checked against are more than three days old. It re-checks on its own; Preemption shows the status.", "firstRun.advisory.failed": "The vulnerability check did not complete. 4DA retries on its own; Preemption shows the status.", - "firstRun.advisory.uncovered": "None of these are in an ecosystem 4DA can check against vulnerability advisories yet.", + "firstRun.advisory.uncovered": "4DA read no installed versions for these yet (a manifest without a lockfile, or a format it does not read), so none of them could be checked against vulnerability advisories.", "shortcuts.title": "Keyboard Shortcuts", "shortcuts.close": "Close keyboard shortcuts", "shortcuts.runAnalysis": "Run analysis", diff --git a/src/locales/es/ui.json b/src/locales/es/ui.json index 8d735c25f..24006117b 100644 --- a/src/locales/es/ui.json +++ b/src/locales/es/ui.json @@ -426,8 +426,8 @@ "preemption.advisories.failed.body": "4DA no pudo sincronizar los avisos publicados, así que no puede afirmar que tus dependencias estén limpias. Lo reintentará en su próximo ciclo.", "preemption.advisories.stale.title": "Los datos de avisos están desactualizados", "preemption.advisories.stale.body": "La última sincronización correcta de avisos fue {{age}}. 4DA no declarará limpio tu stack con datos tan antiguos; lo reintentará en su próximo ciclo.", - "preemption.advisories.uncovered.title": "Ninguna base de datos de avisos cubre estas dependencias", - "preemption.advisories.uncovered.body": "Ninguna de las dependencias que leyó 4DA pertenece a un ecosistema que pueda cotejar con avisos publicados, así que no hay nada que pueda declarar limpio.", + "preemption.advisories.uncovered.title": "No hay versiones instaladas que comprobar", + "preemption.advisories.uncovered.body": "4DA encontró dependencias, pero no leyó ninguna versión instalada: provienen de un manifiesto sin archivo de bloqueo o de un formato que 4DA no lee (Maven, Gradle, NuGet…). No se pudo comprobar nada con los avisos publicados, así que no hay nada que pueda considerar limpio.", "preemption.advisories.notStarted.title": "Los datos de vulnerabilidades aún no se han descargado", "preemption.advisories.notStarted.body": "4DA leyó tus dependencias pero aún no las ha comparado con los avisos publicados, así que no puede darlas por limpias. La comprobación empieza sola en pocos minutos, o puedes ejecutarla ahora.", "preemption.advisories.checkNow": "Comprobar ahora", @@ -1232,7 +1232,7 @@ "firstRun.advisory.notStarted": "La comprobación de vulnerabilidades empieza sola en pocos minutos. Los hallazgos aparecen en Preemption.", "firstRun.advisory.stale": "Los avisos de vulnerabilidades con los que 4DA comprobó por última vez tienen más de tres días. Vuelve a comprobar por sí solo; Preemption muestra el estado.", "firstRun.advisory.failed": "La comprobación de vulnerabilidades no terminó. 4DA lo reintenta solo; Preemption muestra el estado.", - "firstRun.advisory.uncovered": "Ninguna pertenece todavía a un ecosistema que 4DA pueda comparar con avisos de vulnerabilidades.", + "firstRun.advisory.uncovered": "4DA aún no ha leído versiones instaladas de estas dependencias (un manifiesto sin archivo de bloqueo o un formato que no lee), así que ninguna se pudo comprobar con los avisos de vulnerabilidades.", "shortcuts.title": "Atajos de teclado", "shortcuts.close": "Cerrar atajos de teclado", "shortcuts.runAnalysis": "Ejecutar análisis", diff --git a/src/locales/fr/ui.json b/src/locales/fr/ui.json index 3528f589f..e4b43d0dc 100644 --- a/src/locales/fr/ui.json +++ b/src/locales/fr/ui.json @@ -426,8 +426,8 @@ "preemption.advisories.failed.body": "4DA n'a pas pu synchroniser les avis publiés ; il ne peut donc pas affirmer que vos dépendances sont saines. Il réessaiera au prochain cycle.", "preemption.advisories.stale.title": "Les données d'avis sont périmées", "preemption.advisories.stale.body": "La dernière synchronisation réussie des avis date de {{age}}. 4DA ne déclarera pas votre stack sain sur des données aussi anciennes ; il réessaiera au prochain cycle.", - "preemption.advisories.uncovered.title": "Aucune base d'avis ne couvre ces dépendances", - "preemption.advisories.uncovered.body": "Aucune des dépendances lues par 4DA n'appartient à un écosystème qu'il peut vérifier face aux avis publiés : il n'y a donc rien qu'il puisse déclarer sain.", + "preemption.advisories.uncovered.title": "Aucune version installée à vérifier", + "preemption.advisories.uncovered.body": "4DA a trouvé des dépendances mais n’a lu aucune version installée : elles proviennent d’un manifeste sans fichier de verrouillage ou d’un format que 4DA ne lit pas (Maven, Gradle, NuGet…). Rien n’a pu être vérifié par rapport aux avis publiés, donc rien ne peut être déclaré sain.", "preemption.advisories.notStarted.title": "Les données de vulnérabilités n'ont pas encore été téléchargées", "preemption.advisories.notStarted.body": "4DA a lu vos dépendances mais ne les a pas encore comparées aux avis publiés : il ne peut donc pas les déclarer saines. La vérification démarre d'elle-même d'ici quelques minutes, ou lancez-la maintenant.", "preemption.advisories.checkNow": "Vérifier maintenant", @@ -1232,7 +1232,7 @@ "firstRun.advisory.notStarted": "La vérification des vulnérabilités démarre d'elle-même d'ici quelques minutes. Les résultats apparaissent dans Preemption.", "firstRun.advisory.stale": "Les avis de vulnérabilité utilisés lors de la dernière vérification de 4DA datent de plus de trois jours. Il revérifie de lui-même ; Preemption affiche l'état.", "firstRun.advisory.failed": "La vérification des vulnérabilités n'a pas abouti. 4DA réessaie de lui-même ; Preemption affiche l'état.", - "firstRun.advisory.uncovered": "Aucune ne relève encore d'un écosystème que 4DA peut comparer aux avis de vulnérabilités.", + "firstRun.advisory.uncovered": "4DA n’a encore lu aucune version installée pour celles-ci (un manifeste sans fichier de verrouillage ou un format qu’il ne lit pas), donc aucune n’a pu être vérifiée par rapport aux avis de vulnérabilité.", "shortcuts.title": "Raccourcis clavier", "shortcuts.close": "Fermer les raccourcis clavier", "shortcuts.runAnalysis": "Lancer l'analyse", diff --git a/src/locales/hi/ui.json b/src/locales/hi/ui.json index 8b4b9868f..249eb7343 100644 --- a/src/locales/hi/ui.json +++ b/src/locales/hi/ui.json @@ -1059,7 +1059,7 @@ "firstRun.advisory.notStarted": "Vulnerability जाँच कुछ ही मिनटों में अपने आप शुरू होगी। नतीजे Preemption में दिखेंगे।", "firstRun.advisory.stale": "4DA ने पिछली बार जिन vulnerability एडवाइज़री से जाँच की थी, वे तीन दिन से पुरानी हैं। यह अपने आप फिर जाँचता है; Preemption स्थिति दिखाता है।", "firstRun.advisory.failed": "Vulnerability जाँच पूरी नहीं हुई। 4DA अपने आप फिर कोशिश करेगा; स्थिति Preemption में दिखती है।", - "firstRun.advisory.uncovered": "इनमें से कोई भी अभी ऐसे इकोसिस्टम में नहीं है जिसे 4DA vulnerability एडवाइज़री से मिला सके।", + "firstRun.advisory.uncovered": "4DA ने अभी तक इनके इंस्टॉल किए हुए संस्करण नहीं पढ़े (बिना लॉकफ़ाइल वाला मैनिफ़ेस्ट या ऐसा प्रारूप जिसे वह नहीं पढ़ता), इसलिए इनमें से किसी का भी कमज़ोरी एडवाइज़री से मिलान नहीं हो सका।", "firstRun.skipToResults": "परिणामों पर जाएँ", "firstRun.sourcesScanned": "स्रोत", "firstRun.stackInsight": "स्टैक अंतर्दृष्टि", @@ -1399,8 +1399,8 @@ "preemption.advisories.failed.body": "4DA प्रकाशित सूचनाएँ सिंक नहीं कर सका, इसलिए वह यह नहीं कह सकता कि आपकी निर्भरताएँ सुरक्षित हैं। वह अगले चक्र में फिर कोशिश करेगा।", "preemption.advisories.stale.title": "सुरक्षा सूचनाओं का डेटा पुराना है", "preemption.advisories.stale.body": "पिछला सफल सिंक {{age}} हुआ था। इतने पुराने डेटा पर 4DA आपके स्टैक को साफ़ नहीं बताएगा; वह अगले चक्र में फिर कोशिश करेगा।", - "preemption.advisories.uncovered.title": "कोई सूचना डेटाबेस इन निर्भरताओं को कवर नहीं करता", - "preemption.advisories.uncovered.body": "4DA द्वारा पढ़ी गई कोई भी निर्भरता ऐसे इकोसिस्टम में नहीं है जिसे वह प्रकाशित सूचनाओं से जाँच सके, इसलिए साफ़ बताने लायक कुछ नहीं है।", + "preemption.advisories.uncovered.title": "जाँचने के लिए कोई इंस्टॉल किया हुआ संस्करण नहीं", + "preemption.advisories.uncovered.body": "4DA को निर्भरताएँ मिलीं, लेकिन उनके इंस्टॉल किए हुए संस्करण नहीं पढ़े गए: वे बिना लॉकफ़ाइल वाले मैनिफ़ेस्ट से या ऐसे प्रारूप से आती हैं जिसे 4DA नहीं पढ़ता (Maven, Gradle, NuGet…)। प्रकाशित एडवाइज़री से कुछ भी मिलान नहीं हो सका, इसलिए किसी को भी सुरक्षित नहीं कहा जा सकता।", "preemption.advisories.notStarted.title": "कमज़ोरी (vulnerability) डेटा अभी डाउनलोड नहीं हुआ है", "preemption.advisories.notStarted.body": "4DA ने आपकी डिपेंडेंसी पढ़ ली हैं, लेकिन अभी उन्हें प्रकाशित एडवाइज़री से नहीं मिलाया है, इसलिए वह उन्हें साफ़ नहीं कह सकता। जाँच कुछ ही मिनटों में अपने आप शुरू होगी, या इसे अभी चलाएँ।", "preemption.advisories.checkNow": "अभी जाँचें", diff --git a/src/locales/it/ui.json b/src/locales/it/ui.json index fd5dc6d9a..5e112a4fa 100644 --- a/src/locales/it/ui.json +++ b/src/locales/it/ui.json @@ -419,8 +419,8 @@ "preemption.advisories.failed.body": "4DA non è riuscito a sincronizzare gli avvisi pubblicati, quindi non può dire che le tue dipendenze siano pulite. Riproverà al prossimo ciclo.", "preemption.advisories.stale.title": "I dati degli avvisi non sono aggiornati", "preemption.advisories.stale.body": "L'ultima sincronizzazione riuscita degli avvisi risale a {{age}}. 4DA non dichiarerà pulito il tuo stack su dati così vecchi; riproverà al prossimo ciclo.", - "preemption.advisories.uncovered.title": "Nessun database di avvisi copre queste dipendenze", - "preemption.advisories.uncovered.body": "Nessuna delle dipendenze lette da 4DA appartiene a un ecosistema che può confrontare con gli avvisi pubblicati, quindi non c'è nulla che possa dichiarare pulito.", + "preemption.advisories.uncovered.title": "Nessuna versione installata da controllare", + "preemption.advisories.uncovered.body": "4DA ha trovato delle dipendenze ma non ne ha letto le versioni installate: provengono da un manifest senza lockfile o da un formato che 4DA non legge (Maven, Gradle, NuGet…). Non è stato possibile confrontare nulla con gli avvisi pubblicati, quindi nulla può essere considerato pulito.", "preemption.advisories.notStarted.title": "I dati sulle vulnerabilità non sono ancora stati scaricati", "preemption.advisories.notStarted.body": "4DA ha letto le tue dipendenze ma non le ha ancora confrontate con gli avvisi pubblicati, quindi non può dichiararle pulite. Il controllo parte da solo entro pochi minuti, oppure avvialo ora.", "preemption.advisories.checkNow": "Controlla ora", @@ -1227,7 +1227,7 @@ "firstRun.advisory.notStarted": "Il controllo delle vulnerabilità parte da solo entro pochi minuti. I risultati compaiono in Preemption.", "firstRun.advisory.stale": "Gli avvisi di vulnerabilità usati da 4DA nell'ultimo controllo hanno più di tre giorni. Ricontrolla da solo; Preemption mostra lo stato.", "firstRun.advisory.failed": "Il controllo delle vulnerabilità non è stato completato. 4DA riprova da solo; Preemption mostra lo stato.", - "firstRun.advisory.uncovered": "Nessuna appartiene ancora a un ecosistema che 4DA può confrontare con gli avvisi di vulnerabilità.", + "firstRun.advisory.uncovered": "4DA non ha ancora letto versioni installate per queste (un manifest senza lockfile o un formato che non legge), quindi nessuna è stata confrontata con gli avvisi di vulnerabilità.", "shortcuts.title": "Scorciatoie da tastiera", "shortcuts.close": "Chiudi scorciatoie da tastiera", "shortcuts.runAnalysis": "Esegui analisi", diff --git a/src/locales/ja/ui.json b/src/locales/ja/ui.json index 785f7db4f..0270fc187 100644 --- a/src/locales/ja/ui.json +++ b/src/locales/ja/ui.json @@ -419,8 +419,8 @@ "preemption.advisories.failed.body": "4DA は公開済みアドバイザリを同期できなかったため、依存関係に問題がないとは言えません。次のサイクルで再試行します。", "preemption.advisories.stale.title": "アドバイザリデータが古くなっています", "preemption.advisories.stale.body": "最後にアドバイザリの同期に成功したのは {{age}} です。これほど古いデータでは 4DA はスタックを問題なしとは判断しません。次のサイクルで再試行します。", - "preemption.advisories.uncovered.title": "これらの依存関係を対象とするアドバイザリデータベースがありません", - "preemption.advisories.uncovered.body": "4DA が読み取った依存関係はいずれも、公開済みアドバイザリと照合できるエコシステムに属していないため、問題なしと判断できる対象がありません。", + "preemption.advisories.uncovered.title": "確認できるインストール済みバージョンがありません", + "preemption.advisories.uncovered.body": "4DA は依存関係を見つけましたが、インストール済みのバージョンを読み取れませんでした。ロックファイルのないマニフェスト、または 4DA が読み取らない形式(Maven、Gradle、NuGet など)に由来します。公開されたアドバイザリと照合できたものはないため、安全と判断できるものはありません。", "preemption.advisories.notStarted.title": "脆弱性データはまだダウンロードされていません", "preemption.advisories.notStarted.body": "4DA は依存関係を読み取りましたが、公開されたアドバイザリとの照合はまだのため、問題なしとは言えません。チェックは数分以内に自動で始まります。今すぐ実行することもできます。", "preemption.advisories.checkNow": "今すぐチェック", @@ -1227,7 +1227,7 @@ "firstRun.advisory.notStarted": "脆弱性チェックは数分以内に自動で始まります。結果は Preemption に表示されます。", "firstRun.advisory.stale": "4DA が前回照合した脆弱性アドバイザリは 3 日以上前のものです。自動で再チェックします。状況は Preemption に表示されます。", "firstRun.advisory.failed": "脆弱性チェックが完了しませんでした。4DA が自動で再試行します。状態は Preemption で確認できます。", - "firstRun.advisory.uncovered": "いずれも、4DA が脆弱性アドバイザリと照合できるエコシステムにはまだ含まれていません。", + "firstRun.advisory.uncovered": "4DA はまだこれらのインストール済みバージョンを読み取っていません(ロックファイルのないマニフェスト、または読み取らない形式)。そのため、どれも脆弱性アドバイザリと照合できていません。", "shortcuts.title": "キーボードショートカット", "shortcuts.close": "キーボードショートカットを閉じる", "shortcuts.runAnalysis": "分析を実行", diff --git a/src/locales/ko/ui.json b/src/locales/ko/ui.json index 4dcea779e..7aef131ba 100644 --- a/src/locales/ko/ui.json +++ b/src/locales/ko/ui.json @@ -996,7 +996,7 @@ "firstRun.advisory.notStarted": "취약점 검사는 몇 분 안에 자동으로 시작됩니다. 결과는 Preemption에 표시됩니다.", "firstRun.advisory.stale": "4DA가 마지막으로 대조한 취약점 권고가 3일보다 오래되었습니다. 자동으로 다시 확인하며, 상태는 Preemption에 표시됩니다.", "firstRun.advisory.failed": "취약점 검사가 완료되지 않았습니다. 4DA가 자동으로 다시 시도하며, 상태는 Preemption에서 볼 수 있습니다.", - "firstRun.advisory.uncovered": "아직 4DA가 취약점 권고와 대조할 수 있는 에코시스템에 속한 항목이 없습니다.", + "firstRun.advisory.uncovered": "4DA가 아직 이 항목들의 설치 버전을 읽지 않았습니다(잠금 파일이 없는 매니페스트 또는 읽지 않는 형식). 따라서 어느 것도 취약점 권고와 대조하지 못했습니다.", "firstRun.skipToResults": "결과로 건너뛰기", "firstRun.sourcesScanned": "소스", "firstRun.stackInsight": "스택 인사이트", @@ -1340,8 +1340,8 @@ "preemption.advisories.failed.body": "4DA가 공개된 권고를 동기화하지 못해 의존성이 안전하다고 말할 수 없습니다. 다음 주기에 다시 시도합니다.", "preemption.advisories.stale.title": "권고 데이터가 오래되었습니다", "preemption.advisories.stale.body": "마지막으로 권고 동기화에 성공한 시점은 {{age}}입니다. 이렇게 오래된 데이터로는 4DA가 스택을 깨끗하다고 판단하지 않으며, 다음 주기에 다시 시도합니다.", - "preemption.advisories.uncovered.title": "이 의존성을 다루는 권고 데이터베이스가 없습니다", - "preemption.advisories.uncovered.body": "4DA가 읽은 의존성 중 공개된 권고와 대조할 수 있는 생태계에 속한 것이 없어, 깨끗하다고 판단할 대상이 없습니다.", + "preemption.advisories.uncovered.title": "확인할 설치 버전이 없습니다", + "preemption.advisories.uncovered.body": "4DA가 의존성을 찾았지만 설치된 버전을 읽지 못했습니다. 잠금 파일이 없는 매니페스트이거나 4DA가 읽지 않는 형식(Maven, Gradle, NuGet 등)에서 온 것입니다. 게시된 권고와 대조할 수 있었던 항목이 없으므로 안전하다고 할 수 있는 것은 없습니다.", "preemption.advisories.notStarted.title": "취약점 데이터를 아직 내려받지 않았습니다", "preemption.advisories.notStarted.body": "4DA가 의존성을 읽었지만 아직 공개된 권고와 대조하지 않아 문제없다고 말할 수 없습니다. 검사는 몇 분 안에 자동으로 시작되며, 지금 실행할 수도 있습니다.", "preemption.advisories.checkNow": "지금 검사", diff --git a/src/locales/pt-BR/ui.json b/src/locales/pt-BR/ui.json index 8e1797f94..b84736e02 100644 --- a/src/locales/pt-BR/ui.json +++ b/src/locales/pt-BR/ui.json @@ -1039,7 +1039,7 @@ "firstRun.advisory.notStarted": "A verificação de vulnerabilidades começa sozinha em poucos minutos. Os resultados aparecem em Preemption.", "firstRun.advisory.stale": "Os avisos de vulnerabilidade usados na última verificação do 4DA têm mais de três dias. Ele verifica de novo sozinho; Preemption mostra o status.", "firstRun.advisory.failed": "A verificação de vulnerabilidades não terminou. O 4DA tenta de novo sozinho; Preemption mostra o status.", - "firstRun.advisory.uncovered": "Nenhuma está ainda em um ecossistema que o 4DA consiga comparar com avisos de vulnerabilidades.", + "firstRun.advisory.uncovered": "O 4DA ainda não leu versões instaladas destas (um manifesto sem lockfile ou um formato que ele não lê), então nenhuma pôde ser verificada contra avisos de vulnerabilidade.", "firstRun.skipToResults": "Ir para os resultados", "firstRun.sourcesScanned": "fontes", "firstRun.stackInsight": "Insight do stack", @@ -1403,8 +1403,8 @@ "preemption.advisories.failed.body": "O 4DA não conseguiu sincronizar os avisos publicados, então não pode dizer que suas dependências estão limpas. Ele tenta de novo no próximo ciclo.", "preemption.advisories.stale.title": "Os dados de avisos estão desatualizados", "preemption.advisories.stale.body": "A última sincronização bem-sucedida de avisos foi {{age}}. O 4DA não vai declarar seu stack limpo com dados tão antigos; ele tenta de novo no próximo ciclo.", - "preemption.advisories.uncovered.title": "Nenhum banco de avisos cobre essas dependências", - "preemption.advisories.uncovered.body": "Nenhuma das dependências que o 4DA leu está em um ecossistema que ele consiga verificar nos avisos publicados, então não há nada que ele possa declarar limpo.", + "preemption.advisories.uncovered.title": "Nenhuma versão instalada para verificar", + "preemption.advisories.uncovered.body": "O 4DA encontrou dependências, mas não leu nenhuma versão instalada: elas vêm de um manifesto sem lockfile ou de um formato que o 4DA não lê (Maven, Gradle, NuGet…). Nada pôde ser verificado contra os avisos publicados, então não há nada que possa ser considerado limpo.", "preemption.advisories.notStarted.title": "Os dados de vulnerabilidades ainda não foram baixados", "preemption.advisories.notStarted.body": "O 4DA leu suas dependências, mas ainda não as comparou com os avisos publicados, então não pode considerá-las limpas. A verificação começa sozinha em poucos minutos, ou execute-a agora.", "preemption.advisories.checkNow": "Verificar agora", diff --git a/src/locales/ru/ui.json b/src/locales/ru/ui.json index 73381dbae..5ada59fc9 100644 --- a/src/locales/ru/ui.json +++ b/src/locales/ru/ui.json @@ -419,8 +419,8 @@ "preemption.advisories.failed.body": "4DA не удалось синхронизировать опубликованные бюллетени, поэтому он не может утверждать, что ваши зависимости чисты. Он повторит попытку в следующем цикле.", "preemption.advisories.stale.title": "Данные бюллетеней устарели", "preemption.advisories.stale.body": "Последняя успешная синхронизация бюллетеней была {{age}}. На таких старых данных 4DA не объявит ваш стек чистым; он повторит попытку в следующем цикле.", - "preemption.advisories.uncovered.title": "Ни одна база бюллетеней не охватывает эти зависимости", - "preemption.advisories.uncovered.body": "Ни одна из прочитанных 4DA зависимостей не относится к экосистеме, которую он может проверить по опубликованным бюллетеням, поэтому объявлять чистым нечего.", + "preemption.advisories.uncovered.title": "Нет установленных версий для проверки", + "preemption.advisories.uncovered.body": "4DA нашёл зависимости, но не прочитал их установленные версии: они взяты из манифеста без lock-файла или из формата, который 4DA не читает (Maven, Gradle, NuGet…). Ничего не удалось сверить с опубликованными уведомлениями, поэтому ничего нельзя считать чистым.", "preemption.advisories.notStarted.title": "Данные об уязвимостях ещё не загружены", "preemption.advisories.notStarted.body": "4DA прочитал ваши зависимости, но ещё не сверил их с опубликованными бюллетенями, поэтому не может назвать их чистыми. Проверка запустится сама через несколько минут, или запустите её сейчас.", "preemption.advisories.checkNow": "Проверить сейчас", @@ -1225,7 +1225,7 @@ "firstRun.advisory.notStarted": "Проверка уязвимостей запустится сама через несколько минут. Результаты появятся в Preemption.", "firstRun.advisory.stale": "Бюллетени об уязвимостях, с которыми 4DA сверялся в последний раз, старше трёх дней. Он перепроверит сам; статус показан в Preemption.", "firstRun.advisory.failed": "Проверка уязвимостей не завершилась. 4DA повторит её сам; статус показан в Preemption.", - "firstRun.advisory.uncovered": "Ни одна из них пока не относится к экосистеме, которую 4DA может сверить с бюллетенями уязвимостей.", + "firstRun.advisory.uncovered": "4DA ещё не прочитал установленные версии для них (манифест без lock-файла или формат, который он не читает), поэтому ни одну из них не удалось сверить с уведомлениями об уязвимостях.", "shortcuts.title": "Горячие клавиши", "shortcuts.close": "Закрыть горячие клавиши", "shortcuts.runAnalysis": "Запустить анализ", diff --git a/src/locales/tr/ui.json b/src/locales/tr/ui.json index 185475392..84a2b9e18 100644 --- a/src/locales/tr/ui.json +++ b/src/locales/tr/ui.json @@ -1014,7 +1014,7 @@ "firstRun.advisory.notStarted": "Güvenlik açığı kontrolü birkaç dakika içinde kendiliğinden başlar. Bulgular Preemption'da görünür.", "firstRun.advisory.stale": "4DA'nın son karşılaştırdığı güvenlik açığı bildirimleri üç günden eski. Kendiliğinden yeniden kontrol eder; durum Preemption'da görünür.", "firstRun.advisory.failed": "Güvenlik açığı kontrolü tamamlanmadı. 4DA kendiliğinden yeniden dener; durum Preemption'da görünür.", - "firstRun.advisory.uncovered": "Bunların hiçbiri henüz 4DA'nın güvenlik açığı bildirimleriyle karşılaştırabildiği bir ekosistemde değil.", + "firstRun.advisory.uncovered": "4DA bunlar için henüz kurulu sürüm okumadı (kilit dosyası olmayan bir manifest ya da okumadığı bir biçim), bu yüzden hiçbiri güvenlik açığı duyurularına karşı denetlenemedi.", "firstRun.skipToResults": "Sonuçlara Atla", "firstRun.sourcesScanned": "kaynak", "firstRun.stackInsight": "Yığın İçgörüsü", @@ -1372,8 +1372,8 @@ "preemption.advisories.failed.body": "4DA yayımlanmış bildirimleri eşitleyemedi, bu yüzden bağımlılıklarının temiz olduğunu söyleyemez. Bir sonraki döngüde yeniden dener.", "preemption.advisories.stale.title": "Güvenlik bildirimi verileri güncel değil", "preemption.advisories.stale.body": "Son başarılı bildirim eşitlemesi {{age}} yapıldı. 4DA bu kadar eski verilerle yığınını temiz ilan etmez; bir sonraki döngüde yeniden dener.", - "preemption.advisories.uncovered.title": "Bu bağımlılıkları kapsayan bir güvenlik bildirimi veritabanı yok", - "preemption.advisories.uncovered.body": "4DA'nın okuduğu bağımlılıkların hiçbiri, yayımlanmış bildirimlerle karşılaştırabildiği bir ekosistemde değil; bu yüzden temiz ilan edebileceği bir şey yok.", + "preemption.advisories.uncovered.title": "Denetlenecek kurulu sürüm yok", + "preemption.advisories.uncovered.body": "4DA bağımlılıklar buldu ancak kurulu sürümlerini okumadı: bunlar kilit dosyası olmayan bir manifestten ya da 4DA'nın okumadığı bir biçimden (Maven, Gradle, NuGet…) geliyor. Yayımlanmış duyurulara karşı hiçbir şey denetlenemedi, bu yüzden hiçbir şey temiz sayılamaz.", "preemption.advisories.notStarted.title": "Güvenlik açığı verileri henüz indirilmedi", "preemption.advisories.notStarted.body": "4DA bağımlılıklarınızı okudu ancak henüz yayımlanmış güvenlik bildirimleriyle karşılaştırmadı, bu yüzden temiz diyemez. Kontrol birkaç dakika içinde kendiliğinden başlar ya da şimdi çalıştırabilirsiniz.", "preemption.advisories.checkNow": "Şimdi kontrol et", diff --git a/src/locales/zh/ui.json b/src/locales/zh/ui.json index 3fe8feac4..ada3baf95 100644 --- a/src/locales/zh/ui.json +++ b/src/locales/zh/ui.json @@ -419,8 +419,8 @@ "preemption.advisories.failed.body": "4DA 无法同步已发布的公告,因此无法判断你的依赖是否安全。它会在下一个周期重试。", "preemption.advisories.stale.title": "公告数据已过期", "preemption.advisories.stale.body": "上一次成功同步公告是在 {{age}}。基于这么旧的数据,4DA 不会判定你的技术栈安全;它会在下一个周期重试。", - "preemption.advisories.uncovered.title": "没有公告数据库覆盖这些依赖", - "preemption.advisories.uncovered.body": "4DA 读取到的依赖都不属于它能与已发布公告比对的生态系统,因此没有可以判定为安全的内容。", + "preemption.advisories.uncovered.title": "没有可检查的已安装版本", + "preemption.advisories.uncovered.body": "4DA 找到了依赖项,但没有读取到它们的已安装版本:它们来自没有锁文件的清单,或来自 4DA 不读取的格式(Maven、Gradle、NuGet 等)。没有任何内容能与已发布的安全公告比对,因此无法认定任何内容是安全的。", "preemption.advisories.notStarted.title": "漏洞数据尚未下载", "preemption.advisories.notStarted.body": "4DA 已读取你的依赖项,但尚未与已发布的安全公告比对,因此无法判定它们是安全的。检查会在几分钟内自动开始,你也可以现在运行。", "preemption.advisories.checkNow": "立即检查", @@ -1227,7 +1227,7 @@ "firstRun.advisory.notStarted": "漏洞检查会在几分钟内自动开始。结果会显示在 Preemption 中。", "firstRun.advisory.stale": "4DA 上次比对所用的漏洞公告已超过三天。它会自动重新检查;状态显示在 Preemption 中。", "firstRun.advisory.failed": "漏洞检查未完成。4DA 会自动重试;状态显示在 Preemption 中。", - "firstRun.advisory.uncovered": "它们目前都不属于 4DA 能与漏洞公告比对的生态系统。", + "firstRun.advisory.uncovered": "4DA 尚未读取到这些依赖的已安装版本(没有锁文件的清单,或它不读取的格式),因此都未能与漏洞公告比对。", "shortcuts.title": "键盘快捷键", "shortcuts.close": "关闭键盘快捷键", "shortcuts.runAnalysis": "运行分析",